aboutsummaryrefslogtreecommitdiff
path: root/sound/soc/fsl
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/fsl')
-rw-r--r--sound/soc/fsl/Kconfig142
-rw-r--r--sound/soc/fsl/Makefile26
-rw-r--r--sound/soc/fsl/eukrea-tlv320.c156
-rw-r--r--sound/soc/fsl/fsl_dma.c36
-rw-r--r--sound/soc/fsl/fsl_esai.c863
-rw-r--r--sound/soc/fsl/fsl_esai.h354
-rw-r--r--sound/soc/fsl/fsl_sai.c651
-rw-r--r--sound/soc/fsl/fsl_sai.h143
-rw-r--r--sound/soc/fsl/fsl_spdif.c1293
-rw-r--r--sound/soc/fsl/fsl_spdif.h193
-rw-r--r--sound/soc/fsl/fsl_ssi.c1528
-rw-r--r--sound/soc/fsl/fsl_ssi.h122
-rw-r--r--sound/soc/fsl/fsl_ssi_dbg.c163
-rw-r--r--sound/soc/fsl/fsl_utils.c27
-rw-r--r--sound/soc/fsl/fsl_utils.h4
-rw-r--r--sound/soc/fsl/imx-audmux.c122
-rw-r--r--sound/soc/fsl/imx-audmux.h51
-rw-r--r--sound/soc/fsl/imx-mc13783.c58
-rw-r--r--sound/soc/fsl/imx-pcm-dma.c134
-rw-r--r--sound/soc/fsl/imx-pcm-fiq.c175
-rw-r--r--sound/soc/fsl/imx-pcm.c105
-rw-r--r--sound/soc/fsl/imx-pcm.h51
-rw-r--r--sound/soc/fsl/imx-sgtl5000.c74
-rw-r--r--sound/soc/fsl/imx-spdif.c103
-rw-r--r--sound/soc/fsl/imx-ssi.c178
-rw-r--r--sound/soc/fsl/imx-ssi.h16
-rw-r--r--sound/soc/fsl/imx-wm8962.c324
-rw-r--r--sound/soc/fsl/mpc5200_dma.c44
-rw-r--r--sound/soc/fsl/mpc5200_dma.h3
-rw-r--r--sound/soc/fsl/mpc5200_psc_ac97.c36
-rw-r--r--sound/soc/fsl/mpc5200_psc_i2s.c28
-rw-r--r--sound/soc/fsl/mpc8610_hpcd.c37
-rw-r--r--sound/soc/fsl/mx27vis-aic32x4.c50
-rw-r--r--sound/soc/fsl/p1022_ds.c36
-rw-r--r--sound/soc/fsl/p1022_rdk.c393
-rw-r--r--sound/soc/fsl/pcm030-audio-fabric.c105
-rw-r--r--sound/soc/fsl/phycore-ac97.c2
-rw-r--r--sound/soc/fsl/wm1133-ev1.c13
38 files changed, 6507 insertions, 1332 deletions
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index d70133086ac..37933629cbe 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -1,16 +1,78 @@
+menu "SoC Audio for Freescale CPUs"
+
+comment "Common SoC Audio options for Freescale CPUs:"
+
+config SND_SOC_FSL_SAI
+ tristate "Synchronous Audio Interface (SAI) module support"
+ select REGMAP_MMIO
+ 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
+ tristate "Synchronous Serial Interface module support"
+ select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
+ select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && ARCH_MXC
+ 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 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 && ARCH_MXC
+ 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_FSL_UTILS
+ 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
-menuconfig SND_POWERPC_SOC
+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
+ 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
@@ -19,6 +81,8 @@ config SND_MPC52xx_DMA
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
@@ -46,6 +110,20 @@ config SND_SOC_P1022_DS
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
@@ -82,37 +160,21 @@ config SND_MPC52xx_SOC_EFIKA
endif # SND_POWERPC_SOC
-menuconfig SND_IMX_SOC
- tristate "SoC Audio for Freescale i.MX CPUs"
- depends on ARCH_MXC
- help
- Say Y or M if you want to add support for codecs attached to
- the i.MX CPUs.
-
if SND_IMX_SOC
config SND_SOC_IMX_SSI
tristate
-
-config SND_SOC_IMX_PCM
- tristate
+ select SND_SOC_FSL_UTILS
config SND_SOC_IMX_PCM_FIQ
tristate
select FIQ
- select SND_SOC_IMX_PCM
-config SND_SOC_IMX_PCM_DMA
- tristate
- select SND_SOC_DMAENGINE_PCM
- select SND_SOC_IMX_PCM
-
-config SND_SOC_IMX_AUDMUX
- tristate
+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 && EXPERIMENTAL
+ depends on MACH_MX31ADS_WM1133_EV1
select SND_SOC_WM8350
select SND_SOC_IMX_PCM_FIQ
select SND_SOC_IMX_AUDMUX
@@ -146,19 +208,27 @@ config SND_SOC_PHYCORE_AC97
config SND_SOC_EUKREA_TLV320
tristate "Eukrea TLV320"
- depends on MACH_EUKREA_MBIMX27_BASEBOARD \
- || MACH_EUKREA_MBIMXSD25_BASEBOARD \
- || MACH_EUKREA_MBIMXSD35_BASEBOARD \
- || MACH_EUKREA_MBIMXSD51_BASEBOARD
- depends on I2C
- select SND_SOC_TLV320AIC23
- select SND_SOC_IMX_PCM_FIQ
+ 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_SGTL5000
tristate "SoC Audio support for i.MX boards with sgtl5000"
depends on OF && I2C
@@ -166,17 +236,27 @@ config SND_SOC_IMX_SGTL5000
select SND_SOC_IMX_PCM_DMA
select SND_SOC_IMX_AUDMUX
select SND_SOC_FSL_SSI
- select SND_SOC_FSL_UTILS
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_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
endif # SND_IMX_SOC
+
+endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index 5f3cf3f52ea..db254e358c1 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -6,11 +6,22 @@ obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += snd-soc-mpc8610-hpcd.o
snd-soc-p1022-ds-objs := p1022_ds.o
obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o
-# Freescale PowerPC SSI/DMA Platform Support
-snd-soc-fsl-ssi-objs := fsl_ssi.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-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_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
@@ -26,14 +37,11 @@ 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) += snd-soc-imx-pcm.o
-snd-soc-imx-pcm-y := imx-pcm.o
-snd-soc-imx-pcm-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o
-snd-soc-imx-pcm-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.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
@@ -41,6 +49,8 @@ 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-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
@@ -48,4 +58,6 @@ 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_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/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c
index efb9ede0120..eb093d5b85c 100644
--- a/sound/soc/fsl/eukrea-tlv320.c
+++ b/sound/soc/fsl/eukrea-tlv320.c
@@ -15,8 +15,11 @@
*
*/
+#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>
@@ -26,6 +29,7 @@
#include "../codecs/tlv320aic23.h"
#include "imx-ssi.h"
+#include "fsl_ssi.h"
#include "imx-audmux.h"
#define CODEC_CLOCK 12000000
@@ -41,8 +45,10 @@ static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream,
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
- if (ret) {
- pr_err("%s: failed set cpu dai format\n", __func__);
+ /* fsl_ssi lacks the set_fmt ops. */
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(cpu_dai->dev,
+ "Failed to set the cpu dai format.\n");
return ret;
}
@@ -50,22 +56,27 @@ static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream,
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
if (ret) {
- pr_err("%s: failed set codec dai format\n", __func__);
+ dev_err(cpu_dai->dev,
+ "Failed to set the codec format.\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
CODEC_CLOCK, SND_SOC_CLOCK_OUT);
if (ret) {
- pr_err("%s: failed setting codec sysclk\n", __func__);
+ dev_err(cpu_dai->dev,
+ "Failed to set the codec sysclk.\n");
return ret;
}
+
snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);
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");
+ /* 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;
}
@@ -80,27 +91,78 @@ static struct snd_soc_dai_link eukrea_tlv320_dai = {
.name = "tlv320aic23",
.stream_name = "TLV320AIC23",
.codec_dai_name = "tlv320aic23-hifi",
- .platform_name = "imx-fiq-pcm-audio.0",
- .codec_name = "tlv320aic23-codec.0-001a",
- .cpu_dai_name = "imx-ssi.0",
.ops = &eukrea_tlv320_snd_ops,
};
static struct snd_soc_card eukrea_tlv320 = {
- .name = "cpuimx-audio",
.owner = THIS_MODULE,
.dai_link = &eukrea_tlv320_dai,
.num_links = 1,
};
-static struct platform_device *eukrea_tlv320_snd_device;
-
-static int __init eukrea_tlv320_init(void)
+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, *codec_np;
+
+ 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()) {
+ 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 |
@@ -117,8 +179,12 @@ static int __init eukrea_tlv320_init(void)
);
} else if (machine_is_eukrea_cpuimx25sd() ||
machine_is_eukrea_cpuimx35sd() ||
- machine_is_eukrea_cpuimx51sd()) {
- ext_port = machine_is_eukrea_cpuimx25sd() ? 4 : 3;
+ 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 |
@@ -132,33 +198,57 @@ static int __init eukrea_tlv320_init(void)
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)
);
} else {
- /* return happy. We might run on a totally different machine */
- return 0;
+ 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;
+ }
}
- eukrea_tlv320_snd_device = platform_device_alloc("soc-audio", -1);
- if (!eukrea_tlv320_snd_device)
- return -ENOMEM;
-
- platform_set_drvdata(eukrea_tlv320_snd_device, &eukrea_tlv320);
- ret = platform_device_add(eukrea_tlv320_snd_device);
-
- if (ret) {
- printk(KERN_ERR "ASoC: Platform device allocation failed\n");
- platform_device_put(eukrea_tlv320_snd_device);
- }
+ ret = snd_soc_register_card(&eukrea_tlv320);
+err:
+ if (ret)
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ if (np)
+ of_node_put(ssi_np);
return ret;
}
-static void __exit eukrea_tlv320_exit(void)
+static int eukrea_tlv320_remove(struct platform_device *pdev)
{
- platform_device_unregister(eukrea_tlv320_snd_device);
+ snd_soc_unregister_card(&eukrea_tlv320);
+
+ return 0;
}
-module_init(eukrea_tlv320_init);
-module_exit(eukrea_tlv320_exit);
+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",
+ .owner = THIS_MODULE,
+ .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_dma.c b/sound/soc/fsl/fsl_dma.c
index 96bb92dd174..a609aafc994 100644
--- a/sound/soc/fsl/fsl_dma.c
+++ b/sound/soc/fsl/fsl_dma.c
@@ -21,6 +21,8 @@
#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>
@@ -53,10 +55,6 @@
SNDRV_PCM_FMTBIT_S32_BE | \
SNDRV_PCM_FMTBIT_U32_LE | \
SNDRV_PCM_FMTBIT_U32_BE)
-
-#define FSLDMA_PCM_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
- SNDRV_PCM_RATE_CONTINUOUS)
-
struct dma_object {
struct snd_soc_platform_driver dai;
dma_addr_t ssi_stx_phys;
@@ -138,9 +136,6 @@ static const struct snd_pcm_hardware fsl_dma_hardware = {
SNDRV_PCM_INFO_JOINT_DUPLEX |
SNDRV_PCM_INFO_PAUSE,
.formats = FSLDMA_PCM_FORMATS,
- .rates = FSLDMA_PCM_RATES,
- .rate_min = 5512,
- .rate_max = 192000,
.period_bytes_min = 512, /* A reasonable limit */
.period_bytes_max = (u32) -1,
.periods_min = NUM_DMA_LINKS,
@@ -298,14 +293,11 @@ 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;
- static u64 fsl_dma_dmamask = DMA_BIT_MASK(36);
int ret;
- if (!card->dev->dma_mask)
- card->dev->dma_mask = &fsl_dma_dmamask;
-
- if (!card->dev->coherent_dma_mask)
- card->dev->coherent_dma_mask = fsl_dma_dmamask;
+ 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.
@@ -823,12 +815,6 @@ static int fsl_dma_close(struct snd_pcm_substream *substream)
if (dma_private->irq)
free_irq(dma_private->irq, dma_private);
- if (dma_private->ld_buf_phys) {
- dma_unmap_single(dev, dma_private->ld_buf_phys,
- sizeof(dma_private->link),
- DMA_TO_DEVICE);
- }
-
/* Deallocate the fsl_dma_private structure */
dma_free_coherent(dev, sizeof(struct fsl_dma_private),
dma_private, dma_private->ld_buf_phys);
@@ -859,7 +845,7 @@ static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm)
}
/**
- * find_ssi_node -- returns the SSI node that points to his DMA channel node
+ * 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
@@ -900,7 +886,7 @@ static struct snd_pcm_ops fsl_dma_ops = {
.pointer = fsl_dma_pointer,
};
-static int __devinit fsl_soc_dma_probe(struct platform_device *pdev)
+static int fsl_soc_dma_probe(struct platform_device *pdev)
{
struct dma_object *dma;
struct device_node *np = pdev->dev.of_node;
@@ -937,8 +923,8 @@ static int __devinit fsl_soc_dma_probe(struct platform_device *pdev)
dma->dai.pcm_free = fsl_dma_free_dma_buffers;
/* Store the SSI-specific information that we need */
- dma->ssi_stx_phys = res.start + offsetof(struct ccsr_ssi, stx0);
- dma->ssi_srx_phys = res.start + offsetof(struct ccsr_ssi, srx0);
+ 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)
@@ -964,7 +950,7 @@ static int __devinit fsl_soc_dma_probe(struct platform_device *pdev)
return 0;
}
-static int __devexit fsl_soc_dma_remove(struct platform_device *pdev)
+static int fsl_soc_dma_remove(struct platform_device *pdev)
{
struct dma_object *dma = dev_get_drvdata(&pdev->dev);
@@ -989,7 +975,7 @@ static struct platform_driver fsl_soc_dma_driver = {
.of_match_table = fsl_soc_dma_ids,
},
.probe = fsl_soc_dma_probe,
- .remove = __devexit_p(fsl_soc_dma_remove),
+ .remove = fsl_soc_dma_remove,
};
module_platform_driver(fsl_soc_dma_driver);
diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c
new file mode 100644
index 00000000000..d719caf26dc
--- /dev/null
+++ b/sound/soc/fsl/fsl_esai.c
@@ -0,0 +1,863 @@
+/*
+ * 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"
+#include "fsl_utils.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
+ * @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 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;
+
+ 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 bclk, mask, val;
+ int ret;
+
+ bclk = params_rate(params) * esai_priv->slot_width * 2;
+
+ 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(channels) | ESAI_xFCR_TIEN : ESAI_xFCR_RE(channels));
+
+ 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(esai_priv->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;
+
+ 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(channels) : ESAI_xCR_RE(channels));
+ 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,
+ .xlate_tdm_slot_mask = fsl_asoc_xlate_tdm_slot_mask,
+ .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 = {
+ .channels_min = 1,
+ .channels_max = 12,
+ .rates = FSL_ESAI_RATES,
+ .formats = FSL_ESAI_FORMATS,
+ },
+ .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 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;
+ strcpy(esai_priv->name, np->name);
+
+ if (of_property_read_bool(np, "big-endian"))
+ fsl_esai_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG;
+
+ /* 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", np->full_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 size */
+ esai_priv->slot_width = 32;
+
+ /* 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",
+ .owner = THIS_MODULE,
+ .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 00000000000..75e14033e8d
--- /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 + 1) >> 1))) & ESAI_xFCR_TE_MASK)
+#define ESAI_xFCR_RE(x) ((ESAI_xFCR_RE_MASK >> (ESAI_xFCR_RE_WIDTH - ((x + 1) >> 1))) & 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 + 1) >> 1))) & ESAI_xCR_TE_MASK)
+#define ESAI_xCR_RE(x) ((ESAI_xCR_RE_MASK >> (ESAI_xCR_RE_WIDTH - ((x + 1) >> 1))) & 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 4
+#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 00000000000..c5a0e8af822
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai.c
@@ -0,0 +1,651 @@
+/*
+ * 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_TCSR, 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->big_endian_data)
+ 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->big_endian_data)
+ 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 tcsr, rcsr;
+
+ /*
+ * The transmitter bit clock and frame sync are to be
+ * used by both the transmitter and receiver.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC,
+ ~FSL_SAI_CR2_SYNC);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC,
+ FSL_SAI_CR2_SYNC);
+
+ regmap_read(sai->regmap, FSL_SAI_TCSR, &tcsr);
+ regmap_read(sai->regmap, FSL_SAI_RCSR, &rcsr);
+
+ /*
+ * 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:
+ if (!(tcsr & FSL_SAI_CSR_FRDE || rcsr & 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);
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
+ FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE);
+ 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 */
+ if (!(tx ? rcsr & FSL_SAI_CSR_FRDE : tcsr & FSL_SAI_CSR_FRDE)) {
+ 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);
+ }
+ 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);
+
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, 0xffffffff, 0x0);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR, 0xffffffff, 0x0);
+ 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 = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_SAI_FORMATS,
+ },
+ .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 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->big_endian_regs = of_property_read_bool(np, "big-endian-regs");
+ if (sai->big_endian_regs)
+ fsl_sai_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG;
+
+ sai->big_endian_data = of_property_read_bool(np, "big-endian-data");
+
+ 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", np->full_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;
+ }
+
+ 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",
+ .owner = THIS_MODULE,
+ .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 00000000000..0e6c9f595d7
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai.h
@@ -0,0 +1,143 @@
+/*
+ * 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_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 big_endian_regs;
+ bool big_endian_data;
+ bool is_dsp_mode;
+ bool sai_on_imx;
+
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct snd_dmaengine_dai_dma_data dma_params_tx;
+};
+
+#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 00000000000..d7a60614dd2
--- /dev/null
+++ b/sound/soc/fsl/fsl_spdif.c
@@ -0,0 +1,1293 @@
+/*
+ * 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/clk-private.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)
+
+/* 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
+ * @name: driver name
+ */
+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;
+ u16 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;
+
+ /* The name space will be allocated dynamically */
+ char name[0];
+};
+
+
+/* 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");
+
+ if (!spdif_priv->dpll_locked) {
+ /* DPLL unlocked seems no audio stream */
+ 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;
+ 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);
+ mask = STC_TXCLK_ALL_EN_MASK | STC_TXCLK_SRC_MASK | STC_TXCLK_DF_MASK;
+ regmap_update_bits(regmap, REG_SPDIF_STC, mask, stc);
+
+ regmap_update_bits(regmap, REG_SPDIF_STC,
+ STC_SYSCLK_DF_MASK, STC_SYSCLK_DF(sysclk_df));
+
+ 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;
+ int is_playack = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+ u32 intr = is_playack ? INTR_FOR_PLAYBACK : INTR_FOR_CAPTURE;
+ u32 dmaen = is_playack ? SCR_DMA_TX_EN : SCR_DMA_RX_EN;;
+
+ 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 = 0;
+
+ 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);
+ } else {
+ ret = -EAGAIN;
+ }
+ 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 = 0;
+
+ 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);
+ } else {
+ ret = -EAGAIN;
+ }
+ 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;
+ if (srpc_dpll_locked[clksrc] && (phaseconf & SRPC_DPLL_LOCKED)) {
+ /* Get bus clock from system */
+ 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 = spdif_get_rxclk_rate(spdif_priv, SPDIF_DEFAULT_GAINSEL);
+
+ if (spdif_priv->dpll_locked)
+ ucontrol->value.integer.value[0] = rate;
+ else
+ ucontrol->value.integer.value[0] = 0;
+
+ 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 = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSL_SPDIF_RATES_PLAYBACK,
+ .formats = FSL_SPDIF_FORMATS_PLAYBACK,
+ },
+ .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 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 };
+ bool is_sysclk = 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 };
+ 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 (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(struct fsl_spdif_priv) + strlen(np->name) + 1,
+ GFP_KERNEL);
+ if (!spdif_priv)
+ return -ENOMEM;
+
+ strcpy(spdif_priv->name, np->name);
+
+ 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 = spdif_priv->name;
+
+ if (of_property_read_bool(np, "big-endian"))
+ fsl_spdif_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG;
+
+ /* 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", np->full_name);
+ return irq;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0,
+ spdif_priv->name, 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",
+ .owner = THIS_MODULE,
+ .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 00000000000..16fde4b927d
--- /dev/null
+++ b/sound/soc/fsl/fsl_spdif.h
@@ -0,0 +1,193 @@
+/*
+ * 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)
+
+/* 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,
+};
+#define SPDIF_TXRATE_MAX (SPDIF_TXRATE_48000 + 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)
+
+#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
index 4ed2afd4778..9bfef55d77d 100644
--- a/sound/soc/fsl/fsl_ssi.c
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -8,6 +8,26 @@
* 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>
@@ -18,6 +38,8 @@
#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>
@@ -27,29 +49,11 @@
#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"
-#ifdef PPC
-#define read_ssi(addr) in_be32(addr)
-#define write_ssi(val, addr) out_be32(addr, val)
-#define write_ssi_mask(addr, clear, set) clrsetbits_be32(addr, clear, set)
-#elif defined ARM
-#define read_ssi(addr) readl(addr)
-#define write_ssi(val, addr) writel(val, addr)
-/*
- * FIXME: Proper locking should be added at write_ssi_mask caller level
- * to ensure this register read/modify/write sequence is race free.
- */
-static inline void write_ssi_mask(u32 __iomem *addr, u32 clear, u32 set)
-{
- u32 val = readl(addr);
- val = (val & ~clear) | set;
- writel(val, addr);
-}
-#endif
-
/**
* FSLSSI_I2S_RATES: sample rates supported by the I2S
*
@@ -58,8 +62,7 @@ static inline void write_ssi_mask(u32 __iomem *addr, u32 clear, u32 set)
* ALSA that we support all rates and let the codec driver decide what rates
* are really supported.
*/
-#define FSLSSI_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
- SNDRV_PCM_RATE_CONTINUOUS)
+#define FSLSSI_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS
/**
* FSLSSI_I2S_FORMATS: audio formats supported by the SSI
@@ -85,73 +88,177 @@ static inline void write_ssi_mask(u32 __iomem *addr, u32 clear, u32 set)
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE)
#endif
-/* SIER bitflag of interrupts to enable */
-#define SIER_FLAGS (CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE | \
- CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN | \
- CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN | \
- CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE | \
- CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN)
+#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
*
- * @ssi: pointer to the SSI's registers
- * @ssi_phys: physical address of the SSI registers
+ * @reg: Pointer to the regmap registers
* @irq: IRQ of this SSI
- * @first_stream: pointer to the stream that was opened first
- * @second_stream: pointer to second stream
- * @playback: the number of playback streams opened
- * @capture: the number of capture streams opened
- * @cpu_dai: the CPU DAI for this device
- * @dev_attr: the sysfs device attribute structure
- * @stats: SSI statistics
- * @name: name for this device
+ * @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 ccsr_ssi __iomem *ssi;
- dma_addr_t ssi_phys;
+ struct regmap *regs;
unsigned int irq;
- struct snd_pcm_substream *first_stream;
- struct snd_pcm_substream *second_stream;
- unsigned int fifo_depth;
struct snd_soc_dai_driver cpu_dai_drv;
- struct device_attribute dev_attr;
- struct platform_device *pdev;
- bool new_binding;
- bool ssi_on_imx;
+ unsigned int dai_fmt;
+ u8 i2s_mode;
+ bool use_dma;
+ bool use_dual_fifo;
+ unsigned int fifo_depth;
+ struct fsl_ssi_rxtx_reg_val rxtx_reg_val;
+
struct clk *clk;
- struct platform_device *imx_pcm_pdev;
- struct imx_pcm_dma_params dma_params_tx;
- struct imx_pcm_dma_params dma_params_rx;
-
- 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;
-
- char name[1];
+ 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;
+}
+
/**
* fsl_ssi_isr: SSI interrupt handler
*
@@ -167,133 +274,241 @@ struct fsl_ssi_private {
static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
{
struct fsl_ssi_private *ssi_private = dev_id;
- struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
- irqreturn_t ret = IRQ_NONE;
+ struct regmap *regs = ssi_private->regs;
__be32 sisr;
- __be32 sisr2 = 0;
+ __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.
*/
- sisr = read_ssi(&ssi->sisr) & SIER_FLAGS;
+ regmap_read(regs, CCSR_SSI_SISR, &sisr);
- if (sisr & CCSR_SSI_SISR_RFRC) {
- ssi_private->stats.rfrc++;
- sisr2 |= CCSR_SSI_SISR_RFRC;
- ret = IRQ_HANDLED;
- }
+ sisr2 = sisr & ssi_private->soc->sisr_write_mask;
+ /* Clear the bits that we set */
+ if (sisr2)
+ regmap_write(regs, CCSR_SSI_SISR, sisr2);
- if (sisr & CCSR_SSI_SISR_TFRC) {
- ssi_private->stats.tfrc++;
- sisr2 |= CCSR_SSI_SISR_TFRC;
- ret = IRQ_HANDLED;
- }
+ fsl_ssi_dbg_isr(&ssi_private->dbg_stats, sisr);
- if (sisr & CCSR_SSI_SISR_CMDAU) {
- ssi_private->stats.cmdau++;
- ret = IRQ_HANDLED;
- }
+ return IRQ_HANDLED;
+}
- if (sisr & CCSR_SSI_SISR_CMDDU) {
- ssi_private->stats.cmddu++;
- ret = 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);
}
+}
- if (sisr & CCSR_SSI_SISR_RXT) {
- ssi_private->stats.rxt++;
- ret = IRQ_HANDLED;
- }
+/*
+ * 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))))
- if (sisr & CCSR_SSI_SISR_RDR1) {
- ssi_private->stats.rdr1++;
- ret = IRQ_HANDLED;
- }
+/*
+ * 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;
- if (sisr & CCSR_SSI_SISR_RDR0) {
- ssi_private->stats.rdr0++;
- ret = IRQ_HANDLED;
- }
+ regmap_read(regs, CCSR_SSI_SCR, &scr_val);
- if (sisr & CCSR_SSI_SISR_TDE1) {
- ssi_private->stats.tde1++;
- ret = IRQ_HANDLED;
- }
+ nr_active_streams = !!(scr_val & CCSR_SSI_SCR_TE) +
+ !!(scr_val & CCSR_SSI_SCR_RE);
- if (sisr & CCSR_SSI_SISR_TDE0) {
- ssi_private->stats.tde0++;
- ret = IRQ_HANDLED;
- }
+ if (nr_active_streams - 1 > 0)
+ keep_active = 1;
+ else
+ keep_active = 0;
- if (sisr & CCSR_SSI_SISR_ROE1) {
- ssi_private->stats.roe1++;
- sisr2 |= CCSR_SSI_SISR_ROE1;
- ret = IRQ_HANDLED;
- }
+ /* 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 (sisr & CCSR_SSI_SISR_ROE0) {
- ssi_private->stats.roe0++;
- sisr2 |= CCSR_SSI_SISR_ROE0;
- ret = IRQ_HANDLED;
+ /* 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);
}
- if (sisr & CCSR_SSI_SISR_TUE1) {
- ssi_private->stats.tue1++;
- sisr2 |= CCSR_SSI_SISR_TUE1;
- ret = IRQ_HANDLED;
- }
+ /*
+ * 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);
- if (sisr & CCSR_SSI_SISR_TUE0) {
- ssi_private->stats.tue0++;
- sisr2 |= CCSR_SSI_SISR_TUE0;
- ret = IRQ_HANDLED;
+ goto config_done;
}
- if (sisr & CCSR_SSI_SISR_TFS) {
- ssi_private->stats.tfs++;
- ret = IRQ_HANDLED;
- }
+ /*
+ * 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;
- if (sisr & CCSR_SSI_SISR_RFS) {
- ssi_private->stats.rfs++;
- ret = IRQ_HANDLED;
- }
+ /*
+ * 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
+ */
- if (sisr & CCSR_SSI_SISR_TLS) {
- ssi_private->stats.tls++;
- ret = IRQ_HANDLED;
+ /* 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);
}
- if (sisr & CCSR_SSI_SISR_RLS) {
- ssi_private->stats.rls++;
- ret = IRQ_HANDLED;
- }
+config_done:
+ /* Enabling of subunits is done after configuration */
+ if (enable)
+ regmap_update_bits(regs, CCSR_SSI_SCR, vals->scr, vals->scr);
+}
- if (sisr & CCSR_SSI_SISR_RFF1) {
- ssi_private->stats.rff1++;
- ret = IRQ_HANDLED;
- }
- if (sisr & CCSR_SSI_SISR_RFF0) {
- ssi_private->stats.rff0++;
- ret = IRQ_HANDLED;
- }
+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);
+}
- if (sisr & CCSR_SSI_SISR_TFE1) {
- ssi_private->stats.tfe1++;
- ret = IRQ_HANDLED;
+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 (sisr & CCSR_SSI_SISR_TFE0) {
- ssi_private->stats.tfe0++;
- ret = IRQ_HANDLED;
+ 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;
}
- /* Clear the bits that we set */
- if (sisr2)
- write_ssi(sisr2, &ssi->sisr);
+ reg->rx.sier |= FSLSSI_SIER_DBG_RX_FLAGS;
+ reg->tx.sier |= FSLSSI_SIER_DBG_TX_FLAGS;
+}
- return ret;
+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));
}
/**
@@ -310,123 +525,131 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream,
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct fsl_ssi_private *ssi_private =
snd_soc_dai_get_drvdata(rtd->cpu_dai);
- int synchronous = ssi_private->cpu_dai_drv.symmetric_rates;
- /*
- * If this is the first stream opened, then request the IRQ
- * and initialize the SSI registers.
+ /* 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->first_stream) {
- struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ if (ssi_private->use_dual_fifo)
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
- ssi_private->first_stream = substream;
+ return 0;
+}
- /*
- * Section 16.5 of the MPC8610 reference manual says that the
- * SSI needs to be disabled before updating the registers we set
- * here.
- */
- write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0);
+/**
+ * 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);
- /*
- * Program the SSI into I2S Slave Non-Network Synchronous mode.
- * Also enable the transmit and receive FIFO.
- *
- * FIXME: Little-endian samples require a different shift dir
- */
- write_ssi_mask(&ssi->scr,
- CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN,
- CCSR_SSI_SCR_TFR_CLK_DIS | CCSR_SSI_SCR_I2S_MODE_SLAVE
- | (synchronous ? CCSR_SSI_SCR_SYN : 0));
+ /* Don't apply it to any non-baudclk circumstance */
+ if (IS_ERR(ssi_private->baudclk))
+ return -EINVAL;
- write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
- CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS |
- CCSR_SSI_STCR_TSCKP, &ssi->stcr);
+ baudclk_is_used = ssi_private->baudclk_streams & ~(BIT(substream->stream));
- write_ssi(CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 |
- CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS |
- CCSR_SSI_SRCR_RSCKP, &ssi->srcr);
+ /* It should be already enough to divide clock by setting pm alone */
+ psr = 0;
+ div2 = 0;
- /*
- * The DC and PM bits are only used if the SSI is the clock
- * master.
- */
+ factor = (div2 + 1) * (7 * psr + 1) * 2;
- /* Enable the interrupts and DMA requests */
- write_ssi(SIER_FLAGS, &ssi->sier);
+ for (i = 0; i < 255; i++) {
+ /* The bclk rate must be smaller than 1/5 sysclk rate */
+ if (factor * (i + 1) < 5)
+ continue;
- /*
- * 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 program the receive FIFO to notify us if at least two
- * elements (one frame) have been written to the FIFO. We could
- * make this value larger (and maybe we should), but this way
- * data will be written to memory as soon as it's available.
- */
- write_ssi(CCSR_SSI_SFCSR_TFWM0(ssi_private->fifo_depth - 2) |
- CCSR_SSI_SFCSR_RFWM0(ssi_private->fifo_depth - 2),
- &ssi->sfcsr);
+ tmprate = freq * factor * (i + 2);
- /*
- * We keep the SSI disabled because if we enable it, then the
- * DMA controller will start. It's not supposed to start until
- * the SCR.TE (or SCR.RE) bit is set, but it does anyway. The
- * DMA controller will transfer one "BWC" of data (i.e. the
- * amount of data that the MR.BWC bits are set to). The reason
- * this is bad is because at this point, the PCM driver has not
- * finished initializing the DMA controller.
- */
- } else {
- if (synchronous) {
- struct snd_pcm_runtime *first_runtime =
- ssi_private->first_stream->runtime;
- /*
- * This is the second stream open, and we're in
- * synchronous mode, so we need to impose sample
- * sample size constraints. This is because STCCR is
- * used for playback and capture in synchronous mode,
- * so there's no way to specify different word
- * lengths.
- *
- * Note that this can cause a race condition if the
- * second stream is opened before the first stream is
- * fully initialized. We provide some protection by
- * checking to make sure the first stream is
- * initialized, but it's not perfect. ALSA sometimes
- * re-initializes the driver with a different sample
- * rate or size. If the second stream is opened
- * before the first stream has received its final
- * parameters, then the second stream may be
- * constrained to the wrong sample rate or size.
- */
- if (!first_runtime->sample_bits) {
- dev_err(substream->pcm->card->dev,
- "set sample size in %s stream first\n",
- substream->stream ==
- SNDRV_PCM_STREAM_PLAYBACK
- ? "capture" : "playback");
- return -EAGAIN;
- }
-
- snd_pcm_hw_constraint_minmax(substream->runtime,
- SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
- first_runtime->sample_bits,
- first_runtime->sample_bits);
+ if (baudclk_is_used)
+ clkrate = clk_get_rate(ssi_private->baudclk);
+ else
+ clkrate = clk_round_rate(ssi_private->baudclk, tmprate);
+
+ do_div(clkrate, factor);
+ afreq = (u32)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;
}
- ssi_private->second_stream = substream;
+ /* We are lucky */
+ if (savesub == 0)
+ break;
}
- if (ssi_private->ssi_on_imx)
- snd_soc_dai_set_dma_data(dai, substream,
- (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
- &ssi_private->dma_params_tx :
- &ssi_private->dma_params_rx);
+ /* 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;
}
@@ -448,11 +671,17 @@ 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 ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ 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 enabled = read_ssi(&ssi->scr) & CCSR_SSI_SCR_SSIEN;
+ 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,
@@ -461,6 +690,21 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
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);
+ }
+ }
+
/*
* FIXME: The documentation says that SxCCR[WL] should not be
* modified while the SSI is enabled. The only time this can
@@ -474,9 +718,241 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
/* In synchronous mode, the SSI uses STCCR for capture */
if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ||
ssi_private->cpu_dai_drv.symmetric_rates)
- write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl);
+ regmap_update_bits(regs, CCSR_SSI_STCCR, CCSR_SSI_SxCCR_WL_MASK,
+ wl);
else
- write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl);
+ regmap_update_bits(regs, CCSR_SSI_SRCCR, CCSR_SSI_SxCCR_WL_MASK,
+ wl);
+
+ if (!fsl_ssi_is_ac97(ssi_private))
+ regmap_update_bits(regs, CCSR_SSI_SCR,
+ CCSR_SSI_SCR_NET | CCSR_SSI_SCR_I2S_MODE_MASK,
+ channels == 1 ? 0 : ssi_private->i2s_mode);
+
+ 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 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(&ssi_private->pdev->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_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;
+ 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(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;
}
@@ -495,78 +971,74 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
- struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
+ 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)
- write_ssi_mask(&ssi->scr, 0,
- CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE);
+ fsl_ssi_tx_config(ssi_private, true);
else
- write_ssi_mask(&ssi->scr, 0,
- CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE);
+ 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)
- write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_TE, 0);
+ fsl_ssi_tx_config(ssi_private, false);
else
- write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_RE, 0);
+ 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;
}
-/**
- * fsl_ssi_shutdown: shutdown the SSI
- *
- * Shutdown the SSI if there are no other substreams open.
- */
-static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
+static int fsl_ssi_dai_probe(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 fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(dai);
- if (ssi_private->first_stream == substream)
- ssi_private->first_stream = ssi_private->second_stream;
-
- ssi_private->second_stream = NULL;
-
- /*
- * If this is the last active substream, disable the SSI.
- */
- if (!ssi_private->first_stream) {
- struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
-
- write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0);
+ 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,
.hw_params = fsl_ssi_hw_params,
- .shutdown = fsl_ssi_shutdown,
+ .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 = {
- /* The SSI does not support monaural audio. */
- .channels_min = 2,
+ .channels_min = 1,
.channels_max = 2,
.rates = FSLSSI_I2S_RATES,
.formats = FSLSSI_I2S_FORMATS,
},
.capture = {
- .channels_min = 2,
+ .channels_min = 1,
.channels_max = 2,
.rates = FSLSSI_I2S_RATES,
.formats = FSLSSI_I2S_FORMATS,
@@ -574,56 +1046,81 @@ static struct snd_soc_dai_driver fsl_ssi_dai_template = {
.ops = &fsl_ssi_dai_ops,
};
-/* 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 (SIER_FLAGS & CCSR_SSI_SIER_##flag) \
- length += sprintf(buf + length, #name "=%u\n", \
- ssi_private->stats.name); \
- } while (0)
+static const struct snd_soc_component_driver fsl_ssi_component = {
+ .name = "fsl-ssi",
+};
+
+static struct snd_soc_dai_driver fsl_ssi_ac97_dai = {
+ .ac97_control = 1,
+ .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,
+};
-/**
- * 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 ssize_t fsl_sysfs_ssi_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+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 fsl_ssi_private *ssi_private =
- container_of(attr, struct fsl_ssi_private, dev_attr);
- ssize_t length = 0;
-
- 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 length;
+ 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, &reg_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
*/
@@ -639,15 +1136,104 @@ static void make_lowercase(char *s)
}
}
-static int __devinit fsl_ssi_probe(struct platform_device *pdev)
+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;
+
+ 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;
+ }
+
+ 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:
+ 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);
+ 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_attribute *dev_attr = NULL;
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
@@ -657,61 +1243,78 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev)
if (!of_device_is_available(np))
return -ENODEV;
- /* We only support the SSI in "I2S Slave" mode */
- sprop = of_get_property(np, "fsl,mode", NULL);
- if (!sprop || strcmp(sprop, "i2s-slave")) {
- dev_notice(&pdev->dev, "mode %s is unsupported\n", sprop);
- return -ENODEV;
- }
+ of_id = of_match_device(fsl_ssi_ids, &pdev->dev);
+ if (!of_id || !of_id->data)
+ return -EINVAL;
- /* The DAI name is the last part of the full name of the node. */
- p = strrchr(np->full_name, '/') + 1;
- ssi_private = kzalloc(sizeof(struct fsl_ssi_private) + strlen(p),
- GFP_KERNEL);
+ 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;
}
- strcpy(ssi_private->name, p);
+ 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;
+ else if (!strcmp(sprop, "i2s-slave"))
+ ssi_private->dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_CBM_CFM;
+ }
+
+ ssi_private->use_dma = !of_property_read_bool(np,
+ "fsl,fiq-stream-filter");
- /* 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 = ssi_private->name;
+ 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);
/* Get the addresses and IRQ */
ret = of_address_to_resource(np, 0, &res);
if (ret) {
dev_err(&pdev->dev, "could not determine device resources\n");
- goto error_kmalloc;
+ return ret;
}
- ssi_private->ssi = of_iomap(np, 0);
- if (!ssi_private->ssi) {
+ ssi_private->ssi_phys = res.start;
+
+ iomem = devm_ioremap(&pdev->dev, res.start, resource_size(&res));
+ if (!iomem) {
dev_err(&pdev->dev, "could not map device resources\n");
- ret = -ENOMEM;
- goto error_kmalloc;
+ return -ENOMEM;
}
- ssi_private->ssi_phys = res.start;
- ssi_private->irq = irq_of_parse_and_map(np, 0);
- if (ssi_private->irq == NO_IRQ) {
- dev_err(&pdev->dev, "no irq for node %s\n", np->full_name);
- ret = -ENXIO;
- goto error_iomap;
+ ssi_private->regs = devm_regmap_init_mmio(&pdev->dev, 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);
}
- /* The 'name' should not have any slashes in it. */
- ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0, ssi_private->name,
- ssi_private);
- if (ret < 0) {
- dev_err(&pdev->dev, "could not claim irq %u\n", ssi_private->irq);
- goto error_irqmap;
+ ssi_private->irq = irq_of_parse_and_map(np, 0);
+ if (!ssi_private->irq) {
+ dev_err(&pdev->dev, "no irq for node %s\n", np->full_name);
+ return -ENXIO;
}
/* Are the RX and the TX clocks locked? */
- if (!of_find_property(np, "fsl,ssi-asynchronous", NULL))
+ 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);
@@ -721,92 +1324,43 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev)
/* Older 8610 DTs didn't have the fifo-depth property */
ssi_private->fifo_depth = 8;
- if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx21-ssi")) {
- u32 dma_events[2];
- ssi_private->ssi_on_imx = true;
-
- ssi_private->clk = 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);
- goto error_irq;
- }
- clk_prepare_enable(ssi_private->clk);
-
- /*
- * We have burstsize be "fifo_depth - 2" to match the SSI
- * watermark setting in fsl_ssi_startup().
- */
- ssi_private->dma_params_tx.burstsize =
- ssi_private->fifo_depth - 2;
- ssi_private->dma_params_rx.burstsize =
- ssi_private->fifo_depth - 2;
- ssi_private->dma_params_tx.dma_addr =
- ssi_private->ssi_phys + offsetof(struct ccsr_ssi, stx0);
- ssi_private->dma_params_rx.dma_addr =
- ssi_private->ssi_phys + offsetof(struct ccsr_ssi, srx0);
- /*
- * TODO: This is a temporary solution and should be changed
- * to use generic DMA binding later when the helplers get in.
- */
- ret = of_property_read_u32_array(pdev->dev.of_node,
- "fsl,ssi-dma-events", dma_events, 2);
- if (ret) {
- dev_err(&pdev->dev, "could not get dma events\n");
- goto error_clk;
- }
- ssi_private->dma_params_tx.dma = dma_events[0];
- ssi_private->dma_params_rx.dma = dma_events[1];
-
- ssi_private->dma_params_tx.shared_peripheral =
- of_device_is_compatible(of_get_parent(np),
- "fsl,spba-bus");
- ssi_private->dma_params_rx.shared_peripheral =
- ssi_private->dma_params_tx.shared_peripheral;
- }
-
- /* Initialize the the device_attribute structure */
- dev_attr = &ssi_private->dev_attr;
- sysfs_attr_init(&dev_attr->attr);
- dev_attr->attr.name = "statistics";
- dev_attr->attr.mode = S_IRUGO;
- dev_attr->show = fsl_sysfs_ssi_show;
+ dev_set_drvdata(&pdev->dev, ssi_private);
- ret = device_create_file(&pdev->dev, dev_attr);
- if (ret) {
- dev_err(&pdev->dev, "could not create sysfs %s file\n",
- ssi_private->dev_attr.attr.name);
- goto error_irq;
+ if (ssi_private->soc->imx) {
+ ret = fsl_ssi_imx_probe(pdev, ssi_private, iomem);
+ if (ret)
+ goto error_irqmap;
}
- /* Register with ASoC */
- dev_set_drvdata(&pdev->dev, ssi_private);
-
- ret = snd_soc_register_dai(&pdev->dev, &ssi_private->cpu_dai_drv);
+ ret = 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_dev;
+ goto error_asoc_register;
}
- if (ssi_private->ssi_on_imx) {
- ssi_private->imx_pcm_pdev =
- platform_device_register_simple("imx-pcm-audio",
- -1, NULL, 0);
- if (IS_ERR(ssi_private->imx_pcm_pdev)) {
- ret = PTR_ERR(ssi_private->imx_pcm_pdev);
- goto error_dev;
+ 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_irq;
}
}
+ 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)) {
- ssi_private->new_binding = true;
+ 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
@@ -826,38 +1380,28 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev)
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_dai;
+ goto error_sound_card;
}
done:
- return 0;
-
-error_dai:
- if (ssi_private->ssi_on_imx)
- platform_device_unregister(ssi_private->imx_pcm_pdev);
- snd_soc_unregister_dai(&pdev->dev);
+ if (ssi_private->dai_fmt)
+ _fsl_ssi_set_dai_fmt(ssi_private, ssi_private->dai_fmt);
-error_dev:
- dev_set_drvdata(&pdev->dev, NULL);
- device_remove_file(&pdev->dev, dev_attr);
+ return 0;
-error_clk:
- if (ssi_private->ssi_on_imx) {
- clk_disable_unprepare(ssi_private->clk);
- clk_put(ssi_private->clk);
- }
+error_sound_card:
+ fsl_ssi_debugfs_remove(&ssi_private->dbg_stats);
error_irq:
- free_irq(ssi_private->irq, ssi_private);
-
-error_irqmap:
- irq_dispose_mapping(ssi_private->irq);
+ snd_soc_unregister_component(&pdev->dev);
-error_iomap:
- iounmap(ssi_private->ssi);
+error_asoc_register:
+ if (ssi_private->soc->imx)
+ fsl_ssi_imx_clean(pdev, ssi_private);
-error_kmalloc:
- kfree(ssi_private);
+error_irqmap:
+ if (ssi_private->use_dma)
+ irq_dispose_mapping(ssi_private->irq);
return ret;
}
@@ -866,32 +1410,21 @@ static int fsl_ssi_remove(struct platform_device *pdev)
{
struct fsl_ssi_private *ssi_private = dev_get_drvdata(&pdev->dev);
- if (!ssi_private->new_binding)
+ fsl_ssi_debugfs_remove(&ssi_private->dbg_stats);
+
+ if (ssi_private->pdev)
platform_device_unregister(ssi_private->pdev);
- if (ssi_private->ssi_on_imx) {
- platform_device_unregister(ssi_private->imx_pcm_pdev);
- clk_disable_unprepare(ssi_private->clk);
- clk_put(ssi_private->clk);
- }
- snd_soc_unregister_dai(&pdev->dev);
- device_remove_file(&pdev->dev, &ssi_private->dev_attr);
+ snd_soc_unregister_component(&pdev->dev);
- free_irq(ssi_private->irq, ssi_private);
- irq_dispose_mapping(ssi_private->irq);
+ if (ssi_private->soc->imx)
+ fsl_ssi_imx_clean(pdev, ssi_private);
- kfree(ssi_private);
- dev_set_drvdata(&pdev->dev, NULL);
+ if (ssi_private->use_dma)
+ irq_dispose_mapping(ssi_private->irq);
return 0;
}
-static const struct of_device_id fsl_ssi_ids[] = {
- { .compatible = "fsl,mpc8610-ssi", },
- { .compatible = "fsl,imx21-ssi", },
- {}
-};
-MODULE_DEVICE_TABLE(of, fsl_ssi_ids);
-
static struct platform_driver fsl_ssi_driver = {
.driver = {
.name = "fsl-ssi-dai",
@@ -904,6 +1437,7 @@ static struct platform_driver fsl_ssi_driver = {
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
index 217300029b5..506510540d0 100644
--- a/sound/soc/fsl/fsl_ssi.h
+++ b/sound/soc/fsl/fsl_ssi.h
@@ -12,33 +12,32 @@
#ifndef _MPC8610_I2S_H
#define _MPC8610_I2S_H
-/* SSI Register Map */
-struct ccsr_ssi {
- __be32 stx0; /* 0x.0000 - SSI Transmit Data Register 0 */
- __be32 stx1; /* 0x.0004 - SSI Transmit Data Register 1 */
- __be32 srx0; /* 0x.0008 - SSI Receive Data Register 0 */
- __be32 srx1; /* 0x.000C - SSI Receive Data Register 1 */
- __be32 scr; /* 0x.0010 - SSI Control Register */
- __be32 sisr; /* 0x.0014 - SSI Interrupt Status Register Mixed */
- __be32 sier; /* 0x.0018 - SSI Interrupt Enable Register */
- __be32 stcr; /* 0x.001C - SSI Transmit Configuration Register */
- __be32 srcr; /* 0x.0020 - SSI Receive Configuration Register */
- __be32 stccr; /* 0x.0024 - SSI Transmit Clock Control Register */
- __be32 srccr; /* 0x.0028 - SSI Receive Clock Control Register */
- __be32 sfcsr; /* 0x.002C - SSI FIFO Control/Status Register */
- __be32 str; /* 0x.0030 - SSI Test Register */
- __be32 sor; /* 0x.0034 - SSI Option Register */
- __be32 sacnt; /* 0x.0038 - SSI AC97 Control Register */
- __be32 sacadd; /* 0x.003C - SSI AC97 Command Address Register */
- __be32 sacdat; /* 0x.0040 - SSI AC97 Command Data Register */
- __be32 satag; /* 0x.0044 - SSI AC97 Tag Register */
- __be32 stmsk; /* 0x.0048 - SSI Transmit Time Slot Mask Register */
- __be32 srmsk; /* 0x.004C - SSI Receive Time Slot Mask Register */
- __be32 saccst; /* 0x.0050 - SSI AC97 Channel Status Register */
- __be32 saccen; /* 0x.0054 - SSI AC97 Channel Enable Register */
- __be32 saccdis; /* 0x.0058 - SSI AC97 Channel Disable Register */
-};
+/* 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
@@ -125,7 +124,9 @@ struct ccsr_ssi {
#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
@@ -196,5 +197,72 @@ struct ccsr_ssi {
#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT)
#define CCSR_SSI_SOR_SYNRST 0x00000001
-#endif
+#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 00000000000..5469ffbc025
--- /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
index b9e42b503a3..2ac7755da87 100644
--- a/sound/soc/fsl/fsl_utils.c
+++ b/sound/soc/fsl/fsl_utils.c
@@ -86,6 +86,33 @@ int fsl_asoc_get_dma_channel(struct device_node *ssi_np,
}
EXPORT_SYMBOL(fsl_asoc_get_dma_channel);
+/**
+ * fsl_asoc_xlate_tdm_slot_mask - generate TDM slot TX/RX mask.
+ *
+ * @slots: Number of slots in use.
+ * @tx_mask: bitmask representing active TX slots.
+ * @rx_mask: bitmask representing active RX slots.
+ *
+ * This function used to generate the TDM slot TX/RX mask. And the TX/RX
+ * mask will use a 0 bit for an active slot as default, and the default
+ * active bits are at the LSB of the mask value.
+ */
+int fsl_asoc_xlate_tdm_slot_mask(unsigned int slots,
+ unsigned int *tx_mask,
+ unsigned int *rx_mask)
+{
+ if (!slots)
+ return -EINVAL;
+
+ if (tx_mask)
+ *tx_mask = ~((1 << slots) - 1);
+ if (rx_mask)
+ *rx_mask = ~((1 << slots) - 1);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fsl_asoc_xlate_tdm_slot_mask);
+
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
index b2951126527..df535db4031 100644
--- a/sound/soc/fsl/fsl_utils.h
+++ b/sound/soc/fsl/fsl_utils.h
@@ -22,5 +22,7 @@ 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);
-
+int fsl_asoc_xlate_tdm_slot_mask(unsigned int slots,
+ unsigned int *tx_mask,
+ unsigned int *rx_mask);
#endif /* _FSL_UTILS_H */
diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c
index 080327414c6..267717aa96c 100644
--- a/sound/soc/fsl/imx-audmux.c
+++ b/sound/soc/fsl/imx-audmux.c
@@ -26,7 +26,6 @@
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
-#include <linux/pinctrl/consumer.h>
#include "imx-audmux.h"
@@ -67,18 +66,15 @@ static ssize_t audmux_read_file(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
ssize_t ret;
- char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ char *buf;
int port = (int)file->private_data;
u32 pdcr, ptcr;
- if (!buf)
- return -ENOMEM;
-
- if (!audmux_base)
- return -ENOSYS;
-
- if (audmux_clk)
- clk_prepare_enable(audmux_clk);
+ 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));
@@ -86,6 +82,10 @@ static ssize_t audmux_read_file(struct file *file, char __user *user_buf,
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);
@@ -145,7 +145,7 @@ static const struct file_operations audmux_debugfs_fops = {
.llseek = default_llseek,
};
-static void __init audmux_debugfs_init(void)
+static void audmux_debugfs_init(void)
{
int i;
char buf[20];
@@ -156,7 +156,7 @@ static void __init audmux_debugfs_init(void)
return;
}
- for (i = 0; i < MX31_AUDMUX_PORT6_SSI_PINS_6 + 1; i++) {
+ for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) {
snprintf(buf, sizeof(buf), "ssi%d", i);
if (!debugfs_create_file(buf, 0444, audmux_debugfs_root,
(void *)i, &audmux_debugfs_fops))
@@ -165,7 +165,7 @@ static void __init audmux_debugfs_init(void)
}
}
-static void __devexit audmux_debugfs_remove(void)
+static void audmux_debugfs_remove(void)
{
debugfs_remove_recursive(audmux_debugfs_root);
}
@@ -179,7 +179,7 @@ static inline void audmux_debugfs_remove(void)
}
#endif
-enum imx_audmux_type {
+static enum imx_audmux_type {
IMX21_AUDMUX,
IMX31_AUDMUX,
} audmux_type;
@@ -228,14 +228,19 @@ 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)
- clk_prepare_enable(audmux_clk);
+ 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));
@@ -247,25 +252,78 @@ int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr,
}
EXPORT_SYMBOL_GPL(imx_audmux_v2_configure_port);
-static int __devinit imx_audmux_probe(struct platform_device *pdev)
+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;
- struct pinctrl *pinctrl;
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_request_and_ioremap(&pdev->dev, res);
- if (!audmux_base)
- return -EADDRNOTAVAIL;
-
- pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
- if (IS_ERR(pinctrl)) {
- dev_err(&pdev->dev, "setup pinctrl failed!");
- return PTR_ERR(pinctrl);
- }
+ audmux_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(audmux_base))
+ return PTR_ERR(audmux_base);
- audmux_clk = clk_get(&pdev->dev, "audmux");
+ 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));
@@ -278,21 +336,23 @@ static int __devinit imx_audmux_probe(struct platform_device *pdev)
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 __devexit imx_audmux_remove(struct platform_device *pdev)
+static int imx_audmux_remove(struct platform_device *pdev)
{
if (audmux_type == IMX31_AUDMUX)
audmux_debugfs_remove();
- clk_put(audmux_clk);
return 0;
}
static struct platform_driver imx_audmux_driver = {
.probe = imx_audmux_probe,
- .remove = __devexit_p(imx_audmux_remove),
+ .remove = imx_audmux_remove,
.id_table = imx_audmux_ids,
.driver = {
.name = DRIVER_NAME,
diff --git a/sound/soc/fsl/imx-audmux.h b/sound/soc/fsl/imx-audmux.h
index 04ebbab8d7b..38a4209af7c 100644
--- a/sound/soc/fsl/imx-audmux.h
+++ b/sound/soc/fsl/imx-audmux.h
@@ -1,56 +1,7 @@
#ifndef __IMX_AUDMUX_H
#define __IMX_AUDMUX_H
-#define MX27_AUDMUX_HPCR1_SSI0 0
-#define MX27_AUDMUX_HPCR2_SSI1 1
-#define MX27_AUDMUX_HPCR3_SSI_PINS_4 2
-#define MX27_AUDMUX_PPCR1_SSI_PINS_1 3
-#define MX27_AUDMUX_PPCR2_SSI_PINS_2 4
-#define MX27_AUDMUX_PPCR3_SSI_PINS_3 5
-
-#define MX31_AUDMUX_PORT1_SSI0 0
-#define MX31_AUDMUX_PORT2_SSI1 1
-#define MX31_AUDMUX_PORT3_SSI_PINS_3 2
-#define MX31_AUDMUX_PORT4_SSI_PINS_4 3
-#define MX31_AUDMUX_PORT5_SSI_PINS_5 4
-#define MX31_AUDMUX_PORT6_SSI_PINS_6 5
-
-#define MX51_AUDMUX_PORT1_SSI0 0
-#define MX51_AUDMUX_PORT2_SSI1 1
-#define MX51_AUDMUX_PORT3 2
-#define MX51_AUDMUX_PORT4 3
-#define MX51_AUDMUX_PORT5 4
-#define MX51_AUDMUX_PORT6 5
-#define MX51_AUDMUX_PORT7 6
-
-/* Register definitions for the i.MX21/27 Digital Audio Multiplexer */
-#define IMX_AUDMUX_V1_PCR_INMMASK(x) ((x) & 0xff)
-#define IMX_AUDMUX_V1_PCR_INMEN (1 << 8)
-#define IMX_AUDMUX_V1_PCR_TXRXEN (1 << 10)
-#define IMX_AUDMUX_V1_PCR_SYN (1 << 12)
-#define IMX_AUDMUX_V1_PCR_RXDSEL(x) (((x) & 0x7) << 13)
-#define IMX_AUDMUX_V1_PCR_RFCSEL(x) (((x) & 0xf) << 20)
-#define IMX_AUDMUX_V1_PCR_RCLKDIR (1 << 24)
-#define IMX_AUDMUX_V1_PCR_RFSDIR (1 << 25)
-#define IMX_AUDMUX_V1_PCR_TFCSEL(x) (((x) & 0xf) << 26)
-#define IMX_AUDMUX_V1_PCR_TCLKDIR (1 << 30)
-#define IMX_AUDMUX_V1_PCR_TFSDIR (1 << 31)
-
-/* Register definitions for the i.MX25/31/35/51 Digital Audio Multiplexer */
-#define IMX_AUDMUX_V2_PTCR_TFSDIR (1 << 31)
-#define IMX_AUDMUX_V2_PTCR_TFSEL(x) (((x) & 0xf) << 27)
-#define IMX_AUDMUX_V2_PTCR_TCLKDIR (1 << 26)
-#define IMX_AUDMUX_V2_PTCR_TCSEL(x) (((x) & 0xf) << 22)
-#define IMX_AUDMUX_V2_PTCR_RFSDIR (1 << 21)
-#define IMX_AUDMUX_V2_PTCR_RFSEL(x) (((x) & 0xf) << 17)
-#define IMX_AUDMUX_V2_PTCR_RCLKDIR (1 << 16)
-#define IMX_AUDMUX_V2_PTCR_RCSEL(x) (((x) & 0xf) << 12)
-#define IMX_AUDMUX_V2_PTCR_SYN (1 << 11)
-
-#define IMX_AUDMUX_V2_PDCR_RXDSEL(x) (((x) & 0x7) << 13)
-#define IMX_AUDMUX_V2_PDCR_TXRXEN (1 << 12)
-#define IMX_AUDMUX_V2_PDCR_MODE(x) (((x) & 0x3) << 8)
-#define IMX_AUDMUX_V2_PDCR_INMMASK(x) ((x) & 0xff)
+#include <dt-bindings/sound/fsl-imx-audmux.h>
int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr);
diff --git a/sound/soc/fsl/imx-mc13783.c b/sound/soc/fsl/imx-mc13783.c
index f59c3494366..a2fd7321b5a 100644
--- a/sound/soc/fsl/imx-mc13783.c
+++ b/sound/soc/fsl/imx-mc13783.c
@@ -64,7 +64,7 @@ static struct snd_soc_dai_link imx_mc13783_dai_mc13783[] = {
.codec_dai_name = "mc13783-hifi",
.codec_name = "mc13783-codec",
.cpu_dai_name = "imx-ssi.0",
- .platform_name = "imx-pcm-audio.0",
+ .platform_name = "imx-ssi.0",
.ops = &imx_mc13783_hifi_ops,
.symmetric_rates = 1,
.dai_fmt = FMT_SSI,
@@ -90,6 +90,7 @@ static const struct snd_soc_dapm_route imx_mc13783_routes[] = {
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,
@@ -98,7 +99,7 @@ static struct snd_soc_card imx_mc13783 = {
.num_dapm_routes = ARRAY_SIZE(imx_mc13783_routes),
};
-static int __devinit imx_mc13783_probe(struct platform_device *pdev)
+static int imx_mc13783_probe(struct platform_device *pdev)
{
int ret;
@@ -111,27 +112,44 @@ static int __devinit imx_mc13783_probe(struct platform_device *pdev)
return ret;
}
- 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));
+ 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 __devexit imx_mc13783_remove(struct platform_device *pdev)
+static int imx_mc13783_remove(struct platform_device *pdev)
{
snd_soc_unregister_card(&imx_mc13783);
@@ -144,7 +162,7 @@ static struct platform_driver imx_mc13783_audio_driver = {
.owner = THIS_MODULE,
},
.probe = imx_mc13783_probe,
- .remove = __devexit_p(imx_mc13783_remove)
+ .remove = imx_mc13783_remove
};
module_platform_driver(imx_mc13783_audio_driver);
diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c
index f3c0a5ef35c..0db94f492e9 100644
--- a/sound/soc/fsl/imx-pcm-dma.c
+++ b/sound/soc/fsl/imx-pcm-dma.c
@@ -11,27 +11,16 @@
* 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 <linux/dmaengine.h>
#include <linux/types.h>
+#include <linux/module.h>
#include <sound/core.h>
-#include <sound/initval.h>
#include <sound/pcm.h>
-#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
-#include <mach/dma.h>
-
#include "imx-pcm.h"
static bool filter(struct dma_chan *chan, void *param)
@@ -44,51 +33,13 @@ static bool filter(struct dma_chan *chan, void *param)
return true;
}
-static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
- struct imx_pcm_dma_params *dma_params;
- struct dma_slave_config slave_config;
- int ret;
-
- dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
-
- ret = snd_hwparams_to_dma_slave_config(substream, params, &slave_config);
- if (ret)
- return ret;
-
- slave_config.device_fc = false;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- slave_config.dst_addr = dma_params->dma_addr;
- slave_config.dst_maxburst = dma_params->burstsize;
- } else {
- slave_config.src_addr = dma_params->dma_addr;
- slave_config.src_maxburst = dma_params->burstsize;
- }
-
- ret = dmaengine_slave_config(chan, &slave_config);
- if (ret)
- return ret;
-
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
-
- return 0;
-}
-
-static struct snd_pcm_hardware snd_imx_hardware = {
+static const struct snd_pcm_hardware imx_pcm_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- .rate_min = 8000,
- .channels_min = 2,
- .channels_max = 2,
.buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
.period_bytes_min = 128,
.period_bytes_max = 65535, /* Limited by SDMA engine */
@@ -97,80 +48,19 @@ static struct snd_pcm_hardware snd_imx_hardware = {
.fifo_size = 0,
};
-static int snd_imx_open(struct snd_pcm_substream *substream)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct imx_pcm_dma_params *dma_params;
- struct imx_dma_data *dma_data;
- int ret;
-
- snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
-
- dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
-
- dma_data = kzalloc(sizeof(*dma_data), GFP_KERNEL);
- dma_data->peripheral_type = dma_params->shared_peripheral ?
- IMX_DMATYPE_SSI_SP : IMX_DMATYPE_SSI;
- dma_data->priority = DMA_PRIO_HIGH;
- dma_data->dma_request = dma_params->dma;
-
- ret = snd_dmaengine_pcm_open(substream, filter, dma_data);
- if (ret) {
- kfree(dma_data);
- return 0;
- }
-
- snd_dmaengine_pcm_set_data(substream, dma_data);
-
- return 0;
-}
-
-static int snd_imx_close(struct snd_pcm_substream *substream)
-{
- struct imx_dma_data *dma_data = snd_dmaengine_pcm_get_data(substream);
-
- snd_dmaengine_pcm_close(substream);
- kfree(dma_data);
-
- return 0;
-}
-
-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,
- .trigger = snd_dmaengine_pcm_trigger,
- .pointer = snd_dmaengine_pcm_pointer,
- .mmap = snd_imx_pcm_mmap,
-};
-
-static struct snd_soc_platform_driver imx_soc_platform_mx2 = {
- .ops = &imx_pcm_ops,
- .pcm_new = imx_pcm_new,
- .pcm_free = imx_pcm_free,
+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,
};
-static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
-{
- return snd_soc_register_platform(&pdev->dev, &imx_soc_platform_mx2);
-}
-
-static int __devexit imx_soc_platform_remove(struct platform_device *pdev)
+int imx_pcm_dma_init(struct platform_device *pdev)
{
- snd_soc_unregister_platform(&pdev->dev);
- return 0;
+ return devm_snd_dmaengine_pcm_register(&pdev->dev,
+ &imx_dmaengine_pcm_config,
+ SND_DMAENGINE_PCM_FLAG_COMPAT);
}
+EXPORT_SYMBOL_GPL(imx_pcm_dma_init);
-static struct platform_driver imx_pcm_driver = {
- .driver = {
- .name = "imx-pcm-audio",
- .owner = THIS_MODULE,
- },
- .probe = imx_soc_platform_probe,
- .remove = __devexit_p(imx_soc_platform_remove),
-};
-
-module_platform_driver(imx_pcm_driver);
MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:imx-pcm-audio");
diff --git a/sound/soc/fsl/imx-pcm-fiq.c b/sound/soc/fsl/imx-pcm-fiq.c
index 456b7d723d6..7abf6a07957 100644
--- a/sound/soc/fsl/imx-pcm-fiq.c
+++ b/sound/soc/fsl/imx-pcm-fiq.c
@@ -22,6 +22,7 @@
#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>
@@ -29,20 +30,20 @@
#include <asm/fiq.h>
-#include <mach/ssi.h>
+#include <linux/platform_data/asoc-imx-ssi.h>
#include "imx-ssi.h"
+#include "imx-pcm.h"
struct imx_pcm_runtime_data {
- int period;
+ unsigned int period;
int periods;
unsigned long offset;
- unsigned long last_offset;
- unsigned long size;
struct hrtimer hrt;
int poll_time_ns;
struct snd_pcm_substream *substream;
- atomic_t running;
+ atomic_t playing;
+ atomic_t capturing;
};
static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
@@ -50,11 +51,9 @@ 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 snd_pcm_runtime *runtime = substream->runtime;
struct pt_regs regs;
- unsigned long delta;
- if (!atomic_read(&iprtd->running))
+ if (!atomic_read(&iprtd->playing) && !atomic_read(&iprtd->capturing))
return HRTIMER_NORESTART;
get_fiq_regs(&regs);
@@ -64,19 +63,7 @@ static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
else
iprtd->offset = regs.ARM_r9 & 0xffff;
- /* How much data have we transferred since the last period report? */
- if (iprtd->offset >= iprtd->last_offset)
- delta = iprtd->offset - iprtd->last_offset;
- else
- delta = runtime->buffer_size + iprtd->offset
- - iprtd->last_offset;
-
- /* If we've transferred at least a period then report it and
- * reset our poll time */
- if (delta >= iprtd->period) {
- snd_pcm_period_elapsed(substream);
- iprtd->last_offset = iprtd->offset;
- }
+ snd_pcm_period_elapsed(substream);
hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns));
@@ -93,11 +80,9 @@ static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime = substream->runtime;
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
- iprtd->size = params_buffer_bytes(params);
iprtd->periods = params_periods(params);
- iprtd->period = params_period_bytes(params) ;
+ iprtd->period = params_period_bytes(params);
iprtd->offset = 0;
- iprtd->last_offset = 0;
iprtd->poll_time_ns = 1000000000 / params_rate(params) *
params_period_size(params);
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
@@ -122,7 +107,6 @@ static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
return 0;
}
-static int fiq_enable;
static int imx_pcm_fiq;
static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
@@ -134,23 +118,27 @@ static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- atomic_set(&iprtd->running, 1);
+ 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);
- if (++fiq_enable == 1)
- enable_fiq(imx_pcm_fiq);
-
+ enable_fiq(imx_pcm_fiq);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- atomic_set(&iprtd->running, 0);
-
- if (--fiq_enable == 0)
+ 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;
}
@@ -174,9 +162,6 @@ static struct snd_pcm_hardware snd_imx_hardware = {
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- .rate_min = 8000,
- .channels_min = 2,
- .channels_max = 2,
.buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
.period_bytes_min = 128,
.period_bytes_max = 16 * 1024,
@@ -198,7 +183,8 @@ static int snd_imx_open(struct snd_pcm_substream *substream)
iprtd->substream = substream;
- atomic_set(&iprtd->running, 0);
+ 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;
@@ -225,6 +211,22 @@ static int snd_imx_close(struct snd_pcm_substream *substream)
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,
@@ -236,6 +238,51 @@ static struct snd_pcm_ops imx_pcm_ops = {
.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)
@@ -268,6 +315,27 @@ static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd)
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);
@@ -281,9 +349,9 @@ static struct snd_soc_platform_driver imx_soc_platform_fiq = {
.pcm_free = imx_pcm_fiq_free,
};
-static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
+int imx_pcm_fiq_init(struct platform_device *pdev,
+ struct imx_pcm_fiq_params *params)
{
- struct imx_ssi *ssi = platform_get_drvdata(pdev);
int ret;
ret = claim_fiq(&fh);
@@ -292,15 +360,15 @@ static int __devinit imx_soc_platform_probe(struct platform_device *pdev)
return ret;
}
- mxc_set_irq_fiq(ssi->irq, 1);
- ssi_irq = ssi->irq;
+ mxc_set_irq_fiq(params->irq, 1);
+ ssi_irq = params->irq;
- imx_pcm_fiq = ssi->irq;
+ imx_pcm_fiq = params->irq;
- imx_ssi_fiq_base = (unsigned long)ssi->base;
+ imx_ssi_fiq_base = (unsigned long)params->base;
- ssi->dma_params_tx.burstsize = 4;
- ssi->dma_params_rx.burstsize = 6;
+ 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)
@@ -314,23 +382,12 @@ failed_register:
return ret;
}
+EXPORT_SYMBOL_GPL(imx_pcm_fiq_init);
-static int __devexit imx_soc_platform_remove(struct platform_device *pdev)
+void imx_pcm_fiq_exit(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
- return 0;
}
-
-static struct platform_driver imx_pcm_driver = {
- .driver = {
- .name = "imx-fiq-pcm-audio",
- .owner = THIS_MODULE,
- },
-
- .probe = imx_soc_platform_probe,
- .remove = __devexit_p(imx_soc_platform_remove),
-};
-
-module_platform_driver(imx_pcm_driver);
+EXPORT_SYMBOL_GPL(imx_pcm_fiq_exit);
MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-pcm.c b/sound/soc/fsl/imx-pcm.c
deleted file mode 100644
index 93dc360b177..00000000000
--- a/sound/soc/fsl/imx-pcm.c
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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/dma-mapping.h>
-#include <linux/module.h>
-#include <sound/pcm.h>
-#include <sound/soc.h>
-#include "imx-pcm.h"
-
-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;
-}
-EXPORT_SYMBOL_GPL(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 u64 imx_pcm_dmamask = DMA_BIT_MASK(32);
-
-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 = 0;
-
- if (!card->dev->dma_mask)
- card->dev->dma_mask = &imx_pcm_dmamask;
- if (!card->dev->coherent_dma_mask)
- card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
- if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
- ret = imx_pcm_preallocate_dma_buffer(pcm,
- SNDRV_PCM_STREAM_PLAYBACK);
- if (ret)
- goto out;
- }
-
- if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
- ret = imx_pcm_preallocate_dma_buffer(pcm,
- SNDRV_PCM_STREAM_CAPTURE);
- if (ret)
- goto out;
- }
-
-out:
- return ret;
-}
-EXPORT_SYMBOL_GPL(imx_pcm_new);
-
-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;
- }
-}
-EXPORT_SYMBOL_GPL(imx_pcm_free);
diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h
index 83c0ed7d55c..c79cb27473b 100644
--- a/sound/soc/fsl/imx-pcm.h
+++ b/sound/soc/fsl/imx-pcm.h
@@ -13,21 +13,54 @@
#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)
-struct imx_pcm_dma_params {
- int dma;
- unsigned long dma_addr;
- int burstsize;
- bool shared_peripheral; /* The peripheral is on SPBA bus */
+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;
};
-int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,
- struct vm_area_struct *vma);
-int imx_pcm_new(struct snd_soc_pcm_runtime *rtd);
-void imx_pcm_free(struct snd_pcm *pcm);
+#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
index 3a729caeb8c..1cb22dd034e 100644
--- a/sound/soc/fsl/imx-sgtl5000.c
+++ b/sound/soc/fsl/imx-sgtl5000.c
@@ -13,7 +13,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
-#include <linux/of_i2c.h>
+#include <linux/i2c.h>
#include <linux/clk.h>
#include <sound/soc.h>
@@ -33,8 +33,7 @@ struct imx_sgtl5000_data {
static int imx_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd)
{
- struct imx_sgtl5000_data *data = container_of(rtd->card,
- struct imx_sgtl5000_data, card);
+ struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(rtd->card);
struct device *dev = rtd->card->dev;
int ret;
@@ -56,13 +55,13 @@ static const struct snd_soc_dapm_widget imx_sgtl5000_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Ext Spk", NULL),
};
-static int __devinit imx_sgtl5000_probe(struct platform_device *pdev)
+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;
+ struct imx_sgtl5000_data *data = NULL;
int int_port, ext_port;
int ret;
@@ -94,9 +93,8 @@ static int __devinit imx_sgtl5000_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "audmux internal port setup failed\n");
return ret;
}
- imx_audmux_v2_configure_port(ext_port,
- IMX_AUDMUX_V2_PTCR_SYN |
- IMX_AUDMUX_V2_PTCR_TCSEL(int_port),
+ 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");
@@ -114,13 +112,13 @@ static int __devinit imx_sgtl5000_probe(struct platform_device *pdev)
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;
+ 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 -EINVAL;
+ return -EPROBE_DEFER;
}
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
@@ -131,26 +129,18 @@ static int __devinit imx_sgtl5000_probe(struct platform_device *pdev)
data->codec_clk = clk_get(&codec_dev->dev, NULL);
if (IS_ERR(data->codec_clk)) {
- /* assuming clock enabled by default */
- data->codec_clk = NULL;
- ret = of_property_read_u32(codec_np, "clock-frequency",
- &data->clk_frequency);
- if (ret) {
- dev_err(&codec_dev->dev,
- "clock-frequency missing or invalid\n");
- goto fail;
- }
- } else {
- data->clk_frequency = clk_get_rate(data->codec_clk);
- clk_prepare_enable(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_dai_name = dev_name(&ssi_pdev->dev);
- data->dai.platform_name = "imx-pcm-audio";
+ 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;
@@ -158,25 +148,33 @@ static int __devinit imx_sgtl5000_probe(struct platform_device *pdev)
data->card.dev = &pdev->dev;
ret = snd_soc_of_parse_card_name(&data->card, "model");
if (ret)
- goto clk_fail;
+ goto fail;
ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
if (ret)
- goto clk_fail;
+ 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);
- ret = snd_soc_register_card(&data->card);
+ 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;
+ goto fail;
}
- platform_set_drvdata(pdev, data);
-clk_fail:
- clk_put(data->codec_clk);
+ 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);
if (ssi_np)
of_node_put(ssi_np);
if (codec_np)
@@ -185,15 +183,12 @@ fail:
return ret;
}
-static int __devexit imx_sgtl5000_remove(struct platform_device *pdev)
+static int imx_sgtl5000_remove(struct platform_device *pdev)
{
- struct imx_sgtl5000_data *data = platform_get_drvdata(pdev);
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card);
- if (data->codec_clk) {
- clk_disable_unprepare(data->codec_clk);
- clk_put(data->codec_clk);
- }
- snd_soc_unregister_card(&data->card);
+ clk_put(data->codec_clk);
return 0;
}
@@ -208,10 +203,11 @@ static struct platform_driver imx_sgtl5000_driver = {
.driver = {
.name = "imx-sgtl5000",
.owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
.of_match_table = imx_sgtl5000_dt_ids,
},
.probe = imx_sgtl5000_probe,
- .remove = __devexit_p(imx_sgtl5000_remove),
+ .remove = imx_sgtl5000_remove,
};
module_platform_driver(imx_sgtl5000_driver);
diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c
new file mode 100644
index 00000000000..e1dc4014360
--- /dev/null
+++ b/sound/soc/fsl/imx-spdif.c
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+ 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:
+ if (spdif_np)
+ 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",
+ .owner = THIS_MODULE,
+ .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
index 28dd76c7cb1..ab2fdd76b69 100644
--- a/sound/soc/fsl/imx-ssi.c
+++ b/sound/soc/fsl/imx-ssi.c
@@ -47,10 +47,10 @@
#include <sound/pcm_params.h>
#include <sound/soc.h>
-#include <mach/ssi.h>
-#include <mach/hardware.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)
@@ -233,23 +233,6 @@ static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
return 0;
}
-static int imx_ssi_startup(struct snd_pcm_substream *substream,
- struct snd_soc_dai *cpu_dai)
-{
- struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
- struct imx_pcm_dma_params *dma_data;
-
- /* Tx/Rx config */
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- dma_data = &ssi->dma_params_tx;
- else
- dma_data = &ssi->dma_params_rx;
-
- snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
-
- return 0;
-}
-
/*
* Should only be called when port is inactive (i.e. SSIEN = 0),
* although can be called multiple times by upper layers.
@@ -322,8 +305,7 @@ static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
scr |= SSI_SCR_RE;
sier |= sier_bits;
- if (++ssi->enabled == 1)
- scr |= SSI_SCR_SSIEN;
+ scr |= SSI_SCR_SSIEN;
break;
@@ -336,7 +318,7 @@ static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
scr &= ~SSI_SCR_RE;
sier &= ~sier_bits;
- if (--ssi->enabled == 0)
+ if (!(scr & (SSI_SCR_TE | SSI_SCR_RE)))
scr &= ~SSI_SCR_SSIEN;
break;
@@ -354,11 +336,11 @@ static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
}
static const struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
- .startup = imx_ssi_startup,
.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,
+ .xlate_tdm_slot_mask = fsl_asoc_xlate_tdm_slot_mask,
.set_tdm_slot = imx_ssi_set_dai_tdm_slot,
.trigger = imx_ssi_trigger,
};
@@ -370,10 +352,14 @@ static int imx_ssi_dai_probe(struct snd_soc_dai *dai)
snd_soc_dai_set_drvdata(dai, ssi);
- val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) |
- SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize);
+ 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;
}
@@ -401,7 +387,7 @@ static struct snd_soc_dai_driver imx_ac97_dai = {
.stream_name = "AC97 Playback",
.channels_min = 2,
.channels_max = 2,
- .rates = SNDRV_PCM_RATE_48000,
+ .rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
@@ -414,6 +400,10 @@ static struct snd_soc_dai_driver imx_ac97_dai = {
.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;
@@ -497,6 +487,8 @@ static void imx_ssi_ac97_reset(struct snd_ac97 *ac97)
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)
@@ -505,15 +497,17 @@ static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
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);
}
-struct snd_ac97_bus_ops soc_ac97_ops = {
+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
};
-EXPORT_SYMBOL_GPL(soc_ac97_ops);
static int imx_ssi_probe(struct platform_device *pdev)
{
@@ -523,7 +517,7 @@ static int imx_ssi_probe(struct platform_device *pdev)
int ret = 0;
struct snd_soc_dai_driver *dai;
- ssi = kzalloc(sizeof(*ssi), GFP_KERNEL);
+ ssi = devm_kzalloc(&pdev->dev, sizeof(*ssi), GFP_KERNEL);
if (!ssi)
return -ENOMEM;
dev_set_drvdata(&pdev->dev, ssi);
@@ -536,38 +530,29 @@ static int imx_ssi_probe(struct platform_device *pdev)
ssi->irq = platform_get_irq(pdev, 0);
- ssi->clk = clk_get(&pdev->dev, NULL);
+ 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;
}
- clk_prepare_enable(ssi->clk);
+ ret = clk_prepare_enable(ssi->clk);
+ if (ret)
+ goto failed_clk;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
- ret = -ENODEV;
- goto failed_get_resource;
- }
-
- if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) {
- dev_err(&pdev->dev, "request_mem_region failed\n");
- ret = -EBUSY;
- goto failed_get_resource;
- }
-
- ssi->base = ioremap(res->start, resource_size(res));
- if (!ssi->base) {
- dev_err(&pdev->dev, "ioremap failed\n");
- ret = -ENODEV;
- goto failed_ioremap;
+ 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_ac97;
+ goto failed_register;
}
ac97_ssi = ssi;
setup_channel_to_ac97(ssi);
@@ -577,103 +562,88 @@ static int imx_ssi_probe(struct platform_device *pdev)
writel(0x0, ssi->base + SSI_SIER);
- ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0;
- ssi->dma_params_tx.dma_addr = res->start + SSI_STX0;
+ ssi->dma_params_rx.addr = res->start + SSI_SRX0;
+ ssi->dma_params_tx.addr = res->start + SSI_STX0;
- ssi->dma_params_tx.burstsize = 6;
- ssi->dma_params_rx.burstsize = 4;
+ 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)
- ssi->dma_params_tx.dma = res->start;
+ 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)
- ssi->dma_params_rx.dma = res->start;
+ 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_register_dai(&pdev->dev, dai);
- if (ret) {
- dev_err(&pdev->dev, "register DAI failed\n");
+ 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;
}
- ssi->soc_platform_pdev_fiq = platform_device_alloc("imx-fiq-pcm-audio", pdev->id);
- if (!ssi->soc_platform_pdev_fiq) {
- ret = -ENOMEM;
- goto failed_pdev_fiq_alloc;
- }
-
- platform_set_drvdata(ssi->soc_platform_pdev_fiq, ssi);
- ret = platform_device_add(ssi->soc_platform_pdev_fiq);
+ ret = snd_soc_register_component(&pdev->dev, &imx_component,
+ dai, 1);
if (ret) {
- dev_err(&pdev->dev, "failed to add platform device\n");
- goto failed_pdev_fiq_add;
+ dev_err(&pdev->dev, "register DAI failed\n");
+ goto failed_register;
}
- ssi->soc_platform_pdev = platform_device_alloc("imx-pcm-audio", pdev->id);
- if (!ssi->soc_platform_pdev) {
- ret = -ENOMEM;
- goto failed_pdev_alloc;
- }
+ 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;
- platform_set_drvdata(ssi->soc_platform_pdev, ssi);
- ret = platform_device_add(ssi->soc_platform_pdev);
- if (ret) {
- dev_err(&pdev->dev, "failed to add platform device\n");
- goto failed_pdev_add;
+ 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_pdev_add:
- platform_device_put(ssi->soc_platform_pdev);
-failed_pdev_alloc:
- platform_device_del(ssi->soc_platform_pdev_fiq);
-failed_pdev_fiq_add:
- platform_device_put(ssi->soc_platform_pdev_fiq);
-failed_pdev_fiq_alloc:
- snd_soc_unregister_dai(&pdev->dev);
+failed_pcm:
+ snd_soc_unregister_component(&pdev->dev);
failed_register:
-failed_ac97:
- iounmap(ssi->base);
-failed_ioremap:
- release_mem_region(res->start, resource_size(res));
-failed_get_resource:
clk_disable_unprepare(ssi->clk);
- clk_put(ssi->clk);
failed_clk:
- kfree(ssi);
+ snd_soc_set_ac97_ops(NULL);
return ret;
}
-static int __devexit imx_ssi_remove(struct platform_device *pdev)
+static int imx_ssi_remove(struct platform_device *pdev)
{
- struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
struct imx_ssi *ssi = platform_get_drvdata(pdev);
- platform_device_unregister(ssi->soc_platform_pdev);
- platform_device_unregister(ssi->soc_platform_pdev_fiq);
+ if (!ssi->fiq_init)
+ imx_pcm_fiq_exit(pdev);
- snd_soc_unregister_dai(&pdev->dev);
+ snd_soc_unregister_component(&pdev->dev);
if (ssi->flags & IMX_SSI_USE_AC97)
ac97_ssi = NULL;
- iounmap(ssi->base);
- release_mem_region(res->start, resource_size(res));
clk_disable_unprepare(ssi->clk);
- clk_put(ssi->clk);
- kfree(ssi);
+ snd_soc_set_ac97_ops(NULL);
return 0;
}
static struct platform_driver imx_ssi_driver = {
.probe = imx_ssi_probe,
- .remove = __devexit_p(imx_ssi_remove),
+ .remove = imx_ssi_remove,
.driver = {
.name = "imx-ssi",
diff --git a/sound/soc/fsl/imx-ssi.h b/sound/soc/fsl/imx-ssi.h
index 5744e86ca87..be6562365b6 100644
--- a/sound/soc/fsl/imx-ssi.h
+++ b/sound/soc/fsl/imx-ssi.h
@@ -186,7 +186,8 @@
#define DRV_NAME "imx-ssi"
#include <linux/dmaengine.h>
-#include <mach/dma.h>
+#include <linux/platform_data/dma-imx.h>
+#include <sound/dmaengine_pcm.h>
#include "imx-pcm.h"
struct imx_ssi {
@@ -204,13 +205,14 @@ struct imx_ssi {
void (*ac97_reset) (struct snd_ac97 *ac97);
void (*ac97_warm_reset)(struct snd_ac97 *ac97);
- struct imx_pcm_dma_params dma_params_rx;
- struct imx_pcm_dma_params dma_params_tx;
+ 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 enabled;
-
- struct platform_device *soc_platform_pdev;
- struct platform_device *soc_platform_pdev_fiq;
+ 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 00000000000..3a3d17ce6ba
--- /dev/null
+++ b/sound/soc/fsl/imx-wm8962.c
@@ -0,0 +1,324 @@
+/*
+ * 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;
+ }
+ 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.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:
+ if (ssi_np)
+ of_node_put(ssi_np);
+ if (codec_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",
+ .owner = THIS_MODULE,
+ .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
index 9a3f7c5ab68..f2b5d756b1f 100644
--- a/sound/soc/fsl/mpc5200_dma.c
+++ b/sound/soc/fsl/mpc5200_dma.c
@@ -10,12 +10,14 @@
#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 <sysdev/bestcomm/bestcomm.h>
-#include <sysdev/bestcomm/gen_bd.h>
+#include <linux/fsl/bestcomm/bestcomm.h>
+#include <linux/fsl/bestcomm/gen_bd.h>
#include <asm/mpc52xx_psc.h>
#include "mpc5200_dma.h"
@@ -198,10 +200,6 @@ static const struct snd_pcm_hardware psc_dma_hardware = {
SNDRV_PCM_INFO_BATCH,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
- .rate_min = 8000,
- .rate_max = 48000,
- .channels_min = 1,
- .channels_max = 2,
.period_bytes_max = 1024 * 1024,
.period_bytes_min = 32,
.periods_min = 2,
@@ -299,7 +297,6 @@ static struct snd_pcm_ops psc_dma_ops = {
.hw_params = psc_dma_hw_params,
};
-static u64 psc_dma_dmamask = DMA_BIT_MASK(32);
static int psc_dma_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
@@ -307,15 +304,14 @@ static int psc_dma_new(struct snd_soc_pcm_runtime *rtd)
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 = 0;
+ int rc;
dev_dbg(rtd->platform->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n",
card, dai, pcm);
- if (!card->dev->dma_mask)
- card->dev->dma_mask = &psc_dma_dmamask;
- if (!card->dev->coherent_dma_mask)
- card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+ 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,
@@ -370,7 +366,7 @@ static struct snd_soc_platform_driver mpc5200_audio_dma_platform = {
.pcm_free = &psc_dma_free,
};
-static int mpc5200_hpcd_probe(struct platform_device *op)
+int mpc5200_audio_dma_create(struct platform_device *op)
{
phys_addr_t fifo;
struct psc_dma *psc_dma;
@@ -487,8 +483,9 @@ out_unmap:
iounmap(regs);
return ret;
}
+EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create);
-static int mpc5200_hpcd_remove(struct platform_device *op)
+int mpc5200_audio_dma_destroy(struct platform_device *op)
{
struct psc_dma *psc_dma = dev_get_drvdata(&op->dev);
@@ -510,24 +507,7 @@ static int mpc5200_hpcd_remove(struct platform_device *op)
return 0;
}
-
-static struct of_device_id mpc5200_hpcd_match[] = {
- { .compatible = "fsl,mpc5200-pcm", },
- {}
-};
-MODULE_DEVICE_TABLE(of, mpc5200_hpcd_match);
-
-static struct platform_driver mpc5200_hpcd_of_driver = {
- .probe = mpc5200_hpcd_probe,
- .remove = mpc5200_hpcd_remove,
- .driver = {
- .owner = THIS_MODULE,
- .name = "mpc5200-pcm-audio",
- .of_match_table = mpc5200_hpcd_match,
- }
-};
-
-module_platform_driver(mpc5200_hpcd_of_driver);
+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");
diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h
index a3c0cd5382f..dff253fde29 100644
--- a/sound/soc/fsl/mpc5200_dma.h
+++ b/sound/soc/fsl/mpc5200_dma.h
@@ -81,4 +81,7 @@ to_psc_dma_stream(struct snd_pcm_substream *substream, struct psc_dma *psc_dma)
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
index ffa00a2eb77..24eafa2cfbf 100644
--- a/sound/soc/fsl/mpc5200_psc_ac97.c
+++ b/sound/soc/fsl/mpc5200_psc_ac97.c
@@ -131,13 +131,12 @@ static void psc_ac97_cold_reset(struct snd_ac97 *ac97)
psc_ac97_warm_reset(ac97);
}
-struct snd_ac97_bus_ops soc_ac97_ops = {
+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,
};
-EXPORT_SYMBOL_GPL(soc_ac97_ops);
static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
@@ -237,15 +236,18 @@ static const struct snd_soc_dai_ops psc_ac97_digital_ops = {
static struct snd_soc_dai_driver psc_ac97_dai[] = {
{
+ .name = "mpc5200-psc-ac97.0",
.ac97_control = 1,
.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,
@@ -254,8 +256,10 @@ static struct snd_soc_dai_driver psc_ac97_dai[] = {
.ops = &psc_ac97_analog_ops,
},
{
+ .name = "mpc5200-psc-ac97.1",
.ac97_control = 1,
.playback = {
+ .stream_name = "AC97 SPDIF",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_32000 | \
@@ -265,6 +269,9 @@ static struct snd_soc_dai_driver psc_ac97_dai[] = {
.ops = &psc_ac97_digital_ops,
} };
+static const struct snd_soc_component_driver psc_ac97_component = {
+ .name = DRV_NAME,
+};
/* ---------------------------------------------------------------------
@@ -272,13 +279,24 @@ static struct snd_soc_dai_driver psc_ac97_dai[] = {
* - Probe/remove operations
* - OF device match table
*/
-static int __devinit psc_ac97_of_probe(struct platform_device *op)
+static int psc_ac97_of_probe(struct platform_device *op)
{
int rc;
struct snd_ac97 ac97;
struct mpc52xx_psc __iomem *regs;
- rc = snd_soc_register_dais(&op->dev, psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai));
+ 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;
@@ -301,14 +319,16 @@ static int __devinit psc_ac97_of_probe(struct platform_device *op)
return 0;
}
-static int __devexit psc_ac97_of_remove(struct platform_device *op)
+static int psc_ac97_of_remove(struct platform_device *op)
{
- snd_soc_unregister_dais(&op->dev, ARRAY_SIZE(psc_ac97_dai));
+ 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 struct of_device_id psc_ac97_match[] __devinitdata = {
+static struct of_device_id psc_ac97_match[] = {
{ .compatible = "fsl,mpc5200-psc-ac97", },
{ .compatible = "fsl,mpc5200b-psc-ac97", },
{}
@@ -317,7 +337,7 @@ MODULE_DEVICE_TABLE(of, psc_ac97_match);
static struct platform_driver psc_ac97_driver = {
.probe = psc_ac97_of_probe,
- .remove = __devexit_p(psc_ac97_of_remove),
+ .remove = psc_ac97_of_remove,
.driver = {
.name = "mpc5200-psc-ac97",
.owner = THIS_MODULE,
diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c
index 7b530327553..5d07e8a74a2 100644
--- a/sound/soc/fsl/mpc5200_psc_i2s.c
+++ b/sound/soc/fsl/mpc5200_psc_i2s.c
@@ -26,8 +26,7 @@
* ALSA that we support all rates and let the codec driver decide what rates
* are really supported.
*/
-#define PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
- SNDRV_PCM_RATE_CONTINUOUS)
+#define PSC_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS
/**
* PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
@@ -130,13 +129,16 @@ static const struct snd_soc_dai_ops psc_i2s_dai_ops = {
};
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,
@@ -145,18 +147,27 @@ static struct snd_soc_dai_driver psc_i2s_dai[] = {{
.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 __devinit psc_i2s_of_probe(struct platform_device *op)
+static int psc_i2s_of_probe(struct platform_device *op)
{
int rc;
struct psc_dma *psc_dma;
struct mpc52xx_psc __iomem *regs;
- rc = snd_soc_register_dais(&op->dev, psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai));
+ 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;
@@ -198,14 +209,15 @@ static int __devinit psc_i2s_of_probe(struct platform_device *op)
}
-static int __devexit psc_i2s_of_remove(struct platform_device *op)
+static int psc_i2s_of_remove(struct platform_device *op)
{
- snd_soc_unregister_dais(&op->dev, ARRAY_SIZE(psc_i2s_dai));
+ mpc5200_audio_dma_destroy(op);
+ snd_soc_unregister_component(&op->dev);
return 0;
}
/* Match table for of_platform binding */
-static struct of_device_id psc_i2s_match[] __devinitdata = {
+static struct of_device_id psc_i2s_match[] = {
{ .compatible = "fsl,mpc5200-psc-i2s", },
{ .compatible = "fsl,mpc5200b-psc-i2s", },
{}
@@ -214,7 +226,7 @@ MODULE_DEVICE_TABLE(of, psc_i2s_match);
static struct platform_driver psc_i2s_driver = {
.probe = psc_i2s_of_probe,
- .remove = __devexit_p(psc_i2s_of_remove),
+ .remove = psc_i2s_of_remove,
.driver = {
.name = "mpc5200-psc-i2s",
.owner = THIS_MODULE,
diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c
index 60bcba1bc30..fa756d05b2f 100644
--- a/sound/soc/fsl/mpc8610_hpcd.c
+++ b/sound/soc/fsl/mpc8610_hpcd.c
@@ -12,6 +12,7 @@
#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>
@@ -192,7 +193,6 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev)
container_of(dev, struct platform_device, dev);
struct device_node *np = ssi_pdev->dev.of_node;
struct device_node *codec_np = NULL;
- struct platform_device *sound_device = NULL;
struct mpc8610_hpcd_data *machine_data;
int ret = -ENODEV;
const char *sprop;
@@ -341,34 +341,22 @@ static int mpc8610_hpcd_probe(struct platform_device *pdev)
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;
- /* Allocate a new audio platform device structure */
- sound_device = platform_device_alloc("soc-audio", -1);
- if (!sound_device) {
- dev_err(&pdev->dev, "platform device alloc failed\n");
- ret = -ENOMEM;
- goto error;
- }
-
- /* Associate the card data with the sound device */
- platform_set_drvdata(sound_device, &machine_data->card);
-
/* Register with ASoC */
- ret = platform_device_add(sound_device);
+ ret = snd_soc_register_card(&machine_data->card);
if (ret) {
- dev_err(&pdev->dev, "platform device add failed\n");
- goto error_sound;
+ dev_err(&pdev->dev, "could not register card\n");
+ goto error;
}
- dev_set_drvdata(&pdev->dev, sound_device);
of_node_put(codec_np);
return 0;
-error_sound:
- platform_device_put(sound_device);
error:
kfree(machine_data);
error_alloc:
@@ -381,26 +369,21 @@ error_alloc:
*
* This function is called when the platform device is removed.
*/
-static int __devexit mpc8610_hpcd_remove(struct platform_device *pdev)
+static int mpc8610_hpcd_remove(struct platform_device *pdev)
{
- struct platform_device *sound_device = dev_get_drvdata(&pdev->dev);
- struct snd_soc_card *card = platform_get_drvdata(sound_device);
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
struct mpc8610_hpcd_data *machine_data =
container_of(card, struct mpc8610_hpcd_data, card);
- platform_device_unregister(sound_device);
-
+ snd_soc_unregister_card(card);
kfree(machine_data);
- sound_device->dev.platform_data = NULL;
-
- dev_set_drvdata(&pdev->dev, NULL);
return 0;
}
static struct platform_driver mpc8610_hpcd_driver = {
.probe = mpc8610_hpcd_probe,
- .remove = __devexit_p(mpc8610_hpcd_remove),
+ .remove = mpc8610_hpcd_remove,
.driver = {
/* The name must match 'compatible' property in the device tree,
* in lowercase letters.
diff --git a/sound/soc/fsl/mx27vis-aic32x4.c b/sound/soc/fsl/mx27vis-aic32x4.c
index f6d04ad4bb3..f4c3bda5e69 100644
--- a/sound/soc/fsl/mx27vis-aic32x4.c
+++ b/sound/soc/fsl/mx27vis-aic32x4.c
@@ -26,13 +26,13 @@
#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 <mach/iomux-mx27.h>
#include "../codecs/tlv320aic32x4.h"
#include "imx-ssi.h"
@@ -41,20 +41,12 @@
#define MX27VIS_AMP_GAIN 0
#define MX27VIS_AMP_MUTE 1
-#define MX27VIS_PIN_G0 (GPIO_PORTF + 9)
-#define MX27VIS_PIN_G1 (GPIO_PORTF + 8)
-#define MX27VIS_PIN_SDL (GPIO_PORTE + 5)
-#define MX27VIS_PIN_SDR (GPIO_PORTF + 7)
-
static int mx27vis_amp_gain;
static int mx27vis_amp_mute;
-
-static const int mx27vis_amp_pins[] = {
- MX27VIS_PIN_G0 | GPIO_GPIO | GPIO_OUT,
- MX27VIS_PIN_G1 | GPIO_GPIO | GPIO_OUT,
- MX27VIS_PIN_SDL | GPIO_GPIO | GPIO_OUT,
- MX27VIS_PIN_SDR | GPIO_GPIO | GPIO_OUT,
-};
+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)
@@ -109,13 +101,13 @@ static int mx27vis_amp_set(struct snd_kcontrol *kcontrol,
switch (reg) {
case MX27VIS_AMP_GAIN:
- gpio_set_value(MX27VIS_PIN_G0, value & 1);
- gpio_set_value(MX27VIS_PIN_G1, value >> 1);
+ 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_PIN_SDL, value & 1);
- gpio_set_value(MX27VIS_PIN_SDR, value >> 1);
+ gpio_set_value(mx27vis_amp_mutel_gpio, value & 1);
+ gpio_set_value(mx27vis_amp_muter_gpio, value >> 1);
mx27vis_amp_mute = value;
break;
}
@@ -169,7 +161,7 @@ static struct snd_soc_dai_link mx27vis_aic32x4_dai = {
.name = "tlv320aic32x4",
.stream_name = "TLV320AIC32X4",
.codec_dai_name = "tlv320aic32x4-hifi",
- .platform_name = "imx-pcm-audio.0",
+ .platform_name = "imx-ssi.0",
.codec_name = "tlv320aic32x4.0-0018",
.cpu_dai_name = "imx-ssi.0",
.ops = &mx27vis_aic32x4_snd_ops,
@@ -188,10 +180,21 @@ static struct snd_soc_card mx27vis_aic32x4 = {
.num_dapm_routes = ARRAY_SIZE(aic32x4_dapm_routes),
};
-static int __devinit mx27vis_aic32x4_probe(struct platform_device *pdev)
+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) {
@@ -213,15 +216,10 @@ static int __devinit mx27vis_aic32x4_probe(struct platform_device *pdev)
IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)
);
- ret = mxc_gpio_setup_multiple_pins(mx27vis_amp_pins,
- ARRAY_SIZE(mx27vis_amp_pins), "MX27VIS_AMP");
- if (ret)
- printk(KERN_ERR "ASoC: unable to setup gpios\n");
-
return ret;
}
-static int __devexit mx27vis_aic32x4_remove(struct platform_device *pdev)
+static int mx27vis_aic32x4_remove(struct platform_device *pdev)
{
snd_soc_unregister_card(&mx27vis_aic32x4);
@@ -234,7 +232,7 @@ static struct platform_driver mx27vis_aic32x4_audio_driver = {
.owner = THIS_MODULE,
},
.probe = mx27vis_aic32x4_probe,
- .remove = __devexit_p(mx27vis_aic32x4_remove),
+ .remove = mx27vis_aic32x4_remove,
};
module_platform_driver(mx27vis_aic32x4_audio_driver);
diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c
index 50adf4032bc..f75c3cf0e6d 100644
--- a/sound/soc/fsl/p1022_ds.c
+++ b/sound/soc/fsl/p1022_ds.c
@@ -12,6 +12,7 @@
#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>
@@ -202,7 +203,6 @@ static int p1022_ds_probe(struct platform_device *pdev)
container_of(dev, struct platform_device, dev);
struct device_node *np = ssi_pdev->dev.of_node;
struct device_node *codec_np = NULL;
- struct platform_device *sound_device = NULL;
struct machine_data *mdata;
int ret = -ENODEV;
const char *sprop;
@@ -349,36 +349,23 @@ static int p1022_ds_probe(struct platform_device *pdev)
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;
- /* Allocate a new audio platform device structure */
- sound_device = platform_device_alloc("soc-audio", -1);
- if (!sound_device) {
- dev_err(&pdev->dev, "platform device alloc failed\n");
- ret = -ENOMEM;
- goto error;
- }
-
- /* Associate the card data with the sound device */
- platform_set_drvdata(sound_device, &mdata->card);
-
/* Register with ASoC */
- ret = platform_device_add(sound_device);
+ ret = snd_soc_register_card(&mdata->card);
if (ret) {
- dev_err(&pdev->dev, "platform device add failed\n");
+ dev_err(&pdev->dev, "could not register card\n");
goto error;
}
- dev_set_drvdata(&pdev->dev, sound_device);
of_node_put(codec_np);
return 0;
error:
- if (sound_device)
- platform_device_put(sound_device);
-
kfree(mdata);
error_put:
of_node_put(codec_np);
@@ -390,26 +377,21 @@ error_put:
*
* This function is called when the platform device is removed.
*/
-static int __devexit p1022_ds_remove(struct platform_device *pdev)
+static int p1022_ds_remove(struct platform_device *pdev)
{
- struct platform_device *sound_device = dev_get_drvdata(&pdev->dev);
- struct snd_soc_card *card = platform_get_drvdata(sound_device);
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
struct machine_data *mdata =
container_of(card, struct machine_data, card);
- platform_device_unregister(sound_device);
-
+ snd_soc_unregister_card(card);
kfree(mdata);
- sound_device->dev.platform_data = NULL;
-
- dev_set_drvdata(&pdev->dev, NULL);
return 0;
}
static struct platform_driver p1022_ds_driver = {
.probe = p1022_ds_probe,
- .remove = __devexit_p(p1022_ds_remove),
+ .remove = p1022_ds_remove,
.driver = {
/*
* The name must match 'compatible' property in the device tree,
diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c
new file mode 100644
index 00000000000..9d89bb02862
--- /dev/null
+++ b/sound/soc/fsl/p1022_rdk.c
@@ -0,0 +1,393 @@
+/**
+ * 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",
+ .owner = THIS_MODULE,
+ },
+};
+
+/**
+ * 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
index b3af55dcde9..3665f612819 100644
--- a/sound/soc/fsl/pcm030-audio-fabric.c
+++ b/sound/soc/fsl/pcm030-audio-fabric.c
@@ -12,77 +12,124 @@
#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/wm9712.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",
+ .name = "AC97.0",
.stream_name = "AC97 Analog",
.codec_dai_name = "wm9712-hifi",
.cpu_dai_name = "mpc5200-psc-ac97.0",
- .platform_name = "mpc5200-pcm-audio",
.codec_name = "wm9712-codec",
},
{
- .name = "AC97",
+ .name = "AC97.1",
.stream_name = "AC97 IEC958",
.codec_dai_name = "wm9712-aux",
.cpu_dai_name = "mpc5200-psc-ac97.1",
- .platform_name = "mpc5200-pcm-audio",
.codec_name = "wm9712-codec",
},
};
-static struct snd_soc_card card = {
+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 __init int pcm030_fabric_init(void)
+static int pcm030_fabric_probe(struct platform_device *op)
{
- struct platform_device *pdev;
- int rc;
+ 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;
- pdev = platform_device_alloc("soc-audio", 1);
- if (!pdev) {
- pr_err("pcm030_fabric_init: platform_device_alloc() failed\n");
- return -ENODEV;
- }
+ pdata = devm_kzalloc(&op->dev, sizeof(struct pcm030_audio_data),
+ GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ card->dev = &op->dev;
- platform_set_drvdata(pdev, &card);
+ pdata->card = card;
- rc = platform_device_add(pdev);
- if (rc) {
- pr_err("pcm030_fabric_init: platform_device_add() failed\n");
- platform_device_put(pdev);
+ platform_np = of_parse_phandle(np, "asoc-platform", 0);
+ if (!platform_np) {
+ dev_err(&op->dev, "ac97 not registered\n");
return -ENODEV;
}
- return 0;
+
+ 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;
}
-module_init(pcm030_fabric_init);
+static 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,
+ .owner = THIS_MODULE,
+ .of_match_table = pcm030_audio_match,
+ },
+};
+
+module_platform_driver(pcm030_fabric_driver);
MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
diff --git a/sound/soc/fsl/phycore-ac97.c b/sound/soc/fsl/phycore-ac97.c
index f8da6dd115e..ae403c29688 100644
--- a/sound/soc/fsl/phycore-ac97.c
+++ b/sound/soc/fsl/phycore-ac97.c
@@ -33,7 +33,7 @@ static struct snd_soc_dai_link imx_phycore_dai_ac97[] = {
.codec_dai_name = "wm9712-hifi",
.codec_name = "wm9712-codec",
.cpu_dai_name = "imx-ssi.0",
- .platform_name = "imx-fiq-pcm-audio.0",
+ .platform_name = "imx-ssi.0",
.ops = &imx_phycore_hifi_ops,
},
};
diff --git a/sound/soc/fsl/wm1133-ev1.c b/sound/soc/fsl/wm1133-ev1.c
index fe54a69073e..804749a6c61 100644
--- a/sound/soc/fsl/wm1133-ev1.c
+++ b/sound/soc/fsl/wm1133-ev1.c
@@ -214,12 +214,6 @@ static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd)
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
- snd_soc_dapm_new_controls(dapm, wm1133_ev1_widgets,
- ARRAY_SIZE(wm1133_ev1_widgets));
-
- snd_soc_dapm_add_routes(dapm, wm1133_ev1_map,
- ARRAY_SIZE(wm1133_ev1_map));
-
/* Headphone jack detection */
snd_soc_jack_new(codec, "Headphone", SND_JACK_HEADPHONE, &hp_jack);
snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
@@ -245,7 +239,7 @@ static struct snd_soc_dai_link wm1133_ev1_dai = {
.stream_name = "Audio",
.cpu_dai_name = "imx-ssi.0",
.codec_dai_name = "wm8350-hifi",
- .platform_name = "imx-fiq-pcm-audio.0",
+ .platform_name = "imx-ssi.0",
.codec_name = "wm8350-codec.0-0x1a",
.init = wm1133_ev1_init,
.ops = &wm1133_ev1_ops,
@@ -257,6 +251,11 @@ static struct snd_soc_card 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;