diff options
Diffstat (limited to 'sound/soc/fsl')
45 files changed, 15929 insertions, 0 deletions
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig new file mode 100644 index 000000000..19c302b0d --- /dev/null +++ b/sound/soc/fsl/Kconfig @@ -0,0 +1,299 @@ +menu "SoC Audio for Freescale CPUs" + +comment "Common SoC Audio options for Freescale CPUs:" + +config SND_SOC_FSL_ASRC + tristate "Asynchronous Sample Rate Converter (ASRC) module support" + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Asynchronous Sample Rate Converter (ASRC) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_SAI + tristate "Synchronous Audio Interface (SAI) module support" + select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Synchronous Audio Interface (SAI) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_SSI + tristate "Synchronous Serial Interface module (SSI) support" + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC) + select REGMAP_MMIO + help + Say Y if you want to add Synchronous Serial Interface (SSI) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_SPDIF + tristate "Sony/Philips Digital Interface (S/PDIF) module support" + select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC) + help + Say Y if you want to add Sony/Philips Digital Interface (SPDIF) + support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_ESAI + tristate "Enhanced Serial Audio Interface (ESAI) module support" + select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + help + Say Y if you want to add Enhanced Synchronous Audio Interface + (ESAI) support for the Freescale CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_UTILS + tristate + +config SND_SOC_IMX_PCM_DMA + tristate + select SND_SOC_GENERIC_DMAENGINE_PCM + +config SND_SOC_IMX_AUDMUX + tristate "Digital Audio Mux module support" + help + Say Y if you want to add Digital Audio Mux (AUDMUX) support + for the ARM i.MX CPUs. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_POWERPC_SOC + tristate "SoC Audio for Freescale PowerPC CPUs" + depends on FSL_SOC || PPC_MPC52xx + help + Say Y or M if you want to add support for codecs attached to + the PowerPC CPUs. + +config SND_IMX_SOC + tristate "SoC Audio for Freescale i.MX CPUs" + depends on ARCH_MXC || COMPILE_TEST + help + Say Y or M if you want to add support for codecs attached to + the i.MX CPUs. + +if SND_POWERPC_SOC + +config SND_MPC52xx_DMA + tristate + +config SND_SOC_POWERPC_DMA + tristate + +comment "SoC Audio support for Freescale PPC boards:" + +config SND_SOC_MPC8610_HPCD + tristate "ALSA SoC support for the Freescale MPC8610 HPCD board" + # I2C is necessary for the CS4270 driver + depends on MPC8610_HPCD && I2C + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_POWERPC_DMA + select SND_SOC_CS4270 + select SND_SOC_CS4270_VD33_ERRATA + default y if MPC8610_HPCD + help + Say Y if you want to enable audio on the Freescale MPC8610 HPCD. + +config SND_SOC_P1022_DS + tristate "ALSA SoC support for the Freescale P1022 DS board" + # I2C is necessary for the WM8776 driver + depends on P1022_DS && I2C + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_POWERPC_DMA + select SND_SOC_WM8776 + default y if P1022_DS + help + Say Y if you want to enable audio on the Freescale P1022 DS board. + This will also include the Wolfson Microelectronics WM8776 codec + driver. + +config SND_SOC_P1022_RDK + tristate "ALSA SoC support for the Freescale / iVeia P1022 RDK board" + # I2C is necessary for the WM8960 driver + depends on P1022_RDK && I2C + select SND_SOC_FSL_SSI + select SND_SOC_FSL_UTILS + select SND_SOC_POWERPC_DMA + select SND_SOC_WM8960 + default y if P1022_RDK + help + Say Y if you want to enable audio on the Freescale / iVeia + P1022 RDK board. This will also include the Wolfson + Microelectronics WM8960 codec driver. + +config SND_SOC_MPC5200_I2S + tristate "Freescale MPC5200 PSC in I2S mode driver" + depends on PPC_MPC52xx && PPC_BESTCOMM + select SND_MPC52xx_DMA + select PPC_BESTCOMM_GEN_BD + help + Say Y here to support the MPC5200 PSCs in I2S mode. + +config SND_SOC_MPC5200_AC97 + tristate "Freescale MPC5200 PSC in AC97 mode driver" + depends on PPC_MPC52xx && PPC_BESTCOMM + select SND_SOC_AC97_BUS + select SND_MPC52xx_DMA + select PPC_BESTCOMM_GEN_BD + help + Say Y here to support the MPC5200 PSCs in AC97 mode. + +config SND_MPC52xx_SOC_PCM030 + tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" + depends on PPC_MPC5200_SIMPLE + select SND_SOC_MPC5200_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for sound on the Phytec pcm030 + baseboard. + +config SND_MPC52xx_SOC_EFIKA + tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" + depends on PPC_EFIKA + select SND_SOC_MPC5200_AC97 + select SND_SOC_STAC9766 + help + Say Y if you want to add support for sound on the Efika. + +endif # SND_POWERPC_SOC + +if SND_IMX_SOC + +config SND_SOC_IMX_SSI + tristate + select SND_SOC_FSL_UTILS + +config SND_SOC_IMX_PCM_FIQ + tristate + select FIQ + +comment "SoC Audio support for Freescale i.MX boards:" + +config SND_MXC_SOC_WM1133_EV1 + tristate "Audio on the i.MX31ADS with WM1133-EV1 fitted" + depends on MACH_MX31ADS_WM1133_EV1 + select SND_SOC_WM8350 + select SND_SOC_IMX_PCM_FIQ + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Enable support for audio on the i.MX31ADS with the WM1133-EV1 + PMIC board with WM8835x fitted. + +config SND_SOC_MX27VIS_AIC32X4 + tristate "SoC audio support for Visstrim M10 boards" + depends on MACH_IMX27_VISSTRIM_M10 && I2C + select SND_SOC_TLV320AIC32X4 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Say Y if you want to add support for SoC audio on Visstrim SM10 + board with TLV320AIC32X4 codec. + +config SND_SOC_PHYCORE_AC97 + tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards" + depends on MACH_PCM043 || MACH_PCA100 + select SND_SOC_AC97_BUS + select SND_SOC_WM9712 + select SND_SOC_IMX_PCM_FIQ + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + help + Say Y if you want to add support for SoC audio on Phytec phyCORE + and phyCARD boards in AC97 mode + +config SND_SOC_EUKREA_TLV320 + tristate "Eukrea TLV320" + depends on ARCH_MXC && I2C + select SND_SOC_TLV320AIC23_I2C + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_SSI + select SND_SOC_FSL_SSI + select SND_SOC_IMX_PCM_DMA + help + Enable I2S based access to the TLV320AIC23B codec attached + to the SSI interface + +config SND_SOC_IMX_WM8962 + tristate "SoC Audio support for i.MX boards with wm8962" + depends on OF && I2C && INPUT + select SND_SOC_WM8962 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + help + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8962 codec. + +config SND_SOC_IMX_ES8328 + tristate "SoC Audio support for i.MX boards with the ES8328 codec" + depends on OF && (I2C || SPI) + select SND_SOC_ES8328_I2C if I2C + select SND_SOC_ES8328_SPI if SPI_MASTER + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + help + Say Y if you want to add support for the ES8328 audio codec connected + via SSI/I2S over either SPI or I2C. + +config SND_SOC_IMX_SGTL5000 + tristate "SoC Audio support for i.MX boards with sgtl5000" + depends on OF && I2C + select SND_SOC_SGTL5000 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + help + Say Y if you want to add support for SoC audio on an i.MX board with + a sgtl5000 codec. + +config SND_SOC_IMX_SPDIF + tristate "SoC Audio support for i.MX boards with S/PDIF" + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SPDIF + help + SoC Audio support for i.MX boards with S/PDIF + Say Y if you want to add support for SoC audio on an i.MX board with + a S/DPDIF. + +config SND_SOC_IMX_MC13783 + tristate "SoC Audio support for I.MX boards with mc13783" + depends on MFD_MC13XXX && ARM + select SND_SOC_IMX_SSI + select SND_SOC_IMX_AUDMUX + select SND_SOC_MC13783 + select SND_SOC_IMX_PCM_DMA + +config SND_SOC_FSL_ASOC_CARD + tristate "Generic ASoC Sound Card with ASRC support" + depends on OF && I2C + select SND_SOC_IMX_AUDMUX + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_ESAI + select SND_SOC_FSL_SAI + select SND_SOC_FSL_SSI + help + ALSA SoC Audio support with ASRC feature for Freescale SoCs that have + ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888 + and SGTL5000. + Say Y if you want to add support for Freescale Generic ASoC Sound Card. + +endif # SND_IMX_SOC + +endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile new file mode 100644 index 000000000..d28dc25c9 --- /dev/null +++ b/sound/soc/fsl/Makefile @@ -0,0 +1,69 @@ +# MPC8610 HPCD Machine Support +snd-soc-mpc8610-hpcd-objs := mpc8610_hpcd.o +obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += snd-soc-mpc8610-hpcd.o + +# P1022 DS Machine Support +snd-soc-p1022-ds-objs := p1022_ds.o +obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o + +# P1022 RDK Machine Support +snd-soc-p1022-rdk-objs := p1022_rdk.o +obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o + +# Freescale SSI/DMA/SAI/SPDIF Support +snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o +snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o +snd-soc-fsl-sai-objs := fsl_sai.o +snd-soc-fsl-ssi-y := fsl_ssi.o +snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o +snd-soc-fsl-spdif-objs := fsl_spdif.o +snd-soc-fsl-esai-objs := fsl_esai.o +snd-soc-fsl-utils-objs := fsl_utils.o +snd-soc-fsl-dma-objs := fsl_dma.o +obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o +obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o +obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o +obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o +obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o +obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o +obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o +obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o + +# MPC5200 Platform Support +obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o +obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o +obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o + +# MPC5200 Machine Support +obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o +obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o + +# i.MX Platform Support +snd-soc-imx-ssi-objs := imx-ssi.o +snd-soc-imx-audmux-objs := imx-audmux.o +obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o +obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o + +obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o +obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o + +# i.MX Machine Support +snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o +snd-soc-phycore-ac97-objs := phycore-ac97.o +snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o +snd-soc-wm1133-ev1-objs := wm1133-ev1.o +snd-soc-imx-es8328-objs := imx-es8328.o +snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o +snd-soc-imx-wm8962-objs := imx-wm8962.o +snd-soc-imx-spdif-objs := imx-spdif.o +snd-soc-imx-mc13783-objs := imx-mc13783.o + +obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o +obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o +obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o +obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o +obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o +obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o +obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o +obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o +obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c new file mode 100644 index 000000000..b2acd3293 --- /dev/null +++ b/sound/soc/fsl/efika-audio-fabric.c @@ -0,0 +1,91 @@ +/* + * Efika driver for the PSC of the Freescale MPC52xx + * configured as AC97 interface + * + * Copyright 2008 Jon Smirl, Digispeaker + * Author: Jon Smirl <jonsmirl@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "mpc5200_dma.h" +#include "mpc5200_psc_ac97.h" +#include "../codecs/stac9766.h" + +#define DRV_NAME "efika-audio-fabric" + +static struct snd_soc_dai_link efika_fabric_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 Analog", + .codec_dai_name = "stac9766-hifi-analog", + .cpu_dai_name = "mpc5200-psc-ac97.0", + .platform_name = "mpc5200-pcm-audio", + .codec_name = "stac9766-codec", +}, +{ + .name = "AC97", + .stream_name = "AC97 IEC958", + .codec_dai_name = "stac9766-hifi-IEC958", + .cpu_dai_name = "mpc5200-psc-ac97.1", + .platform_name = "mpc5200-pcm-audio", + .codec_name = "stac9766-codec", +}, +}; + +static struct snd_soc_card card = { + .name = "Efika", + .owner = THIS_MODULE, + .dai_link = efika_fabric_dai, + .num_links = ARRAY_SIZE(efika_fabric_dai), +}; + +static __init int efika_fabric_init(void) +{ + struct platform_device *pdev; + int rc; + + if (!of_machine_is_compatible("bplan,efika")) + return -ENODEV; + + pdev = platform_device_alloc("soc-audio", 1); + if (!pdev) { + pr_err("efika_fabric_init: platform_device_alloc() failed\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, &card); + + rc = platform_device_add(pdev); + if (rc) { + pr_err("efika_fabric_init: platform_device_add() failed\n"); + platform_device_put(pdev); + return -ENODEV; + } + return 0; +} + +module_init(efika_fabric_init); + + +MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>"); +MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c new file mode 100644 index 000000000..e1aa3834b --- /dev/null +++ b/sound/soc/fsl/eukrea-tlv320.c @@ -0,0 +1,235 @@ +/* + * eukrea-tlv320.c -- SoC audio for eukrea_cpuimxXX in I2S mode + * + * Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com> + * + * based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c + * which is Copyright 2009 Simtec Electronics + * and on sound/soc/imx/phycore-ac97.c which is + * 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/errno.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <asm/mach-types.h> + +#include "../codecs/tlv320aic23.h" +#include "imx-ssi.h" +#include "fsl_ssi.h" +#include "imx-audmux.h" + +#define CODEC_CLOCK 12000000 + +static int eukrea_tlv320_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 *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + CODEC_CLOCK, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(cpu_dai->dev, + "Failed to set the codec sysclk.\n"); + return ret; + } + + snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 0); + + ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, + SND_SOC_CLOCK_IN); + /* fsl_ssi lacks the set_sysclk ops */ + if (ret && ret != -EINVAL) { + dev_err(cpu_dai->dev, + "Can't set the IMX_SSP_SYS_CLK CPU system clock.\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops eukrea_tlv320_snd_ops = { + .hw_params = eukrea_tlv320_hw_params, +}; + +static struct snd_soc_dai_link eukrea_tlv320_dai = { + .name = "tlv320aic23", + .stream_name = "TLV320AIC23", + .codec_dai_name = "tlv320aic23-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &eukrea_tlv320_snd_ops, +}; + +static struct snd_soc_card eukrea_tlv320 = { + .owner = THIS_MODULE, + .dai_link = &eukrea_tlv320_dai, + .num_links = 1, +}; + +static int eukrea_tlv320_probe(struct platform_device *pdev) +{ + int ret; + int int_port = 0, ext_port; + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np = NULL, *codec_np = NULL; + + eukrea_tlv320.dev = &pdev->dev; + if (np) { + ret = snd_soc_of_parse_card_name(&eukrea_tlv320, + "eukrea,model"); + if (ret) { + dev_err(&pdev->dev, + "eukrea,model node missing or invalid.\n"); + goto err; + } + + ssi_np = of_parse_phandle(pdev->dev.of_node, + "ssi-controller", 0); + if (!ssi_np) { + dev_err(&pdev->dev, + "ssi-controller missing or invalid.\n"); + ret = -ENODEV; + goto err; + } + + codec_np = of_parse_phandle(ssi_np, "codec-handle", 0); + if (codec_np) + eukrea_tlv320_dai.codec_of_node = codec_np; + else + dev_err(&pdev->dev, "codec-handle node missing or invalid.\n"); + + ret = of_property_read_u32(np, "fsl,mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, + "fsl,mux-int-port node missing or invalid.\n"); + return ret; + } + ret = of_property_read_u32(np, "fsl,mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, + "fsl,mux-ext-port node missing or invalid.\n"); + return ret; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + + eukrea_tlv320_dai.cpu_of_node = ssi_np; + eukrea_tlv320_dai.platform_of_node = ssi_np; + } else { + eukrea_tlv320_dai.cpu_dai_name = "imx-ssi.0"; + eukrea_tlv320_dai.platform_name = "imx-ssi.0"; + eukrea_tlv320_dai.codec_name = "tlv320aic23-codec.0-001a"; + eukrea_tlv320.name = "cpuimx-audio"; + } + + if (machine_is_eukrea_cpuimx27() || + of_find_compatible_node(NULL, NULL, "fsl,imx21-audmux")) { + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_TFSDIR | + IMX_AUDMUX_V1_PCR_TCLKDIR | + IMX_AUDMUX_V1_PCR_RFSDIR | + IMX_AUDMUX_V1_PCR_RCLKDIR | + IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) | + IMX_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) + ); + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR3_SSI_PINS_4, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0) + ); + } else if (machine_is_eukrea_cpuimx25sd() || + machine_is_eukrea_cpuimx35sd() || + machine_is_eukrea_cpuimx51sd() || + of_find_compatible_node(NULL, NULL, "fsl,imx31-audmux")) { + if (!np) + ext_port = machine_is_eukrea_cpuimx25sd() ? + 4 : 3; + + imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port), + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port) + ); + imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port) + ); + } else { + if (np) { + /* The eukrea,asoc-tlv320 driver was explicitely + * requested (through the device tree). + */ + dev_err(&pdev->dev, + "Missing or invalid audmux DT node.\n"); + return -ENODEV; + } else { + /* Return happy. + * We might run on a totally different machine. + */ + return 0; + } + } + + ret = snd_soc_register_card(&eukrea_tlv320); +err: + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + of_node_put(ssi_np); + + return ret; +} + +static int eukrea_tlv320_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&eukrea_tlv320); + + return 0; +} + +static const struct of_device_id imx_tlv320_dt_ids[] = { + { .compatible = "eukrea,asoc-tlv320"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_tlv320_dt_ids); + +static struct platform_driver eukrea_tlv320_driver = { + .driver = { + .name = "eukrea_tlv320", + .of_match_table = imx_tlv320_dt_ids, + }, + .probe = eukrea_tlv320_probe, + .remove = eukrea_tlv320_remove, +}; + +module_platform_driver(eukrea_tlv320_driver); + +MODULE_AUTHOR("Eric Bénard <eric@eukrea.com>"); +MODULE_DESCRIPTION("CPUIMX ALSA SoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:eukrea_tlv320"); diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c new file mode 100644 index 000000000..de4388710 --- /dev/null +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -0,0 +1,597 @@ +/* + * Freescale Generic ASoC Sound Card driver with ASRC + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <nicoleotsuka@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "fsl_esai.h" +#include "fsl_sai.h" +#include "imx-audmux.h" + +#include "../codecs/sgtl5000.h" +#include "../codecs/wm8962.h" + +#define RX 0 +#define TX 1 + +/* Default DAI format without Master and Slave flag */ +#define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF) + +/** + * CODEC private data + * + * @mclk_freq: Clock rate of MCLK + * @mclk_id: MCLK (or main clock) id for set_sysclk() + * @fll_id: FLL (or secordary clock) id for set_sysclk() + * @pll_id: PLL id for set_pll() + */ +struct codec_priv { + unsigned long mclk_freq; + u32 mclk_id; + u32 fll_id; + u32 pll_id; +}; + +/** + * CPU private data + * + * @sysclk_freq[2]: SYSCLK rates for set_sysclk() + * @sysclk_dir[2]: SYSCLK directions for set_sysclk() + * @sysclk_id[2]: SYSCLK ids for set_sysclk() + * @slot_width: Slot width of each frame + * + * Note: [1] for tx and [0] for rx + */ +struct cpu_priv { + unsigned long sysclk_freq[2]; + u32 sysclk_dir[2]; + u32 sysclk_id[2]; + u32 slot_width; +}; + +/** + * Freescale Generic ASOC card private data + * + * @dai_link[3]: DAI link structure including normal one and DPCM link + * @pdev: platform device pointer + * @codec_priv: CODEC private data + * @cpu_priv: CPU private data + * @card: ASoC card structure + * @sample_rate: Current sample rate + * @sample_format: Current sample format + * @asrc_rate: ASRC sample rate used by Back-Ends + * @asrc_format: ASRC sample format used by Back-Ends + * @dai_fmt: DAI format between CPU and CODEC + * @name: Card name + */ + +struct fsl_asoc_card_priv { + struct snd_soc_dai_link dai_link[3]; + struct platform_device *pdev; + struct codec_priv codec_priv; + struct cpu_priv cpu_priv; + struct snd_soc_card card; + u32 sample_rate; + u32 sample_format; + u32 asrc_rate; + u32 asrc_format; + u32 dai_fmt; + char name[32]; +}; + +/** + * This dapm route map exsits for DPCM link only. + * The other routes shall go through Device Tree. + */ +static const struct snd_soc_dapm_route audio_map[] = { + {"CPU-Playback", NULL, "ASRC-Playback"}, + {"Playback", NULL, "CPU-Playback"}, + {"ASRC-Capture", NULL, "CPU-Capture"}, + {"CPU-Capture", NULL, "Capture"}, +}; + +/* Add all possible widgets into here without being redundant */ +static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_MIC("DMIC", NULL), +}; + +static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct cpu_priv *cpu_priv = &priv->cpu_priv; + struct device *dev = rtd->card->dev; + int ret; + + priv->sample_rate = params_rate(params); + priv->sample_format = params_format(params); + + /* + * If codec-dai is DAI Master and all configurations are already in the + * set_bias_level(), bypass the remaining settings in hw_params(). + * Note: (dai_fmt & CBM_CFM) includes CBM_CFM and CBM_CFS. + */ + if (priv->card.set_bias_level && priv->dai_fmt & SND_SOC_DAIFMT_CBM_CFM) + return 0; + + /* Specific configurations of DAIs starts from here */ + ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, cpu_priv->sysclk_id[tx], + cpu_priv->sysclk_freq[tx], + cpu_priv->sysclk_dir[tx]); + if (ret) { + dev_err(dev, "failed to set sysclk for cpu dai\n"); + return ret; + } + + if (cpu_priv->slot_width) { + ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2, + cpu_priv->slot_width); + if (ret) { + dev_err(dev, "failed to set TDM slot for cpu dai\n"); + return ret; + } + } + + return 0; +} + +static struct snd_soc_ops fsl_asoc_card_ops = { + .hw_params = fsl_asoc_card_hw_params, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); + struct snd_interval *rate; + struct snd_mask *mask; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = priv->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, priv->asrc_format); + + return 0; +} + +static struct snd_soc_dai_link fsl_asoc_card_dai[] = { + /* Default ASoC DAI Link*/ + { + .name = "HiFi", + .stream_name = "HiFi", + .ops = &fsl_asoc_card_ops, + }, + /* DPCM Link between Front-End and Back-End (Optional) */ + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .dpcm_playback = 1, + .dpcm_capture = 1, + .dynamic = 1, + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .platform_name = "snd-soc-dummy", + .be_hw_params_fixup = be_hw_params_fixup, + .ops = &fsl_asoc_card_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, +}; + +static int fsl_asoc_card_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + struct codec_priv *codec_priv = &priv->codec_priv; + struct device *dev = card->dev; + unsigned int pll_out; + int ret; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level != SND_SOC_BIAS_STANDBY) + break; + + if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE) + pll_out = priv->sample_rate * 384; + else + pll_out = priv->sample_rate * 256; + + ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, + codec_priv->mclk_id, + codec_priv->mclk_freq, pll_out); + if (ret) { + dev_err(dev, "failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->fll_id, + pll_out, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set SYSCLK: %d\n", ret); + return ret; + } + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level != SND_SOC_BIAS_PREPARE) + break; + + ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, + codec_priv->mclk_freq, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to switch away from FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, 0, 0, 0); + if (ret) { + dev_err(dev, "failed to stop FLL: %d\n", ret); + return ret; + } + break; + + default: + break; + } + + return 0; +} + +static int fsl_asoc_card_audmux_init(struct device_node *np, + struct fsl_asoc_card_priv *priv) +{ + struct device *dev = &priv->pdev->dev; + u32 int_ptcr = 0, ext_ptcr = 0; + int int_port, ext_port; + int ret; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(dev, "mux-int-port missing or invalid\n"); + return ret; + } + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(dev, "mux-ext-port missing or invalid\n"); + return ret; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the AUDMUX API expects it starts at 0. + */ + int_port--; + ext_port--; + + /* + * Use asynchronous mode (6 wires) for all cases. + * If only 4 wires are needed, just set SSI into + * synchronous mode and enable 4 PADs in IOMUX. + */ + switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) | + IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + int_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR; + ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) | + IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_TFSDIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_TFSDIR; + ext_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(int_port) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) | + IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) | + IMX_AUDMUX_V2_PTCR_TFSEL(int_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(int_port) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR; + break; + default: + return -EINVAL; + } + + /* Asynchronous mode can not be set along with RCLKDIR */ + ret = imx_audmux_v2_configure_port(int_port, 0, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(dev, "audmux internal port setup failed\n"); + return ret; + } + + ret = imx_audmux_v2_configure_port(int_port, int_ptcr, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(dev, "audmux internal port setup failed\n"); + return ret; + } + + ret = imx_audmux_v2_configure_port(ext_port, 0, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(dev, "audmux external port setup failed\n"); + return ret; + } + + ret = imx_audmux_v2_configure_port(ext_port, ext_ptcr, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(dev, "audmux external port setup failed\n"); + return ret; + } + + return 0; +} + +static int fsl_asoc_card_late_probe(struct snd_soc_card *card) +{ + struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + struct codec_priv *codec_priv = &priv->codec_priv; + struct device *dev = card->dev; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, + codec_priv->mclk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set sysclk in %s\n", __func__); + return ret; + } + + return 0; +} + +static int fsl_asoc_card_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np, *asrc_np; + struct device_node *np = pdev->dev.of_node; + struct platform_device *asrc_pdev = NULL; + struct platform_device *cpu_pdev; + struct fsl_asoc_card_priv *priv; + struct i2c_client *codec_dev; + struct clk *codec_clk; + u32 width; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + /* Give a chance to old DT binding */ + if (!cpu_np) + cpu_np = of_parse_phandle(np, "ssi-controller", 0); + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!cpu_np || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find CPU DAI device\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + asrc_np = of_parse_phandle(np, "audio-asrc", 0); + if (asrc_np) + asrc_pdev = of_find_device_by_node(asrc_np); + + /* Get the MCLK rate only, and leave it controlled by CODEC drivers */ + codec_clk = clk_get(&codec_dev->dev, NULL); + if (!IS_ERR(codec_clk)) { + priv->codec_priv.mclk_freq = clk_get_rate(codec_clk); + clk_put(codec_clk); + } + + /* Default sample rate and format, will be updated in hw_params() */ + priv->sample_rate = 44100; + priv->sample_format = SNDRV_PCM_FORMAT_S16_LE; + + /* Assign a default DAI format, and allow each card to overwrite it */ + priv->dai_fmt = DAI_FMT_BASE; + + /* Diversify the card configurations */ + if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) { + priv->card.set_bias_level = NULL; + priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv.mclk_freq; + priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv.mclk_freq; + priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT; + priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT; + priv->cpu_priv.slot_width = 32; + priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + } else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) { + priv->codec_priv.mclk_id = SGTL5000_SYSCLK; + priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) { + priv->card.set_bias_level = fsl_asoc_card_set_bias_level; + priv->codec_priv.mclk_id = WM8962_SYSCLK_MCLK; + priv->codec_priv.fll_id = WM8962_SYSCLK_FLL; + priv->codec_priv.pll_id = WM8962_FLL; + priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + } else { + dev_err(&pdev->dev, "unknown Device Tree compatible\n"); + return -EINVAL; + } + + /* Common settings for corresponding Freescale CPU DAI driver */ + if (strstr(cpu_np->name, "ssi")) { + /* Only SSI needs to configure AUDMUX */ + ret = fsl_asoc_card_audmux_init(np, priv); + if (ret) { + dev_err(&pdev->dev, "failed to init audmux\n"); + goto asrc_fail; + } + } else if (strstr(cpu_np->name, "esai")) { + priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL; + priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL; + } else if (strstr(cpu_np->name, "sai")) { + priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1; + priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; + } + + sprintf(priv->name, "%s-audio", codec_dev->name); + + /* Initialize sound card */ + priv->pdev = pdev; + priv->card.dev = &pdev->dev; + priv->card.name = priv->name; + priv->card.dai_link = priv->dai_link; + priv->card.dapm_routes = audio_map; + priv->card.late_probe = fsl_asoc_card_late_probe; + priv->card.num_dapm_routes = ARRAY_SIZE(audio_map); + priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets; + priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets); + + memcpy(priv->dai_link, fsl_asoc_card_dai, + sizeof(struct snd_soc_dai_link) * ARRAY_SIZE(priv->dai_link)); + + ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + goto asrc_fail; + } + + /* Normal DAI Link */ + priv->dai_link[0].cpu_of_node = cpu_np; + priv->dai_link[0].codec_of_node = codec_np; + priv->dai_link[0].codec_dai_name = codec_dev->name; + priv->dai_link[0].platform_of_node = cpu_np; + priv->dai_link[0].dai_fmt = priv->dai_fmt; + priv->card.num_links = 1; + + if (asrc_pdev) { + /* DPCM DAI Links only if ASRC exsits */ + priv->dai_link[1].cpu_of_node = asrc_np; + priv->dai_link[1].platform_of_node = asrc_np; + priv->dai_link[2].codec_dai_name = codec_dev->name; + priv->dai_link[2].codec_of_node = codec_np; + priv->dai_link[2].cpu_of_node = cpu_np; + priv->dai_link[2].dai_fmt = priv->dai_fmt; + priv->card.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto asrc_fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto asrc_fail; + } + + if (width == 24) + priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + /* Finish card registering */ + platform_set_drvdata(pdev, priv); + snd_soc_card_set_drvdata(&priv->card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, &priv->card); + if (ret) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + +asrc_fail: + of_node_put(asrc_np); +fail: + of_node_put(codec_np); + of_node_put(cpu_np); + + return ret; +} + +static const struct of_device_id fsl_asoc_card_dt_ids[] = { + { .compatible = "fsl,imx-audio-cs42888", }, + { .compatible = "fsl,imx-audio-sgtl5000", }, + { .compatible = "fsl,imx-audio-wm8962", }, + {} +}; + +static struct platform_driver fsl_asoc_card_driver = { + .probe = fsl_asoc_card_probe, + .driver = { + .name = "fsl-asoc-card", + .pm = &snd_soc_pm_ops, + .of_match_table = fsl_asoc_card_dt_ids, + }, +}; +module_platform_driver(fsl_asoc_card_driver); + +MODULE_DESCRIPTION("Freescale Generic ASoC Sound Card driver with ASRC"); +MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>"); +MODULE_ALIAS("platform:fsl-asoc-card"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c new file mode 100644 index 000000000..c068494ba --- /dev/null +++ b/sound/soc/fsl/fsl_asrc.c @@ -0,0 +1,1016 @@ +/* + * Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <nicoleotsuka@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_data/dma-imx.h> +#include <linux/pm_runtime.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_asrc.h" + +#define IDEAL_RATIO_DECIMAL_DEPTH 26 + +#define pair_err(fmt, ...) \ + dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) + +#define pair_dbg(fmt, ...) \ + dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__) + +/* Sample rates are aligned with that defined in pcm.h file */ +static const u8 process_option[][8][2] = { + /* 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 5512Hz */ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 8kHz */ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 11025Hz */ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 16kHz */ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 22050Hz */ + {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, /* 32kHz */ + {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 44.1kHz */ + {{0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 48kHz */ + {{1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, /* 64kHz */ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 88.2kHz */ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 96kHz */ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 176kHz */ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 192kHz */ +}; + +/* Corresponding to process_option */ +static int supported_input_rate[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, + 96000, 176400, 192000, +}; + +static int supported_asrc_rate[] = { + 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000, +}; + +/** + * The following tables map the relationship between asrc_inclk/asrc_outclk in + * fsl_asrc.h and the registers of ASRCSR + */ +static unsigned char input_clk_map_imx35[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, +}; + +static unsigned char output_clk_map_imx35[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, +}; + +/* i.MX53 uses the same map for input and output */ +static unsigned char input_clk_map_imx53[] = { +/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ + 0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd, +}; + +static unsigned char output_clk_map_imx53[] = { +/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */ + 0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd, +}; + +static unsigned char *clk_map[2]; + +/** + * Request ASRC pair + * + * It assigns pair by the order of A->C->B because allocation of pair B, + * within range [ANCA, ANCA+ANCB-1], depends on the channels of pair A + * while pair A and pair C are comparatively independent. + */ +static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair) +{ + enum asrc_pair_index index = ASRC_INVALID_PAIR; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct device *dev = &asrc_priv->pdev->dev; + unsigned long lock_flags; + int i, ret = 0; + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) { + if (asrc_priv->pair[i] != NULL) + continue; + + index = i; + + if (i != ASRC_PAIR_B) + break; + } + + if (index == ASRC_INVALID_PAIR) { + dev_err(dev, "all pairs are busy now\n"); + ret = -EBUSY; + } else if (asrc_priv->channel_avail < channels) { + dev_err(dev, "can't afford required channels: %d\n", channels); + ret = -EINVAL; + } else { + asrc_priv->channel_avail -= channels; + asrc_priv->pair[index] = pair; + pair->channels = channels; + pair->index = index; + } + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + + return ret; +} + +/** + * Release ASRC pair + * + * It clears the resource from asrc_priv and releases the occupied channels. + */ +static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + unsigned long lock_flags; + + /* Make sure the pair is disabled */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + asrc_priv->channel_avail += pair->channels; + asrc_priv->pair[index] = NULL; + pair->error = 0; + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); +} + +/** + * Configure input and output thresholds + */ +static void fsl_asrc_set_watermarks(struct fsl_asrc_pair *pair, u32 in, u32 out) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index), + ASRMCRi_EXTTHRSHi_MASK | + ASRMCRi_INFIFO_THRESHOLD_MASK | + ASRMCRi_OUTFIFO_THRESHOLD_MASK, + ASRMCRi_EXTTHRSHi | + ASRMCRi_INFIFO_THRESHOLD(in) | + ASRMCRi_OUTFIFO_THRESHOLD(out)); +} + +/** + * Calculate the total divisor between asrck clock rate and sample rate + * + * It follows the formula clk_rate = samplerate * (2 ^ prescaler) * divider + */ +static u32 fsl_asrc_cal_asrck_divisor(struct fsl_asrc_pair *pair, u32 div) +{ + u32 ps; + + /* Calculate the divisors: prescaler [2^0, 2^7], divder [1, 8] */ + for (ps = 0; div > 8; ps++) + div >>= 1; + + return ((div - 1) << ASRCDRi_AxCPi_WIDTH) | ps; +} + +/** + * Calculate and set the ratio for Ideal Ratio mode only + * + * The ratio is a 32-bit fixed point value with 26 fractional bits. + */ +static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair, + int inrate, int outrate) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + unsigned long ratio; + int i; + + if (!outrate) { + pair_err("output rate should not be zero\n"); + return -EINVAL; + } + + /* Calculate the intergal part of the ratio */ + ratio = (inrate / outrate) << IDEAL_RATIO_DECIMAL_DEPTH; + + /* ... and then the 26 depth decimal part */ + inrate %= outrate; + + for (i = 1; i <= IDEAL_RATIO_DECIMAL_DEPTH; i++) { + inrate <<= 1; + + if (inrate < outrate) + continue; + + ratio |= 1 << (IDEAL_RATIO_DECIMAL_DEPTH - i); + inrate -= outrate; + + if (!inrate) + break; + } + + regmap_write(asrc_priv->regmap, REG_ASRIDRL(index), ratio); + regmap_write(asrc_priv->regmap, REG_ASRIDRH(index), ratio >> 24); + + return 0; +} + +/** + * Configure the assigned ASRC pair + * + * It configures those ASRC registers according to a configuration instance + * of struct asrc_config which includes in/output sample rate, width, channel + * and clock settings. + */ +static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair) +{ + struct asrc_config *config = pair->config; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + u32 inrate, outrate, indiv, outdiv; + u32 clk_index[2], div[2]; + int in, out, channels; + struct clk *clk; + bool ideal; + + if (!config) { + pair_err("invalid pair config\n"); + return -EINVAL; + } + + /* Validate channels */ + if (config->channel_num < 1 || config->channel_num > 10) { + pair_err("does not support %d channels\n", config->channel_num); + return -EINVAL; + } + + /* Validate output width */ + if (config->output_word_width == ASRC_WIDTH_8_BIT) { + pair_err("does not support 8bit width output\n"); + return -EINVAL; + } + + inrate = config->input_sample_rate; + outrate = config->output_sample_rate; + ideal = config->inclk == INCLK_NONE; + + /* Validate input and output sample rates */ + for (in = 0; in < ARRAY_SIZE(supported_input_rate); in++) + if (inrate == supported_input_rate[in]) + break; + + if (in == ARRAY_SIZE(supported_input_rate)) { + pair_err("unsupported input sample rate: %dHz\n", inrate); + return -EINVAL; + } + + for (out = 0; out < ARRAY_SIZE(supported_asrc_rate); out++) + if (outrate == supported_asrc_rate[out]) + break; + + if (out == ARRAY_SIZE(supported_asrc_rate)) { + pair_err("unsupported output sample rate: %dHz\n", outrate); + return -EINVAL; + } + + /* Validate input and output clock sources */ + clk_index[IN] = clk_map[IN][config->inclk]; + clk_index[OUT] = clk_map[OUT][config->outclk]; + + /* We only have output clock for ideal ratio mode */ + clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]]; + + div[IN] = clk_get_rate(clk) / inrate; + if (div[IN] == 0) { + pair_err("failed to support input sample rate %dHz by asrck_%x\n", + inrate, clk_index[ideal ? OUT : IN]); + return -EINVAL; + } + + clk = asrc_priv->asrck_clk[clk_index[OUT]]; + + /* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */ + if (ideal) + div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE; + else + div[OUT] = clk_get_rate(clk) / outrate; + + if (div[OUT] == 0) { + pair_err("failed to support output sample rate %dHz by asrck_%x\n", + outrate, clk_index[OUT]); + return -EINVAL; + } + + /* Set the channel number */ + channels = config->channel_num; + + if (asrc_priv->channel_bits < 4) + channels /= 2; + + /* Update channels for current pair */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCNCR, + ASRCNCR_ANCi_MASK(index, asrc_priv->channel_bits), + ASRCNCR_ANCi(index, channels, asrc_priv->channel_bits)); + + /* Default setting: Automatic selection for processing mode */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index)); + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_USRi_MASK(index), 0); + + /* Set the input and output clock sources */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCSR, + ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index), + ASRCSR_AICS(index, clk_index[IN]) | + ASRCSR_AOCS(index, clk_index[OUT])); + + /* Calculate the input clock divisors */ + indiv = fsl_asrc_cal_asrck_divisor(pair, div[IN]); + outdiv = fsl_asrc_cal_asrck_divisor(pair, div[OUT]); + + /* Suppose indiv and outdiv includes prescaler, so add its MASK too */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCDR(index), + ASRCDRi_AOCPi_MASK(index) | ASRCDRi_AICPi_MASK(index) | + ASRCDRi_AOCDi_MASK(index) | ASRCDRi_AICDi_MASK(index), + ASRCDRi_AOCP(index, outdiv) | ASRCDRi_AICP(index, indiv)); + + /* Implement word_width configurations */ + regmap_update_bits(asrc_priv->regmap, REG_ASRMCR1(index), + ASRMCR1i_OW16_MASK | ASRMCR1i_IWD_MASK, + ASRMCR1i_OW16(config->output_word_width) | + ASRMCR1i_IWD(config->input_word_width)); + + /* Enable BUFFER STALL */ + regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index), + ASRMCRi_BUFSTALLi_MASK, ASRMCRi_BUFSTALLi); + + /* Set default thresholds for input and output FIFO */ + fsl_asrc_set_watermarks(pair, ASRC_INPUTFIFO_THRESHOLD, + ASRC_INPUTFIFO_THRESHOLD); + + /* Configure the followings only for Ideal Ratio mode */ + if (!ideal) + return 0; + + /* Clear ASTSx bit to use Ideal Ratio mode */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ATSi_MASK(index), 0); + + /* Enable Ideal Ratio mode */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_IDRi_MASK(index) | ASRCTR_USRi_MASK(index), + ASRCTR_IDR(index) | ASRCTR_USR(index)); + + /* Apply configurations for pre- and post-processing */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCFG, + ASRCFG_PREMODi_MASK(index) | ASRCFG_POSTMODi_MASK(index), + ASRCFG_PREMOD(index, process_option[in][out][0]) | + ASRCFG_POSTMOD(index, process_option[in][out][1])); + + return fsl_asrc_set_ideal_ratio(pair, inrate, outrate); +} + +/** + * Start the assigned ASRC pair + * + * It enables the assigned pair and makes it stopped at the stall level. + */ +static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + int reg, retry = 10, i; + + /* Enable the current pair */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), ASRCTR_ASRCE(index)); + + /* Wait for status of initialization */ + do { + udelay(5); + regmap_read(asrc_priv->regmap, REG_ASRCFG, ®); + reg &= ASRCFG_INIRQi_MASK(index); + } while (!reg && --retry); + + /* Make the input fifo to ASRC STALL level */ + regmap_read(asrc_priv->regmap, REG_ASRCNCR, ®); + for (i = 0; i < pair->channels * 4; i++) + regmap_write(asrc_priv->regmap, REG_ASRDI(index), 0); + + /* Enable overload interrupt */ + regmap_write(asrc_priv->regmap, REG_ASRIER, ASRIER_AOLIE); +} + +/** + * Stop the assigned ASRC pair + */ +static void fsl_asrc_stop_pair(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + /* Stop the current pair */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_MASK(index), 0); +} + +/** + * Get DMA channel according to the pair and direction. + */ +struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + char name[4]; + + sprintf(name, "%cx%c", dir == IN ? 'r' : 't', index + 'a'); + + return dma_request_slave_channel(&asrc_priv->pdev->dev, name); +} +EXPORT_SYMBOL_GPL(fsl_asrc_get_dma_channel); + +static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai); + int width = snd_pcm_format_width(params_format(params)); + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + struct asrc_config config; + int word_width, ret; + + ret = fsl_asrc_request_pair(channels, pair); + if (ret) { + dev_err(dai->dev, "fail to request asrc pair\n"); + return ret; + } + + pair->config = &config; + + if (width == 16) + width = ASRC_WIDTH_16_BIT; + else + width = ASRC_WIDTH_24_BIT; + + if (asrc_priv->asrc_width == 16) + word_width = ASRC_WIDTH_16_BIT; + else + word_width = ASRC_WIDTH_24_BIT; + + config.pair = pair->index; + config.channel_num = channels; + config.inclk = INCLK_NONE; + config.outclk = OUTCLK_ASRCK1_CLK; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + config.input_word_width = width; + config.output_word_width = word_width; + config.input_sample_rate = rate; + config.output_sample_rate = asrc_priv->asrc_rate; + } else { + config.input_word_width = word_width; + config.output_word_width = width; + config.input_sample_rate = asrc_priv->asrc_rate; + config.output_sample_rate = rate; + } + + ret = fsl_asrc_config_pair(pair); + if (ret) { + dev_err(dai->dev, "fail to config asrc pair\n"); + return ret; + } + + return 0; +} + +static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + if (pair) + fsl_asrc_release_pair(pair); + + return 0; +} + +static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + fsl_asrc_start_pair(pair); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fsl_asrc_stop_pair(pair); + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops fsl_asrc_dai_ops = { + .hw_params = fsl_asrc_dai_hw_params, + .hw_free = fsl_asrc_dai_hw_free, + .trigger = fsl_asrc_dai_trigger, +}; + +static int fsl_asrc_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &asrc_priv->dma_params_tx, + &asrc_priv->dma_params_rx); + + return 0; +} + +#define FSL_ASRC_RATES SNDRV_PCM_RATE_8000_192000 +#define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE) + +static struct snd_soc_dai_driver fsl_asrc_dai = { + .probe = fsl_asrc_dai_probe, + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 10, + .rates = FSL_ASRC_RATES, + .formats = FSL_ASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 10, + .rates = FSL_ASRC_RATES, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_asrc_component = { + .name = "fsl-asrc-dai", +}; + +static bool fsl_asrc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ASRCTR: + case REG_ASRIER: + case REG_ASRCNCR: + case REG_ASRCFG: + case REG_ASRCSR: + case REG_ASRCDR1: + case REG_ASRCDR2: + case REG_ASRSTR: + case REG_ASRPM1: + case REG_ASRPM2: + case REG_ASRPM3: + case REG_ASRPM4: + case REG_ASRPM5: + case REG_ASRTFR1: + case REG_ASRCCR: + case REG_ASRDOA: + case REG_ASRDOB: + case REG_ASRDOC: + case REG_ASRIDRHA: + case REG_ASRIDRLA: + case REG_ASRIDRHB: + case REG_ASRIDRLB: + case REG_ASRIDRHC: + case REG_ASRIDRLC: + case REG_ASR76K: + case REG_ASR56K: + case REG_ASRMCRA: + case REG_ASRFSTA: + case REG_ASRMCRB: + case REG_ASRFSTB: + case REG_ASRMCRC: + case REG_ASRFSTC: + case REG_ASRMCR1A: + case REG_ASRMCR1B: + case REG_ASRMCR1C: + return true; + default: + return false; + } +} + +static bool fsl_asrc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ASRSTR: + case REG_ASRDIA: + case REG_ASRDIB: + case REG_ASRDIC: + case REG_ASRDOA: + case REG_ASRDOB: + case REG_ASRDOC: + case REG_ASRFSTA: + case REG_ASRFSTB: + case REG_ASRFSTC: + case REG_ASRCFG: + return true; + default: + return false; + } +} + +static bool fsl_asrc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ASRCTR: + case REG_ASRIER: + case REG_ASRCNCR: + case REG_ASRCFG: + case REG_ASRCSR: + case REG_ASRCDR1: + case REG_ASRCDR2: + case REG_ASRSTR: + case REG_ASRPM1: + case REG_ASRPM2: + case REG_ASRPM3: + case REG_ASRPM4: + case REG_ASRPM5: + case REG_ASRTFR1: + case REG_ASRCCR: + case REG_ASRDIA: + case REG_ASRDIB: + case REG_ASRDIC: + case REG_ASRIDRHA: + case REG_ASRIDRLA: + case REG_ASRIDRHB: + case REG_ASRIDRLB: + case REG_ASRIDRHC: + case REG_ASRIDRLC: + case REG_ASR76K: + case REG_ASR56K: + case REG_ASRMCRA: + case REG_ASRMCRB: + case REG_ASRMCRC: + case REG_ASRMCR1A: + case REG_ASRMCR1B: + case REG_ASRMCR1C: + return true; + default: + return false; + } +} + +static struct reg_default fsl_asrc_reg[] = { + { REG_ASRCTR, 0x0000 }, { REG_ASRIER, 0x0000 }, + { REG_ASRCNCR, 0x0000 }, { REG_ASRCFG, 0x0000 }, + { REG_ASRCSR, 0x0000 }, { REG_ASRCDR1, 0x0000 }, + { REG_ASRCDR2, 0x0000 }, { REG_ASRSTR, 0x0000 }, + { REG_ASRRA, 0x0000 }, { REG_ASRRB, 0x0000 }, + { REG_ASRRC, 0x0000 }, { REG_ASRPM1, 0x0000 }, + { REG_ASRPM2, 0x0000 }, { REG_ASRPM3, 0x0000 }, + { REG_ASRPM4, 0x0000 }, { REG_ASRPM5, 0x0000 }, + { REG_ASRTFR1, 0x0000 }, { REG_ASRCCR, 0x0000 }, + { REG_ASRDIA, 0x0000 }, { REG_ASRDOA, 0x0000 }, + { REG_ASRDIB, 0x0000 }, { REG_ASRDOB, 0x0000 }, + { REG_ASRDIC, 0x0000 }, { REG_ASRDOC, 0x0000 }, + { REG_ASRIDRHA, 0x0000 }, { REG_ASRIDRLA, 0x0000 }, + { REG_ASRIDRHB, 0x0000 }, { REG_ASRIDRLB, 0x0000 }, + { REG_ASRIDRHC, 0x0000 }, { REG_ASRIDRLC, 0x0000 }, + { REG_ASR76K, 0x0A47 }, { REG_ASR56K, 0x0DF3 }, + { REG_ASRMCRA, 0x0000 }, { REG_ASRFSTA, 0x0000 }, + { REG_ASRMCRB, 0x0000 }, { REG_ASRFSTB, 0x0000 }, + { REG_ASRMCRC, 0x0000 }, { REG_ASRFSTC, 0x0000 }, + { REG_ASRMCR1A, 0x0000 }, { REG_ASRMCR1B, 0x0000 }, + { REG_ASRMCR1C, 0x0000 }, +}; + +static const struct regmap_config fsl_asrc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_ASRMCR1C, + .reg_defaults = fsl_asrc_reg, + .num_reg_defaults = ARRAY_SIZE(fsl_asrc_reg), + .readable_reg = fsl_asrc_readable_reg, + .volatile_reg = fsl_asrc_volatile_reg, + .writeable_reg = fsl_asrc_writeable_reg, + .cache_type = REGCACHE_RBTREE, +}; + +/** + * Initialize ASRC registers with a default configurations + */ +static int fsl_asrc_init(struct fsl_asrc *asrc_priv) +{ + /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ + regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN); + + /* Disable interrupt by default */ + regmap_write(asrc_priv->regmap, REG_ASRIER, 0x0); + + /* Apply recommended settings for parameters from Reference Manual */ + regmap_write(asrc_priv->regmap, REG_ASRPM1, 0x7fffff); + regmap_write(asrc_priv->regmap, REG_ASRPM2, 0x255555); + regmap_write(asrc_priv->regmap, REG_ASRPM3, 0xff7280); + regmap_write(asrc_priv->regmap, REG_ASRPM4, 0xff7280); + regmap_write(asrc_priv->regmap, REG_ASRPM5, 0xff7280); + + /* Base address for task queue FIFO. Set to 0x7C */ + regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1, + ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc)); + + /* Set the processing clock for 76KHz to 133M */ + regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6); + + /* Set the processing clock for 56KHz to 133M */ + return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947); +} + +/** + * Interrupt handler for ASRC + */ +static irqreturn_t fsl_asrc_isr(int irq, void *dev_id) +{ + struct fsl_asrc *asrc_priv = (struct fsl_asrc *)dev_id; + struct device *dev = &asrc_priv->pdev->dev; + enum asrc_pair_index index; + u32 status; + + regmap_read(asrc_priv->regmap, REG_ASRSTR, &status); + + /* Clean overload error */ + regmap_write(asrc_priv->regmap, REG_ASRSTR, ASRSTR_AOLE); + + /* + * We here use dev_dbg() for all exceptions because ASRC itself does + * not care if FIFO overflowed or underrun while a warning in the + * interrupt would result a ridged conversion. + */ + for (index = ASRC_PAIR_A; index < ASRC_PAIR_MAX_NUM; index++) { + if (!asrc_priv->pair[index]) + continue; + + if (status & ASRSTR_ATQOL) { + asrc_priv->pair[index]->error |= ASRC_TASK_Q_OVERLOAD; + dev_dbg(dev, "ASRC Task Queue FIFO overload\n"); + } + + if (status & ASRSTR_AOOL(index)) { + asrc_priv->pair[index]->error |= ASRC_OUTPUT_TASK_OVERLOAD; + pair_dbg("Output Task Overload\n"); + } + + if (status & ASRSTR_AIOL(index)) { + asrc_priv->pair[index]->error |= ASRC_INPUT_TASK_OVERLOAD; + pair_dbg("Input Task Overload\n"); + } + + if (status & ASRSTR_AODO(index)) { + asrc_priv->pair[index]->error |= ASRC_OUTPUT_BUFFER_OVERFLOW; + pair_dbg("Output Data Buffer has overflowed\n"); + } + + if (status & ASRSTR_AIDU(index)) { + asrc_priv->pair[index]->error |= ASRC_INPUT_BUFFER_UNDERRUN; + pair_dbg("Input Data Buffer has underflowed\n"); + } + } + + return IRQ_HANDLED; +} + +static int fsl_asrc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_asrc *asrc_priv; + struct resource *res; + void __iomem *regs; + int irq, ret, i; + char tmp[16]; + + asrc_priv = devm_kzalloc(&pdev->dev, sizeof(*asrc_priv), GFP_KERNEL); + if (!asrc_priv) + return -ENOMEM; + + asrc_priv->pdev = pdev; + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + asrc_priv->paddr = res->start; + + asrc_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs, + &fsl_asrc_regmap_config); + if (IS_ERR(asrc_priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap\n"); + return PTR_ERR(asrc_priv->regmap); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, fsl_asrc_isr, 0, + dev_name(&pdev->dev), asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret); + return ret; + } + + asrc_priv->mem_clk = devm_clk_get(&pdev->dev, "mem"); + if (IS_ERR(asrc_priv->mem_clk)) { + dev_err(&pdev->dev, "failed to get mem clock\n"); + return PTR_ERR(asrc_priv->mem_clk); + } + + asrc_priv->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(asrc_priv->ipg_clk)) { + dev_err(&pdev->dev, "failed to get ipg clock\n"); + return PTR_ERR(asrc_priv->ipg_clk); + } + + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) { + sprintf(tmp, "asrck_%x", i); + asrc_priv->asrck_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(asrc_priv->asrck_clk[i])) { + dev_err(&pdev->dev, "failed to get %s clock\n", tmp); + return PTR_ERR(asrc_priv->asrck_clk[i]); + } + } + + if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx35-asrc")) { + asrc_priv->channel_bits = 3; + clk_map[IN] = input_clk_map_imx35; + clk_map[OUT] = output_clk_map_imx35; + } else { + asrc_priv->channel_bits = 4; + clk_map[IN] = input_clk_map_imx53; + clk_map[OUT] = output_clk_map_imx53; + } + + ret = fsl_asrc_init(asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to init asrc %d\n", ret); + return -EINVAL; + } + + asrc_priv->channel_avail = 10; + + ret = of_property_read_u32(np, "fsl,asrc-rate", + &asrc_priv->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + return -EINVAL; + } + + ret = of_property_read_u32(np, "fsl,asrc-width", + &asrc_priv->asrc_width); + if (ret) { + dev_err(&pdev->dev, "failed to get output width\n"); + return -EINVAL; + } + + if (asrc_priv->asrc_width != 16 && asrc_priv->asrc_width != 24) { + dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n"); + asrc_priv->asrc_width = 24; + } + + platform_set_drvdata(pdev, asrc_priv); + pm_runtime_enable(&pdev->dev); + spin_lock_init(&asrc_priv->lock); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component, + &fsl_asrc_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC DAI\n"); + return ret; + } + + ret = devm_snd_soc_register_platform(&pdev->dev, &fsl_asrc_platform); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC platform\n"); + return ret; + } + + dev_info(&pdev->dev, "driver registered\n"); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_asrc_runtime_resume(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + int i; + + clk_prepare_enable(asrc_priv->mem_clk); + clk_prepare_enable(asrc_priv->ipg_clk); + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) + clk_prepare_enable(asrc_priv->asrck_clk[i]); + + return 0; +} + +static int fsl_asrc_runtime_suspend(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + int i; + + for (i = 0; i < ASRC_CLK_MAX_NUM; i++) + clk_disable_unprepare(asrc_priv->asrck_clk[i]); + clk_disable_unprepare(asrc_priv->ipg_clk); + clk_disable_unprepare(asrc_priv->mem_clk); + + return 0; +} +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_SLEEP +static int fsl_asrc_suspend(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + + regcache_cache_only(asrc_priv->regmap, true); + regcache_mark_dirty(asrc_priv->regmap); + + return 0; +} + +static int fsl_asrc_resume(struct device *dev) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + u32 asrctr; + + /* Stop all pairs provisionally */ + regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr); + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, 0); + + /* Restore all registers */ + regcache_cache_only(asrc_priv->regmap, false); + regcache_sync(asrc_priv->regmap); + + /* Restart enabled pairs */ + regmap_update_bits(asrc_priv->regmap, REG_ASRCTR, + ASRCTR_ASRCEi_ALL_MASK, asrctr); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_asrc_pm = { + SET_RUNTIME_PM_OPS(fsl_asrc_runtime_suspend, fsl_asrc_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_asrc_suspend, fsl_asrc_resume) +}; + +static const struct of_device_id fsl_asrc_ids[] = { + { .compatible = "fsl,imx35-asrc", }, + { .compatible = "fsl,imx53-asrc", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_asrc_ids); + +static struct platform_driver fsl_asrc_driver = { + .probe = fsl_asrc_probe, + .driver = { + .name = "fsl-asrc", + .of_match_table = fsl_asrc_ids, + .pm = &fsl_asrc_pm, + }, +}; +module_platform_driver(fsl_asrc_driver); + +MODULE_DESCRIPTION("Freescale ASRC ASoC driver"); +MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>"); +MODULE_ALIAS("platform:fsl-asrc"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h new file mode 100644 index 000000000..4aed63c4b --- /dev/null +++ b/sound/soc/fsl/fsl_asrc.h @@ -0,0 +1,458 @@ +/* + * fsl_asrc.h - Freescale ASRC ALSA SoC header file + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <nicoleotsuka@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _FSL_ASRC_H +#define _FSL_ASRC_H + +#define IN 0 +#define OUT 1 + +#define ASRC_DMA_BUFFER_NUM 2 +#define ASRC_INPUTFIFO_THRESHOLD 32 +#define ASRC_OUTPUTFIFO_THRESHOLD 32 +#define ASRC_FIFO_THRESHOLD_MIN 0 +#define ASRC_FIFO_THRESHOLD_MAX 63 +#define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4) +#define ASRC_MAX_BUFFER_SIZE (1024 * 48) +#define ASRC_OUTPUT_LAST_SAMPLE 8 + +#define IDEAL_RATIO_RATE 1000000 + +#define REG_ASRCTR 0x00 +#define REG_ASRIER 0x04 +#define REG_ASRCNCR 0x0C +#define REG_ASRCFG 0x10 +#define REG_ASRCSR 0x14 + +#define REG_ASRCDR1 0x18 +#define REG_ASRCDR2 0x1C +#define REG_ASRCDR(i) ((i < 2) ? REG_ASRCDR1 : REG_ASRCDR2) + +#define REG_ASRSTR 0x20 +#define REG_ASRRA 0x24 +#define REG_ASRRB 0x28 +#define REG_ASRRC 0x2C +#define REG_ASRPM1 0x40 +#define REG_ASRPM2 0x44 +#define REG_ASRPM3 0x48 +#define REG_ASRPM4 0x4C +#define REG_ASRPM5 0x50 +#define REG_ASRTFR1 0x54 +#define REG_ASRCCR 0x5C + +#define REG_ASRDIA 0x60 +#define REG_ASRDOA 0x64 +#define REG_ASRDIB 0x68 +#define REG_ASRDOB 0x6C +#define REG_ASRDIC 0x70 +#define REG_ASRDOC 0x74 +#define REG_ASRDI(i) (REG_ASRDIA + (i << 3)) +#define REG_ASRDO(i) (REG_ASRDOA + (i << 3)) +#define REG_ASRDx(x, i) (x == IN ? REG_ASRDI(i) : REG_ASRDO(i)) + +#define REG_ASRIDRHA 0x80 +#define REG_ASRIDRLA 0x84 +#define REG_ASRIDRHB 0x88 +#define REG_ASRIDRLB 0x8C +#define REG_ASRIDRHC 0x90 +#define REG_ASRIDRLC 0x94 +#define REG_ASRIDRH(i) (REG_ASRIDRHA + (i << 3)) +#define REG_ASRIDRL(i) (REG_ASRIDRLA + (i << 3)) + +#define REG_ASR76K 0x98 +#define REG_ASR56K 0x9C + +#define REG_ASRMCRA 0xA0 +#define REG_ASRFSTA 0xA4 +#define REG_ASRMCRB 0xA8 +#define REG_ASRFSTB 0xAC +#define REG_ASRMCRC 0xB0 +#define REG_ASRFSTC 0xB4 +#define REG_ASRMCR(i) (REG_ASRMCRA + (i << 3)) +#define REG_ASRFST(i) (REG_ASRFSTA + (i << 3)) + +#define REG_ASRMCR1A 0xC0 +#define REG_ASRMCR1B 0xC4 +#define REG_ASRMCR1C 0xC8 +#define REG_ASRMCR1(i) (REG_ASRMCR1A + (i << 2)) + + +/* REG0 0x00 REG_ASRCTR */ +#define ASRCTR_ATSi_SHIFT(i) (20 + i) +#define ASRCTR_ATSi_MASK(i) (1 << ASRCTR_ATSi_SHIFT(i)) +#define ASRCTR_ATS(i) (1 << ASRCTR_ATSi_SHIFT(i)) +#define ASRCTR_USRi_SHIFT(i) (14 + (i << 1)) +#define ASRCTR_USRi_MASK(i) (1 << ASRCTR_USRi_SHIFT(i)) +#define ASRCTR_USR(i) (1 << ASRCTR_USRi_SHIFT(i)) +#define ASRCTR_IDRi_SHIFT(i) (13 + (i << 1)) +#define ASRCTR_IDRi_MASK(i) (1 << ASRCTR_IDRi_SHIFT(i)) +#define ASRCTR_IDR(i) (1 << ASRCTR_IDRi_SHIFT(i)) +#define ASRCTR_SRST_SHIFT 4 +#define ASRCTR_SRST_MASK (1 << ASRCTR_SRST_SHIFT) +#define ASRCTR_SRST (1 << ASRCTR_SRST_SHIFT) +#define ASRCTR_ASRCEi_SHIFT(i) (1 + i) +#define ASRCTR_ASRCEi_MASK(i) (1 << ASRCTR_ASRCEi_SHIFT(i)) +#define ASRCTR_ASRCE(i) (1 << ASRCTR_ASRCEi_SHIFT(i)) +#define ASRCTR_ASRCEi_ALL_MASK (0x7 << ASRCTR_ASRCEi_SHIFT(0)) +#define ASRCTR_ASRCEN_SHIFT 0 +#define ASRCTR_ASRCEN_MASK (1 << ASRCTR_ASRCEN_SHIFT) +#define ASRCTR_ASRCEN (1 << ASRCTR_ASRCEN_SHIFT) + +/* REG1 0x04 REG_ASRIER */ +#define ASRIER_AFPWE_SHIFT 7 +#define ASRIER_AFPWE_MASK (1 << ASRIER_AFPWE_SHIFT) +#define ASRIER_AFPWE (1 << ASRIER_AFPWE_SHIFT) +#define ASRIER_AOLIE_SHIFT 6 +#define ASRIER_AOLIE_MASK (1 << ASRIER_AOLIE_SHIFT) +#define ASRIER_AOLIE (1 << ASRIER_AOLIE_SHIFT) +#define ASRIER_ADOEi_SHIFT(i) (3 + i) +#define ASRIER_ADOEi_MASK(i) (1 << ASRIER_ADOEi_SHIFT(i)) +#define ASRIER_ADOE(i) (1 << ASRIER_ADOEi_SHIFT(i)) +#define ASRIER_ADIEi_SHIFT(i) (0 + i) +#define ASRIER_ADIEi_MASK(i) (1 << ASRIER_ADIEi_SHIFT(i)) +#define ASRIER_ADIE(i) (1 << ASRIER_ADIEi_SHIFT(i)) + +/* REG2 0x0C REG_ASRCNCR */ +#define ASRCNCR_ANCi_SHIFT(i, b) (b * i) +#define ASRCNCR_ANCi_MASK(i, b) (((1 << b) - 1) << ASRCNCR_ANCi_SHIFT(i, b)) +#define ASRCNCR_ANCi(i, v, b) ((v << ASRCNCR_ANCi_SHIFT(i, b)) & ASRCNCR_ANCi_MASK(i, b)) + +/* REG3 0x10 REG_ASRCFG */ +#define ASRCFG_INIRQi_SHIFT(i) (21 + i) +#define ASRCFG_INIRQi_MASK(i) (1 << ASRCFG_INIRQi_SHIFT(i)) +#define ASRCFG_INIRQi (1 << ASRCFG_INIRQi_SHIFT(i)) +#define ASRCFG_NDPRi_SHIFT(i) (18 + i) +#define ASRCFG_NDPRi_MASK(i) (1 << ASRCFG_NDPRi_SHIFT(i)) +#define ASRCFG_NDPRi (1 << ASRCFG_NDPRi_SHIFT(i)) +#define ASRCFG_POSTMODi_SHIFT(i) (8 + (i << 2)) +#define ASRCFG_POSTMODi_WIDTH 2 +#define ASRCFG_POSTMODi_MASK(i) (((1 << ASRCFG_POSTMODi_WIDTH) - 1) << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMOD(i, v) ((v) << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_UP(i) (0 << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_DCON(i) (1 << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_POSTMODi_DOWN(i) (2 << ASRCFG_POSTMODi_SHIFT(i)) +#define ASRCFG_PREMODi_SHIFT(i) (6 + (i << 2)) +#define ASRCFG_PREMODi_WIDTH 2 +#define ASRCFG_PREMODi_MASK(i) (((1 << ASRCFG_PREMODi_WIDTH) - 1) << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMOD(i, v) ((v) << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_UP(i) (0 << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_DCON(i) (1 << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_DOWN(i) (2 << ASRCFG_PREMODi_SHIFT(i)) +#define ASRCFG_PREMODi_BYPASS(i) (3 << ASRCFG_PREMODi_SHIFT(i)) + +/* REG4 0x14 REG_ASRCSR */ +#define ASRCSR_AxCSi_WIDTH 4 +#define ASRCSR_AxCSi_MASK ((1 << ASRCSR_AxCSi_WIDTH) - 1) +#define ASRCSR_AOCSi_SHIFT(i) (12 + (i << 2)) +#define ASRCSR_AOCSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AOCSi_SHIFT(i)) +#define ASRCSR_AOCS(i, v) ((v) << ASRCSR_AOCSi_SHIFT(i)) +#define ASRCSR_AICSi_SHIFT(i) (i << 2) +#define ASRCSR_AICSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AICSi_SHIFT(i)) +#define ASRCSR_AICS(i, v) ((v) << ASRCSR_AICSi_SHIFT(i)) + +/* REG5&6 0x18 & 0x1C REG_ASRCDR1 & ASRCDR2 */ +#define ASRCDRi_AxCPi_WIDTH 3 +#define ASRCDRi_AICPi_SHIFT(i) (0 + (i % 2) * 6) +#define ASRCDRi_AICPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICPi_SHIFT(i)) +#define ASRCDRi_AICP(i, v) ((v) << ASRCDRi_AICPi_SHIFT(i)) +#define ASRCDRi_AICDi_SHIFT(i) (3 + (i % 2) * 6) +#define ASRCDRi_AICDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICDi_SHIFT(i)) +#define ASRCDRi_AICD(i, v) ((v) << ASRCDRi_AICDi_SHIFT(i)) +#define ASRCDRi_AOCPi_SHIFT(i) ((i < 2) ? 12 + i * 6 : 6) +#define ASRCDRi_AOCPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCPi_SHIFT(i)) +#define ASRCDRi_AOCP(i, v) ((v) << ASRCDRi_AOCPi_SHIFT(i)) +#define ASRCDRi_AOCDi_SHIFT(i) ((i < 2) ? 15 + i * 6 : 9) +#define ASRCDRi_AOCDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCDi_SHIFT(i)) +#define ASRCDRi_AOCD(i, v) ((v) << ASRCDRi_AOCDi_SHIFT(i)) + +/* REG7 0x20 REG_ASRSTR */ +#define ASRSTR_DSLCNT_SHIFT 21 +#define ASRSTR_DSLCNT_MASK (1 << ASRSTR_DSLCNT_SHIFT) +#define ASRSTR_DSLCNT (1 << ASRSTR_DSLCNT_SHIFT) +#define ASRSTR_ATQOL_SHIFT 20 +#define ASRSTR_ATQOL_MASK (1 << ASRSTR_ATQOL_SHIFT) +#define ASRSTR_ATQOL (1 << ASRSTR_ATQOL_SHIFT) +#define ASRSTR_AOOLi_SHIFT(i) (17 + i) +#define ASRSTR_AOOLi_MASK(i) (1 << ASRSTR_AOOLi_SHIFT(i)) +#define ASRSTR_AOOL(i) (1 << ASRSTR_AOOLi_SHIFT(i)) +#define ASRSTR_AIOLi_SHIFT(i) (14 + i) +#define ASRSTR_AIOLi_MASK(i) (1 << ASRSTR_AIOLi_SHIFT(i)) +#define ASRSTR_AIOL(i) (1 << ASRSTR_AIOLi_SHIFT(i)) +#define ASRSTR_AODOi_SHIFT(i) (11 + i) +#define ASRSTR_AODOi_MASK(i) (1 << ASRSTR_AODOi_SHIFT(i)) +#define ASRSTR_AODO(i) (1 << ASRSTR_AODOi_SHIFT(i)) +#define ASRSTR_AIDUi_SHIFT(i) (8 + i) +#define ASRSTR_AIDUi_MASK(i) (1 << ASRSTR_AIDUi_SHIFT(i)) +#define ASRSTR_AIDU(i) (1 << ASRSTR_AIDUi_SHIFT(i)) +#define ASRSTR_FPWT_SHIFT 7 +#define ASRSTR_FPWT_MASK (1 << ASRSTR_FPWT_SHIFT) +#define ASRSTR_FPWT (1 << ASRSTR_FPWT_SHIFT) +#define ASRSTR_AOLE_SHIFT 6 +#define ASRSTR_AOLE_MASK (1 << ASRSTR_AOLE_SHIFT) +#define ASRSTR_AOLE (1 << ASRSTR_AOLE_SHIFT) +#define ASRSTR_AODEi_SHIFT(i) (3 + i) +#define ASRSTR_AODFi_MASK(i) (1 << ASRSTR_AODEi_SHIFT(i)) +#define ASRSTR_AODF(i) (1 << ASRSTR_AODEi_SHIFT(i)) +#define ASRSTR_AIDEi_SHIFT(i) (0 + i) +#define ASRSTR_AIDEi_MASK(i) (1 << ASRSTR_AIDEi_SHIFT(i)) +#define ASRSTR_AIDE(i) (1 << ASRSTR_AIDEi_SHIFT(i)) + +/* REG10 0x54 REG_ASRTFR1 */ +#define ASRTFR1_TF_BASE_WIDTH 7 +#define ASRTFR1_TF_BASE_SHIFT 6 +#define ASRTFR1_TF_BASE_MASK (((1 << ASRTFR1_TF_BASE_WIDTH) - 1) << ASRTFR1_TF_BASE_SHIFT) +#define ASRTFR1_TF_BASE(i) ((i) << ASRTFR1_TF_BASE_SHIFT) + +/* + * REG22 0xA0 REG_ASRMCRA + * REG24 0xA8 REG_ASRMCRB + * REG26 0xB0 REG_ASRMCRC + */ +#define ASRMCRi_ZEROBUFi_SHIFT 23 +#define ASRMCRi_ZEROBUFi_MASK (1 << ASRMCRi_ZEROBUFi_SHIFT) +#define ASRMCRi_ZEROBUFi (1 << ASRMCRi_ZEROBUFi_SHIFT) +#define ASRMCRi_EXTTHRSHi_SHIFT 22 +#define ASRMCRi_EXTTHRSHi_MASK (1 << ASRMCRi_EXTTHRSHi_SHIFT) +#define ASRMCRi_EXTTHRSHi (1 << ASRMCRi_EXTTHRSHi_SHIFT) +#define ASRMCRi_BUFSTALLi_SHIFT 21 +#define ASRMCRi_BUFSTALLi_MASK (1 << ASRMCRi_BUFSTALLi_SHIFT) +#define ASRMCRi_BUFSTALLi (1 << ASRMCRi_BUFSTALLi_SHIFT) +#define ASRMCRi_BYPASSPOLYi_SHIFT 20 +#define ASRMCRi_BYPASSPOLYi_MASK (1 << ASRMCRi_BYPASSPOLYi_SHIFT) +#define ASRMCRi_BYPASSPOLYi (1 << ASRMCRi_BYPASSPOLYi_SHIFT) +#define ASRMCRi_OUTFIFO_THRESHOLD_WIDTH 6 +#define ASRMCRi_OUTFIFO_THRESHOLD_SHIFT 12 +#define ASRMCRi_OUTFIFO_THRESHOLD_MASK (((1 << ASRMCRi_OUTFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT) +#define ASRMCRi_OUTFIFO_THRESHOLD(v) (((v) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT) & ASRMCRi_OUTFIFO_THRESHOLD_MASK) +#define ASRMCRi_RSYNIFi_SHIFT 11 +#define ASRMCRi_RSYNIFi_MASK (1 << ASRMCRi_RSYNIFi_SHIFT) +#define ASRMCRi_RSYNIFi (1 << ASRMCRi_RSYNIFi_SHIFT) +#define ASRMCRi_RSYNOFi_SHIFT 10 +#define ASRMCRi_RSYNOFi_MASK (1 << ASRMCRi_RSYNOFi_SHIFT) +#define ASRMCRi_RSYNOFi (1 << ASRMCRi_RSYNOFi_SHIFT) +#define ASRMCRi_INFIFO_THRESHOLD_WIDTH 6 +#define ASRMCRi_INFIFO_THRESHOLD_SHIFT 0 +#define ASRMCRi_INFIFO_THRESHOLD_MASK (((1 << ASRMCRi_INFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_INFIFO_THRESHOLD_SHIFT) +#define ASRMCRi_INFIFO_THRESHOLD(v) (((v) << ASRMCRi_INFIFO_THRESHOLD_SHIFT) & ASRMCRi_INFIFO_THRESHOLD_MASK) + +/* + * REG23 0xA4 REG_ASRFSTA + * REG25 0xAC REG_ASRFSTB + * REG27 0xB4 REG_ASRFSTC + */ +#define ASRFSTi_OAFi_SHIFT 23 +#define ASRFSTi_OAFi_MASK (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_OAFi (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_OUTPUT_FIFO_WIDTH 7 +#define ASRFSTi_OUTPUT_FIFO_SHIFT 12 +#define ASRFSTi_OUTPUT_FIFO_MASK (((1 << ASRFSTi_OUTPUT_FIFO_WIDTH) - 1) << ASRFSTi_OUTPUT_FIFO_SHIFT) +#define ASRFSTi_IAEi_SHIFT 11 +#define ASRFSTi_IAEi_MASK (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_IAEi (1 << ASRFSTi_OAFi_SHIFT) +#define ASRFSTi_INPUT_FIFO_WIDTH 7 +#define ASRFSTi_INPUT_FIFO_SHIFT 0 +#define ASRFSTi_INPUT_FIFO_MASK ((1 << ASRFSTi_INPUT_FIFO_WIDTH) - 1) + +/* REG28 0xC0 & 0xC4 & 0xC8 REG_ASRMCR1i */ +#define ASRMCR1i_IWD_WIDTH 3 +#define ASRMCR1i_IWD_SHIFT 9 +#define ASRMCR1i_IWD_MASK (((1 << ASRMCR1i_IWD_WIDTH) - 1) << ASRMCR1i_IWD_SHIFT) +#define ASRMCR1i_IWD(v) ((v) << ASRMCR1i_IWD_SHIFT) +#define ASRMCR1i_IMSB_SHIFT 8 +#define ASRMCR1i_IMSB_MASK (1 << ASRMCR1i_IMSB_SHIFT) +#define ASRMCR1i_IMSB_MSB (1 << ASRMCR1i_IMSB_SHIFT) +#define ASRMCR1i_IMSB_LSB (0 << ASRMCR1i_IMSB_SHIFT) +#define ASRMCR1i_OMSB_SHIFT 2 +#define ASRMCR1i_OMSB_MASK (1 << ASRMCR1i_OMSB_SHIFT) +#define ASRMCR1i_OMSB_MSB (1 << ASRMCR1i_OMSB_SHIFT) +#define ASRMCR1i_OMSB_LSB (0 << ASRMCR1i_OMSB_SHIFT) +#define ASRMCR1i_OSGN_SHIFT 1 +#define ASRMCR1i_OSGN_MASK (1 << ASRMCR1i_OSGN_SHIFT) +#define ASRMCR1i_OSGN (1 << ASRMCR1i_OSGN_SHIFT) +#define ASRMCR1i_OW16_SHIFT 0 +#define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT) +#define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT) + + +enum asrc_pair_index { + ASRC_INVALID_PAIR = -1, + ASRC_PAIR_A = 0, + ASRC_PAIR_B = 1, + ASRC_PAIR_C = 2, +}; + +#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) + +enum asrc_inclk { + INCLK_NONE = 0x03, + INCLK_ESAI_RX = 0x00, + INCLK_SSI1_RX = 0x01, + INCLK_SSI2_RX = 0x02, + INCLK_SSI3_RX = 0x07, + INCLK_SPDIF_RX = 0x04, + INCLK_MLB_CLK = 0x05, + INCLK_PAD = 0x06, + INCLK_ESAI_TX = 0x08, + INCLK_SSI1_TX = 0x09, + INCLK_SSI2_TX = 0x0a, + INCLK_SSI3_TX = 0x0b, + INCLK_SPDIF_TX = 0x0c, + INCLK_ASRCK1_CLK = 0x0f, +}; + +enum asrc_outclk { + OUTCLK_NONE = 0x03, + OUTCLK_ESAI_TX = 0x00, + OUTCLK_SSI1_TX = 0x01, + OUTCLK_SSI2_TX = 0x02, + OUTCLK_SSI3_TX = 0x07, + OUTCLK_SPDIF_TX = 0x04, + OUTCLK_MLB_CLK = 0x05, + OUTCLK_PAD = 0x06, + OUTCLK_ESAI_RX = 0x08, + OUTCLK_SSI1_RX = 0x09, + OUTCLK_SSI2_RX = 0x0a, + OUTCLK_SSI3_RX = 0x0b, + OUTCLK_SPDIF_RX = 0x0c, + OUTCLK_ASRCK1_CLK = 0x0f, +}; + +#define ASRC_CLK_MAX_NUM 16 + +enum asrc_word_width { + ASRC_WIDTH_24_BIT = 0, + ASRC_WIDTH_16_BIT = 1, + ASRC_WIDTH_8_BIT = 2, +}; + +struct asrc_config { + enum asrc_pair_index pair; + unsigned int channel_num; + unsigned int buffer_num; + unsigned int dma_buffer_size; + unsigned int input_sample_rate; + unsigned int output_sample_rate; + enum asrc_word_width input_word_width; + enum asrc_word_width output_word_width; + enum asrc_inclk inclk; + enum asrc_outclk outclk; +}; + +struct asrc_req { + unsigned int chn_num; + enum asrc_pair_index index; +}; + +struct asrc_querybuf { + unsigned int buffer_index; + unsigned int input_length; + unsigned int output_length; + unsigned long input_offset; + unsigned long output_offset; +}; + +struct asrc_convert_buffer { + void *input_buffer_vaddr; + void *output_buffer_vaddr; + unsigned int input_buffer_length; + unsigned int output_buffer_length; +}; + +struct asrc_status_flags { + enum asrc_pair_index index; + unsigned int overload_error; +}; + +enum asrc_error_status { + ASRC_TASK_Q_OVERLOAD = 0x01, + ASRC_OUTPUT_TASK_OVERLOAD = 0x02, + ASRC_INPUT_TASK_OVERLOAD = 0x04, + ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, + ASRC_INPUT_BUFFER_UNDERRUN = 0x10, +}; + +struct dma_block { + dma_addr_t dma_paddr; + void *dma_vaddr; + unsigned int length; +}; + +/** + * fsl_asrc_pair: ASRC Pair private data + * + * @asrc_priv: pointer to its parent module + * @config: configuration profile + * @error: error record + * @index: pair index (ASRC_PAIR_A, ASRC_PAIR_B, ASRC_PAIR_C) + * @channels: occupied channel number + * @desc: input and output dma descriptors + * @dma_chan: inputer and output DMA channels + * @dma_data: private dma data + * @pos: hardware pointer position + * @private: pair private area + */ +struct fsl_asrc_pair { + struct fsl_asrc *asrc_priv; + struct asrc_config *config; + unsigned int error; + + enum asrc_pair_index index; + unsigned int channels; + + struct dma_async_tx_descriptor *desc[2]; + struct dma_chan *dma_chan[2]; + struct imx_dma_data dma_data; + unsigned int pos; + + void *private; +}; + +/** + * fsl_asrc_pair: ASRC private data + * + * @dma_params_rx: DMA parameters for receive channel + * @dma_params_tx: DMA parameters for transmit channel + * @pdev: platform device pointer + * @regmap: regmap handler + * @paddr: physical address to the base address of registers + * @mem_clk: clock source to access register + * @ipg_clk: clock source to drive peripheral + * @asrck_clk: clock sources to driver ASRC internal logic + * @lock: spin lock for resource protection + * @pair: pair pointers + * @channel_bits: width of ASRCNCR register for each pair + * @channel_avail: non-occupied channel numbers + * @asrc_rate: default sample rate for ASoC Back-Ends + * @asrc_width: default sample width for ASoC Back-Ends + */ +struct fsl_asrc { + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct platform_device *pdev; + struct regmap *regmap; + unsigned long paddr; + struct clk *mem_clk; + struct clk *ipg_clk; + struct clk *asrck_clk[ASRC_CLK_MAX_NUM]; + spinlock_t lock; + + struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM]; + unsigned int channel_bits; + unsigned int channel_avail; + + int asrc_rate; + int asrc_width; +}; + +extern struct snd_soc_platform_driver fsl_asrc_platform; +struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir); +#endif /* _FSL_ASRC_H */ diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c new file mode 100644 index 000000000..ffc000bc1 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -0,0 +1,391 @@ +/* + * Freescale ASRC ALSA SoC Platform (DMA) driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <nicoleotsuka@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/platform_data/dma-imx.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_asrc.h" + +#define FSL_ASRC_DMABUF_SIZE (256 * 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, + .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65535, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static bool filter(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +static void fsl_asrc_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + pair->pos += snd_pcm_lib_period_bytes(substream); + if (pair->pos >= snd_pcm_lib_buffer_bytes(substream)) + pair->pos = 0; + + snd_pcm_period_elapsed(substream); +} + +static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream) +{ + u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + struct device *dev = rtd->platform->dev; + unsigned long flags = DMA_CTRL_ACK; + + /* Prepare and submit Front-End DMA channel */ + if (!substream->runtime->no_period_wakeup) + flags |= DMA_PREP_INTERRUPT; + + pair->pos = 0; + pair->desc[!dir] = dmaengine_prep_dma_cyclic( + pair->dma_chan[!dir], runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + dir == OUT ? DMA_TO_DEVICE : DMA_FROM_DEVICE, flags); + if (!pair->desc[!dir]) { + dev_err(dev, "failed to prepare slave DMA for Front-End\n"); + return -ENOMEM; + } + + pair->desc[!dir]->callback = fsl_asrc_dma_complete; + pair->desc[!dir]->callback_param = substream; + + dmaengine_submit(pair->desc[!dir]); + + /* Prepare and submit Back-End DMA channel */ + pair->desc[dir] = dmaengine_prep_dma_cyclic( + pair->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0); + if (!pair->desc[dir]) { + dev_err(dev, "failed to prepare slave DMA for Back-End\n"); + return -ENOMEM; + } + + dmaengine_submit(pair->desc[dir]); + + return 0; +} + +static int fsl_asrc_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = fsl_asrc_dma_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(pair->dma_chan[IN]); + dma_async_issue_pending(pair->dma_chan[OUT]); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_terminate_all(pair->dma_chan[OUT]); + dmaengine_terminate_all(pair->dma_chan[IN]); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL; + struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct dma_slave_config config_fe, config_be; + enum asrc_pair_index index = pair->index; + struct device *dev = rtd->platform->dev; + int stream = substream->stream; + struct imx_dma_data *tmp_data; + struct snd_soc_dpcm *dpcm; + struct dma_chan *tmp_chan; + struct device *dev_be; + u8 dir = tx ? OUT : IN; + dma_cap_mask_t mask; + int ret; + + /* Fetch the Back-End dma_data from DPCM */ + list_for_each_entry(dpcm, &rtd->dpcm[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dpcm->be; + struct snd_pcm_substream *substream_be; + struct snd_soc_dai *dai = be->cpu_dai; + + if (dpcm->fe != rtd) + continue; + + substream_be = snd_soc_dpcm_get_substream(be, stream); + dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be); + dev_be = dai->dev; + break; + } + + if (!dma_params_be) { + dev_err(dev, "failed to get the substream of Back-End\n"); + return -EINVAL; + } + + /* Override dma_data of the Front-End and config its dmaengine */ + dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + dma_params_fe->addr = asrc_priv->paddr + REG_ASRDx(!dir, index); + dma_params_fe->maxburst = dma_params_be->maxburst; + + pair->dma_chan[!dir] = fsl_asrc_get_dma_channel(pair, !dir); + if (!pair->dma_chan[!dir]) { + dev_err(dev, "failed to request DMA channel\n"); + return -EINVAL; + } + + memset(&config_fe, 0, sizeof(config_fe)); + ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe); + if (ret) { + dev_err(dev, "failed to prepare DMA config for Front-End\n"); + return ret; + } + + ret = dmaengine_slave_config(pair->dma_chan[!dir], &config_fe); + if (ret) { + dev_err(dev, "failed to config DMA channel for Front-End\n"); + return ret; + } + + /* Request and config DMA channel for Back-End */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_CYCLIC, mask); + + /* Get DMA request of Back-End */ + tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx"); + tmp_data = tmp_chan->private; + pair->dma_data.dma_request = tmp_data->dma_request; + dma_release_channel(tmp_chan); + + /* Get DMA request of Front-End */ + tmp_chan = fsl_asrc_get_dma_channel(pair, dir); + tmp_data = tmp_chan->private; + pair->dma_data.dma_request2 = tmp_data->dma_request; + pair->dma_data.peripheral_type = tmp_data->peripheral_type; + pair->dma_data.priority = tmp_data->priority; + dma_release_channel(tmp_chan); + + pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data); + if (!pair->dma_chan[dir]) { + dev_err(dev, "failed to request DMA channel for Back-End\n"); + return -EINVAL; + } + + if (asrc_priv->asrc_width == 16) + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + else + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + + config_be.direction = DMA_DEV_TO_DEV; + config_be.src_addr_width = buswidth; + config_be.src_maxburst = dma_params_be->maxburst; + config_be.dst_addr_width = buswidth; + config_be.dst_maxburst = dma_params_be->maxburst; + + if (tx) { + config_be.src_addr = asrc_priv->paddr + REG_ASRDO(index); + config_be.dst_addr = dma_params_be->addr; + } else { + config_be.dst_addr = asrc_priv->paddr + REG_ASRDI(index); + config_be.src_addr = dma_params_be->addr; + } + + ret = dmaengine_slave_config(pair->dma_chan[dir], &config_be); + if (ret) { + dev_err(dev, "failed to config DMA channel for Back-End\n"); + return ret; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int fsl_asrc_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, NULL); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + + pair->dma_chan[IN] = NULL; + pair->dma_chan[OUT] = NULL; + + return 0; +} + +static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = rtd->platform->dev; + struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + struct fsl_asrc_pair *pair; + + pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); + if (!pair) { + dev_err(dev, "failed to allocate pair\n"); + return -ENOMEM; + } + + pair->asrc_priv = asrc_priv; + + runtime->private_data = pair; + + snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + + return 0; +} + +static int fsl_asrc_dma_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + struct fsl_asrc *asrc_priv; + + if (!pair) + return 0; + + asrc_priv = pair->asrc_priv; + + if (asrc_priv->pair[pair->index] == pair) + asrc_priv->pair[pair->index] = NULL; + + kfree(pair); + + return 0; +} + +static snd_pcm_uframes_t fsl_asrc_dma_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_asrc_pair *pair = runtime->private_data; + + return bytes_to_frames(substream->runtime, pair->pos); +} + +static struct snd_pcm_ops fsl_asrc_dma_pcm_ops = { + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsl_asrc_dma_hw_params, + .hw_free = fsl_asrc_dma_hw_free, + .trigger = fsl_asrc_dma_trigger, + .open = fsl_asrc_dma_startup, + .close = fsl_asrc_dma_shutdown, + .pointer = fsl_asrc_dma_pcm_pointer, +}; + +static int fsl_asrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = rtd->pcm; + int ret, i; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(card->dev, "failed to set DMA mask\n"); + return ret; + } + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + FSL_ASRC_DMABUF_SIZE, &substream->dma_buffer); + if (ret) { + dev_err(card->dev, "failed to allocate DMA buffer\n"); + goto err; + } + } + + return 0; + +err: + if (--i == 0 && pcm->streams[i].substream) + snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer); + + return ret; +} + +static void fsl_asrc_dma_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; + + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } +} + +struct snd_soc_platform_driver fsl_asrc_platform = { + .ops = &fsl_asrc_dma_pcm_ops, + .pcm_new = fsl_asrc_dma_pcm_new, + .pcm_free = fsl_asrc_dma_pcm_free, +}; +EXPORT_SYMBOL_GPL(fsl_asrc_platform); diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c new file mode 100644 index 000000000..93d7e56c6 --- /dev/null +++ b/sound/soc/fsl/fsl_dma.c @@ -0,0 +1,977 @@ +/* + * Freescale DMA ALSA SoC PCM driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007-2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * This driver implements ASoC support for the Elo DMA controller, which is + * the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms, + * the PCM driver is what handles the DMA buffer. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/gfp.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/list.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/io.h> + +#include "fsl_dma.h" +#include "fsl_ssi.h" /* For the offset of stx0 and srx0 */ + +/* + * The formats that the DMA controller supports, which is anything + * that is 8, 16, or 32 bits. + */ +#define FSLDMA_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_U16_BE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_U24_LE | \ + SNDRV_PCM_FMTBIT_U24_BE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_S32_BE | \ + SNDRV_PCM_FMTBIT_U32_LE | \ + SNDRV_PCM_FMTBIT_U32_BE) +struct dma_object { + struct snd_soc_platform_driver dai; + dma_addr_t ssi_stx_phys; + dma_addr_t ssi_srx_phys; + unsigned int ssi_fifo_depth; + struct ccsr_dma_channel __iomem *channel; + unsigned int irq; + bool assigned; + char path[1]; +}; + +/* + * The number of DMA links to use. Two is the bare minimum, but if you + * have really small links you might need more. + */ +#define NUM_DMA_LINKS 2 + +/** fsl_dma_private: p-substream DMA data + * + * Each substream has a 1-to-1 association with a DMA channel. + * + * The link[] array is first because it needs to be aligned on a 32-byte + * boundary, so putting it first will ensure alignment without padding the + * structure. + * + * @link[]: array of link descriptors + * @dma_channel: pointer to the DMA channel's registers + * @irq: IRQ for this DMA channel + * @substream: pointer to the substream object, needed by the ISR + * @ssi_sxx_phys: bus address of the STX or SRX register to use + * @ld_buf_phys: physical address of the LD buffer + * @current_link: index into link[] of the link currently being processed + * @dma_buf_phys: physical address of the DMA buffer + * @dma_buf_next: physical address of the next period to process + * @dma_buf_end: physical address of the byte after the end of the DMA + * @buffer period_size: the size of a single period + * @num_periods: the number of periods in the DMA buffer + */ +struct fsl_dma_private { + struct fsl_dma_link_descriptor link[NUM_DMA_LINKS]; + struct ccsr_dma_channel __iomem *dma_channel; + unsigned int irq; + struct snd_pcm_substream *substream; + dma_addr_t ssi_sxx_phys; + unsigned int ssi_fifo_depth; + dma_addr_t ld_buf_phys; + unsigned int current_link; + dma_addr_t dma_buf_phys; + dma_addr_t dma_buf_next; + dma_addr_t dma_buf_end; + size_t period_size; + unsigned int num_periods; +}; + +/** + * fsl_dma_hardare: define characteristics of the PCM hardware. + * + * The PCM hardware is the Freescale DMA controller. This structure defines + * the capabilities of that hardware. + * + * Since the sampling rate and data format are not controlled by the DMA + * controller, we specify no limits for those values. The only exception is + * period_bytes_min, which is set to a reasonably low value to prevent the + * DMA controller from generating too many interrupts per second. + * + * Since each link descriptor has a 32-bit byte count field, we set + * period_bytes_max to the largest 32-bit number. We also have no maximum + * number of periods. + * + * Note that we specify SNDRV_PCM_INFO_JOINT_DUPLEX here, but only because a + * limitation in the SSI driver requires the sample rates for playback and + * capture to be the same. + */ +static const struct snd_pcm_hardware fsl_dma_hardware = { + + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_PAUSE, + .formats = FSLDMA_PCM_FORMATS, + .period_bytes_min = 512, /* A reasonable limit */ + .period_bytes_max = (u32) -1, + .periods_min = NUM_DMA_LINKS, + .periods_max = (unsigned int) -1, + .buffer_bytes_max = 128 * 1024, /* A reasonable limit */ +}; + +/** + * fsl_dma_abort_stream: tell ALSA that the DMA transfer has aborted + * + * This function should be called by the ISR whenever the DMA controller + * halts data transfer. + */ +static void fsl_dma_abort_stream(struct snd_pcm_substream *substream) +{ + snd_pcm_stop_xrun(substream); +} + +/** + * fsl_dma_update_pointers - update LD pointers to point to the next period + * + * As each period is completed, this function changes the the link + * descriptor pointers for that period to point to the next period. + */ +static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private) +{ + struct fsl_dma_link_descriptor *link = + &dma_private->link[dma_private->current_link]; + + /* Update our link descriptors to point to the next period. On a 36-bit + * system, we also need to update the ESAD bits. We also set (keep) the + * snoop bits. See the comments in fsl_dma_hw_params() about snooping. + */ + if (dma_private->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + link->source_addr = cpu_to_be32(dma_private->dma_buf_next); +#ifdef CONFIG_PHYS_64BIT + link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP | + upper_32_bits(dma_private->dma_buf_next)); +#endif + } else { + link->dest_addr = cpu_to_be32(dma_private->dma_buf_next); +#ifdef CONFIG_PHYS_64BIT + link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP | + upper_32_bits(dma_private->dma_buf_next)); +#endif + } + + /* Update our variables for next time */ + dma_private->dma_buf_next += dma_private->period_size; + + if (dma_private->dma_buf_next >= dma_private->dma_buf_end) + dma_private->dma_buf_next = dma_private->dma_buf_phys; + + if (++dma_private->current_link >= NUM_DMA_LINKS) + dma_private->current_link = 0; +} + +/** + * fsl_dma_isr: interrupt handler for the DMA controller + * + * @irq: IRQ of the DMA channel + * @dev_id: pointer to the dma_private structure for this DMA channel + */ +static irqreturn_t fsl_dma_isr(int irq, void *dev_id) +{ + struct fsl_dma_private *dma_private = dev_id; + struct snd_pcm_substream *substream = dma_private->substream; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + irqreturn_t ret = IRQ_NONE; + u32 sr, sr2 = 0; + + /* We got an interrupt, so read the status register to see what we + were interrupted for. + */ + sr = in_be32(&dma_channel->sr); + + if (sr & CCSR_DMA_SR_TE) { + dev_err(dev, "dma transmit error\n"); + fsl_dma_abort_stream(substream); + sr2 |= CCSR_DMA_SR_TE; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_CH) + ret = IRQ_HANDLED; + + if (sr & CCSR_DMA_SR_PE) { + dev_err(dev, "dma programming error\n"); + fsl_dma_abort_stream(substream); + sr2 |= CCSR_DMA_SR_PE; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_EOLNI) { + sr2 |= CCSR_DMA_SR_EOLNI; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_CB) + ret = IRQ_HANDLED; + + if (sr & CCSR_DMA_SR_EOSI) { + /* Tell ALSA we completed a period. */ + snd_pcm_period_elapsed(substream); + + /* + * Update our link descriptors to point to the next period. We + * only need to do this if the number of periods is not equal to + * the number of links. + */ + if (dma_private->num_periods != NUM_DMA_LINKS) + fsl_dma_update_pointers(dma_private); + + sr2 |= CCSR_DMA_SR_EOSI; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_EOLSI) { + sr2 |= CCSR_DMA_SR_EOLSI; + ret = IRQ_HANDLED; + } + + /* Clear the bits that we set */ + if (sr2) + out_be32(&dma_channel->sr, sr2); + + return ret; +} + +/** + * fsl_dma_new: initialize this PCM driver. + * + * This function is called when the codec driver calls snd_soc_new_pcms(), + * once for each .dai_link in the machine driver's snd_soc_card + * structure. + * + * snd_dma_alloc_pages() is just a front-end to dma_alloc_coherent(), which + * (currently) always allocates the DMA buffer in lowmem, even if GFP_HIGHMEM + * is specified. Therefore, any DMA buffers we allocate will always be in low + * memory, but we support for 36-bit physical addresses anyway. + * + * Regardless of where the memory is actually allocated, since the device can + * technically DMA to any 36-bit address, we do need to set the DMA mask to 36. + */ +static int fsl_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(36)); + if (ret) + return ret; + + /* Some codecs have separate DAIs for playback and capture, so we + * should allocate a DMA buffer only for the streams that are valid. + */ + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev, + fsl_dma_hardware.buffer_bytes_max, + &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer); + if (ret) { + dev_err(card->dev, "can't alloc playback dma buffer\n"); + return ret; + } + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev, + fsl_dma_hardware.buffer_bytes_max, + &pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer); + if (ret) { + dev_err(card->dev, "can't alloc capture dma buffer\n"); + snd_dma_free_pages(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer); + return ret; + } + } + + return 0; +} + +/** + * fsl_dma_open: open a new substream. + * + * Each substream has its own DMA buffer. + * + * ALSA divides the DMA buffer into N periods. We create NUM_DMA_LINKS link + * descriptors that ping-pong from one period to the next. For example, if + * there are six periods and two link descriptors, this is how they look + * before playback starts: + * + * The last link descriptor + * ____________ points back to the first + * | | + * V | + * ___ ___ | + * | |->| |->| + * |___| |___| + * | | + * | | + * V V + * _________________________________________ + * | | | | | | | The DMA buffer is + * | | | | | | | divided into 6 parts + * |______|______|______|______|______|______| + * + * and here's how they look after the first period is finished playing: + * + * ____________ + * | | + * V | + * ___ ___ | + * | |->| |->| + * |___| |___| + * | | + * |______________ + * | | + * V V + * _________________________________________ + * | | | | | | | + * | | | | | | | + * |______|______|______|______|______|______| + * + * The first link descriptor now points to the third period. The DMA + * controller is currently playing the second period. When it finishes, it + * will jump back to the first descriptor and play the third period. + * + * There are four reasons we do this: + * + * 1. The only way to get the DMA controller to automatically restart the + * transfer when it gets to the end of the buffer is to use chaining + * mode. Basic direct mode doesn't offer that feature. + * 2. We need to receive an interrupt at the end of every period. The DMA + * controller can generate an interrupt at the end of every link transfer + * (aka segment). Making each period into a DMA segment will give us the + * interrupts we need. + * 3. By creating only two link descriptors, regardless of the number of + * periods, we do not need to reallocate the link descriptors if the + * number of periods changes. + * 4. All of the audio data is still stored in a single, contiguous DMA + * buffer, which is what ALSA expects. We're just dividing it into + * contiguous parts, and creating a link descriptor for each one. + */ +static int fsl_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + struct dma_object *dma = + container_of(rtd->platform->driver, struct dma_object, dai); + struct fsl_dma_private *dma_private; + struct ccsr_dma_channel __iomem *dma_channel; + dma_addr_t ld_buf_phys; + u64 temp_link; /* Pointer to next link descriptor */ + u32 mr; + unsigned int channel; + int ret = 0; + unsigned int i; + + /* + * Reject any DMA buffer whose size is not a multiple of the period + * size. We need to make sure that the DMA buffer can be evenly divided + * into periods. + */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(dev, "invalid buffer size\n"); + return ret; + } + + channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + if (dma->assigned) { + dev_err(dev, "dma channel already assigned\n"); + return -EBUSY; + } + + dma_private = dma_alloc_coherent(dev, sizeof(struct fsl_dma_private), + &ld_buf_phys, GFP_KERNEL); + if (!dma_private) { + dev_err(dev, "can't allocate dma private data\n"); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_private->ssi_sxx_phys = dma->ssi_stx_phys; + else + dma_private->ssi_sxx_phys = dma->ssi_srx_phys; + + dma_private->ssi_fifo_depth = dma->ssi_fifo_depth; + dma_private->dma_channel = dma->channel; + dma_private->irq = dma->irq; + dma_private->substream = substream; + dma_private->ld_buf_phys = ld_buf_phys; + dma_private->dma_buf_phys = substream->dma_buffer.addr; + + ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "fsldma-audio", + dma_private); + if (ret) { + dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", + dma_private->irq, ret); + dma_free_coherent(dev, sizeof(struct fsl_dma_private), + dma_private, dma_private->ld_buf_phys); + return ret; + } + + dma->assigned = 1; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware); + runtime->private_data = dma_private; + + /* Program the fixed DMA controller parameters */ + + dma_channel = dma_private->dma_channel; + + temp_link = dma_private->ld_buf_phys + + sizeof(struct fsl_dma_link_descriptor); + + for (i = 0; i < NUM_DMA_LINKS; i++) { + dma_private->link[i].next = cpu_to_be64(temp_link); + + temp_link += sizeof(struct fsl_dma_link_descriptor); + } + /* The last link descriptor points to the first */ + dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys); + + /* Tell the DMA controller where the first link descriptor is */ + out_be32(&dma_channel->clndar, + CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys)); + out_be32(&dma_channel->eclndar, + CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys)); + + /* The manual says the BCR must be clear before enabling EMP */ + out_be32(&dma_channel->bcr, 0); + + /* + * Program the mode register for interrupts, external master control, + * and source/destination hold. Also clear the Channel Abort bit. + */ + mr = in_be32(&dma_channel->mr) & + ~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE); + + /* + * We want External Master Start and External Master Pause enabled, + * because the SSI is controlling the DMA controller. We want the DMA + * controller to be set up in advance, and then we signal only the SSI + * to start transferring. + * + * We want End-Of-Segment Interrupts enabled, because this will generate + * an interrupt at the end of each segment (each link descriptor + * represents one segment). Each DMA segment is the same thing as an + * ALSA period, so this is how we get an interrupt at the end of every + * period. + * + * We want Error Interrupt enabled, so that we can get an error if + * the DMA controller is mis-programmed somehow. + */ + mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN | + CCSR_DMA_MR_EMS_EN; + + /* For playback, we want the destination address to be held. For + capture, set the source address to be held. */ + mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE; + + out_be32(&dma_channel->mr, mr); + + return 0; +} + +/** + * fsl_dma_hw_params: continue initializing the DMA links + * + * This function obtains hardware parameters about the opened stream and + * programs the DMA controller accordingly. + * + * One drawback of big-endian is that when copying integers of different + * sizes to a fixed-sized register, the address to which the integer must be + * copied is dependent on the size of the integer. + * + * For example, if P is the address of a 32-bit register, and X is a 32-bit + * integer, then X should be copied to address P. However, if X is a 16-bit + * integer, then it should be copied to P+2. If X is an 8-bit register, + * then it should be copied to P+3. + * + * So for playback of 8-bit samples, the DMA controller must transfer single + * bytes from the DMA buffer to the last byte of the STX0 register, i.e. + * offset by 3 bytes. For 16-bit samples, the offset is two bytes. + * + * For 24-bit samples, the offset is 1 byte. However, the DMA controller + * does not support 3-byte copies (the DAHTS register supports only 1, 2, 4, + * and 8 bytes at a time). So we do not support packed 24-bit samples. + * 24-bit data must be padded to 32 bits. + */ +static int fsl_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + + /* Number of bits per sample */ + unsigned int sample_bits = + snd_pcm_format_physical_width(params_format(hw_params)); + + /* Number of bytes per frame */ + unsigned int sample_bytes = sample_bits / 8; + + /* Bus address of SSI STX register */ + dma_addr_t ssi_sxx_phys = dma_private->ssi_sxx_phys; + + /* Size of the DMA buffer, in bytes */ + size_t buffer_size = params_buffer_bytes(hw_params); + + /* Number of bytes per period */ + size_t period_size = params_period_bytes(hw_params); + + /* Pointer to next period */ + dma_addr_t temp_addr = substream->dma_buffer.addr; + + /* Pointer to DMA controller */ + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + + u32 mr; /* DMA Mode Register */ + + unsigned int i; + + /* Initialize our DMA tracking variables */ + dma_private->period_size = period_size; + dma_private->num_periods = params_periods(hw_params); + dma_private->dma_buf_end = dma_private->dma_buf_phys + buffer_size; + dma_private->dma_buf_next = dma_private->dma_buf_phys + + (NUM_DMA_LINKS * period_size); + + if (dma_private->dma_buf_next >= dma_private->dma_buf_end) + /* This happens if the number of periods == NUM_DMA_LINKS */ + dma_private->dma_buf_next = dma_private->dma_buf_phys; + + mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK | + CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK); + + /* Due to a quirk of the SSI's STX register, the target address + * for the DMA operations depends on the sample size. So we calculate + * that offset here. While we're at it, also tell the DMA controller + * how much data to transfer per sample. + */ + switch (sample_bits) { + case 8: + mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1; + ssi_sxx_phys += 3; + break; + case 16: + mr |= CCSR_DMA_MR_DAHTS_2 | CCSR_DMA_MR_SAHTS_2; + ssi_sxx_phys += 2; + break; + case 32: + mr |= CCSR_DMA_MR_DAHTS_4 | CCSR_DMA_MR_SAHTS_4; + break; + default: + /* We should never get here */ + dev_err(dev, "unsupported sample size %u\n", sample_bits); + return -EINVAL; + } + + /* + * BWC determines how many bytes are sent/received before the DMA + * controller checks the SSI to see if it needs to stop. BWC should + * always be a multiple of the frame size, so that we always transmit + * whole frames. Each frame occupies two slots in the FIFO. The + * parameter for CCSR_DMA_MR_BWC() is rounded down the next power of two + * (MR[BWC] can only represent even powers of two). + * + * To simplify the process, we set BWC to the largest value that is + * less than or equal to the FIFO watermark. For playback, this ensures + * that we transfer the maximum amount without overrunning the FIFO. + * For capture, this ensures that we transfer the maximum amount without + * underrunning the FIFO. + * + * f = SSI FIFO depth + * w = SSI watermark value (which equals f - 2) + * b = DMA bandwidth count (in bytes) + * s = sample size (in bytes, which equals frame_size * 2) + * + * For playback, we never transmit more than the transmit FIFO + * watermark, otherwise we might write more data than the FIFO can hold. + * The watermark is equal to the FIFO depth minus two. + * + * For capture, two equations must hold: + * w > f - (b / s) + * w >= b / s + * + * So, b > 2 * s, but b must also be <= s * w. To simplify, we set + * b = s * w, which is equal to + * (dma_private->ssi_fifo_depth - 2) * sample_bytes. + */ + mr |= CCSR_DMA_MR_BWC((dma_private->ssi_fifo_depth - 2) * sample_bytes); + + out_be32(&dma_channel->mr, mr); + + for (i = 0; i < NUM_DMA_LINKS; i++) { + struct fsl_dma_link_descriptor *link = &dma_private->link[i]; + + link->count = cpu_to_be32(period_size); + + /* The snoop bit tells the DMA controller whether it should tell + * the ECM to snoop during a read or write to an address. For + * audio, we use DMA to transfer data between memory and an I/O + * device (the SSI's STX0 or SRX0 register). Snooping is only + * needed if there is a cache, so we need to snoop memory + * addresses only. For playback, that means we snoop the source + * but not the destination. For capture, we snoop the + * destination but not the source. + * + * Note that failing to snoop properly is unlikely to cause + * cache incoherency if the period size is larger than the + * size of L1 cache. This is because filling in one period will + * flush out the data for the previous period. So if you + * increased period_bytes_min to a large enough size, you might + * get more performance by not snooping, and you'll still be + * okay. You'll need to update fsl_dma_update_pointers() also. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + link->source_addr = cpu_to_be32(temp_addr); + link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP | + upper_32_bits(temp_addr)); + + link->dest_addr = cpu_to_be32(ssi_sxx_phys); + link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP | + upper_32_bits(ssi_sxx_phys)); + } else { + link->source_addr = cpu_to_be32(ssi_sxx_phys); + link->source_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP | + upper_32_bits(ssi_sxx_phys)); + + link->dest_addr = cpu_to_be32(temp_addr); + link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP | + upper_32_bits(temp_addr)); + } + + temp_addr += period_size; + } + + return 0; +} + +/** + * fsl_dma_pointer: determine the current position of the DMA transfer + * + * This function is called by ALSA when ALSA wants to know where in the + * stream buffer the hardware currently is. + * + * For playback, the SAR register contains the physical address of the most + * recent DMA transfer. For capture, the value is in the DAR register. + * + * The base address of the buffer is stored in the source_addr field of the + * first link descriptor. + */ +static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + dma_addr_t position; + snd_pcm_uframes_t frames; + + /* Obtain the current DMA pointer, but don't read the ESAD bits if we + * only have 32-bit DMA addresses. This function is typically called + * in interrupt context, so we need to optimize it. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + position = in_be32(&dma_channel->sar); +#ifdef CONFIG_PHYS_64BIT + position |= (u64)(in_be32(&dma_channel->satr) & + CCSR_DMA_ATR_ESAD_MASK) << 32; +#endif + } else { + position = in_be32(&dma_channel->dar); +#ifdef CONFIG_PHYS_64BIT + position |= (u64)(in_be32(&dma_channel->datr) & + CCSR_DMA_ATR_ESAD_MASK) << 32; +#endif + } + + /* + * When capture is started, the SSI immediately starts to fill its FIFO. + * This means that the DMA controller is not started until the FIFO is + * full. However, ALSA calls this function before that happens, when + * MR.DAR is still zero. In this case, just return zero to indicate + * that nothing has been received yet. + */ + if (!position) + return 0; + + if ((position < dma_private->dma_buf_phys) || + (position > dma_private->dma_buf_end)) { + dev_err(dev, "dma pointer is out of range, halting stream\n"); + return SNDRV_PCM_POS_XRUN; + } + + frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys); + + /* + * If the current address is just past the end of the buffer, wrap it + * around. + */ + if (frames == runtime->buffer_size) + frames = 0; + + return frames; +} + +/** + * fsl_dma_hw_free: release resources allocated in fsl_dma_hw_params() + * + * Release the resources allocated in fsl_dma_hw_params() and de-program the + * registers. + * + * This function can be called multiple times. + */ +static int fsl_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + + if (dma_private) { + struct ccsr_dma_channel __iomem *dma_channel; + + dma_channel = dma_private->dma_channel; + + /* Stop the DMA */ + out_be32(&dma_channel->mr, CCSR_DMA_MR_CA); + out_be32(&dma_channel->mr, 0); + + /* Reset all the other registers */ + out_be32(&dma_channel->sr, -1); + out_be32(&dma_channel->clndar, 0); + out_be32(&dma_channel->eclndar, 0); + out_be32(&dma_channel->satr, 0); + out_be32(&dma_channel->sar, 0); + out_be32(&dma_channel->datr, 0); + out_be32(&dma_channel->dar, 0); + out_be32(&dma_channel->bcr, 0); + out_be32(&dma_channel->nlndar, 0); + out_be32(&dma_channel->enlndar, 0); + } + + return 0; +} + +/** + * fsl_dma_close: close the stream. + */ +static int fsl_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->platform->dev; + struct dma_object *dma = + container_of(rtd->platform->driver, struct dma_object, dai); + + if (dma_private) { + if (dma_private->irq) + free_irq(dma_private->irq, dma_private); + + /* Deallocate the fsl_dma_private structure */ + dma_free_coherent(dev, sizeof(struct fsl_dma_private), + dma_private, dma_private->ld_buf_phys); + substream->runtime->private_data = NULL; + } + + dma->assigned = 0; + + return 0; +} + +/* + * Remove this PCM driver. + */ +static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) { + substream = pcm->streams[i].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +/** + * find_ssi_node -- returns the SSI node that points to its DMA channel node + * + * Although this DMA driver attempts to operate independently of the other + * devices, it still needs to determine some information about the SSI device + * that it's working with. Unfortunately, the device tree does not contain + * a pointer from the DMA channel node to the SSI node -- the pointer goes the + * other way. So we need to scan the device tree for SSI nodes until we find + * the one that points to the given DMA channel node. It's ugly, but at least + * it's contained in this one function. + */ +static struct device_node *find_ssi_node(struct device_node *dma_channel_np) +{ + struct device_node *ssi_np, *np; + + for_each_compatible_node(ssi_np, NULL, "fsl,mpc8610-ssi") { + /* Check each DMA phandle to see if it points to us. We + * assume that device_node pointers are a valid comparison. + */ + np = of_parse_phandle(ssi_np, "fsl,playback-dma", 0); + of_node_put(np); + if (np == dma_channel_np) + return ssi_np; + + np = of_parse_phandle(ssi_np, "fsl,capture-dma", 0); + of_node_put(np); + if (np == dma_channel_np) + return ssi_np; + } + + return NULL; +} + +static struct snd_pcm_ops fsl_dma_ops = { + .open = fsl_dma_open, + .close = fsl_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsl_dma_hw_params, + .hw_free = fsl_dma_hw_free, + .pointer = fsl_dma_pointer, +}; + +static int fsl_soc_dma_probe(struct platform_device *pdev) + { + struct dma_object *dma; + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np; + struct resource res; + const uint32_t *iprop; + int ret; + + /* Find the SSI node that points to us. */ + ssi_np = find_ssi_node(np); + if (!ssi_np) { + dev_err(&pdev->dev, "cannot find parent SSI node\n"); + return -ENODEV; + } + + ret = of_address_to_resource(ssi_np, 0, &res); + if (ret) { + dev_err(&pdev->dev, "could not determine resources for %s\n", + ssi_np->full_name); + of_node_put(ssi_np); + return ret; + } + + dma = kzalloc(sizeof(*dma) + strlen(np->full_name), GFP_KERNEL); + if (!dma) { + dev_err(&pdev->dev, "could not allocate dma object\n"); + of_node_put(ssi_np); + return -ENOMEM; + } + + strcpy(dma->path, np->full_name); + dma->dai.ops = &fsl_dma_ops; + dma->dai.pcm_new = fsl_dma_new; + dma->dai.pcm_free = fsl_dma_free_dma_buffers; + + /* Store the SSI-specific information that we need */ + dma->ssi_stx_phys = res.start + CCSR_SSI_STX0; + dma->ssi_srx_phys = res.start + CCSR_SSI_SRX0; + + iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL); + if (iprop) + dma->ssi_fifo_depth = be32_to_cpup(iprop); + else + /* Older 8610 DTs didn't have the fifo-depth property */ + dma->ssi_fifo_depth = 8; + + of_node_put(ssi_np); + + ret = snd_soc_register_platform(&pdev->dev, &dma->dai); + if (ret) { + dev_err(&pdev->dev, "could not register platform\n"); + kfree(dma); + return ret; + } + + dma->channel = of_iomap(np, 0); + dma->irq = irq_of_parse_and_map(np, 0); + + dev_set_drvdata(&pdev->dev, dma); + + return 0; +} + +static int fsl_soc_dma_remove(struct platform_device *pdev) +{ + struct dma_object *dma = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_platform(&pdev->dev); + iounmap(dma->channel); + irq_dispose_mapping(dma->irq); + kfree(dma); + + return 0; +} + +static const struct of_device_id fsl_soc_dma_ids[] = { + { .compatible = "fsl,ssi-dma-channel", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_soc_dma_ids); + +static struct platform_driver fsl_soc_dma_driver = { + .driver = { + .name = "fsl-pcm-audio", + .of_match_table = fsl_soc_dma_ids, + }, + .probe = fsl_soc_dma_probe, + .remove = fsl_soc_dma_remove, +}; + +module_platform_driver(fsl_soc_dma_driver); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h new file mode 100644 index 000000000..78fee97e8 --- /dev/null +++ b/sound/soc/fsl/fsl_dma.h @@ -0,0 +1,129 @@ +/* + * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC + * + * 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 _MPC8610_PCM_H +#define _MPC8610_PCM_H + +struct ccsr_dma { + u8 res0[0x100]; + struct ccsr_dma_channel { + __be32 mr; /* Mode register */ + __be32 sr; /* Status register */ + __be32 eclndar; /* Current link descriptor extended addr reg */ + __be32 clndar; /* Current link descriptor address register */ + __be32 satr; /* Source attributes register */ + __be32 sar; /* Source address register */ + __be32 datr; /* Destination attributes register */ + __be32 dar; /* Destination address register */ + __be32 bcr; /* Byte count register */ + __be32 enlndar; /* Next link descriptor extended address reg */ + __be32 nlndar; /* Next link descriptor address register */ + u8 res1[4]; + __be32 eclsdar; /* Current list descriptor extended addr reg */ + __be32 clsdar; /* Current list descriptor address register */ + __be32 enlsdar; /* Next list descriptor extended address reg */ + __be32 nlsdar; /* Next list descriptor address register */ + __be32 ssr; /* Source stride register */ + __be32 dsr; /* Destination stride register */ + u8 res2[0x38]; + } channel[4]; + __be32 dgsr; +}; + +#define CCSR_DMA_MR_BWC_DISABLED 0x0F000000 +#define CCSR_DMA_MR_BWC_SHIFT 24 +#define CCSR_DMA_MR_BWC_MASK 0x0F000000 +#define CCSR_DMA_MR_BWC(x) \ + ((ilog2(x) << CCSR_DMA_MR_BWC_SHIFT) & CCSR_DMA_MR_BWC_MASK) +#define CCSR_DMA_MR_EMP_EN 0x00200000 +#define CCSR_DMA_MR_EMS_EN 0x00040000 +#define CCSR_DMA_MR_DAHTS_MASK 0x00030000 +#define CCSR_DMA_MR_DAHTS_1 0x00000000 +#define CCSR_DMA_MR_DAHTS_2 0x00010000 +#define CCSR_DMA_MR_DAHTS_4 0x00020000 +#define CCSR_DMA_MR_DAHTS_8 0x00030000 +#define CCSR_DMA_MR_SAHTS_MASK 0x0000C000 +#define CCSR_DMA_MR_SAHTS_1 0x00000000 +#define CCSR_DMA_MR_SAHTS_2 0x00004000 +#define CCSR_DMA_MR_SAHTS_4 0x00008000 +#define CCSR_DMA_MR_SAHTS_8 0x0000C000 +#define CCSR_DMA_MR_DAHE 0x00002000 +#define CCSR_DMA_MR_SAHE 0x00001000 +#define CCSR_DMA_MR_SRW 0x00000400 +#define CCSR_DMA_MR_EOSIE 0x00000200 +#define CCSR_DMA_MR_EOLNIE 0x00000100 +#define CCSR_DMA_MR_EOLSIE 0x00000080 +#define CCSR_DMA_MR_EIE 0x00000040 +#define CCSR_DMA_MR_XFE 0x00000020 +#define CCSR_DMA_MR_CDSM_SWSM 0x00000010 +#define CCSR_DMA_MR_CA 0x00000008 +#define CCSR_DMA_MR_CTM 0x00000004 +#define CCSR_DMA_MR_CC 0x00000002 +#define CCSR_DMA_MR_CS 0x00000001 + +#define CCSR_DMA_SR_TE 0x00000080 +#define CCSR_DMA_SR_CH 0x00000020 +#define CCSR_DMA_SR_PE 0x00000010 +#define CCSR_DMA_SR_EOLNI 0x00000008 +#define CCSR_DMA_SR_CB 0x00000004 +#define CCSR_DMA_SR_EOSI 0x00000002 +#define CCSR_DMA_SR_EOLSI 0x00000001 + +/* ECLNDAR takes bits 32-36 of the CLNDAR register */ +static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x) +{ + return (x >> 32) & 0xf; +} + +#define CCSR_DMA_CLNDAR_ADDR(x) ((x) & 0xFFFFFFFE) +#define CCSR_DMA_CLNDAR_EOSIE 0x00000008 + +/* SATR and DATR, combined */ +#define CCSR_DMA_ATR_PBATMU 0x20000000 +#define CCSR_DMA_ATR_TFLOWLVL_0 0x00000000 +#define CCSR_DMA_ATR_TFLOWLVL_1 0x06000000 +#define CCSR_DMA_ATR_TFLOWLVL_2 0x08000000 +#define CCSR_DMA_ATR_TFLOWLVL_3 0x0C000000 +#define CCSR_DMA_ATR_PCIORDER 0x02000000 +#define CCSR_DMA_ATR_SME 0x01000000 +#define CCSR_DMA_ATR_NOSNOOP 0x00040000 +#define CCSR_DMA_ATR_SNOOP 0x00050000 +#define CCSR_DMA_ATR_ESAD_MASK 0x0000000F + +/** + * List Descriptor for extended chaining mode DMA operations. + * + * The CLSDAR register points to the first (in a linked-list) List + * Descriptor. Each object must be aligned on a 32-byte boundary. Each + * list descriptor points to a linked-list of link Descriptors. + */ +struct fsl_dma_list_descriptor { + __be64 next; /* Address of next list descriptor */ + __be64 first_link; /* Address of first link descriptor */ + __be32 source; /* Source stride */ + __be32 dest; /* Destination stride */ + u8 res[8]; /* Reserved */ +} __attribute__ ((aligned(32), packed)); + +/** + * Link Descriptor for basic and extended chaining mode DMA operations. + * + * A Link Descriptor points to a single DMA buffer. Each link descriptor + * must be aligned on a 32-byte boundary. + */ +struct fsl_dma_link_descriptor { + __be32 source_attr; /* Programmed into SATR register */ + __be32 source_addr; /* Programmed into SAR register */ + __be32 dest_attr; /* Programmed into DATR register */ + __be32 dest_addr; /* Programmed into DAR register */ + __be64 next; /* Address of next link descriptor */ + __be32 count; /* Byte count */ + u8 res[4]; /* Reserved */ +} __attribute__ ((aligned(32), packed)); + +#endif diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c new file mode 100644 index 000000000..5c7597191 --- /dev/null +++ b/sound/soc/fsl/fsl_esai.c @@ -0,0 +1,869 @@ +/* + * Freescale ESAI ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/dmaengine.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_esai.h" +#include "imx-pcm.h" + +#define FSL_ESAI_RATES SNDRV_PCM_RATE_8000_192000 +#define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/** + * fsl_esai: ESAI private data + * + * @dma_params_rx: DMA parameters for receive channel + * @dma_params_tx: DMA parameters for transmit channel + * @pdev: platform device pointer + * @regmap: regmap handler + * @coreclk: clock source to access register + * @extalclk: esai clock source to derive HCK, SCK and FS + * @fsysclk: system clock source to derive HCK, SCK and FS + * @fifo_depth: depth of tx/rx FIFO + * @slot_width: width of each DAI slot + * @slots: number of slots + * @hck_rate: clock rate of desired HCKx clock + * @sck_rate: clock rate of desired SCKx clock + * @hck_dir: the direction of HCKx pads + * @sck_div: if using PSR/PM dividers for SCKx clock + * @slave_mode: if fully using DAI slave mode + * @synchronous: if using tx/rx synchronous mode + * @name: driver name + */ +struct fsl_esai { + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct platform_device *pdev; + struct regmap *regmap; + struct clk *coreclk; + struct clk *extalclk; + struct clk *fsysclk; + u32 fifo_depth; + u32 slot_width; + u32 slots; + u32 hck_rate[2]; + u32 sck_rate[2]; + bool hck_dir[2]; + bool sck_div[2]; + bool slave_mode; + bool synchronous; + char name[32]; +}; + +static irqreturn_t esai_isr(int irq, void *devid) +{ + struct fsl_esai *esai_priv = (struct fsl_esai *)devid; + struct platform_device *pdev = esai_priv->pdev; + u32 esr; + + regmap_read(esai_priv->regmap, REG_ESAI_ESR, &esr); + + if (esr & ESAI_ESR_TINIT_MASK) + dev_dbg(&pdev->dev, "isr: Transmition Initialized\n"); + + if (esr & ESAI_ESR_RFF_MASK) + dev_warn(&pdev->dev, "isr: Receiving overrun\n"); + + if (esr & ESAI_ESR_TFE_MASK) + dev_warn(&pdev->dev, "isr: Transmition underrun\n"); + + if (esr & ESAI_ESR_TLS_MASK) + dev_dbg(&pdev->dev, "isr: Just transmitted the last slot\n"); + + if (esr & ESAI_ESR_TDE_MASK) + dev_dbg(&pdev->dev, "isr: Transmition data exception\n"); + + if (esr & ESAI_ESR_TED_MASK) + dev_dbg(&pdev->dev, "isr: Transmitting even slots\n"); + + if (esr & ESAI_ESR_TD_MASK) + dev_dbg(&pdev->dev, "isr: Transmitting data\n"); + + if (esr & ESAI_ESR_RLS_MASK) + dev_dbg(&pdev->dev, "isr: Just received the last slot\n"); + + if (esr & ESAI_ESR_RDE_MASK) + dev_dbg(&pdev->dev, "isr: Receiving data exception\n"); + + if (esr & ESAI_ESR_RED_MASK) + dev_dbg(&pdev->dev, "isr: Receiving even slots\n"); + + if (esr & ESAI_ESR_RD_MASK) + dev_dbg(&pdev->dev, "isr: Receiving data\n"); + + return IRQ_HANDLED; +} + +/** + * This function is used to calculate the divisors of psr, pm, fp and it is + * supposed to be called in set_dai_sysclk() and set_bclk(). + * + * @ratio: desired overall ratio for the paticipating dividers + * @usefp: for HCK setting, there is no need to set fp divider + * @fp: bypass other dividers by setting fp directly if fp != 0 + * @tx: current setting is for playback or capture + */ +static int fsl_esai_divisor_cal(struct snd_soc_dai *dai, bool tx, u32 ratio, + bool usefp, u32 fp) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + u32 psr, pm = 999, maxfp, prod, sub, savesub, i, j; + + maxfp = usefp ? 16 : 1; + + if (usefp && fp) + goto out_fp; + + if (ratio > 2 * 8 * 256 * maxfp || ratio < 2) { + dev_err(dai->dev, "the ratio is out of range (2 ~ %d)\n", + 2 * 8 * 256 * maxfp); + return -EINVAL; + } else if (ratio % 2) { + dev_err(dai->dev, "the raio must be even if using upper divider\n"); + return -EINVAL; + } + + ratio /= 2; + + psr = ratio <= 256 * maxfp ? ESAI_xCCR_xPSR_BYPASS : ESAI_xCCR_xPSR_DIV8; + + /* Set the max fluctuation -- 0.1% of the max devisor */ + savesub = (psr ? 1 : 8) * 256 * maxfp / 1000; + + /* Find the best value for PM */ + for (i = 1; i <= 256; i++) { + for (j = 1; j <= maxfp; j++) { + /* PSR (1 or 8) * PM (1 ~ 256) * FP (1 ~ 16) */ + prod = (psr ? 1 : 8) * i * j; + + if (prod == ratio) + sub = 0; + else if (prod / ratio == 1) + sub = prod - ratio; + else if (ratio / prod == 1) + sub = ratio - prod; + else + continue; + + /* Calculate the fraction */ + sub = sub * 1000 / ratio; + if (sub < savesub) { + savesub = sub; + pm = i; + fp = j; + } + + /* We are lucky */ + if (savesub == 0) + goto out; + } + } + + if (pm == 999) { + dev_err(dai->dev, "failed to calculate proper divisors\n"); + return -EINVAL; + } + +out: + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx), + ESAI_xCCR_xPSR_MASK | ESAI_xCCR_xPM_MASK, + psr | ESAI_xCCR_xPM(pm)); + +out_fp: + /* Bypass fp if not being required */ + if (maxfp <= 1) + return 0; + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx), + ESAI_xCCR_xFP_MASK, ESAI_xCCR_xFP(fp)); + + return 0; +} + +/** + * This function mainly configures the clock frequency of MCLK (HCKT/HCKR) + * + * @Parameters: + * clk_id: The clock source of HCKT/HCKR + * (Input from outside; output from inside, FSYS or EXTAL) + * freq: The required clock rate of HCKT/HCKR + * dir: The clock direction of HCKT/HCKR + * + * Note: If the direction is input, we do not care about clk_id. + */ +static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + struct clk *clksrc = esai_priv->extalclk; + bool tx = clk_id <= ESAI_HCKT_EXTAL; + bool in = dir == SND_SOC_CLOCK_IN; + u32 ratio, ecr = 0; + unsigned long clk_rate; + int ret; + + /* Bypass divider settings if the requirement doesn't change */ + if (freq == esai_priv->hck_rate[tx] && dir == esai_priv->hck_dir[tx]) + return 0; + + /* sck_div can be only bypassed if ETO/ERO=0 and SNC_SOC_CLOCK_OUT */ + esai_priv->sck_div[tx] = true; + + /* Set the direction of HCKT/HCKR pins */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx), + ESAI_xCCR_xHCKD, in ? 0 : ESAI_xCCR_xHCKD); + + if (in) + goto out; + + switch (clk_id) { + case ESAI_HCKT_FSYS: + case ESAI_HCKR_FSYS: + clksrc = esai_priv->fsysclk; + break; + case ESAI_HCKT_EXTAL: + ecr |= ESAI_ECR_ETI; + case ESAI_HCKR_EXTAL: + ecr |= ESAI_ECR_ERI; + break; + default: + return -EINVAL; + } + + if (IS_ERR(clksrc)) { + dev_err(dai->dev, "no assigned %s clock\n", + clk_id % 2 ? "extal" : "fsys"); + return PTR_ERR(clksrc); + } + clk_rate = clk_get_rate(clksrc); + + ratio = clk_rate / freq; + if (ratio * freq > clk_rate) + ret = ratio * freq - clk_rate; + else if (ratio * freq < clk_rate) + ret = clk_rate - ratio * freq; + else + ret = 0; + + /* Block if clock source can not be divided into the required rate */ + if (ret != 0 && clk_rate / ret < 1000) { + dev_err(dai->dev, "failed to derive required HCK%c rate\n", + tx ? 'T' : 'R'); + return -EINVAL; + } + + /* Only EXTAL source can be output directly without using PSR and PM */ + if (ratio == 1 && clksrc == esai_priv->extalclk) { + /* Bypass all the dividers if not being needed */ + ecr |= tx ? ESAI_ECR_ETO : ESAI_ECR_ERO; + goto out; + } else if (ratio < 2) { + /* The ratio should be no less than 2 if using other sources */ + dev_err(dai->dev, "failed to derive required HCK%c rate\n", + tx ? 'T' : 'R'); + return -EINVAL; + } + + ret = fsl_esai_divisor_cal(dai, tx, ratio, false, 0); + if (ret) + return ret; + + esai_priv->sck_div[tx] = false; + +out: + esai_priv->hck_dir[tx] = dir; + esai_priv->hck_rate[tx] = freq; + + regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR, + tx ? ESAI_ECR_ETI | ESAI_ECR_ETO : + ESAI_ECR_ERI | ESAI_ECR_ERO, ecr); + + return 0; +} + +/** + * This function configures the related dividers according to the bclk rate + */ +static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + u32 hck_rate = esai_priv->hck_rate[tx]; + u32 sub, ratio = hck_rate / freq; + int ret; + + /* Don't apply for fully slave mode or unchanged bclk */ + if (esai_priv->slave_mode || esai_priv->sck_rate[tx] == freq) + return 0; + + if (ratio * freq > hck_rate) + sub = ratio * freq - hck_rate; + else if (ratio * freq < hck_rate) + sub = hck_rate - ratio * freq; + else + sub = 0; + + /* Block if clock source can not be divided into the required rate */ + if (sub != 0 && hck_rate / sub < 1000) { + dev_err(dai->dev, "failed to derive required SCK%c rate\n", + tx ? 'T' : 'R'); + return -EINVAL; + } + + /* The ratio should be contented by FP alone if bypassing PM and PSR */ + if (!esai_priv->sck_div[tx] && (ratio > 16 || ratio == 0)) { + dev_err(dai->dev, "the ratio is out of range (1 ~ 16)\n"); + return -EINVAL; + } + + ret = fsl_esai_divisor_cal(dai, tx, ratio, true, + esai_priv->sck_div[tx] ? 0 : ratio); + if (ret) + return ret; + + /* Save current bclk rate */ + esai_priv->sck_rate[tx] = freq; + + return 0; +} + +static int fsl_esai_set_dai_tdm_slot(struct snd_soc_dai *dai, u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, + ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots)); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMA, + ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(tx_mask)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMB, + ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(tx_mask)); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, + ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots)); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMA, + ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(rx_mask)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMB, + ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(rx_mask)); + + esai_priv->slot_width = slot_width; + esai_priv->slots = slots; + + return 0; +} + +static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + u32 xcr = 0, xccr = 0, mask; + + /* 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 */ + xcr |= ESAI_xCR_xFSR; + xccr |= ESAI_xCCR_xFSP | ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* Data on rising edge of bclk, frame high */ + xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + case SND_SOC_DAIFMT_RIGHT_J: + /* Data on rising edge of bclk, frame high, right aligned */ + xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCR_xWA; + break; + case SND_SOC_DAIFMT_DSP_A: + /* Data on rising edge of bclk, frame high, 1clk before data */ + xcr |= ESAI_xCR_xFSL | ESAI_xCR_xFSR; + xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + case SND_SOC_DAIFMT_DSP_B: + /* Data on rising edge of bclk, frame high */ + xcr |= ESAI_xCR_xFSL; + xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do for both normal cases */ + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + xccr ^= ESAI_xCCR_xFSP; + break; + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP; + break; + default: + return -EINVAL; + } + + esai_priv->slave_mode = false; + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + esai_priv->slave_mode = true; + break; + case SND_SOC_DAIFMT_CBS_CFM: + xccr |= ESAI_xCCR_xCKD; + break; + case SND_SOC_DAIFMT_CBM_CFS: + xccr |= ESAI_xCCR_xFSD; + break; + case SND_SOC_DAIFMT_CBS_CFS: + xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + break; + default: + return -EINVAL; + } + + mask = ESAI_xCR_xFSL | ESAI_xCR_xFSR; + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, xcr); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, mask, xcr); + + mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | + ESAI_xCCR_xFSD | ESAI_xCCR_xCKD | ESAI_xCR_xWA; + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, mask, xccr); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, mask, xccr); + + return 0; +} + +static int fsl_esai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + int ret; + + /* + * Some platforms might use the same bit to gate all three or two of + * clocks, so keep all clocks open/close at the same time for safety + */ + ret = clk_prepare_enable(esai_priv->coreclk); + if (ret) + return ret; + if (!IS_ERR(esai_priv->extalclk)) { + ret = clk_prepare_enable(esai_priv->extalclk); + if (ret) + goto err_extalck; + } + if (!IS_ERR(esai_priv->fsysclk)) { + ret = clk_prepare_enable(esai_priv->fsysclk); + if (ret) + goto err_fsysclk; + } + + if (!dai->active) { + /* Set synchronous mode */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_SAICR, + ESAI_SAICR_SYNC, esai_priv->synchronous ? + ESAI_SAICR_SYNC : 0); + + /* Set a default slot number -- 2 */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, + ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, + ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2)); + } + + return 0; + +err_fsysclk: + if (!IS_ERR(esai_priv->extalclk)) + clk_disable_unprepare(esai_priv->extalclk); +err_extalck: + clk_disable_unprepare(esai_priv->coreclk); + + return ret; +} + +static int fsl_esai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 width = snd_pcm_format_width(params_format(params)); + u32 channels = params_channels(params); + u32 pins = DIV_ROUND_UP(channels, esai_priv->slots); + u32 slot_width = width; + u32 bclk, mask, val; + int ret; + + /* Override slot_width if being specifially set */ + if (esai_priv->slot_width) + slot_width = esai_priv->slot_width; + + bclk = params_rate(params) * slot_width * esai_priv->slots; + + ret = fsl_esai_set_bclk(dai, tx, bclk); + if (ret) + return ret; + + /* Use Normal mode to support monaural audio */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ? + ESAI_xCR_xMOD_NETWORK : 0); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR); + + mask = ESAI_xFCR_xFR_MASK | ESAI_xFCR_xWA_MASK | ESAI_xFCR_xFWM_MASK | + (tx ? ESAI_xFCR_TE_MASK | ESAI_xFCR_TIEN : ESAI_xFCR_RE_MASK); + val = ESAI_xFCR_xWA(width) | ESAI_xFCR_xFWM(esai_priv->fifo_depth) | + (tx ? ESAI_xFCR_TE(pins) | ESAI_xFCR_TIEN : ESAI_xFCR_RE(pins)); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), mask, val); + + mask = ESAI_xCR_xSWS_MASK | (tx ? ESAI_xCR_PADC : 0); + val = ESAI_xCR_xSWS(slot_width, width) | (tx ? ESAI_xCR_PADC : 0); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val); + + /* Remove ESAI personal reset by configuring ESAI_PCRC and ESAI_PRRC */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, + ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, + ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO)); + return 0; +} + +static void fsl_esai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + + if (!IS_ERR(esai_priv->fsysclk)) + clk_disable_unprepare(esai_priv->fsysclk); + if (!IS_ERR(esai_priv->extalclk)) + clk_disable_unprepare(esai_priv->extalclk); + clk_disable_unprepare(esai_priv->coreclk); +} + +static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 i, channels = substream->runtime->channels; + u32 pins = DIV_ROUND_UP(channels, esai_priv->slots); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFEN_MASK, ESAI_xFCR_xFEN); + + /* Write initial words reqiured by ESAI as normal procedure */ + for (i = 0; tx && i < channels; i++) + regmap_write(esai_priv->regmap, REG_ESAI_ETDR, 0x0); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, + tx ? ESAI_xCR_TE(pins) : ESAI_xCR_RE(pins)); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, 0); + + /* Disable and reset FIFO */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFR | ESAI_xFCR_xFEN, ESAI_xFCR_xFR); + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFR, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops fsl_esai_dai_ops = { + .startup = fsl_esai_startup, + .shutdown = fsl_esai_shutdown, + .trigger = fsl_esai_trigger, + .hw_params = fsl_esai_hw_params, + .set_sysclk = fsl_esai_set_dai_sysclk, + .set_fmt = fsl_esai_set_dai_fmt, + .set_tdm_slot = fsl_esai_set_dai_tdm_slot, +}; + +static int fsl_esai_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &esai_priv->dma_params_tx, + &esai_priv->dma_params_rx); + + return 0; +} + +static struct snd_soc_dai_driver fsl_esai_dai = { + .probe = fsl_esai_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 12, + .rates = FSL_ESAI_RATES, + .formats = FSL_ESAI_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = FSL_ESAI_RATES, + .formats = FSL_ESAI_FORMATS, + }, + .ops = &fsl_esai_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_esai_component = { + .name = "fsl-esai", +}; + +static bool fsl_esai_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ESAI_ERDR: + case REG_ESAI_ECR: + case REG_ESAI_ESR: + case REG_ESAI_TFCR: + case REG_ESAI_TFSR: + case REG_ESAI_RFCR: + case REG_ESAI_RFSR: + case REG_ESAI_RX0: + case REG_ESAI_RX1: + case REG_ESAI_RX2: + case REG_ESAI_RX3: + case REG_ESAI_SAISR: + case REG_ESAI_SAICR: + case REG_ESAI_TCR: + case REG_ESAI_TCCR: + case REG_ESAI_RCR: + case REG_ESAI_RCCR: + case REG_ESAI_TSMA: + case REG_ESAI_TSMB: + case REG_ESAI_RSMA: + case REG_ESAI_RSMB: + case REG_ESAI_PRRC: + case REG_ESAI_PCRC: + return true; + default: + return false; + } +} + +static bool fsl_esai_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_ESAI_ETDR: + case REG_ESAI_ECR: + case REG_ESAI_TFCR: + case REG_ESAI_RFCR: + case REG_ESAI_TX0: + case REG_ESAI_TX1: + case REG_ESAI_TX2: + case REG_ESAI_TX3: + case REG_ESAI_TX4: + case REG_ESAI_TX5: + case REG_ESAI_TSR: + case REG_ESAI_SAICR: + case REG_ESAI_TCR: + case REG_ESAI_TCCR: + case REG_ESAI_RCR: + case REG_ESAI_RCCR: + case REG_ESAI_TSMA: + case REG_ESAI_TSMB: + case REG_ESAI_RSMA: + case REG_ESAI_RSMB: + case REG_ESAI_PRRC: + case REG_ESAI_PCRC: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_esai_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_ESAI_PCRC, + .readable_reg = fsl_esai_readable_reg, + .writeable_reg = fsl_esai_writeable_reg, +}; + +static int fsl_esai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_esai *esai_priv; + struct resource *res; + const uint32_t *iprop; + void __iomem *regs; + int irq, ret; + + esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL); + if (!esai_priv) + return -ENOMEM; + + esai_priv->pdev = pdev; + strncpy(esai_priv->name, np->name, sizeof(esai_priv->name) - 1); + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + esai_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "core", regs, &fsl_esai_regmap_config); + if (IS_ERR(esai_priv->regmap)) { + dev_err(&pdev->dev, "failed to init regmap: %ld\n", + PTR_ERR(esai_priv->regmap)); + return PTR_ERR(esai_priv->regmap); + } + + esai_priv->coreclk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(esai_priv->coreclk)) { + dev_err(&pdev->dev, "failed to get core clock: %ld\n", + PTR_ERR(esai_priv->coreclk)); + return PTR_ERR(esai_priv->coreclk); + } + + esai_priv->extalclk = devm_clk_get(&pdev->dev, "extal"); + if (IS_ERR(esai_priv->extalclk)) + dev_warn(&pdev->dev, "failed to get extal clock: %ld\n", + PTR_ERR(esai_priv->extalclk)); + + esai_priv->fsysclk = devm_clk_get(&pdev->dev, "fsys"); + if (IS_ERR(esai_priv->fsysclk)) + dev_warn(&pdev->dev, "failed to get fsys clock: %ld\n", + PTR_ERR(esai_priv->fsysclk)); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0, + esai_priv->name, esai_priv); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u\n", irq); + return ret; + } + + /* Set a default slot number */ + esai_priv->slots = 2; + + /* Set a default master/slave state */ + esai_priv->slave_mode = true; + + /* Determine the FIFO depth */ + iprop = of_get_property(np, "fsl,fifo-depth", NULL); + if (iprop) + esai_priv->fifo_depth = be32_to_cpup(iprop); + else + esai_priv->fifo_depth = 64; + + esai_priv->dma_params_tx.maxburst = 16; + esai_priv->dma_params_rx.maxburst = 16; + esai_priv->dma_params_tx.addr = res->start + REG_ESAI_ETDR; + esai_priv->dma_params_rx.addr = res->start + REG_ESAI_ERDR; + + esai_priv->synchronous = + of_property_read_bool(np, "fsl,esai-synchronous"); + + /* Implement full symmetry for synchronous mode */ + if (esai_priv->synchronous) { + fsl_esai_dai.symmetric_rates = 1; + fsl_esai_dai.symmetric_channels = 1; + fsl_esai_dai.symmetric_samplebits = 1; + } + + dev_set_drvdata(&pdev->dev, esai_priv); + + /* Reset ESAI unit */ + ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ERST); + if (ret) { + dev_err(&pdev->dev, "failed to reset ESAI: %d\n", ret); + return ret; + } + + /* + * We need to enable ESAI so as to access some of its registers. + * Otherwise, we would fail to dump regmap from user space. + */ + ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ESAIEN); + if (ret) { + dev_err(&pdev->dev, "failed to enable ESAI: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_esai_component, + &fsl_esai_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); + return ret; + } + + ret = imx_pcm_dma_init(pdev); + if (ret) + dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + + return ret; +} + +static const struct of_device_id fsl_esai_dt_ids[] = { + { .compatible = "fsl,imx35-esai", }, + { .compatible = "fsl,vf610-esai", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids); + +static struct platform_driver fsl_esai_driver = { + .probe = fsl_esai_probe, + .driver = { + .name = "fsl-esai-dai", + .of_match_table = fsl_esai_dt_ids, + }, +}; + +module_platform_driver(fsl_esai_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale ESAI CPU DAI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:fsl-esai-dai"); diff --git a/sound/soc/fsl/fsl_esai.h b/sound/soc/fsl/fsl_esai.h new file mode 100644 index 000000000..5e793bbb6 --- /dev/null +++ b/sound/soc/fsl/fsl_esai.h @@ -0,0 +1,354 @@ +/* + * fsl_esai.h - ALSA ESAI interface for the Freescale i.MX SoC + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <Guangyu.Chen@freescale.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _FSL_ESAI_DAI_H +#define _FSL_ESAI_DAI_H + +/* ESAI Register Map */ +#define REG_ESAI_ETDR 0x00 +#define REG_ESAI_ERDR 0x04 +#define REG_ESAI_ECR 0x08 +#define REG_ESAI_ESR 0x0C +#define REG_ESAI_TFCR 0x10 +#define REG_ESAI_TFSR 0x14 +#define REG_ESAI_RFCR 0x18 +#define REG_ESAI_RFSR 0x1C +#define REG_ESAI_xFCR(tx) (tx ? REG_ESAI_TFCR : REG_ESAI_RFCR) +#define REG_ESAI_xFSR(tx) (tx ? REG_ESAI_TFSR : REG_ESAI_RFSR) +#define REG_ESAI_TX0 0x80 +#define REG_ESAI_TX1 0x84 +#define REG_ESAI_TX2 0x88 +#define REG_ESAI_TX3 0x8C +#define REG_ESAI_TX4 0x90 +#define REG_ESAI_TX5 0x94 +#define REG_ESAI_TSR 0x98 +#define REG_ESAI_RX0 0xA0 +#define REG_ESAI_RX1 0xA4 +#define REG_ESAI_RX2 0xA8 +#define REG_ESAI_RX3 0xAC +#define REG_ESAI_SAISR 0xCC +#define REG_ESAI_SAICR 0xD0 +#define REG_ESAI_TCR 0xD4 +#define REG_ESAI_TCCR 0xD8 +#define REG_ESAI_RCR 0xDC +#define REG_ESAI_RCCR 0xE0 +#define REG_ESAI_xCR(tx) (tx ? REG_ESAI_TCR : REG_ESAI_RCR) +#define REG_ESAI_xCCR(tx) (tx ? REG_ESAI_TCCR : REG_ESAI_RCCR) +#define REG_ESAI_TSMA 0xE4 +#define REG_ESAI_TSMB 0xE8 +#define REG_ESAI_RSMA 0xEC +#define REG_ESAI_RSMB 0xF0 +#define REG_ESAI_xSMA(tx) (tx ? REG_ESAI_TSMA : REG_ESAI_RSMA) +#define REG_ESAI_xSMB(tx) (tx ? REG_ESAI_TSMB : REG_ESAI_RSMB) +#define REG_ESAI_PRRC 0xF8 +#define REG_ESAI_PCRC 0xFC + +/* ESAI Control Register -- REG_ESAI_ECR 0x8 */ +#define ESAI_ECR_ETI_SHIFT 19 +#define ESAI_ECR_ETI_MASK (1 << ESAI_ECR_ETI_SHIFT) +#define ESAI_ECR_ETI (1 << ESAI_ECR_ETI_SHIFT) +#define ESAI_ECR_ETO_SHIFT 18 +#define ESAI_ECR_ETO_MASK (1 << ESAI_ECR_ETO_SHIFT) +#define ESAI_ECR_ETO (1 << ESAI_ECR_ETO_SHIFT) +#define ESAI_ECR_ERI_SHIFT 17 +#define ESAI_ECR_ERI_MASK (1 << ESAI_ECR_ERI_SHIFT) +#define ESAI_ECR_ERI (1 << ESAI_ECR_ERI_SHIFT) +#define ESAI_ECR_ERO_SHIFT 16 +#define ESAI_ECR_ERO_MASK (1 << ESAI_ECR_ERO_SHIFT) +#define ESAI_ECR_ERO (1 << ESAI_ECR_ERO_SHIFT) +#define ESAI_ECR_ERST_SHIFT 1 +#define ESAI_ECR_ERST_MASK (1 << ESAI_ECR_ERST_SHIFT) +#define ESAI_ECR_ERST (1 << ESAI_ECR_ERST_SHIFT) +#define ESAI_ECR_ESAIEN_SHIFT 0 +#define ESAI_ECR_ESAIEN_MASK (1 << ESAI_ECR_ESAIEN_SHIFT) +#define ESAI_ECR_ESAIEN (1 << ESAI_ECR_ESAIEN_SHIFT) + +/* ESAI Status Register -- REG_ESAI_ESR 0xC */ +#define ESAI_ESR_TINIT_SHIFT 10 +#define ESAI_ESR_TINIT_MASK (1 << ESAI_ESR_TINIT_SHIFT) +#define ESAI_ESR_TINIT (1 << ESAI_ESR_TINIT_SHIFT) +#define ESAI_ESR_RFF_SHIFT 9 +#define ESAI_ESR_RFF_MASK (1 << ESAI_ESR_RFF_SHIFT) +#define ESAI_ESR_RFF (1 << ESAI_ESR_RFF_SHIFT) +#define ESAI_ESR_TFE_SHIFT 8 +#define ESAI_ESR_TFE_MASK (1 << ESAI_ESR_TFE_SHIFT) +#define ESAI_ESR_TFE (1 << ESAI_ESR_TFE_SHIFT) +#define ESAI_ESR_TLS_SHIFT 7 +#define ESAI_ESR_TLS_MASK (1 << ESAI_ESR_TLS_SHIFT) +#define ESAI_ESR_TLS (1 << ESAI_ESR_TLS_SHIFT) +#define ESAI_ESR_TDE_SHIFT 6 +#define ESAI_ESR_TDE_MASK (1 << ESAI_ESR_TDE_SHIFT) +#define ESAI_ESR_TDE (1 << ESAI_ESR_TDE_SHIFT) +#define ESAI_ESR_TED_SHIFT 5 +#define ESAI_ESR_TED_MASK (1 << ESAI_ESR_TED_SHIFT) +#define ESAI_ESR_TED (1 << ESAI_ESR_TED_SHIFT) +#define ESAI_ESR_TD_SHIFT 4 +#define ESAI_ESR_TD_MASK (1 << ESAI_ESR_TD_SHIFT) +#define ESAI_ESR_TD (1 << ESAI_ESR_TD_SHIFT) +#define ESAI_ESR_RLS_SHIFT 3 +#define ESAI_ESR_RLS_MASK (1 << ESAI_ESR_RLS_SHIFT) +#define ESAI_ESR_RLS (1 << ESAI_ESR_RLS_SHIFT) +#define ESAI_ESR_RDE_SHIFT 2 +#define ESAI_ESR_RDE_MASK (1 << ESAI_ESR_RDE_SHIFT) +#define ESAI_ESR_RDE (1 << ESAI_ESR_RDE_SHIFT) +#define ESAI_ESR_RED_SHIFT 1 +#define ESAI_ESR_RED_MASK (1 << ESAI_ESR_RED_SHIFT) +#define ESAI_ESR_RED (1 << ESAI_ESR_RED_SHIFT) +#define ESAI_ESR_RD_SHIFT 0 +#define ESAI_ESR_RD_MASK (1 << ESAI_ESR_RD_SHIFT) +#define ESAI_ESR_RD (1 << ESAI_ESR_RD_SHIFT) + +/* + * Transmit FIFO Configuration Register -- REG_ESAI_TFCR 0x10 + * Receive FIFO Configuration Register -- REG_ESAI_RFCR 0x18 + */ +#define ESAI_xFCR_TIEN_SHIFT 19 +#define ESAI_xFCR_TIEN_MASK (1 << ESAI_xFCR_TIEN_SHIFT) +#define ESAI_xFCR_TIEN (1 << ESAI_xFCR_TIEN_SHIFT) +#define ESAI_xFCR_REXT_SHIFT 19 +#define ESAI_xFCR_REXT_MASK (1 << ESAI_xFCR_REXT_SHIFT) +#define ESAI_xFCR_REXT (1 << ESAI_xFCR_REXT_SHIFT) +#define ESAI_xFCR_xWA_SHIFT 16 +#define ESAI_xFCR_xWA_WIDTH 3 +#define ESAI_xFCR_xWA_MASK (((1 << ESAI_xFCR_xWA_WIDTH) - 1) << ESAI_xFCR_xWA_SHIFT) +#define ESAI_xFCR_xWA(v) (((8 - ((v) >> 2)) << ESAI_xFCR_xWA_SHIFT) & ESAI_xFCR_xWA_MASK) +#define ESAI_xFCR_xFWM_SHIFT 8 +#define ESAI_xFCR_xFWM_WIDTH 8 +#define ESAI_xFCR_xFWM_MASK (((1 << ESAI_xFCR_xFWM_WIDTH) - 1) << ESAI_xFCR_xFWM_SHIFT) +#define ESAI_xFCR_xFWM(v) ((((v) - 1) << ESAI_xFCR_xFWM_SHIFT) & ESAI_xFCR_xFWM_MASK) +#define ESAI_xFCR_xE_SHIFT 2 +#define ESAI_xFCR_TE_WIDTH 6 +#define ESAI_xFCR_RE_WIDTH 4 +#define ESAI_xFCR_TE_MASK (((1 << ESAI_xFCR_TE_WIDTH) - 1) << ESAI_xFCR_xE_SHIFT) +#define ESAI_xFCR_RE_MASK (((1 << ESAI_xFCR_RE_WIDTH) - 1) << ESAI_xFCR_xE_SHIFT) +#define ESAI_xFCR_TE(x) ((ESAI_xFCR_TE_MASK >> (ESAI_xFCR_TE_WIDTH - x)) & ESAI_xFCR_TE_MASK) +#define ESAI_xFCR_RE(x) ((ESAI_xFCR_RE_MASK >> (ESAI_xFCR_RE_WIDTH - x)) & ESAI_xFCR_RE_MASK) +#define ESAI_xFCR_xFR_SHIFT 1 +#define ESAI_xFCR_xFR_MASK (1 << ESAI_xFCR_xFR_SHIFT) +#define ESAI_xFCR_xFR (1 << ESAI_xFCR_xFR_SHIFT) +#define ESAI_xFCR_xFEN_SHIFT 0 +#define ESAI_xFCR_xFEN_MASK (1 << ESAI_xFCR_xFEN_SHIFT) +#define ESAI_xFCR_xFEN (1 << ESAI_xFCR_xFEN_SHIFT) + +/* + * Transmit FIFO Status Register -- REG_ESAI_TFSR 0x14 + * Receive FIFO Status Register --REG_ESAI_RFSR 0x1C + */ +#define ESAI_xFSR_NTFO_SHIFT 12 +#define ESAI_xFSR_NRFI_SHIFT 12 +#define ESAI_xFSR_NTFI_SHIFT 8 +#define ESAI_xFSR_NRFO_SHIFT 8 +#define ESAI_xFSR_NTFx_WIDTH 3 +#define ESAI_xFSR_NRFx_WIDTH 2 +#define ESAI_xFSR_NTFO_MASK (((1 << ESAI_xFSR_NTFx_WIDTH) - 1) << ESAI_xFSR_NTFO_SHIFT) +#define ESAI_xFSR_NTFI_MASK (((1 << ESAI_xFSR_NTFx_WIDTH) - 1) << ESAI_xFSR_NTFI_SHIFT) +#define ESAI_xFSR_NRFO_MASK (((1 << ESAI_xFSR_NRFx_WIDTH) - 1) << ESAI_xFSR_NRFO_SHIFT) +#define ESAI_xFSR_NRFI_MASK (((1 << ESAI_xFSR_NRFx_WIDTH) - 1) << ESAI_xFSR_NRFI_SHIFT) +#define ESAI_xFSR_xFCNT_SHIFT 0 +#define ESAI_xFSR_xFCNT_WIDTH 8 +#define ESAI_xFSR_xFCNT_MASK (((1 << ESAI_xFSR_xFCNT_WIDTH) - 1) << ESAI_xFSR_xFCNT_SHIFT) + +/* ESAI Transmit Slot Register -- REG_ESAI_TSR 0x98 */ +#define ESAI_TSR_SHIFT 0 +#define ESAI_TSR_WIDTH 24 +#define ESAI_TSR_MASK (((1 << ESAI_TSR_WIDTH) - 1) << ESAI_TSR_SHIFT) + +/* Serial Audio Interface Status Register -- REG_ESAI_SAISR 0xCC */ +#define ESAI_SAISR_TODFE_SHIFT 17 +#define ESAI_SAISR_TODFE_MASK (1 << ESAI_SAISR_TODFE_SHIFT) +#define ESAI_SAISR_TODFE (1 << ESAI_SAISR_TODFE_SHIFT) +#define ESAI_SAISR_TEDE_SHIFT 16 +#define ESAI_SAISR_TEDE_MASK (1 << ESAI_SAISR_TEDE_SHIFT) +#define ESAI_SAISR_TEDE (1 << ESAI_SAISR_TEDE_SHIFT) +#define ESAI_SAISR_TDE_SHIFT 15 +#define ESAI_SAISR_TDE_MASK (1 << ESAI_SAISR_TDE_SHIFT) +#define ESAI_SAISR_TDE (1 << ESAI_SAISR_TDE_SHIFT) +#define ESAI_SAISR_TUE_SHIFT 14 +#define ESAI_SAISR_TUE_MASK (1 << ESAI_SAISR_TUE_SHIFT) +#define ESAI_SAISR_TUE (1 << ESAI_SAISR_TUE_SHIFT) +#define ESAI_SAISR_TFS_SHIFT 13 +#define ESAI_SAISR_TFS_MASK (1 << ESAI_SAISR_TFS_SHIFT) +#define ESAI_SAISR_TFS (1 << ESAI_SAISR_TFS_SHIFT) +#define ESAI_SAISR_RODF_SHIFT 10 +#define ESAI_SAISR_RODF_MASK (1 << ESAI_SAISR_RODF_SHIFT) +#define ESAI_SAISR_RODF (1 << ESAI_SAISR_RODF_SHIFT) +#define ESAI_SAISR_REDF_SHIFT 9 +#define ESAI_SAISR_REDF_MASK (1 << ESAI_SAISR_REDF_SHIFT) +#define ESAI_SAISR_REDF (1 << ESAI_SAISR_REDF_SHIFT) +#define ESAI_SAISR_RDF_SHIFT 8 +#define ESAI_SAISR_RDF_MASK (1 << ESAI_SAISR_RDF_SHIFT) +#define ESAI_SAISR_RDF (1 << ESAI_SAISR_RDF_SHIFT) +#define ESAI_SAISR_ROE_SHIFT 7 +#define ESAI_SAISR_ROE_MASK (1 << ESAI_SAISR_ROE_SHIFT) +#define ESAI_SAISR_ROE (1 << ESAI_SAISR_ROE_SHIFT) +#define ESAI_SAISR_RFS_SHIFT 6 +#define ESAI_SAISR_RFS_MASK (1 << ESAI_SAISR_RFS_SHIFT) +#define ESAI_SAISR_RFS (1 << ESAI_SAISR_RFS_SHIFT) +#define ESAI_SAISR_IF2_SHIFT 2 +#define ESAI_SAISR_IF2_MASK (1 << ESAI_SAISR_IF2_SHIFT) +#define ESAI_SAISR_IF2 (1 << ESAI_SAISR_IF2_SHIFT) +#define ESAI_SAISR_IF1_SHIFT 1 +#define ESAI_SAISR_IF1_MASK (1 << ESAI_SAISR_IF1_SHIFT) +#define ESAI_SAISR_IF1 (1 << ESAI_SAISR_IF1_SHIFT) +#define ESAI_SAISR_IF0_SHIFT 0 +#define ESAI_SAISR_IF0_MASK (1 << ESAI_SAISR_IF0_SHIFT) +#define ESAI_SAISR_IF0 (1 << ESAI_SAISR_IF0_SHIFT) + +/* Serial Audio Interface Control Register -- REG_ESAI_SAICR 0xD0 */ +#define ESAI_SAICR_ALC_SHIFT 8 +#define ESAI_SAICR_ALC_MASK (1 << ESAI_SAICR_ALC_SHIFT) +#define ESAI_SAICR_ALC (1 << ESAI_SAICR_ALC_SHIFT) +#define ESAI_SAICR_TEBE_SHIFT 7 +#define ESAI_SAICR_TEBE_MASK (1 << ESAI_SAICR_TEBE_SHIFT) +#define ESAI_SAICR_TEBE (1 << ESAI_SAICR_TEBE_SHIFT) +#define ESAI_SAICR_SYNC_SHIFT 6 +#define ESAI_SAICR_SYNC_MASK (1 << ESAI_SAICR_SYNC_SHIFT) +#define ESAI_SAICR_SYNC (1 << ESAI_SAICR_SYNC_SHIFT) +#define ESAI_SAICR_OF2_SHIFT 2 +#define ESAI_SAICR_OF2_MASK (1 << ESAI_SAICR_OF2_SHIFT) +#define ESAI_SAICR_OF2 (1 << ESAI_SAICR_OF2_SHIFT) +#define ESAI_SAICR_OF1_SHIFT 1 +#define ESAI_SAICR_OF1_MASK (1 << ESAI_SAICR_OF1_SHIFT) +#define ESAI_SAICR_OF1 (1 << ESAI_SAICR_OF1_SHIFT) +#define ESAI_SAICR_OF0_SHIFT 0 +#define ESAI_SAICR_OF0_MASK (1 << ESAI_SAICR_OF0_SHIFT) +#define ESAI_SAICR_OF0 (1 << ESAI_SAICR_OF0_SHIFT) + +/* + * Transmit Control Register -- REG_ESAI_TCR 0xD4 + * Receive Control Register -- REG_ESAI_RCR 0xDC + */ +#define ESAI_xCR_xLIE_SHIFT 23 +#define ESAI_xCR_xLIE_MASK (1 << ESAI_xCR_xLIE_SHIFT) +#define ESAI_xCR_xLIE (1 << ESAI_xCR_xLIE_SHIFT) +#define ESAI_xCR_xIE_SHIFT 22 +#define ESAI_xCR_xIE_MASK (1 << ESAI_xCR_xIE_SHIFT) +#define ESAI_xCR_xIE (1 << ESAI_xCR_xIE_SHIFT) +#define ESAI_xCR_xEDIE_SHIFT 21 +#define ESAI_xCR_xEDIE_MASK (1 << ESAI_xCR_xEDIE_SHIFT) +#define ESAI_xCR_xEDIE (1 << ESAI_xCR_xEDIE_SHIFT) +#define ESAI_xCR_xEIE_SHIFT 20 +#define ESAI_xCR_xEIE_MASK (1 << ESAI_xCR_xEIE_SHIFT) +#define ESAI_xCR_xEIE (1 << ESAI_xCR_xEIE_SHIFT) +#define ESAI_xCR_xPR_SHIFT 19 +#define ESAI_xCR_xPR_MASK (1 << ESAI_xCR_xPR_SHIFT) +#define ESAI_xCR_xPR (1 << ESAI_xCR_xPR_SHIFT) +#define ESAI_xCR_PADC_SHIFT 17 +#define ESAI_xCR_PADC_MASK (1 << ESAI_xCR_PADC_SHIFT) +#define ESAI_xCR_PADC (1 << ESAI_xCR_PADC_SHIFT) +#define ESAI_xCR_xFSR_SHIFT 16 +#define ESAI_xCR_xFSR_MASK (1 << ESAI_xCR_xFSR_SHIFT) +#define ESAI_xCR_xFSR (1 << ESAI_xCR_xFSR_SHIFT) +#define ESAI_xCR_xFSL_SHIFT 15 +#define ESAI_xCR_xFSL_MASK (1 << ESAI_xCR_xFSL_SHIFT) +#define ESAI_xCR_xFSL (1 << ESAI_xCR_xFSL_SHIFT) +#define ESAI_xCR_xSWS_SHIFT 10 +#define ESAI_xCR_xSWS_WIDTH 5 +#define ESAI_xCR_xSWS_MASK (((1 << ESAI_xCR_xSWS_WIDTH) - 1) << ESAI_xCR_xSWS_SHIFT) +#define ESAI_xCR_xSWS(s, w) ((w < 24 ? (s - w + ((w - 8) >> 2)) : (s < 32 ? 0x1e : 0x1f)) << ESAI_xCR_xSWS_SHIFT) +#define ESAI_xCR_xMOD_SHIFT 8 +#define ESAI_xCR_xMOD_WIDTH 2 +#define ESAI_xCR_xMOD_MASK (((1 << ESAI_xCR_xMOD_WIDTH) - 1) << ESAI_xCR_xMOD_SHIFT) +#define ESAI_xCR_xMOD_ONDEMAND (0x1 << ESAI_xCR_xMOD_SHIFT) +#define ESAI_xCR_xMOD_NETWORK (0x1 << ESAI_xCR_xMOD_SHIFT) +#define ESAI_xCR_xMOD_AC97 (0x3 << ESAI_xCR_xMOD_SHIFT) +#define ESAI_xCR_xWA_SHIFT 7 +#define ESAI_xCR_xWA_MASK (1 << ESAI_xCR_xWA_SHIFT) +#define ESAI_xCR_xWA (1 << ESAI_xCR_xWA_SHIFT) +#define ESAI_xCR_xSHFD_SHIFT 6 +#define ESAI_xCR_xSHFD_MASK (1 << ESAI_xCR_xSHFD_SHIFT) +#define ESAI_xCR_xSHFD (1 << ESAI_xCR_xSHFD_SHIFT) +#define ESAI_xCR_xE_SHIFT 0 +#define ESAI_xCR_TE_WIDTH 6 +#define ESAI_xCR_RE_WIDTH 4 +#define ESAI_xCR_TE_MASK (((1 << ESAI_xCR_TE_WIDTH) - 1) << ESAI_xCR_xE_SHIFT) +#define ESAI_xCR_RE_MASK (((1 << ESAI_xCR_RE_WIDTH) - 1) << ESAI_xCR_xE_SHIFT) +#define ESAI_xCR_TE(x) ((ESAI_xCR_TE_MASK >> (ESAI_xCR_TE_WIDTH - x)) & ESAI_xCR_TE_MASK) +#define ESAI_xCR_RE(x) ((ESAI_xCR_RE_MASK >> (ESAI_xCR_RE_WIDTH - x)) & ESAI_xCR_RE_MASK) + +/* + * Transmit Clock Control Register -- REG_ESAI_TCCR 0xD8 + * Receive Clock Control Register -- REG_ESAI_RCCR 0xE0 + */ +#define ESAI_xCCR_xHCKD_SHIFT 23 +#define ESAI_xCCR_xHCKD_MASK (1 << ESAI_xCCR_xHCKD_SHIFT) +#define ESAI_xCCR_xHCKD (1 << ESAI_xCCR_xHCKD_SHIFT) +#define ESAI_xCCR_xFSD_SHIFT 22 +#define ESAI_xCCR_xFSD_MASK (1 << ESAI_xCCR_xFSD_SHIFT) +#define ESAI_xCCR_xFSD (1 << ESAI_xCCR_xFSD_SHIFT) +#define ESAI_xCCR_xCKD_SHIFT 21 +#define ESAI_xCCR_xCKD_MASK (1 << ESAI_xCCR_xCKD_SHIFT) +#define ESAI_xCCR_xCKD (1 << ESAI_xCCR_xCKD_SHIFT) +#define ESAI_xCCR_xHCKP_SHIFT 20 +#define ESAI_xCCR_xHCKP_MASK (1 << ESAI_xCCR_xHCKP_SHIFT) +#define ESAI_xCCR_xHCKP (1 << ESAI_xCCR_xHCKP_SHIFT) +#define ESAI_xCCR_xFSP_SHIFT 19 +#define ESAI_xCCR_xFSP_MASK (1 << ESAI_xCCR_xFSP_SHIFT) +#define ESAI_xCCR_xFSP (1 << ESAI_xCCR_xFSP_SHIFT) +#define ESAI_xCCR_xCKP_SHIFT 18 +#define ESAI_xCCR_xCKP_MASK (1 << ESAI_xCCR_xCKP_SHIFT) +#define ESAI_xCCR_xCKP (1 << ESAI_xCCR_xCKP_SHIFT) +#define ESAI_xCCR_xFP_SHIFT 14 +#define ESAI_xCCR_xFP_WIDTH 4 +#define ESAI_xCCR_xFP_MASK (((1 << ESAI_xCCR_xFP_WIDTH) - 1) << ESAI_xCCR_xFP_SHIFT) +#define ESAI_xCCR_xFP(v) ((((v) - 1) << ESAI_xCCR_xFP_SHIFT) & ESAI_xCCR_xFP_MASK) +#define ESAI_xCCR_xDC_SHIFT 9 +#define ESAI_xCCR_xDC_WIDTH 5 +#define ESAI_xCCR_xDC_MASK (((1 << ESAI_xCCR_xDC_WIDTH) - 1) << ESAI_xCCR_xDC_SHIFT) +#define ESAI_xCCR_xDC(v) ((((v) - 1) << ESAI_xCCR_xDC_SHIFT) & ESAI_xCCR_xDC_MASK) +#define ESAI_xCCR_xPSR_SHIFT 8 +#define ESAI_xCCR_xPSR_MASK (1 << ESAI_xCCR_xPSR_SHIFT) +#define ESAI_xCCR_xPSR_BYPASS (1 << ESAI_xCCR_xPSR_SHIFT) +#define ESAI_xCCR_xPSR_DIV8 (0 << ESAI_xCCR_xPSR_SHIFT) +#define ESAI_xCCR_xPM_SHIFT 0 +#define ESAI_xCCR_xPM_WIDTH 8 +#define ESAI_xCCR_xPM_MASK (((1 << ESAI_xCCR_xPM_WIDTH) - 1) << ESAI_xCCR_xPM_SHIFT) +#define ESAI_xCCR_xPM(v) ((((v) - 1) << ESAI_xCCR_xPM_SHIFT) & ESAI_xCCR_xPM_MASK) + +/* Transmit Slot Mask Register A/B -- REG_ESAI_TSMA/B 0xE4 ~ 0xF0 */ +#define ESAI_xSMA_xS_SHIFT 0 +#define ESAI_xSMA_xS_WIDTH 16 +#define ESAI_xSMA_xS_MASK (((1 << ESAI_xSMA_xS_WIDTH) - 1) << ESAI_xSMA_xS_SHIFT) +#define ESAI_xSMA_xS(v) ((v) & ESAI_xSMA_xS_MASK) +#define ESAI_xSMB_xS_SHIFT 0 +#define ESAI_xSMB_xS_WIDTH 16 +#define ESAI_xSMB_xS_MASK (((1 << ESAI_xSMB_xS_WIDTH) - 1) << ESAI_xSMB_xS_SHIFT) +#define ESAI_xSMB_xS(v) (((v) >> ESAI_xSMA_xS_WIDTH) & ESAI_xSMB_xS_MASK) + +/* Port C Direction Register -- REG_ESAI_PRRC 0xF8 */ +#define ESAI_PRRC_PDC_SHIFT 0 +#define ESAI_PRRC_PDC_WIDTH 12 +#define ESAI_PRRC_PDC_MASK (((1 << ESAI_PRRC_PDC_WIDTH) - 1) << ESAI_PRRC_PDC_SHIFT) +#define ESAI_PRRC_PDC(v) ((v) & ESAI_PRRC_PDC_MASK) + +/* Port C Control Register -- REG_ESAI_PCRC 0xFC */ +#define ESAI_PCRC_PC_SHIFT 0 +#define ESAI_PCRC_PC_WIDTH 12 +#define ESAI_PCRC_PC_MASK (((1 << ESAI_PCRC_PC_WIDTH) - 1) << ESAI_PCRC_PC_SHIFT) +#define ESAI_PCRC_PC(v) ((v) & ESAI_PCRC_PC_MASK) + +#define ESAI_GPIO 0xfff + +/* ESAI clock source */ +#define ESAI_HCKT_FSYS 0 +#define ESAI_HCKT_EXTAL 1 +#define ESAI_HCKR_FSYS 2 +#define ESAI_HCKR_EXTAL 3 + +/* ESAI clock divider */ +#define ESAI_TX_DIV_PSR 0 +#define ESAI_TX_DIV_PM 1 +#define ESAI_TX_DIV_FP 2 +#define ESAI_RX_DIV_PSR 3 +#define ESAI_RX_DIV_PM 4 +#define ESAI_RX_DIV_FP 5 +#endif /* _FSL_ESAI_DAI_H */ diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c new file mode 100644 index 000000000..ec79c3d5e --- /dev/null +++ b/sound/soc/fsl/fsl_sai.c @@ -0,0 +1,689 @@ +/* + * Freescale ALSA SoC Digital Audio Interface (SAI) driver. + * + * Copyright 2012-2013 Freescale Semiconductor, Inc. + * + * This program is free software, you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or(at your + * option) any later version. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "fsl_sai.h" +#include "imx-pcm.h" + +#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\ + FSL_SAI_CSR_FEIE) + +static irqreturn_t fsl_sai_isr(int irq, void *devid) +{ + struct fsl_sai *sai = (struct fsl_sai *)devid; + struct device *dev = &sai->pdev->dev; + u32 flags, xcsr, mask; + bool irq_none = true; + + /* + * Both IRQ status bits and IRQ mask bits are in the xCSR but + * different shifts. And we here create a mask only for those + * IRQs that we activated. + */ + mask = (FSL_SAI_FLAGS >> FSL_SAI_CSR_xIE_SHIFT) << FSL_SAI_CSR_xF_SHIFT; + + /* Tx IRQ */ + regmap_read(sai->regmap, FSL_SAI_TCSR, &xcsr); + flags = xcsr & mask; + + if (flags) + irq_none = false; + else + goto irq_rx; + + if (flags & FSL_SAI_CSR_WSF) + dev_dbg(dev, "isr: Start of Tx word detected\n"); + + if (flags & FSL_SAI_CSR_SEF) + dev_warn(dev, "isr: Tx Frame sync error detected\n"); + + if (flags & FSL_SAI_CSR_FEF) { + dev_warn(dev, "isr: Transmit underrun detected\n"); + /* FIFO reset for safety */ + xcsr |= FSL_SAI_CSR_FR; + } + + if (flags & FSL_SAI_CSR_FWF) + dev_dbg(dev, "isr: Enabled transmit FIFO is empty\n"); + + if (flags & FSL_SAI_CSR_FRF) + dev_dbg(dev, "isr: Transmit FIFO watermark has been reached\n"); + + flags &= FSL_SAI_CSR_xF_W_MASK; + xcsr &= ~FSL_SAI_CSR_xF_MASK; + + if (flags) + regmap_write(sai->regmap, FSL_SAI_TCSR, flags | xcsr); + +irq_rx: + /* Rx IRQ */ + regmap_read(sai->regmap, FSL_SAI_RCSR, &xcsr); + flags = xcsr & mask; + + if (flags) + irq_none = false; + else + goto out; + + if (flags & FSL_SAI_CSR_WSF) + dev_dbg(dev, "isr: Start of Rx word detected\n"); + + if (flags & FSL_SAI_CSR_SEF) + dev_warn(dev, "isr: Rx Frame sync error detected\n"); + + if (flags & FSL_SAI_CSR_FEF) { + dev_warn(dev, "isr: Receive overflow detected\n"); + /* FIFO reset for safety */ + xcsr |= FSL_SAI_CSR_FR; + } + + if (flags & FSL_SAI_CSR_FWF) + dev_dbg(dev, "isr: Enabled receive FIFO is full\n"); + + if (flags & FSL_SAI_CSR_FRF) + dev_dbg(dev, "isr: Receive FIFO watermark has been reached\n"); + + flags &= FSL_SAI_CSR_xF_W_MASK; + xcsr &= ~FSL_SAI_CSR_xF_MASK; + + if (flags) + regmap_write(sai->regmap, FSL_SAI_RCSR, flags | xcsr); + +out: + if (irq_none) + return IRQ_NONE; + else + return IRQ_HANDLED; +} + +static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int fsl_dir) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = fsl_dir == FSL_FMT_TRANSMITTER; + u32 val_cr2 = 0; + + switch (clk_id) { + case FSL_SAI_CLK_BUS: + val_cr2 |= FSL_SAI_CR2_MSEL_BUS; + break; + case FSL_SAI_CLK_MAST1: + val_cr2 |= FSL_SAI_CR2_MSEL_MCLK1; + break; + case FSL_SAI_CLK_MAST2: + val_cr2 |= FSL_SAI_CR2_MSEL_MCLK2; + break; + case FSL_SAI_CLK_MAST3: + val_cr2 |= FSL_SAI_CR2_MSEL_MCLK3; + break; + default: + return -EINVAL; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx), + FSL_SAI_CR2_MSEL_MASK, val_cr2); + + return 0; +} + +static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + int ret; + + if (dir == SND_SOC_CLOCK_IN) + return 0; + + ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, + FSL_FMT_TRANSMITTER); + if (ret) { + dev_err(cpu_dai->dev, "Cannot set tx sysclk: %d\n", ret); + return ret; + } + + ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, + FSL_FMT_RECEIVER); + if (ret) + dev_err(cpu_dai->dev, "Cannot set rx sysclk: %d\n", ret); + + return ret; +} + +static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, + unsigned int fmt, int fsl_dir) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = fsl_dir == FSL_FMT_TRANSMITTER; + u32 val_cr2 = 0, val_cr4 = 0; + + if (!sai->is_lsb_first) + val_cr4 |= FSL_SAI_CR4_MF; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* + * Frame low, 1clk before data, one word length for frame sync, + * frame sync starts one serial clock cycle earlier, + * that is, together with the last bit of the previous + * data word. + */ + val_cr2 |= FSL_SAI_CR2_BCP; + val_cr4 |= FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* + * Frame high, one word length for frame sync, + * frame sync asserts with the first bit of the frame. + */ + val_cr2 |= FSL_SAI_CR2_BCP; + break; + case SND_SOC_DAIFMT_DSP_A: + /* + * Frame high, 1clk before data, one bit for frame sync, + * frame sync starts one serial clock cycle earlier, + * that is, together with the last bit of the previous + * data word. + */ + val_cr2 |= FSL_SAI_CR2_BCP; + val_cr4 |= FSL_SAI_CR4_FSE; + sai->is_dsp_mode = true; + break; + case SND_SOC_DAIFMT_DSP_B: + /* + * Frame high, one bit for frame sync, + * frame sync asserts with the first bit of the frame. + */ + val_cr2 |= FSL_SAI_CR2_BCP; + sai->is_dsp_mode = true; + break; + case SND_SOC_DAIFMT_RIGHT_J: + /* To be done */ + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + val_cr2 ^= FSL_SAI_CR2_BCP; + val_cr4 ^= FSL_SAI_CR4_FSP; + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + val_cr2 ^= FSL_SAI_CR2_BCP; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + val_cr4 ^= FSL_SAI_CR4_FSP; + break; + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do for both normal cases */ + break; + default: + return -EINVAL; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + val_cr2 |= FSL_SAI_CR2_BCD_MSTR; + val_cr4 |= FSL_SAI_CR4_FSD_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFM: + val_cr2 |= FSL_SAI_CR2_BCD_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + val_cr4 |= FSL_SAI_CR4_FSD_MSTR; + break; + default: + return -EINVAL; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx), + FSL_SAI_CR2_BCP | FSL_SAI_CR2_BCD_MSTR, val_cr2); + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx), + FSL_SAI_CR4_MF | FSL_SAI_CR4_FSE | + FSL_SAI_CR4_FSP | FSL_SAI_CR4_FSD_MSTR, val_cr4); + + return 0; +} + +static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + int ret; + + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER); + if (ret) { + dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret); + return ret; + } + + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER); + if (ret) + dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret); + + return ret; +} + +static int fsl_sai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + unsigned int channels = params_channels(params); + u32 word_width = snd_pcm_format_width(params_format(params)); + u32 val_cr4 = 0, val_cr5 = 0; + + if (!sai->is_dsp_mode) + val_cr4 |= FSL_SAI_CR4_SYWD(word_width); + + val_cr5 |= FSL_SAI_CR5_WNW(word_width); + val_cr5 |= FSL_SAI_CR5_W0W(word_width); + + if (sai->is_lsb_first) + val_cr5 |= FSL_SAI_CR5_FBT(0); + else + val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1); + + val_cr4 |= FSL_SAI_CR4_FRSZ(channels); + + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx), + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, + val_cr4); + regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx), + FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | + FSL_SAI_CR5_FBT_MASK, val_cr5); + regmap_write(sai->regmap, FSL_SAI_xMR(tx), ~0UL - ((1 << channels) - 1)); + + return 0; +} + +static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 xcsr, count = 100; + + /* + * Asynchronous mode: Clear SYNC for both Tx and Rx. + * Rx sync with Tx clocks: Clear SYNC for Tx, set it for Rx. + * Tx sync with Rx clocks: Clear SYNC for Rx, set it for Tx. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC, 0); + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC, + sai->synchronous[RX] ? FSL_SAI_CR2_SYNC : 0); + + /* + * It is recommended that the transmitter is the last enabled + * and the first disabled. + */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE); + + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + FSL_SAI_CSR_FRDE, 0); + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx), + FSL_SAI_CSR_xIE_MASK, 0); + + /* Check if the opposite FRDE is also disabled */ + regmap_read(sai->regmap, FSL_SAI_xCSR(!tx), &xcsr); + if (!(xcsr & FSL_SAI_CSR_FRDE)) { + /* Disable both directions and reset their FIFOs */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_TERE, 0); + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_TERE, 0); + + /* TERE will remain set till the end of current frame */ + do { + udelay(10); + regmap_read(sai->regmap, FSL_SAI_xCSR(tx), &xcsr); + } while (--count && xcsr & FSL_SAI_CSR_TERE); + + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_sai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = &sai->pdev->dev; + int ret; + + ret = clk_prepare_enable(sai->bus_clk); + if (ret) { + dev_err(dev, "failed to enable bus clock: %d\n", ret); + return ret; + } + + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE, + FSL_SAI_CR3_TRCE); + + return 0; +} + +static void fsl_sai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE, 0); + + clk_disable_unprepare(sai->bus_clk); +} + +static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { + .set_sysclk = fsl_sai_set_dai_sysclk, + .set_fmt = fsl_sai_set_dai_fmt, + .hw_params = fsl_sai_hw_params, + .trigger = fsl_sai_trigger, + .startup = fsl_sai_startup, + .shutdown = fsl_sai_shutdown, +}; + +static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + + /* Software Reset for both Tx and Rx */ + regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR); + regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR); + /* Clear SR bit to finish the reset */ + regmap_write(sai->regmap, FSL_SAI_TCSR, 0); + regmap_write(sai->regmap, FSL_SAI_RCSR, 0); + + regmap_update_bits(sai->regmap, FSL_SAI_TCR1, FSL_SAI_CR1_RFW_MASK, + FSL_SAI_MAXBURST_TX * 2); + regmap_update_bits(sai->regmap, FSL_SAI_RCR1, FSL_SAI_CR1_RFW_MASK, + FSL_SAI_MAXBURST_RX - 1); + + snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx, + &sai->dma_params_rx); + + snd_soc_dai_set_drvdata(cpu_dai, sai); + + return 0; +} + +static struct snd_soc_dai_driver fsl_sai_dai = { + .probe = fsl_sai_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_SAI_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = FSL_SAI_FORMATS, + }, + .ops = &fsl_sai_pcm_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_component = { + .name = "fsl-sai", +}; + +static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TFR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RDR: + case FSL_SAI_RFR: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TFR: + case FSL_SAI_RFR: + case FSL_SAI_TDR: + case FSL_SAI_RDR: + return true; + default: + return false; + } + +} + +static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TDR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_sai_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FSL_SAI_RMR, + .readable_reg = fsl_sai_readable_reg, + .volatile_reg = fsl_sai_volatile_reg, + .writeable_reg = fsl_sai_writeable_reg, +}; + +static int fsl_sai_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_sai *sai; + struct resource *res; + void __iomem *base; + char tmp[8]; + int irq, ret, i; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + sai->pdev = pdev; + + if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx6sx-sai")) + sai->sai_on_imx = true; + + sai->is_lsb_first = of_property_read_bool(np, "lsb-first"); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "bus", base, &fsl_sai_regmap_config); + + /* Compatible with old DTB cases */ + if (IS_ERR(sai->regmap)) + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "sai", base, &fsl_sai_regmap_config); + if (IS_ERR(sai->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(sai->regmap); + } + + /* No error out for old DTB cases but only mark the clock NULL */ + sai->bus_clk = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(sai->bus_clk)) { + dev_err(&pdev->dev, "failed to get bus clock: %ld\n", + PTR_ERR(sai->bus_clk)); + sai->bus_clk = NULL; + } + + for (i = 0; i < FSL_SAI_MCLK_MAX; i++) { + sprintf(tmp, "mclk%d", i + 1); + sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(sai->mclk_clk[i])) { + dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n", + i + 1, PTR_ERR(sai->mclk_clk[i])); + sai->mclk_clk[i] = NULL; + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, 0, np->name, sai); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u\n", irq); + return ret; + } + + /* Sync Tx with Rx as default by following old DT binding */ + sai->synchronous[RX] = true; + sai->synchronous[TX] = false; + fsl_sai_dai.symmetric_rates = 1; + fsl_sai_dai.symmetric_channels = 1; + fsl_sai_dai.symmetric_samplebits = 1; + + if (of_find_property(np, "fsl,sai-synchronous-rx", NULL) && + of_find_property(np, "fsl,sai-asynchronous", NULL)) { + /* error out if both synchronous and asynchronous are present */ + dev_err(&pdev->dev, "invalid binding for synchronous mode\n"); + return -EINVAL; + } + + if (of_find_property(np, "fsl,sai-synchronous-rx", NULL)) { + /* Sync Rx with Tx */ + sai->synchronous[RX] = false; + sai->synchronous[TX] = true; + } else if (of_find_property(np, "fsl,sai-asynchronous", NULL)) { + /* Discard all settings for asynchronous mode */ + sai->synchronous[RX] = false; + sai->synchronous[TX] = false; + fsl_sai_dai.symmetric_rates = 0; + fsl_sai_dai.symmetric_channels = 0; + fsl_sai_dai.symmetric_samplebits = 0; + } + + sai->dma_params_rx.addr = res->start + FSL_SAI_RDR; + sai->dma_params_tx.addr = res->start + FSL_SAI_TDR; + sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX; + sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX; + + platform_set_drvdata(pdev, sai); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, + &fsl_sai_dai, 1); + if (ret) + return ret; + + if (sai->sai_on_imx) + return imx_pcm_dma_init(pdev); + else + return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + SND_DMAENGINE_PCM_FLAG_NO_RESIDUE); +} + +static const struct of_device_id fsl_sai_ids[] = { + { .compatible = "fsl,vf610-sai", }, + { .compatible = "fsl,imx6sx-sai", }, + { /* sentinel */ } +}; + +static struct platform_driver fsl_sai_driver = { + .probe = fsl_sai_probe, + .driver = { + .name = "fsl-sai", + .of_match_table = fsl_sai_ids, + }, +}; +module_platform_driver(fsl_sai_driver); + +MODULE_DESCRIPTION("Freescale Soc SAI Interface"); +MODULE_AUTHOR("Xiubo Li, <Li.Xiubo@freescale.com>"); +MODULE_ALIAS("platform:fsl-sai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h new file mode 100644 index 000000000..34667209b --- /dev/null +++ b/sound/soc/fsl/fsl_sai.h @@ -0,0 +1,147 @@ +/* + * Copyright 2012-2013 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __FSL_SAI_H +#define __FSL_SAI_H + +#include <sound/dmaengine_pcm.h> + +#define FSL_SAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +/* SAI Register Map Register */ +#define FSL_SAI_TCSR 0x00 /* SAI Transmit Control */ +#define FSL_SAI_TCR1 0x04 /* SAI Transmit Configuration 1 */ +#define FSL_SAI_TCR2 0x08 /* SAI Transmit Configuration 2 */ +#define FSL_SAI_TCR3 0x0c /* SAI Transmit Configuration 3 */ +#define FSL_SAI_TCR4 0x10 /* SAI Transmit Configuration 4 */ +#define FSL_SAI_TCR5 0x14 /* SAI Transmit Configuration 5 */ +#define FSL_SAI_TDR 0x20 /* SAI Transmit Data */ +#define FSL_SAI_TFR 0x40 /* SAI Transmit FIFO */ +#define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */ +#define FSL_SAI_RCSR 0x80 /* SAI Receive Control */ +#define FSL_SAI_RCR1 0x84 /* SAI Receive Configuration 1 */ +#define FSL_SAI_RCR2 0x88 /* SAI Receive Configuration 2 */ +#define FSL_SAI_RCR3 0x8c /* SAI Receive Configuration 3 */ +#define FSL_SAI_RCR4 0x90 /* SAI Receive Configuration 4 */ +#define FSL_SAI_RCR5 0x94 /* SAI Receive Configuration 5 */ +#define FSL_SAI_RDR 0xa0 /* SAI Receive Data */ +#define FSL_SAI_RFR 0xc0 /* SAI Receive FIFO */ +#define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */ + +#define FSL_SAI_xCSR(tx) (tx ? FSL_SAI_TCSR : FSL_SAI_RCSR) +#define FSL_SAI_xCR1(tx) (tx ? FSL_SAI_TCR1 : FSL_SAI_RCR1) +#define FSL_SAI_xCR2(tx) (tx ? FSL_SAI_TCR2 : FSL_SAI_RCR2) +#define FSL_SAI_xCR3(tx) (tx ? FSL_SAI_TCR3 : FSL_SAI_RCR3) +#define FSL_SAI_xCR4(tx) (tx ? FSL_SAI_TCR4 : FSL_SAI_RCR4) +#define FSL_SAI_xCR5(tx) (tx ? FSL_SAI_TCR5 : FSL_SAI_RCR5) +#define FSL_SAI_xDR(tx) (tx ? FSL_SAI_TDR : FSL_SAI_RDR) +#define FSL_SAI_xFR(tx) (tx ? FSL_SAI_TFR : FSL_SAI_RFR) +#define FSL_SAI_xMR(tx) (tx ? FSL_SAI_TMR : FSL_SAI_RMR) + +/* SAI Transmit/Recieve Control Register */ +#define FSL_SAI_CSR_TERE BIT(31) +#define FSL_SAI_CSR_FR BIT(25) +#define FSL_SAI_CSR_SR BIT(24) +#define FSL_SAI_CSR_xF_SHIFT 16 +#define FSL_SAI_CSR_xF_W_SHIFT 18 +#define FSL_SAI_CSR_xF_MASK (0x1f << FSL_SAI_CSR_xF_SHIFT) +#define FSL_SAI_CSR_xF_W_MASK (0x7 << FSL_SAI_CSR_xF_W_SHIFT) +#define FSL_SAI_CSR_WSF BIT(20) +#define FSL_SAI_CSR_SEF BIT(19) +#define FSL_SAI_CSR_FEF BIT(18) +#define FSL_SAI_CSR_FWF BIT(17) +#define FSL_SAI_CSR_FRF BIT(16) +#define FSL_SAI_CSR_xIE_SHIFT 8 +#define FSL_SAI_CSR_xIE_MASK (0x1f << FSL_SAI_CSR_xIE_SHIFT) +#define FSL_SAI_CSR_WSIE BIT(12) +#define FSL_SAI_CSR_SEIE BIT(11) +#define FSL_SAI_CSR_FEIE BIT(10) +#define FSL_SAI_CSR_FWIE BIT(9) +#define FSL_SAI_CSR_FRIE BIT(8) +#define FSL_SAI_CSR_FRDE BIT(0) + +/* SAI Transmit and Recieve Configuration 1 Register */ +#define FSL_SAI_CR1_RFW_MASK 0x1f + +/* SAI Transmit and Recieve Configuration 2 Register */ +#define FSL_SAI_CR2_SYNC BIT(30) +#define FSL_SAI_CR2_MSEL_MASK (0xff << 26) +#define FSL_SAI_CR2_MSEL_BUS 0 +#define FSL_SAI_CR2_MSEL_MCLK1 BIT(26) +#define FSL_SAI_CR2_MSEL_MCLK2 BIT(27) +#define FSL_SAI_CR2_MSEL_MCLK3 (BIT(26) | BIT(27)) +#define FSL_SAI_CR2_BCP BIT(25) +#define FSL_SAI_CR2_BCD_MSTR BIT(24) + +/* SAI Transmit and Recieve Configuration 3 Register */ +#define FSL_SAI_CR3_TRCE BIT(16) +#define FSL_SAI_CR3_WDFL(x) (x) +#define FSL_SAI_CR3_WDFL_MASK 0x1f + +/* SAI Transmit and Recieve Configuration 4 Register */ +#define FSL_SAI_CR4_FRSZ(x) (((x) - 1) << 16) +#define FSL_SAI_CR4_FRSZ_MASK (0x1f << 16) +#define FSL_SAI_CR4_SYWD(x) (((x) - 1) << 8) +#define FSL_SAI_CR4_SYWD_MASK (0x1f << 8) +#define FSL_SAI_CR4_MF BIT(4) +#define FSL_SAI_CR4_FSE BIT(3) +#define FSL_SAI_CR4_FSP BIT(1) +#define FSL_SAI_CR4_FSD_MSTR BIT(0) + +/* SAI Transmit and Recieve Configuration 5 Register */ +#define FSL_SAI_CR5_WNW(x) (((x) - 1) << 24) +#define FSL_SAI_CR5_WNW_MASK (0x1f << 24) +#define FSL_SAI_CR5_W0W(x) (((x) - 1) << 16) +#define FSL_SAI_CR5_W0W_MASK (0x1f << 16) +#define FSL_SAI_CR5_FBT(x) ((x) << 8) +#define FSL_SAI_CR5_FBT_MASK (0x1f << 8) + +/* SAI type */ +#define FSL_SAI_DMA BIT(0) +#define FSL_SAI_USE_AC97 BIT(1) +#define FSL_SAI_NET BIT(2) +#define FSL_SAI_TRA_SYN BIT(3) +#define FSL_SAI_REC_SYN BIT(4) +#define FSL_SAI_USE_I2S_SLAVE BIT(5) + +#define FSL_FMT_TRANSMITTER 0 +#define FSL_FMT_RECEIVER 1 + +/* SAI clock sources */ +#define FSL_SAI_CLK_BUS 0 +#define FSL_SAI_CLK_MAST1 1 +#define FSL_SAI_CLK_MAST2 2 +#define FSL_SAI_CLK_MAST3 3 + +#define FSL_SAI_MCLK_MAX 3 + +/* SAI data transfer numbers per DMA request */ +#define FSL_SAI_MAXBURST_TX 6 +#define FSL_SAI_MAXBURST_RX 6 + +struct fsl_sai { + struct platform_device *pdev; + struct regmap *regmap; + struct clk *bus_clk; + struct clk *mclk_clk[FSL_SAI_MCLK_MAX]; + + bool is_lsb_first; + bool is_dsp_mode; + bool sai_on_imx; + bool synchronous[2]; + + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +#define TX 1 +#define RX 0 + +#endif /* __FSL_SAI_H */ diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c new file mode 100644 index 000000000..91eb3aef7 --- /dev/null +++ b/sound/soc/fsl/fsl_spdif.c @@ -0,0 +1,1287 @@ +/* + * Freescale S/PDIF ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Based on stmp3xxx_spdif_dai.c + * Vladimir Barinov <vbarinov@embeddedalley.com> + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/bitrev.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> + +#include <sound/asoundef.h> +#include <sound/dmaengine_pcm.h> +#include <sound/soc.h> + +#include "fsl_spdif.h" +#include "imx-pcm.h" + +#define FSL_SPDIF_TXFIFO_WML 0x8 +#define FSL_SPDIF_RXFIFO_WML 0x8 + +#define INTR_FOR_PLAYBACK (INT_TXFIFO_RESYNC) +#define INTR_FOR_CAPTURE (INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL |\ + INT_URX_OV | INT_QRX_FUL | INT_QRX_OV |\ + INT_UQ_SYNC | INT_UQ_ERR | INT_RXFIFO_RESYNC |\ + INT_LOSS_LOCK | INT_DPLL_LOCKED) + +#define SIE_INTR_FOR(tx) (tx ? INTR_FOR_PLAYBACK : INTR_FOR_CAPTURE) + +/* Index list for the values that has if (DPLL Locked) condition */ +static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb }; +#define SRPC_NODPLL_START1 0x5 +#define SRPC_NODPLL_START2 0xc + +#define DEFAULT_RXCLK_SRC 1 + +/* + * SPDIF control structure + * Defines channel status, subcode and Q sub + */ +struct spdif_mixer_control { + /* spinlock to access control data */ + spinlock_t ctl_lock; + + /* IEC958 channel tx status bit */ + unsigned char ch_status[4]; + + /* User bits */ + unsigned char subcode[2 * SPDIF_UBITS_SIZE]; + + /* Q subcode part of user bits */ + unsigned char qsub[2 * SPDIF_QSUB_SIZE]; + + /* Buffer offset for U/Q */ + u32 upos; + u32 qpos; + + /* Ready buffer index of the two buffers */ + u32 ready_buf; +}; + +/** + * fsl_spdif_priv: Freescale SPDIF private data + * + * @fsl_spdif_control: SPDIF control data + * @cpu_dai_drv: cpu dai driver + * @pdev: platform device pointer + * @regmap: regmap handler + * @dpll_locked: dpll lock flag + * @txrate: the best rates for playback + * @txclk_df: STC_TXCLK_DF dividers value for playback + * @sysclk_df: STC_SYSCLK_DF dividers value for playback + * @txclk_src: STC_TXCLK_SRC values for playback + * @rxclk_src: SRPC_CLKSRC_SEL values for capture + * @txclk: tx clock sources for playback + * @rxclk: rx clock sources for capture + * @coreclk: core clock for register access via DMA + * @sysclk: system clock for rx clock rate measurement + * @dma_params_tx: DMA parameters for transmit channel + * @dma_params_rx: DMA parameters for receive channel + */ +struct fsl_spdif_priv { + struct spdif_mixer_control fsl_spdif_control; + struct snd_soc_dai_driver cpu_dai_drv; + struct platform_device *pdev; + struct regmap *regmap; + bool dpll_locked; + u32 txrate[SPDIF_TXRATE_MAX]; + u8 txclk_df[SPDIF_TXRATE_MAX]; + u8 sysclk_df[SPDIF_TXRATE_MAX]; + u8 txclk_src[SPDIF_TXRATE_MAX]; + u8 rxclk_src; + struct clk *txclk[SPDIF_TXRATE_MAX]; + struct clk *rxclk; + struct clk *coreclk; + struct clk *sysclk; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_dmaengine_dai_dma_data dma_params_rx; +}; + +/* DPLL locked and lock loss interrupt handler */ +static void spdif_irq_dpll_lock(struct fsl_spdif_priv *spdif_priv) +{ + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u32 locked; + + regmap_read(regmap, REG_SPDIF_SRPC, &locked); + locked &= SRPC_DPLL_LOCKED; + + dev_dbg(&pdev->dev, "isr: Rx dpll %s \n", + locked ? "locked" : "loss lock"); + + spdif_priv->dpll_locked = locked ? true : false; +} + +/* Receiver found illegal symbol interrupt handler */ +static void spdif_irq_sym_error(struct fsl_spdif_priv *spdif_priv) +{ + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + + dev_dbg(&pdev->dev, "isr: receiver found illegal symbol\n"); + + /* Clear illegal symbol if DPLL unlocked since no audio stream */ + if (!spdif_priv->dpll_locked) + regmap_update_bits(regmap, REG_SPDIF_SIE, INT_SYM_ERR, 0); +} + +/* U/Q Channel receive register full */ +static void spdif_irq_uqrx_full(struct fsl_spdif_priv *spdif_priv, char name) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u32 *pos, size, val, reg; + + switch (name) { + case 'U': + pos = &ctrl->upos; + size = SPDIF_UBITS_SIZE; + reg = REG_SPDIF_SRU; + break; + case 'Q': + pos = &ctrl->qpos; + size = SPDIF_QSUB_SIZE; + reg = REG_SPDIF_SRQ; + break; + default: + dev_err(&pdev->dev, "unsupported channel name\n"); + return; + } + + dev_dbg(&pdev->dev, "isr: %c Channel receive register full\n", name); + + if (*pos >= size * 2) { + *pos = 0; + } else if (unlikely((*pos % size) + 3 > size)) { + dev_err(&pdev->dev, "User bit receivce buffer overflow\n"); + return; + } + + regmap_read(regmap, reg, &val); + ctrl->subcode[*pos++] = val >> 16; + ctrl->subcode[*pos++] = val >> 8; + ctrl->subcode[*pos++] = val; +} + +/* U/Q Channel sync found */ +static void spdif_irq_uq_sync(struct fsl_spdif_priv *spdif_priv) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct platform_device *pdev = spdif_priv->pdev; + + dev_dbg(&pdev->dev, "isr: U/Q Channel sync found\n"); + + /* U/Q buffer reset */ + if (ctrl->qpos == 0) + return; + + /* Set ready to this buffer */ + ctrl->ready_buf = (ctrl->qpos - 1) / SPDIF_QSUB_SIZE + 1; +} + +/* U/Q Channel framing error */ +static void spdif_irq_uq_err(struct fsl_spdif_priv *spdif_priv) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u32 val; + + dev_dbg(&pdev->dev, "isr: U/Q Channel framing error\n"); + + /* Read U/Q data to clear the irq and do buffer reset */ + regmap_read(regmap, REG_SPDIF_SRU, &val); + regmap_read(regmap, REG_SPDIF_SRQ, &val); + + /* Drop this U/Q buffer */ + ctrl->ready_buf = 0; + ctrl->upos = 0; + ctrl->qpos = 0; +} + +/* Get spdif interrupt status and clear the interrupt */ +static u32 spdif_intr_status_clear(struct fsl_spdif_priv *spdif_priv) +{ + struct regmap *regmap = spdif_priv->regmap; + u32 val, val2; + + regmap_read(regmap, REG_SPDIF_SIS, &val); + regmap_read(regmap, REG_SPDIF_SIE, &val2); + + regmap_write(regmap, REG_SPDIF_SIC, val & val2); + + return val; +} + +static irqreturn_t spdif_isr(int irq, void *devid) +{ + struct fsl_spdif_priv *spdif_priv = (struct fsl_spdif_priv *)devid; + struct platform_device *pdev = spdif_priv->pdev; + u32 sis; + + sis = spdif_intr_status_clear(spdif_priv); + + if (sis & INT_DPLL_LOCKED) + spdif_irq_dpll_lock(spdif_priv); + + if (sis & INT_TXFIFO_UNOV) + dev_dbg(&pdev->dev, "isr: Tx FIFO under/overrun\n"); + + if (sis & INT_TXFIFO_RESYNC) + dev_dbg(&pdev->dev, "isr: Tx FIFO resync\n"); + + if (sis & INT_CNEW) + dev_dbg(&pdev->dev, "isr: cstatus new\n"); + + if (sis & INT_VAL_NOGOOD) + dev_dbg(&pdev->dev, "isr: validity flag no good\n"); + + if (sis & INT_SYM_ERR) + spdif_irq_sym_error(spdif_priv); + + if (sis & INT_BIT_ERR) + dev_dbg(&pdev->dev, "isr: receiver found parity bit error\n"); + + if (sis & INT_URX_FUL) + spdif_irq_uqrx_full(spdif_priv, 'U'); + + if (sis & INT_URX_OV) + dev_dbg(&pdev->dev, "isr: U Channel receive register overrun\n"); + + if (sis & INT_QRX_FUL) + spdif_irq_uqrx_full(spdif_priv, 'Q'); + + if (sis & INT_QRX_OV) + dev_dbg(&pdev->dev, "isr: Q Channel receive register overrun\n"); + + if (sis & INT_UQ_SYNC) + spdif_irq_uq_sync(spdif_priv); + + if (sis & INT_UQ_ERR) + spdif_irq_uq_err(spdif_priv); + + if (sis & INT_RXFIFO_UNOV) + dev_dbg(&pdev->dev, "isr: Rx FIFO under/overrun\n"); + + if (sis & INT_RXFIFO_RESYNC) + dev_dbg(&pdev->dev, "isr: Rx FIFO resync\n"); + + if (sis & INT_LOSS_LOCK) + spdif_irq_dpll_lock(spdif_priv); + + /* FIXME: Write Tx FIFO to clear TxEm */ + if (sis & INT_TX_EM) + dev_dbg(&pdev->dev, "isr: Tx FIFO empty\n"); + + /* FIXME: Read Rx FIFO to clear RxFIFOFul */ + if (sis & INT_RXFIFO_FUL) + dev_dbg(&pdev->dev, "isr: Rx FIFO full\n"); + + return IRQ_HANDLED; +} + +static int spdif_softreset(struct fsl_spdif_priv *spdif_priv) +{ + struct regmap *regmap = spdif_priv->regmap; + u32 val, cycle = 1000; + + regmap_write(regmap, REG_SPDIF_SCR, SCR_SOFT_RESET); + + /* + * RESET bit would be cleared after finishing its reset procedure, + * which typically lasts 8 cycles. 1000 cycles will keep it safe. + */ + do { + regmap_read(regmap, REG_SPDIF_SCR, &val); + } while ((val & SCR_SOFT_RESET) && cycle--); + + if (cycle) + return 0; + else + return -EBUSY; +} + +static void spdif_set_cstatus(struct spdif_mixer_control *ctrl, + u8 mask, u8 cstatus) +{ + ctrl->ch_status[3] &= ~mask; + ctrl->ch_status[3] |= cstatus & mask; +} + +static void spdif_write_channel_status(struct fsl_spdif_priv *spdif_priv) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u32 ch_status; + + ch_status = (bitrev8(ctrl->ch_status[0]) << 16) | + (bitrev8(ctrl->ch_status[1]) << 8) | + bitrev8(ctrl->ch_status[2]); + regmap_write(regmap, REG_SPDIF_STCSCH, ch_status); + + dev_dbg(&pdev->dev, "STCSCH: 0x%06x\n", ch_status); + + ch_status = bitrev8(ctrl->ch_status[3]) << 16; + regmap_write(regmap, REG_SPDIF_STCSCL, ch_status); + + dev_dbg(&pdev->dev, "STCSCL: 0x%06x\n", ch_status); +} + +/* Set SPDIF PhaseConfig register for rx clock */ +static int spdif_set_rx_clksrc(struct fsl_spdif_priv *spdif_priv, + enum spdif_gainsel gainsel, int dpll_locked) +{ + struct regmap *regmap = spdif_priv->regmap; + u8 clksrc = spdif_priv->rxclk_src; + + if (clksrc >= SRPC_CLKSRC_MAX || gainsel >= GAINSEL_MULTI_MAX) + return -EINVAL; + + regmap_update_bits(regmap, REG_SPDIF_SRPC, + SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK, + SRPC_CLKSRC_SEL_SET(clksrc) | SRPC_GAINSEL_SET(gainsel)); + + return 0; +} + +static int spdif_set_sample_rate(struct snd_pcm_substream *substream, + int sample_rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + unsigned long csfs = 0; + u32 stc, mask, rate; + u8 clk, txclk_df, sysclk_df; + int ret; + + switch (sample_rate) { + case 32000: + rate = SPDIF_TXRATE_32000; + csfs = IEC958_AES3_CON_FS_32000; + break; + case 44100: + rate = SPDIF_TXRATE_44100; + csfs = IEC958_AES3_CON_FS_44100; + break; + case 48000: + rate = SPDIF_TXRATE_48000; + csfs = IEC958_AES3_CON_FS_48000; + break; + case 96000: + rate = SPDIF_TXRATE_96000; + csfs = IEC958_AES3_CON_FS_96000; + break; + case 192000: + rate = SPDIF_TXRATE_192000; + csfs = IEC958_AES3_CON_FS_192000; + break; + default: + dev_err(&pdev->dev, "unsupported sample rate %d\n", sample_rate); + return -EINVAL; + } + + clk = spdif_priv->txclk_src[rate]; + if (clk >= STC_TXCLK_SRC_MAX) { + dev_err(&pdev->dev, "tx clock source is out of range\n"); + return -EINVAL; + } + + txclk_df = spdif_priv->txclk_df[rate]; + if (txclk_df == 0) { + dev_err(&pdev->dev, "the txclk_df can't be zero\n"); + return -EINVAL; + } + + sysclk_df = spdif_priv->sysclk_df[rate]; + + /* Don't mess up the clocks from other modules */ + if (clk != STC_TXCLK_SPDIF_ROOT) + goto clk_set_bypass; + + /* + * The S/PDIF block needs a clock of 64 * fs * txclk_df. + * So request 64 * fs * (txclk_df + 1) to get rounded. + */ + ret = clk_set_rate(spdif_priv->txclk[rate], 64 * sample_rate * (txclk_df + 1)); + if (ret) { + dev_err(&pdev->dev, "failed to set tx clock rate\n"); + return ret; + } + +clk_set_bypass: + dev_dbg(&pdev->dev, "expected clock rate = %d\n", + (64 * sample_rate * txclk_df * sysclk_df)); + dev_dbg(&pdev->dev, "actual clock rate = %ld\n", + clk_get_rate(spdif_priv->txclk[rate])); + + /* set fs field in consumer channel status */ + spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs); + + /* select clock source and divisor */ + stc = STC_TXCLK_ALL_EN | STC_TXCLK_SRC_SET(clk) | + STC_TXCLK_DF(txclk_df) | STC_SYSCLK_DF(sysclk_df); + mask = STC_TXCLK_ALL_EN_MASK | STC_TXCLK_SRC_MASK | + STC_TXCLK_DF_MASK | STC_SYSCLK_DF_MASK; + regmap_update_bits(regmap, REG_SPDIF_STC, mask, stc); + + dev_dbg(&pdev->dev, "set sample rate to %dHz for %dHz playback\n", + spdif_priv->txrate[rate], sample_rate); + + return 0; +} + +static int fsl_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct platform_device *pdev = spdif_priv->pdev; + struct regmap *regmap = spdif_priv->regmap; + u32 scr, mask, i; + int ret; + + /* Reset module and interrupts only for first initialization */ + if (!cpu_dai->active) { + ret = clk_prepare_enable(spdif_priv->coreclk); + if (ret) { + dev_err(&pdev->dev, "failed to enable core clock\n"); + return ret; + } + + ret = spdif_softreset(spdif_priv); + if (ret) { + dev_err(&pdev->dev, "failed to soft reset\n"); + goto err; + } + + /* Disable all the interrupts */ + regmap_update_bits(regmap, REG_SPDIF_SIE, 0xffffff, 0); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + scr = SCR_TXFIFO_AUTOSYNC | SCR_TXFIFO_CTRL_NORMAL | + SCR_TXSEL_NORMAL | SCR_USRC_SEL_CHIP | + SCR_TXFIFO_FSEL_IF8; + mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | + SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | + SCR_TXFIFO_FSEL_MASK; + for (i = 0; i < SPDIF_TXRATE_MAX; i++) + clk_prepare_enable(spdif_priv->txclk[i]); + } else { + scr = SCR_RXFIFO_FSEL_IF8 | SCR_RXFIFO_AUTOSYNC; + mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| + SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; + clk_prepare_enable(spdif_priv->rxclk); + } + regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); + + /* Power up SPDIF module */ + regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, 0); + + return 0; + +err: + clk_disable_unprepare(spdif_priv->coreclk); + + return ret; +} + +static void fsl_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 scr, mask, i; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + scr = 0; + mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | + SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | + SCR_TXFIFO_FSEL_MASK; + for (i = 0; i < SPDIF_TXRATE_MAX; i++) + clk_disable_unprepare(spdif_priv->txclk[i]); + } else { + scr = SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO; + mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| + SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; + clk_disable_unprepare(spdif_priv->rxclk); + } + regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); + + /* Power down SPDIF module only if tx&rx are both inactive */ + if (!cpu_dai->active) { + spdif_intr_status_clear(spdif_priv); + regmap_update_bits(regmap, REG_SPDIF_SCR, + SCR_LOW_POWER, SCR_LOW_POWER); + clk_disable_unprepare(spdif_priv->coreclk); + } +} + +static int fsl_spdif_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 fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct platform_device *pdev = spdif_priv->pdev; + u32 sample_rate = params_rate(params); + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = spdif_set_sample_rate(substream, sample_rate); + if (ret) { + dev_err(&pdev->dev, "%s: set sample rate failed: %d\n", + __func__, sample_rate); + return ret; + } + spdif_set_cstatus(ctrl, IEC958_AES3_CON_CLOCK, + IEC958_AES3_CON_CLOCK_1000PPM); + spdif_write_channel_status(spdif_priv); + } else { + /* Setup rx clock source */ + ret = spdif_set_rx_clksrc(spdif_priv, SPDIF_DEFAULT_GAINSEL, 1); + } + + return ret; +} + +static int fsl_spdif_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 intr = SIE_INTR_FOR(tx); + u32 dmaen = SCR_DMA_xX_EN(tx); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(regmap, REG_SPDIF_SIE, intr, intr); + regmap_update_bits(regmap, REG_SPDIF_SCR, dmaen, dmaen); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_update_bits(regmap, REG_SPDIF_SCR, dmaen, 0); + regmap_update_bits(regmap, REG_SPDIF_SIE, intr, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops fsl_spdif_dai_ops = { + .startup = fsl_spdif_startup, + .hw_params = fsl_spdif_hw_params, + .trigger = fsl_spdif_trigger, + .shutdown = fsl_spdif_shutdown, +}; + + +/* + * FSL SPDIF IEC958 controller(mixer) functions + * + * Channel status get/put control + * User bit value get/put control + * Valid bit value get control + * DPLL lock status get control + * User bit sync mode selection control + */ + +static int fsl_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +static int fsl_spdif_pb_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + + uvalue->value.iec958.status[0] = ctrl->ch_status[0]; + uvalue->value.iec958.status[1] = ctrl->ch_status[1]; + uvalue->value.iec958.status[2] = ctrl->ch_status[2]; + uvalue->value.iec958.status[3] = ctrl->ch_status[3]; + + return 0; +} + +static int fsl_spdif_pb_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + + ctrl->ch_status[0] = uvalue->value.iec958.status[0]; + ctrl->ch_status[1] = uvalue->value.iec958.status[1]; + ctrl->ch_status[2] = uvalue->value.iec958.status[2]; + ctrl->ch_status[3] = uvalue->value.iec958.status[3]; + + spdif_write_channel_status(spdif_priv); + + return 0; +} + +/* Get channel status from SPDIF_RX_CCHAN register */ +static int fsl_spdif_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 cstatus, val; + + regmap_read(regmap, REG_SPDIF_SIS, &val); + if (!(val & INT_CNEW)) + return -EAGAIN; + + regmap_read(regmap, REG_SPDIF_SRCSH, &cstatus); + ucontrol->value.iec958.status[0] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[1] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[2] = cstatus & 0xFF; + + regmap_read(regmap, REG_SPDIF_SRCSL, &cstatus); + ucontrol->value.iec958.status[3] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[4] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[5] = cstatus & 0xFF; + + /* Clear intr */ + regmap_write(regmap, REG_SPDIF_SIC, INT_CNEW); + + return 0; +} + +/* + * Get User bits (subcode) from chip value which readed out + * in UChannel register. + */ +static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + unsigned long flags; + int ret = -EAGAIN; + + spin_lock_irqsave(&ctrl->ctl_lock, flags); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE; + memcpy(&ucontrol->value.iec958.subcode[0], + &ctrl->subcode[idx], SPDIF_UBITS_SIZE); + ret = 0; + } + spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; +} + +/* Q-subcode infomation. The byte size is SPDIF_UBITS_SIZE/8 */ +static int fsl_spdif_qinfo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = SPDIF_QSUB_SIZE; + + return 0; +} + +/* Get Q subcode from chip value which readed out in QChannel register */ +static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + unsigned long flags; + int ret = -EAGAIN; + + spin_lock_irqsave(&ctrl->ctl_lock, flags); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE; + memcpy(&ucontrol->value.bytes.data[0], + &ctrl->qsub[idx], SPDIF_QSUB_SIZE); + ret = 0; + } + spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; +} + +/* Valid bit infomation */ +static int fsl_spdif_vbit_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +/* Get valid good bit from interrupt status register */ +static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val; + + regmap_read(regmap, REG_SPDIF_SIS, &val); + ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0; + regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD); + + return 0; +} + +/* DPLL lock infomation */ +static int fsl_spdif_rxrate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 16000; + uinfo->value.integer.max = 96000; + + return 0; +} + +static u32 gainsel_multi[GAINSEL_MULTI_MAX] = { + 24, 16, 12, 8, 6, 4, 3, +}; + +/* Get RX data clock rate given the SPDIF bus_clk */ +static int spdif_get_rxclk_rate(struct fsl_spdif_priv *spdif_priv, + enum spdif_gainsel gainsel) +{ + struct regmap *regmap = spdif_priv->regmap; + struct platform_device *pdev = spdif_priv->pdev; + u64 tmpval64, busclk_freq = 0; + u32 freqmeas, phaseconf; + u8 clksrc; + + regmap_read(regmap, REG_SPDIF_SRFM, &freqmeas); + regmap_read(regmap, REG_SPDIF_SRPC, &phaseconf); + + clksrc = (phaseconf >> SRPC_CLKSRC_SEL_OFFSET) & 0xf; + + /* Get bus clock from system */ + if (srpc_dpll_locked[clksrc] && (phaseconf & SRPC_DPLL_LOCKED)) + busclk_freq = clk_get_rate(spdif_priv->sysclk); + + /* FreqMeas_CLK = (BUS_CLK * FreqMeas) / 2 ^ 10 / GAINSEL / 128 */ + tmpval64 = (u64) busclk_freq * freqmeas; + do_div(tmpval64, gainsel_multi[gainsel] * 1024); + do_div(tmpval64, 128 * 1024); + + dev_dbg(&pdev->dev, "FreqMeas: %d\n", freqmeas); + dev_dbg(&pdev->dev, "BusclkFreq: %lld\n", busclk_freq); + dev_dbg(&pdev->dev, "RxRate: %lld\n", tmpval64); + + return (int)tmpval64; +} + +/* + * Get DPLL lock or not info from stable interrupt status register. + * User application must use this control to get locked, + * then can do next PCM operation + */ +static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + int rate = 0; + + if (spdif_priv->dpll_locked) + rate = spdif_get_rxclk_rate(spdif_priv, SPDIF_DEFAULT_GAINSEL); + + ucontrol->value.integer.value[0] = rate; + + return 0; +} + +/* User bit sync mode info */ +static int fsl_spdif_usync_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +/* + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int fsl_spdif_usync_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val; + + regmap_read(regmap, REG_SPDIF_SRCD, &val); + ucontrol->value.integer.value[0] = (val & SRCD_CD_USER) != 0; + + return 0; +} + +/* + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int fsl_spdif_usync_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val = ucontrol->value.integer.value[0] << SRCD_CD_USER_OFFSET; + + regmap_update_bits(regmap, REG_SPDIF_SRCD, SRCD_CD_USER, val); + + return 0; +} + +/* FSL SPDIF IEC958 controller defines */ +static struct snd_kcontrol_new fsl_spdif_ctrls[] = { + /* Status cchanel controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_info, + .get = fsl_spdif_pb_get, + .put = fsl_spdif_pb_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_info, + .get = fsl_spdif_capture_get, + }, + /* User bits controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_info, + .get = fsl_spdif_subcode_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_qinfo, + .get = fsl_spdif_qget, + }, + /* Valid bit error controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 V-Bit Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_vbit_info, + .get = fsl_spdif_vbit_get, + }, + /* DPLL lock info get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "RX Sample Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_rxrate_info, + .get = fsl_spdif_rxrate_get, + }, + /* User bit sync mode set/get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 USyncMode CDText", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_usync_info, + .get = fsl_spdif_usync_get, + .put = fsl_spdif_usync_put, + }, +}; + +static int fsl_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_spdif_priv *spdif_private = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &spdif_private->dma_params_tx, + &spdif_private->dma_params_rx); + + snd_soc_add_dai_controls(dai, fsl_spdif_ctrls, ARRAY_SIZE(fsl_spdif_ctrls)); + + return 0; +} + +static struct snd_soc_dai_driver fsl_spdif_dai = { + .probe = &fsl_spdif_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_SPDIF_RATES_PLAYBACK, + .formats = FSL_SPDIF_FORMATS_PLAYBACK, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 2, + .channels_max = 2, + .rates = FSL_SPDIF_RATES_CAPTURE, + .formats = FSL_SPDIF_FORMATS_CAPTURE, + }, + .ops = &fsl_spdif_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_spdif_component = { + .name = "fsl-spdif", +}; + +/* FSL SPDIF REGMAP */ + +static bool fsl_spdif_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_SPDIF_SCR: + case REG_SPDIF_SRCD: + case REG_SPDIF_SRPC: + case REG_SPDIF_SIE: + case REG_SPDIF_SIS: + case REG_SPDIF_SRL: + case REG_SPDIF_SRR: + case REG_SPDIF_SRCSH: + case REG_SPDIF_SRCSL: + case REG_SPDIF_SRU: + case REG_SPDIF_SRQ: + case REG_SPDIF_STCSCH: + case REG_SPDIF_STCSCL: + case REG_SPDIF_SRFM: + case REG_SPDIF_STC: + return true; + default: + return false; + } +} + +static bool fsl_spdif_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case REG_SPDIF_SCR: + case REG_SPDIF_SRCD: + case REG_SPDIF_SRPC: + case REG_SPDIF_SIE: + case REG_SPDIF_SIC: + case REG_SPDIF_STL: + case REG_SPDIF_STR: + case REG_SPDIF_STCSCH: + case REG_SPDIF_STCSCL: + case REG_SPDIF_STC: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = REG_SPDIF_STC, + .readable_reg = fsl_spdif_readable_reg, + .writeable_reg = fsl_spdif_writeable_reg, +}; + +static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, + struct clk *clk, u64 savesub, + enum spdif_txrate index, bool round) +{ + const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; + bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk); + u64 rate_ideal, rate_actual, sub; + u32 sysclk_dfmin, sysclk_dfmax; + u32 txclk_df, sysclk_df, arate; + + /* The sysclk has an extra divisor [2, 512] */ + sysclk_dfmin = is_sysclk ? 2 : 1; + sysclk_dfmax = is_sysclk ? 512 : 1; + + for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) { + for (txclk_df = 1; txclk_df <= 128; txclk_df++) { + rate_ideal = rate[index] * (txclk_df + 1) * 64; + if (round) + rate_actual = clk_round_rate(clk, rate_ideal); + else + rate_actual = clk_get_rate(clk); + + arate = rate_actual / 64; + arate /= txclk_df * sysclk_df; + + if (arate == rate[index]) { + /* We are lucky */ + savesub = 0; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + goto out; + } else if (arate / rate[index] == 1) { + /* A little bigger than expect */ + sub = (u64)(arate - rate[index]) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } else if (rate[index] / arate == 1) { + /* A little smaller than expect */ + sub = (u64)(rate[index] - arate) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } + } + } + +out: + return savesub; +} + +static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, + enum spdif_txrate index) +{ + const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; + struct platform_device *pdev = spdif_priv->pdev; + struct device *dev = &pdev->dev; + u64 savesub = 100000, ret; + struct clk *clk; + char tmp[16]; + int i; + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + sprintf(tmp, "rxtx%d", i); + clk = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(clk)) { + dev_err(dev, "no rxtx%d clock in devicetree\n", i); + return PTR_ERR(clk); + } + if (!clk_get_rate(clk)) + continue; + + ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index, + i == STC_TXCLK_SPDIF_ROOT); + if (savesub == ret) + continue; + + savesub = ret; + spdif_priv->txclk[index] = clk; + spdif_priv->txclk_src[index] = i; + + /* To quick catch a divisor, we allow a 0.1% deviation */ + if (savesub < 100) + break; + } + + dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n", + spdif_priv->txclk_src[index], rate[index]); + dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n", + spdif_priv->txclk_df[index], rate[index]); + if (clk_is_match(spdif_priv->txclk[index], spdif_priv->sysclk)) + dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n", + spdif_priv->sysclk_df[index], rate[index]); + dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n", + rate[index], spdif_priv->txrate[index]); + + return 0; +} + +static int fsl_spdif_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_spdif_priv *spdif_priv; + struct spdif_mixer_control *ctrl; + struct resource *res; + void __iomem *regs; + int irq, ret, i; + + if (!np) + return -ENODEV; + + spdif_priv = devm_kzalloc(&pdev->dev, sizeof(*spdif_priv), GFP_KERNEL); + if (!spdif_priv) + return -ENOMEM; + + spdif_priv->pdev = pdev; + + /* Initialize this copy of the CPU DAI driver structure */ + memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai)); + spdif_priv->cpu_dai_drv.name = dev_name(&pdev->dev); + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + spdif_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "core", regs, &fsl_spdif_regmap_config); + if (IS_ERR(spdif_priv->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(spdif_priv->regmap); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0, + dev_name(&pdev->dev), spdif_priv); + if (ret) { + dev_err(&pdev->dev, "could not claim irq %u\n", irq); + return ret; + } + + /* Get system clock for rx clock rate calculation */ + spdif_priv->sysclk = devm_clk_get(&pdev->dev, "rxtx5"); + if (IS_ERR(spdif_priv->sysclk)) { + dev_err(&pdev->dev, "no sys clock (rxtx5) in devicetree\n"); + return PTR_ERR(spdif_priv->sysclk); + } + + /* Get core clock for data register access via DMA */ + spdif_priv->coreclk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(spdif_priv->coreclk)) { + dev_err(&pdev->dev, "no core clock in devicetree\n"); + return PTR_ERR(spdif_priv->coreclk); + } + + /* Select clock source for rx/tx clock */ + spdif_priv->rxclk = devm_clk_get(&pdev->dev, "rxtx1"); + if (IS_ERR(spdif_priv->rxclk)) { + dev_err(&pdev->dev, "no rxtx1 clock in devicetree\n"); + return PTR_ERR(spdif_priv->rxclk); + } + spdif_priv->rxclk_src = DEFAULT_RXCLK_SRC; + + for (i = 0; i < SPDIF_TXRATE_MAX; i++) { + ret = fsl_spdif_probe_txclk(spdif_priv, i); + if (ret) + return ret; + } + + /* Initial spinlock for control data */ + ctrl = &spdif_priv->fsl_spdif_control; + spin_lock_init(&ctrl->ctl_lock); + + /* Init tx channel status default value */ + ctrl->ch_status[0] = IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS_5015; + ctrl->ch_status[1] = IEC958_AES1_CON_DIGDIGCONV_ID; + ctrl->ch_status[2] = 0x00; + ctrl->ch_status[3] = IEC958_AES3_CON_FS_44100 | + IEC958_AES3_CON_CLOCK_1000PPM; + + spdif_priv->dpll_locked = false; + + spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML; + spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML; + spdif_priv->dma_params_tx.addr = res->start + REG_SPDIF_STL; + spdif_priv->dma_params_rx.addr = res->start + REG_SPDIF_SRL; + + /* Register with ASoC */ + dev_set_drvdata(&pdev->dev, spdif_priv); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_spdif_component, + &spdif_priv->cpu_dai_drv, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); + return ret; + } + + ret = imx_pcm_dma_init(pdev); + if (ret) + dev_err(&pdev->dev, "imx_pcm_dma_init failed: %d\n", ret); + + return ret; +} + +static const struct of_device_id fsl_spdif_dt_ids[] = { + { .compatible = "fsl,imx35-spdif", }, + { .compatible = "fsl,vf610-spdif", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); + +static struct platform_driver fsl_spdif_driver = { + .driver = { + .name = "fsl-spdif-dai", + .of_match_table = fsl_spdif_dt_ids, + }, + .probe = fsl_spdif_probe, +}; + +module_platform_driver(fsl_spdif_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale S/PDIF CPU DAI Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:fsl-spdif-dai"); diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h new file mode 100644 index 000000000..00bd3514c --- /dev/null +++ b/sound/soc/fsl/fsl_spdif.h @@ -0,0 +1,199 @@ +/* + * fsl_spdif.h - ALSA S/PDIF interface for the Freescale i.MX SoC + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <b42378@freescale.com> + * + * Based on fsl_ssi.h + * Author: Timur Tabi <timur@freescale.com> + * Copyright 2007-2008 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _FSL_SPDIF_DAI_H +#define _FSL_SPDIF_DAI_H + +/* S/PDIF Register Map */ +#define REG_SPDIF_SCR 0x0 /* SPDIF Configuration Register */ +#define REG_SPDIF_SRCD 0x4 /* CDText Control Register */ +#define REG_SPDIF_SRPC 0x8 /* PhaseConfig Register */ +#define REG_SPDIF_SIE 0xc /* InterruptEn Register */ +#define REG_SPDIF_SIS 0x10 /* InterruptStat Register */ +#define REG_SPDIF_SIC 0x10 /* InterruptClear Register */ +#define REG_SPDIF_SRL 0x14 /* SPDIFRxLeft Register */ +#define REG_SPDIF_SRR 0x18 /* SPDIFRxRight Register */ +#define REG_SPDIF_SRCSH 0x1c /* SPDIFRxCChannel_h Register */ +#define REG_SPDIF_SRCSL 0x20 /* SPDIFRxCChannel_l Register */ +#define REG_SPDIF_SRU 0x24 /* UchannelRx Register */ +#define REG_SPDIF_SRQ 0x28 /* QchannelRx Register */ +#define REG_SPDIF_STL 0x2C /* SPDIFTxLeft Register */ +#define REG_SPDIF_STR 0x30 /* SPDIFTxRight Register */ +#define REG_SPDIF_STCSCH 0x34 /* SPDIFTxCChannelCons_h Register */ +#define REG_SPDIF_STCSCL 0x38 /* SPDIFTxCChannelCons_l Register */ +#define REG_SPDIF_SRFM 0x44 /* FreqMeas Register */ +#define REG_SPDIF_STC 0x50 /* SPDIFTxClk Register */ + + +/* SPDIF Configuration register */ +#define SCR_RXFIFO_CTL_OFFSET 23 +#define SCR_RXFIFO_CTL_MASK (1 << SCR_RXFIFO_CTL_OFFSET) +#define SCR_RXFIFO_CTL_ZERO (1 << SCR_RXFIFO_CTL_OFFSET) +#define SCR_RXFIFO_OFF_OFFSET 22 +#define SCR_RXFIFO_OFF_MASK (1 << SCR_RXFIFO_OFF_OFFSET) +#define SCR_RXFIFO_OFF (1 << SCR_RXFIFO_OFF_OFFSET) +#define SCR_RXFIFO_RST_OFFSET 21 +#define SCR_RXFIFO_RST_MASK (1 << SCR_RXFIFO_RST_OFFSET) +#define SCR_RXFIFO_RST (1 << SCR_RXFIFO_RST_OFFSET) +#define SCR_RXFIFO_FSEL_OFFSET 19 +#define SCR_RXFIFO_FSEL_MASK (0x3 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF0 (0x0 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF4 (0x1 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF8 (0x2 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF12 (0x3 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_AUTOSYNC_OFFSET 18 +#define SCR_RXFIFO_AUTOSYNC_MASK (1 << SCR_RXFIFO_AUTOSYNC_OFFSET) +#define SCR_RXFIFO_AUTOSYNC (1 << SCR_RXFIFO_AUTOSYNC_OFFSET) +#define SCR_TXFIFO_AUTOSYNC_OFFSET 17 +#define SCR_TXFIFO_AUTOSYNC_MASK (1 << SCR_TXFIFO_AUTOSYNC_OFFSET) +#define SCR_TXFIFO_AUTOSYNC (1 << SCR_TXFIFO_AUTOSYNC_OFFSET) +#define SCR_TXFIFO_FSEL_OFFSET 15 +#define SCR_TXFIFO_FSEL_MASK (0x3 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF0 (0x0 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF4 (0x1 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF8 (0x2 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF12 (0x3 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_LOW_POWER (1 << 13) +#define SCR_SOFT_RESET (1 << 12) +#define SCR_TXFIFO_CTRL_OFFSET 10 +#define SCR_TXFIFO_CTRL_MASK (0x3 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_TXFIFO_CTRL_ZERO (0x0 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_TXFIFO_CTRL_NORMAL (0x1 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_TXFIFO_CTRL_ONESAMPLE (0x2 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_DMA_RX_EN_OFFSET 9 +#define SCR_DMA_RX_EN_MASK (1 << SCR_DMA_RX_EN_OFFSET) +#define SCR_DMA_RX_EN (1 << SCR_DMA_RX_EN_OFFSET) +#define SCR_DMA_TX_EN_OFFSET 8 +#define SCR_DMA_TX_EN_MASK (1 << SCR_DMA_TX_EN_OFFSET) +#define SCR_DMA_TX_EN (1 << SCR_DMA_TX_EN_OFFSET) +#define SCR_VAL_OFFSET 5 +#define SCR_VAL_MASK (1 << SCR_VAL_OFFSET) +#define SCR_VAL_CLEAR (1 << SCR_VAL_OFFSET) +#define SCR_TXSEL_OFFSET 2 +#define SCR_TXSEL_MASK (0x7 << SCR_TXSEL_OFFSET) +#define SCR_TXSEL_OFF (0 << SCR_TXSEL_OFFSET) +#define SCR_TXSEL_RX (1 << SCR_TXSEL_OFFSET) +#define SCR_TXSEL_NORMAL (0x5 << SCR_TXSEL_OFFSET) +#define SCR_USRC_SEL_OFFSET 0x0 +#define SCR_USRC_SEL_MASK (0x3 << SCR_USRC_SEL_OFFSET) +#define SCR_USRC_SEL_NONE (0x0 << SCR_USRC_SEL_OFFSET) +#define SCR_USRC_SEL_RECV (0x1 << SCR_USRC_SEL_OFFSET) +#define SCR_USRC_SEL_CHIP (0x3 << SCR_USRC_SEL_OFFSET) + +#define SCR_DMA_xX_EN(tx) (tx ? SCR_DMA_TX_EN : SCR_DMA_RX_EN) + +/* SPDIF CDText control */ +#define SRCD_CD_USER_OFFSET 1 +#define SRCD_CD_USER (1 << SRCD_CD_USER_OFFSET) + +/* SPDIF Phase Configuration register */ +#define SRPC_DPLL_LOCKED (1 << 6) +#define SRPC_CLKSRC_SEL_OFFSET 7 +#define SRPC_CLKSRC_SEL_MASK (0xf << SRPC_CLKSRC_SEL_OFFSET) +#define SRPC_CLKSRC_SEL_SET(x) ((x << SRPC_CLKSRC_SEL_OFFSET) & SRPC_CLKSRC_SEL_MASK) +#define SRPC_CLKSRC_SEL_LOCKED_OFFSET1 5 +#define SRPC_CLKSRC_SEL_LOCKED_OFFSET2 2 +#define SRPC_GAINSEL_OFFSET 3 +#define SRPC_GAINSEL_MASK (0x7 << SRPC_GAINSEL_OFFSET) +#define SRPC_GAINSEL_SET(x) ((x << SRPC_GAINSEL_OFFSET) & SRPC_GAINSEL_MASK) + +#define SRPC_CLKSRC_MAX 16 + +enum spdif_gainsel { + GAINSEL_MULTI_24 = 0, + GAINSEL_MULTI_16, + GAINSEL_MULTI_12, + GAINSEL_MULTI_8, + GAINSEL_MULTI_6, + GAINSEL_MULTI_4, + GAINSEL_MULTI_3, +}; +#define GAINSEL_MULTI_MAX (GAINSEL_MULTI_3 + 1) +#define SPDIF_DEFAULT_GAINSEL GAINSEL_MULTI_8 + +/* SPDIF interrupt mask define */ +#define INT_DPLL_LOCKED (1 << 20) +#define INT_TXFIFO_UNOV (1 << 19) +#define INT_TXFIFO_RESYNC (1 << 18) +#define INT_CNEW (1 << 17) +#define INT_VAL_NOGOOD (1 << 16) +#define INT_SYM_ERR (1 << 15) +#define INT_BIT_ERR (1 << 14) +#define INT_URX_FUL (1 << 10) +#define INT_URX_OV (1 << 9) +#define INT_QRX_FUL (1 << 8) +#define INT_QRX_OV (1 << 7) +#define INT_UQ_SYNC (1 << 6) +#define INT_UQ_ERR (1 << 5) +#define INT_RXFIFO_UNOV (1 << 4) +#define INT_RXFIFO_RESYNC (1 << 3) +#define INT_LOSS_LOCK (1 << 2) +#define INT_TX_EM (1 << 1) +#define INT_RXFIFO_FUL (1 << 0) + +/* SPDIF Clock register */ +#define STC_SYSCLK_DF_OFFSET 11 +#define STC_SYSCLK_DF_MASK (0x1ff << STC_SYSCLK_DF_OFFSET) +#define STC_SYSCLK_DF(x) ((((x) - 1) << STC_SYSCLK_DF_OFFSET) & STC_SYSCLK_DF_MASK) +#define STC_TXCLK_SRC_OFFSET 8 +#define STC_TXCLK_SRC_MASK (0x7 << STC_TXCLK_SRC_OFFSET) +#define STC_TXCLK_SRC_SET(x) ((x << STC_TXCLK_SRC_OFFSET) & STC_TXCLK_SRC_MASK) +#define STC_TXCLK_ALL_EN_OFFSET 7 +#define STC_TXCLK_ALL_EN_MASK (1 << STC_TXCLK_ALL_EN_OFFSET) +#define STC_TXCLK_ALL_EN (1 << STC_TXCLK_ALL_EN_OFFSET) +#define STC_TXCLK_DF_OFFSET 0 +#define STC_TXCLK_DF_MASK (0x7ff << STC_TXCLK_DF_OFFSET) +#define STC_TXCLK_DF(x) ((((x) - 1) << STC_TXCLK_DF_OFFSET) & STC_TXCLK_DF_MASK) +#define STC_TXCLK_SRC_MAX 8 + +#define STC_TXCLK_SPDIF_ROOT 1 + +/* SPDIF tx rate */ +enum spdif_txrate { + SPDIF_TXRATE_32000 = 0, + SPDIF_TXRATE_44100, + SPDIF_TXRATE_48000, + SPDIF_TXRATE_96000, + SPDIF_TXRATE_192000, +}; +#define SPDIF_TXRATE_MAX (SPDIF_TXRATE_192000 + 1) + + +#define SPDIF_CSTATUS_BYTE 6 +#define SPDIF_UBITS_SIZE 96 +#define SPDIF_QSUB_SIZE (SPDIF_UBITS_SIZE / 8) + + +#define FSL_SPDIF_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) + +#define FSL_SPDIF_RATES_CAPTURE (SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_96000) + +#define FSL_SPDIF_FORMATS_PLAYBACK (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +#define FSL_SPDIF_FORMATS_CAPTURE (SNDRV_PCM_FMTBIT_S24_LE) + +#endif /* _FSL_SPDIF_DAI_H */ diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c new file mode 100644 index 000000000..0d4880421 --- /dev/null +++ b/sound/soc/fsl/fsl_ssi.c @@ -0,0 +1,1485 @@ +/* + * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007-2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * + * Some notes why imx-pcm-fiq is used instead of DMA on some boards: + * + * 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 developed 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 challenge. 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/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "fsl_ssi.h" +#include "imx-pcm.h" + +/** + * FSLSSI_I2S_RATES: sample rates supported by the I2S + * + * This driver currently only supports the SSI running in I2S slave mode, + * which means the codec determines the sample rate. Therefore, we tell + * ALSA that we support all rates and let the codec driver decide what rates + * are really supported. + */ +#define FSLSSI_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS + +/** + * FSLSSI_I2S_FORMATS: audio formats supported by the SSI + * + * The SSI has a limitation in that the samples must be in the same byte + * order as the host CPU. This is because when multiple bytes are written + * to the STX register, the bytes and bits must be written in the same + * order. The STX is a shift register, so all the bits need to be aligned + * (bit-endianness must match byte-endianness). Processors typically write + * the bits within a byte in the same order that the bytes of a word are + * written in. So if the host CPU is big-endian, then only big-endian + * samples will be written to STX properly. + */ +#ifdef __BIG_ENDIAN +#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_S24_BE) +#else +#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE) +#endif + +#define FSLSSI_SIER_DBG_RX_FLAGS (CCSR_SSI_SIER_RFF0_EN | \ + CCSR_SSI_SIER_RLS_EN | CCSR_SSI_SIER_RFS_EN | \ + CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_RFRC_EN) +#define FSLSSI_SIER_DBG_TX_FLAGS (CCSR_SSI_SIER_TFE0_EN | \ + CCSR_SSI_SIER_TLS_EN | CCSR_SSI_SIER_TFS_EN | \ + CCSR_SSI_SIER_TUE0_EN | CCSR_SSI_SIER_TFRC_EN) + +enum fsl_ssi_type { + FSL_SSI_MCP8610, + FSL_SSI_MX21, + FSL_SSI_MX35, + FSL_SSI_MX51, +}; + +struct fsl_ssi_reg_val { + u32 sier; + u32 srcr; + u32 stcr; + u32 scr; +}; + +struct fsl_ssi_rxtx_reg_val { + struct fsl_ssi_reg_val rx; + struct fsl_ssi_reg_val tx; +}; +static const struct regmap_config fsl_ssi_regconfig = { + .max_register = CCSR_SSI_SACCDIS, + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +struct fsl_ssi_soc_data { + bool imx; + bool offline_config; + u32 sisr_write_mask; +}; + +/** + * fsl_ssi_private: per-SSI private data + * + * @reg: Pointer to the regmap registers + * @irq: IRQ of this SSI + * @cpu_dai_drv: CPU DAI driver for this device + * + * @dai_fmt: DAI configuration this device is currently used with + * @i2s_mode: i2s and network mode configuration of the device. Is used to + * switch between normal and i2s/network mode + * mode depending on the number of channels + * @use_dma: DMA is used or FIQ with stream filter + * @use_dual_fifo: DMA with support for both FIFOs used + * @fifo_deph: Depth of the SSI FIFOs + * @rxtx_reg_val: Specific register settings for receive/transmit configuration + * + * @clk: SSI clock + * @baudclk: SSI baud clock for master mode + * @baudclk_streams: Active streams that are using baudclk + * @bitclk_freq: bitclock frequency set by .set_dai_sysclk + * + * @dma_params_tx: DMA transmit parameters + * @dma_params_rx: DMA receive parameters + * @ssi_phys: physical address of the SSI registers + * + * @fiq_params: FIQ stream filtering parameters + * + * @pdev: Pointer to pdev used for deprecated fsl-ssi sound card + * + * @dbg_stats: Debugging statistics + * + * @soc: SoC specifc data + */ +struct fsl_ssi_private { + struct regmap *regs; + int irq; + struct snd_soc_dai_driver cpu_dai_drv; + + unsigned int dai_fmt; + u8 i2s_mode; + bool use_dma; + bool use_dual_fifo; + bool has_ipg_clk_name; + unsigned int fifo_depth; + struct fsl_ssi_rxtx_reg_val rxtx_reg_val; + + struct clk *clk; + struct clk *baudclk; + unsigned int baudclk_streams; + unsigned int bitclk_freq; + + /* DMA params */ + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_dmaengine_dai_dma_data dma_params_rx; + dma_addr_t ssi_phys; + + /* params for non-dma FIQ stream filtered mode */ + struct imx_pcm_fiq_params fiq_params; + + /* Used when using fsl-ssi as sound-card. This is only used by ppc and + * should be replaced with simple-sound-card. */ + struct platform_device *pdev; + + struct fsl_ssi_dbg dbg_stats; + + const struct fsl_ssi_soc_data *soc; +}; + +/* + * imx51 and later SoCs have a slightly different IP that allows the + * SSI configuration while the SSI unit is running. + * + * More important, it is necessary on those SoCs to configure the + * sperate TX/RX DMA bits just before starting the stream + * (fsl_ssi_trigger). The SDMA unit has to be configured before fsl_ssi + * sends any DMA requests to the SDMA unit, otherwise it is not defined + * how the SDMA unit handles the DMA request. + * + * SDMA units are present on devices starting at imx35 but the imx35 + * reference manual states that the DMA bits should not be changed + * while the SSI unit is running (SSIEN). So we support the necessary + * online configuration of fsl-ssi starting at imx51. + */ + +static struct fsl_ssi_soc_data fsl_ssi_mpc8610 = { + .imx = false, + .offline_config = true, + .sisr_write_mask = CCSR_SSI_SISR_RFRC | CCSR_SSI_SISR_TFRC | + CCSR_SSI_SISR_ROE0 | CCSR_SSI_SISR_ROE1 | + CCSR_SSI_SISR_TUE0 | CCSR_SSI_SISR_TUE1, +}; + +static struct fsl_ssi_soc_data fsl_ssi_imx21 = { + .imx = true, + .offline_config = true, + .sisr_write_mask = 0, +}; + +static struct fsl_ssi_soc_data fsl_ssi_imx35 = { + .imx = true, + .offline_config = true, + .sisr_write_mask = CCSR_SSI_SISR_RFRC | CCSR_SSI_SISR_TFRC | + CCSR_SSI_SISR_ROE0 | CCSR_SSI_SISR_ROE1 | + CCSR_SSI_SISR_TUE0 | CCSR_SSI_SISR_TUE1, +}; + +static struct fsl_ssi_soc_data fsl_ssi_imx51 = { + .imx = true, + .offline_config = false, + .sisr_write_mask = CCSR_SSI_SISR_ROE0 | CCSR_SSI_SISR_ROE1 | + CCSR_SSI_SISR_TUE0 | CCSR_SSI_SISR_TUE1, +}; + +static const struct of_device_id fsl_ssi_ids[] = { + { .compatible = "fsl,mpc8610-ssi", .data = &fsl_ssi_mpc8610 }, + { .compatible = "fsl,imx51-ssi", .data = &fsl_ssi_imx51 }, + { .compatible = "fsl,imx35-ssi", .data = &fsl_ssi_imx35 }, + { .compatible = "fsl,imx21-ssi", .data = &fsl_ssi_imx21 }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_ssi_ids); + +static bool fsl_ssi_is_ac97(struct fsl_ssi_private *ssi_private) +{ + return !!(ssi_private->dai_fmt & SND_SOC_DAIFMT_AC97); +} + +static bool fsl_ssi_is_i2s_master(struct fsl_ssi_private *ssi_private) +{ + return (ssi_private->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBS_CFS; +} + +static bool fsl_ssi_is_i2s_cbm_cfs(struct fsl_ssi_private *ssi_private) +{ + return (ssi_private->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBM_CFS; +} +/** + * fsl_ssi_isr: SSI interrupt handler + * + * Although it's possible to use the interrupt handler to send and receive + * data to/from the SSI, we use the DMA instead. Programming is more + * complicated, but the performance is much better. + * + * This interrupt handler is used only to gather statistics. + * + * @irq: IRQ of the SSI device + * @dev_id: pointer to the ssi_private structure for this SSI device + */ +static irqreturn_t fsl_ssi_isr(int irq, void *dev_id) +{ + struct fsl_ssi_private *ssi_private = dev_id; + struct regmap *regs = ssi_private->regs; + __be32 sisr; + __be32 sisr2; + + /* We got an interrupt, so read the status register to see what we + were interrupted for. We mask it with the Interrupt Enable register + so that we only check for events that we're interested in. + */ + regmap_read(regs, CCSR_SSI_SISR, &sisr); + + sisr2 = sisr & ssi_private->soc->sisr_write_mask; + /* Clear the bits that we set */ + if (sisr2) + regmap_write(regs, CCSR_SSI_SISR, sisr2); + + fsl_ssi_dbg_isr(&ssi_private->dbg_stats, sisr); + + return IRQ_HANDLED; +} + +/* + * Enable/Disable all rx/tx config flags at once. + */ +static void fsl_ssi_rxtx_config(struct fsl_ssi_private *ssi_private, + bool enable) +{ + struct regmap *regs = ssi_private->regs; + struct fsl_ssi_rxtx_reg_val *vals = &ssi_private->rxtx_reg_val; + + if (enable) { + regmap_update_bits(regs, CCSR_SSI_SIER, + vals->rx.sier | vals->tx.sier, + vals->rx.sier | vals->tx.sier); + regmap_update_bits(regs, CCSR_SSI_SRCR, + vals->rx.srcr | vals->tx.srcr, + vals->rx.srcr | vals->tx.srcr); + regmap_update_bits(regs, CCSR_SSI_STCR, + vals->rx.stcr | vals->tx.stcr, + vals->rx.stcr | vals->tx.stcr); + } else { + regmap_update_bits(regs, CCSR_SSI_SRCR, + vals->rx.srcr | vals->tx.srcr, 0); + regmap_update_bits(regs, CCSR_SSI_STCR, + vals->rx.stcr | vals->tx.stcr, 0); + regmap_update_bits(regs, CCSR_SSI_SIER, + vals->rx.sier | vals->tx.sier, 0); + } +} + +/* + * Calculate the bits that have to be disabled for the current stream that is + * getting disabled. This keeps the bits enabled that are necessary for the + * second stream to work if 'stream_active' is true. + * + * Detailed calculation: + * These are the values that need to be active after disabling. For non-active + * second stream, this is 0: + * vals_stream * !!stream_active + * + * The following computes the overall differences between the setup for the + * to-disable stream and the active stream, a simple XOR: + * vals_disable ^ (vals_stream * !!(stream_active)) + * + * The full expression adds a mask on all values we care about + */ +#define fsl_ssi_disable_val(vals_disable, vals_stream, stream_active) \ + ((vals_disable) & \ + ((vals_disable) ^ ((vals_stream) * (u32)!!(stream_active)))) + +/* + * Enable/Disable a ssi configuration. You have to pass either + * ssi_private->rxtx_reg_val.rx or tx as vals parameter. + */ +static void fsl_ssi_config(struct fsl_ssi_private *ssi_private, bool enable, + struct fsl_ssi_reg_val *vals) +{ + struct regmap *regs = ssi_private->regs; + struct fsl_ssi_reg_val *avals; + int nr_active_streams; + u32 scr_val; + int keep_active; + + regmap_read(regs, CCSR_SSI_SCR, &scr_val); + + nr_active_streams = !!(scr_val & CCSR_SSI_SCR_TE) + + !!(scr_val & CCSR_SSI_SCR_RE); + + if (nr_active_streams - 1 > 0) + keep_active = 1; + else + keep_active = 0; + + /* Find the other direction values rx or tx which we do not want to + * modify */ + if (&ssi_private->rxtx_reg_val.rx == vals) + avals = &ssi_private->rxtx_reg_val.tx; + else + avals = &ssi_private->rxtx_reg_val.rx; + + /* If vals should be disabled, start with disabling the unit */ + if (!enable) { + u32 scr = fsl_ssi_disable_val(vals->scr, avals->scr, + keep_active); + regmap_update_bits(regs, CCSR_SSI_SCR, scr, 0); + } + + /* + * We are running on a SoC which does not support online SSI + * reconfiguration, so we have to enable all necessary flags at once + * even if we do not use them later (capture and playback configuration) + */ + if (ssi_private->soc->offline_config) { + if ((enable && !nr_active_streams) || + (!enable && !keep_active)) + fsl_ssi_rxtx_config(ssi_private, enable); + + goto config_done; + } + + /* + * Configure single direction units while the SSI unit is running + * (online configuration) + */ + if (enable) { + regmap_update_bits(regs, CCSR_SSI_SIER, vals->sier, vals->sier); + regmap_update_bits(regs, CCSR_SSI_SRCR, vals->srcr, vals->srcr); + regmap_update_bits(regs, CCSR_SSI_STCR, vals->stcr, vals->stcr); + } else { + u32 sier; + u32 srcr; + u32 stcr; + + /* + * Disabling the necessary flags for one of rx/tx while the + * other stream is active is a little bit more difficult. We + * have to disable only those flags that differ between both + * streams (rx XOR tx) and that are set in the stream that is + * disabled now. Otherwise we could alter flags of the other + * stream + */ + + /* These assignments are simply vals without bits set in avals*/ + sier = fsl_ssi_disable_val(vals->sier, avals->sier, + keep_active); + srcr = fsl_ssi_disable_val(vals->srcr, avals->srcr, + keep_active); + stcr = fsl_ssi_disable_val(vals->stcr, avals->stcr, + keep_active); + + regmap_update_bits(regs, CCSR_SSI_SRCR, srcr, 0); + regmap_update_bits(regs, CCSR_SSI_STCR, stcr, 0); + regmap_update_bits(regs, CCSR_SSI_SIER, sier, 0); + } + +config_done: + /* Enabling of subunits is done after configuration */ + if (enable) + regmap_update_bits(regs, CCSR_SSI_SCR, vals->scr, vals->scr); +} + + +static void fsl_ssi_rx_config(struct fsl_ssi_private *ssi_private, bool enable) +{ + fsl_ssi_config(ssi_private, enable, &ssi_private->rxtx_reg_val.rx); +} + +static void fsl_ssi_tx_config(struct fsl_ssi_private *ssi_private, bool enable) +{ + fsl_ssi_config(ssi_private, enable, &ssi_private->rxtx_reg_val.tx); +} + +/* + * Setup rx/tx register values used to enable/disable the streams. These will + * be used later in fsl_ssi_config to setup the streams without the need to + * check for all different SSI modes. + */ +static void fsl_ssi_setup_reg_vals(struct fsl_ssi_private *ssi_private) +{ + struct fsl_ssi_rxtx_reg_val *reg = &ssi_private->rxtx_reg_val; + + reg->rx.sier = CCSR_SSI_SIER_RFF0_EN; + reg->rx.srcr = CCSR_SSI_SRCR_RFEN0; + reg->rx.scr = 0; + reg->tx.sier = CCSR_SSI_SIER_TFE0_EN; + reg->tx.stcr = CCSR_SSI_STCR_TFEN0; + reg->tx.scr = 0; + + if (!fsl_ssi_is_ac97(ssi_private)) { + reg->rx.scr = CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE; + reg->rx.sier |= CCSR_SSI_SIER_RFF0_EN; + reg->tx.scr = CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE; + reg->tx.sier |= CCSR_SSI_SIER_TFE0_EN; + } + + if (ssi_private->use_dma) { + reg->rx.sier |= CCSR_SSI_SIER_RDMAE; + reg->tx.sier |= CCSR_SSI_SIER_TDMAE; + } else { + reg->rx.sier |= CCSR_SSI_SIER_RIE; + reg->tx.sier |= CCSR_SSI_SIER_TIE; + } + + reg->rx.sier |= FSLSSI_SIER_DBG_RX_FLAGS; + reg->tx.sier |= FSLSSI_SIER_DBG_TX_FLAGS; +} + +static void fsl_ssi_setup_ac97(struct fsl_ssi_private *ssi_private) +{ + struct regmap *regs = ssi_private->regs; + + /* + * Setup the clock control register + */ + regmap_write(regs, CCSR_SSI_STCCR, + CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13)); + regmap_write(regs, CCSR_SSI_SRCCR, + CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13)); + + /* + * Enable AC97 mode and startup the SSI + */ + regmap_write(regs, CCSR_SSI_SACNT, + CCSR_SSI_SACNT_AC97EN | CCSR_SSI_SACNT_FV); + regmap_write(regs, CCSR_SSI_SACCDIS, 0xff); + regmap_write(regs, CCSR_SSI_SACCEN, 0x300); + + /* + * Enable SSI, Transmit and Receive. AC97 has to communicate with the + * codec before a stream is started. + */ + regmap_update_bits(regs, CCSR_SSI_SCR, + CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE, + CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE); + + regmap_write(regs, CCSR_SSI_SOR, CCSR_SSI_SOR_WAIT(3)); +} + +/** + * fsl_ssi_startup: create a new substream + * + * This is the first function called when a stream is opened. + * + * If this is the first stream open, then grab the IRQ and program most of + * the SSI registers. + */ +static int fsl_ssi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi_private *ssi_private = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + int ret; + + ret = clk_prepare_enable(ssi_private->clk); + if (ret) + return ret; + + /* When using dual fifo mode, it is safer to ensure an even period + * size. If appearing to an odd number while DMA always starts its + * task from fifo0, fifo1 would be neglected at the end of each + * period. But SSI would still access fifo1 with an invalid data. + */ + if (ssi_private->use_dual_fifo) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2); + + return 0; +} + +/** + * fsl_ssi_shutdown: shutdown the SSI + * + */ +static void fsl_ssi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi_private *ssi_private = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + + clk_disable_unprepare(ssi_private->clk); + +} + +/** + * fsl_ssi_set_bclk - configure Digital Audio Interface bit clock + * + * Note: This function can be only called when using SSI as DAI master + * + * Quick instruction for parameters: + * freq: Output BCLK frequency = samplerate * 32 (fixed) * channels + * dir: SND_SOC_CLOCK_OUT -> TxBCLK, SND_SOC_CLOCK_IN -> RxBCLK. + */ +static int fsl_ssi_set_bclk(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai, + struct snd_pcm_hw_params *hw_params) +{ + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regs = ssi_private->regs; + int synchronous = ssi_private->cpu_dai_drv.symmetric_rates, ret; + u32 pm = 999, div2, psr, stccr, mask, afreq, factor, i; + unsigned long clkrate, baudrate, tmprate; + u64 sub, savesub = 100000; + unsigned int freq; + bool baudclk_is_used; + + /* Prefer the explicitly set bitclock frequency */ + if (ssi_private->bitclk_freq) + freq = ssi_private->bitclk_freq; + else + freq = params_channels(hw_params) * 32 * params_rate(hw_params); + + /* Don't apply it to any non-baudclk circumstance */ + if (IS_ERR(ssi_private->baudclk)) + return -EINVAL; + + baudclk_is_used = ssi_private->baudclk_streams & ~(BIT(substream->stream)); + + /* It should be already enough to divide clock by setting pm alone */ + psr = 0; + div2 = 0; + + factor = (div2 + 1) * (7 * psr + 1) * 2; + + for (i = 0; i < 255; i++) { + tmprate = freq * factor * (i + 1); + + if (baudclk_is_used) + clkrate = clk_get_rate(ssi_private->baudclk); + else + clkrate = clk_round_rate(ssi_private->baudclk, tmprate); + + /* + * Hardware limitation: The bclk rate must be + * never greater than 1/5 IPG clock rate + */ + if (clkrate * 5 > clk_get_rate(ssi_private->clk)) + continue; + + clkrate /= factor; + afreq = clkrate / (i + 1); + + if (freq == afreq) + sub = 0; + else if (freq / afreq == 1) + sub = freq - afreq; + else if (afreq / freq == 1) + sub = afreq - freq; + else + continue; + + /* Calculate the fraction */ + sub *= 100000; + do_div(sub, freq); + + if (sub < savesub) { + baudrate = tmprate; + savesub = sub; + pm = i; + } + + /* We are lucky */ + if (savesub == 0) + break; + } + + /* No proper pm found if it is still remaining the initial value */ + if (pm == 999) { + dev_err(cpu_dai->dev, "failed to handle the required sysclk\n"); + return -EINVAL; + } + + stccr = CCSR_SSI_SxCCR_PM(pm + 1) | (div2 ? CCSR_SSI_SxCCR_DIV2 : 0) | + (psr ? CCSR_SSI_SxCCR_PSR : 0); + mask = CCSR_SSI_SxCCR_PM_MASK | CCSR_SSI_SxCCR_DIV2 | + CCSR_SSI_SxCCR_PSR; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || synchronous) + regmap_update_bits(regs, CCSR_SSI_STCCR, mask, stccr); + else + regmap_update_bits(regs, CCSR_SSI_SRCCR, mask, stccr); + + if (!baudclk_is_used) { + ret = clk_set_rate(ssi_private->baudclk, baudrate); + if (ret) { + dev_err(cpu_dai->dev, "failed to set baudclk rate\n"); + return -EINVAL; + } + } + + return 0; +} + +static int fsl_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); + + ssi_private->bitclk_freq = freq; + + return 0; +} + +/** + * fsl_ssi_hw_params - program the sample size + * + * Most of the SSI registers have been programmed in the startup function, + * but the word length must be programmed here. Unfortunately, programming + * the SxCCR.WL bits requires the SSI to be temporarily disabled. This can + * cause a problem with supporting simultaneous playback and capture. If + * the SSI is already playing a stream, then that stream may be temporarily + * stopped when you start capture. + * + * Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the + * clock master. + */ +static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *cpu_dai) +{ + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regs = ssi_private->regs; + unsigned int channels = params_channels(hw_params); + unsigned int sample_size = + snd_pcm_format_width(params_format(hw_params)); + u32 wl = CCSR_SSI_SxCCR_WL(sample_size); + int ret; + u32 scr_val; + int enabled; + + regmap_read(regs, CCSR_SSI_SCR, &scr_val); + enabled = scr_val & CCSR_SSI_SCR_SSIEN; + + /* + * If we're in synchronous mode, and the SSI is already enabled, + * then STCCR is already set properly. + */ + if (enabled && ssi_private->cpu_dai_drv.symmetric_rates) + return 0; + + if (fsl_ssi_is_i2s_master(ssi_private)) { + ret = fsl_ssi_set_bclk(substream, cpu_dai, hw_params); + if (ret) + return ret; + + /* Do not enable the clock if it is already enabled */ + if (!(ssi_private->baudclk_streams & BIT(substream->stream))) { + ret = clk_prepare_enable(ssi_private->baudclk); + if (ret) + return ret; + + ssi_private->baudclk_streams |= BIT(substream->stream); + } + } + + if (!fsl_ssi_is_ac97(ssi_private)) { + u8 i2smode; + /* + * Switch to normal net mode in order to have a frame sync + * signal every 32 bits instead of 16 bits + */ + if (fsl_ssi_is_i2s_cbm_cfs(ssi_private) && sample_size == 16) + i2smode = CCSR_SSI_SCR_I2S_MODE_NORMAL | + CCSR_SSI_SCR_NET; + else + i2smode = ssi_private->i2s_mode; + + regmap_update_bits(regs, CCSR_SSI_SCR, + CCSR_SSI_SCR_NET | CCSR_SSI_SCR_I2S_MODE_MASK, + channels == 1 ? 0 : i2smode); + } + + /* + * FIXME: The documentation says that SxCCR[WL] should not be + * modified while the SSI is enabled. The only time this can + * happen is if we're trying to do simultaneous playback and + * capture in asynchronous mode. Unfortunately, I have been enable + * to get that to work at all on the P1022DS. Therefore, we don't + * bother to disable/enable the SSI when setting SxCCR[WL], because + * the SSI will stop anyway. Maybe one day, this will get fixed. + */ + + /* In synchronous mode, the SSI uses STCCR for capture */ + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) || + ssi_private->cpu_dai_drv.symmetric_rates) + regmap_update_bits(regs, CCSR_SSI_STCCR, CCSR_SSI_SxCCR_WL_MASK, + wl); + else + regmap_update_bits(regs, CCSR_SSI_SRCCR, CCSR_SSI_SxCCR_WL_MASK, + wl); + + return 0; +} + +static int fsl_ssi_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi_private *ssi_private = + snd_soc_dai_get_drvdata(rtd->cpu_dai); + + if (fsl_ssi_is_i2s_master(ssi_private) && + ssi_private->baudclk_streams & BIT(substream->stream)) { + clk_disable_unprepare(ssi_private->baudclk); + ssi_private->baudclk_streams &= ~BIT(substream->stream); + } + + return 0; +} + +static int _fsl_ssi_set_dai_fmt(struct device *dev, + struct fsl_ssi_private *ssi_private, + unsigned int fmt) +{ + struct regmap *regs = ssi_private->regs; + u32 strcr = 0, stcr, srcr, scr, mask; + u8 wm; + + ssi_private->dai_fmt = fmt; + + if (fsl_ssi_is_i2s_master(ssi_private) && IS_ERR(ssi_private->baudclk)) { + dev_err(dev, "baudclk is missing which is necessary for master mode\n"); + return -EINVAL; + } + + fsl_ssi_setup_reg_vals(ssi_private); + + regmap_read(regs, CCSR_SSI_SCR, &scr); + scr &= ~(CCSR_SSI_SCR_SYN | CCSR_SSI_SCR_I2S_MODE_MASK); + scr |= CCSR_SSI_SCR_SYNC_TX_FS; + + mask = CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR | + CCSR_SSI_STCR_TSCKP | CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TFSL | + CCSR_SSI_STCR_TEFS; + regmap_read(regs, CCSR_SSI_STCR, &stcr); + regmap_read(regs, CCSR_SSI_SRCR, &srcr); + stcr &= ~mask; + srcr &= ~mask; + + ssi_private->i2s_mode = CCSR_SSI_SCR_NET; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFS: + ssi_private->i2s_mode |= CCSR_SSI_SCR_I2S_MODE_MASTER; + regmap_update_bits(regs, CCSR_SSI_STCCR, + CCSR_SSI_SxCCR_DC_MASK, + CCSR_SSI_SxCCR_DC(2)); + regmap_update_bits(regs, CCSR_SSI_SRCCR, + CCSR_SSI_SxCCR_DC_MASK, + CCSR_SSI_SxCCR_DC(2)); + break; + case SND_SOC_DAIFMT_CBM_CFM: + ssi_private->i2s_mode |= CCSR_SSI_SCR_I2S_MODE_SLAVE; + break; + default: + return -EINVAL; + } + + /* Data on rising edge of bclk, frame low, 1clk before data */ + strcr |= CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TSCKP | + CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TEFS; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* Data on rising edge of bclk, frame high */ + strcr |= CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_DSP_A: + /* Data on rising edge of bclk, frame high, 1clk before data */ + strcr |= CCSR_SSI_STCR_TFSL | CCSR_SSI_STCR_TSCKP | + CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TEFS; + break; + case SND_SOC_DAIFMT_DSP_B: + /* Data on rising edge of bclk, frame high */ + strcr |= CCSR_SSI_STCR_TFSL | CCSR_SSI_STCR_TSCKP | + CCSR_SSI_STCR_TXBIT0; + break; + case SND_SOC_DAIFMT_AC97: + ssi_private->i2s_mode |= CCSR_SSI_SCR_I2S_MODE_NORMAL; + break; + default: + return -EINVAL; + } + scr |= ssi_private->i2s_mode; + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do for both normal cases */ + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + strcr ^= CCSR_SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + strcr ^= CCSR_SSI_STCR_TFSI; + break; + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + strcr ^= CCSR_SSI_STCR_TSCKP; + strcr ^= CCSR_SSI_STCR_TFSI; + break; + default: + return -EINVAL; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + strcr |= CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR; + scr |= CCSR_SSI_SCR_SYS_CLK_EN; + break; + case SND_SOC_DAIFMT_CBM_CFM: + scr &= ~CCSR_SSI_SCR_SYS_CLK_EN; + break; + case SND_SOC_DAIFMT_CBM_CFS: + strcr &= ~CCSR_SSI_STCR_TXDIR; + strcr |= CCSR_SSI_STCR_TFDIR; + scr &= ~CCSR_SSI_SCR_SYS_CLK_EN; + break; + default: + return -EINVAL; + } + + stcr |= strcr; + srcr |= strcr; + + if (ssi_private->cpu_dai_drv.symmetric_rates) { + /* Need to clear RXDIR when using SYNC mode */ + srcr &= ~CCSR_SSI_SRCR_RXDIR; + scr |= CCSR_SSI_SCR_SYN; + } + + regmap_write(regs, CCSR_SSI_STCR, stcr); + regmap_write(regs, CCSR_SSI_SRCR, srcr); + regmap_write(regs, CCSR_SSI_SCR, scr); + + /* + * Set the watermark for transmit FIFI 0 and receive FIFO 0. We don't + * use FIFO 1. We program the transmit water to signal a DMA transfer + * if there are only two (or fewer) elements left in the FIFO. Two + * elements equals one frame (left channel, right channel). This value, + * however, depends on the depth of the transmit buffer. + * + * We set the watermark on the same level as the DMA burstsize. For + * fiq it is probably better to use the biggest possible watermark + * size. + */ + if (ssi_private->use_dma) + wm = ssi_private->fifo_depth - 2; + else + wm = ssi_private->fifo_depth; + + regmap_write(regs, CCSR_SSI_SFCSR, + CCSR_SSI_SFCSR_TFWM0(wm) | CCSR_SSI_SFCSR_RFWM0(wm) | + CCSR_SSI_SFCSR_TFWM1(wm) | CCSR_SSI_SFCSR_RFWM1(wm)); + + if (ssi_private->use_dual_fifo) { + regmap_update_bits(regs, CCSR_SSI_SRCR, CCSR_SSI_SRCR_RFEN1, + CCSR_SSI_SRCR_RFEN1); + regmap_update_bits(regs, CCSR_SSI_STCR, CCSR_SSI_STCR_TFEN1, + CCSR_SSI_STCR_TFEN1); + regmap_update_bits(regs, CCSR_SSI_SCR, CCSR_SSI_SCR_TCH_EN, + CCSR_SSI_SCR_TCH_EN); + } + + if (fmt & SND_SOC_DAIFMT_AC97) + fsl_ssi_setup_ac97(ssi_private); + + return 0; + +} + +/** + * fsl_ssi_set_dai_fmt - configure Digital Audio Interface Format. + */ +static int fsl_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); + + return _fsl_ssi_set_dai_fmt(cpu_dai->dev, ssi_private, fmt); +} + +/** + * fsl_ssi_set_dai_tdm_slot - set TDM slot number + * + * Note: This function can be only called when using SSI as DAI master + */ +static int fsl_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regs = ssi_private->regs; + u32 val; + + /* The slot number should be >= 2 if using Network mode or I2S mode */ + regmap_read(regs, CCSR_SSI_SCR, &val); + val &= CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_NET; + if (val && slots < 2) { + dev_err(cpu_dai->dev, "slot number should be >= 2 in I2S or NET\n"); + return -EINVAL; + } + + regmap_update_bits(regs, CCSR_SSI_STCCR, CCSR_SSI_SxCCR_DC_MASK, + CCSR_SSI_SxCCR_DC(slots)); + regmap_update_bits(regs, CCSR_SSI_SRCCR, CCSR_SSI_SxCCR_DC_MASK, + CCSR_SSI_SxCCR_DC(slots)); + + /* The register SxMSKs needs SSI to provide essential clock due to + * hardware design. So we here temporarily enable SSI to set them. + */ + regmap_read(regs, CCSR_SSI_SCR, &val); + val &= CCSR_SSI_SCR_SSIEN; + regmap_update_bits(regs, CCSR_SSI_SCR, CCSR_SSI_SCR_SSIEN, + CCSR_SSI_SCR_SSIEN); + + regmap_write(regs, CCSR_SSI_STMSK, ~tx_mask); + regmap_write(regs, CCSR_SSI_SRMSK, ~rx_mask); + + regmap_update_bits(regs, CCSR_SSI_SCR, CCSR_SSI_SCR_SSIEN, val); + + return 0; +} + +/** + * fsl_ssi_trigger: start and stop the DMA transfer. + * + * This function is called by ALSA to start, stop, pause, and resume the DMA + * transfer of data. + * + * The DMA channel is in external master start and pause mode, which + * means the SSI completely controls the flow of data. + */ +static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct regmap *regs = ssi_private->regs; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + fsl_ssi_tx_config(ssi_private, true); + else + fsl_ssi_rx_config(ssi_private, true); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + fsl_ssi_tx_config(ssi_private, false); + else + fsl_ssi_rx_config(ssi_private, false); + break; + + default: + return -EINVAL; + } + + if (fsl_ssi_is_ac97(ssi_private)) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_write(regs, CCSR_SSI_SOR, CCSR_SSI_SOR_TX_CLR); + else + regmap_write(regs, CCSR_SSI_SOR, CCSR_SSI_SOR_RX_CLR); + } + + return 0; +} + +static int fsl_ssi_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(dai); + + if (ssi_private->soc->imx && ssi_private->use_dma) { + dai->playback_dma_data = &ssi_private->dma_params_tx; + dai->capture_dma_data = &ssi_private->dma_params_rx; + } + + return 0; +} + +static const struct snd_soc_dai_ops fsl_ssi_dai_ops = { + .startup = fsl_ssi_startup, + .shutdown = fsl_ssi_shutdown, + .hw_params = fsl_ssi_hw_params, + .hw_free = fsl_ssi_hw_free, + .set_fmt = fsl_ssi_set_dai_fmt, + .set_sysclk = fsl_ssi_set_dai_sysclk, + .set_tdm_slot = fsl_ssi_set_dai_tdm_slot, + .trigger = fsl_ssi_trigger, +}; + +/* Template for the CPU dai driver structure */ +static struct snd_soc_dai_driver fsl_ssi_dai_template = { + .probe = fsl_ssi_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 2, + .rates = FSLSSI_I2S_RATES, + .formats = FSLSSI_I2S_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = FSLSSI_I2S_RATES, + .formats = FSLSSI_I2S_FORMATS, + }, + .ops = &fsl_ssi_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_ssi_component = { + .name = "fsl-ssi", +}; + +static struct snd_soc_dai_driver fsl_ssi_ac97_dai = { + .bus_control = true, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &fsl_ssi_dai_ops, +}; + + +static struct fsl_ssi_private *fsl_ac97_data; + +static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct regmap *regs = fsl_ac97_data->regs; + unsigned int lreg; + unsigned int lval; + + if (reg > 0x7f) + return; + + + lreg = reg << 12; + regmap_write(regs, CCSR_SSI_SACADD, lreg); + + lval = val << 4; + regmap_write(regs, CCSR_SSI_SACDAT, lval); + + regmap_update_bits(regs, CCSR_SSI_SACNT, CCSR_SSI_SACNT_RDWR_MASK, + CCSR_SSI_SACNT_WR); + udelay(100); +} + +static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct regmap *regs = fsl_ac97_data->regs; + + unsigned short val = -1; + u32 reg_val; + unsigned int lreg; + + lreg = (reg & 0x7f) << 12; + regmap_write(regs, CCSR_SSI_SACADD, lreg); + regmap_update_bits(regs, CCSR_SSI_SACNT, CCSR_SSI_SACNT_RDWR_MASK, + CCSR_SSI_SACNT_RD); + + udelay(100); + + regmap_read(regs, CCSR_SSI_SACDAT, ®_val); + val = (reg_val >> 4) & 0xffff; + + return val; +} + +static struct snd_ac97_bus_ops fsl_ssi_ac97_ops = { + .read = fsl_ssi_ac97_read, + .write = fsl_ssi_ac97_write, +}; + +/** + * Make every character in a string lower-case + */ +static void make_lowercase(char *s) +{ + char *p = s; + char c; + + while ((c = *p)) { + if ((c >= 'A') && (c <= 'Z')) + *p = c + ('a' - 'A'); + p++; + } +} + +static int fsl_ssi_imx_probe(struct platform_device *pdev, + struct fsl_ssi_private *ssi_private, void __iomem *iomem) +{ + struct device_node *np = pdev->dev.of_node; + u32 dmas[4]; + int ret; + + if (ssi_private->has_ipg_clk_name) + ssi_private->clk = devm_clk_get(&pdev->dev, "ipg"); + else + ssi_private->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(ssi_private->clk)) { + ret = PTR_ERR(ssi_private->clk); + dev_err(&pdev->dev, "could not get clock: %d\n", ret); + return ret; + } + + if (!ssi_private->has_ipg_clk_name) { + ret = clk_prepare_enable(ssi_private->clk); + if (ret) { + dev_err(&pdev->dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + } + + /* For those SLAVE implementations, we ingore non-baudclk cases + * and, instead, abandon MASTER mode that needs baud clock. + */ + ssi_private->baudclk = devm_clk_get(&pdev->dev, "baud"); + if (IS_ERR(ssi_private->baudclk)) + dev_dbg(&pdev->dev, "could not get baud clock: %ld\n", + PTR_ERR(ssi_private->baudclk)); + + /* + * We have burstsize be "fifo_depth - 2" to match the SSI + * watermark setting in fsl_ssi_startup(). + */ + ssi_private->dma_params_tx.maxburst = ssi_private->fifo_depth - 2; + ssi_private->dma_params_rx.maxburst = ssi_private->fifo_depth - 2; + ssi_private->dma_params_tx.addr = ssi_private->ssi_phys + CCSR_SSI_STX0; + ssi_private->dma_params_rx.addr = ssi_private->ssi_phys + CCSR_SSI_SRX0; + + ret = of_property_read_u32_array(np, "dmas", dmas, 4); + if (ssi_private->use_dma && !ret && dmas[2] == IMX_DMATYPE_SSI_DUAL) { + ssi_private->use_dual_fifo = true; + /* When using dual fifo mode, we need to keep watermark + * as even numbers due to dma script limitation. + */ + ssi_private->dma_params_tx.maxburst &= ~0x1; + ssi_private->dma_params_rx.maxburst &= ~0x1; + } + + if (!ssi_private->use_dma) { + + /* + * Some boards use an incompatible codec. To get it + * working, we are using imx-fiq-pcm-audio, that + * can handle those codecs. DMA is not possible in this + * situation. + */ + + ssi_private->fiq_params.irq = ssi_private->irq; + ssi_private->fiq_params.base = iomem; + ssi_private->fiq_params.dma_params_rx = + &ssi_private->dma_params_rx; + ssi_private->fiq_params.dma_params_tx = + &ssi_private->dma_params_tx; + + ret = imx_pcm_fiq_init(pdev, &ssi_private->fiq_params); + if (ret) + goto error_pcm; + } else { + ret = imx_pcm_dma_init(pdev); + if (ret) + goto error_pcm; + } + + return 0; + +error_pcm: + + if (!ssi_private->has_ipg_clk_name) + clk_disable_unprepare(ssi_private->clk); + return ret; +} + +static void fsl_ssi_imx_clean(struct platform_device *pdev, + struct fsl_ssi_private *ssi_private) +{ + if (!ssi_private->use_dma) + imx_pcm_fiq_exit(pdev); + if (!ssi_private->has_ipg_clk_name) + clk_disable_unprepare(ssi_private->clk); +} + +static int fsl_ssi_probe(struct platform_device *pdev) +{ + struct fsl_ssi_private *ssi_private; + int ret = 0; + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; + const char *p, *sprop; + const uint32_t *iprop; + struct resource *res; + void __iomem *iomem; + char name[64]; + + /* SSIs that are not connected on the board should have a + * status = "disabled" + * property in their device tree nodes. + */ + if (!of_device_is_available(np)) + return -ENODEV; + + of_id = of_match_device(fsl_ssi_ids, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + ssi_private = devm_kzalloc(&pdev->dev, sizeof(*ssi_private), + GFP_KERNEL); + if (!ssi_private) { + dev_err(&pdev->dev, "could not allocate DAI object\n"); + return -ENOMEM; + } + + ssi_private->soc = of_id->data; + + sprop = of_get_property(np, "fsl,mode", NULL); + if (sprop) { + if (!strcmp(sprop, "ac97-slave")) + ssi_private->dai_fmt = SND_SOC_DAIFMT_AC97; + } + + ssi_private->use_dma = !of_property_read_bool(np, + "fsl,fiq-stream-filter"); + + if (fsl_ssi_is_ac97(ssi_private)) { + memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_ac97_dai, + sizeof(fsl_ssi_ac97_dai)); + + fsl_ac97_data = ssi_private; + + snd_soc_set_ac97_ops_of_reset(&fsl_ssi_ac97_ops, pdev); + } else { + /* Initialize this copy of the CPU DAI driver structure */ + memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template, + sizeof(fsl_ssi_dai_template)); + } + ssi_private->cpu_dai_drv.name = dev_name(&pdev->dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iomem = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(iomem)) + return PTR_ERR(iomem); + ssi_private->ssi_phys = res->start; + + ret = of_property_match_string(np, "clock-names", "ipg"); + if (ret < 0) { + ssi_private->has_ipg_clk_name = false; + ssi_private->regs = devm_regmap_init_mmio(&pdev->dev, iomem, + &fsl_ssi_regconfig); + } else { + ssi_private->has_ipg_clk_name = true; + ssi_private->regs = devm_regmap_init_mmio_clk(&pdev->dev, + "ipg", iomem, &fsl_ssi_regconfig); + } + if (IS_ERR(ssi_private->regs)) { + dev_err(&pdev->dev, "Failed to init register map\n"); + return PTR_ERR(ssi_private->regs); + } + + ssi_private->irq = platform_get_irq(pdev, 0); + if (ssi_private->irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return ssi_private->irq; + } + + /* Are the RX and the TX clocks locked? */ + if (!of_find_property(np, "fsl,ssi-asynchronous", NULL)) { + ssi_private->cpu_dai_drv.symmetric_rates = 1; + ssi_private->cpu_dai_drv.symmetric_channels = 1; + ssi_private->cpu_dai_drv.symmetric_samplebits = 1; + } + + /* Determine the FIFO depth. */ + iprop = of_get_property(np, "fsl,fifo-depth", NULL); + if (iprop) + ssi_private->fifo_depth = be32_to_cpup(iprop); + else + /* Older 8610 DTs didn't have the fifo-depth property */ + ssi_private->fifo_depth = 8; + + dev_set_drvdata(&pdev->dev, ssi_private); + + if (ssi_private->soc->imx) { + ret = fsl_ssi_imx_probe(pdev, ssi_private, iomem); + if (ret) + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_ssi_component, + &ssi_private->cpu_dai_drv, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); + goto error_asoc_register; + } + + if (ssi_private->use_dma) { + ret = devm_request_irq(&pdev->dev, ssi_private->irq, + fsl_ssi_isr, 0, dev_name(&pdev->dev), + ssi_private); + if (ret < 0) { + dev_err(&pdev->dev, "could not claim irq %u\n", + ssi_private->irq); + goto error_asoc_register; + } + } + + ret = fsl_ssi_debugfs_create(&ssi_private->dbg_stats, &pdev->dev); + if (ret) + goto error_asoc_register; + + /* + * If codec-handle property is missing from SSI node, we assume + * that the machine driver uses new binding which does not require + * SSI driver to trigger machine driver's probe. + */ + if (!of_get_property(np, "codec-handle", NULL)) + goto done; + + /* Trigger the machine driver's probe function. The platform driver + * name of the machine driver is taken from /compatible property of the + * device tree. We also pass the address of the CPU DAI driver + * structure. + */ + sprop = of_get_property(of_find_node_by_path("/"), "compatible", NULL); + /* Sometimes the compatible name has a "fsl," prefix, so we strip it. */ + p = strrchr(sprop, ','); + if (p) + sprop = p + 1; + snprintf(name, sizeof(name), "snd-soc-%s", sprop); + make_lowercase(name); + + ssi_private->pdev = + platform_device_register_data(&pdev->dev, name, 0, NULL, 0); + if (IS_ERR(ssi_private->pdev)) { + ret = PTR_ERR(ssi_private->pdev); + dev_err(&pdev->dev, "failed to register platform: %d\n", ret); + goto error_sound_card; + } + +done: + if (ssi_private->dai_fmt) + _fsl_ssi_set_dai_fmt(&pdev->dev, ssi_private, + ssi_private->dai_fmt); + + return 0; + +error_sound_card: + fsl_ssi_debugfs_remove(&ssi_private->dbg_stats); + +error_asoc_register: + if (ssi_private->soc->imx) + fsl_ssi_imx_clean(pdev, ssi_private); + + return ret; +} + +static int fsl_ssi_remove(struct platform_device *pdev) +{ + struct fsl_ssi_private *ssi_private = dev_get_drvdata(&pdev->dev); + + fsl_ssi_debugfs_remove(&ssi_private->dbg_stats); + + if (ssi_private->pdev) + platform_device_unregister(ssi_private->pdev); + + if (ssi_private->soc->imx) + fsl_ssi_imx_clean(pdev, ssi_private); + + return 0; +} + +static struct platform_driver fsl_ssi_driver = { + .driver = { + .name = "fsl-ssi-dai", + .of_match_table = fsl_ssi_ids, + }, + .probe = fsl_ssi_probe, + .remove = fsl_ssi_remove, +}; + +module_platform_driver(fsl_ssi_driver); + +MODULE_ALIAS("platform:fsl-ssi-dai"); +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h new file mode 100644 index 000000000..506510540 --- /dev/null +++ b/sound/soc/fsl/fsl_ssi.h @@ -0,0 +1,268 @@ +/* + * fsl_ssi.h - ALSA SSI interface for the Freescale MPC8610 SoC + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed + * under the terms of the GNU General Public License version 2. This + * program is licensed "as is" without any warranty of any kind, whether + * express or implied. + */ + +#ifndef _MPC8610_I2S_H +#define _MPC8610_I2S_H + +/* SSI registers */ +#define CCSR_SSI_STX0 0x00 +#define CCSR_SSI_STX1 0x04 +#define CCSR_SSI_SRX0 0x08 +#define CCSR_SSI_SRX1 0x0c +#define CCSR_SSI_SCR 0x10 +#define CCSR_SSI_SISR 0x14 +#define CCSR_SSI_SIER 0x18 +#define CCSR_SSI_STCR 0x1c +#define CCSR_SSI_SRCR 0x20 +#define CCSR_SSI_STCCR 0x24 +#define CCSR_SSI_SRCCR 0x28 +#define CCSR_SSI_SFCSR 0x2c +#define CCSR_SSI_STR 0x30 +#define CCSR_SSI_SOR 0x34 +#define CCSR_SSI_SACNT 0x38 +#define CCSR_SSI_SACADD 0x3c +#define CCSR_SSI_SACDAT 0x40 +#define CCSR_SSI_SATAG 0x44 +#define CCSR_SSI_STMSK 0x48 +#define CCSR_SSI_SRMSK 0x4c +#define CCSR_SSI_SACCST 0x50 +#define CCSR_SSI_SACCEN 0x54 +#define CCSR_SSI_SACCDIS 0x58 + +#define CCSR_SSI_SCR_SYNC_TX_FS 0x00001000 +#define CCSR_SSI_SCR_RFR_CLK_DIS 0x00000800 +#define CCSR_SSI_SCR_TFR_CLK_DIS 0x00000400 +#define CCSR_SSI_SCR_TCH_EN 0x00000100 +#define CCSR_SSI_SCR_SYS_CLK_EN 0x00000080 +#define CCSR_SSI_SCR_I2S_MODE_MASK 0x00000060 +#define CCSR_SSI_SCR_I2S_MODE_NORMAL 0x00000000 +#define CCSR_SSI_SCR_I2S_MODE_MASTER 0x00000020 +#define CCSR_SSI_SCR_I2S_MODE_SLAVE 0x00000040 +#define CCSR_SSI_SCR_SYN 0x00000010 +#define CCSR_SSI_SCR_NET 0x00000008 +#define CCSR_SSI_SCR_RE 0x00000004 +#define CCSR_SSI_SCR_TE 0x00000002 +#define CCSR_SSI_SCR_SSIEN 0x00000001 + +#define CCSR_SSI_SISR_RFRC 0x01000000 +#define CCSR_SSI_SISR_TFRC 0x00800000 +#define CCSR_SSI_SISR_CMDAU 0x00040000 +#define CCSR_SSI_SISR_CMDDU 0x00020000 +#define CCSR_SSI_SISR_RXT 0x00010000 +#define CCSR_SSI_SISR_RDR1 0x00008000 +#define CCSR_SSI_SISR_RDR0 0x00004000 +#define CCSR_SSI_SISR_TDE1 0x00002000 +#define CCSR_SSI_SISR_TDE0 0x00001000 +#define CCSR_SSI_SISR_ROE1 0x00000800 +#define CCSR_SSI_SISR_ROE0 0x00000400 +#define CCSR_SSI_SISR_TUE1 0x00000200 +#define CCSR_SSI_SISR_TUE0 0x00000100 +#define CCSR_SSI_SISR_TFS 0x00000080 +#define CCSR_SSI_SISR_RFS 0x00000040 +#define CCSR_SSI_SISR_TLS 0x00000020 +#define CCSR_SSI_SISR_RLS 0x00000010 +#define CCSR_SSI_SISR_RFF1 0x00000008 +#define CCSR_SSI_SISR_RFF0 0x00000004 +#define CCSR_SSI_SISR_TFE1 0x00000002 +#define CCSR_SSI_SISR_TFE0 0x00000001 + +#define CCSR_SSI_SIER_RFRC_EN 0x01000000 +#define CCSR_SSI_SIER_TFRC_EN 0x00800000 +#define CCSR_SSI_SIER_RDMAE 0x00400000 +#define CCSR_SSI_SIER_RIE 0x00200000 +#define CCSR_SSI_SIER_TDMAE 0x00100000 +#define CCSR_SSI_SIER_TIE 0x00080000 +#define CCSR_SSI_SIER_CMDAU_EN 0x00040000 +#define CCSR_SSI_SIER_CMDDU_EN 0x00020000 +#define CCSR_SSI_SIER_RXT_EN 0x00010000 +#define CCSR_SSI_SIER_RDR1_EN 0x00008000 +#define CCSR_SSI_SIER_RDR0_EN 0x00004000 +#define CCSR_SSI_SIER_TDE1_EN 0x00002000 +#define CCSR_SSI_SIER_TDE0_EN 0x00001000 +#define CCSR_SSI_SIER_ROE1_EN 0x00000800 +#define CCSR_SSI_SIER_ROE0_EN 0x00000400 +#define CCSR_SSI_SIER_TUE1_EN 0x00000200 +#define CCSR_SSI_SIER_TUE0_EN 0x00000100 +#define CCSR_SSI_SIER_TFS_EN 0x00000080 +#define CCSR_SSI_SIER_RFS_EN 0x00000040 +#define CCSR_SSI_SIER_TLS_EN 0x00000020 +#define CCSR_SSI_SIER_RLS_EN 0x00000010 +#define CCSR_SSI_SIER_RFF1_EN 0x00000008 +#define CCSR_SSI_SIER_RFF0_EN 0x00000004 +#define CCSR_SSI_SIER_TFE1_EN 0x00000002 +#define CCSR_SSI_SIER_TFE0_EN 0x00000001 + +#define CCSR_SSI_STCR_TXBIT0 0x00000200 +#define CCSR_SSI_STCR_TFEN1 0x00000100 +#define CCSR_SSI_STCR_TFEN0 0x00000080 +#define CCSR_SSI_STCR_TFDIR 0x00000040 +#define CCSR_SSI_STCR_TXDIR 0x00000020 +#define CCSR_SSI_STCR_TSHFD 0x00000010 +#define CCSR_SSI_STCR_TSCKP 0x00000008 +#define CCSR_SSI_STCR_TFSI 0x00000004 +#define CCSR_SSI_STCR_TFSL 0x00000002 +#define CCSR_SSI_STCR_TEFS 0x00000001 + +#define CCSR_SSI_SRCR_RXEXT 0x00000400 +#define CCSR_SSI_SRCR_RXBIT0 0x00000200 +#define CCSR_SSI_SRCR_RFEN1 0x00000100 +#define CCSR_SSI_SRCR_RFEN0 0x00000080 +#define CCSR_SSI_SRCR_RFDIR 0x00000040 +#define CCSR_SSI_SRCR_RXDIR 0x00000020 +#define CCSR_SSI_SRCR_RSHFD 0x00000010 +#define CCSR_SSI_SRCR_RSCKP 0x00000008 +#define CCSR_SSI_SRCR_RFSI 0x00000004 +#define CCSR_SSI_SRCR_RFSL 0x00000002 +#define CCSR_SSI_SRCR_REFS 0x00000001 + +/* STCCR and SRCCR */ +#define CCSR_SSI_SxCCR_DIV2_SHIFT 18 +#define CCSR_SSI_SxCCR_DIV2 0x00040000 +#define CCSR_SSI_SxCCR_PSR_SHIFT 17 +#define CCSR_SSI_SxCCR_PSR 0x00020000 +#define CCSR_SSI_SxCCR_WL_SHIFT 13 +#define CCSR_SSI_SxCCR_WL_MASK 0x0001E000 +#define CCSR_SSI_SxCCR_WL(x) \ + (((((x) / 2) - 1) << CCSR_SSI_SxCCR_WL_SHIFT) & CCSR_SSI_SxCCR_WL_MASK) +#define CCSR_SSI_SxCCR_DC_SHIFT 8 +#define CCSR_SSI_SxCCR_DC_MASK 0x00001F00 +#define CCSR_SSI_SxCCR_DC(x) \ + ((((x) - 1) << CCSR_SSI_SxCCR_DC_SHIFT) & CCSR_SSI_SxCCR_DC_MASK) +#define CCSR_SSI_SxCCR_PM_SHIFT 0 +#define CCSR_SSI_SxCCR_PM_MASK 0x000000FF +#define CCSR_SSI_SxCCR_PM(x) \ + ((((x) - 1) << CCSR_SSI_SxCCR_PM_SHIFT) & CCSR_SSI_SxCCR_PM_MASK) + +/* + * The xFCNT bits are read-only, and the xFWM bits are read/write. Use the + * CCSR_SSI_SFCSR_xFCNTy() macros to read the FIFO counters, and use the + * CCSR_SSI_SFCSR_xFWMy() macros to set the watermarks. + */ +#define CCSR_SSI_SFCSR_RFCNT1_SHIFT 28 +#define CCSR_SSI_SFCSR_RFCNT1_MASK 0xF0000000 +#define CCSR_SSI_SFCSR_RFCNT1(x) \ + (((x) & CCSR_SSI_SFCSR_RFCNT1_MASK) >> CCSR_SSI_SFCSR_RFCNT1_SHIFT) +#define CCSR_SSI_SFCSR_TFCNT1_SHIFT 24 +#define CCSR_SSI_SFCSR_TFCNT1_MASK 0x0F000000 +#define CCSR_SSI_SFCSR_TFCNT1(x) \ + (((x) & CCSR_SSI_SFCSR_TFCNT1_MASK) >> CCSR_SSI_SFCSR_TFCNT1_SHIFT) +#define CCSR_SSI_SFCSR_RFWM1_SHIFT 20 +#define CCSR_SSI_SFCSR_RFWM1_MASK 0x00F00000 +#define CCSR_SSI_SFCSR_RFWM1(x) \ + (((x) << CCSR_SSI_SFCSR_RFWM1_SHIFT) & CCSR_SSI_SFCSR_RFWM1_MASK) +#define CCSR_SSI_SFCSR_TFWM1_SHIFT 16 +#define CCSR_SSI_SFCSR_TFWM1_MASK 0x000F0000 +#define CCSR_SSI_SFCSR_TFWM1(x) \ + (((x) << CCSR_SSI_SFCSR_TFWM1_SHIFT) & CCSR_SSI_SFCSR_TFWM1_MASK) +#define CCSR_SSI_SFCSR_RFCNT0_SHIFT 12 +#define CCSR_SSI_SFCSR_RFCNT0_MASK 0x0000F000 +#define CCSR_SSI_SFCSR_RFCNT0(x) \ + (((x) & CCSR_SSI_SFCSR_RFCNT0_MASK) >> CCSR_SSI_SFCSR_RFCNT0_SHIFT) +#define CCSR_SSI_SFCSR_TFCNT0_SHIFT 8 +#define CCSR_SSI_SFCSR_TFCNT0_MASK 0x00000F00 +#define CCSR_SSI_SFCSR_TFCNT0(x) \ + (((x) & CCSR_SSI_SFCSR_TFCNT0_MASK) >> CCSR_SSI_SFCSR_TFCNT0_SHIFT) +#define CCSR_SSI_SFCSR_RFWM0_SHIFT 4 +#define CCSR_SSI_SFCSR_RFWM0_MASK 0x000000F0 +#define CCSR_SSI_SFCSR_RFWM0(x) \ + (((x) << CCSR_SSI_SFCSR_RFWM0_SHIFT) & CCSR_SSI_SFCSR_RFWM0_MASK) +#define CCSR_SSI_SFCSR_TFWM0_SHIFT 0 +#define CCSR_SSI_SFCSR_TFWM0_MASK 0x0000000F +#define CCSR_SSI_SFCSR_TFWM0(x) \ + (((x) << CCSR_SSI_SFCSR_TFWM0_SHIFT) & CCSR_SSI_SFCSR_TFWM0_MASK) + +#define CCSR_SSI_STR_TEST 0x00008000 +#define CCSR_SSI_STR_RCK2TCK 0x00004000 +#define CCSR_SSI_STR_RFS2TFS 0x00002000 +#define CCSR_SSI_STR_RXSTATE(x) (((x) >> 8) & 0x1F) +#define CCSR_SSI_STR_TXD2RXD 0x00000080 +#define CCSR_SSI_STR_TCK2RCK 0x00000040 +#define CCSR_SSI_STR_TFS2RFS 0x00000020 +#define CCSR_SSI_STR_TXSTATE(x) ((x) & 0x1F) + +#define CCSR_SSI_SOR_CLKOFF 0x00000040 +#define CCSR_SSI_SOR_RX_CLR 0x00000020 +#define CCSR_SSI_SOR_TX_CLR 0x00000010 +#define CCSR_SSI_SOR_INIT 0x00000008 +#define CCSR_SSI_SOR_WAIT_SHIFT 1 +#define CCSR_SSI_SOR_WAIT_MASK 0x00000006 +#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT) +#define CCSR_SSI_SOR_SYNRST 0x00000001 + +#define CCSR_SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5) +#define CCSR_SSI_SACNT_WR 0x00000010 +#define CCSR_SSI_SACNT_RD 0x00000008 +#define CCSR_SSI_SACNT_RDWR_MASK 0x00000018 +#define CCSR_SSI_SACNT_TIF 0x00000004 +#define CCSR_SSI_SACNT_FV 0x00000002 +#define CCSR_SSI_SACNT_AC97EN 0x00000001 + + +struct device; + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +struct fsl_ssi_dbg { + struct dentry *dbg_dir; + struct dentry *dbg_stats; + + struct { + unsigned int rfrc; + unsigned int tfrc; + unsigned int cmdau; + unsigned int cmddu; + unsigned int rxt; + unsigned int rdr1; + unsigned int rdr0; + unsigned int tde1; + unsigned int tde0; + unsigned int roe1; + unsigned int roe0; + unsigned int tue1; + unsigned int tue0; + unsigned int tfs; + unsigned int rfs; + unsigned int tls; + unsigned int rls; + unsigned int rff1; + unsigned int rff0; + unsigned int tfe1; + unsigned int tfe0; + } stats; +}; + +void fsl_ssi_dbg_isr(struct fsl_ssi_dbg *ssi_dbg, u32 sisr); + +int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, struct device *dev); + +void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg); + +#else + +struct fsl_ssi_dbg { +}; + +static inline void fsl_ssi_dbg_isr(struct fsl_ssi_dbg *stats, u32 sisr) +{ +} + +static inline int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, + struct device *dev) +{ + return 0; +} + +static inline void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg) +{ +} +#endif /* ! IS_ENABLED(CONFIG_DEBUG_FS) */ + +#endif diff --git a/sound/soc/fsl/fsl_ssi_dbg.c b/sound/soc/fsl/fsl_ssi_dbg.c new file mode 100644 index 000000000..5469ffbc0 --- /dev/null +++ b/sound/soc/fsl/fsl_ssi_dbg.c @@ -0,0 +1,163 @@ +/* + * Freescale SSI ALSA SoC Digital Audio Interface (DAI) debugging functions + * + * Copyright 2014 Markus Pargmann <mpa@pengutronix.de>, Pengutronix + * + * Splitted from fsl_ssi.c + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/kernel.h> + +#include "fsl_ssi.h" + +void fsl_ssi_dbg_isr(struct fsl_ssi_dbg *dbg, u32 sisr) +{ + if (sisr & CCSR_SSI_SISR_RFRC) + dbg->stats.rfrc++; + + if (sisr & CCSR_SSI_SISR_TFRC) + dbg->stats.tfrc++; + + if (sisr & CCSR_SSI_SISR_CMDAU) + dbg->stats.cmdau++; + + if (sisr & CCSR_SSI_SISR_CMDDU) + dbg->stats.cmddu++; + + if (sisr & CCSR_SSI_SISR_RXT) + dbg->stats.rxt++; + + if (sisr & CCSR_SSI_SISR_RDR1) + dbg->stats.rdr1++; + + if (sisr & CCSR_SSI_SISR_RDR0) + dbg->stats.rdr0++; + + if (sisr & CCSR_SSI_SISR_TDE1) + dbg->stats.tde1++; + + if (sisr & CCSR_SSI_SISR_TDE0) + dbg->stats.tde0++; + + if (sisr & CCSR_SSI_SISR_ROE1) + dbg->stats.roe1++; + + if (sisr & CCSR_SSI_SISR_ROE0) + dbg->stats.roe0++; + + if (sisr & CCSR_SSI_SISR_TUE1) + dbg->stats.tue1++; + + if (sisr & CCSR_SSI_SISR_TUE0) + dbg->stats.tue0++; + + if (sisr & CCSR_SSI_SISR_TFS) + dbg->stats.tfs++; + + if (sisr & CCSR_SSI_SISR_RFS) + dbg->stats.rfs++; + + if (sisr & CCSR_SSI_SISR_TLS) + dbg->stats.tls++; + + if (sisr & CCSR_SSI_SISR_RLS) + dbg->stats.rls++; + + if (sisr & CCSR_SSI_SISR_RFF1) + dbg->stats.rff1++; + + if (sisr & CCSR_SSI_SISR_RFF0) + dbg->stats.rff0++; + + if (sisr & CCSR_SSI_SISR_TFE1) + dbg->stats.tfe1++; + + if (sisr & CCSR_SSI_SISR_TFE0) + dbg->stats.tfe0++; +} + +/* Show the statistics of a flag only if its interrupt is enabled. The + * compiler will optimze this code to a no-op if the interrupt is not + * enabled. + */ +#define SIER_SHOW(flag, name) \ + do { \ + if (CCSR_SSI_SIER_##flag) \ + seq_printf(s, #name "=%u\n", ssi_dbg->stats.name); \ + } while (0) + + +/** + * fsl_sysfs_ssi_show: display SSI statistics + * + * Display the statistics for the current SSI device. To avoid confusion, + * we only show those counts that are enabled. + */ +static int fsl_ssi_stats_show(struct seq_file *s, void *unused) +{ + struct fsl_ssi_dbg *ssi_dbg = s->private; + + SIER_SHOW(RFRC_EN, rfrc); + SIER_SHOW(TFRC_EN, tfrc); + SIER_SHOW(CMDAU_EN, cmdau); + SIER_SHOW(CMDDU_EN, cmddu); + SIER_SHOW(RXT_EN, rxt); + SIER_SHOW(RDR1_EN, rdr1); + SIER_SHOW(RDR0_EN, rdr0); + SIER_SHOW(TDE1_EN, tde1); + SIER_SHOW(TDE0_EN, tde0); + SIER_SHOW(ROE1_EN, roe1); + SIER_SHOW(ROE0_EN, roe0); + SIER_SHOW(TUE1_EN, tue1); + SIER_SHOW(TUE0_EN, tue0); + SIER_SHOW(TFS_EN, tfs); + SIER_SHOW(RFS_EN, rfs); + SIER_SHOW(TLS_EN, tls); + SIER_SHOW(RLS_EN, rls); + SIER_SHOW(RFF1_EN, rff1); + SIER_SHOW(RFF0_EN, rff0); + SIER_SHOW(TFE1_EN, tfe1); + SIER_SHOW(TFE0_EN, tfe0); + + return 0; +} + +static int fsl_ssi_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, fsl_ssi_stats_show, inode->i_private); +} + +static const struct file_operations fsl_ssi_stats_ops = { + .open = fsl_ssi_stats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, struct device *dev) +{ + ssi_dbg->dbg_dir = debugfs_create_dir(dev_name(dev), NULL); + if (!ssi_dbg->dbg_dir) + return -ENOMEM; + + ssi_dbg->dbg_stats = debugfs_create_file("stats", S_IRUGO, + ssi_dbg->dbg_dir, ssi_dbg, &fsl_ssi_stats_ops); + if (!ssi_dbg->dbg_stats) { + debugfs_remove(ssi_dbg->dbg_dir); + return -ENOMEM; + } + + return 0; +} + +void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg) +{ + debugfs_remove(ssi_dbg->dbg_stats); + debugfs_remove(ssi_dbg->dbg_dir); +} diff --git a/sound/soc/fsl/fsl_utils.c b/sound/soc/fsl/fsl_utils.c new file mode 100644 index 000000000..b9e42b503 --- /dev/null +++ b/sound/soc/fsl/fsl_utils.c @@ -0,0 +1,91 @@ +/** + * Freescale ALSA SoC Machine driver utility + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/of_address.h> +#include <sound/soc.h> + +#include "fsl_utils.h" + +/** + * fsl_asoc_get_dma_channel - determine the dma channel for a SSI node + * + * @ssi_np: pointer to the SSI device tree node + * @name: name of the phandle pointing to the dma channel + * @dai: ASoC DAI link pointer to be filled with platform_name + * @dma_channel_id: dma channel id to be returned + * @dma_id: dma id to be returned + * + * This function determines the dma and channel id for given SSI node. It + * also discovers the platform_name for the ASoC DAI link. + */ +int fsl_asoc_get_dma_channel(struct device_node *ssi_np, + const char *name, + struct snd_soc_dai_link *dai, + unsigned int *dma_channel_id, + unsigned int *dma_id) +{ + struct resource res; + struct device_node *dma_channel_np, *dma_np; + const u32 *iprop; + int ret; + + dma_channel_np = of_parse_phandle(ssi_np, name, 0); + if (!dma_channel_np) + return -EINVAL; + + if (!of_device_is_compatible(dma_channel_np, "fsl,ssi-dma-channel")) { + of_node_put(dma_channel_np); + return -EINVAL; + } + + /* Determine the dev_name for the device_node. This code mimics the + * behavior of of_device_make_bus_id(). We need this because ASoC uses + * the dev_name() of the device to match the platform (DMA) device with + * the CPU (SSI) device. It's all ugly and hackish, but it works (for + * now). + * + * dai->platform name should already point to an allocated buffer. + */ + ret = of_address_to_resource(dma_channel_np, 0, &res); + if (ret) { + of_node_put(dma_channel_np); + return ret; + } + snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s", + (unsigned long long) res.start, dma_channel_np->name); + + iprop = of_get_property(dma_channel_np, "cell-index", NULL); + if (!iprop) { + of_node_put(dma_channel_np); + return -EINVAL; + } + *dma_channel_id = be32_to_cpup(iprop); + + dma_np = of_get_parent(dma_channel_np); + iprop = of_get_property(dma_np, "cell-index", NULL); + if (!iprop) { + of_node_put(dma_np); + return -EINVAL; + } + *dma_id = be32_to_cpup(iprop); + + of_node_put(dma_np); + of_node_put(dma_channel_np); + + return 0; +} +EXPORT_SYMBOL(fsl_asoc_get_dma_channel); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale ASoC utility code"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_utils.h b/sound/soc/fsl/fsl_utils.h new file mode 100644 index 000000000..1687b66ef --- /dev/null +++ b/sound/soc/fsl/fsl_utils.h @@ -0,0 +1,25 @@ +/** + * Freescale ALSA SoC Machine driver utility + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _FSL_UTILS_H +#define _FSL_UTILS_H + +#define DAI_NAME_SIZE 32 + +struct snd_soc_dai_link; +struct device_node; + +int fsl_asoc_get_dma_channel(struct device_node *ssi_np, const char *name, + struct snd_soc_dai_link *dai, + unsigned int *dma_channel_id, + unsigned int *dma_id); +#endif /* _FSL_UTILS_H */ diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c new file mode 100644 index 000000000..d9050d946 --- /dev/null +++ b/sound/soc/fsl/imx-audmux.c @@ -0,0 +1,378 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * Copyright 2009 Pengutronix, 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. + */ + +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include "imx-audmux.h" + +#define DRIVER_NAME "imx-audmux" + +static struct clk *audmux_clk; +static void __iomem *audmux_base; + +#define IMX_AUDMUX_V2_PTCR(x) ((x) * 8) +#define IMX_AUDMUX_V2_PDCR(x) ((x) * 8 + 4) + +#ifdef CONFIG_DEBUG_FS +static struct dentry *audmux_debugfs_root; + +/* There is an annoying discontinuity in the SSI numbering with regard + * to the Linux number of the devices */ +static const char *audmux_port_string(int port) +{ + switch (port) { + case MX31_AUDMUX_PORT1_SSI0: + return "imx-ssi.0"; + case MX31_AUDMUX_PORT2_SSI1: + return "imx-ssi.1"; + case MX31_AUDMUX_PORT3_SSI_PINS_3: + return "SSI3"; + case MX31_AUDMUX_PORT4_SSI_PINS_4: + return "SSI4"; + case MX31_AUDMUX_PORT5_SSI_PINS_5: + return "SSI5"; + case MX31_AUDMUX_PORT6_SSI_PINS_6: + return "SSI6"; + default: + return "UNKNOWN"; + } +} + +static ssize_t audmux_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + char *buf; + uintptr_t port = (uintptr_t)file->private_data; + u32 pdcr, ptcr; + + if (audmux_clk) { + ret = clk_prepare_enable(audmux_clk); + if (ret) + return ret; + } + + ptcr = readl(audmux_base + IMX_AUDMUX_V2_PTCR(port)); + pdcr = readl(audmux_base + IMX_AUDMUX_V2_PDCR(port)); + + if (audmux_clk) + clk_disable_unprepare(audmux_clk); + + buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = snprintf(buf, PAGE_SIZE, "PDCR: %08x\nPTCR: %08x\n", + pdcr, ptcr); + + if (ptcr & IMX_AUDMUX_V2_PTCR_TFSDIR) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "TxFS output from %s, ", + audmux_port_string((ptcr >> 27) & 0x7)); + else + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "TxFS input, "); + + if (ptcr & IMX_AUDMUX_V2_PTCR_TCLKDIR) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "TxClk output from %s", + audmux_port_string((ptcr >> 22) & 0x7)); + else + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "TxClk input"); + + ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n"); + + if (ptcr & IMX_AUDMUX_V2_PTCR_SYN) { + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "Port is symmetric"); + } else { + if (ptcr & IMX_AUDMUX_V2_PTCR_RFSDIR) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "RxFS output from %s, ", + audmux_port_string((ptcr >> 17) & 0x7)); + else + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "RxFS input, "); + + if (ptcr & IMX_AUDMUX_V2_PTCR_RCLKDIR) + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "RxClk output from %s", + audmux_port_string((ptcr >> 12) & 0x7)); + else + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "RxClk input"); + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, + "\nData received from %s\n", + audmux_port_string((pdcr >> 13) & 0x7)); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + + kfree(buf); + + return ret; +} + +static const struct file_operations audmux_debugfs_fops = { + .open = simple_open, + .read = audmux_read_file, + .llseek = default_llseek, +}; + +static void audmux_debugfs_init(void) +{ + uintptr_t i; + char buf[20]; + + audmux_debugfs_root = debugfs_create_dir("audmux", NULL); + if (!audmux_debugfs_root) { + pr_warning("Failed to create AUDMUX debugfs root\n"); + return; + } + + for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) { + snprintf(buf, sizeof(buf), "ssi%lu", i); + if (!debugfs_create_file(buf, 0444, audmux_debugfs_root, + (void *)i, &audmux_debugfs_fops)) + pr_warning("Failed to create AUDMUX port %lu debugfs file\n", + i); + } +} + +static void audmux_debugfs_remove(void) +{ + debugfs_remove_recursive(audmux_debugfs_root); +} +#else +static inline void audmux_debugfs_init(void) +{ +} + +static inline void audmux_debugfs_remove(void) +{ +} +#endif + +static enum imx_audmux_type { + IMX21_AUDMUX, + IMX31_AUDMUX, +} audmux_type; + +static struct platform_device_id imx_audmux_ids[] = { + { + .name = "imx21-audmux", + .driver_data = IMX21_AUDMUX, + }, { + .name = "imx31-audmux", + .driver_data = IMX31_AUDMUX, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, imx_audmux_ids); + +static const struct of_device_id imx_audmux_dt_ids[] = { + { .compatible = "fsl,imx21-audmux", .data = &imx_audmux_ids[0], }, + { .compatible = "fsl,imx31-audmux", .data = &imx_audmux_ids[1], }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_audmux_dt_ids); + +static const uint8_t port_mapping[] = { + 0x0, 0x4, 0x8, 0x10, 0x14, 0x1c, +}; + +int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr) +{ + if (audmux_type != IMX21_AUDMUX) + return -EINVAL; + + if (!audmux_base) + return -ENOSYS; + + if (port >= ARRAY_SIZE(port_mapping)) + return -EINVAL; + + writel(pcr, audmux_base + port_mapping[port]); + + return 0; +} +EXPORT_SYMBOL_GPL(imx_audmux_v1_configure_port); + +int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr, + unsigned int pdcr) +{ + int ret; + + if (audmux_type != IMX31_AUDMUX) + return -EINVAL; + + if (!audmux_base) + return -ENOSYS; + + if (audmux_clk) { + ret = clk_prepare_enable(audmux_clk); + if (ret) + return ret; + } + + writel(ptcr, audmux_base + IMX_AUDMUX_V2_PTCR(port)); + writel(pdcr, audmux_base + IMX_AUDMUX_V2_PDCR(port)); + + if (audmux_clk) + clk_disable_unprepare(audmux_clk); + + return 0; +} +EXPORT_SYMBOL_GPL(imx_audmux_v2_configure_port); + +static int imx_audmux_parse_dt_defaults(struct platform_device *pdev, + struct device_node *of_node) +{ + struct device_node *child; + + for_each_available_child_of_node(of_node, child) { + unsigned int port; + unsigned int ptcr = 0; + unsigned int pdcr = 0; + unsigned int pcr = 0; + unsigned int val; + int ret; + int i = 0; + + ret = of_property_read_u32(child, "fsl,audmux-port", &port); + if (ret) { + dev_warn(&pdev->dev, "Failed to get fsl,audmux-port of child node \"%s\"\n", + child->full_name); + continue; + } + if (!of_property_read_bool(child, "fsl,port-config")) { + dev_warn(&pdev->dev, "child node \"%s\" does not have property fsl,port-config\n", + child->full_name); + continue; + } + + for (i = 0; (ret = of_property_read_u32_index(child, + "fsl,port-config", i, &val)) == 0; + ++i) { + if (audmux_type == IMX31_AUDMUX) { + if (i % 2) + pdcr |= val; + else + ptcr |= val; + } else { + pcr |= val; + } + } + + if (ret != -EOVERFLOW) { + dev_err(&pdev->dev, "Failed to read u32 at index %d of child %s\n", + i, child->full_name); + continue; + } + + if (audmux_type == IMX31_AUDMUX) { + if (i % 2) { + dev_err(&pdev->dev, "One pdcr value is missing in child node %s\n", + child->full_name); + continue; + } + imx_audmux_v2_configure_port(port, ptcr, pdcr); + } else { + imx_audmux_v1_configure_port(port, pcr); + } + } + + return 0; +} + +static int imx_audmux_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct of_device_id *of_id = + of_match_device(imx_audmux_dt_ids, &pdev->dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + audmux_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(audmux_base)) + return PTR_ERR(audmux_base); + + audmux_clk = devm_clk_get(&pdev->dev, "audmux"); + if (IS_ERR(audmux_clk)) { + dev_dbg(&pdev->dev, "cannot get clock: %ld\n", + PTR_ERR(audmux_clk)); + audmux_clk = NULL; + } + + if (of_id) + pdev->id_entry = of_id->data; + audmux_type = pdev->id_entry->driver_data; + if (audmux_type == IMX31_AUDMUX) + audmux_debugfs_init(); + + if (of_id) + imx_audmux_parse_dt_defaults(pdev, pdev->dev.of_node); + + return 0; +} + +static int imx_audmux_remove(struct platform_device *pdev) +{ + if (audmux_type == IMX31_AUDMUX) + audmux_debugfs_remove(); + + return 0; +} + +static struct platform_driver imx_audmux_driver = { + .probe = imx_audmux_probe, + .remove = imx_audmux_remove, + .id_table = imx_audmux_ids, + .driver = { + .name = DRIVER_NAME, + .of_match_table = imx_audmux_dt_ids, + } +}; + +static int __init imx_audmux_init(void) +{ + return platform_driver_register(&imx_audmux_driver); +} +subsys_initcall(imx_audmux_init); + +static void __exit imx_audmux_exit(void) +{ + platform_driver_unregister(&imx_audmux_driver); +} +module_exit(imx_audmux_exit); + +MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver"); +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/sound/soc/fsl/imx-audmux.h b/sound/soc/fsl/imx-audmux.h new file mode 100644 index 000000000..38a4209af --- /dev/null +++ b/sound/soc/fsl/imx-audmux.h @@ -0,0 +1,11 @@ +#ifndef __IMX_AUDMUX_H +#define __IMX_AUDMUX_H + +#include <dt-bindings/sound/fsl-imx-audmux.h> + +int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr); + +int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr, + unsigned int pdcr); + +#endif /* __IMX_AUDMUX_H */ diff --git a/sound/soc/fsl/imx-es8328.c b/sound/soc/fsl/imx-es8328.c new file mode 100644 index 000000000..20e7400e2 --- /dev/null +++ b/sound/soc/fsl/imx-es8328.c @@ -0,0 +1,233 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/of_gpio.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "imx-audmux.h" + +#define DAI_NAME_SIZE 32 +#define MUX_PORT_MAX 7 + +struct imx_es8328_data { + struct device *dev; + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + int jack_gpio; +}; + +static struct snd_soc_jack_gpio headset_jack_gpios[] = { + { + .gpio = -1, + .name = "headset-gpio", + .report = SND_JACK_HEADSET, + .invert = 0, + .debounce_time = 200, + }, +}; + +static struct snd_soc_jack headset_jack; + +static int imx_es8328_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct imx_es8328_data *data = container_of(rtd->card, + struct imx_es8328_data, card); + int ret = 0; + + /* Headphone jack detection */ + if (gpio_is_valid(data->jack_gpio)) { + ret = snd_soc_card_jack_new(rtd->card, "Headphone", + SND_JACK_HEADPHONE | SND_JACK_BTN_0, + &headset_jack, NULL, 0); + if (ret) + return ret; + + headset_jack_gpios[0].gpio = data->jack_gpio; + ret = snd_soc_jack_add_gpios(&headset_jack, + ARRAY_SIZE(headset_jack_gpios), + headset_jack_gpios); + } + + return ret; +} + +static const struct snd_soc_dapm_widget imx_es8328_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_REGULATOR_SUPPLY("audio-amp", 1, 0), +}; + +static int imx_es8328_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np = NULL, *codec_np = NULL; + struct platform_device *ssi_pdev; + struct imx_es8328_data *data; + u32 int_port, ext_port; + int ret; + struct device *dev = &pdev->dev; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(dev, "mux-int-port missing or invalid\n"); + goto fail; + } + if (int_port > MUX_PORT_MAX || int_port == 0) { + dev_err(dev, "mux-int-port: hardware only has %d mux ports\n", + MUX_PORT_MAX); + goto fail; + } + + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(dev, "mux-ext-port missing or invalid\n"); + goto fail; + } + if (ext_port > MUX_PORT_MAX || ext_port == 0) { + dev_err(dev, "mux-ext-port: hardware only has %d mux ports\n", + MUX_PORT_MAX); + ret = -EINVAL; + goto fail; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + ret = imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(dev, "audmux internal port setup failed\n"); + return ret; + } + ret = imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(dev, "audmux external port setup failed\n"); + return ret; + } + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!ssi_np || !codec_np) { + dev_err(dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->dev = dev; + + data->jack_gpio = of_get_named_gpio(pdev->dev.of_node, "jack-gpio", 0); + + data->dai.name = "hifi"; + data->dai.stream_name = "hifi"; + data->dai.codec_dai_name = "es8328-hifi-analog"; + data->dai.codec_of_node = codec_np; + data->dai.cpu_of_node = ssi_np; + data->dai.platform_of_node = ssi_np; + data->dai.init = &imx_es8328_dai_init; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + data->card.dev = dev; + data->card.dapm_widgets = imx_es8328_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_es8328_dapm_widgets); + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) { + dev_err(dev, "Unable to parse card name\n"); + goto fail; + } + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) { + dev_err(dev, "Unable to parse routing: %d\n", ret); + goto fail; + } + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + data->card.dai_link = &data->dai; + + ret = snd_soc_register_card(&data->card); + if (ret) { + dev_err(dev, "Unable to register: %d\n", ret); + goto fail; + } + + platform_set_drvdata(pdev, data); +fail: + of_node_put(ssi_np); + of_node_put(codec_np); + + return ret; +} + +static int imx_es8328_remove(struct platform_device *pdev) +{ + struct imx_es8328_data *data = platform_get_drvdata(pdev); + + snd_soc_jack_free_gpios(&headset_jack, ARRAY_SIZE(headset_jack_gpios), + headset_jack_gpios); + + snd_soc_unregister_card(&data->card); + + return 0; +} + +static const struct of_device_id imx_es8328_dt_ids[] = { + { .compatible = "fsl,imx-audio-es8328", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_es8328_dt_ids); + +static struct platform_driver imx_es8328_driver = { + .driver = { + .name = "imx-es8328", + .of_match_table = imx_es8328_dt_ids, + }, + .probe = imx_es8328_probe, + .remove = imx_es8328_remove, +}; +module_platform_driver(imx_es8328_driver); + +MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>"); +MODULE_DESCRIPTION("Kosagi i.MX6 ES8328 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-audio-es8328"); diff --git a/sound/soc/fsl/imx-mc13783.c b/sound/soc/fsl/imx-mc13783.c new file mode 100644 index 000000000..9e6493d4e --- /dev/null +++ b/sound/soc/fsl/imx-mc13783.c @@ -0,0 +1,172 @@ +/* + * imx-mc13783.c -- SoC audio for imx based boards with mc13783 codec + * + * Copyright 2012 Philippe Retornaz, <philippe.retornaz@epfl.ch> + * + * Heavly based on phycore-mc13783: + * 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 <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 "imx-ssi.h" +#include "imx-audmux.h" + +#define FMT_SSI (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBM_CFM) + +static int imx_mc13783_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->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x3, 0x3, 4, 16); + if (ret) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, MC13783_CLK_CLIA, 26000000, 0); + if (ret) + return ret; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 16); + if (ret) + return ret; + + return 0; +} + +static struct snd_soc_ops imx_mc13783_hifi_ops = { + .hw_params = imx_mc13783_hifi_hw_params, +}; + +static struct snd_soc_dai_link imx_mc13783_dai_mc13783[] = { + { + .name = "MC13783", + .stream_name = "Sound", + .codec_dai_name = "mc13783-hifi", + .codec_name = "mc13783-codec", + .cpu_dai_name = "imx-ssi.0", + .platform_name = "imx-ssi.0", + .ops = &imx_mc13783_hifi_ops, + .symmetric_rates = 1, + .dai_fmt = FMT_SSI, + }, +}; + +static const struct snd_soc_dapm_widget imx_mc13783_widget[] = { + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route imx_mc13783_routes[] = { + {"Speaker", NULL, "LSP"}, + {"Headphone", NULL, "HSL"}, + {"Headphone", NULL, "HSR"}, + + {"MC1LIN", NULL, "MC1 Bias"}, + {"MC2IN", NULL, "MC2 Bias"}, + {"MC1 Bias", NULL, "Mic"}, + {"MC2 Bias", NULL, "Mic"}, +}; + +static struct snd_soc_card imx_mc13783 = { + .name = "imx_mc13783", + .owner = THIS_MODULE, + .dai_link = imx_mc13783_dai_mc13783, + .num_links = ARRAY_SIZE(imx_mc13783_dai_mc13783), + .dapm_widgets = imx_mc13783_widget, + .num_dapm_widgets = ARRAY_SIZE(imx_mc13783_widget), + .dapm_routes = imx_mc13783_routes, + .num_dapm_routes = ARRAY_SIZE(imx_mc13783_routes), +}; + +static int imx_mc13783_probe(struct platform_device *pdev) +{ + int ret; + + imx_mc13783.dev = &pdev->dev; + + ret = snd_soc_register_card(&imx_mc13783); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + if (machine_is_mx31_3ds() || machine_is_mx31moboard()) { + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT4_SSI_PINS_4, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0) | + IMX_AUDMUX_V2_PDCR_MODE(1) | + IMX_AUDMUX_V2_PDCR_INMMASK(0xfc)); + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + IMX_AUDMUX_V2_PTCR_RFSDIR | + IMX_AUDMUX_V2_PTCR_RFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + IMX_AUDMUX_V2_PTCR_RCLKDIR | + IMX_AUDMUX_V2_PTCR_RCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4), + IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT4_SSI_PINS_4)); + } else if (machine_is_mx27_3ds()) { + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_TFSDIR | + IMX_AUDMUX_V1_PCR_TCLKDIR | + IMX_AUDMUX_V1_PCR_RFSDIR | + IMX_AUDMUX_V1_PCR_RCLKDIR | + IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) | + IMX_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) + ); + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR3_SSI_PINS_4, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0) + ); + } + + return ret; +} + +static int imx_mc13783_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&imx_mc13783); + + return 0; +} + +static struct platform_driver imx_mc13783_audio_driver = { + .driver = { + .name = "imx_mc13783", + }, + .probe = imx_mc13783_probe, + .remove = imx_mc13783_remove +}; + +module_platform_driver(imx_mc13783_audio_driver); + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch"); +MODULE_DESCRIPTION("imx with mc13783 codec ALSA SoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx_mc13783"); diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c new file mode 100644 index 000000000..0db94f492 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-dma.c @@ -0,0 +1,66 @@ +/* + * imx-pcm-dma-mx2.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * 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/platform_device.h> +#include <linux/dmaengine.h> +#include <linux/types.h> +#include <linux/module.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "imx-pcm.h" + +static bool filter(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +static const struct snd_pcm_hardware imx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65535, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static const struct snd_dmaengine_pcm_config imx_dmaengine_pcm_config = { + .pcm_hardware = &imx_pcm_hardware, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .compat_filter_fn = filter, + .prealloc_buffer_size = IMX_SSI_DMABUF_SIZE, +}; + +int imx_pcm_dma_init(struct platform_device *pdev) +{ + return devm_snd_dmaengine_pcm_register(&pdev->dev, + &imx_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); +} +EXPORT_SYMBOL_GPL(imx_pcm_dma_init); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm-fiq.c b/sound/soc/fsl/imx-pcm-fiq.c new file mode 100644 index 000000000..7abf6a079 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-fiq.c @@ -0,0 +1,393 @@ +/* + * imx-pcm-fiq.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * 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/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 <linux/slab.h> + +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/fiq.h> + +#include <linux/platform_data/asoc-imx-ssi.h> + +#include "imx-ssi.h" +#include "imx-pcm.h" + +struct imx_pcm_runtime_data { + unsigned int period; + int periods; + unsigned long offset; + struct hrtimer hrt; + int poll_time_ns; + struct snd_pcm_substream *substream; + atomic_t playing; + atomic_t capturing; +}; + +static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt) +{ + struct imx_pcm_runtime_data *iprtd = + container_of(hrt, struct imx_pcm_runtime_data, hrt); + struct snd_pcm_substream *substream = iprtd->substream; + struct pt_regs regs; + + if (!atomic_read(&iprtd->playing) && !atomic_read(&iprtd->capturing)) + return HRTIMER_NORESTART; + + get_fiq_regs(®s); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + iprtd->offset = regs.ARM_r8 & 0xffff; + else + iprtd->offset = regs.ARM_r9 & 0xffff; + + snd_pcm_period_elapsed(substream); + + hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns)); + + return HRTIMER_RESTART; +} + +static struct fiq_handler fh = { + .name = DRV_NAME, +}; + +static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + iprtd->periods = params_periods(params); + iprtd->period = params_period_bytes(params); + iprtd->offset = 0; + iprtd->poll_time_ns = 1000000000 / params_rate(params) * + params_period_size(params); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + struct pt_regs regs; + + get_fiq_regs(®s); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16; + else + regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16; + + set_fiq_regs(®s); + + return 0; +} + +static int imx_pcm_fiq; + +static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + atomic_set(&iprtd->playing, 1); + else + atomic_set(&iprtd->capturing, 1); + hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns), + HRTIMER_MODE_REL); + enable_fiq(imx_pcm_fiq); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + atomic_set(&iprtd->playing, 0); + else + atomic_set(&iprtd->capturing, 0); + if (!atomic_read(&iprtd->playing) && + !atomic_read(&iprtd->capturing)) + disable_fiq(imx_pcm_fiq); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + return bytes_to_frames(substream->runtime, iprtd->offset); +} + +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, + .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 16 * 1024, + .periods_min = 4, + .periods_max = 255, + .fifo_size = 0, +}; + +static int snd_imx_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd; + int ret; + + iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); + if (iprtd == NULL) + return -ENOMEM; + runtime->private_data = iprtd; + + iprtd->substream = substream; + + atomic_set(&iprtd->playing, 0); + atomic_set(&iprtd->capturing, 0); + hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + iprtd->hrt.function = snd_hrtimer_callback; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + kfree(iprtd); + return ret; + } + + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + return 0; +} + +static int snd_imx_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + hrtimer_cancel(&iprtd->hrt); + + kfree(iprtd); + + return 0; +} + +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_writecombine(substream->pcm->card->dev, 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 struct snd_pcm_ops imx_pcm_ops = { + .open = snd_imx_open, + .close = snd_imx_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_imx_pcm_hw_params, + .prepare = snd_imx_pcm_prepare, + .trigger = snd_imx_pcm_trigger, + .pointer = snd_imx_pcm_pointer, + .mmap = snd_imx_pcm_mmap, +}; + +static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + 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; + + return 0; +} + +static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + + return 0; +} + +static int ssi_irq = 0; + +static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct snd_pcm_substream *substream; + int ret; + + ret = imx_pcm_new(rtd); + if (ret) + return ret; + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (substream) { + struct snd_dma_buffer *buf = &substream->dma_buffer; + + imx_ssi_fiq_tx_buffer = (unsigned long)buf->area; + } + + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) { + struct snd_dma_buffer *buf = &substream->dma_buffer; + + imx_ssi_fiq_rx_buffer = (unsigned long)buf->area; + } + + set_fiq_handler(&imx_ssi_fiq_start, + &imx_ssi_fiq_end - &imx_ssi_fiq_start); + + return 0; +} + +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 void imx_pcm_fiq_free(struct snd_pcm *pcm) +{ + mxc_set_irq_fiq(ssi_irq, 0); + release_fiq(&fh); + imx_pcm_free(pcm); +} + +static struct snd_soc_platform_driver imx_soc_platform_fiq = { + .ops = &imx_pcm_ops, + .pcm_new = imx_pcm_fiq_new, + .pcm_free = imx_pcm_fiq_free, +}; + +int imx_pcm_fiq_init(struct platform_device *pdev, + struct imx_pcm_fiq_params *params) +{ + int ret; + + ret = claim_fiq(&fh); + if (ret) { + dev_err(&pdev->dev, "failed to claim fiq: %d", ret); + return ret; + } + + mxc_set_irq_fiq(params->irq, 1); + ssi_irq = params->irq; + + imx_pcm_fiq = params->irq; + + imx_ssi_fiq_base = (unsigned long)params->base; + + params->dma_params_tx->maxburst = 4; + params->dma_params_rx->maxburst = 6; + + ret = snd_soc_register_platform(&pdev->dev, &imx_soc_platform_fiq); + if (ret) + goto failed_register; + + return 0; + +failed_register: + mxc_set_irq_fiq(ssi_irq, 0); + release_fiq(&fh); + + return ret; +} +EXPORT_SYMBOL_GPL(imx_pcm_fiq_init); + +void imx_pcm_fiq_exit(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); +} +EXPORT_SYMBOL_GPL(imx_pcm_fiq_exit); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h new file mode 100644 index 000000000..c79cb2747 --- /dev/null +++ b/sound/soc/fsl/imx-pcm.h @@ -0,0 +1,66 @@ +/* + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _IMX_PCM_H +#define _IMX_PCM_H + +#include <linux/platform_data/dma-imx.h> + +/* + * Do not change this as the FIQ handler depends on this size + */ +#define IMX_SSI_DMABUF_SIZE (64 * 1024) + +static inline void +imx_pcm_dma_params_init_data(struct imx_dma_data *dma_data, + int dma, enum sdma_peripheral_type peripheral_type) +{ + dma_data->dma_request = dma; + dma_data->priority = DMA_PRIO_HIGH; + dma_data->peripheral_type = peripheral_type; +} + +struct imx_pcm_fiq_params { + int irq; + void __iomem *base; + + /* Pointer to original ssi driver to setup tx rx sizes */ + struct snd_dmaengine_dai_dma_data *dma_params_rx; + struct snd_dmaengine_dai_dma_data *dma_params_tx; +}; + +#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA) +int imx_pcm_dma_init(struct platform_device *pdev); +#else +static inline int imx_pcm_dma_init(struct platform_device *pdev) +{ + return -ENODEV; +} +#endif + +#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ) +int imx_pcm_fiq_init(struct platform_device *pdev, + struct imx_pcm_fiq_params *params); +void imx_pcm_fiq_exit(struct platform_device *pdev); +#else +static inline int imx_pcm_fiq_init(struct platform_device *pdev, + struct imx_pcm_fiq_params *params) +{ + return -ENODEV; +} + +static inline void imx_pcm_fiq_exit(struct platform_device *pdev) +{ +} +#endif + +#endif /* _IMX_PCM_H */ diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c new file mode 100644 index 000000000..b99e0b5e0 --- /dev/null +++ b/sound/soc/fsl/imx-sgtl5000.c @@ -0,0 +1,214 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <sound/soc.h> + +#include "../codecs/sgtl5000.h" +#include "imx-audmux.h" + +#define DAI_NAME_SIZE 32 + +struct imx_sgtl5000_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *codec_clk; + unsigned int clk_frequency; +}; + +static int imx_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(rtd->card); + struct device *dev = rtd->card->dev; + int ret; + + ret = snd_soc_dai_set_sysclk(rtd->codec_dai, SGTL5000_SYSCLK, + data->clk_frequency, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + +static const struct snd_soc_dapm_widget imx_sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Line Out Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static int imx_sgtl5000_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np, *codec_np; + struct platform_device *ssi_pdev; + struct i2c_client *codec_dev; + struct imx_sgtl5000_data *data = NULL; + int int_port, ext_port; + int ret; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); + return ret; + } + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); + return ret; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + ret = imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(&pdev->dev, "audmux internal port setup failed\n"); + return ret; + } + ret = imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(&pdev->dev, "audmux external port setup failed\n"); + return ret; + } + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!ssi_np || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EPROBE_DEFER; + goto fail; + } + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + return -EPROBE_DEFER; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->codec_clk = clk_get(&codec_dev->dev, NULL); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + goto fail; + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + + data->dai.name = "HiFi"; + data->dai.stream_name = "HiFi"; + data->dai.codec_dai_name = "sgtl5000"; + data->dai.codec_of_node = codec_np; + data->dai.cpu_of_node = ssi_np; + data->dai.platform_of_node = ssi_np; + data->dai.init = &imx_sgtl5000_dai_init; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto fail; + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + data->card.dai_link = &data->dai; + data->card.dapm_widgets = imx_sgtl5000_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_sgtl5000_dapm_widgets); + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + of_node_put(ssi_np); + of_node_put(codec_np); + + return 0; + +fail: + if (data && !IS_ERR(data->codec_clk)) + clk_put(data->codec_clk); + of_node_put(ssi_np); + of_node_put(codec_np); + + return ret; +} + +static int imx_sgtl5000_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card); + + clk_put(data->codec_clk); + + return 0; +} + +static const struct of_device_id imx_sgtl5000_dt_ids[] = { + { .compatible = "fsl,imx-audio-sgtl5000", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_sgtl5000_dt_ids); + +static struct platform_driver imx_sgtl5000_driver = { + .driver = { + .name = "imx-sgtl5000", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_sgtl5000_dt_ids, + }, + .probe = imx_sgtl5000_probe, + .remove = imx_sgtl5000_remove, +}; +module_platform_driver(imx_sgtl5000_driver); + +MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>"); +MODULE_DESCRIPTION("Freescale i.MX SGTL5000 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-sgtl5000"); diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c new file mode 100644 index 000000000..33da26a12 --- /dev/null +++ b/sound/soc/fsl/imx-spdif.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <sound/soc.h> + +struct imx_spdif_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; +}; + +static int imx_spdif_audio_probe(struct platform_device *pdev) +{ + struct device_node *spdif_np, *np = pdev->dev.of_node; + struct imx_spdif_data *data; + int ret = 0; + + spdif_np = of_parse_phandle(np, "spdif-controller", 0); + if (!spdif_np) { + dev_err(&pdev->dev, "failed to find spdif-controller\n"); + ret = -EINVAL; + goto end; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto end; + } + + data->dai.name = "S/PDIF PCM"; + data->dai.stream_name = "S/PDIF PCM"; + data->dai.codec_dai_name = "snd-soc-dummy-dai"; + data->dai.codec_name = "snd-soc-dummy"; + data->dai.cpu_of_node = spdif_np; + data->dai.platform_of_node = spdif_np; + data->dai.playback_only = true; + data->dai.capture_only = true; + + if (of_property_read_bool(np, "spdif-out")) + data->dai.capture_only = false; + + if (of_property_read_bool(np, "spdif-in")) + data->dai.playback_only = false; + + if (data->dai.playback_only && data->dai.capture_only) { + dev_err(&pdev->dev, "no enabled S/PDIF DAI link\n"); + goto end; + } + + data->card.dev = &pdev->dev; + data->card.dai_link = &data->dai; + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto end; + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed: %d\n", ret); + goto end; + } + + platform_set_drvdata(pdev, data); + +end: + of_node_put(spdif_np); + + return ret; +} + +static const struct of_device_id imx_spdif_dt_ids[] = { + { .compatible = "fsl,imx-audio-spdif", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_spdif_dt_ids); + +static struct platform_driver imx_spdif_driver = { + .driver = { + .name = "imx-spdif", + .of_match_table = imx_spdif_dt_ids, + }, + .probe = imx_spdif_audio_probe, +}; + +module_platform_driver(imx_spdif_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX S/PDIF machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-spdif"); diff --git a/sound/soc/fsl/imx-ssi.c b/sound/soc/fsl/imx-ssi.c new file mode 100644 index 000000000..461ce27b8 --- /dev/null +++ b/sound/soc/fsl/imx-ssi.c @@ -0,0 +1,658 @@ +/* + * imx-ssi.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * 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 developed 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 challenge. 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 <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <linux/platform_data/asoc-imx-ssi.h> + +#include "imx-ssi.h" +#include "fsl_utils.h" + +#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV) + +/* + * 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 = snd_soc_dai_get_drvdata(cpu_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). + */ +static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_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; + if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) { + scr &= ~SSI_I2S_MODE_MASK; + scr |= SSI_SCR_I2S_MODE_SLAVE; + } + 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 | SSI_STCR_TXBIT0; + break; + case SND_SOC_DAIFMT_DSP_A: + /* data on rising edge of bclk, frame high 1clk before data */ + strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0 | 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_CBM_CFM: + break; + default: + /* Master mode not implemented, needs handling of clocks. */ + return -EINVAL; + } + + strcr |= SSI_STCR_TFEN0; + + if (ssi->flags & IMX_SSI_NET) + scr |= SSI_SCR_NET; + if (ssi->flags & IMX_SSI_SYN) + scr |= SSI_SCR_SYN; + + 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 = snd_soc_dai_get_drvdata(cpu_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 = snd_soc_dai_get_drvdata(cpu_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 = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg, sccr; + + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = SSI_STCCR; + else + reg = SSI_SRCCR; + + if (ssi->flags & IMX_SSI_SYN) + reg = SSI_STCCR; + + 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 int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai); + unsigned int sier_bits, sier; + unsigned int scr; + + scr = readl(ssi->base + SSI_SCR); + sier = readl(ssi->base + SSI_SIER); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (ssi->flags & IMX_SSI_DMA) + sier_bits = SSI_SIER_TDMAE; + else + sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN; + } else { + if (ssi->flags & IMX_SSI_DMA) + sier_bits = SSI_SIER_RDMAE; + else + sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + scr |= SSI_SCR_TE; + else + scr |= SSI_SCR_RE; + sier |= sier_bits; + + scr |= SSI_SCR_SSIEN; + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + scr &= ~SSI_SCR_TE; + else + scr &= ~SSI_SCR_RE; + sier &= ~sier_bits; + + if (!(scr & (SSI_SCR_TE | SSI_SCR_RE))) + scr &= ~SSI_SCR_SSIEN; + + break; + default: + return -EINVAL; + } + + if (!(ssi->flags & IMX_SSI_USE_AC97)) + /* rx/tx are always enabled to access ac97 registers */ + writel(scr, ssi->base + SSI_SCR); + + writel(sier, ssi->base + SSI_SIER); + + return 0; +} + +static const 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, + .trigger = imx_ssi_trigger, +}; + +static int imx_ssi_dai_probe(struct snd_soc_dai *dai) +{ + struct imx_ssi *ssi = dev_get_drvdata(dai->dev); + uint32_t val; + + snd_soc_dai_set_drvdata(dai, ssi); + + val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.maxburst) | + SSI_SFCSR_RFWM0(ssi->dma_params_rx.maxburst); + writel(val, ssi->base + SSI_SFCSR); + + /* Tx/Rx config */ + dai->playback_dma_data = &ssi->dma_params_tx; + dai->capture_dma_data = &ssi->dma_params_rx; + + return 0; +} + +static struct snd_soc_dai_driver imx_ssi_dai = { + .probe = imx_ssi_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &imx_ssi_pcm_dai_ops, +}; + +static struct snd_soc_dai_driver imx_ac97_dai = { + .probe = imx_ssi_dai_probe, + .bus_control = true, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &imx_ssi_pcm_dai_ops, +}; + +static const struct snd_soc_component_driver imx_component = { + .name = DRV_NAME, +}; + +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_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 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); + /* First read sometimes fails, do a dummy read */ + imx_ssi_ac97_read(ac97, 0); +} + +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); + + /* First read sometimes fails, do a dummy read */ + imx_ssi_ac97_read(ac97, 0); +} + +static struct snd_ac97_bus_ops imx_ssi_ac97_ops = { + .read = imx_ssi_ac97_read, + .write = imx_ssi_ac97_write, + .reset = imx_ssi_ac97_reset, + .warm_reset = imx_ssi_ac97_warm_reset +}; + +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; + struct snd_soc_dai_driver *dai; + + ssi = devm_kzalloc(&pdev->dev, sizeof(*ssi), GFP_KERNEL); + if (!ssi) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, ssi); + + if (pdata) { + ssi->ac97_reset = pdata->ac97_reset; + ssi->ac97_warm_reset = pdata->ac97_warm_reset; + ssi->flags = pdata->flags; + } + + ssi->irq = platform_get_irq(pdev, 0); + + ssi->clk = devm_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; + } + ret = clk_prepare_enable(ssi->clk); + if (ret) + goto failed_clk; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ssi->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ssi->base)) { + ret = PTR_ERR(ssi->base); + goto failed_register; + } + + if (ssi->flags & IMX_SSI_USE_AC97) { + if (ac97_ssi) { + dev_err(&pdev->dev, "AC'97 SSI already registered\n"); + ret = -EBUSY; + goto failed_register; + } + ac97_ssi = ssi; + setup_channel_to_ac97(ssi); + dai = &imx_ac97_dai; + } else + dai = &imx_ssi_dai; + + writel(0x0, ssi->base + SSI_SIER); + + ssi->dma_params_rx.addr = res->start + SSI_SRX0; + ssi->dma_params_tx.addr = res->start + SSI_STX0; + + ssi->dma_params_tx.maxburst = 6; + ssi->dma_params_rx.maxburst = 4; + + ssi->dma_params_tx.filter_data = &ssi->filter_data_tx; + ssi->dma_params_rx.filter_data = &ssi->filter_data_rx; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0"); + if (res) { + imx_pcm_dma_params_init_data(&ssi->filter_data_tx, res->start, + IMX_DMATYPE_SSI); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0"); + if (res) { + imx_pcm_dma_params_init_data(&ssi->filter_data_rx, res->start, + IMX_DMATYPE_SSI); + } + + platform_set_drvdata(pdev, ssi); + + ret = snd_soc_set_ac97_ops(&imx_ssi_ac97_ops); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret); + goto failed_register; + } + + ret = snd_soc_register_component(&pdev->dev, &imx_component, + dai, 1); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + goto failed_register; + } + + ssi->fiq_params.irq = ssi->irq; + ssi->fiq_params.base = ssi->base; + ssi->fiq_params.dma_params_rx = &ssi->dma_params_rx; + ssi->fiq_params.dma_params_tx = &ssi->dma_params_tx; + + ssi->fiq_init = imx_pcm_fiq_init(pdev, &ssi->fiq_params); + ssi->dma_init = imx_pcm_dma_init(pdev); + + if (ssi->fiq_init && ssi->dma_init) { + ret = ssi->fiq_init; + goto failed_pcm; + } + + return 0; + +failed_pcm: + snd_soc_unregister_component(&pdev->dev); +failed_register: + clk_disable_unprepare(ssi->clk); +failed_clk: + snd_soc_set_ac97_ops(NULL); + + return ret; +} + +static int imx_ssi_remove(struct platform_device *pdev) +{ + struct imx_ssi *ssi = platform_get_drvdata(pdev); + + if (!ssi->fiq_init) + imx_pcm_fiq_exit(pdev); + + snd_soc_unregister_component(&pdev->dev); + + if (ssi->flags & IMX_SSI_USE_AC97) + ac97_ssi = NULL; + + clk_disable_unprepare(ssi->clk); + snd_soc_set_ac97_ops(NULL); + + return 0; +} + +static struct platform_driver imx_ssi_driver = { + .probe = imx_ssi_probe, + .remove = imx_ssi_remove, + + .driver = { + .name = "imx-ssi", + }, +}; + +module_platform_driver(imx_ssi_driver); + +/* Module information */ +MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>"); +MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx-ssi"); diff --git a/sound/soc/fsl/imx-ssi.h b/sound/soc/fsl/imx-ssi.h new file mode 100644 index 000000000..be6562365 --- /dev/null +++ b/sound/soc/fsl/imx-ssi.h @@ -0,0 +1,218 @@ +/* + * 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 + +#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 + +/* 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 + +#define DRV_NAME "imx-ssi" + +#include <linux/dmaengine.h> +#include <linux/platform_data/dma-imx.h> +#include <sound/dmaengine_pcm.h> +#include "imx-pcm.h" + +struct imx_ssi { + struct platform_device *ac97_dev; + + struct snd_soc_dai *imx_ac97; + struct clk *clk; + void __iomem *base; + int irq; + int fiq_enable; + unsigned int offset; + + unsigned int flags; + + void (*ac97_reset) (struct snd_ac97 *ac97); + void (*ac97_warm_reset)(struct snd_ac97 *ac97); + + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct imx_dma_data filter_data_tx; + struct imx_dma_data filter_data_rx; + struct imx_pcm_fiq_params fiq_params; + + int fiq_init; + int dma_init; +}; + +#endif /* _IMX_SSI_H */ diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c new file mode 100644 index 000000000..b38b98cae --- /dev/null +++ b/sound/soc/fsl/imx-wm8962.c @@ -0,0 +1,322 @@ +/* + * Copyright 2013 Freescale Semiconductor, Inc. + * + * Based on imx-sgtl5000.c + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <linux/pinctrl/consumer.h> + +#include "../codecs/wm8962.h" +#include "imx-audmux.h" + +#define DAI_NAME_SIZE 32 + +struct imx_wm8962_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *codec_clk; + unsigned int clk_frequency; +}; + +struct imx_priv { + struct platform_device *pdev; +}; +static struct imx_priv card_priv; + +static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_MIC("DMIC", NULL), +}; + +static int sample_rate = 44100; +static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE; + +static int imx_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + sample_rate = params_rate(params); + sample_format = params_format(params); + + return 0; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, +}; + +static int imx_wm8962_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + struct imx_priv *priv = &card_priv; + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + struct device *dev = &priv->pdev->dev; + unsigned int pll_out; + int ret; + + if (dapm->dev != codec_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { + if (sample_format == SNDRV_PCM_FORMAT_S24_LE) + pll_out = sample_rate * 384; + else + pll_out = sample_rate * 256; + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + WM8962_FLL_MCLK, data->clk_frequency, + pll_out); + if (ret < 0) { + dev_err(dev, "failed to start FLL: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8962_SYSCLK_FLL, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "failed to set SYSCLK: %d\n", ret); + return ret; + } + } + break; + + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level == SND_SOC_BIAS_PREPARE) { + ret = snd_soc_dai_set_sysclk(codec_dai, + WM8962_SYSCLK_MCLK, data->clk_frequency, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, + "failed to switch away from FLL: %d\n", + ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, + 0, 0, 0); + if (ret < 0) { + dev_err(dev, "failed to stop FLL: %d\n", ret); + return ret; + } + } + break; + + default: + break; + } + + return 0; +} + +static int imx_wm8962_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + struct imx_priv *priv = &card_priv; + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + struct device *dev = &priv->pdev->dev; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, + data->clk_frequency, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(dev, "failed to set sysclk in %s\n", __func__); + + return ret; +} + +static int imx_wm8962_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *ssi_np, *codec_np; + struct platform_device *ssi_pdev; + struct imx_priv *priv = &card_priv; + struct i2c_client *codec_dev; + struct imx_wm8962_data *data; + int int_port, ext_port; + int ret; + + priv->pdev = pdev; + + ret = of_property_read_u32(np, "mux-int-port", &int_port); + if (ret) { + dev_err(&pdev->dev, "mux-int-port missing or invalid\n"); + return ret; + } + ret = of_property_read_u32(np, "mux-ext-port", &ext_port); + if (ret) { + dev_err(&pdev->dev, "mux-ext-port missing or invalid\n"); + return ret; + } + + /* + * The port numbering in the hardware manual starts at 1, while + * the audmux API expects it starts at 0. + */ + int_port--; + ext_port--; + ret = imx_audmux_v2_configure_port(int_port, + IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TCLKDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); + if (ret) { + dev_err(&pdev->dev, "audmux internal port setup failed\n"); + return ret; + } + ret = imx_audmux_v2_configure_port(ext_port, + IMX_AUDMUX_V2_PTCR_SYN, + IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); + if (ret) { + dev_err(&pdev->dev, "audmux external port setup failed\n"); + return ret; + } + + ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!ssi_np || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + ssi_pdev = of_find_device_by_node(ssi_np); + if (!ssi_pdev) { + dev_err(&pdev->dev, "failed to find SSI platform device\n"); + ret = -EINVAL; + goto fail; + } + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + data->codec_clk = devm_clk_get(&codec_dev->dev, NULL); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + ret = clk_prepare_enable(data->codec_clk); + if (ret) { + dev_err(&codec_dev->dev, "failed to enable codec clk: %d\n", ret); + goto fail; + } + + data->dai.name = "HiFi"; + data->dai.stream_name = "HiFi"; + data->dai.codec_dai_name = "wm8962"; + data->dai.codec_of_node = codec_np; + data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev); + data->dai.platform_of_node = ssi_np; + data->dai.ops = &imx_hifi_ops; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto clk_fail; + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto clk_fail; + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + data->card.dai_link = &data->dai; + data->card.dapm_widgets = imx_wm8962_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8962_dapm_widgets); + + data->card.late_probe = imx_wm8962_late_probe; + data->card.set_bias_level = imx_wm8962_set_bias_level; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto clk_fail; + } + + of_node_put(ssi_np); + of_node_put(codec_np); + + return 0; + +clk_fail: + clk_disable_unprepare(data->codec_clk); +fail: + of_node_put(ssi_np); + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8962_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card); + + if (!IS_ERR(data->codec_clk)) + clk_disable_unprepare(data->codec_clk); + + return 0; +} + +static const struct of_device_id imx_wm8962_dt_ids[] = { + { .compatible = "fsl,imx-audio-wm8962", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8962_dt_ids); + +static struct platform_driver imx_wm8962_driver = { + .driver = { + .name = "imx-wm8962", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8962_dt_ids, + }, + .probe = imx_wm8962_probe, + .remove = imx_wm8962_remove, +}; +module_platform_driver(imx_wm8962_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8962 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8962"); diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c new file mode 100644 index 000000000..0b82e209b --- /dev/null +++ b/sound/soc/fsl/mpc5200_dma.c @@ -0,0 +1,511 @@ +/* + * Freescale MPC5200 PSC DMA + * ALSA SoC Platform driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + * Copyright (C) 2009 Jon Smirl, Digispeaker + */ + +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> + +#include <sound/soc.h> + +#include <linux/fsl/bestcomm/bestcomm.h> +#include <linux/fsl/bestcomm/gen_bd.h> +#include <asm/mpc52xx_psc.h> + +#include "mpc5200_dma.h" + +/* + * Interrupt handlers + */ +static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma) +{ + struct psc_dma *psc_dma = _psc_dma; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 isr; + + isr = in_be16(®s->mpc52xx_psc_isr); + + /* Playback underrun error */ + if (psc_dma->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) + psc_dma->stats.underrun_count++; + + /* Capture overrun error */ + if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) + psc_dma->stats.overrun_count++; + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + + return IRQ_HANDLED; +} + +/** + * psc_dma_bcom_enqueue_next_buffer - Enqueue another audio buffer + * @s: pointer to stream private data structure + * + * Enqueues another audio period buffer into the bestcomm queue. + * + * Note: The routine must only be called when there is space available in + * the queue. Otherwise the enqueue will fail and the audio ring buffer + * will get out of sync + */ +static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s) +{ + struct bcom_bd *bd; + + /* Prepare and enqueue the next buffer descriptor */ + bd = bcom_prepare_next_buffer(s->bcom_task); + bd->status = s->period_bytes; + bd->data[0] = s->runtime->dma_addr + (s->period_next * s->period_bytes); + bcom_submit_next_buffer(s->bcom_task, NULL); + + /* Update for next period */ + s->period_next = (s->period_next + 1) % s->runtime->periods; +} + +/* Bestcomm DMA irq handler */ +static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) +{ + struct psc_dma_stream *s = _psc_dma_stream; + + spin_lock(&s->psc_dma->lock); + /* For each finished period, dequeue the completed period buffer + * and enqueue a new one in it's place. */ + while (bcom_buffer_done(s->bcom_task)) { + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + + s->period_current = (s->period_current+1) % s->runtime->periods; + s->period_count++; + + psc_dma_bcom_enqueue_next_buffer(s); + } + spin_unlock(&s->psc_dma->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + return IRQ_HANDLED; +} + +static int psc_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +/** + * psc_dma_trigger: start and stop the DMA transfer. + * + * This function is called by ALSA to start, stop, pause, and resume the DMA + * transfer of data. + */ +static int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct snd_pcm_runtime *runtime = substream->runtime; + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + u16 imr; + unsigned long flags; + int i; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev_dbg(psc_dma->dev, "START: stream=%i fbits=%u ps=%u #p=%u\n", + substream->pstr->stream, runtime->frame_bits, + (int)runtime->period_size, runtime->periods); + s->period_bytes = frames_to_bytes(runtime, + runtime->period_size); + s->period_next = 0; + s->period_current = 0; + s->active = 1; + s->period_count = 0; + s->runtime = runtime; + + /* Fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. + */ + spin_lock_irqsave(&psc_dma->lock, flags); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + + for (i = 0; i < runtime->periods; i++) + if (!bcom_queue_full(s->bcom_task)) + psc_dma_bcom_enqueue_next_buffer(s); + + bcom_enable(s->bcom_task); + spin_unlock_irqrestore(&psc_dma->lock, flags); + + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(psc_dma->dev, "STOP: stream=%i periods_count=%i\n", + substream->pstr->stream, s->period_count); + s->active = 0; + + spin_lock_irqsave(&psc_dma->lock, flags); + bcom_disable(s->bcom_task); + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + spin_unlock_irqrestore(&psc_dma->lock, flags); + + break; + + default: + dev_dbg(psc_dma->dev, "unhandled trigger: stream=%i cmd=%i\n", + substream->pstr->stream, cmd); + return -EINVAL; + } + + /* Update interrupt enable settings */ + imr = 0; + if (psc_dma->playback.active) + imr |= MPC52xx_PSC_IMR_TXEMP; + if (psc_dma->capture.active) + imr |= MPC52xx_PSC_IMR_ORERR; + out_be16(®s->isr_imr.imr, psc_dma->imr | imr); + + return 0; +} + + +/* --------------------------------------------------------------------- + * The PSC DMA 'ASoC platform' driver + * + * Can be referenced by an 'ASoC machine' driver + * This driver only deals with the audio bus; it doesn't have any + * interaction with the attached codec + */ + +static const struct snd_pcm_hardware psc_dma_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_BATCH, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, + .period_bytes_max = 1024 * 1024, + .period_bytes_min = 32, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 2 * 1024 * 1024, + .fifo_size = 512, +}; + +static int psc_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct psc_dma_stream *s; + int rc; + + dev_dbg(psc_dma->dev, "psc_dma_open(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + snd_soc_set_runtime_hwparams(substream, &psc_dma_hardware); + + rc = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (rc < 0) { + dev_err(substream->pcm->card->dev, "invalid buffer size\n"); + return rc; + } + + s->stream = substream; + return 0; +} + +static int psc_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct psc_dma_stream *s; + + dev_dbg(psc_dma->dev, "psc_dma_close(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + if (!psc_dma->playback.active && + !psc_dma->capture.active) { + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */ + } + s->stream = NULL; + return 0; +} + +static snd_pcm_uframes_t +psc_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct psc_dma_stream *s; + dma_addr_t count; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_dma->capture; + else + s = &psc_dma->playback; + + count = s->period_current * s->period_bytes; + + return bytes_to_frames(substream->runtime, count); +} + +static int +psc_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static struct snd_pcm_ops psc_dma_ops = { + .open = psc_dma_open, + .close = psc_dma_close, + .hw_free = psc_dma_hw_free, + .ioctl = snd_pcm_lib_ioctl, + .pointer = psc_dma_pointer, + .trigger = psc_dma_trigger, + .hw_params = psc_dma_hw_params, +}; + +static int psc_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_soc_dai *dai = rtd->cpu_dai; + struct snd_pcm *pcm = rtd->pcm; + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + size_t size = psc_dma_hardware.buffer_bytes_max; + int rc; + + dev_dbg(rtd->platform->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n", + card, dai, pcm); + + rc = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (rc) + return rc; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + size, &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer); + if (rc) + goto playback_alloc_err; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + size, &pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer); + if (rc) + goto capture_alloc_err; + } + + return 0; + + capture_alloc_err: + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) + snd_dma_free_pages(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer); + + playback_alloc_err: + dev_err(card->dev, "Cannot allocate buffer(s)\n"); + + return -ENOMEM; +} + +static void psc_dma_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_pcm_substream *substream; + int stream; + + dev_dbg(rtd->platform->dev, "psc_dma_free(pcm=%p)\n", pcm); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +static struct snd_soc_platform_driver mpc5200_audio_dma_platform = { + .ops = &psc_dma_ops, + .pcm_new = &psc_dma_new, + .pcm_free = &psc_dma_free, +}; + +int mpc5200_audio_dma_create(struct platform_device *op) +{ + phys_addr_t fifo; + struct psc_dma *psc_dma; + struct resource res; + int size, irq, rc; + const __be32 *prop; + void __iomem *regs; + int ret; + + /* Fetch the registers and IRQ of the PSC */ + irq = irq_of_parse_and_map(op->dev.of_node, 0); + if (of_address_to_resource(op->dev.of_node, 0, &res)) { + dev_err(&op->dev, "Missing reg property\n"); + return -ENODEV; + } + regs = ioremap(res.start, resource_size(&res)); + if (!regs) { + dev_err(&op->dev, "Could not map registers\n"); + return -ENODEV; + } + + /* Allocate and initialize the driver private data */ + psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL); + if (!psc_dma) { + ret = -ENOMEM; + goto out_unmap; + } + + /* Get the PSC ID */ + prop = of_get_property(op->dev.of_node, "cell-index", &size); + if (!prop || size < sizeof *prop) { + ret = -ENODEV; + goto out_free; + } + + spin_lock_init(&psc_dma->lock); + mutex_init(&psc_dma->mutex); + psc_dma->id = be32_to_cpu(*prop); + psc_dma->irq = irq; + psc_dma->psc_regs = regs; + psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs; + psc_dma->dev = &op->dev; + psc_dma->playback.psc_dma = psc_dma; + psc_dma->capture.psc_dma = psc_dma; + snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_dma->id); + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ + fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_dma->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512); + psc_dma->playback.bcom_task = + bcom_psc_gen_bd_tx_init(psc_dma->id, 10, fifo); + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); + ret = -ENODEV; + goto out_free; + } + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + /* reset receiver */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_RX); + /* reset transmitter */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_TX); + /* reset error */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_ERR_STAT); + /* reset mode */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_SEL_MODE_REG_1); + + /* Set up mode register; + * First write: RxRdy (FIFO Alarm) generates rx FIFO irq + * Second write: register Normal mode for non loopback + */ + out_8(&psc_dma->psc_regs->mode, 0); + out_8(&psc_dma->psc_regs->mode, 0); + + /* Set the TX and RX fifo alarm thresholds */ + out_be16(&psc_dma->fifo_regs->rfalarm, 0x100); + out_8(&psc_dma->fifo_regs->rfcntl, 0x4); + out_be16(&psc_dma->fifo_regs->tfalarm, 0x100); + out_8(&psc_dma->fifo_regs->tfcntl, 0x7); + + /* Lookup the IRQ numbers */ + psc_dma->playback.irq = + bcom_get_task_irq(psc_dma->playback.bcom_task); + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); + + rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); + rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); + rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); + if (rc) { + ret = -ENODEV; + goto out_irq; + } + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_dma); + + /* Tell the ASoC OF helpers about it */ + return snd_soc_register_platform(&op->dev, &mpc5200_audio_dma_platform); +out_irq: + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, &psc_dma->capture); + free_irq(psc_dma->playback.irq, &psc_dma->playback); +out_free: + kfree(psc_dma); +out_unmap: + iounmap(regs); + return ret; +} +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); + +int mpc5200_audio_dma_destroy(struct platform_device *op) +{ + struct psc_dma *psc_dma = dev_get_drvdata(&op->dev); + + dev_dbg(&op->dev, "mpc5200_audio_dma_destroy()\n"); + + snd_soc_unregister_platform(&op->dev); + + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); + + /* Release irqs */ + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, &psc_dma->capture); + free_irq(psc_dma->playback.irq, &psc_dma->playback); + + iounmap(psc_dma->psc_regs); + kfree(psc_dma); + dev_set_drvdata(&op->dev, NULL); + + return 0; +} +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_destroy); + +MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h new file mode 100644 index 000000000..dff253fde --- /dev/null +++ b/sound/soc/fsl/mpc5200_dma.h @@ -0,0 +1,87 @@ +/* + * Freescale MPC5200 Audio DMA driver + */ + +#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__ +#define __SOUND_SOC_FSL_MPC5200_DMA_H__ + +#define PSC_STREAM_NAME_LEN 32 + +/** + * psc_ac97_stream - Data specific to a single stream (playback or capture) + * @active: flag indicating if the stream is active + * @psc_dma: pointer back to parent psc_dma data structure + * @bcom_task: bestcomm task structure + * @irq: irq number for bestcomm task + * @period_end: physical address of end of DMA region + * @period_next_pt: physical address of next DMA buffer to enqueue + * @period_bytes: size of DMA period in bytes + * @ac97_slot_bits: Enable bits for turning on the correct AC97 slot + */ +struct psc_dma_stream { + struct snd_pcm_runtime *runtime; + int active; + struct psc_dma *psc_dma; + struct bcom_task *bcom_task; + int irq; + struct snd_pcm_substream *stream; + int period_next; + int period_current; + int period_bytes; + int period_count; + + /* AC97 state */ + u32 ac97_slot_bits; +}; + +/** + * psc_dma - Private driver data + * @name: short name for this device ("PSC0", "PSC1", etc) + * @psc_regs: pointer to the PSC's registers + * @fifo_regs: pointer to the PSC's FIFO registers + * @irq: IRQ of this PSC + * @dev: struct device pointer + * @dai: the CPU DAI for this device + * @sicr: Base value used in serial interface control register; mode is ORed + * with this value. + * @playback: Playback stream context data + * @capture: Capture stream context data + */ +struct psc_dma { + char name[32]; + struct mpc52xx_psc __iomem *psc_regs; + struct mpc52xx_psc_fifo __iomem *fifo_regs; + unsigned int irq; + struct device *dev; + spinlock_t lock; + struct mutex mutex; + u32 sicr; + uint sysclk; + int imr; + int id; + unsigned int slots; + + /* per-stream data */ + struct psc_dma_stream playback; + struct psc_dma_stream capture; + + /* Statistics */ + struct { + unsigned long overrun_count; + unsigned long underrun_count; + } stats; +}; + +/* Utility for retrieving psc_dma_stream structure from a substream */ +static inline struct psc_dma_stream * +to_psc_dma_stream(struct snd_pcm_substream *substream, struct psc_dma *psc_dma) +{ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + return &psc_dma->capture; + return &psc_dma->playback; +} + +int mpc5200_audio_dma_create(struct platform_device *op); +int mpc5200_audio_dma_destroy(struct platform_device *op); + +#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */ diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c new file mode 100644 index 000000000..0bab76051 --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -0,0 +1,350 @@ +/* + * linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip. + * + * Copyright (C) 2009 Jon Smirl, Digispeaker + * Author: Jon Smirl <jonsmirl@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/delay.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/time.h> +#include <asm/delay.h> +#include <asm/mpc52xx.h> +#include <asm/mpc52xx_psc.h> + +#include "mpc5200_dma.h" +#include "mpc5200_psc_ac97.h" + +#define DRV_NAME "mpc5200-psc-ac97" + +/* ALSA only supports a single AC97 device so static is recommend here */ +static struct psc_dma *psc_dma; + +static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + int status; + unsigned int val; + + mutex_lock(&psc_dma->mutex); + + /* Wait for command send status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (rdy)\n"); + mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + + /* Force clear the data valid bit */ + in_be32(&psc_dma->psc_regs->ac97_data); + + /* Send the read */ + out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24)); + + /* Wait for the answer */ + status = spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_DATA_VAL), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 read (val) %x\n", + in_be16(&psc_dma->psc_regs->sr_csr.status)); + mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + /* Get the data */ + val = in_be32(&psc_dma->psc_regs->ac97_data); + if (((val >> 24) & 0x7f) != reg) { + pr_err("reg echo error on ac97 read\n"); + mutex_unlock(&psc_dma->mutex); + return -ENODEV; + } + val = (val >> 8) & 0xffff; + + mutex_unlock(&psc_dma->mutex); + return (unsigned short) val; +} + +static void psc_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + int status; + + mutex_lock(&psc_dma->mutex); + + /* Wait for command status zero = ready */ + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { + pr_err("timeout on ac97 bus (write)\n"); + goto out; + } + /* Write data */ + out_be32(&psc_dma->psc_regs->ac97_cmd, + ((reg & 0x7f) << 24) | (val << 8)); + + out: + mutex_unlock(&psc_dma->mutex); +} + +static void psc_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + mutex_lock(&psc_dma->mutex); + + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); + udelay(3); + out_be32(®s->sicr, psc_dma->sicr); + + mutex_unlock(&psc_dma->mutex); +} + +static void psc_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + mutex_lock(&psc_dma->mutex); + dev_dbg(psc_dma->dev, "cold reset\n"); + + mpc5200_psc_ac97_gpio_reset(psc_dma->id); + + /* Notify the PSC that a reset has occurred */ + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB); + + /* Re-enable RX and TX */ + out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); + + mutex_unlock(&psc_dma->mutex); + + msleep(1); + psc_ac97_warm_reset(ac97); +} + +static struct snd_ac97_bus_ops psc_ac97_ops = { + .read = psc_ac97_read, + .write = psc_ac97_write, + .reset = psc_ac97_cold_reset, + .warm_reset = psc_ac97_warm_reset, +}; + +static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + + dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i channels=%i" + " rate=%i format=%i\n", + __func__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params), + params_channels(params), params_rate(params), + params_format(params)); + + /* Determine the set of enable bits to turn on */ + s->ac97_slot_bits = (params_channels(params) == 1) ? 0x100 : 0x300; + if (substream->pstr->stream != SNDRV_PCM_STREAM_CAPTURE) + s->ac97_slot_bits <<= 16; + return 0; +} + +static int psc_ac97_hw_digital_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + + dev_dbg(psc_dma->dev, "%s(substream=%p)\n", __func__, substream); + + if (params_channels(params) == 1) + out_be32(&psc_dma->psc_regs->ac97_slots, 0x01000000); + else + out_be32(&psc_dma->psc_regs->ac97_slots, 0x03000000); + + return 0; +} + +static int psc_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(dai); + struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dev_dbg(psc_dma->dev, "AC97 START: stream=%i\n", + substream->pstr->stream); + + /* Set the slot enable bits */ + psc_dma->slots |= s->ac97_slot_bits; + out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots); + break; + + case SNDRV_PCM_TRIGGER_STOP: + dev_dbg(psc_dma->dev, "AC97 STOP: stream=%i\n", + substream->pstr->stream); + + /* Clear the slot enable bits */ + psc_dma->slots &= ~(s->ac97_slot_bits); + out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots); + break; + } + return 0; +} + +static int psc_ac97_probe(struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + /* Go */ + out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); + return 0; +} + +/* --------------------------------------------------------------------- + * ALSA SoC Bindings + * + * - Digital Audio Interface (DAI) template + * - create/destroy dai hooks + */ + +/** + * psc_ac97_dai_template: template CPU Digital Audio Interface + */ +static const struct snd_soc_dai_ops psc_ac97_analog_ops = { + .hw_params = psc_ac97_hw_analog_params, + .trigger = psc_ac97_trigger, +}; + +static const struct snd_soc_dai_ops psc_ac97_digital_ops = { + .hw_params = psc_ac97_hw_digital_params, +}; + +static struct snd_soc_dai_driver psc_ac97_dai[] = { +{ + .name = "mpc5200-psc-ac97.0", + .bus_control = true, + .probe = psc_ac97_probe, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 1, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_BE, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_BE, + }, + .ops = &psc_ac97_analog_ops, +}, +{ + .name = "mpc5200-psc-ac97.1", + .bus_control = true, + .playback = { + .stream_name = "AC97 SPDIF", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE, + }, + .ops = &psc_ac97_digital_ops, +} }; + +static const struct snd_soc_component_driver psc_ac97_component = { + .name = DRV_NAME, +}; + + +/* --------------------------------------------------------------------- + * OF platform bus binding code: + * - Probe/remove operations + * - OF device match table + */ +static int psc_ac97_of_probe(struct platform_device *op) +{ + int rc; + struct mpc52xx_psc __iomem *regs; + + rc = mpc5200_audio_dma_create(op); + if (rc != 0) + return rc; + + rc = snd_soc_set_ac97_ops(&psc_ac97_ops); + if (rc != 0) { + dev_err(&op->dev, "Failed to set AC'97 ops: %d\n", rc); + return rc; + } + + rc = snd_soc_register_component(&op->dev, &psc_ac97_component, + psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai)); + if (rc != 0) { + dev_err(&op->dev, "Failed to register DAI\n"); + return rc; + } + + psc_dma = dev_get_drvdata(&op->dev); + regs = psc_dma->psc_regs; + + psc_dma->imr = 0; + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + + /* Configure the serial interface mode to AC97 */ + psc_dma->sicr = MPC52xx_PSC_SICR_SIM_AC97 | MPC52xx_PSC_SICR_ENAC97; + out_be32(®s->sicr, psc_dma->sicr); + + /* No slots active */ + out_be32(®s->ac97_slots, 0x00000000); + + return 0; +} + +static int psc_ac97_of_remove(struct platform_device *op) +{ + mpc5200_audio_dma_destroy(op); + snd_soc_unregister_component(&op->dev); + snd_soc_set_ac97_ops(NULL); + return 0; +} + +/* Match table for of_platform binding */ +static const struct of_device_id psc_ac97_match[] = { + { .compatible = "fsl,mpc5200-psc-ac97", }, + { .compatible = "fsl,mpc5200b-psc-ac97", }, + {} +}; +MODULE_DEVICE_TABLE(of, psc_ac97_match); + +static struct platform_driver psc_ac97_driver = { + .probe = psc_ac97_of_probe, + .remove = psc_ac97_of_remove, + .driver = { + .name = "mpc5200-psc-ac97", + .of_match_table = psc_ac97_match, + }, +}; + +module_platform_driver(psc_ac97_driver); + +MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>"); +MODULE_DESCRIPTION("mpc5200 AC97 module"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/mpc5200_psc_ac97.h b/sound/soc/fsl/mpc5200_psc_ac97.h new file mode 100644 index 000000000..e881e784b --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_ac97.h @@ -0,0 +1,13 @@ +/* + * Freescale MPC5200 PSC in AC97 mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + */ + +#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ +#define __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ + +#define MPC5200_AC97_NORMAL 0 +#define MPC5200_AC97_SPDIF 1 + +#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ */ diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c new file mode 100644 index 000000000..d8232943c --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -0,0 +1,241 @@ +/* + * Freescale MPC5200 PSC in I2S mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + * Copyright (C) 2009 Jon Smirl, Digispeaker + */ + +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/mpc52xx_psc.h> + +#include "mpc5200_dma.h" + +/** + * PSC_I2S_RATES: sample rates supported by the I2S + * + * This driver currently only supports the PSC running in I2S slave mode, + * which means the codec determines the sample rate. Therefore, we tell + * ALSA that we support all rates and let the codec driver decide what rates + * are really supported. + */ +#define PSC_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS + +/** + * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode + */ +#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +static int psc_i2s_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 psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai); + u32 mode; + + dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i\n", + __func__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params)); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mode = MPC52xx_PSC_SICR_SIM_CODEC_8; + break; + case SNDRV_PCM_FORMAT_S16_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_16; + break; + case SNDRV_PCM_FORMAT_S24_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_24; + break; + case SNDRV_PCM_FORMAT_S32_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_32; + break; + default: + dev_dbg(psc_dma->dev, "invalid format\n"); + return -EINVAL; + } + out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode); + + return 0; +} + +/** + * psc_i2s_set_sysclk: set the clock frequency and direction + * + * This function is called by the machine driver to tell us what the clock + * frequency and direction are. + * + * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN), + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) + */ +static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + dev_dbg(psc_dma->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n", + cpu_dai, dir); + return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL; +} + +/** + * psc_i2s_set_fmt: set the serial format. + * + * This function is called by the machine driver to tell us what serial + * format to use. + * + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * + * @format: one of SND_SOC_DAIFMT_xxx + */ +static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) +{ + struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai); + dev_dbg(psc_dma->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n", + cpu_dai, format); + return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; +} + +/* --------------------------------------------------------------------- + * ALSA SoC Bindings + * + * - Digital Audio Interface (DAI) template + * - create/destroy dai hooks + */ + +/** + * psc_i2s_dai_template: template CPU Digital Audio Interface + */ +static const struct snd_soc_dai_ops psc_i2s_dai_ops = { + .hw_params = psc_i2s_hw_params, + .set_sysclk = psc_i2s_set_sysclk, + .set_fmt = psc_i2s_set_fmt, +}; + +static struct snd_soc_dai_driver psc_i2s_dai[] = {{ + .name = "mpc5200-psc-i2s.0", + .playback = { + .stream_name = "I2S Playback", + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .capture = { + .stream_name = "I2S Capture", + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .ops = &psc_i2s_dai_ops, +} }; + +static const struct snd_soc_component_driver psc_i2s_component = { + .name = "mpc5200-i2s", +}; + +/* --------------------------------------------------------------------- + * OF platform bus binding code: + * - Probe/remove operations + * - OF device match table + */ +static int psc_i2s_of_probe(struct platform_device *op) +{ + int rc; + struct psc_dma *psc_dma; + struct mpc52xx_psc __iomem *regs; + + rc = mpc5200_audio_dma_create(op); + if (rc != 0) + return rc; + + rc = snd_soc_register_component(&op->dev, &psc_i2s_component, + psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai)); + if (rc != 0) { + pr_err("Failed to register DAI\n"); + return rc; + } + + psc_dma = dev_get_drvdata(&op->dev); + regs = psc_dma->psc_regs; + + /* Configure the serial interface mode; defaulting to CODEC8 mode */ + psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | + MPC52xx_PSC_SICR_CLKPOL; + out_be32(&psc_dma->psc_regs->sicr, + psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); + + /* Check for the codec handle. If it is not present then we + * are done */ + if (!of_get_property(op->dev.of_node, "codec-handle", NULL)) + return 0; + + /* Due to errata in the dma mode; need to line up enabling + * the transmitter with a transition on the frame sync + * line */ + + /* first make sure it is low */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) + ; + /* then wait for the transition to high */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) + ; + /* Finally, enable the PSC. + * Receiver must always be enabled; even when we only want + * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ + + /* Go */ + out_8(&psc_dma->psc_regs->command, + MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); + + return 0; + +} + +static int psc_i2s_of_remove(struct platform_device *op) +{ + mpc5200_audio_dma_destroy(op); + snd_soc_unregister_component(&op->dev); + return 0; +} + +/* Match table for of_platform binding */ +static const struct of_device_id psc_i2s_match[] = { + { .compatible = "fsl,mpc5200-psc-i2s", }, + { .compatible = "fsl,mpc5200b-psc-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, psc_i2s_match); + +static struct platform_driver psc_i2s_driver = { + .probe = psc_i2s_of_probe, + .remove = psc_i2s_of_remove, + .driver = { + .name = "mpc5200-psc-i2s", + .of_match_table = psc_i2s_match, + }, +}; + +module_platform_driver(psc_i2s_driver); + +MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c new file mode 100644 index 000000000..9621b9140 --- /dev/null +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -0,0 +1,433 @@ +/** + * Freescale MPC8610HPCD ALSA SoC Machine driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007-2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <asm/fsl_guts.h> + +#include "fsl_dma.h" +#include "fsl_ssi.h" +#include "fsl_utils.h" + +/* There's only one global utilities register */ +static phys_addr_t guts_phys; + +/** + * mpc8610_hpcd_data: machine-specific ASoC device data + * + * This structure contains data for a single sound platform device on an + * MPC8610 HPCD. Some of the data is taken from the device tree. + */ +struct mpc8610_hpcd_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; + unsigned int dai_format; + unsigned int codec_clk_direction; + unsigned int cpu_clk_direction; + unsigned int clk_frequency; + unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ + unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ + unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ +}; + +/** + * mpc8610_hpcd_machine_probe: initialize the board + * + * This function is used to initialize the board-specific hardware. + * + * Here we program the DMACR and PMUXCR registers. + */ +static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card) +{ + struct mpc8610_hpcd_data *machine_data = + container_of(card, struct mpc8610_hpcd_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Program the signal routing between the SSI and the DMA */ + guts_set_dmacr(guts, machine_data->dma_id[0], + machine_data->dma_channel_id[0], + CCSR_GUTS_DMACR_DEV_SSI); + guts_set_dmacr(guts, machine_data->dma_id[1], + machine_data->dma_channel_id[1], + CCSR_GUTS_DMACR_DEV_SSI); + + guts_set_pmuxcr_dma(guts, machine_data->dma_id[0], + machine_data->dma_channel_id[0], 0); + guts_set_pmuxcr_dma(guts, machine_data->dma_id[1], + machine_data->dma_channel_id[1], 0); + + switch (machine_data->ssi_id) { + case 0: + clrsetbits_be32(&guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI); + break; + case 1: + clrsetbits_be32(&guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI); + break; + } + + iounmap(guts); + + return 0; +} + +/** + * mpc8610_hpcd_startup: program the board with various hardware parameters + * + * This function takes board-specific information, like clock frequencies + * and serial data formats, and passes that information to the codec and + * transport drivers. + */ +static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mpc8610_hpcd_data *machine_data = + container_of(rtd->card, struct mpc8610_hpcd_data, card); + struct device *dev = rtd->card->dev; + int ret = 0; + + /* Tell the codec driver what the serial protocol is. */ + ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format); + if (ret < 0) { + dev_err(dev, "could not set codec driver audio format\n"); + return ret; + } + + /* + * Tell the codec driver what the MCLK frequency is, and whether it's + * a slave or master. + */ + ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, + machine_data->clk_frequency, + machine_data->codec_clk_direction); + if (ret < 0) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + +/** + * mpc8610_hpcd_machine_remove: Remove the sound device + * + * This function is called to remove the sound device for one SSI. We + * de-program the DMACR and PMUXCR register. + */ +static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card) +{ + struct mpc8610_hpcd_data *machine_data = + container_of(card, struct mpc8610_hpcd_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Restore the signal routing */ + + guts_set_dmacr(guts, machine_data->dma_id[0], + machine_data->dma_channel_id[0], 0); + guts_set_dmacr(guts, machine_data->dma_id[1], + machine_data->dma_channel_id[1], 0); + + switch (machine_data->ssi_id) { + case 0: + clrsetbits_be32(&guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA); + break; + case 1: + clrsetbits_be32(&guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA); + break; + } + + iounmap(guts); + + return 0; +} + +/** + * mpc8610_hpcd_ops: ASoC machine driver operations + */ +static struct snd_soc_ops mpc8610_hpcd_ops = { + .startup = mpc8610_hpcd_startup, +}; + +/** + * mpc8610_hpcd_probe: platform probe function for the machine driver + * + * Although this is a machine driver, the SSI node is the "master" node with + * respect to audio hardware connections. Therefore, we create a new ASoC + * device for each new SSI node that has a codec attached. + */ +static int mpc8610_hpcd_probe(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + /* ssi_pdev is the platform device for the SSI node that probed us */ + struct platform_device *ssi_pdev = + container_of(dev, struct platform_device, dev); + struct device_node *np = ssi_pdev->dev.of_node; + struct device_node *codec_np = NULL; + struct mpc8610_hpcd_data *machine_data; + int ret = -ENODEV; + const char *sprop; + const u32 *iprop; + + /* Find the codec node for this SSI. */ + codec_np = of_parse_phandle(np, "codec-handle", 0); + if (!codec_np) { + dev_err(dev, "invalid codec node\n"); + return -EINVAL; + } + + machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL); + if (!machine_data) { + ret = -ENOMEM; + goto error_alloc; + } + + machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); + machine_data->dai[0].ops = &mpc8610_hpcd_ops; + + /* ASoC core can match codec with device node */ + machine_data->dai[0].codec_of_node = codec_np; + + /* The DAI name from the codec (snd_soc_dai_driver.name) */ + machine_data->dai[0].codec_dai_name = "cs4270-hifi"; + + /* We register two DAIs per SSI, one for playback and the other for + * capture. Currently, we only support codecs that have one DAI for + * both playback and capture. + */ + memcpy(&machine_data->dai[1], &machine_data->dai[0], + sizeof(struct snd_soc_dai_link)); + + /* Get the device ID */ + iprop = of_get_property(np, "cell-index", NULL); + if (!iprop) { + dev_err(&pdev->dev, "cell-index property not found\n"); + ret = -EINVAL; + goto error; + } + machine_data->ssi_id = be32_to_cpup(iprop); + + /* Get the serial format and clock direction. */ + sprop = of_get_property(np, "fsl,mode", NULL); + if (!sprop) { + dev_err(&pdev->dev, "fsl,mode property not found\n"); + ret = -EINVAL; + goto error; + } + + if (strcasecmp(sprop, "i2s-slave") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + + /* In i2s-slave mode, the codec has its own clock source, so we + * need to get the frequency from the device tree and pass it to + * the codec driver. + */ + iprop = of_get_property(codec_np, "clock-frequency", NULL); + if (!iprop || !*iprop) { + dev_err(&pdev->dev, "codec bus-frequency " + "property is missing or invalid\n"); + ret = -EINVAL; + goto error; + } + machine_data->clk_frequency = be32_to_cpup(iprop); + } else if (strcasecmp(sprop, "i2s-master") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "lj-slave") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "lj-master") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "rj-slave") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "rj-master") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "ac97-slave") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "ac97-master") == 0) { + machine_data->dai_format = + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else { + dev_err(&pdev->dev, + "unrecognized fsl,mode property '%s'\n", sprop); + ret = -EINVAL; + goto error; + } + + if (!machine_data->clk_frequency) { + dev_err(&pdev->dev, "unknown clock frequency\n"); + ret = -EINVAL; + goto error; + } + + /* Find the playback DMA channel to use. */ + machine_data->dai[0].platform_name = machine_data->platform_name[0]; + ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", + &machine_data->dai[0], + &machine_data->dma_channel_id[0], + &machine_data->dma_id[0]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); + goto error; + } + + /* Find the capture DMA channel to use. */ + machine_data->dai[1].platform_name = machine_data->platform_name[1]; + ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", + &machine_data->dai[1], + &machine_data->dma_channel_id[1], + &machine_data->dma_id[1]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); + goto error; + } + + /* Initialize our DAI data structure. */ + machine_data->dai[0].stream_name = "playback"; + machine_data->dai[1].stream_name = "capture"; + machine_data->dai[0].name = machine_data->dai[0].stream_name; + machine_data->dai[1].name = machine_data->dai[1].stream_name; + + machine_data->card.probe = mpc8610_hpcd_machine_probe; + machine_data->card.remove = mpc8610_hpcd_machine_remove; + machine_data->card.name = pdev->name; /* The platform driver name */ + machine_data->card.owner = THIS_MODULE; + machine_data->card.dev = &pdev->dev; + machine_data->card.num_links = 2; + machine_data->card.dai_link = machine_data->dai; + + /* Register with ASoC */ + ret = snd_soc_register_card(&machine_data->card); + if (ret) { + dev_err(&pdev->dev, "could not register card\n"); + goto error; + } + + of_node_put(codec_np); + + return 0; + +error: + kfree(machine_data); +error_alloc: + of_node_put(codec_np); + return ret; +} + +/** + * mpc8610_hpcd_remove: remove the platform device + * + * This function is called when the platform device is removed. + */ +static int mpc8610_hpcd_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct mpc8610_hpcd_data *machine_data = + container_of(card, struct mpc8610_hpcd_data, card); + + snd_soc_unregister_card(card); + kfree(machine_data); + + return 0; +} + +static struct platform_driver mpc8610_hpcd_driver = { + .probe = mpc8610_hpcd_probe, + .remove = mpc8610_hpcd_remove, + .driver = { + /* The name must match 'compatible' property in the device tree, + * in lowercase letters. + */ + .name = "snd-soc-mpc8610hpcd", + }, +}; + +/** + * mpc8610_hpcd_init: machine driver initialization. + * + * This function is called when this module is loaded. + */ +static int __init mpc8610_hpcd_init(void) +{ + struct device_node *guts_np; + struct resource res; + + pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n"); + + /* Get the physical address of the global utilities registers */ + guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts"); + if (of_address_to_resource(guts_np, 0, &res)) { + pr_err("mpc8610-hpcd: missing/invalid global utilities node\n"); + return -EINVAL; + } + guts_phys = res.start; + + return platform_driver_register(&mpc8610_hpcd_driver); +} + +/** + * mpc8610_hpcd_exit: machine driver exit + * + * This function is called when this driver is unloaded. + */ +static void __exit mpc8610_hpcd_exit(void) +{ + platform_driver_unregister(&mpc8610_hpcd_driver); +} + +module_init(mpc8610_hpcd_init); +module_exit(mpc8610_hpcd_exit); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/mx27vis-aic32x4.c b/sound/soc/fsl/mx27vis-aic32x4.c new file mode 100644 index 000000000..198eeb3f3 --- /dev/null +++ b/sound/soc/fsl/mx27vis-aic32x4.c @@ -0,0 +1,234 @@ +/* + * mx27vis-aic32x4.c + * + * Copyright 2011 Vista Silicon S.L. + * + * Author: Javier Martin <javier.martin@vista-silicon.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/platform_data/asoc-mx27vis.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <asm/mach-types.h> + +#include "../codecs/tlv320aic32x4.h" +#include "imx-ssi.h" +#include "imx-audmux.h" + +#define MX27VIS_AMP_GAIN 0 +#define MX27VIS_AMP_MUTE 1 + +static int mx27vis_amp_gain; +static int mx27vis_amp_mute; +static int mx27vis_amp_gain0_gpio; +static int mx27vis_amp_gain1_gpio; +static int mx27vis_amp_mutel_gpio; +static int mx27vis_amp_muter_gpio; + +static int mx27vis_aic32x4_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 *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + 25000000, SND_SOC_CLOCK_OUT); + if (ret) { + pr_err("%s: failed setting codec sysclk\n", __func__); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, + SND_SOC_CLOCK_IN); + if (ret) { + pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops mx27vis_aic32x4_snd_ops = { + .hw_params = mx27vis_aic32x4_hw_params, +}; + +static int mx27vis_amp_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int value = ucontrol->value.integer.value[0]; + unsigned int reg = mc->reg; + int max = mc->max; + + if (value > max) + return -EINVAL; + + switch (reg) { + case MX27VIS_AMP_GAIN: + gpio_set_value(mx27vis_amp_gain0_gpio, value & 1); + gpio_set_value(mx27vis_amp_gain1_gpio, value >> 1); + mx27vis_amp_gain = value; + break; + case MX27VIS_AMP_MUTE: + gpio_set_value(mx27vis_amp_mutel_gpio, value & 1); + gpio_set_value(mx27vis_amp_muter_gpio, value >> 1); + mx27vis_amp_mute = value; + break; + } + return 0; +} + +static int mx27vis_amp_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + + switch (reg) { + case MX27VIS_AMP_GAIN: + ucontrol->value.integer.value[0] = mx27vis_amp_gain; + break; + case MX27VIS_AMP_MUTE: + ucontrol->value.integer.value[0] = mx27vis_amp_mute; + break; + } + return 0; +} + +/* From 6dB to 24dB in steps of 6dB */ +static const DECLARE_TLV_DB_SCALE(mx27vis_amp_tlv, 600, 600, 0); + +static const struct snd_kcontrol_new mx27vis_aic32x4_controls[] = { + SOC_DAPM_PIN_SWITCH("External Mic"), + SOC_SINGLE_EXT_TLV("LO Ext Boost", MX27VIS_AMP_GAIN, 0, 3, 0, + mx27vis_amp_get, mx27vis_amp_set, mx27vis_amp_tlv), + SOC_DOUBLE_EXT("LO Ext Mute Switch", MX27VIS_AMP_MUTE, 0, 1, 1, 0, + mx27vis_amp_get, mx27vis_amp_set), +}; + +static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = { + SND_SOC_DAPM_MIC("External Mic", NULL), +}; + +static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = { + {"Mic Bias", NULL, "External Mic"}, + {"IN1_R", NULL, "Mic Bias"}, + {"IN2_R", NULL, "Mic Bias"}, + {"IN3_R", NULL, "Mic Bias"}, + {"IN1_L", NULL, "Mic Bias"}, + {"IN2_L", NULL, "Mic Bias"}, + {"IN3_L", NULL, "Mic Bias"}, +}; + +static struct snd_soc_dai_link mx27vis_aic32x4_dai = { + .name = "tlv320aic32x4", + .stream_name = "TLV320AIC32X4", + .codec_dai_name = "tlv320aic32x4-hifi", + .platform_name = "imx-ssi.0", + .codec_name = "tlv320aic32x4.0-0018", + .cpu_dai_name = "imx-ssi.0", + .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + .ops = &mx27vis_aic32x4_snd_ops, +}; + +static struct snd_soc_card mx27vis_aic32x4 = { + .name = "visstrim_m10-audio", + .owner = THIS_MODULE, + .dai_link = &mx27vis_aic32x4_dai, + .num_links = 1, + .controls = mx27vis_aic32x4_controls, + .num_controls = ARRAY_SIZE(mx27vis_aic32x4_controls), + .dapm_widgets = aic32x4_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(aic32x4_dapm_widgets), + .dapm_routes = aic32x4_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(aic32x4_dapm_routes), +}; + +static int mx27vis_aic32x4_probe(struct platform_device *pdev) +{ + struct snd_mx27vis_platform_data *pdata = pdev->dev.platform_data; + int ret; + + if (!pdata) { + dev_err(&pdev->dev, "No platform data supplied\n"); + return -EINVAL; + } + + mx27vis_amp_gain0_gpio = pdata->amp_gain0_gpio; + mx27vis_amp_gain1_gpio = pdata->amp_gain1_gpio; + mx27vis_amp_mutel_gpio = pdata->amp_mutel_gpio; + mx27vis_amp_muter_gpio = pdata->amp_muter_gpio; + + mx27vis_aic32x4.dev = &pdev->dev; + ret = snd_soc_register_card(&mx27vis_aic32x4); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + /* Connect SSI0 as clock slave to SSI1 external pins */ + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_TFSDIR | + IMX_AUDMUX_V1_PCR_TCLKDIR | + IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) + ); + imx_audmux_v1_configure_port(MX27_AUDMUX_PPCR1_SSI_PINS_1, + IMX_AUDMUX_V1_PCR_SYN | + IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0) + ); + + return ret; +} + +static int mx27vis_aic32x4_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&mx27vis_aic32x4); + + return 0; +} + +static struct platform_driver mx27vis_aic32x4_audio_driver = { + .driver = { + .name = "mx27vis", + }, + .probe = mx27vis_aic32x4_probe, + .remove = mx27vis_aic32x4_remove, +}; + +module_platform_driver(mx27vis_aic32x4_audio_driver); + +MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>"); +MODULE_DESCRIPTION("ALSA SoC AIC32X4 mx27 visstrim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mx27vis"); diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c new file mode 100644 index 000000000..71c1a7dc3 --- /dev/null +++ b/sound/soc/fsl/p1022_ds.c @@ -0,0 +1,442 @@ +/** + * Freescale P1022DS ALSA SoC Machine driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2010 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <asm/fsl_guts.h> + +#include "fsl_dma.h" +#include "fsl_ssi.h" +#include "fsl_utils.h" + +/* P1022-specific PMUXCR and DMUXCR bit definitions */ + +#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000 + +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00 +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000 + +#define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */ +#define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */ + +/* + * Set the DMACR register in the GUTS + * + * The DMACR register determines the source of initiated transfers for each + * channel on each DMA controller. Rather than have a bunch of repetitive + * macros for the bit patterns, we just have a function that calculates + * them. + * + * guts: Pointer to GUTS structure + * co: The DMA controller (0 or 1) + * ch: The channel on the DMA controller (0, 1, 2, or 3) + * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx) + */ +static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts, + unsigned int co, unsigned int ch, unsigned int device) +{ + unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); + + clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift); +} + +/* There's only one global utilities register */ +static phys_addr_t guts_phys; + +/** + * machine_data: machine-specific ASoC device data + * + * This structure contains data for a single sound platform device on an + * P1022 DS. Some of the data is taken from the device tree. + */ +struct machine_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; + unsigned int dai_format; + unsigned int codec_clk_direction; + unsigned int cpu_clk_direction; + unsigned int clk_frequency; + unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ + unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ + unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ +}; + +/** + * p1022_ds_machine_probe: initialize the board + * + * This function is used to initialize the board-specific hardware. + * + * Here we program the DMACR and PMUXCR registers. + */ +static int p1022_ds_machine_probe(struct snd_soc_card *card) +{ + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Enable SSI Tx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK, + CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI); + + /* Enable SSI Rx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK, + CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI); + + /* Enable DMA Channel for SSI */ + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], + CCSR_GUTS_DMUXCR_SSI); + + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], + CCSR_GUTS_DMUXCR_SSI); + + iounmap(guts); + + return 0; +} + +/** + * p1022_ds_startup: program the board with various hardware parameters + * + * This function takes board-specific information, like clock frequencies + * and serial data formats, and passes that information to the codec and + * transport drivers. + */ +static int p1022_ds_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct machine_data *mdata = + container_of(rtd->card, struct machine_data, card); + struct device *dev = rtd->card->dev; + int ret = 0; + + /* Tell the codec driver what the serial protocol is. */ + ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format); + if (ret < 0) { + dev_err(dev, "could not set codec driver audio format\n"); + return ret; + } + + /* + * Tell the codec driver what the MCLK frequency is, and whether it's + * a slave or master. + */ + ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency, + mdata->codec_clk_direction); + if (ret < 0) { + dev_err(dev, "could not set codec driver clock params\n"); + return ret; + } + + return 0; +} + +/** + * p1022_ds_machine_remove: Remove the sound device + * + * This function is called to remove the sound device for one SSI. We + * de-program the DMACR and PMUXCR register. + */ +static int p1022_ds_machine_remove(struct snd_soc_card *card) +{ + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Restore the signal routing */ + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK); + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK); + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0); + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0); + + iounmap(guts); + + return 0; +} + +/** + * p1022_ds_ops: ASoC machine driver operations + */ +static struct snd_soc_ops p1022_ds_ops = { + .startup = p1022_ds_startup, +}; + +/** + * p1022_ds_probe: platform probe function for the machine driver + * + * Although this is a machine driver, the SSI node is the "master" node with + * respect to audio hardware connections. Therefore, we create a new ASoC + * device for each new SSI node that has a codec attached. + */ +static int p1022_ds_probe(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + /* ssi_pdev is the platform device for the SSI node that probed us */ + struct platform_device *ssi_pdev = + container_of(dev, struct platform_device, dev); + struct device_node *np = ssi_pdev->dev.of_node; + struct device_node *codec_np = NULL; + struct machine_data *mdata; + int ret = -ENODEV; + const char *sprop; + const u32 *iprop; + + /* Find the codec node for this SSI. */ + codec_np = of_parse_phandle(np, "codec-handle", 0); + if (!codec_np) { + dev_err(dev, "could not find codec node\n"); + return -EINVAL; + } + + mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL); + if (!mdata) { + ret = -ENOMEM; + goto error_put; + } + + mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); + mdata->dai[0].ops = &p1022_ds_ops; + + /* ASoC core can match codec with device node */ + mdata->dai[0].codec_of_node = codec_np; + + /* We register two DAIs per SSI, one for playback and the other for + * capture. We support codecs that have separate DAIs for both playback + * and capture. + */ + memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); + + /* The DAI names from the codec (snd_soc_dai_driver.name) */ + mdata->dai[0].codec_dai_name = "wm8776-hifi-playback"; + mdata->dai[1].codec_dai_name = "wm8776-hifi-capture"; + + /* Get the device ID */ + iprop = of_get_property(np, "cell-index", NULL); + if (!iprop) { + dev_err(&pdev->dev, "cell-index property not found\n"); + ret = -EINVAL; + goto error; + } + mdata->ssi_id = be32_to_cpup(iprop); + + /* Get the serial format and clock direction. */ + sprop = of_get_property(np, "fsl,mode", NULL); + if (!sprop) { + dev_err(&pdev->dev, "fsl,mode property not found\n"); + ret = -EINVAL; + goto error; + } + + if (strcasecmp(sprop, "i2s-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + + /* In i2s-slave mode, the codec has its own clock source, so we + * need to get the frequency from the device tree and pass it to + * the codec driver. + */ + iprop = of_get_property(codec_np, "clock-frequency", NULL); + if (!iprop || !*iprop) { + dev_err(&pdev->dev, "codec bus-frequency " + "property is missing or invalid\n"); + ret = -EINVAL; + goto error; + } + mdata->clk_frequency = be32_to_cpup(iprop); + } else if (strcasecmp(sprop, "i2s-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "lj-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "lj-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "rj-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "rj-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "ac97-slave") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "ac97-master") == 0) { + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS; + mdata->codec_clk_direction = SND_SOC_CLOCK_IN; + mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else { + dev_err(&pdev->dev, + "unrecognized fsl,mode property '%s'\n", sprop); + ret = -EINVAL; + goto error; + } + + if (!mdata->clk_frequency) { + dev_err(&pdev->dev, "unknown clock frequency\n"); + ret = -EINVAL; + goto error; + } + + /* Find the playback DMA channel to use. */ + mdata->dai[0].platform_name = mdata->platform_name[0]; + ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], + &mdata->dma_channel_id[0], + &mdata->dma_id[0]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n"); + goto error; + } + + /* Find the capture DMA channel to use. */ + mdata->dai[1].platform_name = mdata->platform_name[1]; + ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], + &mdata->dma_channel_id[1], + &mdata->dma_id[1]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n"); + goto error; + } + + /* Initialize our DAI data structure. */ + mdata->dai[0].stream_name = "playback"; + mdata->dai[1].stream_name = "capture"; + mdata->dai[0].name = mdata->dai[0].stream_name; + mdata->dai[1].name = mdata->dai[1].stream_name; + + mdata->card.probe = p1022_ds_machine_probe; + mdata->card.remove = p1022_ds_machine_remove; + mdata->card.name = pdev->name; /* The platform driver name */ + mdata->card.owner = THIS_MODULE; + mdata->card.dev = &pdev->dev; + mdata->card.num_links = 2; + mdata->card.dai_link = mdata->dai; + + /* Register with ASoC */ + ret = snd_soc_register_card(&mdata->card); + if (ret) { + dev_err(&pdev->dev, "could not register card\n"); + goto error; + } + + of_node_put(codec_np); + + return 0; + +error: + kfree(mdata); +error_put: + of_node_put(codec_np); + return ret; +} + +/** + * p1022_ds_remove: remove the platform device + * + * This function is called when the platform device is removed. + */ +static int p1022_ds_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct machine_data *mdata = + container_of(card, struct machine_data, card); + + snd_soc_unregister_card(card); + kfree(mdata); + + return 0; +} + +static struct platform_driver p1022_ds_driver = { + .probe = p1022_ds_probe, + .remove = p1022_ds_remove, + .driver = { + /* + * The name must match 'compatible' property in the device tree, + * in lowercase letters. + */ + .name = "snd-soc-p1022ds", + }, +}; + +/** + * p1022_ds_init: machine driver initialization. + * + * This function is called when this module is loaded. + */ +static int __init p1022_ds_init(void) +{ + struct device_node *guts_np; + struct resource res; + + /* Get the physical address of the global utilities registers */ + guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); + if (of_address_to_resource(guts_np, 0, &res)) { + pr_err("snd-soc-p1022ds: missing/invalid global utils node\n"); + of_node_put(guts_np); + return -EINVAL; + } + guts_phys = res.start; + of_node_put(guts_np); + + return platform_driver_register(&p1022_ds_driver); +} + +/** + * p1022_ds_exit: machine driver exit + * + * This function is called when this driver is unloaded. + */ +static void __exit p1022_ds_exit(void) +{ + platform_driver_unregister(&p1022_ds_driver); +} + +module_init(p1022_ds_init); +module_exit(p1022_ds_exit); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c new file mode 100644 index 000000000..ee2904842 --- /dev/null +++ b/sound/soc/fsl/p1022_rdk.c @@ -0,0 +1,392 @@ +/** + * Freescale P1022RDK ALSA SoC Machine driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2012 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * Note: in order for audio to work correctly, the output controls need + * to be enabled, because they control the clock. So for playback, for + * example: + * + * amixer sset 'Left Output Mixer PCM' on + * amixer sset 'Right Output Mixer PCM' on + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <asm/fsl_guts.h> + +#include "fsl_dma.h" +#include "fsl_ssi.h" +#include "fsl_utils.h" + +/* P1022-specific PMUXCR and DMUXCR bit definitions */ + +#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000 +#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000 + +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00 +#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000 + +#define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */ +#define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */ + +/* + * Set the DMACR register in the GUTS + * + * The DMACR register determines the source of initiated transfers for each + * channel on each DMA controller. Rather than have a bunch of repetitive + * macros for the bit patterns, we just have a function that calculates + * them. + * + * guts: Pointer to GUTS structure + * co: The DMA controller (0 or 1) + * ch: The channel on the DMA controller (0, 1, 2, or 3) + * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx) + */ +static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts, + unsigned int co, unsigned int ch, unsigned int device) +{ + unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch)); + + clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift); +} + +/* There's only one global utilities register */ +static phys_addr_t guts_phys; + +/** + * machine_data: machine-specific ASoC device data + * + * This structure contains data for a single sound platform device on an + * P1022 RDK. Some of the data is taken from the device tree. + */ +struct machine_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; + unsigned int dai_format; + unsigned int codec_clk_direction; + unsigned int cpu_clk_direction; + unsigned int clk_frequency; + unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */ + unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ + char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */ +}; + +/** + * p1022_rdk_machine_probe: initialize the board + * + * This function is used to initialize the board-specific hardware. + * + * Here we program the DMACR and PMUXCR registers. + */ +static int p1022_rdk_machine_probe(struct snd_soc_card *card) +{ + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Enable SSI Tx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK, + CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI); + + /* Enable SSI Rx signal */ + clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK, + CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI); + + /* Enable DMA Channel for SSI */ + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], + CCSR_GUTS_DMUXCR_SSI); + + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], + CCSR_GUTS_DMUXCR_SSI); + + iounmap(guts); + + return 0; +} + +/** + * p1022_rdk_startup: program the board with various hardware parameters + * + * This function takes board-specific information, like clock frequencies + * and serial data formats, and passes that information to the codec and + * transport drivers. + */ +static int p1022_rdk_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct machine_data *mdata = + container_of(rtd->card, struct machine_data, card); + struct device *dev = rtd->card->dev; + int ret = 0; + + /* Tell the codec driver what the serial protocol is. */ + ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format); + if (ret < 0) { + dev_err(dev, "could not set codec driver audio format (ret=%i)\n", + ret); + return ret; + } + + ret = snd_soc_dai_set_pll(rtd->codec_dai, 0, 0, mdata->clk_frequency, + mdata->clk_frequency); + if (ret < 0) { + dev_err(dev, "could not set codec PLL frequency (ret=%i)\n", + ret); + return ret; + } + + return 0; +} + +/** + * p1022_rdk_machine_remove: Remove the sound device + * + * This function is called to remove the sound device for one SSI. We + * de-program the DMACR and PMUXCR register. + */ +static int p1022_rdk_machine_remove(struct snd_soc_card *card) +{ + struct machine_data *mdata = + container_of(card, struct machine_data, card); + struct ccsr_guts __iomem *guts; + + guts = ioremap(guts_phys, sizeof(struct ccsr_guts)); + if (!guts) { + dev_err(card->dev, "could not map global utilities\n"); + return -ENOMEM; + } + + /* Restore the signal routing */ + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK); + clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK); + guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0); + guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0); + + iounmap(guts); + + return 0; +} + +/** + * p1022_rdk_ops: ASoC machine driver operations + */ +static struct snd_soc_ops p1022_rdk_ops = { + .startup = p1022_rdk_startup, +}; + +/** + * p1022_rdk_probe: platform probe function for the machine driver + * + * Although this is a machine driver, the SSI node is the "master" node with + * respect to audio hardware connections. Therefore, we create a new ASoC + * device for each new SSI node that has a codec attached. + */ +static int p1022_rdk_probe(struct platform_device *pdev) +{ + struct device *dev = pdev->dev.parent; + /* ssi_pdev is the platform device for the SSI node that probed us */ + struct platform_device *ssi_pdev = + container_of(dev, struct platform_device, dev); + struct device_node *np = ssi_pdev->dev.of_node; + struct device_node *codec_np = NULL; + struct machine_data *mdata; + const u32 *iprop; + int ret; + + /* Find the codec node for this SSI. */ + codec_np = of_parse_phandle(np, "codec-handle", 0); + if (!codec_np) { + dev_err(dev, "could not find codec node\n"); + return -EINVAL; + } + + mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL); + if (!mdata) { + ret = -ENOMEM; + goto error_put; + } + + mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev); + mdata->dai[0].ops = &p1022_rdk_ops; + + /* ASoC core can match codec with device node */ + mdata->dai[0].codec_of_node = codec_np; + + /* + * We register two DAIs per SSI, one for playback and the other for + * capture. We support codecs that have separate DAIs for both playback + * and capture. + */ + memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link)); + + /* The DAI names from the codec (snd_soc_dai_driver.name) */ + mdata->dai[0].codec_dai_name = "wm8960-hifi"; + mdata->dai[1].codec_dai_name = mdata->dai[0].codec_dai_name; + + /* + * Configure the SSI for I2S slave mode. Older device trees have + * an fsl,mode property, but we ignore that since there's really + * only one way to configure the SSI. + */ + mdata->dai_format = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM; + mdata->codec_clk_direction = SND_SOC_CLOCK_OUT; + mdata->cpu_clk_direction = SND_SOC_CLOCK_IN; + + /* + * In i2s-slave mode, the codec has its own clock source, so we + * need to get the frequency from the device tree and pass it to + * the codec driver. + */ + iprop = of_get_property(codec_np, "clock-frequency", NULL); + if (!iprop || !*iprop) { + dev_err(&pdev->dev, "codec bus-frequency property is missing or invalid\n"); + ret = -EINVAL; + goto error; + } + mdata->clk_frequency = be32_to_cpup(iprop); + + if (!mdata->clk_frequency) { + dev_err(&pdev->dev, "unknown clock frequency\n"); + ret = -EINVAL; + goto error; + } + + /* Find the playback DMA channel to use. */ + mdata->dai[0].platform_name = mdata->platform_name[0]; + ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0], + &mdata->dma_channel_id[0], + &mdata->dma_id[0]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid playback DMA phandle (ret=%i)\n", + ret); + goto error; + } + + /* Find the capture DMA channel to use. */ + mdata->dai[1].platform_name = mdata->platform_name[1]; + ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1], + &mdata->dma_channel_id[1], + &mdata->dma_id[1]); + if (ret) { + dev_err(&pdev->dev, "missing/invalid capture DMA phandle (ret=%i)\n", + ret); + goto error; + } + + /* Initialize our DAI data structure. */ + mdata->dai[0].stream_name = "playback"; + mdata->dai[1].stream_name = "capture"; + mdata->dai[0].name = mdata->dai[0].stream_name; + mdata->dai[1].name = mdata->dai[1].stream_name; + + mdata->card.probe = p1022_rdk_machine_probe; + mdata->card.remove = p1022_rdk_machine_remove; + mdata->card.name = pdev->name; /* The platform driver name */ + mdata->card.owner = THIS_MODULE; + mdata->card.dev = &pdev->dev; + mdata->card.num_links = 2; + mdata->card.dai_link = mdata->dai; + + /* Register with ASoC */ + ret = snd_soc_register_card(&mdata->card); + if (ret) { + dev_err(&pdev->dev, "could not register card (ret=%i)\n", ret); + goto error; + } + + return 0; + +error: + kfree(mdata); +error_put: + of_node_put(codec_np); + return ret; +} + +/** + * p1022_rdk_remove: remove the platform device + * + * This function is called when the platform device is removed. + */ +static int p1022_rdk_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct machine_data *mdata = + container_of(card, struct machine_data, card); + + snd_soc_unregister_card(card); + kfree(mdata); + + return 0; +} + +static struct platform_driver p1022_rdk_driver = { + .probe = p1022_rdk_probe, + .remove = p1022_rdk_remove, + .driver = { + /* + * The name must match 'compatible' property in the device tree, + * in lowercase letters. + */ + .name = "snd-soc-p1022rdk", + }, +}; + +/** + * p1022_rdk_init: machine driver initialization. + * + * This function is called when this module is loaded. + */ +static int __init p1022_rdk_init(void) +{ + struct device_node *guts_np; + struct resource res; + + /* Get the physical address of the global utilities registers */ + guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts"); + if (of_address_to_resource(guts_np, 0, &res)) { + pr_err("snd-soc-p1022rdk: missing/invalid global utils node\n"); + of_node_put(guts_np); + return -EINVAL; + } + guts_phys = res.start; + of_node_put(guts_np); + + return platform_driver_register(&p1022_rdk_driver); +} + +/** + * p1022_rdk_exit: machine driver exit + * + * This function is called when this driver is unloaded. + */ +static void __exit p1022_rdk_exit(void) +{ + platform_driver_unregister(&p1022_rdk_driver); +} + +late_initcall(p1022_rdk_init); +module_exit(p1022_rdk_exit); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale / iVeia P1022 RDK ALSA SoC machine driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c new file mode 100644 index 000000000..ec731223c --- /dev/null +++ b/sound/soc/fsl/pcm030-audio-fabric.c @@ -0,0 +1,137 @@ +/* + * Phytec pcm030 driver for the PSC of the Freescale MPC52xx + * configured as AC97 interface + * + * Copyright 2008 Jon Smirl, Digispeaker + * Author: Jon Smirl <jonsmirl@gmail.com> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> + +#include <sound/soc.h> + +#include "mpc5200_dma.h" + +#define DRV_NAME "pcm030-audio-fabric" + +struct pcm030_audio_data { + struct snd_soc_card *card; + struct platform_device *codec_device; +}; + +static struct snd_soc_dai_link pcm030_fabric_dai[] = { +{ + .name = "AC97.0", + .stream_name = "AC97 Analog", + .codec_dai_name = "wm9712-hifi", + .cpu_dai_name = "mpc5200-psc-ac97.0", + .codec_name = "wm9712-codec", +}, +{ + .name = "AC97.1", + .stream_name = "AC97 IEC958", + .codec_dai_name = "wm9712-aux", + .cpu_dai_name = "mpc5200-psc-ac97.1", + .codec_name = "wm9712-codec", +}, +}; + +static struct snd_soc_card pcm030_card = { + .name = "pcm030", + .owner = THIS_MODULE, + .dai_link = pcm030_fabric_dai, + .num_links = ARRAY_SIZE(pcm030_fabric_dai), +}; + +static int pcm030_fabric_probe(struct platform_device *op) +{ + struct device_node *np = op->dev.of_node; + struct device_node *platform_np; + struct snd_soc_card *card = &pcm030_card; + struct pcm030_audio_data *pdata; + int ret; + int i; + + if (!of_machine_is_compatible("phytec,pcm030")) + return -ENODEV; + + pdata = devm_kzalloc(&op->dev, sizeof(struct pcm030_audio_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + card->dev = &op->dev; + + pdata->card = card; + + platform_np = of_parse_phandle(np, "asoc-platform", 0); + if (!platform_np) { + dev_err(&op->dev, "ac97 not registered\n"); + return -ENODEV; + } + + for (i = 0; i < card->num_links; i++) + card->dai_link[i].platform_of_node = platform_np; + + ret = request_module("snd-soc-wm9712"); + if (ret) + dev_err(&op->dev, "request_module returned: %d\n", ret); + + pdata->codec_device = platform_device_alloc("wm9712-codec", -1); + if (!pdata->codec_device) + dev_err(&op->dev, "platform_device_alloc() failed\n"); + + ret = platform_device_add(pdata->codec_device); + if (ret) + dev_err(&op->dev, "platform_device_add() failed: %d\n", ret); + + ret = snd_soc_register_card(card); + if (ret) + dev_err(&op->dev, "snd_soc_register_card() failed: %d\n", ret); + + platform_set_drvdata(op, pdata); + + return ret; +} + +static int pcm030_fabric_remove(struct platform_device *op) +{ + struct pcm030_audio_data *pdata = platform_get_drvdata(op); + int ret; + + ret = snd_soc_unregister_card(pdata->card); + platform_device_unregister(pdata->codec_device); + + return ret; +} + +static const struct of_device_id pcm030_audio_match[] = { + { .compatible = "phytec,pcm030-audio-fabric", }, + {} +}; +MODULE_DEVICE_TABLE(of, pcm030_audio_match); + +static struct platform_driver pcm030_fabric_driver = { + .probe = pcm030_fabric_probe, + .remove = pcm030_fabric_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = pcm030_audio_match, + }, +}; + +module_platform_driver(pcm030_fabric_driver); + + +MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>"); +MODULE_DESCRIPTION(DRV_NAME ": mpc5200 pcm030 fabric driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/phycore-ac97.c b/sound/soc/fsl/phycore-ac97.c new file mode 100644 index 000000000..ae403c296 --- /dev/null +++ b/sound/soc/fsl/phycore-ac97.c @@ -0,0 +1,125 @@ +/* + * phycore-ac97.c -- SoC audio for imx_phycore in AC97 mode + * + * 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 <asm/mach-types.h> + +#include "imx-audmux.h" + +static struct snd_soc_card imx_phycore; + +static struct snd_soc_ops imx_phycore_hifi_ops = { +}; + +static struct snd_soc_dai_link imx_phycore_dai_ac97[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai_name = "wm9712-hifi", + .codec_name = "wm9712-codec", + .cpu_dai_name = "imx-ssi.0", + .platform_name = "imx-ssi.0", + .ops = &imx_phycore_hifi_ops, + }, +}; + +static struct snd_soc_card imx_phycore = { + .name = "PhyCORE-ac97-audio", + .owner = THIS_MODULE, + .dai_link = imx_phycore_dai_ac97, + .num_links = ARRAY_SIZE(imx_phycore_dai_ac97), +}; + +static struct platform_device *imx_phycore_snd_ac97_device; +static struct platform_device *imx_phycore_snd_device; + +static int __init imx_phycore_init(void) +{ + int ret; + + if (machine_is_pca100()) { + imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, + IMX_AUDMUX_V1_PCR_SYN | /* 4wire mode */ + IMX_AUDMUX_V1_PCR_TFCSEL(3) | + IMX_AUDMUX_V1_PCR_TCLKDIR | /* clock is output */ + IMX_AUDMUX_V1_PCR_RXDSEL(3)); + imx_audmux_v1_configure_port(3, + IMX_AUDMUX_V1_PCR_SYN | /* 4wire mode */ + IMX_AUDMUX_V1_PCR_TFCSEL(0) | + IMX_AUDMUX_V1_PCR_TFSDIR | + IMX_AUDMUX_V1_PCR_RXDSEL(0)); + } else if (machine_is_pcm043()) { + imx_audmux_v2_configure_port(3, + IMX_AUDMUX_V2_PTCR_SYN | /* 4wire mode */ + IMX_AUDMUX_V2_PTCR_TFSEL(0) | + IMX_AUDMUX_V2_PTCR_TFSDIR, + IMX_AUDMUX_V2_PDCR_RXDSEL(0)); + imx_audmux_v2_configure_port(0, + IMX_AUDMUX_V2_PTCR_SYN | /* 4wire mode */ + IMX_AUDMUX_V2_PTCR_TCSEL(3) | + IMX_AUDMUX_V2_PTCR_TCLKDIR, /* clock is output */ + IMX_AUDMUX_V2_PDCR_RXDSEL(3)); + } else { + /* return happy. We might run on a totally different machine */ + return 0; + } + + imx_phycore_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!imx_phycore_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(imx_phycore_snd_ac97_device, &imx_phycore); + ret = platform_device_add(imx_phycore_snd_ac97_device); + if (ret) + goto fail1; + + imx_phycore_snd_device = platform_device_alloc("wm9712-codec", -1); + if (!imx_phycore_snd_device) { + ret = -ENOMEM; + goto fail2; + } + ret = platform_device_add(imx_phycore_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + goto fail3; + } + + return 0; + +fail3: + platform_device_put(imx_phycore_snd_device); +fail2: + platform_device_del(imx_phycore_snd_ac97_device); +fail1: + platform_device_put(imx_phycore_snd_ac97_device); + return ret; +} + +static void __exit imx_phycore_exit(void) +{ + platform_device_unregister(imx_phycore_snd_device); + platform_device_unregister(imx_phycore_snd_ac97_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"); diff --git a/sound/soc/fsl/wm1133-ev1.c b/sound/soc/fsl/wm1133-ev1.c new file mode 100644 index 000000000..b454972dc --- /dev/null +++ b/sound/soc/fsl/wm1133-ev1.c @@ -0,0 +1,292 @@ +/* + * wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS + * + * Copyright (c) 2010 Wolfson Microelectronics plc + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * Based on an earlier driver for the same hardware by Liam Girdwood. + * + * 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/platform_device.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "imx-ssi.h" +#include "../codecs/wm8350.h" +#include "imx-audmux.h" + +/* There is a silicon mic on the board optionally connected via a solder pad + * SP1. Define this to enable it. + */ +#undef USE_SIMIC + +struct _wm8350_audio { + unsigned int channels; + snd_pcm_format_t format; + unsigned int rate; + unsigned int sysclk; + unsigned int bclkdiv; + unsigned int clkdiv; + unsigned int lr_rate; +}; + +/* in order of power consumption per rate (lowest first) */ +static const struct _wm8350_audio wm8350_audio[] = { + /* 16bit mono modes */ + {1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000 >> 1, + WM8350_BCLK_DIV_48, WM8350_DACDIV_3, 16,}, + + /* 16 bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000, + WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000, + WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000, + WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600, + WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600, + WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + {2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200, + WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, + + /* 24bit stereo modes */ + {2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, + {2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200, + WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, +}; + +static int wm1133_ev1_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 *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int i, found = 0; + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int channels = params_channels(params); + + /* find the correct audio parameters */ + for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) { + if (rate == wm8350_audio[i].rate && + format == wm8350_audio[i].format && + channels == wm8350_audio[i].channels) { + found = 1; + break; + } + } + if (!found) + return -EINVAL; + + /* codec FLL input is 14.75 MHz from MCLK */ + snd_soc_dai_set_pll(codec_dai, 0, 0, 14750000, wm8350_audio[i].sysclk); + + /* TODO: The SSI driver should figure this out for us */ + switch (channels) { + case 2: + snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 0); + break; + case 1: + snd_soc_dai_set_tdm_slot(cpu_dai, 0x1, 0x1, 1, 0); + break; + default: + return -EINVAL; + } + + /* set MCLK as the codec system clock for DAC and ADC */ + snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_MCLK, + wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN); + + /* set codec BCLK division for sample rate */ + snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV, + wm8350_audio[i].bclkdiv); + + /* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DACLR_CLKDIV, wm8350_audio[i].lr_rate); + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADCLR_CLKDIV, wm8350_audio[i].lr_rate); + + /* now configure DAC and ADC clocks */ + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv); + + snd_soc_dai_set_clkdiv(codec_dai, + WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv); + + return 0; +} + +static struct snd_soc_ops wm1133_ev1_ops = { + .hw_params = wm1133_ev1_hw_params, +}; + +static const struct snd_soc_dapm_widget wm1133_ev1_widgets[] = { +#ifdef USE_SIMIC + SND_SOC_DAPM_MIC("SiMIC", NULL), +#endif + SND_SOC_DAPM_MIC("Mic1 Jack", NULL), + SND_SOC_DAPM_MIC("Mic2 Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +/* imx32ads soc_card audio map */ +static const struct snd_soc_dapm_route wm1133_ev1_map[] = { + +#ifdef USE_SIMIC + /* SiMIC --> IN1LN (with automatic bias) via SP1 */ + { "IN1LN", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "SiMIC" }, +#endif + + /* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */ + { "IN1LN", NULL, "Mic Bias" }, + { "IN1LP", NULL, "Mic1 Jack" }, + { "Mic Bias", NULL, "Mic1 Jack" }, + + /* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */ + { "IN1RN", NULL, "Mic Bias" }, + { "IN1RP", NULL, "Mic2 Jack" }, + { "Mic Bias", NULL, "Mic2 Jack" }, + + /* Line in Jack --> AUX (L+R) */ + { "IN3R", NULL, "Line In Jack" }, + { "IN3L", NULL, "Line In Jack" }, + + /* Out1 --> Headphone Jack */ + { "Headphone Jack", NULL, "OUT1R" }, + { "Headphone Jack", NULL, "OUT1L" }, + + /* Out1 --> Line Out Jack */ + { "Line Out Jack", NULL, "OUT2R" }, + { "Line Out Jack", NULL, "OUT2L" }, +}; + +static struct snd_soc_jack hp_jack; + +static struct snd_soc_jack_pin hp_jack_pins[] = { + { .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE }, +}; + +static struct snd_soc_jack mic_jack; + +static struct snd_soc_jack_pin mic_jack_pins[] = { + { .pin = "Mic1 Jack", .mask = SND_JACK_MICROPHONE }, + { .pin = "Mic2 Jack", .mask = SND_JACK_MICROPHONE }, +}; + +static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + + /* Headphone jack detection */ + snd_soc_card_jack_new(rtd->card, "Headphone", SND_JACK_HEADPHONE, + &hp_jack, hp_jack_pins, ARRAY_SIZE(hp_jack_pins)); + wm8350_hp_jack_detect(codec, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE); + + /* Microphone jack detection */ + snd_soc_card_jack_new(rtd->card, "Microphone", + SND_JACK_MICROPHONE | SND_JACK_BTN_0, &mic_jack, + mic_jack_pins, ARRAY_SIZE(mic_jack_pins)); + wm8350_mic_jack_detect(codec, &mic_jack, SND_JACK_MICROPHONE, + SND_JACK_BTN_0); + + snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias"); + + return 0; +} + + +static struct snd_soc_dai_link wm1133_ev1_dai = { + .name = "WM1133-EV1", + .stream_name = "Audio", + .cpu_dai_name = "imx-ssi.0", + .codec_dai_name = "wm8350-hifi", + .platform_name = "imx-ssi.0", + .codec_name = "wm8350-codec.0-0x1a", + .init = wm1133_ev1_init, + .ops = &wm1133_ev1_ops, + .symmetric_rates = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, +}; + +static struct snd_soc_card wm1133_ev1 = { + .name = "WM1133-EV1", + .owner = THIS_MODULE, + .dai_link = &wm1133_ev1_dai, + .num_links = 1, + + .dapm_widgets = wm1133_ev1_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm1133_ev1_widgets), + .dapm_routes = wm1133_ev1_map, + .num_dapm_routes = ARRAY_SIZE(wm1133_ev1_map), +}; + +static struct platform_device *wm1133_ev1_snd_device; + +static int __init wm1133_ev1_audio_init(void) +{ + int ret; + unsigned int ptcr, pdcr; + + /* SSI0 mastered by port 5 */ + ptcr = IMX_AUDMUX_V2_PTCR_SYN | + IMX_AUDMUX_V2_PTCR_TFSDIR | + IMX_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) | + IMX_AUDMUX_V2_PTCR_TCLKDIR | + IMX_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, ptcr, pdcr); + + ptcr = IMX_AUDMUX_V2_PTCR_SYN; + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0); + imx_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, ptcr, pdcr); + + wm1133_ev1_snd_device = platform_device_alloc("soc-audio", -1); + if (!wm1133_ev1_snd_device) + return -ENOMEM; + + platform_set_drvdata(wm1133_ev1_snd_device, &wm1133_ev1); + ret = platform_device_add(wm1133_ev1_snd_device); + + if (ret) + platform_device_put(wm1133_ev1_snd_device); + + return ret; +} +module_init(wm1133_ev1_audio_init); + +static void __exit wm1133_ev1_audio_exit(void) +{ + platform_device_unregister(wm1133_ev1_snd_device); +} +module_exit(wm1133_ev1_audio_exit); + +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("Audio for WM1133-EV1 on i.MX31ADS"); +MODULE_LICENSE("GPL"); |