v4.19.13 snapshot.
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
new file mode 100644
index 0000000..6ec19fb
--- /dev/null
+++ b/sound/soc/fsl/Kconfig
@@ -0,0 +1,292 @@
+menu "SoC Audio for Freescale CPUs"
+
+comment "Common SoC Audio options for Freescale CPUs:"
+
+config SND_SOC_FSL_ASRC
+	tristate "Asynchronous Sample Rate Converter (ASRC) module support"
+	depends on HAS_DMA
+	select REGMAP_MMIO
+	select SND_SOC_GENERIC_DMAENGINE_PCM
+	help
+	  Say Y if you want to add Asynchronous Sample Rate Converter (ASRC)
+	  support for the Freescale CPUs.
+	  This option is only useful for out-of-tree drivers since
+	  in-tree drivers select it automatically.
+
+config SND_SOC_FSL_SAI
+	tristate "Synchronous Audio Interface (SAI) module support"
+	select REGMAP_MMIO
+	select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
+	select SND_SOC_GENERIC_DMAENGINE_PCM
+	help
+	  Say Y if you want to add Synchronous Audio Interface (SAI)
+	  support for the Freescale CPUs.
+	  This option is only useful for out-of-tree drivers since
+	  in-tree drivers select it automatically.
+
+config SND_SOC_FSL_SSI
+	tristate "Synchronous Serial Interface module (SSI) support"
+	select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
+	select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC)
+	select REGMAP_MMIO
+	help
+	  Say Y if you want to add Synchronous Serial Interface (SSI)
+	  support for the Freescale CPUs.
+	  This option is only useful for out-of-tree drivers since
+	  in-tree drivers select it automatically.
+
+config SND_SOC_FSL_SPDIF
+	tristate "Sony/Philips Digital Interface (S/PDIF) module support"
+	select REGMAP_MMIO
+	select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
+	select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC)
+	select BITREVERSE
+	help
+	  Say Y if you want to add Sony/Philips Digital Interface (SPDIF)
+	  support for the Freescale CPUs.
+	  This option is only useful for out-of-tree drivers since
+	  in-tree drivers select it automatically.
+
+config SND_SOC_FSL_ESAI
+	tristate "Enhanced Serial Audio Interface (ESAI) module support"
+	select REGMAP_MMIO
+	select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
+	help
+	  Say Y if you want to add Enhanced Synchronous Audio Interface
+	  (ESAI) support for the Freescale CPUs.
+	  This option is only useful for out-of-tree drivers since
+	  in-tree drivers select it automatically.
+
+config SND_SOC_FSL_UTILS
+	tristate
+
+config SND_SOC_IMX_PCM_DMA
+	tristate
+	select SND_SOC_GENERIC_DMAENGINE_PCM
+
+config SND_SOC_IMX_AUDMUX
+	tristate "Digital Audio Mux module support"
+	help
+	  Say Y if you want to add Digital Audio Mux (AUDMUX) support
+	  for the ARM i.MX CPUs.
+	  This option is only useful for out-of-tree drivers since
+	  in-tree drivers select it automatically.
+
+config SND_POWERPC_SOC
+	tristate "SoC Audio for Freescale PowerPC CPUs"
+	depends on FSL_SOC || PPC_MPC52xx
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the PowerPC CPUs.
+
+config SND_IMX_SOC
+	tristate "SoC Audio for Freescale i.MX CPUs"
+	depends on ARCH_MXC || COMPILE_TEST
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the i.MX CPUs.
+
+if SND_POWERPC_SOC
+
+config SND_MPC52xx_DMA
+	tristate
+
+config SND_SOC_POWERPC_DMA
+	tristate
+
+comment "SoC Audio support for Freescale PPC boards:"
+
+config SND_SOC_MPC8610_HPCD
+	tristate "ALSA SoC support for the Freescale MPC8610 HPCD board"
+	# I2C is necessary for the CS4270 driver
+	depends on MPC8610_HPCD && I2C
+	select SND_SOC_FSL_SSI
+	select SND_SOC_FSL_UTILS
+	select SND_SOC_POWERPC_DMA
+	select SND_SOC_CS4270
+	select SND_SOC_CS4270_VD33_ERRATA
+	default y if MPC8610_HPCD
+	help
+	  Say Y if you want to enable audio on the Freescale MPC8610 HPCD.
+
+config SND_SOC_P1022_DS
+	tristate "ALSA SoC support for the Freescale P1022 DS board"
+	# I2C is necessary for the WM8776 driver
+	depends on P1022_DS && I2C
+	select SND_SOC_FSL_SSI
+	select SND_SOC_FSL_UTILS
+	select SND_SOC_POWERPC_DMA
+	select SND_SOC_WM8776
+	default y if P1022_DS
+	help
+	  Say Y if you want to enable audio on the Freescale P1022 DS board.
+	  This will also include the Wolfson Microelectronics WM8776 codec
+	  driver.
+
+config SND_SOC_P1022_RDK
+	tristate "ALSA SoC support for the Freescale / iVeia P1022 RDK board"
+	# I2C is necessary for the WM8960 driver
+	depends on P1022_RDK && I2C
+	select SND_SOC_FSL_SSI
+	select SND_SOC_FSL_UTILS
+	select SND_SOC_POWERPC_DMA
+	select SND_SOC_WM8960
+	default y if P1022_RDK
+	help
+	  Say Y if you want to enable audio on the Freescale / iVeia
+	  P1022 RDK board.  This will also include the Wolfson
+	  Microelectronics WM8960 codec driver.
+
+config SND_SOC_MPC5200_I2S
+	tristate "Freescale MPC5200 PSC in I2S mode driver"
+	depends on PPC_MPC52xx && PPC_BESTCOMM
+	select SND_MPC52xx_DMA
+	select PPC_BESTCOMM_GEN_BD
+	help
+	  Say Y here to support the MPC5200 PSCs in I2S mode.
+
+config SND_SOC_MPC5200_AC97
+	tristate "Freescale MPC5200 PSC in AC97 mode driver"
+	depends on PPC_MPC52xx && PPC_BESTCOMM
+	select SND_SOC_AC97_BUS
+	select SND_MPC52xx_DMA
+	select PPC_BESTCOMM_GEN_BD
+	help
+	  Say Y here to support the MPC5200 PSCs in AC97 mode.
+
+config SND_MPC52xx_SOC_PCM030
+	tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712"
+	depends on PPC_MPC5200_SIMPLE
+	select SND_SOC_MPC5200_AC97
+	select SND_SOC_WM9712
+	help
+	  Say Y if you want to add support for sound on the Phytec pcm030
+	  baseboard.
+
+config SND_MPC52xx_SOC_EFIKA
+	tristate "SoC AC97 Audio support for bbplan Efika and STAC9766"
+	depends on PPC_EFIKA
+	select SND_SOC_MPC5200_AC97
+	select SND_SOC_STAC9766
+	help
+	  Say Y if you want to add support for sound on the Efika.
+
+endif # SND_POWERPC_SOC
+
+if SND_IMX_SOC
+
+config SND_SOC_IMX_SSI
+	tristate
+	select SND_SOC_FSL_UTILS
+
+config SND_SOC_IMX_PCM_FIQ
+	tristate
+	select FIQ
+
+comment "SoC Audio support for Freescale i.MX boards:"
+
+config SND_MXC_SOC_WM1133_EV1
+	tristate "Audio on the i.MX31ADS with WM1133-EV1 fitted"
+	depends on MACH_MX31ADS_WM1133_EV1
+	select SND_SOC_WM8350
+	select SND_SOC_IMX_PCM_FIQ
+	select SND_SOC_IMX_AUDMUX
+	select SND_SOC_IMX_SSI
+	help
+	  Enable support for audio on the i.MX31ADS with the WM1133-EV1
+	  PMIC board with WM8835x fitted.
+
+config SND_SOC_MX27VIS_AIC32X4
+	tristate "SoC audio support for Visstrim M10 boards"
+	depends on MACH_IMX27_VISSTRIM_M10 && I2C
+	select SND_SOC_TLV320AIC32X4
+	select SND_SOC_IMX_PCM_DMA
+	select SND_SOC_IMX_AUDMUX
+	select SND_SOC_IMX_SSI
+	help
+	  Say Y if you want to add support for SoC audio on Visstrim SM10
+	  board with TLV320AIC32X4 codec.
+
+config SND_SOC_PHYCORE_AC97
+	tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards"
+	depends on MACH_PCM043 || MACH_PCA100
+	select SND_SOC_AC97_BUS
+	select SND_SOC_WM9712
+	select SND_SOC_IMX_PCM_FIQ
+	select SND_SOC_IMX_AUDMUX
+	select SND_SOC_IMX_SSI
+	help
+	  Say Y if you want to add support for SoC audio on Phytec phyCORE
+	  and phyCARD boards in AC97 mode
+
+config SND_SOC_EUKREA_TLV320
+	tristate "Eukrea TLV320"
+	depends on ARCH_MXC && I2C
+	select SND_SOC_TLV320AIC23_I2C
+	select SND_SOC_IMX_AUDMUX
+	select SND_SOC_IMX_SSI
+	select SND_SOC_FSL_SSI
+	select SND_SOC_IMX_PCM_DMA
+	help
+	  Enable I2S based access to the TLV320AIC23B codec attached
+	  to the SSI interface
+
+config SND_SOC_IMX_ES8328
+	tristate "SoC Audio support for i.MX boards with the ES8328 codec"
+	depends on OF && (I2C || SPI)
+	select SND_SOC_ES8328_I2C if I2C
+	select SND_SOC_ES8328_SPI if SPI_MASTER
+	select SND_SOC_IMX_PCM_DMA
+	select SND_SOC_IMX_AUDMUX
+	select SND_SOC_FSL_SSI
+	help
+	  Say Y if you want to add support for the ES8328 audio codec connected
+	  via SSI/I2S over either SPI or I2C.
+
+config SND_SOC_IMX_SGTL5000
+	tristate "SoC Audio support for i.MX boards with sgtl5000"
+	depends on OF && I2C
+	select SND_SOC_SGTL5000
+	select SND_SOC_IMX_PCM_DMA
+	select SND_SOC_IMX_AUDMUX
+	select SND_SOC_FSL_SSI
+	help
+	  Say Y if you want to add support for SoC audio on an i.MX board with
+	  a sgtl5000 codec.
+
+config SND_SOC_IMX_SPDIF
+	tristate "SoC Audio support for i.MX boards with S/PDIF"
+	select SND_SOC_IMX_PCM_DMA
+	select SND_SOC_FSL_SPDIF
+	help
+	  SoC Audio support for i.MX boards with S/PDIF
+	  Say Y if you want to add support for SoC audio on an i.MX board with
+	  a S/DPDIF.
+
+config SND_SOC_IMX_MC13783
+	tristate "SoC Audio support for I.MX boards with mc13783"
+	depends on MFD_MC13XXX && ARM
+	select SND_SOC_IMX_SSI
+	select SND_SOC_IMX_AUDMUX
+	select SND_SOC_MC13783
+	select SND_SOC_IMX_PCM_DMA
+
+config SND_SOC_FSL_ASOC_CARD
+	tristate "Generic ASoC Sound Card with ASRC support"
+	depends on OF && I2C
+	# enforce SND_SOC_FSL_ASOC_CARD=m if SND_AC97_CODEC=m:
+	depends on SND_AC97_CODEC || SND_AC97_CODEC=n
+	select SND_SOC_IMX_AUDMUX
+	select SND_SOC_IMX_PCM_DMA
+	select SND_SOC_FSL_ESAI
+	select SND_SOC_FSL_SAI
+	select SND_SOC_FSL_SSI
+	help
+	 ALSA SoC Audio support with ASRC feature for Freescale SoCs that have
+	 ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888,
+	 CS4271, CS4272 and SGTL5000.
+	 Say Y if you want to add support for Freescale Generic ASoC Sound Card.
+
+endif # SND_IMX_SOC
+
+endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
new file mode 100644
index 0000000..de94fa0
--- /dev/null
+++ b/sound/soc/fsl/Makefile
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: GPL-2.0
+# MPC8610 HPCD Machine Support
+snd-soc-mpc8610-hpcd-objs := mpc8610_hpcd.o
+obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += snd-soc-mpc8610-hpcd.o
+
+# P1022 DS Machine Support
+snd-soc-p1022-ds-objs := p1022_ds.o
+obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o
+
+# P1022 RDK Machine Support
+snd-soc-p1022-rdk-objs := p1022_rdk.o
+obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
+
+# Freescale SSI/DMA/SAI/SPDIF Support
+snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
+snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o
+snd-soc-fsl-sai-objs := fsl_sai.o
+snd-soc-fsl-ssi-y := fsl_ssi.o
+snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o
+snd-soc-fsl-spdif-objs := fsl_spdif.o
+snd-soc-fsl-esai-objs := fsl_esai.o
+snd-soc-fsl-utils-objs := fsl_utils.o
+snd-soc-fsl-dma-objs := fsl_dma.o
+obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
+obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o
+obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o
+obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o
+obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o
+obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o
+obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o
+obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
+
+# MPC5200 Platform Support
+obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
+obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o
+obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o
+
+# MPC5200 Machine Support
+obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o
+obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o
+
+# i.MX Platform Support
+snd-soc-imx-ssi-objs := imx-ssi.o
+snd-soc-imx-audmux-objs := imx-audmux.o
+obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o
+obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o
+
+obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o
+obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o
+
+# i.MX Machine Support
+snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
+snd-soc-phycore-ac97-objs := phycore-ac97.o
+snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o
+snd-soc-wm1133-ev1-objs := wm1133-ev1.o
+snd-soc-imx-es8328-objs := imx-es8328.o
+snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
+snd-soc-imx-spdif-objs := imx-spdif.o
+snd-soc-imx-mc13783-objs := imx-mc13783.o
+
+obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
+obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
+obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
+obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
+obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o
+obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
+obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
+obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o
diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c
new file mode 100644
index 0000000..667f421
--- /dev/null
+++ b/sound/soc/fsl/efika-audio-fabric.c
@@ -0,0 +1,89 @@
+/*
+ * Efika driver for the PSC of the Freescale MPC52xx
+ * configured as AC97 interface
+ *
+ * Copyright 2008 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "mpc5200_dma.h"
+
+#define DRV_NAME "efika-audio-fabric"
+
+static struct snd_soc_dai_link efika_fabric_dai[] = {
+{
+	.name = "AC97",
+	.stream_name = "AC97 Analog",
+	.codec_dai_name = "stac9766-hifi-analog",
+	.cpu_dai_name = "mpc5200-psc-ac97.0",
+	.platform_name = "mpc5200-pcm-audio",
+	.codec_name = "stac9766-codec",
+},
+{
+	.name = "AC97",
+	.stream_name = "AC97 IEC958",
+	.codec_dai_name = "stac9766-hifi-IEC958",
+	.cpu_dai_name = "mpc5200-psc-ac97.1",
+	.platform_name = "mpc5200-pcm-audio",
+	.codec_name = "stac9766-codec",
+},
+};
+
+static struct snd_soc_card card = {
+	.name = "Efika",
+	.owner = THIS_MODULE,
+	.dai_link = efika_fabric_dai,
+	.num_links = ARRAY_SIZE(efika_fabric_dai),
+};
+
+static __init int efika_fabric_init(void)
+{
+	struct platform_device *pdev;
+	int rc;
+
+	if (!of_machine_is_compatible("bplan,efika"))
+		return -ENODEV;
+
+	pdev = platform_device_alloc("soc-audio", 1);
+	if (!pdev) {
+		pr_err("efika_fabric_init: platform_device_alloc() failed\n");
+		return -ENODEV;
+	}
+
+	platform_set_drvdata(pdev, &card);
+
+	rc = platform_device_add(pdev);
+	if (rc) {
+		pr_err("efika_fabric_init: platform_device_add() failed\n");
+		platform_device_put(pdev);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+module_init(efika_fabric_init);
+
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c
new file mode 100644
index 0000000..191426a
--- /dev/null
+++ b/sound/soc/fsl/eukrea-tlv320.c
@@ -0,0 +1,234 @@
+/*
+ * eukrea-tlv320.c  --  SoC audio for eukrea_cpuimxXX in I2S mode
+ *
+ * Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com>
+ *
+ * based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c
+ * which is Copyright 2009 Simtec Electronics
+ * and on sound/soc/imx/phycore-ac97.c which is
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <asm/mach-types.h>
+
+#include "../codecs/tlv320aic23.h"
+#include "imx-ssi.h"
+#include "imx-audmux.h"
+
+#define CODEC_CLOCK 12000000
+
+static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+				     CODEC_CLOCK, SND_SOC_CLOCK_OUT);
+	if (ret) {
+		dev_err(cpu_dai->dev,
+			"Failed to set the codec sysclk.\n");
+		return ret;
+	}
+
+	snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 0);
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
+				SND_SOC_CLOCK_IN);
+	/* fsl_ssi lacks the set_sysclk ops */
+	if (ret && ret != -EINVAL) {
+		dev_err(cpu_dai->dev,
+			"Can't set the IMX_SSP_SYS_CLK CPU system clock.\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_ops eukrea_tlv320_snd_ops = {
+	.hw_params	= eukrea_tlv320_hw_params,
+};
+
+static struct snd_soc_dai_link eukrea_tlv320_dai = {
+	.name		= "tlv320aic23",
+	.stream_name	= "TLV320AIC23",
+	.codec_dai_name	= "tlv320aic23-hifi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBM_CFM,
+	.ops		= &eukrea_tlv320_snd_ops,
+};
+
+static struct snd_soc_card eukrea_tlv320 = {
+	.owner		= THIS_MODULE,
+	.dai_link	= &eukrea_tlv320_dai,
+	.num_links	= 1,
+};
+
+static int eukrea_tlv320_probe(struct platform_device *pdev)
+{
+	int ret;
+	int int_port = 0, ext_port;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *ssi_np = NULL, *codec_np = NULL;
+
+	eukrea_tlv320.dev = &pdev->dev;
+	if (np) {
+		ret = snd_soc_of_parse_card_name(&eukrea_tlv320,
+						 "eukrea,model");
+		if (ret) {
+			dev_err(&pdev->dev,
+				"eukrea,model node missing or invalid.\n");
+			goto err;
+		}
+
+		ssi_np = of_parse_phandle(pdev->dev.of_node,
+					  "ssi-controller", 0);
+		if (!ssi_np) {
+			dev_err(&pdev->dev,
+				"ssi-controller missing or invalid.\n");
+			ret = -ENODEV;
+			goto err;
+		}
+
+		codec_np = of_parse_phandle(ssi_np, "codec-handle", 0);
+		if (codec_np)
+			eukrea_tlv320_dai.codec_of_node = codec_np;
+		else
+			dev_err(&pdev->dev, "codec-handle node missing or invalid.\n");
+
+		ret = of_property_read_u32(np, "fsl,mux-int-port", &int_port);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"fsl,mux-int-port node missing or invalid.\n");
+			return ret;
+		}
+		ret = of_property_read_u32(np, "fsl,mux-ext-port", &ext_port);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"fsl,mux-ext-port node missing or invalid.\n");
+			return ret;
+		}
+
+		/*
+		 * The port numbering in the hardware manual starts at 1, while
+		 * the audmux API expects it starts at 0.
+		 */
+		int_port--;
+		ext_port--;
+
+		eukrea_tlv320_dai.cpu_of_node = ssi_np;
+		eukrea_tlv320_dai.platform_of_node = ssi_np;
+	} else {
+		eukrea_tlv320_dai.cpu_dai_name = "imx-ssi.0";
+		eukrea_tlv320_dai.platform_name = "imx-ssi.0";
+		eukrea_tlv320_dai.codec_name = "tlv320aic23-codec.0-001a";
+		eukrea_tlv320.name = "cpuimx-audio";
+	}
+
+	if (machine_is_eukrea_cpuimx27() ||
+	    of_find_compatible_node(NULL, NULL, "fsl,imx21-audmux")) {
+		imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
+			IMX_AUDMUX_V1_PCR_SYN |
+			IMX_AUDMUX_V1_PCR_TFSDIR |
+			IMX_AUDMUX_V1_PCR_TCLKDIR |
+			IMX_AUDMUX_V1_PCR_RFSDIR |
+			IMX_AUDMUX_V1_PCR_RCLKDIR |
+			IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) |
+			IMX_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) |
+			IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4)
+		);
+		imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR3_SSI_PINS_4,
+			IMX_AUDMUX_V1_PCR_SYN |
+			IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)
+		);
+	} else if (machine_is_eukrea_cpuimx25sd() ||
+		   machine_is_eukrea_cpuimx35sd() ||
+		   machine_is_eukrea_cpuimx51sd() ||
+		   of_find_compatible_node(NULL, NULL, "fsl,imx31-audmux")) {
+		if (!np)
+			ext_port = machine_is_eukrea_cpuimx25sd() ?
+				4 : 3;
+
+		imx_audmux_v2_configure_port(int_port,
+			IMX_AUDMUX_V2_PTCR_SYN |
+			IMX_AUDMUX_V2_PTCR_TFSDIR |
+			IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+			IMX_AUDMUX_V2_PTCR_TCLKDIR |
+			IMX_AUDMUX_V2_PTCR_TCSEL(ext_port),
+			IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)
+		);
+		imx_audmux_v2_configure_port(ext_port,
+			IMX_AUDMUX_V2_PTCR_SYN,
+			IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)
+		);
+	} else {
+		if (np) {
+			/* The eukrea,asoc-tlv320 driver was explicitly
+			 * requested (through the device tree).
+			 */
+			dev_err(&pdev->dev,
+				"Missing or invalid audmux DT node.\n");
+			return -ENODEV;
+		} else {
+			/* Return happy.
+			 * We might run on a totally different machine.
+			 */
+			return 0;
+		}
+	}
+
+	ret = snd_soc_register_card(&eukrea_tlv320);
+err:
+	if (ret)
+		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+	of_node_put(ssi_np);
+
+	return ret;
+}
+
+static int eukrea_tlv320_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&eukrea_tlv320);
+
+	return 0;
+}
+
+static const struct of_device_id imx_tlv320_dt_ids[] = {
+	{ .compatible = "eukrea,asoc-tlv320"},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_tlv320_dt_ids);
+
+static struct platform_driver eukrea_tlv320_driver = {
+	.driver = {
+		.name = "eukrea_tlv320",
+		.of_match_table = imx_tlv320_dt_ids,
+	},
+	.probe = eukrea_tlv320_probe,
+	.remove = eukrea_tlv320_remove,
+};
+
+module_platform_driver(eukrea_tlv320_driver);
+
+MODULE_AUTHOR("Eric Bénard <eric@eukrea.com>");
+MODULE_DESCRIPTION("CPUIMX ALSA SoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:eukrea_tlv320");
diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c
new file mode 100644
index 0000000..44433b2
--- /dev/null
+++ b/sound/soc/fsl/fsl-asoc-card.c
@@ -0,0 +1,722 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale Generic ASoC Sound Card driver with ASRC
+//
+// Copyright (C) 2014 Freescale Semiconductor, Inc.
+//
+// Author: Nicolin Chen <nicoleotsuka@gmail.com>
+
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#if IS_ENABLED(CONFIG_SND_AC97_CODEC)
+#include <sound/ac97_codec.h>
+#endif
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "fsl_esai.h"
+#include "fsl_sai.h"
+#include "imx-audmux.h"
+
+#include "../codecs/sgtl5000.h"
+#include "../codecs/wm8962.h"
+#include "../codecs/wm8960.h"
+
+#define CS427x_SYSCLK_MCLK 0
+
+#define RX 0
+#define TX 1
+
+/* Default DAI format without Master and Slave flag */
+#define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF)
+
+/**
+ * CODEC private data
+ *
+ * @mclk_freq: Clock rate of MCLK
+ * @mclk_id: MCLK (or main clock) id for set_sysclk()
+ * @fll_id: FLL (or secordary clock) id for set_sysclk()
+ * @pll_id: PLL id for set_pll()
+ */
+struct codec_priv {
+	unsigned long mclk_freq;
+	u32 mclk_id;
+	u32 fll_id;
+	u32 pll_id;
+};
+
+/**
+ * CPU private data
+ *
+ * @sysclk_freq[2]: SYSCLK rates for set_sysclk()
+ * @sysclk_dir[2]: SYSCLK directions for set_sysclk()
+ * @sysclk_id[2]: SYSCLK ids for set_sysclk()
+ * @slot_width: Slot width of each frame
+ *
+ * Note: [1] for tx and [0] for rx
+ */
+struct cpu_priv {
+	unsigned long sysclk_freq[2];
+	u32 sysclk_dir[2];
+	u32 sysclk_id[2];
+	u32 slot_width;
+};
+
+/**
+ * Freescale Generic ASOC card private data
+ *
+ * @dai_link[3]: DAI link structure including normal one and DPCM link
+ * @pdev: platform device pointer
+ * @codec_priv: CODEC private data
+ * @cpu_priv: CPU private data
+ * @card: ASoC card structure
+ * @sample_rate: Current sample rate
+ * @sample_format: Current sample format
+ * @asrc_rate: ASRC sample rate used by Back-Ends
+ * @asrc_format: ASRC sample format used by Back-Ends
+ * @dai_fmt: DAI format between CPU and CODEC
+ * @name: Card name
+ */
+
+struct fsl_asoc_card_priv {
+	struct snd_soc_dai_link dai_link[3];
+	struct platform_device *pdev;
+	struct codec_priv codec_priv;
+	struct cpu_priv cpu_priv;
+	struct snd_soc_card card;
+	u32 sample_rate;
+	snd_pcm_format_t sample_format;
+	u32 asrc_rate;
+	snd_pcm_format_t asrc_format;
+	u32 dai_fmt;
+	char name[32];
+};
+
+/**
+ * This dapm route map exsits for DPCM link only.
+ * The other routes shall go through Device Tree.
+ *
+ * Note: keep all ASRC routes in the second half
+ *	 to drop them easily for non-ASRC cases.
+ */
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* 1st half -- Normal DAPM routes */
+	{"Playback",  NULL, "CPU-Playback"},
+	{"CPU-Capture",  NULL, "Capture"},
+	/* 2nd half -- ASRC DAPM routes */
+	{"CPU-Playback",  NULL, "ASRC-Playback"},
+	{"ASRC-Capture",  NULL, "CPU-Capture"},
+};
+
+static const struct snd_soc_dapm_route audio_map_ac97[] = {
+	/* 1st half -- Normal DAPM routes */
+	{"Playback",  NULL, "AC97 Playback"},
+	{"AC97 Capture",  NULL, "Capture"},
+	/* 2nd half -- ASRC DAPM routes */
+	{"AC97 Playback",  NULL, "ASRC-Playback"},
+	{"ASRC-Capture",  NULL, "AC97 Capture"},
+};
+
+/* Add all possible widgets into here without being redundant */
+static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = {
+	SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In Jack", NULL),
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+	SND_SOC_DAPM_MIC("AMIC", NULL),
+	SND_SOC_DAPM_MIC("DMIC", NULL),
+};
+
+static bool fsl_asoc_card_is_ac97(struct fsl_asoc_card_priv *priv)
+{
+	return priv->dai_fmt == SND_SOC_DAIFMT_AC97;
+}
+
+static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+	bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	struct cpu_priv *cpu_priv = &priv->cpu_priv;
+	struct device *dev = rtd->card->dev;
+	int ret;
+
+	priv->sample_rate = params_rate(params);
+	priv->sample_format = params_format(params);
+
+	/*
+	 * If codec-dai is DAI Master and all configurations are already in the
+	 * set_bias_level(), bypass the remaining settings in hw_params().
+	 * Note: (dai_fmt & CBM_CFM) includes CBM_CFM and CBM_CFS.
+	 */
+	if ((priv->card.set_bias_level &&
+	     priv->dai_fmt & SND_SOC_DAIFMT_CBM_CFM) ||
+	    fsl_asoc_card_is_ac97(priv))
+		return 0;
+
+	/* Specific configurations of DAIs starts from here */
+	ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, cpu_priv->sysclk_id[tx],
+				     cpu_priv->sysclk_freq[tx],
+				     cpu_priv->sysclk_dir[tx]);
+	if (ret && ret != -ENOTSUPP) {
+		dev_err(dev, "failed to set sysclk for cpu dai\n");
+		return ret;
+	}
+
+	if (cpu_priv->slot_width) {
+		ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, 0x3, 0x3, 2,
+					       cpu_priv->slot_width);
+		if (ret && ret != -ENOTSUPP) {
+			dev_err(dev, "failed to set TDM slot for cpu dai\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_ops fsl_asoc_card_ops = {
+	.hw_params = fsl_asoc_card_hw_params,
+};
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+			      struct snd_pcm_hw_params *params)
+{
+	struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+	struct snd_interval *rate;
+	struct snd_mask *mask;
+
+	rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	rate->max = rate->min = priv->asrc_rate;
+
+	mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+	snd_mask_none(mask);
+	snd_mask_set_format(mask, priv->asrc_format);
+
+	return 0;
+}
+
+static struct snd_soc_dai_link fsl_asoc_card_dai[] = {
+	/* Default ASoC DAI Link*/
+	{
+		.name = "HiFi",
+		.stream_name = "HiFi",
+		.ops = &fsl_asoc_card_ops,
+	},
+	/* DPCM Link between Front-End and Back-End (Optional) */
+	{
+		.name = "HiFi-ASRC-FE",
+		.stream_name = "HiFi-ASRC-FE",
+		.codec_name = "snd-soc-dummy",
+		.codec_dai_name = "snd-soc-dummy-dai",
+		.dpcm_playback = 1,
+		.dpcm_capture = 1,
+		.dynamic = 1,
+	},
+	{
+		.name = "HiFi-ASRC-BE",
+		.stream_name = "HiFi-ASRC-BE",
+		.platform_name = "snd-soc-dummy",
+		.be_hw_params_fixup = be_hw_params_fixup,
+		.ops = &fsl_asoc_card_ops,
+		.dpcm_playback = 1,
+		.dpcm_capture = 1,
+		.no_pcm = 1,
+	},
+};
+
+static int fsl_asoc_card_set_bias_level(struct snd_soc_card *card,
+					struct snd_soc_dapm_context *dapm,
+					enum snd_soc_bias_level level)
+{
+	struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *codec_dai;
+	struct codec_priv *codec_priv = &priv->codec_priv;
+	struct device *dev = card->dev;
+	unsigned int pll_out;
+	int ret;
+
+	rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
+	codec_dai = rtd->codec_dai;
+	if (dapm->dev != codec_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+			break;
+
+		if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE)
+			pll_out = priv->sample_rate * 384;
+		else
+			pll_out = priv->sample_rate * 256;
+
+		ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id,
+					  codec_priv->mclk_id,
+					  codec_priv->mclk_freq, pll_out);
+		if (ret) {
+			dev_err(dev, "failed to start FLL: %d\n", ret);
+			return ret;
+		}
+
+		ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->fll_id,
+					     pll_out, SND_SOC_CLOCK_IN);
+		if (ret && ret != -ENOTSUPP) {
+			dev_err(dev, "failed to set SYSCLK: %d\n", ret);
+			return ret;
+		}
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+			break;
+
+		ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
+					     codec_priv->mclk_freq,
+					     SND_SOC_CLOCK_IN);
+		if (ret && ret != -ENOTSUPP) {
+			dev_err(dev, "failed to switch away from FLL: %d\n", ret);
+			return ret;
+		}
+
+		ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, 0, 0, 0);
+		if (ret) {
+			dev_err(dev, "failed to stop FLL: %d\n", ret);
+			return ret;
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int fsl_asoc_card_audmux_init(struct device_node *np,
+				     struct fsl_asoc_card_priv *priv)
+{
+	struct device *dev = &priv->pdev->dev;
+	u32 int_ptcr = 0, ext_ptcr = 0;
+	int int_port, ext_port;
+	int ret;
+
+	ret = of_property_read_u32(np, "mux-int-port", &int_port);
+	if (ret) {
+		dev_err(dev, "mux-int-port missing or invalid\n");
+		return ret;
+	}
+	ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
+	if (ret) {
+		dev_err(dev, "mux-ext-port missing or invalid\n");
+		return ret;
+	}
+
+	/*
+	 * The port numbering in the hardware manual starts at 1, while
+	 * the AUDMUX API expects it starts at 0.
+	 */
+	int_port--;
+	ext_port--;
+
+	/*
+	 * Use asynchronous mode (6 wires) for all cases except AC97.
+	 * If only 4 wires are needed, just set SSI into
+	 * synchronous mode and enable 4 PADs in IOMUX.
+	 */
+	switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
+			   IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
+			   IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+			   IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+			   IMX_AUDMUX_V2_PTCR_RFSDIR |
+			   IMX_AUDMUX_V2_PTCR_RCLKDIR |
+			   IMX_AUDMUX_V2_PTCR_TFSDIR |
+			   IMX_AUDMUX_V2_PTCR_TCLKDIR;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		int_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
+			   IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+			   IMX_AUDMUX_V2_PTCR_RCLKDIR |
+			   IMX_AUDMUX_V2_PTCR_TCLKDIR;
+		ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
+			   IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+			   IMX_AUDMUX_V2_PTCR_RFSDIR |
+			   IMX_AUDMUX_V2_PTCR_TFSDIR;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
+			   IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+			   IMX_AUDMUX_V2_PTCR_RFSDIR |
+			   IMX_AUDMUX_V2_PTCR_TFSDIR;
+		ext_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
+			   IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
+			   IMX_AUDMUX_V2_PTCR_RCLKDIR |
+			   IMX_AUDMUX_V2_PTCR_TCLKDIR;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
+			   IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
+			   IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+			   IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
+			   IMX_AUDMUX_V2_PTCR_RFSDIR |
+			   IMX_AUDMUX_V2_PTCR_RCLKDIR |
+			   IMX_AUDMUX_V2_PTCR_TFSDIR |
+			   IMX_AUDMUX_V2_PTCR_TCLKDIR;
+		break;
+	default:
+		if (!fsl_asoc_card_is_ac97(priv))
+			return -EINVAL;
+	}
+
+	if (fsl_asoc_card_is_ac97(priv)) {
+		int_ptcr = IMX_AUDMUX_V2_PTCR_SYN |
+			   IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+			   IMX_AUDMUX_V2_PTCR_TCLKDIR;
+		ext_ptcr = IMX_AUDMUX_V2_PTCR_SYN |
+			   IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+			   IMX_AUDMUX_V2_PTCR_TFSDIR;
+	}
+
+	/* Asynchronous mode can not be set along with RCLKDIR */
+	if (!fsl_asoc_card_is_ac97(priv)) {
+		unsigned int pdcr =
+				IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port);
+
+		ret = imx_audmux_v2_configure_port(int_port, 0,
+						   pdcr);
+		if (ret) {
+			dev_err(dev, "audmux internal port setup failed\n");
+			return ret;
+		}
+	}
+
+	ret = imx_audmux_v2_configure_port(int_port, int_ptcr,
+					   IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
+	if (ret) {
+		dev_err(dev, "audmux internal port setup failed\n");
+		return ret;
+	}
+
+	if (!fsl_asoc_card_is_ac97(priv)) {
+		unsigned int pdcr =
+				IMX_AUDMUX_V2_PDCR_RXDSEL(int_port);
+
+		ret = imx_audmux_v2_configure_port(ext_port, 0,
+						   pdcr);
+		if (ret) {
+			dev_err(dev, "audmux external port setup failed\n");
+			return ret;
+		}
+	}
+
+	ret = imx_audmux_v2_configure_port(ext_port, ext_ptcr,
+					   IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
+	if (ret) {
+		dev_err(dev, "audmux external port setup failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
+{
+	struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *rtd = list_first_entry(
+			&card->rtd_list, struct snd_soc_pcm_runtime, list);
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct codec_priv *codec_priv = &priv->codec_priv;
+	struct device *dev = card->dev;
+	int ret;
+
+	if (fsl_asoc_card_is_ac97(priv)) {
+#if IS_ENABLED(CONFIG_SND_AC97_CODEC)
+		struct snd_soc_component *component = rtd->codec_dai->component;
+		struct snd_ac97 *ac97 = snd_soc_component_get_drvdata(component);
+
+		/*
+		 * Use slots 3/4 for S/PDIF so SSI won't try to enable
+		 * other slots and send some samples there
+		 * due to SLOTREQ bits for S/PDIF received from codec
+		 */
+		snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
+				     AC97_EA_SPSA_SLOT_MASK, AC97_EA_SPSA_3_4);
+#endif
+
+		return 0;
+	}
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
+				     codec_priv->mclk_freq, SND_SOC_CLOCK_IN);
+	if (ret && ret != -ENOTSUPP) {
+		dev_err(dev, "failed to set sysclk in %s\n", __func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int fsl_asoc_card_probe(struct platform_device *pdev)
+{
+	struct device_node *cpu_np, *codec_np, *asrc_np;
+	struct device_node *np = pdev->dev.of_node;
+	struct platform_device *asrc_pdev = NULL;
+	struct platform_device *cpu_pdev;
+	struct fsl_asoc_card_priv *priv;
+	struct i2c_client *codec_dev;
+	const char *codec_dai_name;
+	u32 width;
+	int ret;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	cpu_np = of_parse_phandle(np, "audio-cpu", 0);
+	/* Give a chance to old DT binding */
+	if (!cpu_np)
+		cpu_np = of_parse_phandle(np, "ssi-controller", 0);
+	if (!cpu_np) {
+		dev_err(&pdev->dev, "CPU phandle missing or invalid\n");
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	cpu_pdev = of_find_device_by_node(cpu_np);
+	if (!cpu_pdev) {
+		dev_err(&pdev->dev, "failed to find CPU DAI device\n");
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	codec_np = of_parse_phandle(np, "audio-codec", 0);
+	if (codec_np)
+		codec_dev = of_find_i2c_device_by_node(codec_np);
+	else
+		codec_dev = NULL;
+
+	asrc_np = of_parse_phandle(np, "audio-asrc", 0);
+	if (asrc_np)
+		asrc_pdev = of_find_device_by_node(asrc_np);
+
+	/* Get the MCLK rate only, and leave it controlled by CODEC drivers */
+	if (codec_dev) {
+		struct clk *codec_clk = clk_get(&codec_dev->dev, NULL);
+
+		if (!IS_ERR(codec_clk)) {
+			priv->codec_priv.mclk_freq = clk_get_rate(codec_clk);
+			clk_put(codec_clk);
+		}
+	}
+
+	/* Default sample rate and format, will be updated in hw_params() */
+	priv->sample_rate = 44100;
+	priv->sample_format = SNDRV_PCM_FORMAT_S16_LE;
+
+	/* Assign a default DAI format, and allow each card to overwrite it */
+	priv->dai_fmt = DAI_FMT_BASE;
+
+	/* Diversify the card configurations */
+	if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) {
+		codec_dai_name = "cs42888";
+		priv->card.set_bias_level = NULL;
+		priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv.mclk_freq;
+		priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv.mclk_freq;
+		priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT;
+		priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT;
+		priv->cpu_priv.slot_width = 32;
+		priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
+	} else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) {
+		codec_dai_name = "cs4271-hifi";
+		priv->codec_priv.mclk_id = CS427x_SYSCLK_MCLK;
+		priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+	} else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) {
+		codec_dai_name = "sgtl5000";
+		priv->codec_priv.mclk_id = SGTL5000_SYSCLK;
+		priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+	} else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) {
+		codec_dai_name = "wm8962";
+		priv->card.set_bias_level = fsl_asoc_card_set_bias_level;
+		priv->codec_priv.mclk_id = WM8962_SYSCLK_MCLK;
+		priv->codec_priv.fll_id = WM8962_SYSCLK_FLL;
+		priv->codec_priv.pll_id = WM8962_FLL;
+		priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+	} else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) {
+		codec_dai_name = "wm8960-hifi";
+		priv->card.set_bias_level = fsl_asoc_card_set_bias_level;
+		priv->codec_priv.fll_id = WM8960_SYSCLK_AUTO;
+		priv->codec_priv.pll_id = WM8960_SYSCLK_AUTO;
+		priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+	} else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) {
+		codec_dai_name = "ac97-hifi";
+		priv->card.set_bias_level = NULL;
+		priv->dai_fmt = SND_SOC_DAIFMT_AC97;
+	} else {
+		dev_err(&pdev->dev, "unknown Device Tree compatible\n");
+		ret = -EINVAL;
+		goto asrc_fail;
+	}
+
+	if (!fsl_asoc_card_is_ac97(priv) && !codec_dev) {
+		dev_err(&pdev->dev, "failed to find codec device\n");
+		ret = -EINVAL;
+		goto asrc_fail;
+	}
+
+	/* Common settings for corresponding Freescale CPU DAI driver */
+	if (strstr(cpu_np->name, "ssi")) {
+		/* Only SSI needs to configure AUDMUX */
+		ret = fsl_asoc_card_audmux_init(np, priv);
+		if (ret) {
+			dev_err(&pdev->dev, "failed to init audmux\n");
+			goto asrc_fail;
+		}
+	} else if (strstr(cpu_np->name, "esai")) {
+		priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL;
+		priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL;
+	} else if (strstr(cpu_np->name, "sai")) {
+		priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1;
+		priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1;
+	}
+
+	snprintf(priv->name, sizeof(priv->name), "%s-audio",
+		 fsl_asoc_card_is_ac97(priv) ? "ac97" :
+		 codec_dev->name);
+
+	/* Initialize sound card */
+	priv->pdev = pdev;
+	priv->card.dev = &pdev->dev;
+	priv->card.name = priv->name;
+	priv->card.dai_link = priv->dai_link;
+	priv->card.dapm_routes = fsl_asoc_card_is_ac97(priv) ?
+				 audio_map_ac97 : audio_map;
+	priv->card.late_probe = fsl_asoc_card_late_probe;
+	priv->card.num_dapm_routes = ARRAY_SIZE(audio_map);
+	priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets;
+	priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets);
+
+	/* Drop the second half of DAPM routes -- ASRC */
+	if (!asrc_pdev)
+		priv->card.num_dapm_routes /= 2;
+
+	memcpy(priv->dai_link, fsl_asoc_card_dai,
+	       sizeof(struct snd_soc_dai_link) * ARRAY_SIZE(priv->dai_link));
+
+	ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing");
+	if (ret) {
+		dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret);
+		goto asrc_fail;
+	}
+
+	/* Normal DAI Link */
+	priv->dai_link[0].cpu_of_node = cpu_np;
+	priv->dai_link[0].codec_dai_name = codec_dai_name;
+
+	if (!fsl_asoc_card_is_ac97(priv))
+		priv->dai_link[0].codec_of_node = codec_np;
+	else {
+		u32 idx;
+
+		ret = of_property_read_u32(cpu_np, "cell-index", &idx);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"cannot get CPU index property\n");
+			goto asrc_fail;
+		}
+
+		priv->dai_link[0].codec_name =
+				devm_kasprintf(&pdev->dev, GFP_KERNEL,
+					       "ac97-codec.%u",
+					       (unsigned int)idx);
+		if (!priv->dai_link[0].codec_name) {
+			ret = -ENOMEM;
+			goto asrc_fail;
+		}
+	}
+
+	priv->dai_link[0].platform_of_node = cpu_np;
+	priv->dai_link[0].dai_fmt = priv->dai_fmt;
+	priv->card.num_links = 1;
+
+	if (asrc_pdev) {
+		/* DPCM DAI Links only if ASRC exsits */
+		priv->dai_link[1].cpu_of_node = asrc_np;
+		priv->dai_link[1].platform_of_node = asrc_np;
+		priv->dai_link[2].codec_dai_name = codec_dai_name;
+		priv->dai_link[2].codec_of_node = codec_np;
+		priv->dai_link[2].codec_name =
+				priv->dai_link[0].codec_name;
+		priv->dai_link[2].cpu_of_node = cpu_np;
+		priv->dai_link[2].dai_fmt = priv->dai_fmt;
+		priv->card.num_links = 3;
+
+		ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
+					   &priv->asrc_rate);
+		if (ret) {
+			dev_err(&pdev->dev, "failed to get output rate\n");
+			ret = -EINVAL;
+			goto asrc_fail;
+		}
+
+		ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+		if (ret) {
+			dev_err(&pdev->dev, "failed to get output rate\n");
+			ret = -EINVAL;
+			goto asrc_fail;
+		}
+
+		if (width == 24)
+			priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+		else
+			priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+	}
+
+	/* Finish card registering */
+	platform_set_drvdata(pdev, priv);
+	snd_soc_card_set_drvdata(&priv->card, priv);
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+
+asrc_fail:
+	of_node_put(asrc_np);
+	of_node_put(codec_np);
+fail:
+	of_node_put(cpu_np);
+
+	return ret;
+}
+
+static const struct of_device_id fsl_asoc_card_dt_ids[] = {
+	{ .compatible = "fsl,imx-audio-ac97", },
+	{ .compatible = "fsl,imx-audio-cs42888", },
+	{ .compatible = "fsl,imx-audio-cs427x", },
+	{ .compatible = "fsl,imx-audio-sgtl5000", },
+	{ .compatible = "fsl,imx-audio-wm8962", },
+	{ .compatible = "fsl,imx-audio-wm8960", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids);
+
+static struct platform_driver fsl_asoc_card_driver = {
+	.probe = fsl_asoc_card_probe,
+	.driver = {
+		.name = "fsl-asoc-card",
+		.pm = &snd_soc_pm_ops,
+		.of_match_table = fsl_asoc_card_dt_ids,
+	},
+};
+module_platform_driver(fsl_asoc_card_driver);
+
+MODULE_DESCRIPTION("Freescale Generic ASoC Sound Card driver with ASRC");
+MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>");
+MODULE_ALIAS("platform:fsl-asoc-card");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c
new file mode 100644
index 0000000..528e8b1
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc.c
@@ -0,0 +1,1043 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver
+//
+// Copyright (C) 2014 Freescale Semiconductor, Inc.
+//
+// Author: Nicolin Chen <nicoleotsuka@gmail.com>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/dma-imx.h>
+#include <linux/pm_runtime.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_asrc.h"
+
+#define IDEAL_RATIO_DECIMAL_DEPTH 26
+
+#define pair_err(fmt, ...) \
+	dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+
+#define pair_dbg(fmt, ...) \
+	dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+
+/* Sample rates are aligned with that defined in pcm.h file */
+static const u8 process_option[][12][2] = {
+	/* 8kHz 11.025kHz 16kHz 22.05kHz 32kHz 44.1kHz 48kHz   64kHz   88.2kHz 96kHz   176kHz  192kHz */
+	{{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},},	/* 5512Hz */
+	{{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},},	/* 8kHz */
+	{{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},},	/* 11025Hz */
+	{{1, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},},	/* 16kHz */
+	{{1, 2}, {1, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},},	/* 22050Hz */
+	{{1, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},},	/* 32kHz */
+	{{2, 2}, {2, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},},	/* 44.1kHz */
+	{{2, 2}, {2, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},},	/* 48kHz */
+	{{2, 2}, {2, 2}, {2, 2}, {2, 1}, {1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},},	/* 64kHz */
+	{{2, 2}, {2, 2}, {2, 2}, {2, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},},	/* 88.2kHz */
+	{{2, 2}, {2, 2}, {2, 2}, {2, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},},	/* 96kHz */
+	{{2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},},	/* 176kHz */
+	{{2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},},	/* 192kHz */
+};
+
+/* Corresponding to process_option */
+static int supported_input_rate[] = {
+	5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200,
+	96000, 176400, 192000,
+};
+
+static int supported_asrc_rate[] = {
+	8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000,
+};
+
+/**
+ * The following tables map the relationship between asrc_inclk/asrc_outclk in
+ * fsl_asrc.h and the registers of ASRCSR
+ */
+static unsigned char input_clk_map_imx35[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+};
+
+static unsigned char output_clk_map_imx35[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+};
+
+/* i.MX53 uses the same map for input and output */
+static unsigned char input_clk_map_imx53[] = {
+/*	0x0  0x1  0x2  0x3  0x4  0x5  0x6  0x7  0x8  0x9  0xa  0xb  0xc  0xd  0xe  0xf */
+	0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd,
+};
+
+static unsigned char output_clk_map_imx53[] = {
+/*	0x0  0x1  0x2  0x3  0x4  0x5  0x6  0x7  0x8  0x9  0xa  0xb  0xc  0xd  0xe  0xf */
+	0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd,
+};
+
+static unsigned char *clk_map[2];
+
+/**
+ * Request ASRC pair
+ *
+ * It assigns pair by the order of A->C->B because allocation of pair B,
+ * within range [ANCA, ANCA+ANCB-1], depends on the channels of pair A
+ * while pair A and pair C are comparatively independent.
+ */
+static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
+{
+	enum asrc_pair_index index = ASRC_INVALID_PAIR;
+	struct fsl_asrc *asrc_priv = pair->asrc_priv;
+	struct device *dev = &asrc_priv->pdev->dev;
+	unsigned long lock_flags;
+	int i, ret = 0;
+
+	spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+
+	for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) {
+		if (asrc_priv->pair[i] != NULL)
+			continue;
+
+		index = i;
+
+		if (i != ASRC_PAIR_B)
+			break;
+	}
+
+	if (index == ASRC_INVALID_PAIR) {
+		dev_err(dev, "all pairs are busy now\n");
+		ret = -EBUSY;
+	} else if (asrc_priv->channel_avail < channels) {
+		dev_err(dev, "can't afford required channels: %d\n", channels);
+		ret = -EINVAL;
+	} else {
+		asrc_priv->channel_avail -= channels;
+		asrc_priv->pair[index] = pair;
+		pair->channels = channels;
+		pair->index = index;
+	}
+
+	spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+
+	return ret;
+}
+
+/**
+ * Release ASRC pair
+ *
+ * It clears the resource from asrc_priv and releases the occupied channels.
+ */
+static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair)
+{
+	struct fsl_asrc *asrc_priv = pair->asrc_priv;
+	enum asrc_pair_index index = pair->index;
+	unsigned long lock_flags;
+
+	/* Make sure the pair is disabled */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+			   ASRCTR_ASRCEi_MASK(index), 0);
+
+	spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+
+	asrc_priv->channel_avail += pair->channels;
+	asrc_priv->pair[index] = NULL;
+	pair->error = 0;
+
+	spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+}
+
+/**
+ * Configure input and output thresholds
+ */
+static void fsl_asrc_set_watermarks(struct fsl_asrc_pair *pair, u32 in, u32 out)
+{
+	struct fsl_asrc *asrc_priv = pair->asrc_priv;
+	enum asrc_pair_index index = pair->index;
+
+	regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index),
+			   ASRMCRi_EXTTHRSHi_MASK |
+			   ASRMCRi_INFIFO_THRESHOLD_MASK |
+			   ASRMCRi_OUTFIFO_THRESHOLD_MASK,
+			   ASRMCRi_EXTTHRSHi |
+			   ASRMCRi_INFIFO_THRESHOLD(in) |
+			   ASRMCRi_OUTFIFO_THRESHOLD(out));
+}
+
+/**
+ * Calculate the total divisor between asrck clock rate and sample rate
+ *
+ * It follows the formula clk_rate = samplerate * (2 ^ prescaler) * divider
+ */
+static u32 fsl_asrc_cal_asrck_divisor(struct fsl_asrc_pair *pair, u32 div)
+{
+	u32 ps;
+
+	/* Calculate the divisors: prescaler [2^0, 2^7], divder [1, 8] */
+	for (ps = 0; div > 8; ps++)
+		div >>= 1;
+
+	return ((div - 1) << ASRCDRi_AxCPi_WIDTH) | ps;
+}
+
+/**
+ * Calculate and set the ratio for Ideal Ratio mode only
+ *
+ * The ratio is a 32-bit fixed point value with 26 fractional bits.
+ */
+static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair,
+				    int inrate, int outrate)
+{
+	struct fsl_asrc *asrc_priv = pair->asrc_priv;
+	enum asrc_pair_index index = pair->index;
+	unsigned long ratio;
+	int i;
+
+	if (!outrate) {
+		pair_err("output rate should not be zero\n");
+		return -EINVAL;
+	}
+
+	/* Calculate the intergal part of the ratio */
+	ratio = (inrate / outrate) << IDEAL_RATIO_DECIMAL_DEPTH;
+
+	/* ... and then the 26 depth decimal part */
+	inrate %= outrate;
+
+	for (i = 1; i <= IDEAL_RATIO_DECIMAL_DEPTH; i++) {
+		inrate <<= 1;
+
+		if (inrate < outrate)
+			continue;
+
+		ratio |= 1 << (IDEAL_RATIO_DECIMAL_DEPTH - i);
+		inrate -= outrate;
+
+		if (!inrate)
+			break;
+	}
+
+	regmap_write(asrc_priv->regmap, REG_ASRIDRL(index), ratio);
+	regmap_write(asrc_priv->regmap, REG_ASRIDRH(index), ratio >> 24);
+
+	return 0;
+}
+
+/**
+ * Configure the assigned ASRC pair
+ *
+ * It configures those ASRC registers according to a configuration instance
+ * of struct asrc_config which includes in/output sample rate, width, channel
+ * and clock settings.
+ */
+static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
+{
+	struct asrc_config *config = pair->config;
+	struct fsl_asrc *asrc_priv = pair->asrc_priv;
+	enum asrc_pair_index index = pair->index;
+	u32 inrate, outrate, indiv, outdiv;
+	u32 clk_index[2], div[2];
+	int in, out, channels;
+	struct clk *clk;
+	bool ideal;
+
+	if (!config) {
+		pair_err("invalid pair config\n");
+		return -EINVAL;
+	}
+
+	/* Validate channels */
+	if (config->channel_num < 1 || config->channel_num > 10) {
+		pair_err("does not support %d channels\n", config->channel_num);
+		return -EINVAL;
+	}
+
+	/* Validate output width */
+	if (config->output_word_width == ASRC_WIDTH_8_BIT) {
+		pair_err("does not support 8bit width output\n");
+		return -EINVAL;
+	}
+
+	inrate = config->input_sample_rate;
+	outrate = config->output_sample_rate;
+	ideal = config->inclk == INCLK_NONE;
+
+	/* Validate input and output sample rates */
+	for (in = 0; in < ARRAY_SIZE(supported_input_rate); in++)
+		if (inrate == supported_input_rate[in])
+			break;
+
+	if (in == ARRAY_SIZE(supported_input_rate)) {
+		pair_err("unsupported input sample rate: %dHz\n", inrate);
+		return -EINVAL;
+	}
+
+	for (out = 0; out < ARRAY_SIZE(supported_asrc_rate); out++)
+		if (outrate == supported_asrc_rate[out])
+			break;
+
+	if (out == ARRAY_SIZE(supported_asrc_rate)) {
+		pair_err("unsupported output sample rate: %dHz\n", outrate);
+		return -EINVAL;
+	}
+
+	if ((outrate > 8000 && outrate < 30000) &&
+	    (outrate/inrate > 24 || inrate/outrate > 8)) {
+		pair_err("exceed supported ratio range [1/24, 8] for \
+				inrate/outrate: %d/%d\n", inrate, outrate);
+		return -EINVAL;
+	}
+
+	/* Validate input and output clock sources */
+	clk_index[IN] = clk_map[IN][config->inclk];
+	clk_index[OUT] = clk_map[OUT][config->outclk];
+
+	/* We only have output clock for ideal ratio mode */
+	clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]];
+
+	div[IN] = clk_get_rate(clk) / inrate;
+	if (div[IN] == 0) {
+		pair_err("failed to support input sample rate %dHz by asrck_%x\n",
+				inrate, clk_index[ideal ? OUT : IN]);
+		return -EINVAL;
+	}
+
+	clk = asrc_priv->asrck_clk[clk_index[OUT]];
+
+	/* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */
+	if (ideal)
+		div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE;
+	else
+		div[OUT] = clk_get_rate(clk) / outrate;
+
+	if (div[OUT] == 0) {
+		pair_err("failed to support output sample rate %dHz by asrck_%x\n",
+				outrate, clk_index[OUT]);
+		return -EINVAL;
+	}
+
+	/* Set the channel number */
+	channels = config->channel_num;
+
+	if (asrc_priv->channel_bits < 4)
+		channels /= 2;
+
+	/* Update channels for current pair */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCNCR,
+			   ASRCNCR_ANCi_MASK(index, asrc_priv->channel_bits),
+			   ASRCNCR_ANCi(index, channels, asrc_priv->channel_bits));
+
+	/* Default setting: Automatic selection for processing mode */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+			   ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index));
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+			   ASRCTR_USRi_MASK(index), 0);
+
+	/* Set the input and output clock sources */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCSR,
+			   ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index),
+			   ASRCSR_AICS(index, clk_index[IN]) |
+			   ASRCSR_AOCS(index, clk_index[OUT]));
+
+	/* Calculate the input clock divisors */
+	indiv = fsl_asrc_cal_asrck_divisor(pair, div[IN]);
+	outdiv = fsl_asrc_cal_asrck_divisor(pair, div[OUT]);
+
+	/* Suppose indiv and outdiv includes prescaler, so add its MASK too */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCDR(index),
+			   ASRCDRi_AOCPi_MASK(index) | ASRCDRi_AICPi_MASK(index) |
+			   ASRCDRi_AOCDi_MASK(index) | ASRCDRi_AICDi_MASK(index),
+			   ASRCDRi_AOCP(index, outdiv) | ASRCDRi_AICP(index, indiv));
+
+	/* Implement word_width configurations */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRMCR1(index),
+			   ASRMCR1i_OW16_MASK | ASRMCR1i_IWD_MASK,
+			   ASRMCR1i_OW16(config->output_word_width) |
+			   ASRMCR1i_IWD(config->input_word_width));
+
+	/* Enable BUFFER STALL */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index),
+			   ASRMCRi_BUFSTALLi_MASK, ASRMCRi_BUFSTALLi);
+
+	/* Set default thresholds for input and output FIFO */
+	fsl_asrc_set_watermarks(pair, ASRC_INPUTFIFO_THRESHOLD,
+				ASRC_INPUTFIFO_THRESHOLD);
+
+	/* Configure the following only for Ideal Ratio mode */
+	if (!ideal)
+		return 0;
+
+	/* Clear ASTSx bit to use Ideal Ratio mode */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+			   ASRCTR_ATSi_MASK(index), 0);
+
+	/* Enable Ideal Ratio mode */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+			   ASRCTR_IDRi_MASK(index) | ASRCTR_USRi_MASK(index),
+			   ASRCTR_IDR(index) | ASRCTR_USR(index));
+
+	/* Apply configurations for pre- and post-processing */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCFG,
+			   ASRCFG_PREMODi_MASK(index) |	ASRCFG_POSTMODi_MASK(index),
+			   ASRCFG_PREMOD(index, process_option[in][out][0]) |
+			   ASRCFG_POSTMOD(index, process_option[in][out][1]));
+
+	return fsl_asrc_set_ideal_ratio(pair, inrate, outrate);
+}
+
+/**
+ * Start the assigned ASRC pair
+ *
+ * It enables the assigned pair and makes it stopped at the stall level.
+ */
+static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair)
+{
+	struct fsl_asrc *asrc_priv = pair->asrc_priv;
+	enum asrc_pair_index index = pair->index;
+	int reg, retry = 10, i;
+
+	/* Enable the current pair */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+			   ASRCTR_ASRCEi_MASK(index), ASRCTR_ASRCE(index));
+
+	/* Wait for status of initialization */
+	do {
+		udelay(5);
+		regmap_read(asrc_priv->regmap, REG_ASRCFG, &reg);
+		reg &= ASRCFG_INIRQi_MASK(index);
+	} while (!reg && --retry);
+
+	/* Make the input fifo to ASRC STALL level */
+	regmap_read(asrc_priv->regmap, REG_ASRCNCR, &reg);
+	for (i = 0; i < pair->channels * 4; i++)
+		regmap_write(asrc_priv->regmap, REG_ASRDI(index), 0);
+
+	/* Enable overload interrupt */
+	regmap_write(asrc_priv->regmap, REG_ASRIER, ASRIER_AOLIE);
+}
+
+/**
+ * Stop the assigned ASRC pair
+ */
+static void fsl_asrc_stop_pair(struct fsl_asrc_pair *pair)
+{
+	struct fsl_asrc *asrc_priv = pair->asrc_priv;
+	enum asrc_pair_index index = pair->index;
+
+	/* Stop the current pair */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+			   ASRCTR_ASRCEi_MASK(index), 0);
+}
+
+/**
+ * Get DMA channel according to the pair and direction.
+ */
+struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir)
+{
+	struct fsl_asrc *asrc_priv = pair->asrc_priv;
+	enum asrc_pair_index index = pair->index;
+	char name[4];
+
+	sprintf(name, "%cx%c", dir == IN ? 'r' : 't', index + 'a');
+
+	return dma_request_slave_channel(&asrc_priv->pdev->dev, name);
+}
+EXPORT_SYMBOL_GPL(fsl_asrc_get_dma_channel);
+
+static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai);
+	int width = params_width(params);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+	unsigned int channels = params_channels(params);
+	unsigned int rate = params_rate(params);
+	struct asrc_config config;
+	int word_width, ret;
+
+	ret = fsl_asrc_request_pair(channels, pair);
+	if (ret) {
+		dev_err(dai->dev, "fail to request asrc pair\n");
+		return ret;
+	}
+
+	pair->config = &config;
+
+	if (width == 16)
+		width = ASRC_WIDTH_16_BIT;
+	else
+		width = ASRC_WIDTH_24_BIT;
+
+	if (asrc_priv->asrc_width == 16)
+		word_width = ASRC_WIDTH_16_BIT;
+	else
+		word_width = ASRC_WIDTH_24_BIT;
+
+	config.pair = pair->index;
+	config.channel_num = channels;
+	config.inclk = INCLK_NONE;
+	config.outclk = OUTCLK_ASRCK1_CLK;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		config.input_word_width   = width;
+		config.output_word_width  = word_width;
+		config.input_sample_rate  = rate;
+		config.output_sample_rate = asrc_priv->asrc_rate;
+	} else {
+		config.input_word_width   = word_width;
+		config.output_word_width  = width;
+		config.input_sample_rate  = asrc_priv->asrc_rate;
+		config.output_sample_rate = rate;
+	}
+
+	ret = fsl_asrc_config_pair(pair);
+	if (ret) {
+		dev_err(dai->dev, "fail to config asrc pair\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream,
+				struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+
+	if (pair)
+		fsl_asrc_release_pair(pair);
+
+	return 0;
+}
+
+static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		fsl_asrc_start_pair(pair);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		fsl_asrc_stop_pair(pair);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops fsl_asrc_dai_ops = {
+	.hw_params    = fsl_asrc_dai_hw_params,
+	.hw_free      = fsl_asrc_dai_hw_free,
+	.trigger      = fsl_asrc_dai_trigger,
+};
+
+static int fsl_asrc_dai_probe(struct snd_soc_dai *dai)
+{
+	struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai);
+
+	snd_soc_dai_init_dma_data(dai, &asrc_priv->dma_params_tx,
+				  &asrc_priv->dma_params_rx);
+
+	return 0;
+}
+
+#define FSL_ASRC_RATES		 SNDRV_PCM_RATE_8000_192000
+#define FSL_ASRC_FORMATS	(SNDRV_PCM_FMTBIT_S24_LE | \
+				 SNDRV_PCM_FMTBIT_S16_LE | \
+				 SNDRV_PCM_FMTBIT_S20_3LE)
+
+static struct snd_soc_dai_driver fsl_asrc_dai = {
+	.probe = fsl_asrc_dai_probe,
+	.playback = {
+		.stream_name = "ASRC-Playback",
+		.channels_min = 1,
+		.channels_max = 10,
+		.rates = FSL_ASRC_RATES,
+		.formats = FSL_ASRC_FORMATS,
+	},
+	.capture = {
+		.stream_name = "ASRC-Capture",
+		.channels_min = 1,
+		.channels_max = 10,
+		.rates = FSL_ASRC_RATES,
+		.formats = FSL_ASRC_FORMATS,
+	},
+	.ops = &fsl_asrc_dai_ops,
+};
+
+static bool fsl_asrc_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_ASRCTR:
+	case REG_ASRIER:
+	case REG_ASRCNCR:
+	case REG_ASRCFG:
+	case REG_ASRCSR:
+	case REG_ASRCDR1:
+	case REG_ASRCDR2:
+	case REG_ASRSTR:
+	case REG_ASRPM1:
+	case REG_ASRPM2:
+	case REG_ASRPM3:
+	case REG_ASRPM4:
+	case REG_ASRPM5:
+	case REG_ASRTFR1:
+	case REG_ASRCCR:
+	case REG_ASRDOA:
+	case REG_ASRDOB:
+	case REG_ASRDOC:
+	case REG_ASRIDRHA:
+	case REG_ASRIDRLA:
+	case REG_ASRIDRHB:
+	case REG_ASRIDRLB:
+	case REG_ASRIDRHC:
+	case REG_ASRIDRLC:
+	case REG_ASR76K:
+	case REG_ASR56K:
+	case REG_ASRMCRA:
+	case REG_ASRFSTA:
+	case REG_ASRMCRB:
+	case REG_ASRFSTB:
+	case REG_ASRMCRC:
+	case REG_ASRFSTC:
+	case REG_ASRMCR1A:
+	case REG_ASRMCR1B:
+	case REG_ASRMCR1C:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool fsl_asrc_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_ASRSTR:
+	case REG_ASRDIA:
+	case REG_ASRDIB:
+	case REG_ASRDIC:
+	case REG_ASRDOA:
+	case REG_ASRDOB:
+	case REG_ASRDOC:
+	case REG_ASRFSTA:
+	case REG_ASRFSTB:
+	case REG_ASRFSTC:
+	case REG_ASRCFG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool fsl_asrc_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_ASRCTR:
+	case REG_ASRIER:
+	case REG_ASRCNCR:
+	case REG_ASRCFG:
+	case REG_ASRCSR:
+	case REG_ASRCDR1:
+	case REG_ASRCDR2:
+	case REG_ASRSTR:
+	case REG_ASRPM1:
+	case REG_ASRPM2:
+	case REG_ASRPM3:
+	case REG_ASRPM4:
+	case REG_ASRPM5:
+	case REG_ASRTFR1:
+	case REG_ASRCCR:
+	case REG_ASRDIA:
+	case REG_ASRDIB:
+	case REG_ASRDIC:
+	case REG_ASRIDRHA:
+	case REG_ASRIDRLA:
+	case REG_ASRIDRHB:
+	case REG_ASRIDRLB:
+	case REG_ASRIDRHC:
+	case REG_ASRIDRLC:
+	case REG_ASR76K:
+	case REG_ASR56K:
+	case REG_ASRMCRA:
+	case REG_ASRMCRB:
+	case REG_ASRMCRC:
+	case REG_ASRMCR1A:
+	case REG_ASRMCR1B:
+	case REG_ASRMCR1C:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static struct reg_default fsl_asrc_reg[] = {
+	{ REG_ASRCTR, 0x0000 }, { REG_ASRIER, 0x0000 },
+	{ REG_ASRCNCR, 0x0000 }, { REG_ASRCFG, 0x0000 },
+	{ REG_ASRCSR, 0x0000 }, { REG_ASRCDR1, 0x0000 },
+	{ REG_ASRCDR2, 0x0000 }, { REG_ASRSTR, 0x0000 },
+	{ REG_ASRRA, 0x0000 }, { REG_ASRRB, 0x0000 },
+	{ REG_ASRRC, 0x0000 }, { REG_ASRPM1, 0x0000 },
+	{ REG_ASRPM2, 0x0000 }, { REG_ASRPM3, 0x0000 },
+	{ REG_ASRPM4, 0x0000 }, { REG_ASRPM5, 0x0000 },
+	{ REG_ASRTFR1, 0x0000 }, { REG_ASRCCR, 0x0000 },
+	{ REG_ASRDIA, 0x0000 }, { REG_ASRDOA, 0x0000 },
+	{ REG_ASRDIB, 0x0000 }, { REG_ASRDOB, 0x0000 },
+	{ REG_ASRDIC, 0x0000 }, { REG_ASRDOC, 0x0000 },
+	{ REG_ASRIDRHA, 0x0000 }, { REG_ASRIDRLA, 0x0000 },
+	{ REG_ASRIDRHB, 0x0000 }, { REG_ASRIDRLB, 0x0000 },
+	{ REG_ASRIDRHC, 0x0000 }, { REG_ASRIDRLC, 0x0000 },
+	{ REG_ASR76K, 0x0A47 }, { REG_ASR56K, 0x0DF3 },
+	{ REG_ASRMCRA, 0x0000 }, { REG_ASRFSTA, 0x0000 },
+	{ REG_ASRMCRB, 0x0000 }, { REG_ASRFSTB, 0x0000 },
+	{ REG_ASRMCRC, 0x0000 }, { REG_ASRFSTC, 0x0000 },
+	{ REG_ASRMCR1A, 0x0000 }, { REG_ASRMCR1B, 0x0000 },
+	{ REG_ASRMCR1C, 0x0000 },
+};
+
+static const struct regmap_config fsl_asrc_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+
+	.max_register = REG_ASRMCR1C,
+	.reg_defaults = fsl_asrc_reg,
+	.num_reg_defaults = ARRAY_SIZE(fsl_asrc_reg),
+	.readable_reg = fsl_asrc_readable_reg,
+	.volatile_reg = fsl_asrc_volatile_reg,
+	.writeable_reg = fsl_asrc_writeable_reg,
+	.cache_type = REGCACHE_FLAT,
+};
+
+/**
+ * Initialize ASRC registers with a default configurations
+ */
+static int fsl_asrc_init(struct fsl_asrc *asrc_priv)
+{
+	/* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */
+	regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN);
+
+	/* Disable interrupt by default */
+	regmap_write(asrc_priv->regmap, REG_ASRIER, 0x0);
+
+	/* Apply recommended settings for parameters from Reference Manual */
+	regmap_write(asrc_priv->regmap, REG_ASRPM1, 0x7fffff);
+	regmap_write(asrc_priv->regmap, REG_ASRPM2, 0x255555);
+	regmap_write(asrc_priv->regmap, REG_ASRPM3, 0xff7280);
+	regmap_write(asrc_priv->regmap, REG_ASRPM4, 0xff7280);
+	regmap_write(asrc_priv->regmap, REG_ASRPM5, 0xff7280);
+
+	/* Base address for task queue FIFO. Set to 0x7C */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1,
+			   ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc));
+
+	/* Set the processing clock for 76KHz to 133M */
+	regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6);
+
+	/* Set the processing clock for 56KHz to 133M */
+	return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947);
+}
+
+/**
+ * Interrupt handler for ASRC
+ */
+static irqreturn_t fsl_asrc_isr(int irq, void *dev_id)
+{
+	struct fsl_asrc *asrc_priv = (struct fsl_asrc *)dev_id;
+	struct device *dev = &asrc_priv->pdev->dev;
+	enum asrc_pair_index index;
+	u32 status;
+
+	regmap_read(asrc_priv->regmap, REG_ASRSTR, &status);
+
+	/* Clean overload error */
+	regmap_write(asrc_priv->regmap, REG_ASRSTR, ASRSTR_AOLE);
+
+	/*
+	 * We here use dev_dbg() for all exceptions because ASRC itself does
+	 * not care if FIFO overflowed or underrun while a warning in the
+	 * interrupt would result a ridged conversion.
+	 */
+	for (index = ASRC_PAIR_A; index < ASRC_PAIR_MAX_NUM; index++) {
+		if (!asrc_priv->pair[index])
+			continue;
+
+		if (status & ASRSTR_ATQOL) {
+			asrc_priv->pair[index]->error |= ASRC_TASK_Q_OVERLOAD;
+			dev_dbg(dev, "ASRC Task Queue FIFO overload\n");
+		}
+
+		if (status & ASRSTR_AOOL(index)) {
+			asrc_priv->pair[index]->error |= ASRC_OUTPUT_TASK_OVERLOAD;
+			pair_dbg("Output Task Overload\n");
+		}
+
+		if (status & ASRSTR_AIOL(index)) {
+			asrc_priv->pair[index]->error |= ASRC_INPUT_TASK_OVERLOAD;
+			pair_dbg("Input Task Overload\n");
+		}
+
+		if (status & ASRSTR_AODO(index)) {
+			asrc_priv->pair[index]->error |= ASRC_OUTPUT_BUFFER_OVERFLOW;
+			pair_dbg("Output Data Buffer has overflowed\n");
+		}
+
+		if (status & ASRSTR_AIDU(index)) {
+			asrc_priv->pair[index]->error |= ASRC_INPUT_BUFFER_UNDERRUN;
+			pair_dbg("Input Data Buffer has underflowed\n");
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int fsl_asrc_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct fsl_asrc *asrc_priv;
+	struct resource *res;
+	void __iomem *regs;
+	int irq, ret, i;
+	char tmp[16];
+
+	asrc_priv = devm_kzalloc(&pdev->dev, sizeof(*asrc_priv), GFP_KERNEL);
+	if (!asrc_priv)
+		return -ENOMEM;
+
+	asrc_priv->pdev = pdev;
+
+	/* Get the addresses and IRQ */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	asrc_priv->paddr = res->start;
+
+	asrc_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs,
+						      &fsl_asrc_regmap_config);
+	if (IS_ERR(asrc_priv->regmap)) {
+		dev_err(&pdev->dev, "failed to init regmap\n");
+		return PTR_ERR(asrc_priv->regmap);
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+		return irq;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq, fsl_asrc_isr, 0,
+			       dev_name(&pdev->dev), asrc_priv);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret);
+		return ret;
+	}
+
+	asrc_priv->mem_clk = devm_clk_get(&pdev->dev, "mem");
+	if (IS_ERR(asrc_priv->mem_clk)) {
+		dev_err(&pdev->dev, "failed to get mem clock\n");
+		return PTR_ERR(asrc_priv->mem_clk);
+	}
+
+	asrc_priv->ipg_clk = devm_clk_get(&pdev->dev, "ipg");
+	if (IS_ERR(asrc_priv->ipg_clk)) {
+		dev_err(&pdev->dev, "failed to get ipg clock\n");
+		return PTR_ERR(asrc_priv->ipg_clk);
+	}
+
+	asrc_priv->spba_clk = devm_clk_get(&pdev->dev, "spba");
+	if (IS_ERR(asrc_priv->spba_clk))
+		dev_warn(&pdev->dev, "failed to get spba clock\n");
+
+	for (i = 0; i < ASRC_CLK_MAX_NUM; i++) {
+		sprintf(tmp, "asrck_%x", i);
+		asrc_priv->asrck_clk[i] = devm_clk_get(&pdev->dev, tmp);
+		if (IS_ERR(asrc_priv->asrck_clk[i])) {
+			dev_err(&pdev->dev, "failed to get %s clock\n", tmp);
+			return PTR_ERR(asrc_priv->asrck_clk[i]);
+		}
+	}
+
+	if (of_device_is_compatible(np, "fsl,imx35-asrc")) {
+		asrc_priv->channel_bits = 3;
+		clk_map[IN] = input_clk_map_imx35;
+		clk_map[OUT] = output_clk_map_imx35;
+	} else {
+		asrc_priv->channel_bits = 4;
+		clk_map[IN] = input_clk_map_imx53;
+		clk_map[OUT] = output_clk_map_imx53;
+	}
+
+	ret = fsl_asrc_init(asrc_priv);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to init asrc %d\n", ret);
+		return ret;
+	}
+
+	asrc_priv->channel_avail = 10;
+
+	ret = of_property_read_u32(np, "fsl,asrc-rate",
+				   &asrc_priv->asrc_rate);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to get output rate\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(np, "fsl,asrc-width",
+				   &asrc_priv->asrc_width);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to get output width\n");
+		return ret;
+	}
+
+	if (asrc_priv->asrc_width != 16 && asrc_priv->asrc_width != 24) {
+		dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n");
+		asrc_priv->asrc_width = 24;
+	}
+
+	platform_set_drvdata(pdev, asrc_priv);
+	pm_runtime_enable(&pdev->dev);
+	spin_lock_init(&asrc_priv->lock);
+
+	ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component,
+					      &fsl_asrc_dai, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ASoC DAI\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_asrc_runtime_resume(struct device *dev)
+{
+	struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+	int i, ret;
+
+	ret = clk_prepare_enable(asrc_priv->mem_clk);
+	if (ret)
+		return ret;
+	ret = clk_prepare_enable(asrc_priv->ipg_clk);
+	if (ret)
+		goto disable_mem_clk;
+	if (!IS_ERR(asrc_priv->spba_clk)) {
+		ret = clk_prepare_enable(asrc_priv->spba_clk);
+		if (ret)
+			goto disable_ipg_clk;
+	}
+	for (i = 0; i < ASRC_CLK_MAX_NUM; i++) {
+		ret = clk_prepare_enable(asrc_priv->asrck_clk[i]);
+		if (ret)
+			goto disable_asrck_clk;
+	}
+
+	return 0;
+
+disable_asrck_clk:
+	for (i--; i >= 0; i--)
+		clk_disable_unprepare(asrc_priv->asrck_clk[i]);
+	if (!IS_ERR(asrc_priv->spba_clk))
+		clk_disable_unprepare(asrc_priv->spba_clk);
+disable_ipg_clk:
+	clk_disable_unprepare(asrc_priv->ipg_clk);
+disable_mem_clk:
+	clk_disable_unprepare(asrc_priv->mem_clk);
+	return ret;
+}
+
+static int fsl_asrc_runtime_suspend(struct device *dev)
+{
+	struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+	int i;
+
+	for (i = 0; i < ASRC_CLK_MAX_NUM; i++)
+		clk_disable_unprepare(asrc_priv->asrck_clk[i]);
+	if (!IS_ERR(asrc_priv->spba_clk))
+		clk_disable_unprepare(asrc_priv->spba_clk);
+	clk_disable_unprepare(asrc_priv->ipg_clk);
+	clk_disable_unprepare(asrc_priv->mem_clk);
+
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_asrc_suspend(struct device *dev)
+{
+	struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+
+	regmap_read(asrc_priv->regmap, REG_ASRCFG,
+		    &asrc_priv->regcache_cfg);
+
+	regcache_cache_only(asrc_priv->regmap, true);
+	regcache_mark_dirty(asrc_priv->regmap);
+
+	return 0;
+}
+
+static int fsl_asrc_resume(struct device *dev)
+{
+	struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+	u32 asrctr;
+
+	/* Stop all pairs provisionally */
+	regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr);
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+			   ASRCTR_ASRCEi_ALL_MASK, 0);
+
+	/* Restore all registers */
+	regcache_cache_only(asrc_priv->regmap, false);
+	regcache_sync(asrc_priv->regmap);
+
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCFG,
+			   ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK |
+			   ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg);
+
+	/* Restart enabled pairs */
+	regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+			   ASRCTR_ASRCEi_ALL_MASK, asrctr);
+
+	return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_asrc_pm = {
+	SET_RUNTIME_PM_OPS(fsl_asrc_runtime_suspend, fsl_asrc_runtime_resume, NULL)
+	SET_SYSTEM_SLEEP_PM_OPS(fsl_asrc_suspend, fsl_asrc_resume)
+};
+
+static const struct of_device_id fsl_asrc_ids[] = {
+	{ .compatible = "fsl,imx35-asrc", },
+	{ .compatible = "fsl,imx53-asrc", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, fsl_asrc_ids);
+
+static struct platform_driver fsl_asrc_driver = {
+	.probe = fsl_asrc_probe,
+	.driver = {
+		.name = "fsl-asrc",
+		.of_match_table = fsl_asrc_ids,
+		.pm = &fsl_asrc_pm,
+	},
+};
+module_platform_driver(fsl_asrc_driver);
+
+MODULE_DESCRIPTION("Freescale ASRC ASoC driver");
+MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>");
+MODULE_ALIAS("platform:fsl-asrc");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h
new file mode 100644
index 0000000..c600751
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc.h
@@ -0,0 +1,465 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * fsl_asrc.h - Freescale ASRC ALSA SoC header file
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <nicoleotsuka@gmail.com>
+ */
+
+#ifndef _FSL_ASRC_H
+#define _FSL_ASRC_H
+
+#define IN	0
+#define OUT	1
+
+#define ASRC_DMA_BUFFER_NUM		2
+#define ASRC_INPUTFIFO_THRESHOLD	32
+#define ASRC_OUTPUTFIFO_THRESHOLD	32
+#define ASRC_FIFO_THRESHOLD_MIN		0
+#define ASRC_FIFO_THRESHOLD_MAX		63
+#define ASRC_DMA_BUFFER_SIZE		(1024 * 48 * 4)
+#define ASRC_MAX_BUFFER_SIZE		(1024 * 48)
+#define ASRC_OUTPUT_LAST_SAMPLE		8
+
+#define IDEAL_RATIO_RATE		1000000
+
+#define REG_ASRCTR			0x00
+#define REG_ASRIER			0x04
+#define REG_ASRCNCR			0x0C
+#define REG_ASRCFG			0x10
+#define REG_ASRCSR			0x14
+
+#define REG_ASRCDR1			0x18
+#define REG_ASRCDR2			0x1C
+#define REG_ASRCDR(i)			((i < 2) ? REG_ASRCDR1 : REG_ASRCDR2)
+
+#define REG_ASRSTR			0x20
+#define REG_ASRRA			0x24
+#define REG_ASRRB			0x28
+#define REG_ASRRC			0x2C
+#define REG_ASRPM1			0x40
+#define REG_ASRPM2			0x44
+#define REG_ASRPM3			0x48
+#define REG_ASRPM4			0x4C
+#define REG_ASRPM5			0x50
+#define REG_ASRTFR1			0x54
+#define REG_ASRCCR			0x5C
+
+#define REG_ASRDIA			0x60
+#define REG_ASRDOA			0x64
+#define REG_ASRDIB			0x68
+#define REG_ASRDOB			0x6C
+#define REG_ASRDIC			0x70
+#define REG_ASRDOC			0x74
+#define REG_ASRDI(i)			(REG_ASRDIA + (i << 3))
+#define REG_ASRDO(i)			(REG_ASRDOA + (i << 3))
+#define REG_ASRDx(x, i)			((x) == IN ? REG_ASRDI(i) : REG_ASRDO(i))
+
+#define REG_ASRIDRHA			0x80
+#define REG_ASRIDRLA			0x84
+#define REG_ASRIDRHB			0x88
+#define REG_ASRIDRLB			0x8C
+#define REG_ASRIDRHC			0x90
+#define REG_ASRIDRLC			0x94
+#define REG_ASRIDRH(i)			(REG_ASRIDRHA + (i << 3))
+#define REG_ASRIDRL(i)			(REG_ASRIDRLA + (i << 3))
+
+#define REG_ASR76K			0x98
+#define REG_ASR56K			0x9C
+
+#define REG_ASRMCRA			0xA0
+#define REG_ASRFSTA			0xA4
+#define REG_ASRMCRB			0xA8
+#define REG_ASRFSTB			0xAC
+#define REG_ASRMCRC			0xB0
+#define REG_ASRFSTC			0xB4
+#define REG_ASRMCR(i)			(REG_ASRMCRA + (i << 3))
+#define REG_ASRFST(i)			(REG_ASRFSTA + (i << 3))
+
+#define REG_ASRMCR1A			0xC0
+#define REG_ASRMCR1B			0xC4
+#define REG_ASRMCR1C			0xC8
+#define REG_ASRMCR1(i)			(REG_ASRMCR1A + (i << 2))
+
+
+/* REG0 0x00 REG_ASRCTR */
+#define ASRCTR_ATSi_SHIFT(i)		(20 + i)
+#define ASRCTR_ATSi_MASK(i)		(1 << ASRCTR_ATSi_SHIFT(i))
+#define ASRCTR_ATS(i)			(1 << ASRCTR_ATSi_SHIFT(i))
+#define ASRCTR_USRi_SHIFT(i)		(14 + (i << 1))
+#define ASRCTR_USRi_MASK(i)		(1 << ASRCTR_USRi_SHIFT(i))
+#define ASRCTR_USR(i)			(1 << ASRCTR_USRi_SHIFT(i))
+#define ASRCTR_IDRi_SHIFT(i)		(13 + (i << 1))
+#define ASRCTR_IDRi_MASK(i)		(1 << ASRCTR_IDRi_SHIFT(i))
+#define ASRCTR_IDR(i)			(1 << ASRCTR_IDRi_SHIFT(i))
+#define ASRCTR_SRST_SHIFT		4
+#define ASRCTR_SRST_MASK		(1 << ASRCTR_SRST_SHIFT)
+#define ASRCTR_SRST			(1 << ASRCTR_SRST_SHIFT)
+#define ASRCTR_ASRCEi_SHIFT(i)		(1 + i)
+#define ASRCTR_ASRCEi_MASK(i)		(1 << ASRCTR_ASRCEi_SHIFT(i))
+#define ASRCTR_ASRCE(i)			(1 << ASRCTR_ASRCEi_SHIFT(i))
+#define ASRCTR_ASRCEi_ALL_MASK		(0x7 << ASRCTR_ASRCEi_SHIFT(0))
+#define ASRCTR_ASRCEN_SHIFT		0
+#define ASRCTR_ASRCEN_MASK		(1 << ASRCTR_ASRCEN_SHIFT)
+#define ASRCTR_ASRCEN			(1 << ASRCTR_ASRCEN_SHIFT)
+
+/* REG1 0x04 REG_ASRIER */
+#define ASRIER_AFPWE_SHIFT		7
+#define ASRIER_AFPWE_MASK		(1 << ASRIER_AFPWE_SHIFT)
+#define ASRIER_AFPWE			(1 << ASRIER_AFPWE_SHIFT)
+#define ASRIER_AOLIE_SHIFT		6
+#define ASRIER_AOLIE_MASK		(1 << ASRIER_AOLIE_SHIFT)
+#define ASRIER_AOLIE			(1 << ASRIER_AOLIE_SHIFT)
+#define ASRIER_ADOEi_SHIFT(i)		(3 + i)
+#define ASRIER_ADOEi_MASK(i)		(1 << ASRIER_ADOEi_SHIFT(i))
+#define ASRIER_ADOE(i)			(1 << ASRIER_ADOEi_SHIFT(i))
+#define ASRIER_ADIEi_SHIFT(i)		(0 + i)
+#define ASRIER_ADIEi_MASK(i)		(1 << ASRIER_ADIEi_SHIFT(i))
+#define ASRIER_ADIE(i)			(1 << ASRIER_ADIEi_SHIFT(i))
+
+/* REG2 0x0C REG_ASRCNCR */
+#define ASRCNCR_ANCi_SHIFT(i, b)	(b * i)
+#define ASRCNCR_ANCi_MASK(i, b)		(((1 << b) - 1) << ASRCNCR_ANCi_SHIFT(i, b))
+#define ASRCNCR_ANCi(i, v, b)		((v << ASRCNCR_ANCi_SHIFT(i, b)) & ASRCNCR_ANCi_MASK(i, b))
+
+/* REG3 0x10 REG_ASRCFG */
+#define ASRCFG_INIRQi_SHIFT(i)		(21 + i)
+#define ASRCFG_INIRQi_MASK(i)		(1 << ASRCFG_INIRQi_SHIFT(i))
+#define ASRCFG_INIRQi			(1 << ASRCFG_INIRQi_SHIFT(i))
+#define ASRCFG_NDPRi_SHIFT(i)		(18 + i)
+#define ASRCFG_NDPRi_MASK(i)		(1 << ASRCFG_NDPRi_SHIFT(i))
+#define ASRCFG_NDPRi_ALL_SHIFT		18
+#define ASRCFG_NDPRi_ALL_MASK		(7 << ASRCFG_NDPRi_ALL_SHIFT)
+#define ASRCFG_NDPRi			(1 << ASRCFG_NDPRi_SHIFT(i))
+#define ASRCFG_POSTMODi_SHIFT(i)	(8 + (i << 2))
+#define ASRCFG_POSTMODi_WIDTH		2
+#define ASRCFG_POSTMODi_MASK(i)		(((1 << ASRCFG_POSTMODi_WIDTH) - 1) << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_POSTMODi_ALL_MASK	(ASRCFG_POSTMODi_MASK(0) | ASRCFG_POSTMODi_MASK(1) | ASRCFG_POSTMODi_MASK(2))
+#define ASRCFG_POSTMOD(i, v)		((v) << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_POSTMODi_UP(i)		(0 << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_POSTMODi_DCON(i)		(1 << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_POSTMODi_DOWN(i)		(2 << ASRCFG_POSTMODi_SHIFT(i))
+#define ASRCFG_PREMODi_SHIFT(i)		(6 + (i << 2))
+#define ASRCFG_PREMODi_WIDTH		2
+#define ASRCFG_PREMODi_MASK(i)		(((1 << ASRCFG_PREMODi_WIDTH) - 1) << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMODi_ALL_MASK		(ASRCFG_PREMODi_MASK(0) | ASRCFG_PREMODi_MASK(1) | ASRCFG_PREMODi_MASK(2))
+#define ASRCFG_PREMOD(i, v)		((v) << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMODi_UP(i)		(0 << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMODi_DCON(i)		(1 << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMODi_DOWN(i)		(2 << ASRCFG_PREMODi_SHIFT(i))
+#define ASRCFG_PREMODi_BYPASS(i)	(3 << ASRCFG_PREMODi_SHIFT(i))
+
+/* REG4 0x14 REG_ASRCSR */
+#define ASRCSR_AxCSi_WIDTH		4
+#define ASRCSR_AxCSi_MASK		((1 << ASRCSR_AxCSi_WIDTH) - 1)
+#define ASRCSR_AOCSi_SHIFT(i)		(12 + (i << 2))
+#define ASRCSR_AOCSi_MASK(i)		(((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AOCSi_SHIFT(i))
+#define ASRCSR_AOCS(i, v)		((v) << ASRCSR_AOCSi_SHIFT(i))
+#define ASRCSR_AICSi_SHIFT(i)		(i << 2)
+#define ASRCSR_AICSi_MASK(i)		(((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AICSi_SHIFT(i))
+#define ASRCSR_AICS(i, v)		((v) << ASRCSR_AICSi_SHIFT(i))
+
+/* REG5&6 0x18 & 0x1C REG_ASRCDR1 & ASRCDR2 */
+#define ASRCDRi_AxCPi_WIDTH		3
+#define ASRCDRi_AICPi_SHIFT(i)		(0 + (i % 2) * 6)
+#define ASRCDRi_AICPi_MASK(i)		(((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICPi_SHIFT(i))
+#define ASRCDRi_AICP(i, v)		((v) << ASRCDRi_AICPi_SHIFT(i))
+#define ASRCDRi_AICDi_SHIFT(i)		(3 + (i % 2) * 6)
+#define ASRCDRi_AICDi_MASK(i)		(((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICDi_SHIFT(i))
+#define ASRCDRi_AICD(i, v)		((v) << ASRCDRi_AICDi_SHIFT(i))
+#define ASRCDRi_AOCPi_SHIFT(i)		((i < 2) ? 12 + i * 6 : 6)
+#define ASRCDRi_AOCPi_MASK(i)		(((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCPi_SHIFT(i))
+#define ASRCDRi_AOCP(i, v)		((v) << ASRCDRi_AOCPi_SHIFT(i))
+#define ASRCDRi_AOCDi_SHIFT(i)		((i < 2) ? 15 + i * 6 : 9)
+#define ASRCDRi_AOCDi_MASK(i)		(((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCDi_SHIFT(i))
+#define ASRCDRi_AOCD(i, v)		((v) << ASRCDRi_AOCDi_SHIFT(i))
+
+/* REG7 0x20 REG_ASRSTR */
+#define ASRSTR_DSLCNT_SHIFT		21
+#define ASRSTR_DSLCNT_MASK		(1 << ASRSTR_DSLCNT_SHIFT)
+#define ASRSTR_DSLCNT			(1 << ASRSTR_DSLCNT_SHIFT)
+#define ASRSTR_ATQOL_SHIFT		20
+#define ASRSTR_ATQOL_MASK		(1 << ASRSTR_ATQOL_SHIFT)
+#define ASRSTR_ATQOL			(1 << ASRSTR_ATQOL_SHIFT)
+#define ASRSTR_AOOLi_SHIFT(i)		(17 + i)
+#define ASRSTR_AOOLi_MASK(i)		(1 << ASRSTR_AOOLi_SHIFT(i))
+#define ASRSTR_AOOL(i)			(1 << ASRSTR_AOOLi_SHIFT(i))
+#define ASRSTR_AIOLi_SHIFT(i)		(14 + i)
+#define ASRSTR_AIOLi_MASK(i)		(1 << ASRSTR_AIOLi_SHIFT(i))
+#define ASRSTR_AIOL(i)			(1 << ASRSTR_AIOLi_SHIFT(i))
+#define ASRSTR_AODOi_SHIFT(i)		(11 + i)
+#define ASRSTR_AODOi_MASK(i)		(1 << ASRSTR_AODOi_SHIFT(i))
+#define ASRSTR_AODO(i)			(1 << ASRSTR_AODOi_SHIFT(i))
+#define ASRSTR_AIDUi_SHIFT(i)		(8 + i)
+#define ASRSTR_AIDUi_MASK(i)		(1 << ASRSTR_AIDUi_SHIFT(i))
+#define ASRSTR_AIDU(i)			(1 << ASRSTR_AIDUi_SHIFT(i))
+#define ASRSTR_FPWT_SHIFT		7
+#define ASRSTR_FPWT_MASK		(1 << ASRSTR_FPWT_SHIFT)
+#define ASRSTR_FPWT			(1 << ASRSTR_FPWT_SHIFT)
+#define ASRSTR_AOLE_SHIFT		6
+#define ASRSTR_AOLE_MASK		(1 << ASRSTR_AOLE_SHIFT)
+#define ASRSTR_AOLE			(1 << ASRSTR_AOLE_SHIFT)
+#define ASRSTR_AODEi_SHIFT(i)		(3 + i)
+#define ASRSTR_AODFi_MASK(i)		(1 << ASRSTR_AODEi_SHIFT(i))
+#define ASRSTR_AODF(i)			(1 << ASRSTR_AODEi_SHIFT(i))
+#define ASRSTR_AIDEi_SHIFT(i)		(0 + i)
+#define ASRSTR_AIDEi_MASK(i)		(1 << ASRSTR_AIDEi_SHIFT(i))
+#define ASRSTR_AIDE(i)			(1 << ASRSTR_AIDEi_SHIFT(i))
+
+/* REG10 0x54 REG_ASRTFR1 */
+#define ASRTFR1_TF_BASE_WIDTH		7
+#define ASRTFR1_TF_BASE_SHIFT		6
+#define ASRTFR1_TF_BASE_MASK		(((1 << ASRTFR1_TF_BASE_WIDTH) - 1) << ASRTFR1_TF_BASE_SHIFT)
+#define ASRTFR1_TF_BASE(i)		((i) << ASRTFR1_TF_BASE_SHIFT)
+
+/*
+ * REG22 0xA0 REG_ASRMCRA
+ * REG24 0xA8 REG_ASRMCRB
+ * REG26 0xB0 REG_ASRMCRC
+ */
+#define ASRMCRi_ZEROBUFi_SHIFT		23
+#define ASRMCRi_ZEROBUFi_MASK		(1 << ASRMCRi_ZEROBUFi_SHIFT)
+#define ASRMCRi_ZEROBUFi		(1 << ASRMCRi_ZEROBUFi_SHIFT)
+#define ASRMCRi_EXTTHRSHi_SHIFT		22
+#define ASRMCRi_EXTTHRSHi_MASK		(1 << ASRMCRi_EXTTHRSHi_SHIFT)
+#define ASRMCRi_EXTTHRSHi		(1 << ASRMCRi_EXTTHRSHi_SHIFT)
+#define ASRMCRi_BUFSTALLi_SHIFT		21
+#define ASRMCRi_BUFSTALLi_MASK		(1 << ASRMCRi_BUFSTALLi_SHIFT)
+#define ASRMCRi_BUFSTALLi		(1 << ASRMCRi_BUFSTALLi_SHIFT)
+#define ASRMCRi_BYPASSPOLYi_SHIFT	20
+#define ASRMCRi_BYPASSPOLYi_MASK	(1 << ASRMCRi_BYPASSPOLYi_SHIFT)
+#define ASRMCRi_BYPASSPOLYi		(1 << ASRMCRi_BYPASSPOLYi_SHIFT)
+#define ASRMCRi_OUTFIFO_THRESHOLD_WIDTH	6
+#define ASRMCRi_OUTFIFO_THRESHOLD_SHIFT	12
+#define ASRMCRi_OUTFIFO_THRESHOLD_MASK	(((1 << ASRMCRi_OUTFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT)
+#define ASRMCRi_OUTFIFO_THRESHOLD(v)	(((v) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT) & ASRMCRi_OUTFIFO_THRESHOLD_MASK)
+#define ASRMCRi_RSYNIFi_SHIFT		11
+#define ASRMCRi_RSYNIFi_MASK		(1 << ASRMCRi_RSYNIFi_SHIFT)
+#define ASRMCRi_RSYNIFi			(1 << ASRMCRi_RSYNIFi_SHIFT)
+#define ASRMCRi_RSYNOFi_SHIFT		10
+#define ASRMCRi_RSYNOFi_MASK		(1 << ASRMCRi_RSYNOFi_SHIFT)
+#define ASRMCRi_RSYNOFi			(1 << ASRMCRi_RSYNOFi_SHIFT)
+#define ASRMCRi_INFIFO_THRESHOLD_WIDTH	6
+#define ASRMCRi_INFIFO_THRESHOLD_SHIFT	0
+#define ASRMCRi_INFIFO_THRESHOLD_MASK	(((1 << ASRMCRi_INFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_INFIFO_THRESHOLD_SHIFT)
+#define ASRMCRi_INFIFO_THRESHOLD(v)	(((v) << ASRMCRi_INFIFO_THRESHOLD_SHIFT) & ASRMCRi_INFIFO_THRESHOLD_MASK)
+
+/*
+ * REG23 0xA4 REG_ASRFSTA
+ * REG25 0xAC REG_ASRFSTB
+ * REG27 0xB4 REG_ASRFSTC
+ */
+#define ASRFSTi_OAFi_SHIFT		23
+#define ASRFSTi_OAFi_MASK		(1 << ASRFSTi_OAFi_SHIFT)
+#define ASRFSTi_OAFi			(1 << ASRFSTi_OAFi_SHIFT)
+#define ASRFSTi_OUTPUT_FIFO_WIDTH	7
+#define ASRFSTi_OUTPUT_FIFO_SHIFT	12
+#define ASRFSTi_OUTPUT_FIFO_MASK	(((1 << ASRFSTi_OUTPUT_FIFO_WIDTH) - 1) << ASRFSTi_OUTPUT_FIFO_SHIFT)
+#define ASRFSTi_IAEi_SHIFT		11
+#define ASRFSTi_IAEi_MASK		(1 << ASRFSTi_IAEi_SHIFT)
+#define ASRFSTi_IAEi			(1 << ASRFSTi_IAEi_SHIFT)
+#define ASRFSTi_INPUT_FIFO_WIDTH	7
+#define ASRFSTi_INPUT_FIFO_SHIFT	0
+#define ASRFSTi_INPUT_FIFO_MASK		((1 << ASRFSTi_INPUT_FIFO_WIDTH) - 1)
+
+/* REG28 0xC0 & 0xC4 & 0xC8 REG_ASRMCR1i */
+#define ASRMCR1i_IWD_WIDTH		3
+#define ASRMCR1i_IWD_SHIFT		9
+#define ASRMCR1i_IWD_MASK		(((1 << ASRMCR1i_IWD_WIDTH) - 1) << ASRMCR1i_IWD_SHIFT)
+#define ASRMCR1i_IWD(v)			((v) << ASRMCR1i_IWD_SHIFT)
+#define ASRMCR1i_IMSB_SHIFT		8
+#define ASRMCR1i_IMSB_MASK		(1 << ASRMCR1i_IMSB_SHIFT)
+#define ASRMCR1i_IMSB_MSB		(1 << ASRMCR1i_IMSB_SHIFT)
+#define ASRMCR1i_IMSB_LSB		(0 << ASRMCR1i_IMSB_SHIFT)
+#define ASRMCR1i_OMSB_SHIFT		2
+#define ASRMCR1i_OMSB_MASK		(1 << ASRMCR1i_OMSB_SHIFT)
+#define ASRMCR1i_OMSB_MSB		(1 << ASRMCR1i_OMSB_SHIFT)
+#define ASRMCR1i_OMSB_LSB		(0 << ASRMCR1i_OMSB_SHIFT)
+#define ASRMCR1i_OSGN_SHIFT		1
+#define ASRMCR1i_OSGN_MASK		(1 << ASRMCR1i_OSGN_SHIFT)
+#define ASRMCR1i_OSGN			(1 << ASRMCR1i_OSGN_SHIFT)
+#define ASRMCR1i_OW16_SHIFT		0
+#define ASRMCR1i_OW16_MASK		(1 << ASRMCR1i_OW16_SHIFT)
+#define ASRMCR1i_OW16(v)		((v) << ASRMCR1i_OW16_SHIFT)
+
+
+enum asrc_pair_index {
+	ASRC_INVALID_PAIR = -1,
+	ASRC_PAIR_A = 0,
+	ASRC_PAIR_B = 1,
+	ASRC_PAIR_C = 2,
+};
+
+#define ASRC_PAIR_MAX_NUM	(ASRC_PAIR_C + 1)
+
+enum asrc_inclk {
+	INCLK_NONE = 0x03,
+	INCLK_ESAI_RX = 0x00,
+	INCLK_SSI1_RX = 0x01,
+	INCLK_SSI2_RX = 0x02,
+	INCLK_SSI3_RX = 0x07,
+	INCLK_SPDIF_RX = 0x04,
+	INCLK_MLB_CLK = 0x05,
+	INCLK_PAD = 0x06,
+	INCLK_ESAI_TX = 0x08,
+	INCLK_SSI1_TX = 0x09,
+	INCLK_SSI2_TX = 0x0a,
+	INCLK_SSI3_TX = 0x0b,
+	INCLK_SPDIF_TX = 0x0c,
+	INCLK_ASRCK1_CLK = 0x0f,
+};
+
+enum asrc_outclk {
+	OUTCLK_NONE = 0x03,
+	OUTCLK_ESAI_TX = 0x00,
+	OUTCLK_SSI1_TX = 0x01,
+	OUTCLK_SSI2_TX = 0x02,
+	OUTCLK_SSI3_TX = 0x07,
+	OUTCLK_SPDIF_TX = 0x04,
+	OUTCLK_MLB_CLK = 0x05,
+	OUTCLK_PAD = 0x06,
+	OUTCLK_ESAI_RX = 0x08,
+	OUTCLK_SSI1_RX = 0x09,
+	OUTCLK_SSI2_RX = 0x0a,
+	OUTCLK_SSI3_RX = 0x0b,
+	OUTCLK_SPDIF_RX = 0x0c,
+	OUTCLK_ASRCK1_CLK = 0x0f,
+};
+
+#define ASRC_CLK_MAX_NUM	16
+
+enum asrc_word_width {
+	ASRC_WIDTH_24_BIT = 0,
+	ASRC_WIDTH_16_BIT = 1,
+	ASRC_WIDTH_8_BIT = 2,
+};
+
+struct asrc_config {
+	enum asrc_pair_index pair;
+	unsigned int channel_num;
+	unsigned int buffer_num;
+	unsigned int dma_buffer_size;
+	unsigned int input_sample_rate;
+	unsigned int output_sample_rate;
+	enum asrc_word_width input_word_width;
+	enum asrc_word_width output_word_width;
+	enum asrc_inclk inclk;
+	enum asrc_outclk outclk;
+};
+
+struct asrc_req {
+	unsigned int chn_num;
+	enum asrc_pair_index index;
+};
+
+struct asrc_querybuf {
+	unsigned int buffer_index;
+	unsigned int input_length;
+	unsigned int output_length;
+	unsigned long input_offset;
+	unsigned long output_offset;
+};
+
+struct asrc_convert_buffer {
+	void *input_buffer_vaddr;
+	void *output_buffer_vaddr;
+	unsigned int input_buffer_length;
+	unsigned int output_buffer_length;
+};
+
+struct asrc_status_flags {
+	enum asrc_pair_index index;
+	unsigned int overload_error;
+};
+
+enum asrc_error_status {
+	ASRC_TASK_Q_OVERLOAD		= 0x01,
+	ASRC_OUTPUT_TASK_OVERLOAD	= 0x02,
+	ASRC_INPUT_TASK_OVERLOAD	= 0x04,
+	ASRC_OUTPUT_BUFFER_OVERFLOW	= 0x08,
+	ASRC_INPUT_BUFFER_UNDERRUN	= 0x10,
+};
+
+struct dma_block {
+	dma_addr_t dma_paddr;
+	void *dma_vaddr;
+	unsigned int length;
+};
+
+/**
+ * fsl_asrc_pair: ASRC Pair private data
+ *
+ * @asrc_priv: pointer to its parent module
+ * @config: configuration profile
+ * @error: error record
+ * @index: pair index (ASRC_PAIR_A, ASRC_PAIR_B, ASRC_PAIR_C)
+ * @channels: occupied channel number
+ * @desc: input and output dma descriptors
+ * @dma_chan: inputer and output DMA channels
+ * @dma_data: private dma data
+ * @pos: hardware pointer position
+ * @private: pair private area
+ */
+struct fsl_asrc_pair {
+	struct fsl_asrc *asrc_priv;
+	struct asrc_config *config;
+	unsigned int error;
+
+	enum asrc_pair_index index;
+	unsigned int channels;
+
+	struct dma_async_tx_descriptor *desc[2];
+	struct dma_chan *dma_chan[2];
+	struct imx_dma_data dma_data;
+	unsigned int pos;
+
+	void *private;
+};
+
+/**
+ * fsl_asrc_pair: ASRC private data
+ *
+ * @dma_params_rx: DMA parameters for receive channel
+ * @dma_params_tx: DMA parameters for transmit channel
+ * @pdev: platform device pointer
+ * @regmap: regmap handler
+ * @paddr: physical address to the base address of registers
+ * @mem_clk: clock source to access register
+ * @ipg_clk: clock source to drive peripheral
+ * @spba_clk: SPBA clock (optional, depending on SoC design)
+ * @asrck_clk: clock sources to driver ASRC internal logic
+ * @lock: spin lock for resource protection
+ * @pair: pair pointers
+ * @channel_bits: width of ASRCNCR register for each pair
+ * @channel_avail: non-occupied channel numbers
+ * @asrc_rate: default sample rate for ASoC Back-Ends
+ * @asrc_width: default sample width for ASoC Back-Ends
+ * @regcache_cfg: store register value of REG_ASRCFG
+ */
+struct fsl_asrc {
+	struct snd_dmaengine_dai_dma_data dma_params_rx;
+	struct snd_dmaengine_dai_dma_data dma_params_tx;
+	struct platform_device *pdev;
+	struct regmap *regmap;
+	unsigned long paddr;
+	struct clk *mem_clk;
+	struct clk *ipg_clk;
+	struct clk *spba_clk;
+	struct clk *asrck_clk[ASRC_CLK_MAX_NUM];
+	spinlock_t lock;
+
+	struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM];
+	unsigned int channel_bits;
+	unsigned int channel_avail;
+
+	int asrc_rate;
+	int asrc_width;
+
+	u32 regcache_cfg;
+};
+
+#define DRV_NAME "fsl-asrc-dai"
+extern struct snd_soc_component_driver fsl_asrc_component;
+struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir);
+#endif /* _FSL_ASRC_H */
diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c
new file mode 100644
index 0000000..1033ac6
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc_dma.c
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale ASRC ALSA SoC Platform (DMA) driver
+//
+// Copyright (C) 2014 Freescale Semiconductor, Inc.
+//
+// Author: Nicolin Chen <nicoleotsuka@gmail.com>
+
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/platform_data/dma-imx.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_asrc.h"
+
+#define FSL_ASRC_DMABUF_SIZE	(256 * 1024)
+
+static const struct snd_pcm_hardware snd_imx_hardware = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_RESUME,
+	.buffer_bytes_max = FSL_ASRC_DMABUF_SIZE,
+	.period_bytes_min = 128,
+	.period_bytes_max = 65535, /* Limited by SDMA engine */
+	.periods_min = 2,
+	.periods_max = 255,
+	.fifo_size = 0,
+};
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+	if (!imx_dma_is_general_purpose(chan))
+		return false;
+
+	chan->private = param;
+
+	return true;
+}
+
+static void fsl_asrc_dma_complete(void *arg)
+{
+	struct snd_pcm_substream *substream = arg;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+
+	pair->pos += snd_pcm_lib_period_bytes(substream);
+	if (pair->pos >= snd_pcm_lib_buffer_bytes(substream))
+		pair->pos = 0;
+
+	snd_pcm_period_elapsed(substream);
+}
+
+static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream)
+{
+	u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct device *dev = component->dev;
+	unsigned long flags = DMA_CTRL_ACK;
+
+	/* Prepare and submit Front-End DMA channel */
+	if (!substream->runtime->no_period_wakeup)
+		flags |= DMA_PREP_INTERRUPT;
+
+	pair->pos = 0;
+	pair->desc[!dir] = dmaengine_prep_dma_cyclic(
+			pair->dma_chan[!dir], runtime->dma_addr,
+			snd_pcm_lib_buffer_bytes(substream),
+			snd_pcm_lib_period_bytes(substream),
+			dir == OUT ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, flags);
+	if (!pair->desc[!dir]) {
+		dev_err(dev, "failed to prepare slave DMA for Front-End\n");
+		return -ENOMEM;
+	}
+
+	pair->desc[!dir]->callback = fsl_asrc_dma_complete;
+	pair->desc[!dir]->callback_param = substream;
+
+	dmaengine_submit(pair->desc[!dir]);
+
+	/* Prepare and submit Back-End DMA channel */
+	pair->desc[dir] = dmaengine_prep_dma_cyclic(
+			pair->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0);
+	if (!pair->desc[dir]) {
+		dev_err(dev, "failed to prepare slave DMA for Back-End\n");
+		return -ENOMEM;
+	}
+
+	dmaengine_submit(pair->desc[dir]);
+
+	return 0;
+}
+
+static int fsl_asrc_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+	int ret;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		ret = fsl_asrc_dma_prepare_and_submit(substream);
+		if (ret)
+			return ret;
+		dma_async_issue_pending(pair->dma_chan[IN]);
+		dma_async_issue_pending(pair->dma_chan[OUT]);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dmaengine_terminate_all(pair->dma_chan[OUT]);
+		dmaengine_terminate_all(pair->dma_chan[IN]);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params)
+{
+	enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL;
+	struct snd_dmaengine_dai_dma_data *dma_params_be = NULL;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+	struct fsl_asrc *asrc_priv = pair->asrc_priv;
+	struct dma_slave_config config_fe, config_be;
+	enum asrc_pair_index index = pair->index;
+	struct device *dev = component->dev;
+	int stream = substream->stream;
+	struct imx_dma_data *tmp_data;
+	struct snd_soc_dpcm *dpcm;
+	struct dma_chan *tmp_chan;
+	struct device *dev_be;
+	u8 dir = tx ? OUT : IN;
+	dma_cap_mask_t mask;
+	int ret;
+
+	/* Fetch the Back-End dma_data from DPCM */
+	list_for_each_entry(dpcm, &rtd->dpcm[stream].be_clients, list_be) {
+		struct snd_soc_pcm_runtime *be = dpcm->be;
+		struct snd_pcm_substream *substream_be;
+		struct snd_soc_dai *dai = be->cpu_dai;
+
+		if (dpcm->fe != rtd)
+			continue;
+
+		substream_be = snd_soc_dpcm_get_substream(be, stream);
+		dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be);
+		dev_be = dai->dev;
+		break;
+	}
+
+	if (!dma_params_be) {
+		dev_err(dev, "failed to get the substream of Back-End\n");
+		return -EINVAL;
+	}
+
+	/* Override dma_data of the Front-End and config its dmaengine */
+	dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+	dma_params_fe->addr = asrc_priv->paddr + REG_ASRDx(!dir, index);
+	dma_params_fe->maxburst = dma_params_be->maxburst;
+
+	pair->dma_chan[!dir] = fsl_asrc_get_dma_channel(pair, !dir);
+	if (!pair->dma_chan[!dir]) {
+		dev_err(dev, "failed to request DMA channel\n");
+		return -EINVAL;
+	}
+
+	memset(&config_fe, 0, sizeof(config_fe));
+	ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe);
+	if (ret) {
+		dev_err(dev, "failed to prepare DMA config for Front-End\n");
+		return ret;
+	}
+
+	ret = dmaengine_slave_config(pair->dma_chan[!dir], &config_fe);
+	if (ret) {
+		dev_err(dev, "failed to config DMA channel for Front-End\n");
+		return ret;
+	}
+
+	/* Request and config DMA channel for Back-End */
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+	dma_cap_set(DMA_CYCLIC, mask);
+
+	/* Get DMA request of Back-End */
+	tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx");
+	tmp_data = tmp_chan->private;
+	pair->dma_data.dma_request = tmp_data->dma_request;
+	dma_release_channel(tmp_chan);
+
+	/* Get DMA request of Front-End */
+	tmp_chan = fsl_asrc_get_dma_channel(pair, dir);
+	tmp_data = tmp_chan->private;
+	pair->dma_data.dma_request2 = tmp_data->dma_request;
+	pair->dma_data.peripheral_type = tmp_data->peripheral_type;
+	pair->dma_data.priority = tmp_data->priority;
+	dma_release_channel(tmp_chan);
+
+	pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data);
+	if (!pair->dma_chan[dir]) {
+		dev_err(dev, "failed to request DMA channel for Back-End\n");
+		return -EINVAL;
+	}
+
+	if (asrc_priv->asrc_width == 16)
+		buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+	else
+		buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+	config_be.direction = DMA_DEV_TO_DEV;
+	config_be.src_addr_width = buswidth;
+	config_be.src_maxburst = dma_params_be->maxburst;
+	config_be.dst_addr_width = buswidth;
+	config_be.dst_maxburst = dma_params_be->maxburst;
+
+	if (tx) {
+		config_be.src_addr = asrc_priv->paddr + REG_ASRDO(index);
+		config_be.dst_addr = dma_params_be->addr;
+	} else {
+		config_be.dst_addr = asrc_priv->paddr + REG_ASRDI(index);
+		config_be.src_addr = dma_params_be->addr;
+	}
+
+	ret = dmaengine_slave_config(pair->dma_chan[dir], &config_be);
+	if (ret) {
+		dev_err(dev, "failed to config DMA channel for Back-End\n");
+		return ret;
+	}
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+	return 0;
+}
+
+static int fsl_asrc_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+
+	snd_pcm_set_runtime_buffer(substream, NULL);
+
+	if (pair->dma_chan[IN])
+		dma_release_channel(pair->dma_chan[IN]);
+
+	if (pair->dma_chan[OUT])
+		dma_release_channel(pair->dma_chan[OUT]);
+
+	pair->dma_chan[IN] = NULL;
+	pair->dma_chan[OUT] = NULL;
+
+	return 0;
+}
+
+static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct device *dev = component->dev;
+	struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+	struct fsl_asrc_pair *pair;
+
+	pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL);
+	if (!pair)
+		return -ENOMEM;
+
+	pair->asrc_priv = asrc_priv;
+
+	runtime->private_data = pair;
+
+	snd_pcm_hw_constraint_integer(substream->runtime,
+				      SNDRV_PCM_HW_PARAM_PERIODS);
+	snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+	return 0;
+}
+
+static int fsl_asrc_dma_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+	struct fsl_asrc *asrc_priv;
+
+	if (!pair)
+		return 0;
+
+	asrc_priv = pair->asrc_priv;
+
+	if (asrc_priv->pair[pair->index] == pair)
+		asrc_priv->pair[pair->index] = NULL;
+
+	kfree(pair);
+
+	return 0;
+}
+
+static snd_pcm_uframes_t fsl_asrc_dma_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_asrc_pair *pair = runtime->private_data;
+
+	return bytes_to_frames(substream->runtime, pair->pos);
+}
+
+static const struct snd_pcm_ops fsl_asrc_dma_pcm_ops = {
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= fsl_asrc_dma_hw_params,
+	.hw_free	= fsl_asrc_dma_hw_free,
+	.trigger	= fsl_asrc_dma_trigger,
+	.open		= fsl_asrc_dma_startup,
+	.close		= fsl_asrc_dma_shutdown,
+	.pointer	= fsl_asrc_dma_pcm_pointer,
+};
+
+static int fsl_asrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_card *card = rtd->card->snd_card;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm *pcm = rtd->pcm;
+	int ret, i;
+
+	ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+	if (ret) {
+		dev_err(card->dev, "failed to set DMA mask\n");
+		return ret;
+	}
+
+	for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) {
+		substream = pcm->streams[i].substream;
+		if (!substream)
+			continue;
+
+		ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+				FSL_ASRC_DMABUF_SIZE, &substream->dma_buffer);
+		if (ret) {
+			dev_err(card->dev, "failed to allocate DMA buffer\n");
+			goto err;
+		}
+	}
+
+	return 0;
+
+err:
+	if (--i == 0 && pcm->streams[i].substream)
+		snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer);
+
+	return ret;
+}
+
+static void fsl_asrc_dma_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	int i;
+
+	for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) {
+		substream = pcm->streams[i].substream;
+		if (!substream)
+			continue;
+
+		snd_dma_free_pages(&substream->dma_buffer);
+		substream->dma_buffer.area = NULL;
+		substream->dma_buffer.addr = 0;
+	}
+}
+
+struct snd_soc_component_driver fsl_asrc_component = {
+	.name		= DRV_NAME,
+	.ops		= &fsl_asrc_dma_pcm_ops,
+	.pcm_new	= fsl_asrc_dma_pcm_new,
+	.pcm_free	= fsl_asrc_dma_pcm_free,
+};
+EXPORT_SYMBOL_GPL(fsl_asrc_component);
diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c
new file mode 100644
index 0000000..78871de
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.c
@@ -0,0 +1,981 @@
+/*
+ * Freescale DMA ALSA SoC PCM driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ * This driver implements ASoC support for the Elo DMA controller, which is
+ * the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms,
+ * the PCM driver is what handles the DMA buffer.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/gfp.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/io.h>
+
+#include "fsl_dma.h"
+#include "fsl_ssi.h"	/* For the offset of stx0 and srx0 */
+
+#define DRV_NAME "fsl_dma"
+
+/*
+ * The formats that the DMA controller supports, which is anything
+ * that is 8, 16, or 32 bits.
+ */
+#define FSLDMA_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 	| \
+			    SNDRV_PCM_FMTBIT_U8 	| \
+			    SNDRV_PCM_FMTBIT_S16_LE     | \
+			    SNDRV_PCM_FMTBIT_S16_BE     | \
+			    SNDRV_PCM_FMTBIT_U16_LE     | \
+			    SNDRV_PCM_FMTBIT_U16_BE     | \
+			    SNDRV_PCM_FMTBIT_S24_LE     | \
+			    SNDRV_PCM_FMTBIT_S24_BE     | \
+			    SNDRV_PCM_FMTBIT_U24_LE     | \
+			    SNDRV_PCM_FMTBIT_U24_BE     | \
+			    SNDRV_PCM_FMTBIT_S32_LE     | \
+			    SNDRV_PCM_FMTBIT_S32_BE     | \
+			    SNDRV_PCM_FMTBIT_U32_LE     | \
+			    SNDRV_PCM_FMTBIT_U32_BE)
+struct dma_object {
+	struct snd_soc_component_driver dai;
+	dma_addr_t ssi_stx_phys;
+	dma_addr_t ssi_srx_phys;
+	unsigned int ssi_fifo_depth;
+	struct ccsr_dma_channel __iomem *channel;
+	unsigned int irq;
+	bool assigned;
+};
+
+/*
+ * The number of DMA links to use.  Two is the bare minimum, but if you
+ * have really small links you might need more.
+ */
+#define NUM_DMA_LINKS   2
+
+/** fsl_dma_private: p-substream DMA data
+ *
+ * Each substream has a 1-to-1 association with a DMA channel.
+ *
+ * The link[] array is first because it needs to be aligned on a 32-byte
+ * boundary, so putting it first will ensure alignment without padding the
+ * structure.
+ *
+ * @link[]: array of link descriptors
+ * @dma_channel: pointer to the DMA channel's registers
+ * @irq: IRQ for this DMA channel
+ * @substream: pointer to the substream object, needed by the ISR
+ * @ssi_sxx_phys: bus address of the STX or SRX register to use
+ * @ld_buf_phys: physical address of the LD buffer
+ * @current_link: index into link[] of the link currently being processed
+ * @dma_buf_phys: physical address of the DMA buffer
+ * @dma_buf_next: physical address of the next period to process
+ * @dma_buf_end: physical address of the byte after the end of the DMA
+ * @buffer period_size: the size of a single period
+ * @num_periods: the number of periods in the DMA buffer
+ */
+struct fsl_dma_private {
+	struct fsl_dma_link_descriptor link[NUM_DMA_LINKS];
+	struct ccsr_dma_channel __iomem *dma_channel;
+	unsigned int irq;
+	struct snd_pcm_substream *substream;
+	dma_addr_t ssi_sxx_phys;
+	unsigned int ssi_fifo_depth;
+	dma_addr_t ld_buf_phys;
+	unsigned int current_link;
+	dma_addr_t dma_buf_phys;
+	dma_addr_t dma_buf_next;
+	dma_addr_t dma_buf_end;
+	size_t period_size;
+	unsigned int num_periods;
+};
+
+/**
+ * fsl_dma_hardare: define characteristics of the PCM hardware.
+ *
+ * The PCM hardware is the Freescale DMA controller.  This structure defines
+ * the capabilities of that hardware.
+ *
+ * Since the sampling rate and data format are not controlled by the DMA
+ * controller, we specify no limits for those values.  The only exception is
+ * period_bytes_min, which is set to a reasonably low value to prevent the
+ * DMA controller from generating too many interrupts per second.
+ *
+ * Since each link descriptor has a 32-bit byte count field, we set
+ * period_bytes_max to the largest 32-bit number.  We also have no maximum
+ * number of periods.
+ *
+ * Note that we specify SNDRV_PCM_INFO_JOINT_DUPLEX here, but only because a
+ * limitation in the SSI driver requires the sample rates for playback and
+ * capture to be the same.
+ */
+static const struct snd_pcm_hardware fsl_dma_hardware = {
+
+	.info   		= SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_JOINT_DUPLEX |
+				  SNDRV_PCM_INFO_PAUSE,
+	.formats		= FSLDMA_PCM_FORMATS,
+	.period_bytes_min       = 512,  	/* A reasonable limit */
+	.period_bytes_max       = (u32) -1,
+	.periods_min    	= NUM_DMA_LINKS,
+	.periods_max    	= (unsigned int) -1,
+	.buffer_bytes_max       = 128 * 1024,   /* A reasonable limit */
+};
+
+/**
+ * fsl_dma_abort_stream: tell ALSA that the DMA transfer has aborted
+ *
+ * This function should be called by the ISR whenever the DMA controller
+ * halts data transfer.
+ */
+static void fsl_dma_abort_stream(struct snd_pcm_substream *substream)
+{
+	snd_pcm_stop_xrun(substream);
+}
+
+/**
+ * fsl_dma_update_pointers - update LD pointers to point to the next period
+ *
+ * As each period is completed, this function changes the the link
+ * descriptor pointers for that period to point to the next period.
+ */
+static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private)
+{
+	struct fsl_dma_link_descriptor *link =
+		&dma_private->link[dma_private->current_link];
+
+	/* Update our link descriptors to point to the next period. On a 36-bit
+	 * system, we also need to update the ESAD bits.  We also set (keep) the
+	 * snoop bits.  See the comments in fsl_dma_hw_params() about snooping.
+	 */
+	if (dma_private->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		link->source_addr = cpu_to_be32(dma_private->dma_buf_next);
+#ifdef CONFIG_PHYS_64BIT
+		link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+			upper_32_bits(dma_private->dma_buf_next));
+#endif
+	} else {
+		link->dest_addr = cpu_to_be32(dma_private->dma_buf_next);
+#ifdef CONFIG_PHYS_64BIT
+		link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+			upper_32_bits(dma_private->dma_buf_next));
+#endif
+	}
+
+	/* Update our variables for next time */
+	dma_private->dma_buf_next += dma_private->period_size;
+
+	if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+		dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+	if (++dma_private->current_link >= NUM_DMA_LINKS)
+		dma_private->current_link = 0;
+}
+
+/**
+ * fsl_dma_isr: interrupt handler for the DMA controller
+ *
+ * @irq: IRQ of the DMA channel
+ * @dev_id: pointer to the dma_private structure for this DMA channel
+ */
+static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
+{
+	struct fsl_dma_private *dma_private = dev_id;
+	struct snd_pcm_substream *substream = dma_private->substream;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct device *dev = component->dev;
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+	irqreturn_t ret = IRQ_NONE;
+	u32 sr, sr2 = 0;
+
+	/* We got an interrupt, so read the status register to see what we
+	   were interrupted for.
+	 */
+	sr = in_be32(&dma_channel->sr);
+
+	if (sr & CCSR_DMA_SR_TE) {
+		dev_err(dev, "dma transmit error\n");
+		fsl_dma_abort_stream(substream);
+		sr2 |= CCSR_DMA_SR_TE;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_CH)
+		ret = IRQ_HANDLED;
+
+	if (sr & CCSR_DMA_SR_PE) {
+		dev_err(dev, "dma programming error\n");
+		fsl_dma_abort_stream(substream);
+		sr2 |= CCSR_DMA_SR_PE;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_EOLNI) {
+		sr2 |= CCSR_DMA_SR_EOLNI;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_CB)
+		ret = IRQ_HANDLED;
+
+	if (sr & CCSR_DMA_SR_EOSI) {
+		/* Tell ALSA we completed a period. */
+		snd_pcm_period_elapsed(substream);
+
+		/*
+		 * Update our link descriptors to point to the next period. We
+		 * only need to do this if the number of periods is not equal to
+		 * the number of links.
+		 */
+		if (dma_private->num_periods != NUM_DMA_LINKS)
+			fsl_dma_update_pointers(dma_private);
+
+		sr2 |= CCSR_DMA_SR_EOSI;
+		ret = IRQ_HANDLED;
+	}
+
+	if (sr & CCSR_DMA_SR_EOLSI) {
+		sr2 |= CCSR_DMA_SR_EOLSI;
+		ret = IRQ_HANDLED;
+	}
+
+	/* Clear the bits that we set */
+	if (sr2)
+		out_be32(&dma_channel->sr, sr2);
+
+	return ret;
+}
+
+/**
+ * fsl_dma_new: initialize this PCM driver.
+ *
+ * This function is called when the codec driver calls snd_soc_new_pcms(),
+ * once for each .dai_link in the machine driver's snd_soc_card
+ * structure.
+ *
+ * snd_dma_alloc_pages() is just a front-end to dma_alloc_coherent(), which
+ * (currently) always allocates the DMA buffer in lowmem, even if GFP_HIGHMEM
+ * is specified. Therefore, any DMA buffers we allocate will always be in low
+ * memory, but we support for 36-bit physical addresses anyway.
+ *
+ * Regardless of where the memory is actually allocated, since the device can
+ * technically DMA to any 36-bit address, we do need to set the DMA mask to 36.
+ */
+static int fsl_dma_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_card *card = rtd->card->snd_card;
+	struct snd_pcm *pcm = rtd->pcm;
+	int ret;
+
+	ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(36));
+	if (ret)
+		return ret;
+
+	/* Some codecs have separate DAIs for playback and capture, so we
+	 * should allocate a DMA buffer only for the streams that are valid.
+	 */
+
+	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+		ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
+			fsl_dma_hardware.buffer_bytes_max,
+			&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
+		if (ret) {
+			dev_err(card->dev, "can't alloc playback dma buffer\n");
+			return ret;
+		}
+	}
+
+	if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+		ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
+			fsl_dma_hardware.buffer_bytes_max,
+			&pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer);
+		if (ret) {
+			dev_err(card->dev, "can't alloc capture dma buffer\n");
+			snd_dma_free_pages(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_dma_open: open a new substream.
+ *
+ * Each substream has its own DMA buffer.
+ *
+ * ALSA divides the DMA buffer into N periods.  We create NUM_DMA_LINKS link
+ * descriptors that ping-pong from one period to the next.  For example, if
+ * there are six periods and two link descriptors, this is how they look
+ * before playback starts:
+ *
+ *      	   The last link descriptor
+ *   ____________  points back to the first
+ *  |   	 |
+ *  V   	 |
+ *  ___    ___   |
+ * |   |->|   |->|
+ * |___|  |___|
+ *   |      |
+ *   |      |
+ *   V      V
+ *  _________________________________________
+ * |      |      |      |      |      |      |  The DMA buffer is
+ * |      |      |      |      |      |      |    divided into 6 parts
+ * |______|______|______|______|______|______|
+ *
+ * and here's how they look after the first period is finished playing:
+ *
+ *   ____________
+ *  |   	 |
+ *  V   	 |
+ *  ___    ___   |
+ * |   |->|   |->|
+ * |___|  |___|
+ *   |      |
+ *   |______________
+ *          |       |
+ *          V       V
+ *  _________________________________________
+ * |      |      |      |      |      |      |
+ * |      |      |      |      |      |      |
+ * |______|______|______|______|______|______|
+ *
+ * The first link descriptor now points to the third period.  The DMA
+ * controller is currently playing the second period.  When it finishes, it
+ * will jump back to the first descriptor and play the third period.
+ *
+ * There are four reasons we do this:
+ *
+ * 1. The only way to get the DMA controller to automatically restart the
+ *    transfer when it gets to the end of the buffer is to use chaining
+ *    mode.  Basic direct mode doesn't offer that feature.
+ * 2. We need to receive an interrupt at the end of every period.  The DMA
+ *    controller can generate an interrupt at the end of every link transfer
+ *    (aka segment).  Making each period into a DMA segment will give us the
+ *    interrupts we need.
+ * 3. By creating only two link descriptors, regardless of the number of
+ *    periods, we do not need to reallocate the link descriptors if the
+ *    number of periods changes.
+ * 4. All of the audio data is still stored in a single, contiguous DMA
+ *    buffer, which is what ALSA expects.  We're just dividing it into
+ *    contiguous parts, and creating a link descriptor for each one.
+ */
+static int fsl_dma_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct device *dev = component->dev;
+	struct dma_object *dma =
+		container_of(component->driver, struct dma_object, dai);
+	struct fsl_dma_private *dma_private;
+	struct ccsr_dma_channel __iomem *dma_channel;
+	dma_addr_t ld_buf_phys;
+	u64 temp_link;  	/* Pointer to next link descriptor */
+	u32 mr;
+	unsigned int channel;
+	int ret = 0;
+	unsigned int i;
+
+	/*
+	 * Reject any DMA buffer whose size is not a multiple of the period
+	 * size.  We need to make sure that the DMA buffer can be evenly divided
+	 * into periods.
+	 */
+	ret = snd_pcm_hw_constraint_integer(runtime,
+		SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0) {
+		dev_err(dev, "invalid buffer size\n");
+		return ret;
+	}
+
+	channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
+
+	if (dma->assigned) {
+		dev_err(dev, "dma channel already assigned\n");
+		return -EBUSY;
+	}
+
+	dma_private = dma_alloc_coherent(dev, sizeof(struct fsl_dma_private),
+					 &ld_buf_phys, GFP_KERNEL);
+	if (!dma_private) {
+		dev_err(dev, "can't allocate dma private data\n");
+		return -ENOMEM;
+	}
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_private->ssi_sxx_phys = dma->ssi_stx_phys;
+	else
+		dma_private->ssi_sxx_phys = dma->ssi_srx_phys;
+
+	dma_private->ssi_fifo_depth = dma->ssi_fifo_depth;
+	dma_private->dma_channel = dma->channel;
+	dma_private->irq = dma->irq;
+	dma_private->substream = substream;
+	dma_private->ld_buf_phys = ld_buf_phys;
+	dma_private->dma_buf_phys = substream->dma_buffer.addr;
+
+	ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "fsldma-audio",
+			  dma_private);
+	if (ret) {
+		dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n",
+			dma_private->irq, ret);
+		dma_free_coherent(dev, sizeof(struct fsl_dma_private),
+			dma_private, dma_private->ld_buf_phys);
+		return ret;
+	}
+
+	dma->assigned = true;
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware);
+	runtime->private_data = dma_private;
+
+	/* Program the fixed DMA controller parameters */
+
+	dma_channel = dma_private->dma_channel;
+
+	temp_link = dma_private->ld_buf_phys +
+		sizeof(struct fsl_dma_link_descriptor);
+
+	for (i = 0; i < NUM_DMA_LINKS; i++) {
+		dma_private->link[i].next = cpu_to_be64(temp_link);
+
+		temp_link += sizeof(struct fsl_dma_link_descriptor);
+	}
+	/* The last link descriptor points to the first */
+	dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys);
+
+	/* Tell the DMA controller where the first link descriptor is */
+	out_be32(&dma_channel->clndar,
+		CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys));
+	out_be32(&dma_channel->eclndar,
+		CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys));
+
+	/* The manual says the BCR must be clear before enabling EMP */
+	out_be32(&dma_channel->bcr, 0);
+
+	/*
+	 * Program the mode register for interrupts, external master control,
+	 * and source/destination hold.  Also clear the Channel Abort bit.
+	 */
+	mr = in_be32(&dma_channel->mr) &
+		~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE);
+
+	/*
+	 * We want External Master Start and External Master Pause enabled,
+	 * because the SSI is controlling the DMA controller.  We want the DMA
+	 * controller to be set up in advance, and then we signal only the SSI
+	 * to start transferring.
+	 *
+	 * We want End-Of-Segment Interrupts enabled, because this will generate
+	 * an interrupt at the end of each segment (each link descriptor
+	 * represents one segment).  Each DMA segment is the same thing as an
+	 * ALSA period, so this is how we get an interrupt at the end of every
+	 * period.
+	 *
+	 * We want Error Interrupt enabled, so that we can get an error if
+	 * the DMA controller is mis-programmed somehow.
+	 */
+	mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN |
+		CCSR_DMA_MR_EMS_EN;
+
+	/* For playback, we want the destination address to be held.  For
+	   capture, set the source address to be held. */
+	mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
+		CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE;
+
+	out_be32(&dma_channel->mr, mr);
+
+	return 0;
+}
+
+/**
+ * fsl_dma_hw_params: continue initializing the DMA links
+ *
+ * This function obtains hardware parameters about the opened stream and
+ * programs the DMA controller accordingly.
+ *
+ * One drawback of big-endian is that when copying integers of different
+ * sizes to a fixed-sized register, the address to which the integer must be
+ * copied is dependent on the size of the integer.
+ *
+ * For example, if P is the address of a 32-bit register, and X is a 32-bit
+ * integer, then X should be copied to address P.  However, if X is a 16-bit
+ * integer, then it should be copied to P+2.  If X is an 8-bit register,
+ * then it should be copied to P+3.
+ *
+ * So for playback of 8-bit samples, the DMA controller must transfer single
+ * bytes from the DMA buffer to the last byte of the STX0 register, i.e.
+ * offset by 3 bytes. For 16-bit samples, the offset is two bytes.
+ *
+ * For 24-bit samples, the offset is 1 byte.  However, the DMA controller
+ * does not support 3-byte copies (the DAHTS register supports only 1, 2, 4,
+ * and 8 bytes at a time).  So we do not support packed 24-bit samples.
+ * 24-bit data must be padded to 32 bits.
+ */
+static int fsl_dma_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct device *dev = component->dev;
+
+	/* Number of bits per sample */
+	unsigned int sample_bits =
+		snd_pcm_format_physical_width(params_format(hw_params));
+
+	/* Number of bytes per frame */
+	unsigned int sample_bytes = sample_bits / 8;
+
+	/* Bus address of SSI STX register */
+	dma_addr_t ssi_sxx_phys = dma_private->ssi_sxx_phys;
+
+	/* Size of the DMA buffer, in bytes */
+	size_t buffer_size = params_buffer_bytes(hw_params);
+
+	/* Number of bytes per period */
+	size_t period_size = params_period_bytes(hw_params);
+
+	/* Pointer to next period */
+	dma_addr_t temp_addr = substream->dma_buffer.addr;
+
+	/* Pointer to DMA controller */
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+
+	u32 mr; /* DMA Mode Register */
+
+	unsigned int i;
+
+	/* Initialize our DMA tracking variables */
+	dma_private->period_size = period_size;
+	dma_private->num_periods = params_periods(hw_params);
+	dma_private->dma_buf_end = dma_private->dma_buf_phys + buffer_size;
+	dma_private->dma_buf_next = dma_private->dma_buf_phys +
+		(NUM_DMA_LINKS * period_size);
+
+	if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
+		/* This happens if the number of periods == NUM_DMA_LINKS */
+		dma_private->dma_buf_next = dma_private->dma_buf_phys;
+
+	mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK |
+		  CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK);
+
+	/* Due to a quirk of the SSI's STX register, the target address
+	 * for the DMA operations depends on the sample size.  So we calculate
+	 * that offset here.  While we're at it, also tell the DMA controller
+	 * how much data to transfer per sample.
+	 */
+	switch (sample_bits) {
+	case 8:
+		mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1;
+		ssi_sxx_phys += 3;
+		break;
+	case 16:
+		mr |= CCSR_DMA_MR_DAHTS_2 | CCSR_DMA_MR_SAHTS_2;
+		ssi_sxx_phys += 2;
+		break;
+	case 32:
+		mr |= CCSR_DMA_MR_DAHTS_4 | CCSR_DMA_MR_SAHTS_4;
+		break;
+	default:
+		/* We should never get here */
+		dev_err(dev, "unsupported sample size %u\n", sample_bits);
+		return -EINVAL;
+	}
+
+	/*
+	 * BWC determines how many bytes are sent/received before the DMA
+	 * controller checks the SSI to see if it needs to stop. BWC should
+	 * always be a multiple of the frame size, so that we always transmit
+	 * whole frames.  Each frame occupies two slots in the FIFO.  The
+	 * parameter for CCSR_DMA_MR_BWC() is rounded down the next power of two
+	 * (MR[BWC] can only represent even powers of two).
+	 *
+	 * To simplify the process, we set BWC to the largest value that is
+	 * less than or equal to the FIFO watermark.  For playback, this ensures
+	 * that we transfer the maximum amount without overrunning the FIFO.
+	 * For capture, this ensures that we transfer the maximum amount without
+	 * underrunning the FIFO.
+	 *
+	 * f = SSI FIFO depth
+	 * w = SSI watermark value (which equals f - 2)
+	 * b = DMA bandwidth count (in bytes)
+	 * s = sample size (in bytes, which equals frame_size * 2)
+	 *
+	 * For playback, we never transmit more than the transmit FIFO
+	 * watermark, otherwise we might write more data than the FIFO can hold.
+	 * The watermark is equal to the FIFO depth minus two.
+	 *
+	 * For capture, two equations must hold:
+	 *	w > f - (b / s)
+	 *	w >= b / s
+	 *
+	 * So, b > 2 * s, but b must also be <= s * w.  To simplify, we set
+	 * b = s * w, which is equal to
+	 *      (dma_private->ssi_fifo_depth - 2) * sample_bytes.
+	 */
+	mr |= CCSR_DMA_MR_BWC((dma_private->ssi_fifo_depth - 2) * sample_bytes);
+
+	out_be32(&dma_channel->mr, mr);
+
+	for (i = 0; i < NUM_DMA_LINKS; i++) {
+		struct fsl_dma_link_descriptor *link = &dma_private->link[i];
+
+		link->count = cpu_to_be32(period_size);
+
+		/* The snoop bit tells the DMA controller whether it should tell
+		 * the ECM to snoop during a read or write to an address. For
+		 * audio, we use DMA to transfer data between memory and an I/O
+		 * device (the SSI's STX0 or SRX0 register). Snooping is only
+		 * needed if there is a cache, so we need to snoop memory
+		 * addresses only.  For playback, that means we snoop the source
+		 * but not the destination.  For capture, we snoop the
+		 * destination but not the source.
+		 *
+		 * Note that failing to snoop properly is unlikely to cause
+		 * cache incoherency if the period size is larger than the
+		 * size of L1 cache.  This is because filling in one period will
+		 * flush out the data for the previous period.  So if you
+		 * increased period_bytes_min to a large enough size, you might
+		 * get more performance by not snooping, and you'll still be
+		 * okay.  You'll need to update fsl_dma_update_pointers() also.
+		 */
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			link->source_addr = cpu_to_be32(temp_addr);
+			link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+				upper_32_bits(temp_addr));
+
+			link->dest_addr = cpu_to_be32(ssi_sxx_phys);
+			link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP |
+				upper_32_bits(ssi_sxx_phys));
+		} else {
+			link->source_addr = cpu_to_be32(ssi_sxx_phys);
+			link->source_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP |
+				upper_32_bits(ssi_sxx_phys));
+
+			link->dest_addr = cpu_to_be32(temp_addr);
+			link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
+				upper_32_bits(temp_addr));
+		}
+
+		temp_addr += period_size;
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_dma_pointer: determine the current position of the DMA transfer
+ *
+ * This function is called by ALSA when ALSA wants to know where in the
+ * stream buffer the hardware currently is.
+ *
+ * For playback, the SAR register contains the physical address of the most
+ * recent DMA transfer.  For capture, the value is in the DAR register.
+ *
+ * The base address of the buffer is stored in the source_addr field of the
+ * first link descriptor.
+ */
+static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct device *dev = component->dev;
+	struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
+	dma_addr_t position;
+	snd_pcm_uframes_t frames;
+
+	/* Obtain the current DMA pointer, but don't read the ESAD bits if we
+	 * only have 32-bit DMA addresses.  This function is typically called
+	 * in interrupt context, so we need to optimize it.
+	 */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		position = in_be32(&dma_channel->sar);
+#ifdef CONFIG_PHYS_64BIT
+		position |= (u64)(in_be32(&dma_channel->satr) &
+				  CCSR_DMA_ATR_ESAD_MASK) << 32;
+#endif
+	} else {
+		position = in_be32(&dma_channel->dar);
+#ifdef CONFIG_PHYS_64BIT
+		position |= (u64)(in_be32(&dma_channel->datr) &
+				  CCSR_DMA_ATR_ESAD_MASK) << 32;
+#endif
+	}
+
+	/*
+	 * When capture is started, the SSI immediately starts to fill its FIFO.
+	 * This means that the DMA controller is not started until the FIFO is
+	 * full.  However, ALSA calls this function before that happens, when
+	 * MR.DAR is still zero.  In this case, just return zero to indicate
+	 * that nothing has been received yet.
+	 */
+	if (!position)
+		return 0;
+
+	if ((position < dma_private->dma_buf_phys) ||
+	    (position > dma_private->dma_buf_end)) {
+		dev_err(dev, "dma pointer is out of range, halting stream\n");
+		return SNDRV_PCM_POS_XRUN;
+	}
+
+	frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys);
+
+	/*
+	 * If the current address is just past the end of the buffer, wrap it
+	 * around.
+	 */
+	if (frames == runtime->buffer_size)
+		frames = 0;
+
+	return frames;
+}
+
+/**
+ * fsl_dma_hw_free: release resources allocated in fsl_dma_hw_params()
+ *
+ * Release the resources allocated in fsl_dma_hw_params() and de-program the
+ * registers.
+ *
+ * This function can be called multiple times.
+ */
+static int fsl_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+
+	if (dma_private) {
+		struct ccsr_dma_channel __iomem *dma_channel;
+
+		dma_channel = dma_private->dma_channel;
+
+		/* Stop the DMA */
+		out_be32(&dma_channel->mr, CCSR_DMA_MR_CA);
+		out_be32(&dma_channel->mr, 0);
+
+		/* Reset all the other registers */
+		out_be32(&dma_channel->sr, -1);
+		out_be32(&dma_channel->clndar, 0);
+		out_be32(&dma_channel->eclndar, 0);
+		out_be32(&dma_channel->satr, 0);
+		out_be32(&dma_channel->sar, 0);
+		out_be32(&dma_channel->datr, 0);
+		out_be32(&dma_channel->dar, 0);
+		out_be32(&dma_channel->bcr, 0);
+		out_be32(&dma_channel->nlndar, 0);
+		out_be32(&dma_channel->enlndar, 0);
+	}
+
+	return 0;
+}
+
+/**
+ * fsl_dma_close: close the stream.
+ */
+static int fsl_dma_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct fsl_dma_private *dma_private = runtime->private_data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct device *dev = component->dev;
+	struct dma_object *dma =
+		container_of(component->driver, struct dma_object, dai);
+
+	if (dma_private) {
+		if (dma_private->irq)
+			free_irq(dma_private->irq, dma_private);
+
+		/* Deallocate the fsl_dma_private structure */
+		dma_free_coherent(dev, sizeof(struct fsl_dma_private),
+				  dma_private, dma_private->ld_buf_phys);
+		substream->runtime->private_data = NULL;
+	}
+
+	dma->assigned = false;
+
+	return 0;
+}
+
+/*
+ * Remove this PCM driver.
+ */
+static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
+		substream = pcm->streams[i].substream;
+		if (substream) {
+			snd_dma_free_pages(&substream->dma_buffer);
+			substream->dma_buffer.area = NULL;
+			substream->dma_buffer.addr = 0;
+		}
+	}
+}
+
+/**
+ * find_ssi_node -- returns the SSI node that points to its DMA channel node
+ *
+ * Although this DMA driver attempts to operate independently of the other
+ * devices, it still needs to determine some information about the SSI device
+ * that it's working with.  Unfortunately, the device tree does not contain
+ * a pointer from the DMA channel node to the SSI node -- the pointer goes the
+ * other way.  So we need to scan the device tree for SSI nodes until we find
+ * the one that points to the given DMA channel node.  It's ugly, but at least
+ * it's contained in this one function.
+ */
+static struct device_node *find_ssi_node(struct device_node *dma_channel_np)
+{
+	struct device_node *ssi_np, *np;
+
+	for_each_compatible_node(ssi_np, NULL, "fsl,mpc8610-ssi") {
+		/* Check each DMA phandle to see if it points to us.  We
+		 * assume that device_node pointers are a valid comparison.
+		 */
+		np = of_parse_phandle(ssi_np, "fsl,playback-dma", 0);
+		of_node_put(np);
+		if (np == dma_channel_np)
+			return ssi_np;
+
+		np = of_parse_phandle(ssi_np, "fsl,capture-dma", 0);
+		of_node_put(np);
+		if (np == dma_channel_np)
+			return ssi_np;
+	}
+
+	return NULL;
+}
+
+static const struct snd_pcm_ops fsl_dma_ops = {
+	.open   	= fsl_dma_open,
+	.close  	= fsl_dma_close,
+	.ioctl  	= snd_pcm_lib_ioctl,
+	.hw_params      = fsl_dma_hw_params,
+	.hw_free	= fsl_dma_hw_free,
+	.pointer	= fsl_dma_pointer,
+};
+
+static int fsl_soc_dma_probe(struct platform_device *pdev)
+{
+	struct dma_object *dma;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *ssi_np;
+	struct resource res;
+	const uint32_t *iprop;
+	int ret;
+
+	/* Find the SSI node that points to us. */
+	ssi_np = find_ssi_node(np);
+	if (!ssi_np) {
+		dev_err(&pdev->dev, "cannot find parent SSI node\n");
+		return -ENODEV;
+	}
+
+	ret = of_address_to_resource(ssi_np, 0, &res);
+	if (ret) {
+		dev_err(&pdev->dev, "could not determine resources for %pOF\n",
+			ssi_np);
+		of_node_put(ssi_np);
+		return ret;
+	}
+
+	dma = kzalloc(sizeof(*dma), GFP_KERNEL);
+	if (!dma) {
+		of_node_put(ssi_np);
+		return -ENOMEM;
+	}
+
+	dma->dai.name = DRV_NAME;
+	dma->dai.ops = &fsl_dma_ops;
+	dma->dai.pcm_new = fsl_dma_new;
+	dma->dai.pcm_free = fsl_dma_free_dma_buffers;
+
+	/* Store the SSI-specific information that we need */
+	dma->ssi_stx_phys = res.start + REG_SSI_STX0;
+	dma->ssi_srx_phys = res.start + REG_SSI_SRX0;
+
+	iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL);
+	if (iprop)
+		dma->ssi_fifo_depth = be32_to_cpup(iprop);
+	else
+                /* Older 8610 DTs didn't have the fifo-depth property */
+		dma->ssi_fifo_depth = 8;
+
+	of_node_put(ssi_np);
+
+	ret = devm_snd_soc_register_component(&pdev->dev, &dma->dai, NULL, 0);
+	if (ret) {
+		dev_err(&pdev->dev, "could not register platform\n");
+		kfree(dma);
+		return ret;
+	}
+
+	dma->channel = of_iomap(np, 0);
+	dma->irq = irq_of_parse_and_map(np, 0);
+
+	dev_set_drvdata(&pdev->dev, dma);
+
+	return 0;
+}
+
+static int fsl_soc_dma_remove(struct platform_device *pdev)
+{
+	struct dma_object *dma = dev_get_drvdata(&pdev->dev);
+
+	iounmap(dma->channel);
+	irq_dispose_mapping(dma->irq);
+	kfree(dma);
+
+	return 0;
+}
+
+static const struct of_device_id fsl_soc_dma_ids[] = {
+	{ .compatible = "fsl,ssi-dma-channel", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, fsl_soc_dma_ids);
+
+static struct platform_driver fsl_soc_dma_driver = {
+	.driver = {
+		.name = "fsl-pcm-audio",
+		.of_match_table = fsl_soc_dma_ids,
+	},
+	.probe = fsl_soc_dma_probe,
+	.remove = fsl_soc_dma_remove,
+};
+
+module_platform_driver(fsl_soc_dma_driver);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h
new file mode 100644
index 0000000..78fee97
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.h
@@ -0,0 +1,129 @@
+/*
+ * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _MPC8610_PCM_H
+#define _MPC8610_PCM_H
+
+struct ccsr_dma {
+	u8 res0[0x100];
+	struct ccsr_dma_channel {
+		__be32 mr;      /* Mode register */
+		__be32 sr;      /* Status register */
+		__be32 eclndar; /* Current link descriptor extended addr reg */
+		__be32 clndar;  /* Current link descriptor address register */
+		__be32 satr;    /* Source attributes register */
+		__be32 sar;     /* Source address register */
+		__be32 datr;    /* Destination attributes register */
+		__be32 dar;     /* Destination address register */
+		__be32 bcr;     /* Byte count register */
+		__be32 enlndar; /* Next link descriptor extended address reg */
+		__be32 nlndar;  /* Next link descriptor address register */
+		u8 res1[4];
+		__be32 eclsdar; /* Current list descriptor extended addr reg */
+		__be32 clsdar;  /* Current list descriptor address register */
+		__be32 enlsdar; /* Next list descriptor extended address reg */
+		__be32 nlsdar;  /* Next list descriptor address register */
+		__be32 ssr;     /* Source stride register */
+		__be32 dsr;     /* Destination stride register */
+		u8 res2[0x38];
+	} channel[4];
+	__be32 dgsr;
+};
+
+#define CCSR_DMA_MR_BWC_DISABLED	0x0F000000
+#define CCSR_DMA_MR_BWC_SHIFT   	24
+#define CCSR_DMA_MR_BWC_MASK    	0x0F000000
+#define CCSR_DMA_MR_BWC(x) \
+	((ilog2(x) << CCSR_DMA_MR_BWC_SHIFT) & CCSR_DMA_MR_BWC_MASK)
+#define CCSR_DMA_MR_EMP_EN      	0x00200000
+#define CCSR_DMA_MR_EMS_EN      	0x00040000
+#define CCSR_DMA_MR_DAHTS_MASK  	0x00030000
+#define CCSR_DMA_MR_DAHTS_1     	0x00000000
+#define CCSR_DMA_MR_DAHTS_2     	0x00010000
+#define CCSR_DMA_MR_DAHTS_4     	0x00020000
+#define CCSR_DMA_MR_DAHTS_8     	0x00030000
+#define CCSR_DMA_MR_SAHTS_MASK  	0x0000C000
+#define CCSR_DMA_MR_SAHTS_1     	0x00000000
+#define CCSR_DMA_MR_SAHTS_2     	0x00004000
+#define CCSR_DMA_MR_SAHTS_4     	0x00008000
+#define CCSR_DMA_MR_SAHTS_8     	0x0000C000
+#define CCSR_DMA_MR_DAHE		0x00002000
+#define CCSR_DMA_MR_SAHE		0x00001000
+#define CCSR_DMA_MR_SRW 		0x00000400
+#define CCSR_DMA_MR_EOSIE       	0x00000200
+#define CCSR_DMA_MR_EOLNIE      	0x00000100
+#define CCSR_DMA_MR_EOLSIE      	0x00000080
+#define CCSR_DMA_MR_EIE 		0x00000040
+#define CCSR_DMA_MR_XFE 		0x00000020
+#define CCSR_DMA_MR_CDSM_SWSM   	0x00000010
+#define CCSR_DMA_MR_CA  		0x00000008
+#define CCSR_DMA_MR_CTM 		0x00000004
+#define CCSR_DMA_MR_CC  		0x00000002
+#define CCSR_DMA_MR_CS  		0x00000001
+
+#define CCSR_DMA_SR_TE  		0x00000080
+#define CCSR_DMA_SR_CH  		0x00000020
+#define CCSR_DMA_SR_PE  		0x00000010
+#define CCSR_DMA_SR_EOLNI       	0x00000008
+#define CCSR_DMA_SR_CB  		0x00000004
+#define CCSR_DMA_SR_EOSI		0x00000002
+#define CCSR_DMA_SR_EOLSI       	0x00000001
+
+/* ECLNDAR takes bits 32-36 of the CLNDAR register */
+static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x)
+{
+	return (x >> 32) & 0xf;
+}
+
+#define CCSR_DMA_CLNDAR_ADDR(x) ((x) & 0xFFFFFFFE)
+#define CCSR_DMA_CLNDAR_EOSIE   	0x00000008
+
+/* SATR and DATR, combined */
+#define CCSR_DMA_ATR_PBATMU     	0x20000000
+#define CCSR_DMA_ATR_TFLOWLVL_0 	0x00000000
+#define CCSR_DMA_ATR_TFLOWLVL_1 	0x06000000
+#define CCSR_DMA_ATR_TFLOWLVL_2 	0x08000000
+#define CCSR_DMA_ATR_TFLOWLVL_3 	0x0C000000
+#define CCSR_DMA_ATR_PCIORDER   	0x02000000
+#define CCSR_DMA_ATR_SME		0x01000000
+#define CCSR_DMA_ATR_NOSNOOP    	0x00040000
+#define CCSR_DMA_ATR_SNOOP      	0x00050000
+#define CCSR_DMA_ATR_ESAD_MASK  	0x0000000F
+
+/**
+ *  List Descriptor for extended chaining mode DMA operations.
+ *
+ *  The CLSDAR register points to the first (in a linked-list) List
+ *  Descriptor.  Each object must be aligned on a 32-byte boundary. Each
+ *  list descriptor points to a linked-list of link Descriptors.
+ */
+struct fsl_dma_list_descriptor {
+	__be64 next;    	/* Address of next list descriptor */
+	__be64 first_link;      /* Address of first link descriptor */
+	__be32 source;  	/* Source stride */
+	__be32 dest;    	/* Destination stride */
+	u8 res[8];      	/* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+/**
+ *  Link Descriptor for basic and extended chaining mode DMA operations.
+ *
+ *  A Link Descriptor points to a single DMA buffer.  Each link descriptor
+ *  must be aligned on a 32-byte boundary.
+ */
+struct fsl_dma_link_descriptor {
+	__be32 source_attr;     /* Programmed into SATR register */
+	__be32 source_addr;     /* Programmed into SAR register */
+	__be32 dest_attr;       /* Programmed into DATR register */
+	__be32 dest_addr;       /* Programmed into DAR register */
+	__be64 next;    /* Address of next link descriptor */
+	__be32 count;   /* Byte count */
+	u8 res[4];      /* Reserved */
+} __attribute__ ((aligned(32), packed));
+
+#endif
diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c
new file mode 100644
index 0000000..c1d1d06
--- /dev/null
+++ b/sound/soc/fsl/fsl_esai.c
@@ -0,0 +1,983 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale ESAI ALSA SoC Digital Audio Interface (DAI) driver
+//
+// Copyright (C) 2014 Freescale Semiconductor, Inc.
+
+#include <linux/clk.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_esai.h"
+#include "imx-pcm.h"
+
+#define FSL_ESAI_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
+ * @spbaclk: SPBA clock (optional, depending on SoC design)
+ * @fifo_depth: depth of tx/rx FIFO
+ * @slot_width: width of each DAI slot
+ * @slots: number of slots
+ * @hck_rate: clock rate of desired HCKx clock
+ * @sck_rate: clock rate of desired SCKx clock
+ * @hck_dir: the direction of HCKx pads
+ * @sck_div: if using PSR/PM dividers for SCKx clock
+ * @slave_mode: if fully using DAI slave mode
+ * @synchronous: if using tx/rx synchronous mode
+ * @name: driver name
+ */
+struct fsl_esai {
+	struct snd_dmaengine_dai_dma_data dma_params_rx;
+	struct snd_dmaengine_dai_dma_data dma_params_tx;
+	struct platform_device *pdev;
+	struct regmap *regmap;
+	struct clk *coreclk;
+	struct clk *extalclk;
+	struct clk *fsysclk;
+	struct clk *spbaclk;
+	u32 fifo_depth;
+	u32 slot_width;
+	u32 slots;
+	u32 hck_rate[2];
+	u32 sck_rate[2];
+	bool hck_dir[2];
+	bool sck_div[2];
+	bool slave_mode;
+	bool synchronous;
+	char name[32];
+};
+
+static irqreturn_t esai_isr(int irq, void *devid)
+{
+	struct fsl_esai *esai_priv = (struct fsl_esai *)devid;
+	struct platform_device *pdev = esai_priv->pdev;
+	u32 esr;
+
+	regmap_read(esai_priv->regmap, REG_ESAI_ESR, &esr);
+
+	if (esr & ESAI_ESR_TINIT_MASK)
+		dev_dbg(&pdev->dev, "isr: Transmission 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: Transmission 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: Transmission 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;
+
+	/* Do not loop-search if PM (1 ~ 256) alone can serve the ratio */
+	if (ratio <= 256) {
+		pm = ratio;
+		fp = 1;
+		goto out;
+	}
+
+	/* 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;
+
+	if (freq == 0) {
+		dev_err(dai->dev, "%sput freq of HCK%c should not be 0Hz\n",
+			in ? "in" : "out", tx ? 'T' : 'R');
+		return -EINVAL;
+	}
+
+	/* 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;
+		/* fall through */
+	case ESAI_HCKR_EXTAL:
+		ecr |= ESAI_ECR_ERI;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (IS_ERR(clksrc)) {
+		dev_err(dai->dev, "no assigned %s clock\n",
+				clk_id % 2 ? "extal" : "fsys");
+		return PTR_ERR(clksrc);
+	}
+	clk_rate = clk_get_rate(clksrc);
+
+	ratio = clk_rate / freq;
+	if (ratio * freq > clk_rate)
+		ret = ratio * freq - clk_rate;
+	else if (ratio * freq < clk_rate)
+		ret = clk_rate - ratio * freq;
+	else
+		ret = 0;
+
+	/* Block if clock source can not be divided into the required rate */
+	if (ret != 0 && clk_rate / ret < 1000) {
+		dev_err(dai->dev, "failed to derive required HCK%c rate\n",
+				tx ? 'T' : 'R');
+		return -EINVAL;
+	}
+
+	/* Only EXTAL source can be output directly without using PSR and PM */
+	if (ratio == 1 && clksrc == esai_priv->extalclk) {
+		/* Bypass all the dividers if not being needed */
+		ecr |= tx ? ESAI_ECR_ETO : ESAI_ECR_ERO;
+		goto out;
+	} else if (ratio < 2) {
+		/* The ratio should be no less than 2 if using other sources */
+		dev_err(dai->dev, "failed to derive required HCK%c rate\n",
+				tx ? 'T' : 'R');
+		return -EINVAL;
+	}
+
+	ret = fsl_esai_divisor_cal(dai, tx, ratio, false, 0);
+	if (ret)
+		return ret;
+
+	esai_priv->sck_div[tx] = false;
+
+out:
+	esai_priv->hck_dir[tx] = dir;
+	esai_priv->hck_rate[tx] = freq;
+
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR,
+			   tx ? ESAI_ECR_ETI | ESAI_ECR_ETO :
+			   ESAI_ECR_ERI | ESAI_ECR_ERO, ecr);
+
+	return 0;
+}
+
+/**
+ * This function configures the related dividers according to the bclk rate
+ */
+static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
+{
+	struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
+	u32 hck_rate = esai_priv->hck_rate[tx];
+	u32 sub, ratio = hck_rate / freq;
+	int ret;
+
+	/* Don't apply for fully slave mode or unchanged bclk */
+	if (esai_priv->slave_mode || esai_priv->sck_rate[tx] == freq)
+		return 0;
+
+	if (ratio * freq > hck_rate)
+		sub = ratio * freq - hck_rate;
+	else if (ratio * freq < hck_rate)
+		sub = hck_rate - ratio * freq;
+	else
+		sub = 0;
+
+	/* Block if clock source can not be divided into the required rate */
+	if (sub != 0 && hck_rate / sub < 1000) {
+		dev_err(dai->dev, "failed to derive required SCK%c rate\n",
+				tx ? 'T' : 'R');
+		return -EINVAL;
+	}
+
+	/* The ratio should be contented by FP alone if bypassing PM and PSR */
+	if (!esai_priv->sck_div[tx] && (ratio > 16 || ratio == 0)) {
+		dev_err(dai->dev, "the ratio is out of range (1 ~ 16)\n");
+		return -EINVAL;
+	}
+
+	ret = fsl_esai_divisor_cal(dai, tx, ratio, true,
+			esai_priv->sck_div[tx] ? 0 : ratio);
+	if (ret)
+		return ret;
+
+	/* Save current bclk rate */
+	esai_priv->sck_rate[tx] = freq;
+
+	return 0;
+}
+
+static int fsl_esai_set_dai_tdm_slot(struct snd_soc_dai *dai, u32 tx_mask,
+				     u32 rx_mask, int slots, int slot_width)
+{
+	struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
+
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR,
+			   ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots));
+
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMA,
+			   ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(tx_mask));
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMB,
+			   ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(tx_mask));
+
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR,
+			   ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots));
+
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMA,
+			   ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(rx_mask));
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMB,
+			   ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(rx_mask));
+
+	esai_priv->slot_width = slot_width;
+	esai_priv->slots = slots;
+
+	return 0;
+}
+
+static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
+	u32 xcr = 0, xccr = 0, mask;
+
+	/* DAI mode */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		/* Data on rising edge of bclk, frame low, 1clk before data */
+		xcr |= ESAI_xCR_xFSR;
+		xccr |= ESAI_xCCR_xFSP | ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		/* Data on rising edge of bclk, frame high */
+		xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		/* Data on rising edge of bclk, frame high, right aligned */
+		xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCR_xWA;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		/* Data on rising edge of bclk, frame high, 1clk before data */
+		xcr |= ESAI_xCR_xFSL | ESAI_xCR_xFSR;
+		xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		/* Data on rising edge of bclk, frame high */
+		xcr |= ESAI_xCR_xFSL;
+		xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* DAI clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		/* Nothing to do for both normal cases */
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		/* Invert bit clock */
+		xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		/* Invert frame clock */
+		xccr ^= ESAI_xCCR_xFSP;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		/* Invert both clocks */
+		xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	esai_priv->slave_mode = false;
+
+	/* DAI clock master masks */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		esai_priv->slave_mode = true;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFM:
+		xccr |= ESAI_xCCR_xCKD;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		xccr |= ESAI_xCCR_xFSD;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mask = ESAI_xCR_xFSL | ESAI_xCR_xFSR;
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, xcr);
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, mask, xcr);
+
+	mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP |
+		ESAI_xCCR_xFSD | ESAI_xCCR_xCKD | ESAI_xCR_xWA;
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, mask, xccr);
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, mask, xccr);
+
+	return 0;
+}
+
+static int fsl_esai_startup(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
+	int ret;
+
+	/*
+	 * Some platforms might use the same bit to gate all three or two of
+	 * clocks, so keep all clocks open/close at the same time for safety
+	 */
+	ret = clk_prepare_enable(esai_priv->coreclk);
+	if (ret)
+		return ret;
+	if (!IS_ERR(esai_priv->spbaclk)) {
+		ret = clk_prepare_enable(esai_priv->spbaclk);
+		if (ret)
+			goto err_spbaclk;
+	}
+	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:
+	if (!IS_ERR(esai_priv->spbaclk))
+		clk_disable_unprepare(esai_priv->spbaclk);
+err_spbaclk:
+	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 = params_width(params);
+	u32 channels = params_channels(params);
+	u32 pins = DIV_ROUND_UP(channels, esai_priv->slots);
+	u32 slot_width = width;
+	u32 bclk, mask, val;
+	int ret;
+
+	/* Override slot_width if being specifically set */
+	if (esai_priv->slot_width)
+		slot_width = esai_priv->slot_width;
+
+	bclk = params_rate(params) * slot_width * esai_priv->slots;
+
+	ret = fsl_esai_set_bclk(dai, tx, bclk);
+	if (ret)
+		return ret;
+
+	/* Use Normal mode to support monaural audio */
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
+			   ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ?
+			   ESAI_xCR_xMOD_NETWORK : 0);
+
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx),
+			   ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR);
+
+	mask = ESAI_xFCR_xFR_MASK | ESAI_xFCR_xWA_MASK | ESAI_xFCR_xFWM_MASK |
+	      (tx ? ESAI_xFCR_TE_MASK | ESAI_xFCR_TIEN : ESAI_xFCR_RE_MASK);
+	val = ESAI_xFCR_xWA(width) | ESAI_xFCR_xFWM(esai_priv->fifo_depth) |
+	     (tx ? ESAI_xFCR_TE(pins) | ESAI_xFCR_TIEN : ESAI_xFCR_RE(pins));
+
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), mask, val);
+
+	mask = ESAI_xCR_xSWS_MASK | (tx ? ESAI_xCR_PADC : 0);
+	val = ESAI_xCR_xSWS(slot_width, width) | (tx ? ESAI_xCR_PADC : 0);
+
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val);
+
+	/* Remove ESAI personal reset by configuring ESAI_PCRC and ESAI_PRRC */
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC,
+			   ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO));
+	regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC,
+			   ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO));
+	return 0;
+}
+
+static void fsl_esai_shutdown(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
+
+	if (!IS_ERR(esai_priv->fsysclk))
+		clk_disable_unprepare(esai_priv->fsysclk);
+	if (!IS_ERR(esai_priv->extalclk))
+		clk_disable_unprepare(esai_priv->extalclk);
+	if (!IS_ERR(esai_priv->spbaclk))
+		clk_disable_unprepare(esai_priv->spbaclk);
+	clk_disable_unprepare(esai_priv->coreclk);
+}
+
+static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
+			    struct snd_soc_dai *dai)
+{
+	struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
+	bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	u8 i, channels = substream->runtime->channels;
+	u32 pins = DIV_ROUND_UP(channels, esai_priv->slots);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx),
+				   ESAI_xFCR_xFEN_MASK, ESAI_xFCR_xFEN);
+
+		/* Write initial words reqiured by ESAI as normal procedure */
+		for (i = 0; tx && i < channels; i++)
+			regmap_write(esai_priv->regmap, REG_ESAI_ETDR, 0x0);
+
+		regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
+				   tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK,
+				   tx ? ESAI_xCR_TE(pins) : ESAI_xCR_RE(pins));
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
+				   tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, 0);
+
+		/* Disable and reset FIFO */
+		regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx),
+				   ESAI_xFCR_xFR | ESAI_xFCR_xFEN, ESAI_xFCR_xFR);
+		regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx),
+				   ESAI_xFCR_xFR, 0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops fsl_esai_dai_ops = {
+	.startup = fsl_esai_startup,
+	.shutdown = fsl_esai_shutdown,
+	.trigger = fsl_esai_trigger,
+	.hw_params = fsl_esai_hw_params,
+	.set_sysclk = fsl_esai_set_dai_sysclk,
+	.set_fmt = fsl_esai_set_dai_fmt,
+	.set_tdm_slot = fsl_esai_set_dai_tdm_slot,
+};
+
+static int fsl_esai_dai_probe(struct snd_soc_dai *dai)
+{
+	struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
+
+	snd_soc_dai_init_dma_data(dai, &esai_priv->dma_params_tx,
+				  &esai_priv->dma_params_rx);
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver fsl_esai_dai = {
+	.probe = fsl_esai_dai_probe,
+	.playback = {
+		.stream_name = "CPU-Playback",
+		.channels_min = 1,
+		.channels_max = 12,
+		.rates = SNDRV_PCM_RATE_8000_192000,
+		.formats = FSL_ESAI_FORMATS,
+	},
+	.capture = {
+		.stream_name = "CPU-Capture",
+		.channels_min = 1,
+		.channels_max = 8,
+		.rates = SNDRV_PCM_RATE_8000_192000,
+		.formats = FSL_ESAI_FORMATS,
+	},
+	.ops = &fsl_esai_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_esai_component = {
+	.name		= "fsl-esai",
+};
+
+static const struct reg_default fsl_esai_reg_defaults[] = {
+	{REG_ESAI_ETDR,	 0x00000000},
+	{REG_ESAI_ECR,	 0x00000000},
+	{REG_ESAI_TFCR,	 0x00000000},
+	{REG_ESAI_RFCR,	 0x00000000},
+	{REG_ESAI_TX0,	 0x00000000},
+	{REG_ESAI_TX1,	 0x00000000},
+	{REG_ESAI_TX2,	 0x00000000},
+	{REG_ESAI_TX3,	 0x00000000},
+	{REG_ESAI_TX4,	 0x00000000},
+	{REG_ESAI_TX5,	 0x00000000},
+	{REG_ESAI_TSR,	 0x00000000},
+	{REG_ESAI_SAICR, 0x00000000},
+	{REG_ESAI_TCR,	 0x00000000},
+	{REG_ESAI_TCCR,	 0x00000000},
+	{REG_ESAI_RCR,	 0x00000000},
+	{REG_ESAI_RCCR,	 0x00000000},
+	{REG_ESAI_TSMA,  0x0000ffff},
+	{REG_ESAI_TSMB,  0x0000ffff},
+	{REG_ESAI_RSMA,  0x0000ffff},
+	{REG_ESAI_RSMB,  0x0000ffff},
+	{REG_ESAI_PRRC,  0x00000000},
+	{REG_ESAI_PCRC,  0x00000000},
+};
+
+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_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_ESAI_ERDR:
+	case REG_ESAI_ESR:
+	case REG_ESAI_TFSR:
+	case REG_ESAI_RFSR:
+	case REG_ESAI_RX0:
+	case REG_ESAI_RX1:
+	case REG_ESAI_RX2:
+	case REG_ESAI_RX3:
+	case REG_ESAI_SAISR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool fsl_esai_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_ESAI_ETDR:
+	case REG_ESAI_ECR:
+	case REG_ESAI_TFCR:
+	case REG_ESAI_RFCR:
+	case REG_ESAI_TX0:
+	case REG_ESAI_TX1:
+	case REG_ESAI_TX2:
+	case REG_ESAI_TX3:
+	case REG_ESAI_TX4:
+	case REG_ESAI_TX5:
+	case REG_ESAI_TSR:
+	case REG_ESAI_SAICR:
+	case REG_ESAI_TCR:
+	case REG_ESAI_TCCR:
+	case REG_ESAI_RCR:
+	case REG_ESAI_RCCR:
+	case REG_ESAI_TSMA:
+	case REG_ESAI_TSMB:
+	case REG_ESAI_RSMA:
+	case REG_ESAI_RSMB:
+	case REG_ESAI_PRRC:
+	case REG_ESAI_PCRC:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config fsl_esai_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+
+	.max_register = REG_ESAI_PCRC,
+	.reg_defaults = fsl_esai_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(fsl_esai_reg_defaults),
+	.readable_reg = fsl_esai_readable_reg,
+	.volatile_reg = fsl_esai_volatile_reg,
+	.writeable_reg = fsl_esai_writeable_reg,
+	.cache_type = REGCACHE_FLAT,
+};
+
+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 __be32 *iprop;
+	void __iomem *regs;
+	int irq, ret;
+
+	esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL);
+	if (!esai_priv)
+		return -ENOMEM;
+
+	esai_priv->pdev = pdev;
+	strncpy(esai_priv->name, np->name, sizeof(esai_priv->name) - 1);
+
+	/* Get the addresses and IRQ */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	esai_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+			"core", regs, &fsl_esai_regmap_config);
+	if (IS_ERR(esai_priv->regmap)) {
+		dev_err(&pdev->dev, "failed to init regmap: %ld\n",
+				PTR_ERR(esai_priv->regmap));
+		return PTR_ERR(esai_priv->regmap);
+	}
+
+	esai_priv->coreclk = devm_clk_get(&pdev->dev, "core");
+	if (IS_ERR(esai_priv->coreclk)) {
+		dev_err(&pdev->dev, "failed to get core clock: %ld\n",
+				PTR_ERR(esai_priv->coreclk));
+		return PTR_ERR(esai_priv->coreclk);
+	}
+
+	esai_priv->extalclk = devm_clk_get(&pdev->dev, "extal");
+	if (IS_ERR(esai_priv->extalclk))
+		dev_warn(&pdev->dev, "failed to get extal clock: %ld\n",
+				PTR_ERR(esai_priv->extalclk));
+
+	esai_priv->fsysclk = devm_clk_get(&pdev->dev, "fsys");
+	if (IS_ERR(esai_priv->fsysclk))
+		dev_warn(&pdev->dev, "failed to get fsys clock: %ld\n",
+				PTR_ERR(esai_priv->fsysclk));
+
+	esai_priv->spbaclk = devm_clk_get(&pdev->dev, "spba");
+	if (IS_ERR(esai_priv->spbaclk))
+		dev_warn(&pdev->dev, "failed to get spba clock: %ld\n",
+				PTR_ERR(esai_priv->spbaclk));
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+		return irq;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0,
+			       esai_priv->name, esai_priv);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
+		return ret;
+	}
+
+	/* Set a default slot number */
+	esai_priv->slots = 2;
+
+	/* Set a default master/slave state */
+	esai_priv->slave_mode = true;
+
+	/* Determine the FIFO depth */
+	iprop = of_get_property(np, "fsl,fifo-depth", NULL);
+	if (iprop)
+		esai_priv->fifo_depth = be32_to_cpup(iprop);
+	else
+		esai_priv->fifo_depth = 64;
+
+	esai_priv->dma_params_tx.maxburst = 16;
+	esai_priv->dma_params_rx.maxburst = 16;
+	esai_priv->dma_params_tx.addr = res->start + REG_ESAI_ETDR;
+	esai_priv->dma_params_rx.addr = res->start + REG_ESAI_ERDR;
+
+	esai_priv->synchronous =
+		of_property_read_bool(np, "fsl,esai-synchronous");
+
+	/* Implement full symmetry for synchronous mode */
+	if (esai_priv->synchronous) {
+		fsl_esai_dai.symmetric_rates = 1;
+		fsl_esai_dai.symmetric_channels = 1;
+		fsl_esai_dai.symmetric_samplebits = 1;
+	}
+
+	dev_set_drvdata(&pdev->dev, esai_priv);
+
+	/* Reset ESAI unit */
+	ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ERST);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to reset ESAI: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * We need to enable ESAI so as to access some of its registers.
+	 * Otherwise, we would fail to dump regmap from user space.
+	 */
+	ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ESAIEN);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to enable ESAI: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_snd_soc_register_component(&pdev->dev, &fsl_esai_component,
+					      &fsl_esai_dai, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register DAI: %d\n", ret);
+		return ret;
+	}
+
+	ret = imx_pcm_dma_init(pdev, IMX_ESAI_DMABUF_SIZE);
+	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);
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_esai_suspend(struct device *dev)
+{
+	struct fsl_esai *esai = dev_get_drvdata(dev);
+
+	regcache_cache_only(esai->regmap, true);
+	regcache_mark_dirty(esai->regmap);
+
+	return 0;
+}
+
+static int fsl_esai_resume(struct device *dev)
+{
+	struct fsl_esai *esai = dev_get_drvdata(dev);
+	int ret;
+
+	regcache_cache_only(esai->regmap, false);
+
+	/* FIFO reset for safety */
+	regmap_update_bits(esai->regmap, REG_ESAI_TFCR,
+			   ESAI_xFCR_xFR, ESAI_xFCR_xFR);
+	regmap_update_bits(esai->regmap, REG_ESAI_RFCR,
+			   ESAI_xFCR_xFR, ESAI_xFCR_xFR);
+
+	ret = regcache_sync(esai->regmap);
+	if (ret)
+		return ret;
+
+	/* FIFO reset done */
+	regmap_update_bits(esai->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR, 0);
+	regmap_update_bits(esai->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR, 0);
+
+	return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_esai_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(fsl_esai_suspend, fsl_esai_resume)
+};
+
+static struct platform_driver fsl_esai_driver = {
+	.probe = fsl_esai_probe,
+	.driver = {
+		.name = "fsl-esai-dai",
+		.pm = &fsl_esai_pm_ops,
+		.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 0000000..f873588
--- /dev/null
+++ b/sound/soc/fsl/fsl_esai.h
@@ -0,0 +1,351 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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>
+ */
+
+#ifndef _FSL_ESAI_DAI_H
+#define _FSL_ESAI_DAI_H
+
+/* ESAI Register Map */
+#define REG_ESAI_ETDR		0x00
+#define REG_ESAI_ERDR		0x04
+#define REG_ESAI_ECR		0x08
+#define REG_ESAI_ESR		0x0C
+#define REG_ESAI_TFCR		0x10
+#define REG_ESAI_TFSR		0x14
+#define REG_ESAI_RFCR		0x18
+#define REG_ESAI_RFSR		0x1C
+#define REG_ESAI_xFCR(tx)	(tx ? REG_ESAI_TFCR : REG_ESAI_RFCR)
+#define REG_ESAI_xFSR(tx)	(tx ? REG_ESAI_TFSR : REG_ESAI_RFSR)
+#define REG_ESAI_TX0		0x80
+#define REG_ESAI_TX1		0x84
+#define REG_ESAI_TX2		0x88
+#define REG_ESAI_TX3		0x8C
+#define REG_ESAI_TX4		0x90
+#define REG_ESAI_TX5		0x94
+#define REG_ESAI_TSR		0x98
+#define REG_ESAI_RX0		0xA0
+#define REG_ESAI_RX1		0xA4
+#define REG_ESAI_RX2		0xA8
+#define REG_ESAI_RX3		0xAC
+#define REG_ESAI_SAISR		0xCC
+#define REG_ESAI_SAICR		0xD0
+#define REG_ESAI_TCR		0xD4
+#define REG_ESAI_TCCR		0xD8
+#define REG_ESAI_RCR		0xDC
+#define REG_ESAI_RCCR		0xE0
+#define REG_ESAI_xCR(tx)	(tx ? REG_ESAI_TCR : REG_ESAI_RCR)
+#define REG_ESAI_xCCR(tx)	(tx ? REG_ESAI_TCCR : REG_ESAI_RCCR)
+#define REG_ESAI_TSMA		0xE4
+#define REG_ESAI_TSMB		0xE8
+#define REG_ESAI_RSMA		0xEC
+#define REG_ESAI_RSMB		0xF0
+#define REG_ESAI_xSMA(tx)	(tx ? REG_ESAI_TSMA : REG_ESAI_RSMA)
+#define REG_ESAI_xSMB(tx)	(tx ? REG_ESAI_TSMB : REG_ESAI_RSMB)
+#define REG_ESAI_PRRC		0xF8
+#define REG_ESAI_PCRC		0xFC
+
+/* ESAI Control Register -- REG_ESAI_ECR 0x8 */
+#define ESAI_ECR_ETI_SHIFT	19
+#define ESAI_ECR_ETI_MASK	(1 << ESAI_ECR_ETI_SHIFT)
+#define ESAI_ECR_ETI		(1 << ESAI_ECR_ETI_SHIFT)
+#define ESAI_ECR_ETO_SHIFT	18
+#define ESAI_ECR_ETO_MASK	(1 << ESAI_ECR_ETO_SHIFT)
+#define ESAI_ECR_ETO		(1 << ESAI_ECR_ETO_SHIFT)
+#define ESAI_ECR_ERI_SHIFT	17
+#define ESAI_ECR_ERI_MASK	(1 << ESAI_ECR_ERI_SHIFT)
+#define ESAI_ECR_ERI		(1 << ESAI_ECR_ERI_SHIFT)
+#define ESAI_ECR_ERO_SHIFT	16
+#define ESAI_ECR_ERO_MASK	(1 << ESAI_ECR_ERO_SHIFT)
+#define ESAI_ECR_ERO		(1 << ESAI_ECR_ERO_SHIFT)
+#define ESAI_ECR_ERST_SHIFT	1
+#define ESAI_ECR_ERST_MASK	(1 << ESAI_ECR_ERST_SHIFT)
+#define ESAI_ECR_ERST		(1 << ESAI_ECR_ERST_SHIFT)
+#define ESAI_ECR_ESAIEN_SHIFT	0
+#define ESAI_ECR_ESAIEN_MASK	(1 << ESAI_ECR_ESAIEN_SHIFT)
+#define ESAI_ECR_ESAIEN		(1 << ESAI_ECR_ESAIEN_SHIFT)
+
+/* ESAI Status Register -- REG_ESAI_ESR 0xC */
+#define ESAI_ESR_TINIT_SHIFT	10
+#define ESAI_ESR_TINIT_MASK	(1 << ESAI_ESR_TINIT_SHIFT)
+#define ESAI_ESR_TINIT		(1 << ESAI_ESR_TINIT_SHIFT)
+#define ESAI_ESR_RFF_SHIFT	9
+#define ESAI_ESR_RFF_MASK	(1 << ESAI_ESR_RFF_SHIFT)
+#define ESAI_ESR_RFF		(1 << ESAI_ESR_RFF_SHIFT)
+#define ESAI_ESR_TFE_SHIFT	8
+#define ESAI_ESR_TFE_MASK	(1 << ESAI_ESR_TFE_SHIFT)
+#define ESAI_ESR_TFE		(1 << ESAI_ESR_TFE_SHIFT)
+#define ESAI_ESR_TLS_SHIFT	7
+#define ESAI_ESR_TLS_MASK	(1 << ESAI_ESR_TLS_SHIFT)
+#define ESAI_ESR_TLS		(1 << ESAI_ESR_TLS_SHIFT)
+#define ESAI_ESR_TDE_SHIFT	6
+#define ESAI_ESR_TDE_MASK	(1 << ESAI_ESR_TDE_SHIFT)
+#define ESAI_ESR_TDE		(1 << ESAI_ESR_TDE_SHIFT)
+#define ESAI_ESR_TED_SHIFT	5
+#define ESAI_ESR_TED_MASK	(1 << ESAI_ESR_TED_SHIFT)
+#define ESAI_ESR_TED		(1 << ESAI_ESR_TED_SHIFT)
+#define ESAI_ESR_TD_SHIFT	4
+#define ESAI_ESR_TD_MASK	(1 << ESAI_ESR_TD_SHIFT)
+#define ESAI_ESR_TD		(1 << ESAI_ESR_TD_SHIFT)
+#define ESAI_ESR_RLS_SHIFT	3
+#define ESAI_ESR_RLS_MASK	(1 << ESAI_ESR_RLS_SHIFT)
+#define ESAI_ESR_RLS		(1 << ESAI_ESR_RLS_SHIFT)
+#define ESAI_ESR_RDE_SHIFT	2
+#define ESAI_ESR_RDE_MASK	(1 << ESAI_ESR_RDE_SHIFT)
+#define ESAI_ESR_RDE		(1 << ESAI_ESR_RDE_SHIFT)
+#define ESAI_ESR_RED_SHIFT	1
+#define ESAI_ESR_RED_MASK	(1 << ESAI_ESR_RED_SHIFT)
+#define ESAI_ESR_RED		(1 << ESAI_ESR_RED_SHIFT)
+#define ESAI_ESR_RD_SHIFT	0
+#define ESAI_ESR_RD_MASK	(1 << ESAI_ESR_RD_SHIFT)
+#define ESAI_ESR_RD		(1 << ESAI_ESR_RD_SHIFT)
+
+/*
+ * Transmit FIFO Configuration Register -- REG_ESAI_TFCR 0x10
+ * Receive FIFO Configuration Register -- REG_ESAI_RFCR 0x18
+ */
+#define ESAI_xFCR_TIEN_SHIFT	19
+#define ESAI_xFCR_TIEN_MASK	(1 << ESAI_xFCR_TIEN_SHIFT)
+#define ESAI_xFCR_TIEN		(1 << ESAI_xFCR_TIEN_SHIFT)
+#define ESAI_xFCR_REXT_SHIFT	19
+#define ESAI_xFCR_REXT_MASK	(1 << ESAI_xFCR_REXT_SHIFT)
+#define ESAI_xFCR_REXT		(1 << ESAI_xFCR_REXT_SHIFT)
+#define ESAI_xFCR_xWA_SHIFT	16
+#define ESAI_xFCR_xWA_WIDTH	3
+#define ESAI_xFCR_xWA_MASK	(((1 << ESAI_xFCR_xWA_WIDTH) - 1) << ESAI_xFCR_xWA_SHIFT)
+#define ESAI_xFCR_xWA(v)	(((8 - ((v) >> 2)) << ESAI_xFCR_xWA_SHIFT) & ESAI_xFCR_xWA_MASK)
+#define ESAI_xFCR_xFWM_SHIFT	8
+#define ESAI_xFCR_xFWM_WIDTH	8
+#define ESAI_xFCR_xFWM_MASK	(((1 << ESAI_xFCR_xFWM_WIDTH) - 1) << ESAI_xFCR_xFWM_SHIFT)
+#define ESAI_xFCR_xFWM(v)	((((v) - 1) << ESAI_xFCR_xFWM_SHIFT) & ESAI_xFCR_xFWM_MASK)
+#define ESAI_xFCR_xE_SHIFT	2
+#define ESAI_xFCR_TE_WIDTH	6
+#define ESAI_xFCR_RE_WIDTH	4
+#define ESAI_xFCR_TE_MASK	(((1 << ESAI_xFCR_TE_WIDTH) - 1) << ESAI_xFCR_xE_SHIFT)
+#define ESAI_xFCR_RE_MASK	(((1 << ESAI_xFCR_RE_WIDTH) - 1) << ESAI_xFCR_xE_SHIFT)
+#define ESAI_xFCR_TE(x) 	((ESAI_xFCR_TE_MASK >> (ESAI_xFCR_TE_WIDTH - x)) & ESAI_xFCR_TE_MASK)
+#define ESAI_xFCR_RE(x) 	((ESAI_xFCR_RE_MASK >> (ESAI_xFCR_RE_WIDTH - x)) & ESAI_xFCR_RE_MASK)
+#define ESAI_xFCR_xFR_SHIFT	1
+#define ESAI_xFCR_xFR_MASK	(1 << ESAI_xFCR_xFR_SHIFT)
+#define ESAI_xFCR_xFR		(1 << ESAI_xFCR_xFR_SHIFT)
+#define ESAI_xFCR_xFEN_SHIFT	0
+#define ESAI_xFCR_xFEN_MASK	(1 << ESAI_xFCR_xFEN_SHIFT)
+#define ESAI_xFCR_xFEN		(1 << ESAI_xFCR_xFEN_SHIFT)
+
+/*
+ * Transmit FIFO Status Register -- REG_ESAI_TFSR 0x14
+ * Receive FIFO Status Register --REG_ESAI_RFSR 0x1C
+ */
+#define ESAI_xFSR_NTFO_SHIFT	12
+#define ESAI_xFSR_NRFI_SHIFT	12
+#define ESAI_xFSR_NTFI_SHIFT	8
+#define ESAI_xFSR_NRFO_SHIFT	8
+#define ESAI_xFSR_NTFx_WIDTH	3
+#define ESAI_xFSR_NRFx_WIDTH	2
+#define ESAI_xFSR_NTFO_MASK	(((1 << ESAI_xFSR_NTFx_WIDTH) - 1) << ESAI_xFSR_NTFO_SHIFT)
+#define ESAI_xFSR_NTFI_MASK	(((1 << ESAI_xFSR_NTFx_WIDTH) - 1) << ESAI_xFSR_NTFI_SHIFT)
+#define ESAI_xFSR_NRFO_MASK	(((1 << ESAI_xFSR_NRFx_WIDTH) - 1) << ESAI_xFSR_NRFO_SHIFT)
+#define ESAI_xFSR_NRFI_MASK	(((1 << ESAI_xFSR_NRFx_WIDTH) - 1) << ESAI_xFSR_NRFI_SHIFT)
+#define ESAI_xFSR_xFCNT_SHIFT	0
+#define ESAI_xFSR_xFCNT_WIDTH	8
+#define ESAI_xFSR_xFCNT_MASK	(((1 << ESAI_xFSR_xFCNT_WIDTH) - 1) << ESAI_xFSR_xFCNT_SHIFT)
+
+/* ESAI Transmit Slot Register -- REG_ESAI_TSR 0x98 */
+#define ESAI_TSR_SHIFT		0
+#define ESAI_TSR_WIDTH		24
+#define ESAI_TSR_MASK		(((1 << ESAI_TSR_WIDTH) - 1) << ESAI_TSR_SHIFT)
+
+/* Serial Audio Interface Status Register -- REG_ESAI_SAISR 0xCC */
+#define ESAI_SAISR_TODFE_SHIFT	17
+#define ESAI_SAISR_TODFE_MASK	(1 << ESAI_SAISR_TODFE_SHIFT)
+#define ESAI_SAISR_TODFE	(1 << ESAI_SAISR_TODFE_SHIFT)
+#define ESAI_SAISR_TEDE_SHIFT	16
+#define ESAI_SAISR_TEDE_MASK	(1 << ESAI_SAISR_TEDE_SHIFT)
+#define ESAI_SAISR_TEDE		(1 << ESAI_SAISR_TEDE_SHIFT)
+#define ESAI_SAISR_TDE_SHIFT	15
+#define ESAI_SAISR_TDE_MASK	(1 << ESAI_SAISR_TDE_SHIFT)
+#define ESAI_SAISR_TDE		(1 << ESAI_SAISR_TDE_SHIFT)
+#define ESAI_SAISR_TUE_SHIFT	14
+#define ESAI_SAISR_TUE_MASK	(1 << ESAI_SAISR_TUE_SHIFT)
+#define ESAI_SAISR_TUE		(1 << ESAI_SAISR_TUE_SHIFT)
+#define ESAI_SAISR_TFS_SHIFT	13
+#define ESAI_SAISR_TFS_MASK	(1 << ESAI_SAISR_TFS_SHIFT)
+#define ESAI_SAISR_TFS		(1 << ESAI_SAISR_TFS_SHIFT)
+#define ESAI_SAISR_RODF_SHIFT	10
+#define ESAI_SAISR_RODF_MASK	(1 << ESAI_SAISR_RODF_SHIFT)
+#define ESAI_SAISR_RODF		(1 << ESAI_SAISR_RODF_SHIFT)
+#define ESAI_SAISR_REDF_SHIFT	9
+#define ESAI_SAISR_REDF_MASK	(1 << ESAI_SAISR_REDF_SHIFT)
+#define ESAI_SAISR_REDF		(1 << ESAI_SAISR_REDF_SHIFT)
+#define ESAI_SAISR_RDF_SHIFT	8
+#define ESAI_SAISR_RDF_MASK	(1 << ESAI_SAISR_RDF_SHIFT)
+#define ESAI_SAISR_RDF		(1 << ESAI_SAISR_RDF_SHIFT)
+#define ESAI_SAISR_ROE_SHIFT	7
+#define ESAI_SAISR_ROE_MASK	(1 << ESAI_SAISR_ROE_SHIFT)
+#define ESAI_SAISR_ROE		(1 << ESAI_SAISR_ROE_SHIFT)
+#define ESAI_SAISR_RFS_SHIFT	6
+#define ESAI_SAISR_RFS_MASK	(1 << ESAI_SAISR_RFS_SHIFT)
+#define ESAI_SAISR_RFS		(1 << ESAI_SAISR_RFS_SHIFT)
+#define ESAI_SAISR_IF2_SHIFT	2
+#define ESAI_SAISR_IF2_MASK	(1 << ESAI_SAISR_IF2_SHIFT)
+#define ESAI_SAISR_IF2		(1 << ESAI_SAISR_IF2_SHIFT)
+#define ESAI_SAISR_IF1_SHIFT	1
+#define ESAI_SAISR_IF1_MASK	(1 << ESAI_SAISR_IF1_SHIFT)
+#define ESAI_SAISR_IF1		(1 << ESAI_SAISR_IF1_SHIFT)
+#define ESAI_SAISR_IF0_SHIFT	0
+#define ESAI_SAISR_IF0_MASK	(1 << ESAI_SAISR_IF0_SHIFT)
+#define ESAI_SAISR_IF0		(1 << ESAI_SAISR_IF0_SHIFT)
+
+/* Serial Audio Interface Control Register -- REG_ESAI_SAICR 0xD0 */
+#define ESAI_SAICR_ALC_SHIFT	8
+#define ESAI_SAICR_ALC_MASK	(1 << ESAI_SAICR_ALC_SHIFT)
+#define ESAI_SAICR_ALC		(1 << ESAI_SAICR_ALC_SHIFT)
+#define ESAI_SAICR_TEBE_SHIFT	7
+#define ESAI_SAICR_TEBE_MASK	(1 << ESAI_SAICR_TEBE_SHIFT)
+#define ESAI_SAICR_TEBE		(1 << ESAI_SAICR_TEBE_SHIFT)
+#define ESAI_SAICR_SYNC_SHIFT	6
+#define ESAI_SAICR_SYNC_MASK	(1 << ESAI_SAICR_SYNC_SHIFT)
+#define ESAI_SAICR_SYNC		(1 << ESAI_SAICR_SYNC_SHIFT)
+#define ESAI_SAICR_OF2_SHIFT	2
+#define ESAI_SAICR_OF2_MASK	(1 << ESAI_SAICR_OF2_SHIFT)
+#define ESAI_SAICR_OF2		(1 << ESAI_SAICR_OF2_SHIFT)
+#define ESAI_SAICR_OF1_SHIFT	1
+#define ESAI_SAICR_OF1_MASK	(1 << ESAI_SAICR_OF1_SHIFT)
+#define ESAI_SAICR_OF1		(1 << ESAI_SAICR_OF1_SHIFT)
+#define ESAI_SAICR_OF0_SHIFT	0
+#define ESAI_SAICR_OF0_MASK	(1 << ESAI_SAICR_OF0_SHIFT)
+#define ESAI_SAICR_OF0		(1 << ESAI_SAICR_OF0_SHIFT)
+
+/*
+ * Transmit Control Register -- REG_ESAI_TCR 0xD4
+ * Receive Control Register -- REG_ESAI_RCR 0xDC
+ */
+#define ESAI_xCR_xLIE_SHIFT	23
+#define ESAI_xCR_xLIE_MASK	(1 << ESAI_xCR_xLIE_SHIFT)
+#define ESAI_xCR_xLIE		(1 << ESAI_xCR_xLIE_SHIFT)
+#define ESAI_xCR_xIE_SHIFT	22
+#define ESAI_xCR_xIE_MASK	(1 << ESAI_xCR_xIE_SHIFT)
+#define ESAI_xCR_xIE		(1 << ESAI_xCR_xIE_SHIFT)
+#define ESAI_xCR_xEDIE_SHIFT	21
+#define ESAI_xCR_xEDIE_MASK	(1 << ESAI_xCR_xEDIE_SHIFT)
+#define ESAI_xCR_xEDIE		(1 << ESAI_xCR_xEDIE_SHIFT)
+#define ESAI_xCR_xEIE_SHIFT	20
+#define ESAI_xCR_xEIE_MASK	(1 << ESAI_xCR_xEIE_SHIFT)
+#define ESAI_xCR_xEIE		(1 << ESAI_xCR_xEIE_SHIFT)
+#define ESAI_xCR_xPR_SHIFT	19
+#define ESAI_xCR_xPR_MASK	(1 << ESAI_xCR_xPR_SHIFT)
+#define ESAI_xCR_xPR		(1 << ESAI_xCR_xPR_SHIFT)
+#define ESAI_xCR_PADC_SHIFT	17
+#define ESAI_xCR_PADC_MASK	(1 << ESAI_xCR_PADC_SHIFT)
+#define ESAI_xCR_PADC		(1 << ESAI_xCR_PADC_SHIFT)
+#define ESAI_xCR_xFSR_SHIFT	16
+#define ESAI_xCR_xFSR_MASK	(1 << ESAI_xCR_xFSR_SHIFT)
+#define ESAI_xCR_xFSR		(1 << ESAI_xCR_xFSR_SHIFT)
+#define ESAI_xCR_xFSL_SHIFT	15
+#define ESAI_xCR_xFSL_MASK	(1 << ESAI_xCR_xFSL_SHIFT)
+#define ESAI_xCR_xFSL		(1 << ESAI_xCR_xFSL_SHIFT)
+#define ESAI_xCR_xSWS_SHIFT	10
+#define ESAI_xCR_xSWS_WIDTH	5
+#define ESAI_xCR_xSWS_MASK	(((1 << ESAI_xCR_xSWS_WIDTH) - 1) << ESAI_xCR_xSWS_SHIFT)
+#define ESAI_xCR_xSWS(s, w)	((w < 24 ? (s - w + ((w - 8) >> 2)) : (s < 32 ? 0x1e : 0x1f)) << ESAI_xCR_xSWS_SHIFT)
+#define ESAI_xCR_xMOD_SHIFT	8
+#define ESAI_xCR_xMOD_WIDTH	2
+#define ESAI_xCR_xMOD_MASK	(((1 << ESAI_xCR_xMOD_WIDTH) - 1) << ESAI_xCR_xMOD_SHIFT)
+#define ESAI_xCR_xMOD_ONDEMAND	(0x1 << ESAI_xCR_xMOD_SHIFT)
+#define ESAI_xCR_xMOD_NETWORK	(0x1 << ESAI_xCR_xMOD_SHIFT)
+#define ESAI_xCR_xMOD_AC97	(0x3 << ESAI_xCR_xMOD_SHIFT)
+#define ESAI_xCR_xWA_SHIFT	7
+#define ESAI_xCR_xWA_MASK	(1 << ESAI_xCR_xWA_SHIFT)
+#define ESAI_xCR_xWA		(1 << ESAI_xCR_xWA_SHIFT)
+#define ESAI_xCR_xSHFD_SHIFT	6
+#define ESAI_xCR_xSHFD_MASK	(1 << ESAI_xCR_xSHFD_SHIFT)
+#define ESAI_xCR_xSHFD		(1 << ESAI_xCR_xSHFD_SHIFT)
+#define ESAI_xCR_xE_SHIFT	0
+#define ESAI_xCR_TE_WIDTH	6
+#define ESAI_xCR_RE_WIDTH	4
+#define ESAI_xCR_TE_MASK	(((1 << ESAI_xCR_TE_WIDTH) - 1) << ESAI_xCR_xE_SHIFT)
+#define ESAI_xCR_RE_MASK	(((1 << ESAI_xCR_RE_WIDTH) - 1) << ESAI_xCR_xE_SHIFT)
+#define ESAI_xCR_TE(x) 		((ESAI_xCR_TE_MASK >> (ESAI_xCR_TE_WIDTH - x)) & ESAI_xCR_TE_MASK)
+#define ESAI_xCR_RE(x) 		((ESAI_xCR_RE_MASK >> (ESAI_xCR_RE_WIDTH - x)) & ESAI_xCR_RE_MASK)
+
+/*
+ * Transmit Clock Control Register -- REG_ESAI_TCCR 0xD8
+ * Receive Clock Control Register -- REG_ESAI_RCCR 0xE0
+ */
+#define ESAI_xCCR_xHCKD_SHIFT	23
+#define ESAI_xCCR_xHCKD_MASK	(1 << ESAI_xCCR_xHCKD_SHIFT)
+#define ESAI_xCCR_xHCKD		(1 << ESAI_xCCR_xHCKD_SHIFT)
+#define ESAI_xCCR_xFSD_SHIFT	22
+#define ESAI_xCCR_xFSD_MASK	(1 << ESAI_xCCR_xFSD_SHIFT)
+#define ESAI_xCCR_xFSD		(1 << ESAI_xCCR_xFSD_SHIFT)
+#define ESAI_xCCR_xCKD_SHIFT	21
+#define ESAI_xCCR_xCKD_MASK	(1 << ESAI_xCCR_xCKD_SHIFT)
+#define ESAI_xCCR_xCKD		(1 << ESAI_xCCR_xCKD_SHIFT)
+#define ESAI_xCCR_xHCKP_SHIFT	20
+#define ESAI_xCCR_xHCKP_MASK	(1 << ESAI_xCCR_xHCKP_SHIFT)
+#define ESAI_xCCR_xHCKP		(1 << ESAI_xCCR_xHCKP_SHIFT)
+#define ESAI_xCCR_xFSP_SHIFT	19
+#define ESAI_xCCR_xFSP_MASK	(1 << ESAI_xCCR_xFSP_SHIFT)
+#define ESAI_xCCR_xFSP		(1 << ESAI_xCCR_xFSP_SHIFT)
+#define ESAI_xCCR_xCKP_SHIFT	18
+#define ESAI_xCCR_xCKP_MASK	(1 << ESAI_xCCR_xCKP_SHIFT)
+#define ESAI_xCCR_xCKP		(1 << ESAI_xCCR_xCKP_SHIFT)
+#define ESAI_xCCR_xFP_SHIFT	14
+#define ESAI_xCCR_xFP_WIDTH	4
+#define ESAI_xCCR_xFP_MASK	(((1 << ESAI_xCCR_xFP_WIDTH) - 1) << ESAI_xCCR_xFP_SHIFT)
+#define ESAI_xCCR_xFP(v)	((((v) - 1) << ESAI_xCCR_xFP_SHIFT) & ESAI_xCCR_xFP_MASK)
+#define ESAI_xCCR_xDC_SHIFT     9
+#define ESAI_xCCR_xDC_WIDTH	5
+#define ESAI_xCCR_xDC_MASK	(((1 << ESAI_xCCR_xDC_WIDTH) - 1) << ESAI_xCCR_xDC_SHIFT)
+#define ESAI_xCCR_xDC(v)	((((v) - 1) << ESAI_xCCR_xDC_SHIFT) & ESAI_xCCR_xDC_MASK)
+#define ESAI_xCCR_xPSR_SHIFT	8
+#define ESAI_xCCR_xPSR_MASK	(1 << ESAI_xCCR_xPSR_SHIFT)
+#define ESAI_xCCR_xPSR_BYPASS	(1 << ESAI_xCCR_xPSR_SHIFT)
+#define ESAI_xCCR_xPSR_DIV8	(0 << ESAI_xCCR_xPSR_SHIFT)
+#define ESAI_xCCR_xPM_SHIFT     0
+#define ESAI_xCCR_xPM_WIDTH     8
+#define ESAI_xCCR_xPM_MASK	(((1 << ESAI_xCCR_xPM_WIDTH) - 1) << ESAI_xCCR_xPM_SHIFT)
+#define ESAI_xCCR_xPM(v)	((((v) - 1) << ESAI_xCCR_xPM_SHIFT) & ESAI_xCCR_xPM_MASK)
+
+/* Transmit Slot Mask Register A/B -- REG_ESAI_TSMA/B 0xE4 ~ 0xF0 */
+#define ESAI_xSMA_xS_SHIFT	0
+#define ESAI_xSMA_xS_WIDTH	16
+#define ESAI_xSMA_xS_MASK	(((1 << ESAI_xSMA_xS_WIDTH) - 1) << ESAI_xSMA_xS_SHIFT)
+#define ESAI_xSMA_xS(v)		((v) & ESAI_xSMA_xS_MASK)
+#define ESAI_xSMB_xS_SHIFT	0
+#define ESAI_xSMB_xS_WIDTH	16
+#define ESAI_xSMB_xS_MASK	(((1 << ESAI_xSMB_xS_WIDTH) - 1) << ESAI_xSMB_xS_SHIFT)
+#define ESAI_xSMB_xS(v)		(((v) >> ESAI_xSMA_xS_WIDTH) & ESAI_xSMB_xS_MASK)
+
+/* Port C Direction Register -- REG_ESAI_PRRC 0xF8 */
+#define ESAI_PRRC_PDC_SHIFT	0
+#define ESAI_PRRC_PDC_WIDTH	12
+#define ESAI_PRRC_PDC_MASK	(((1 << ESAI_PRRC_PDC_WIDTH) - 1) << ESAI_PRRC_PDC_SHIFT)
+#define ESAI_PRRC_PDC(v)	((v) & ESAI_PRRC_PDC_MASK)
+
+/* Port C Control Register -- REG_ESAI_PCRC 0xFC */
+#define ESAI_PCRC_PC_SHIFT	0
+#define ESAI_PCRC_PC_WIDTH	12
+#define ESAI_PCRC_PC_MASK	(((1 << ESAI_PCRC_PC_WIDTH) - 1) << ESAI_PCRC_PC_SHIFT)
+#define ESAI_PCRC_PC(v)		((v) & ESAI_PCRC_PC_MASK)
+
+#define ESAI_GPIO		0xfff
+
+/* ESAI clock source */
+#define ESAI_HCKT_FSYS		0
+#define ESAI_HCKT_EXTAL		1
+#define ESAI_HCKR_FSYS		2
+#define ESAI_HCKR_EXTAL		3
+
+/* ESAI clock divider */
+#define ESAI_TX_DIV_PSR		0
+#define ESAI_TX_DIV_PM		1
+#define ESAI_TX_DIV_FP		2
+#define ESAI_RX_DIV_PSR		3
+#define ESAI_RX_DIV_PM		4
+#define ESAI_RX_DIV_FP		5
+#endif /* _FSL_ESAI_DAI_H */
diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c
new file mode 100644
index 0000000..4163f2c
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai.c
@@ -0,0 +1,963 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Freescale ALSA SoC Digital Audio Interface (SAI) driver.
+//
+// Copyright 2012-2015 Freescale Semiconductor, Inc.
+
+#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 <linux/time.h>
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+
+#include "fsl_sai.h"
+#include "imx-pcm.h"
+
+#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\
+		       FSL_SAI_CSR_FEIE)
+
+static const unsigned int fsl_sai_rates[] = {
+	8000, 11025, 12000, 16000, 22050,
+	24000, 32000, 44100, 48000, 64000,
+	88200, 96000, 176400, 192000
+};
+
+static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = {
+	.count = ARRAY_SIZE(fsl_sai_rates),
+	.list = fsl_sai_rates,
+};
+
+static irqreturn_t fsl_sai_isr(int irq, void *devid)
+{
+	struct fsl_sai *sai = (struct fsl_sai *)devid;
+	struct device *dev = &sai->pdev->dev;
+	u32 flags, xcsr, mask;
+	bool irq_none = true;
+
+	/*
+	 * Both IRQ status bits and IRQ mask bits are in the xCSR but
+	 * different shifts. And we here create a mask only for those
+	 * IRQs that we activated.
+	 */
+	mask = (FSL_SAI_FLAGS >> FSL_SAI_CSR_xIE_SHIFT) << FSL_SAI_CSR_xF_SHIFT;
+
+	/* Tx IRQ */
+	regmap_read(sai->regmap, FSL_SAI_TCSR, &xcsr);
+	flags = xcsr & mask;
+
+	if (flags)
+		irq_none = false;
+	else
+		goto irq_rx;
+
+	if (flags & FSL_SAI_CSR_WSF)
+		dev_dbg(dev, "isr: Start of Tx word detected\n");
+
+	if (flags & FSL_SAI_CSR_SEF)
+		dev_warn(dev, "isr: Tx Frame sync error detected\n");
+
+	if (flags & FSL_SAI_CSR_FEF) {
+		dev_warn(dev, "isr: Transmit underrun detected\n");
+		/* FIFO reset for safety */
+		xcsr |= FSL_SAI_CSR_FR;
+	}
+
+	if (flags & FSL_SAI_CSR_FWF)
+		dev_dbg(dev, "isr: Enabled transmit FIFO is empty\n");
+
+	if (flags & FSL_SAI_CSR_FRF)
+		dev_dbg(dev, "isr: Transmit FIFO watermark has been reached\n");
+
+	flags &= FSL_SAI_CSR_xF_W_MASK;
+	xcsr &= ~FSL_SAI_CSR_xF_MASK;
+
+	if (flags)
+		regmap_write(sai->regmap, FSL_SAI_TCSR, flags | xcsr);
+
+irq_rx:
+	/* Rx IRQ */
+	regmap_read(sai->regmap, FSL_SAI_RCSR, &xcsr);
+	flags = xcsr & mask;
+
+	if (flags)
+		irq_none = false;
+	else
+		goto out;
+
+	if (flags & FSL_SAI_CSR_WSF)
+		dev_dbg(dev, "isr: Start of Rx word detected\n");
+
+	if (flags & FSL_SAI_CSR_SEF)
+		dev_warn(dev, "isr: Rx Frame sync error detected\n");
+
+	if (flags & FSL_SAI_CSR_FEF) {
+		dev_warn(dev, "isr: Receive overflow detected\n");
+		/* FIFO reset for safety */
+		xcsr |= FSL_SAI_CSR_FR;
+	}
+
+	if (flags & FSL_SAI_CSR_FWF)
+		dev_dbg(dev, "isr: Enabled receive FIFO is full\n");
+
+	if (flags & FSL_SAI_CSR_FRF)
+		dev_dbg(dev, "isr: Receive FIFO watermark has been reached\n");
+
+	flags &= FSL_SAI_CSR_xF_W_MASK;
+	xcsr &= ~FSL_SAI_CSR_xF_MASK;
+
+	if (flags)
+		regmap_write(sai->regmap, FSL_SAI_RCSR, flags | xcsr);
+
+out:
+	if (irq_none)
+		return IRQ_NONE;
+	else
+		return IRQ_HANDLED;
+}
+
+static int fsl_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask,
+				u32 rx_mask, int slots, int slot_width)
+{
+	struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+
+	sai->slots = slots;
+	sai->slot_width = slot_width;
+
+	return 0;
+}
+
+static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai,
+		int clk_id, unsigned int freq, int fsl_dir)
+{
+	struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
+	u32 val_cr2 = 0;
+
+	switch (clk_id) {
+	case FSL_SAI_CLK_BUS:
+		val_cr2 |= FSL_SAI_CR2_MSEL_BUS;
+		break;
+	case FSL_SAI_CLK_MAST1:
+		val_cr2 |= FSL_SAI_CR2_MSEL_MCLK1;
+		break;
+	case FSL_SAI_CLK_MAST2:
+		val_cr2 |= FSL_SAI_CR2_MSEL_MCLK2;
+		break;
+	case FSL_SAI_CLK_MAST3:
+		val_cr2 |= FSL_SAI_CR2_MSEL_MCLK3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx),
+			   FSL_SAI_CR2_MSEL_MASK, val_cr2);
+
+	return 0;
+}
+
+static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	int ret;
+
+	if (dir == SND_SOC_CLOCK_IN)
+		return 0;
+
+	ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq,
+					FSL_FMT_TRANSMITTER);
+	if (ret) {
+		dev_err(cpu_dai->dev, "Cannot set tx sysclk: %d\n", ret);
+		return ret;
+	}
+
+	ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq,
+					FSL_FMT_RECEIVER);
+	if (ret)
+		dev_err(cpu_dai->dev, "Cannot set rx sysclk: %d\n", ret);
+
+	return ret;
+}
+
+static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
+				unsigned int fmt, int fsl_dir)
+{
+	struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
+	u32 val_cr2 = 0, val_cr4 = 0;
+
+	if (!sai->is_lsb_first)
+		val_cr4 |= FSL_SAI_CR4_MF;
+
+	/* DAI mode */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		/*
+		 * Frame low, 1clk before data, one word length for frame sync,
+		 * frame sync starts one serial clock cycle earlier,
+		 * that is, together with the last bit of the previous
+		 * data word.
+		 */
+		val_cr2 |= FSL_SAI_CR2_BCP;
+		val_cr4 |= FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		/*
+		 * Frame high, one word length for frame sync,
+		 * frame sync asserts with the first bit of the frame.
+		 */
+		val_cr2 |= FSL_SAI_CR2_BCP;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		/*
+		 * Frame high, 1clk before data, one bit for frame sync,
+		 * frame sync starts one serial clock cycle earlier,
+		 * that is, together with the last bit of the previous
+		 * data word.
+		 */
+		val_cr2 |= FSL_SAI_CR2_BCP;
+		val_cr4 |= FSL_SAI_CR4_FSE;
+		sai->is_dsp_mode = true;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		/*
+		 * Frame high, one bit for frame sync,
+		 * frame sync asserts with the first bit of the frame.
+		 */
+		val_cr2 |= FSL_SAI_CR2_BCP;
+		sai->is_dsp_mode = true;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		/* To be done */
+	default:
+		return -EINVAL;
+	}
+
+	/* DAI clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_IB_IF:
+		/* Invert both clocks */
+		val_cr2 ^= FSL_SAI_CR2_BCP;
+		val_cr4 ^= FSL_SAI_CR4_FSP;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		/* Invert bit clock */
+		val_cr2 ^= FSL_SAI_CR2_BCP;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		/* Invert frame clock */
+		val_cr4 ^= FSL_SAI_CR4_FSP;
+		break;
+	case SND_SOC_DAIFMT_NB_NF:
+		/* Nothing to do for both normal cases */
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* DAI clock master masks */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
+		val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		sai->is_slave_mode = true;
+		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;
+		sai->is_slave_mode = true;
+		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_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
+{
+	struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai);
+	unsigned long clk_rate;
+	u32 savediv = 0, ratio, savesub = freq;
+	u32 id;
+	int ret = 0;
+
+	/* Don't apply to slave mode */
+	if (sai->is_slave_mode)
+		return 0;
+
+	for (id = 0; id < FSL_SAI_MCLK_MAX; id++) {
+		clk_rate = clk_get_rate(sai->mclk_clk[id]);
+		if (!clk_rate)
+			continue;
+
+		ratio = clk_rate / freq;
+
+		ret = clk_rate - ratio * freq;
+
+		/*
+		 * Drop the source that can not be
+		 * divided into the required rate.
+		 */
+		if (ret != 0 && clk_rate / ret < 1000)
+			continue;
+
+		dev_dbg(dai->dev,
+			"ratio %d for freq %dHz based on clock %ldHz\n",
+			ratio, freq, clk_rate);
+
+		if (ratio % 2 == 0 && ratio >= 2 && ratio <= 512)
+			ratio /= 2;
+		else
+			continue;
+
+		if (ret < savesub) {
+			savediv = ratio;
+			sai->mclk_id[tx] = id;
+			savesub = ret;
+		}
+
+		if (ret == 0)
+			break;
+	}
+
+	if (savediv == 0) {
+		dev_err(dai->dev, "failed to derive required %cx rate: %d\n",
+				tx ? 'T' : 'R', freq);
+		return -EINVAL;
+	}
+
+	/*
+	 * 1) For Asynchronous mode, we must set RCR2 register for capture, and
+	 *    set TCR2 register for playback.
+	 * 2) For Tx sync with Rx clock, we must set RCR2 register for playback
+	 *    and capture.
+	 * 3) For Rx sync with Tx clock, we must set TCR2 register for playback
+	 *    and capture.
+	 * 4) For Tx and Rx are both Synchronous with another SAI, we just
+	 *    ignore it.
+	 */
+	if ((sai->synchronous[TX] && !sai->synchronous[RX]) ||
+	    (!tx && !sai->synchronous[RX])) {
+		regmap_update_bits(sai->regmap, FSL_SAI_RCR2,
+				   FSL_SAI_CR2_MSEL_MASK,
+				   FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
+		regmap_update_bits(sai->regmap, FSL_SAI_RCR2,
+				   FSL_SAI_CR2_DIV_MASK, savediv - 1);
+	} else if ((sai->synchronous[RX] && !sai->synchronous[TX]) ||
+		   (tx && !sai->synchronous[TX])) {
+		regmap_update_bits(sai->regmap, FSL_SAI_TCR2,
+				   FSL_SAI_CR2_MSEL_MASK,
+				   FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
+		regmap_update_bits(sai->regmap, FSL_SAI_TCR2,
+				   FSL_SAI_CR2_DIV_MASK, savediv - 1);
+	}
+
+	dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n",
+			sai->mclk_id[tx], savediv, savesub);
+
+	return 0;
+}
+
+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 = params_width(params);
+	u32 val_cr4 = 0, val_cr5 = 0;
+	u32 slots = (channels == 1) ? 2 : channels;
+	u32 slot_width = word_width;
+	int ret;
+
+	if (sai->slots)
+		slots = sai->slots;
+
+	if (sai->slot_width)
+		slot_width = sai->slot_width;
+
+	if (!sai->is_slave_mode) {
+		ret = fsl_sai_set_bclk(cpu_dai, tx,
+				slots * slot_width * params_rate(params));
+		if (ret)
+			return ret;
+
+		/* Do not enable the clock if it is already enabled */
+		if (!(sai->mclk_streams & BIT(substream->stream))) {
+			ret = clk_prepare_enable(sai->mclk_clk[sai->mclk_id[tx]]);
+			if (ret)
+				return ret;
+
+			sai->mclk_streams |= BIT(substream->stream);
+		}
+	}
+
+	if (!sai->is_dsp_mode)
+		val_cr4 |= FSL_SAI_CR4_SYWD(slot_width);
+
+	val_cr5 |= FSL_SAI_CR5_WNW(slot_width);
+	val_cr5 |= FSL_SAI_CR5_W0W(slot_width);
+
+	if (sai->is_lsb_first)
+		val_cr5 |= FSL_SAI_CR5_FBT(0);
+	else
+		val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1);
+
+	val_cr4 |= FSL_SAI_CR4_FRSZ(slots);
+
+	/*
+	 * For SAI master mode, when Tx(Rx) sync with Rx(Tx) clock, Rx(Tx) will
+	 * generate bclk and frame clock for Tx(Rx), we should set RCR4(TCR4),
+	 * RCR5(TCR5) and RMR(TMR) for playback(capture), or there will be sync
+	 * error.
+	 */
+
+	if (!sai->is_slave_mode) {
+		if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) {
+			regmap_update_bits(sai->regmap, FSL_SAI_TCR4,
+				FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
+				val_cr4);
+			regmap_update_bits(sai->regmap, FSL_SAI_TCR5,
+				FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
+				FSL_SAI_CR5_FBT_MASK, val_cr5);
+			regmap_write(sai->regmap, FSL_SAI_TMR,
+				~0UL - ((1 << channels) - 1));
+		} else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) {
+			regmap_update_bits(sai->regmap, FSL_SAI_RCR4,
+				FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
+				val_cr4);
+			regmap_update_bits(sai->regmap, FSL_SAI_RCR5,
+				FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
+				FSL_SAI_CR5_FBT_MASK, val_cr5);
+			regmap_write(sai->regmap, FSL_SAI_RMR,
+				~0UL - ((1 << channels) - 1));
+		}
+	}
+
+	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_hw_free(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;
+
+	if (!sai->is_slave_mode &&
+			sai->mclk_streams & BIT(substream->stream)) {
+		clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[tx]]);
+		sai->mclk_streams &= ~BIT(substream->stream);
+	}
+
+	return 0;
+}
+
+
+static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
+		struct snd_soc_dai *cpu_dai)
+{
+	struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+	bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	u32 xcsr, count = 100;
+
+	/*
+	 * Asynchronous mode: Clear SYNC for both Tx and Rx.
+	 * Rx sync with Tx clocks: Clear SYNC for Tx, set it for Rx.
+	 * Tx sync with Rx clocks: Clear SYNC for Rx, set it for Tx.
+	 */
+	regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC,
+		           sai->synchronous[TX] ? FSL_SAI_CR2_SYNC : 0);
+	regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC,
+			   sai->synchronous[RX] ? FSL_SAI_CR2_SYNC : 0);
+
+	/*
+	 * It is recommended that the transmitter is the last enabled
+	 * and the first disabled.
+	 */
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
+				   FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE);
+
+		regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
+				   FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+		regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
+				   FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+
+		regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
+				   FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
+				   FSL_SAI_CSR_FRDE, 0);
+		regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
+				   FSL_SAI_CSR_xIE_MASK, 0);
+
+		/* Check if the opposite FRDE is also disabled */
+		regmap_read(sai->regmap, FSL_SAI_xCSR(!tx), &xcsr);
+		if (!(xcsr & FSL_SAI_CSR_FRDE)) {
+			/* Disable both directions and reset their FIFOs */
+			regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
+					   FSL_SAI_CSR_TERE, 0);
+			regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
+					   FSL_SAI_CSR_TERE, 0);
+
+			/* TERE will remain set till the end of current frame */
+			do {
+				udelay(10);
+				regmap_read(sai->regmap, FSL_SAI_xCSR(tx), &xcsr);
+			} while (--count && xcsr & FSL_SAI_CSR_TERE);
+
+			regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
+					   FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
+			regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
+					   FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
+
+			/*
+			 * For sai master mode, after several open/close sai,
+			 * there will be no frame clock, and can't recover
+			 * anymore. Add software reset to fix this issue.
+			 * This is a hardware bug, and will be fix in the
+			 * next sai version.
+			 */
+			if (!sai->is_slave_mode) {
+				/* Software Reset for both Tx and Rx */
+				regmap_write(sai->regmap,
+					     FSL_SAI_TCSR, FSL_SAI_CSR_SR);
+				regmap_write(sai->regmap,
+					     FSL_SAI_RCSR, FSL_SAI_CSR_SR);
+				/* Clear SR bit to finish the reset */
+				regmap_write(sai->regmap, FSL_SAI_TCSR, 0);
+				regmap_write(sai->regmap, FSL_SAI_RCSR, 0);
+			}
+		}
+		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);
+
+	ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+			SNDRV_PCM_HW_PARAM_RATE, &fsl_sai_rate_constraints);
+
+	return ret;
+}
+
+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,
+	.set_tdm_slot	= fsl_sai_set_dai_tdm_slot,
+	.hw_params	= fsl_sai_hw_params,
+	.hw_free	= fsl_sai_hw_free,
+	.trigger	= fsl_sai_trigger,
+	.startup	= fsl_sai_startup,
+	.shutdown	= fsl_sai_shutdown,
+};
+
+static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+	struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+
+	/* Software Reset for both Tx and Rx */
+	regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR);
+	regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR);
+	/* Clear SR bit to finish the reset */
+	regmap_write(sai->regmap, FSL_SAI_TCSR, 0);
+	regmap_write(sai->regmap, FSL_SAI_RCSR, 0);
+
+	regmap_update_bits(sai->regmap, FSL_SAI_TCR1, FSL_SAI_CR1_RFW_MASK,
+			   FSL_SAI_MAXBURST_TX * 2);
+	regmap_update_bits(sai->regmap, FSL_SAI_RCR1, FSL_SAI_CR1_RFW_MASK,
+			   FSL_SAI_MAXBURST_RX - 1);
+
+	snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx,
+				&sai->dma_params_rx);
+
+	snd_soc_dai_set_drvdata(cpu_dai, sai);
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver fsl_sai_dai = {
+	.probe = fsl_sai_dai_probe,
+	.playback = {
+		.stream_name = "CPU-Playback",
+		.channels_min = 1,
+		.channels_max = 32,
+		.rate_min = 8000,
+		.rate_max = 192000,
+		.rates = SNDRV_PCM_RATE_KNOT,
+		.formats = FSL_SAI_FORMATS,
+	},
+	.capture = {
+		.stream_name = "CPU-Capture",
+		.channels_min = 1,
+		.channels_max = 32,
+		.rate_min = 8000,
+		.rate_max = 192000,
+		.rates = SNDRV_PCM_RATE_KNOT,
+		.formats = FSL_SAI_FORMATS,
+	},
+	.ops = &fsl_sai_pcm_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_component = {
+	.name           = "fsl-sai",
+};
+
+static struct reg_default fsl_sai_reg_defaults[] = {
+	{FSL_SAI_TCR1, 0},
+	{FSL_SAI_TCR2, 0},
+	{FSL_SAI_TCR3, 0},
+	{FSL_SAI_TCR4, 0},
+	{FSL_SAI_TCR5, 0},
+	{FSL_SAI_TDR,  0},
+	{FSL_SAI_TMR,  0},
+	{FSL_SAI_RCR1, 0},
+	{FSL_SAI_RCR2, 0},
+	{FSL_SAI_RCR3, 0},
+	{FSL_SAI_RCR4, 0},
+	{FSL_SAI_RCR5, 0},
+	{FSL_SAI_RMR,  0},
+};
+
+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_TCSR:
+	case FSL_SAI_RCSR:
+	case FSL_SAI_TFR:
+	case FSL_SAI_RFR:
+	case FSL_SAI_RDR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case FSL_SAI_TCSR:
+	case FSL_SAI_TCR1:
+	case FSL_SAI_TCR2:
+	case FSL_SAI_TCR3:
+	case FSL_SAI_TCR4:
+	case FSL_SAI_TCR5:
+	case FSL_SAI_TDR:
+	case FSL_SAI_TMR:
+	case FSL_SAI_RCSR:
+	case FSL_SAI_RCR1:
+	case FSL_SAI_RCR2:
+	case FSL_SAI_RCR3:
+	case FSL_SAI_RCR4:
+	case FSL_SAI_RCR5:
+	case FSL_SAI_RMR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config fsl_sai_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+
+	.max_register = FSL_SAI_RMR,
+	.reg_defaults = fsl_sai_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(fsl_sai_reg_defaults),
+	.readable_reg = fsl_sai_readable_reg,
+	.volatile_reg = fsl_sai_volatile_reg,
+	.writeable_reg = fsl_sai_writeable_reg,
+	.cache_type = REGCACHE_FLAT,
+};
+
+static int fsl_sai_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct fsl_sai *sai;
+	struct regmap *gpr;
+	struct resource *res;
+	void __iomem *base;
+	char tmp[8];
+	int irq, ret, i;
+	int index;
+
+	sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
+	if (!sai)
+		return -ENOMEM;
+
+	sai->pdev = pdev;
+
+	if (of_device_is_compatible(np, "fsl,imx6sx-sai") ||
+	    of_device_is_compatible(np, "fsl,imx6ul-sai"))
+		sai->sai_on_imx = true;
+
+	sai->is_lsb_first = of_property_read_bool(np, "lsb-first");
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+			"bus", base, &fsl_sai_regmap_config);
+
+	/* Compatible with old DTB cases */
+	if (IS_ERR(sai->regmap))
+		sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+				"sai", base, &fsl_sai_regmap_config);
+	if (IS_ERR(sai->regmap)) {
+		dev_err(&pdev->dev, "regmap init failed\n");
+		return PTR_ERR(sai->regmap);
+	}
+
+	/* No error out for old DTB cases but only mark the clock NULL */
+	sai->bus_clk = devm_clk_get(&pdev->dev, "bus");
+	if (IS_ERR(sai->bus_clk)) {
+		dev_err(&pdev->dev, "failed to get bus clock: %ld\n",
+				PTR_ERR(sai->bus_clk));
+		sai->bus_clk = NULL;
+	}
+
+	sai->mclk_clk[0] = sai->bus_clk;
+	for (i = 1; i < FSL_SAI_MCLK_MAX; i++) {
+		sprintf(tmp, "mclk%d", i);
+		sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp);
+		if (IS_ERR(sai->mclk_clk[i])) {
+			dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n",
+					i + 1, PTR_ERR(sai->mclk_clk[i]));
+			sai->mclk_clk[i] = NULL;
+		}
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+		return irq;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, 0, np->name, sai);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
+		return ret;
+	}
+
+	/* Sync Tx with Rx as default by following old DT binding */
+	sai->synchronous[RX] = true;
+	sai->synchronous[TX] = false;
+	fsl_sai_dai.symmetric_rates = 1;
+	fsl_sai_dai.symmetric_channels = 1;
+	fsl_sai_dai.symmetric_samplebits = 1;
+
+	if (of_find_property(np, "fsl,sai-synchronous-rx", NULL) &&
+	    of_find_property(np, "fsl,sai-asynchronous", NULL)) {
+		/* error out if both synchronous and asynchronous are present */
+		dev_err(&pdev->dev, "invalid binding for synchronous mode\n");
+		return -EINVAL;
+	}
+
+	if (of_find_property(np, "fsl,sai-synchronous-rx", NULL)) {
+		/* Sync Rx with Tx */
+		sai->synchronous[RX] = false;
+		sai->synchronous[TX] = true;
+	} else if (of_find_property(np, "fsl,sai-asynchronous", NULL)) {
+		/* Discard all settings for asynchronous mode */
+		sai->synchronous[RX] = false;
+		sai->synchronous[TX] = false;
+		fsl_sai_dai.symmetric_rates = 0;
+		fsl_sai_dai.symmetric_channels = 0;
+		fsl_sai_dai.symmetric_samplebits = 0;
+	}
+
+	if (of_find_property(np, "fsl,sai-mclk-direction-output", NULL) &&
+	    of_device_is_compatible(np, "fsl,imx6ul-sai")) {
+		gpr = syscon_regmap_lookup_by_compatible("fsl,imx6ul-iomuxc-gpr");
+		if (IS_ERR(gpr)) {
+			dev_err(&pdev->dev, "cannot find iomuxc registers\n");
+			return PTR_ERR(gpr);
+		}
+
+		index = of_alias_get_id(np, "sai");
+		if (index < 0)
+			return index;
+
+		regmap_update_bits(gpr, IOMUXC_GPR1, MCLK_DIR(index),
+				   MCLK_DIR(index));
+	}
+
+	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, IMX_SAI_DMABUF_SIZE);
+	else
+		return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+}
+
+static const struct of_device_id fsl_sai_ids[] = {
+	{ .compatible = "fsl,vf610-sai", },
+	{ .compatible = "fsl,imx6sx-sai", },
+	{ .compatible = "fsl,imx6ul-sai", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_sai_ids);
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_sai_suspend(struct device *dev)
+{
+	struct fsl_sai *sai = dev_get_drvdata(dev);
+
+	regcache_cache_only(sai->regmap, true);
+	regcache_mark_dirty(sai->regmap);
+
+	return 0;
+}
+
+static int fsl_sai_resume(struct device *dev)
+{
+	struct fsl_sai *sai = dev_get_drvdata(dev);
+
+	regcache_cache_only(sai->regmap, false);
+	regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR);
+	regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR);
+	usleep_range(1000, 2000);
+	regmap_write(sai->regmap, FSL_SAI_TCSR, 0);
+	regmap_write(sai->regmap, FSL_SAI_RCSR, 0);
+	return regcache_sync(sai->regmap);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_sai_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(fsl_sai_suspend, fsl_sai_resume)
+};
+
+static struct platform_driver fsl_sai_driver = {
+	.probe = fsl_sai_probe,
+	.driver = {
+		.name = "fsl-sai",
+		.pm = &fsl_sai_pm_ops,
+		.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 0000000..24cb156
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai.h
@@ -0,0 +1,153 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2012-2013 Freescale Semiconductor, Inc.
+ */
+
+#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 |\
+			 SNDRV_PCM_FMTBIT_S32_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/Receive Control Register */
+#define FSL_SAI_CSR_TERE	BIT(31)
+#define FSL_SAI_CSR_FR		BIT(25)
+#define FSL_SAI_CSR_SR		BIT(24)
+#define FSL_SAI_CSR_xF_SHIFT	16
+#define FSL_SAI_CSR_xF_W_SHIFT	18
+#define FSL_SAI_CSR_xF_MASK	(0x1f << FSL_SAI_CSR_xF_SHIFT)
+#define FSL_SAI_CSR_xF_W_MASK	(0x7 << FSL_SAI_CSR_xF_W_SHIFT)
+#define FSL_SAI_CSR_WSF		BIT(20)
+#define FSL_SAI_CSR_SEF		BIT(19)
+#define FSL_SAI_CSR_FEF		BIT(18)
+#define FSL_SAI_CSR_FWF		BIT(17)
+#define FSL_SAI_CSR_FRF		BIT(16)
+#define FSL_SAI_CSR_xIE_SHIFT	8
+#define FSL_SAI_CSR_xIE_MASK	(0x1f << FSL_SAI_CSR_xIE_SHIFT)
+#define FSL_SAI_CSR_WSIE	BIT(12)
+#define FSL_SAI_CSR_SEIE	BIT(11)
+#define FSL_SAI_CSR_FEIE	BIT(10)
+#define FSL_SAI_CSR_FWIE	BIT(9)
+#define FSL_SAI_CSR_FRIE	BIT(8)
+#define FSL_SAI_CSR_FRDE	BIT(0)
+
+/* SAI Transmit and Receive Configuration 1 Register */
+#define FSL_SAI_CR1_RFW_MASK	0x1f
+
+/* SAI Transmit and Receive Configuration 2 Register */
+#define FSL_SAI_CR2_SYNC	BIT(30)
+#define FSL_SAI_CR2_MSEL_MASK	(0x3 << 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_MSEL(ID)	((ID) << 26)
+#define FSL_SAI_CR2_BCP		BIT(25)
+#define FSL_SAI_CR2_BCD_MSTR	BIT(24)
+#define FSL_SAI_CR2_DIV_MASK	0xff
+
+/* SAI Transmit and Receive 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 Receive 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 Receive 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	4
+
+/* SAI data transfer numbers per DMA request */
+#define FSL_SAI_MAXBURST_TX 6
+#define FSL_SAI_MAXBURST_RX 6
+
+struct fsl_sai {
+	struct platform_device *pdev;
+	struct regmap *regmap;
+	struct clk *bus_clk;
+	struct clk *mclk_clk[FSL_SAI_MCLK_MAX];
+
+	bool is_slave_mode;
+	bool is_lsb_first;
+	bool is_dsp_mode;
+	bool sai_on_imx;
+	bool synchronous[2];
+
+	unsigned int mclk_id[2];
+	unsigned int mclk_streams;
+	unsigned int slots;
+	unsigned int slot_width;
+
+	struct snd_dmaengine_dai_dma_data dma_params_rx;
+	struct snd_dmaengine_dai_dma_data dma_params_tx;
+};
+
+#define TX 1
+#define RX 0
+
+#endif /* __FSL_SAI_H */
diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c
new file mode 100644
index 0000000..740b90d
--- /dev/null
+++ b/sound/soc/fsl/fsl_spdif.c
@@ -0,0 +1,1382 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// 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
+
+#include <linux/bitrev.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+
+#include <sound/asoundef.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+#include "fsl_spdif.h"
+#include "imx-pcm.h"
+
+#define FSL_SPDIF_TXFIFO_WML	0x8
+#define FSL_SPDIF_RXFIFO_WML	0x8
+
+#define INTR_FOR_PLAYBACK	(INT_TXFIFO_RESYNC)
+#define INTR_FOR_CAPTURE	(INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL |\
+				INT_URX_OV | INT_QRX_FUL | INT_QRX_OV |\
+				INT_UQ_SYNC | INT_UQ_ERR | INT_RXFIFO_RESYNC |\
+				INT_LOSS_LOCK | INT_DPLL_LOCKED)
+
+#define SIE_INTR_FOR(tx)	(tx ? INTR_FOR_PLAYBACK : INTR_FOR_CAPTURE)
+
+/* Index list for the values that has if (DPLL Locked) condition */
+static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb };
+#define SRPC_NODPLL_START1	0x5
+#define SRPC_NODPLL_START2	0xc
+
+#define DEFAULT_RXCLK_SRC	1
+
+/*
+ * SPDIF control structure
+ * Defines channel status, subcode and Q sub
+ */
+struct spdif_mixer_control {
+	/* spinlock to access control data */
+	spinlock_t ctl_lock;
+
+	/* IEC958 channel tx status bit */
+	unsigned char ch_status[4];
+
+	/* User bits */
+	unsigned char subcode[2 * SPDIF_UBITS_SIZE];
+
+	/* Q subcode part of user bits */
+	unsigned char qsub[2 * SPDIF_QSUB_SIZE];
+
+	/* Buffer offset for U/Q */
+	u32 upos;
+	u32 qpos;
+
+	/* Ready buffer index of the two buffers */
+	u32 ready_buf;
+};
+
+/**
+ * fsl_spdif_priv: Freescale SPDIF private data
+ *
+ * @fsl_spdif_control: SPDIF control data
+ * @cpu_dai_drv: cpu dai driver
+ * @pdev: platform device pointer
+ * @regmap: regmap handler
+ * @dpll_locked: dpll lock flag
+ * @txrate: the best rates for playback
+ * @txclk_df: STC_TXCLK_DF dividers value for playback
+ * @sysclk_df: STC_SYSCLK_DF dividers value for playback
+ * @txclk_src: STC_TXCLK_SRC values for playback
+ * @rxclk_src: SRPC_CLKSRC_SEL values for capture
+ * @txclk: tx clock sources for playback
+ * @rxclk: rx clock sources for capture
+ * @coreclk: core clock for register access via DMA
+ * @sysclk: system clock for rx clock rate measurement
+ * @spbaclk: SPBA clock (optional, depending on SoC design)
+ * @dma_params_tx: DMA parameters for transmit channel
+ * @dma_params_rx: DMA parameters for receive channel
+ */
+struct fsl_spdif_priv {
+	struct spdif_mixer_control fsl_spdif_control;
+	struct snd_soc_dai_driver cpu_dai_drv;
+	struct platform_device *pdev;
+	struct regmap *regmap;
+	bool dpll_locked;
+	u32 txrate[SPDIF_TXRATE_MAX];
+	u8 txclk_df[SPDIF_TXRATE_MAX];
+	u8 sysclk_df[SPDIF_TXRATE_MAX];
+	u8 txclk_src[SPDIF_TXRATE_MAX];
+	u8 rxclk_src;
+	struct clk *txclk[SPDIF_TXRATE_MAX];
+	struct clk *rxclk;
+	struct clk *coreclk;
+	struct clk *sysclk;
+	struct clk *spbaclk;
+	struct snd_dmaengine_dai_dma_data dma_params_tx;
+	struct snd_dmaengine_dai_dma_data dma_params_rx;
+	/* regcache for SRPC */
+	u32 regcache_srpc;
+};
+
+/* DPLL locked and lock loss interrupt handler */
+static void spdif_irq_dpll_lock(struct fsl_spdif_priv *spdif_priv)
+{
+	struct regmap *regmap = spdif_priv->regmap;
+	struct platform_device *pdev = spdif_priv->pdev;
+	u32 locked;
+
+	regmap_read(regmap, REG_SPDIF_SRPC, &locked);
+	locked &= SRPC_DPLL_LOCKED;
+
+	dev_dbg(&pdev->dev, "isr: Rx dpll %s \n",
+			locked ? "locked" : "loss lock");
+
+	spdif_priv->dpll_locked = locked ? true : false;
+}
+
+/* Receiver found illegal symbol interrupt handler */
+static void spdif_irq_sym_error(struct fsl_spdif_priv *spdif_priv)
+{
+	struct regmap *regmap = spdif_priv->regmap;
+	struct platform_device *pdev = spdif_priv->pdev;
+
+	dev_dbg(&pdev->dev, "isr: receiver found illegal symbol\n");
+
+	/* Clear illegal symbol if DPLL unlocked since no audio stream */
+	if (!spdif_priv->dpll_locked)
+		regmap_update_bits(regmap, REG_SPDIF_SIE, INT_SYM_ERR, 0);
+}
+
+/* U/Q Channel receive register full */
+static void spdif_irq_uqrx_full(struct fsl_spdif_priv *spdif_priv, char name)
+{
+	struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control;
+	struct regmap *regmap = spdif_priv->regmap;
+	struct platform_device *pdev = spdif_priv->pdev;
+	u32 *pos, size, val, reg;
+
+	switch (name) {
+	case 'U':
+		pos = &ctrl->upos;
+		size = SPDIF_UBITS_SIZE;
+		reg = REG_SPDIF_SRU;
+		break;
+	case 'Q':
+		pos = &ctrl->qpos;
+		size = SPDIF_QSUB_SIZE;
+		reg = REG_SPDIF_SRQ;
+		break;
+	default:
+		dev_err(&pdev->dev, "unsupported channel name\n");
+		return;
+	}
+
+	dev_dbg(&pdev->dev, "isr: %c Channel receive register full\n", name);
+
+	if (*pos >= size * 2) {
+		*pos = 0;
+	} else if (unlikely((*pos % size) + 3 > size)) {
+		dev_err(&pdev->dev, "User bit receive 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;
+
+	regcache_cache_bypass(regmap, true);
+
+	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--);
+
+	regcache_cache_bypass(regmap, false);
+	regcache_mark_dirty(regmap);
+	regcache_sync(regmap);
+
+	if (cycle)
+		return 0;
+	else
+		return -EBUSY;
+}
+
+static void spdif_set_cstatus(struct spdif_mixer_control *ctrl,
+				u8 mask, u8 cstatus)
+{
+	ctrl->ch_status[3] &= ~mask;
+	ctrl->ch_status[3] |= cstatus & mask;
+}
+
+static void spdif_write_channel_status(struct fsl_spdif_priv *spdif_priv)
+{
+	struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control;
+	struct regmap *regmap = spdif_priv->regmap;
+	struct platform_device *pdev = spdif_priv->pdev;
+	u32 ch_status;
+
+	ch_status = (bitrev8(ctrl->ch_status[0]) << 16) |
+		    (bitrev8(ctrl->ch_status[1]) << 8) |
+		    bitrev8(ctrl->ch_status[2]);
+	regmap_write(regmap, REG_SPDIF_STCSCH, ch_status);
+
+	dev_dbg(&pdev->dev, "STCSCH: 0x%06x\n", ch_status);
+
+	ch_status = bitrev8(ctrl->ch_status[3]) << 16;
+	regmap_write(regmap, REG_SPDIF_STCSCL, ch_status);
+
+	dev_dbg(&pdev->dev, "STCSCL: 0x%06x\n", ch_status);
+}
+
+/* Set SPDIF PhaseConfig register for rx clock */
+static int spdif_set_rx_clksrc(struct fsl_spdif_priv *spdif_priv,
+				enum spdif_gainsel gainsel, int dpll_locked)
+{
+	struct regmap *regmap = spdif_priv->regmap;
+	u8 clksrc = spdif_priv->rxclk_src;
+
+	if (clksrc >= SRPC_CLKSRC_MAX || gainsel >= GAINSEL_MULTI_MAX)
+		return -EINVAL;
+
+	regmap_update_bits(regmap, REG_SPDIF_SRPC,
+			SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK,
+			SRPC_CLKSRC_SEL_SET(clksrc) | SRPC_GAINSEL_SET(gainsel));
+
+	return 0;
+}
+
+static int spdif_set_sample_rate(struct snd_pcm_substream *substream,
+				int sample_rate)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control;
+	struct regmap *regmap = spdif_priv->regmap;
+	struct platform_device *pdev = spdif_priv->pdev;
+	unsigned long csfs = 0;
+	u32 stc, mask, rate;
+	u8 clk, txclk_df, sysclk_df;
+	int ret;
+
+	switch (sample_rate) {
+	case 32000:
+		rate = SPDIF_TXRATE_32000;
+		csfs = IEC958_AES3_CON_FS_32000;
+		break;
+	case 44100:
+		rate = SPDIF_TXRATE_44100;
+		csfs = IEC958_AES3_CON_FS_44100;
+		break;
+	case 48000:
+		rate = SPDIF_TXRATE_48000;
+		csfs = IEC958_AES3_CON_FS_48000;
+		break;
+	case 96000:
+		rate = SPDIF_TXRATE_96000;
+		csfs = IEC958_AES3_CON_FS_96000;
+		break;
+	case 192000:
+		rate = SPDIF_TXRATE_192000;
+		csfs = IEC958_AES3_CON_FS_192000;
+		break;
+	default:
+		dev_err(&pdev->dev, "unsupported sample rate %d\n", sample_rate);
+		return -EINVAL;
+	}
+
+	clk = spdif_priv->txclk_src[rate];
+	if (clk >= STC_TXCLK_SRC_MAX) {
+		dev_err(&pdev->dev, "tx clock source is out of range\n");
+		return -EINVAL;
+	}
+
+	txclk_df = spdif_priv->txclk_df[rate];
+	if (txclk_df == 0) {
+		dev_err(&pdev->dev, "the txclk_df can't be zero\n");
+		return -EINVAL;
+	}
+
+	sysclk_df = spdif_priv->sysclk_df[rate];
+
+	/* Don't mess up the clocks from other modules */
+	if (clk != STC_TXCLK_SPDIF_ROOT)
+		goto clk_set_bypass;
+
+	/* The S/PDIF block needs a clock of 64 * fs * txclk_df */
+	ret = clk_set_rate(spdif_priv->txclk[rate],
+			   64 * sample_rate * txclk_df);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to set tx clock rate\n");
+		return ret;
+	}
+
+clk_set_bypass:
+	dev_dbg(&pdev->dev, "expected clock rate = %d\n",
+			(64 * sample_rate * txclk_df * sysclk_df));
+	dev_dbg(&pdev->dev, "actual clock rate = %ld\n",
+			clk_get_rate(spdif_priv->txclk[rate]));
+
+	/* set fs field in consumer channel status */
+	spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs);
+
+	/* select clock source and divisor */
+	stc = STC_TXCLK_ALL_EN | STC_TXCLK_SRC_SET(clk) |
+	      STC_TXCLK_DF(txclk_df) | STC_SYSCLK_DF(sysclk_df);
+	mask = STC_TXCLK_ALL_EN_MASK | STC_TXCLK_SRC_MASK |
+	       STC_TXCLK_DF_MASK | STC_SYSCLK_DF_MASK;
+	regmap_update_bits(regmap, REG_SPDIF_STC, mask, stc);
+
+	dev_dbg(&pdev->dev, "set sample rate to %dHz for %dHz playback\n",
+			spdif_priv->txrate[rate], sample_rate);
+
+	return 0;
+}
+
+static int fsl_spdif_startup(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *cpu_dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct platform_device *pdev = spdif_priv->pdev;
+	struct regmap *regmap = spdif_priv->regmap;
+	u32 scr, mask;
+	int 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;
+		}
+
+		if (!IS_ERR(spdif_priv->spbaclk)) {
+			ret = clk_prepare_enable(spdif_priv->spbaclk);
+			if (ret) {
+				dev_err(&pdev->dev, "failed to enable spba clock\n");
+				goto err_spbaclk;
+			}
+		}
+
+		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++) {
+			ret = clk_prepare_enable(spdif_priv->txclk[i]);
+			if (ret)
+				goto disable_txclk;
+		}
+	} 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;
+		ret = clk_prepare_enable(spdif_priv->rxclk);
+		if (ret)
+			goto err;
+	}
+	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;
+
+disable_txclk:
+	for (i--; i >= 0; i--)
+		clk_disable_unprepare(spdif_priv->txclk[i]);
+err:
+	if (!IS_ERR(spdif_priv->spbaclk))
+		clk_disable_unprepare(spdif_priv->spbaclk);
+err_spbaclk:
+	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);
+		if (!IS_ERR(spdif_priv->spbaclk))
+			clk_disable_unprepare(spdif_priv->spbaclk);
+		clk_disable_unprepare(spdif_priv->coreclk);
+	}
+}
+
+static int fsl_spdif_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control;
+	struct platform_device *pdev = spdif_priv->pdev;
+	u32 sample_rate = params_rate(params);
+	int ret = 0;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		ret  = spdif_set_sample_rate(substream, sample_rate);
+		if (ret) {
+			dev_err(&pdev->dev, "%s: set sample rate failed: %d\n",
+					__func__, sample_rate);
+			return ret;
+		}
+		spdif_set_cstatus(ctrl, IEC958_AES3_CON_CLOCK,
+				  IEC958_AES3_CON_CLOCK_1000PPM);
+		spdif_write_channel_status(spdif_priv);
+	} else {
+		/* Setup rx clock source */
+		ret = spdif_set_rx_clksrc(spdif_priv, SPDIF_DEFAULT_GAINSEL, 1);
+	}
+
+	return ret;
+}
+
+static int fsl_spdif_trigger(struct snd_pcm_substream *substream,
+				int cmd, struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct regmap *regmap = spdif_priv->regmap;
+	bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	u32 intr = SIE_INTR_FOR(tx);
+	u32 dmaen = SCR_DMA_xX_EN(tx);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		regmap_update_bits(regmap, REG_SPDIF_SIE, intr, intr);
+		regmap_update_bits(regmap, REG_SPDIF_SCR, dmaen, dmaen);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		regmap_update_bits(regmap, REG_SPDIF_SCR, dmaen, 0);
+		regmap_update_bits(regmap, REG_SPDIF_SIE, intr, 0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops fsl_spdif_dai_ops = {
+	.startup = fsl_spdif_startup,
+	.hw_params = fsl_spdif_hw_params,
+	.trigger = fsl_spdif_trigger,
+	.shutdown = fsl_spdif_shutdown,
+};
+
+
+/*
+ * FSL SPDIF IEC958 controller(mixer) functions
+ *
+ *	Channel status get/put control
+ *	User bit value get/put control
+ *	Valid bit value get control
+ *	DPLL lock status get control
+ *	User bit sync mode selection control
+ */
+
+static int fsl_spdif_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+
+	return 0;
+}
+
+static int fsl_spdif_pb_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *uvalue)
+{
+	struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control;
+
+	uvalue->value.iec958.status[0] = ctrl->ch_status[0];
+	uvalue->value.iec958.status[1] = ctrl->ch_status[1];
+	uvalue->value.iec958.status[2] = ctrl->ch_status[2];
+	uvalue->value.iec958.status[3] = ctrl->ch_status[3];
+
+	return 0;
+}
+
+static int fsl_spdif_pb_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *uvalue)
+{
+	struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control;
+
+	ctrl->ch_status[0] = uvalue->value.iec958.status[0];
+	ctrl->ch_status[1] = uvalue->value.iec958.status[1];
+	ctrl->ch_status[2] = uvalue->value.iec958.status[2];
+	ctrl->ch_status[3] = uvalue->value.iec958.status[3];
+
+	spdif_write_channel_status(spdif_priv);
+
+	return 0;
+}
+
+/* Get channel status from SPDIF_RX_CCHAN register */
+static int fsl_spdif_capture_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct regmap *regmap = spdif_priv->regmap;
+	u32 cstatus, val;
+
+	regmap_read(regmap, REG_SPDIF_SIS, &val);
+	if (!(val & INT_CNEW))
+		return -EAGAIN;
+
+	regmap_read(regmap, REG_SPDIF_SRCSH, &cstatus);
+	ucontrol->value.iec958.status[0] = (cstatus >> 16) & 0xFF;
+	ucontrol->value.iec958.status[1] = (cstatus >> 8) & 0xFF;
+	ucontrol->value.iec958.status[2] = cstatus & 0xFF;
+
+	regmap_read(regmap, REG_SPDIF_SRCSL, &cstatus);
+	ucontrol->value.iec958.status[3] = (cstatus >> 16) & 0xFF;
+	ucontrol->value.iec958.status[4] = (cstatus >> 8) & 0xFF;
+	ucontrol->value.iec958.status[5] = cstatus & 0xFF;
+
+	/* Clear intr */
+	regmap_write(regmap, REG_SPDIF_SIC, INT_CNEW);
+
+	return 0;
+}
+
+/*
+ * Get User bits (subcode) from chip value which readed out
+ * in UChannel register.
+ */
+static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control;
+	unsigned long flags;
+	int ret = -EAGAIN;
+
+	spin_lock_irqsave(&ctrl->ctl_lock, flags);
+	if (ctrl->ready_buf) {
+		int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE;
+		memcpy(&ucontrol->value.iec958.subcode[0],
+				&ctrl->subcode[idx], SPDIF_UBITS_SIZE);
+		ret = 0;
+	}
+	spin_unlock_irqrestore(&ctrl->ctl_lock, flags);
+
+	return ret;
+}
+
+/* Q-subcode information. The byte size is SPDIF_UBITS_SIZE/8 */
+static int fsl_spdif_qinfo(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	uinfo->count = SPDIF_QSUB_SIZE;
+
+	return 0;
+}
+
+/* Get Q subcode from chip value which readed out in QChannel register */
+static int fsl_spdif_qget(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control;
+	unsigned long flags;
+	int ret = -EAGAIN;
+
+	spin_lock_irqsave(&ctrl->ctl_lock, flags);
+	if (ctrl->ready_buf) {
+		int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE;
+		memcpy(&ucontrol->value.bytes.data[0],
+				&ctrl->qsub[idx], SPDIF_QSUB_SIZE);
+		ret = 0;
+	}
+	spin_unlock_irqrestore(&ctrl->ctl_lock, flags);
+
+	return ret;
+}
+
+/* Valid bit information */
+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 information */
+static int fsl_spdif_rxrate_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 16000;
+	uinfo->value.integer.max = 96000;
+
+	return 0;
+}
+
+static u32 gainsel_multi[GAINSEL_MULTI_MAX] = {
+	24, 16, 12, 8, 6, 4, 3,
+};
+
+/* Get RX data clock rate given the SPDIF bus_clk */
+static int spdif_get_rxclk_rate(struct fsl_spdif_priv *spdif_priv,
+				enum spdif_gainsel gainsel)
+{
+	struct regmap *regmap = spdif_priv->regmap;
+	struct platform_device *pdev = spdif_priv->pdev;
+	u64 tmpval64, busclk_freq = 0;
+	u32 freqmeas, phaseconf;
+	u8 clksrc;
+
+	regmap_read(regmap, REG_SPDIF_SRFM, &freqmeas);
+	regmap_read(regmap, REG_SPDIF_SRPC, &phaseconf);
+
+	clksrc = (phaseconf >> SRPC_CLKSRC_SEL_OFFSET) & 0xf;
+
+	/* Get bus clock from system */
+	if (srpc_dpll_locked[clksrc] && (phaseconf & SRPC_DPLL_LOCKED))
+		busclk_freq = clk_get_rate(spdif_priv->sysclk);
+
+	/* FreqMeas_CLK = (BUS_CLK * FreqMeas) / 2 ^ 10 / GAINSEL / 128 */
+	tmpval64 = (u64) busclk_freq * freqmeas;
+	do_div(tmpval64, gainsel_multi[gainsel] * 1024);
+	do_div(tmpval64, 128 * 1024);
+
+	dev_dbg(&pdev->dev, "FreqMeas: %d\n", freqmeas);
+	dev_dbg(&pdev->dev, "BusclkFreq: %lld\n", busclk_freq);
+	dev_dbg(&pdev->dev, "RxRate: %lld\n", tmpval64);
+
+	return (int)tmpval64;
+}
+
+/*
+ * Get DPLL lock or not info from stable interrupt status register.
+ * User application must use this control to get locked,
+ * then can do next PCM operation
+ */
+static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	int rate = 0;
+
+	if (spdif_priv->dpll_locked)
+		rate = spdif_get_rxclk_rate(spdif_priv, SPDIF_DEFAULT_GAINSEL);
+
+	ucontrol->value.integer.value[0] = rate;
+
+	return 0;
+}
+
+/* User bit sync mode info */
+static int fsl_spdif_usync_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+
+	return 0;
+}
+
+/*
+ * User bit sync mode:
+ * 1 CD User channel subcode
+ * 0 Non-CD data
+ */
+static int fsl_spdif_usync_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct regmap *regmap = spdif_priv->regmap;
+	u32 val;
+
+	regmap_read(regmap, REG_SPDIF_SRCD, &val);
+	ucontrol->value.integer.value[0] = (val & SRCD_CD_USER) != 0;
+
+	return 0;
+}
+
+/*
+ * User bit sync mode:
+ * 1 CD User channel subcode
+ * 0 Non-CD data
+ */
+static int fsl_spdif_usync_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+	struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+	struct regmap *regmap = spdif_priv->regmap;
+	u32 val = ucontrol->value.integer.value[0] << SRCD_CD_USER_OFFSET;
+
+	regmap_update_bits(regmap, REG_SPDIF_SRCD, SRCD_CD_USER, val);
+
+	return 0;
+}
+
+/* FSL SPDIF IEC958 controller defines */
+static struct snd_kcontrol_new fsl_spdif_ctrls[] = {
+	/* Status cchanel controller */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_WRITE |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = fsl_spdif_info,
+		.get = fsl_spdif_pb_get,
+		.put = fsl_spdif_pb_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = fsl_spdif_info,
+		.get = fsl_spdif_capture_get,
+	},
+	/* User bits controller */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = "IEC958 Subcode Capture Default",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = fsl_spdif_info,
+		.get = fsl_spdif_subcode_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = "IEC958 Q-subcode Capture Default",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = fsl_spdif_qinfo,
+		.get = fsl_spdif_qget,
+	},
+	/* Valid bit error controller */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = "IEC958 V-Bit Errors",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = fsl_spdif_vbit_info,
+		.get = fsl_spdif_vbit_get,
+	},
+	/* DPLL lock info get controller */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = "RX Sample Rate",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = fsl_spdif_rxrate_info,
+		.get = fsl_spdif_rxrate_get,
+	},
+	/* User bit sync mode set/get controller */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = "IEC958 USyncMode CDText",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_WRITE |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = fsl_spdif_usync_info,
+		.get = fsl_spdif_usync_get,
+		.put = fsl_spdif_usync_put,
+	},
+};
+
+static int fsl_spdif_dai_probe(struct snd_soc_dai *dai)
+{
+	struct fsl_spdif_priv *spdif_private = snd_soc_dai_get_drvdata(dai);
+
+	snd_soc_dai_init_dma_data(dai, &spdif_private->dma_params_tx,
+				  &spdif_private->dma_params_rx);
+
+	snd_soc_add_dai_controls(dai, fsl_spdif_ctrls, ARRAY_SIZE(fsl_spdif_ctrls));
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver fsl_spdif_dai = {
+	.probe = &fsl_spdif_dai_probe,
+	.playback = {
+		.stream_name = "CPU-Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = FSL_SPDIF_RATES_PLAYBACK,
+		.formats = FSL_SPDIF_FORMATS_PLAYBACK,
+	},
+	.capture = {
+		.stream_name = "CPU-Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = FSL_SPDIF_RATES_CAPTURE,
+		.formats = FSL_SPDIF_FORMATS_CAPTURE,
+	},
+	.ops = &fsl_spdif_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_spdif_component = {
+	.name		= "fsl-spdif",
+};
+
+/* FSL SPDIF REGMAP */
+static const struct reg_default fsl_spdif_reg_defaults[] = {
+	{REG_SPDIF_SCR,    0x00000400},
+	{REG_SPDIF_SRCD,   0x00000000},
+	{REG_SPDIF_SIE,	   0x00000000},
+	{REG_SPDIF_STL,	   0x00000000},
+	{REG_SPDIF_STR,	   0x00000000},
+	{REG_SPDIF_STCSCH, 0x00000000},
+	{REG_SPDIF_STCSCL, 0x00000000},
+	{REG_SPDIF_STC,	   0x00020f00},
+};
+
+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_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_SPDIF_SRPC:
+	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_SRFM:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool fsl_spdif_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_SPDIF_SCR:
+	case REG_SPDIF_SRCD:
+	case REG_SPDIF_SRPC:
+	case REG_SPDIF_SIE:
+	case REG_SPDIF_SIC:
+	case REG_SPDIF_STL:
+	case REG_SPDIF_STR:
+	case REG_SPDIF_STCSCH:
+	case REG_SPDIF_STCSCL:
+	case REG_SPDIF_STC:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config fsl_spdif_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+
+	.max_register = REG_SPDIF_STC,
+	.reg_defaults = fsl_spdif_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(fsl_spdif_reg_defaults),
+	.readable_reg = fsl_spdif_readable_reg,
+	.volatile_reg = fsl_spdif_volatile_reg,
+	.writeable_reg = fsl_spdif_writeable_reg,
+	.cache_type = REGCACHE_FLAT,
+};
+
+static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
+				struct clk *clk, u64 savesub,
+				enum spdif_txrate index, bool round)
+{
+	static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
+	bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
+	u64 rate_ideal, rate_actual, sub;
+	u32 sysclk_dfmin, sysclk_dfmax;
+	u32 txclk_df, sysclk_df, arate;
+
+	/* The sysclk has an extra divisor [2, 512] */
+	sysclk_dfmin = is_sysclk ? 2 : 1;
+	sysclk_dfmax = is_sysclk ? 512 : 1;
+
+	for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) {
+		for (txclk_df = 1; txclk_df <= 128; txclk_df++) {
+			rate_ideal = rate[index] * txclk_df * 64ULL;
+			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)
+{
+	static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
+	struct platform_device *pdev = spdif_priv->pdev;
+	struct device *dev = &pdev->dev;
+	u64 savesub = 100000, ret;
+	struct clk *clk;
+	char tmp[16];
+	int i;
+
+	for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
+		sprintf(tmp, "rxtx%d", i);
+		clk = devm_clk_get(&pdev->dev, tmp);
+		if (IS_ERR(clk)) {
+			dev_err(dev, "no rxtx%d clock in devicetree\n", i);
+			return PTR_ERR(clk);
+		}
+		if (!clk_get_rate(clk))
+			continue;
+
+		ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index,
+					     i == STC_TXCLK_SPDIF_ROOT);
+		if (savesub == ret)
+			continue;
+
+		savesub = ret;
+		spdif_priv->txclk[index] = clk;
+		spdif_priv->txclk_src[index] = i;
+
+		/* To quick catch a divisor, we allow a 0.1% deviation */
+		if (savesub < 100)
+			break;
+	}
+
+	dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
+			spdif_priv->txclk_src[index], rate[index]);
+	dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n",
+			spdif_priv->txclk_df[index], rate[index]);
+	if (clk_is_match(spdif_priv->txclk[index], spdif_priv->sysclk))
+		dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n",
+				spdif_priv->sysclk_df[index], rate[index]);
+	dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n",
+			rate[index], spdif_priv->txrate[index]);
+
+	return 0;
+}
+
+static int fsl_spdif_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct fsl_spdif_priv *spdif_priv;
+	struct spdif_mixer_control *ctrl;
+	struct resource *res;
+	void __iomem *regs;
+	int irq, ret, i;
+
+	if (!np)
+		return -ENODEV;
+
+	spdif_priv = devm_kzalloc(&pdev->dev, sizeof(*spdif_priv), GFP_KERNEL);
+	if (!spdif_priv)
+		return -ENOMEM;
+
+	spdif_priv->pdev = pdev;
+
+	/* Initialize this copy of the CPU DAI driver structure */
+	memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai));
+	spdif_priv->cpu_dai_drv.name = dev_name(&pdev->dev);
+
+	/* Get the addresses and IRQ */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	spdif_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+			"core", regs, &fsl_spdif_regmap_config);
+	if (IS_ERR(spdif_priv->regmap)) {
+		dev_err(&pdev->dev, "regmap init failed\n");
+		return PTR_ERR(spdif_priv->regmap);
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+		return irq;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0,
+			       dev_name(&pdev->dev), spdif_priv);
+	if (ret) {
+		dev_err(&pdev->dev, "could not claim irq %u\n", irq);
+		return ret;
+	}
+
+	/* Get system clock for rx clock rate calculation */
+	spdif_priv->sysclk = devm_clk_get(&pdev->dev, "rxtx5");
+	if (IS_ERR(spdif_priv->sysclk)) {
+		dev_err(&pdev->dev, "no sys clock (rxtx5) in devicetree\n");
+		return PTR_ERR(spdif_priv->sysclk);
+	}
+
+	/* Get core clock for data register access via DMA */
+	spdif_priv->coreclk = devm_clk_get(&pdev->dev, "core");
+	if (IS_ERR(spdif_priv->coreclk)) {
+		dev_err(&pdev->dev, "no core clock in devicetree\n");
+		return PTR_ERR(spdif_priv->coreclk);
+	}
+
+	spdif_priv->spbaclk = devm_clk_get(&pdev->dev, "spba");
+	if (IS_ERR(spdif_priv->spbaclk))
+		dev_warn(&pdev->dev, "no spba clock in devicetree\n");
+
+	/* 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, IMX_SPDIF_DMABUF_SIZE);
+	if (ret)
+		dev_err(&pdev->dev, "imx_pcm_dma_init failed: %d\n", ret);
+
+	return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_spdif_suspend(struct device *dev)
+{
+	struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
+
+	regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC,
+			&spdif_priv->regcache_srpc);
+
+	regcache_cache_only(spdif_priv->regmap, true);
+	regcache_mark_dirty(spdif_priv->regmap);
+
+	return 0;
+}
+
+static int fsl_spdif_resume(struct device *dev)
+{
+	struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
+
+	regcache_cache_only(spdif_priv->regmap, false);
+
+	regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SRPC,
+			SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK,
+			spdif_priv->regcache_srpc);
+
+	return regcache_sync(spdif_priv->regmap);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_spdif_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(fsl_spdif_suspend, fsl_spdif_resume)
+};
+
+static const struct of_device_id fsl_spdif_dt_ids[] = {
+	{ .compatible = "fsl,imx35-spdif", },
+	{ .compatible = "fsl,vf610-spdif", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids);
+
+static struct platform_driver fsl_spdif_driver = {
+	.driver = {
+		.name = "fsl-spdif-dai",
+		.of_match_table = fsl_spdif_dt_ids,
+		.pm = &fsl_spdif_pm,
+	},
+	.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 0000000..7666dab
--- /dev/null
+++ b/sound/soc/fsl/fsl_spdif.h
@@ -0,0 +1,196 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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.
+ */
+
+#ifndef _FSL_SPDIF_DAI_H
+#define _FSL_SPDIF_DAI_H
+
+/* S/PDIF Register Map */
+#define REG_SPDIF_SCR 			0x0	/* SPDIF Configuration Register */
+#define REG_SPDIF_SRCD		 	0x4	/* CDText Control Register */
+#define REG_SPDIF_SRPC			0x8	/* PhaseConfig Register */
+#define REG_SPDIF_SIE			0xc	/* InterruptEn Register */
+#define REG_SPDIF_SIS			0x10	/* InterruptStat Register */
+#define REG_SPDIF_SIC			0x10	/* InterruptClear Register */
+#define REG_SPDIF_SRL			0x14	/* SPDIFRxLeft Register */
+#define REG_SPDIF_SRR			0x18	/* SPDIFRxRight Register */
+#define REG_SPDIF_SRCSH			0x1c	/* SPDIFRxCChannel_h Register */
+#define REG_SPDIF_SRCSL			0x20	/* SPDIFRxCChannel_l Register */
+#define REG_SPDIF_SRU			0x24	/* UchannelRx Register */
+#define REG_SPDIF_SRQ			0x28	/* QchannelRx Register */
+#define REG_SPDIF_STL			0x2C	/* SPDIFTxLeft Register */
+#define REG_SPDIF_STR			0x30	/* SPDIFTxRight Register */
+#define REG_SPDIF_STCSCH		0x34	/* SPDIFTxCChannelCons_h Register */
+#define REG_SPDIF_STCSCL		0x38	/* SPDIFTxCChannelCons_l Register */
+#define REG_SPDIF_SRFM			0x44	/* FreqMeas Register */
+#define REG_SPDIF_STC			0x50	/* SPDIFTxClk Register */
+
+
+/* SPDIF Configuration register */
+#define SCR_RXFIFO_CTL_OFFSET		23
+#define SCR_RXFIFO_CTL_MASK		(1 << SCR_RXFIFO_CTL_OFFSET)
+#define SCR_RXFIFO_CTL_ZERO		(1 << SCR_RXFIFO_CTL_OFFSET)
+#define SCR_RXFIFO_OFF_OFFSET		22
+#define SCR_RXFIFO_OFF_MASK		(1 << SCR_RXFIFO_OFF_OFFSET)
+#define SCR_RXFIFO_OFF			(1 << SCR_RXFIFO_OFF_OFFSET)
+#define SCR_RXFIFO_RST_OFFSET		21
+#define SCR_RXFIFO_RST_MASK		(1 << SCR_RXFIFO_RST_OFFSET)
+#define SCR_RXFIFO_RST			(1 << SCR_RXFIFO_RST_OFFSET)
+#define SCR_RXFIFO_FSEL_OFFSET		19
+#define SCR_RXFIFO_FSEL_MASK		(0x3 << SCR_RXFIFO_FSEL_OFFSET)
+#define SCR_RXFIFO_FSEL_IF0		(0x0 << SCR_RXFIFO_FSEL_OFFSET)
+#define SCR_RXFIFO_FSEL_IF4		(0x1 << SCR_RXFIFO_FSEL_OFFSET)
+#define SCR_RXFIFO_FSEL_IF8		(0x2 << SCR_RXFIFO_FSEL_OFFSET)
+#define SCR_RXFIFO_FSEL_IF12		(0x3 << SCR_RXFIFO_FSEL_OFFSET)
+#define SCR_RXFIFO_AUTOSYNC_OFFSET	18
+#define SCR_RXFIFO_AUTOSYNC_MASK	(1 << SCR_RXFIFO_AUTOSYNC_OFFSET)
+#define SCR_RXFIFO_AUTOSYNC		(1 << SCR_RXFIFO_AUTOSYNC_OFFSET)
+#define SCR_TXFIFO_AUTOSYNC_OFFSET	17
+#define SCR_TXFIFO_AUTOSYNC_MASK	(1 << SCR_TXFIFO_AUTOSYNC_OFFSET)
+#define SCR_TXFIFO_AUTOSYNC		(1 << SCR_TXFIFO_AUTOSYNC_OFFSET)
+#define SCR_TXFIFO_FSEL_OFFSET		15
+#define SCR_TXFIFO_FSEL_MASK		(0x3 << SCR_TXFIFO_FSEL_OFFSET)
+#define SCR_TXFIFO_FSEL_IF0		(0x0 << SCR_TXFIFO_FSEL_OFFSET)
+#define SCR_TXFIFO_FSEL_IF4		(0x1 << SCR_TXFIFO_FSEL_OFFSET)
+#define SCR_TXFIFO_FSEL_IF8		(0x2 << SCR_TXFIFO_FSEL_OFFSET)
+#define SCR_TXFIFO_FSEL_IF12		(0x3 << SCR_TXFIFO_FSEL_OFFSET)
+#define SCR_LOW_POWER			(1 << 13)
+#define SCR_SOFT_RESET			(1 << 12)
+#define SCR_TXFIFO_CTRL_OFFSET		10
+#define SCR_TXFIFO_CTRL_MASK		(0x3 << SCR_TXFIFO_CTRL_OFFSET)
+#define SCR_TXFIFO_CTRL_ZERO		(0x0 << SCR_TXFIFO_CTRL_OFFSET)
+#define SCR_TXFIFO_CTRL_NORMAL		(0x1 << SCR_TXFIFO_CTRL_OFFSET)
+#define SCR_TXFIFO_CTRL_ONESAMPLE	(0x2 << SCR_TXFIFO_CTRL_OFFSET)
+#define SCR_DMA_RX_EN_OFFSET		9
+#define SCR_DMA_RX_EN_MASK		(1 << SCR_DMA_RX_EN_OFFSET)
+#define SCR_DMA_RX_EN			(1 << SCR_DMA_RX_EN_OFFSET)
+#define SCR_DMA_TX_EN_OFFSET		8
+#define SCR_DMA_TX_EN_MASK		(1 << SCR_DMA_TX_EN_OFFSET)
+#define SCR_DMA_TX_EN			(1 << SCR_DMA_TX_EN_OFFSET)
+#define SCR_VAL_OFFSET			5
+#define SCR_VAL_MASK			(1 << SCR_VAL_OFFSET)
+#define SCR_VAL_CLEAR			(1 << SCR_VAL_OFFSET)
+#define SCR_TXSEL_OFFSET		2
+#define SCR_TXSEL_MASK			(0x7 << SCR_TXSEL_OFFSET)
+#define SCR_TXSEL_OFF			(0 << SCR_TXSEL_OFFSET)
+#define SCR_TXSEL_RX			(1 << SCR_TXSEL_OFFSET)
+#define SCR_TXSEL_NORMAL		(0x5 << SCR_TXSEL_OFFSET)
+#define SCR_USRC_SEL_OFFSET		0x0
+#define SCR_USRC_SEL_MASK		(0x3 << SCR_USRC_SEL_OFFSET)
+#define SCR_USRC_SEL_NONE		(0x0 << SCR_USRC_SEL_OFFSET)
+#define SCR_USRC_SEL_RECV		(0x1 << SCR_USRC_SEL_OFFSET)
+#define SCR_USRC_SEL_CHIP		(0x3 << SCR_USRC_SEL_OFFSET)
+
+#define SCR_DMA_xX_EN(tx)		(tx ? SCR_DMA_TX_EN : SCR_DMA_RX_EN)
+
+/* SPDIF CDText control */
+#define SRCD_CD_USER_OFFSET		1
+#define SRCD_CD_USER			(1 << SRCD_CD_USER_OFFSET)
+
+/* SPDIF Phase Configuration register */
+#define SRPC_DPLL_LOCKED		(1 << 6)
+#define SRPC_CLKSRC_SEL_OFFSET		7
+#define SRPC_CLKSRC_SEL_MASK		(0xf << SRPC_CLKSRC_SEL_OFFSET)
+#define SRPC_CLKSRC_SEL_SET(x)		((x << SRPC_CLKSRC_SEL_OFFSET) & SRPC_CLKSRC_SEL_MASK)
+#define SRPC_CLKSRC_SEL_LOCKED_OFFSET1	5
+#define SRPC_CLKSRC_SEL_LOCKED_OFFSET2	2
+#define SRPC_GAINSEL_OFFSET		3
+#define SRPC_GAINSEL_MASK		(0x7 << SRPC_GAINSEL_OFFSET)
+#define SRPC_GAINSEL_SET(x)		((x << SRPC_GAINSEL_OFFSET) & SRPC_GAINSEL_MASK)
+
+#define SRPC_CLKSRC_MAX			16
+
+enum spdif_gainsel {
+	GAINSEL_MULTI_24 = 0,
+	GAINSEL_MULTI_16,
+	GAINSEL_MULTI_12,
+	GAINSEL_MULTI_8,
+	GAINSEL_MULTI_6,
+	GAINSEL_MULTI_4,
+	GAINSEL_MULTI_3,
+};
+#define GAINSEL_MULTI_MAX		(GAINSEL_MULTI_3 + 1)
+#define SPDIF_DEFAULT_GAINSEL		GAINSEL_MULTI_8
+
+/* SPDIF interrupt mask define */
+#define INT_DPLL_LOCKED			(1 << 20)
+#define INT_TXFIFO_UNOV			(1 << 19)
+#define INT_TXFIFO_RESYNC		(1 << 18)
+#define INT_CNEW			(1 << 17)
+#define INT_VAL_NOGOOD			(1 << 16)
+#define INT_SYM_ERR			(1 << 15)
+#define INT_BIT_ERR			(1 << 14)
+#define INT_URX_FUL			(1 << 10)
+#define INT_URX_OV			(1 << 9)
+#define INT_QRX_FUL			(1 << 8)
+#define INT_QRX_OV			(1 << 7)
+#define INT_UQ_SYNC			(1 << 6)
+#define INT_UQ_ERR			(1 << 5)
+#define INT_RXFIFO_UNOV			(1 << 4)
+#define INT_RXFIFO_RESYNC		(1 << 3)
+#define INT_LOSS_LOCK			(1 << 2)
+#define INT_TX_EM			(1 << 1)
+#define INT_RXFIFO_FUL			(1 << 0)
+
+/* SPDIF Clock register */
+#define STC_SYSCLK_DF_OFFSET		11
+#define STC_SYSCLK_DF_MASK		(0x1ff << STC_SYSCLK_DF_OFFSET)
+#define STC_SYSCLK_DF(x)		((((x) - 1) << STC_SYSCLK_DF_OFFSET) & STC_SYSCLK_DF_MASK)
+#define STC_TXCLK_SRC_OFFSET		8
+#define STC_TXCLK_SRC_MASK		(0x7 << STC_TXCLK_SRC_OFFSET)
+#define STC_TXCLK_SRC_SET(x)		((x << STC_TXCLK_SRC_OFFSET) & STC_TXCLK_SRC_MASK)
+#define STC_TXCLK_ALL_EN_OFFSET		7
+#define STC_TXCLK_ALL_EN_MASK		(1 << STC_TXCLK_ALL_EN_OFFSET)
+#define STC_TXCLK_ALL_EN		(1 << STC_TXCLK_ALL_EN_OFFSET)
+#define STC_TXCLK_DF_OFFSET		0
+#define STC_TXCLK_DF_MASK		(0x7ff << STC_TXCLK_DF_OFFSET)
+#define STC_TXCLK_DF(x)		((((x) - 1) << STC_TXCLK_DF_OFFSET) & STC_TXCLK_DF_MASK)
+#define STC_TXCLK_SRC_MAX		8
+
+#define STC_TXCLK_SPDIF_ROOT		1
+
+/* SPDIF tx rate */
+enum spdif_txrate {
+	SPDIF_TXRATE_32000 = 0,
+	SPDIF_TXRATE_44100,
+	SPDIF_TXRATE_48000,
+	SPDIF_TXRATE_96000,
+	SPDIF_TXRATE_192000,
+};
+#define SPDIF_TXRATE_MAX		(SPDIF_TXRATE_192000 + 1)
+
+
+#define SPDIF_CSTATUS_BYTE		6
+#define SPDIF_UBITS_SIZE		96
+#define SPDIF_QSUB_SIZE			(SPDIF_UBITS_SIZE / 8)
+
+
+#define FSL_SPDIF_RATES_PLAYBACK	(SNDRV_PCM_RATE_32000 |	\
+					 SNDRV_PCM_RATE_44100 |	\
+					 SNDRV_PCM_RATE_48000 |	\
+					 SNDRV_PCM_RATE_96000 |	\
+					 SNDRV_PCM_RATE_192000)
+
+#define FSL_SPDIF_RATES_CAPTURE		(SNDRV_PCM_RATE_16000 | \
+					 SNDRV_PCM_RATE_32000 |	\
+					 SNDRV_PCM_RATE_44100 | \
+					 SNDRV_PCM_RATE_48000 |	\
+					 SNDRV_PCM_RATE_64000 | \
+					 SNDRV_PCM_RATE_96000)
+
+#define FSL_SPDIF_FORMATS_PLAYBACK	(SNDRV_PCM_FMTBIT_S16_LE | \
+					 SNDRV_PCM_FMTBIT_S20_3LE | \
+					 SNDRV_PCM_FMTBIT_S24_LE)
+
+#define FSL_SPDIF_FORMATS_CAPTURE	(SNDRV_PCM_FMTBIT_S24_LE)
+
+#endif /* _FSL_SPDIF_DAI_H */
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
new file mode 100644
index 0000000..0a64822
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -0,0 +1,1703 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
+//
+// Author: Timur Tabi <timur@freescale.com>
+//
+// Copyright 2007-2010 Freescale Semiconductor, Inc.
+//
+// Some notes why imx-pcm-fiq is used instead of DMA on some boards:
+//
+// The i.MX SSI core has some nasty limitations in AC97 mode. While most
+// sane processor vendors have a FIFO per AC97 slot, the i.MX has only
+// one FIFO which combines all valid receive slots. We cannot even select
+// which slots we want to receive. The WM9712 with which this driver
+// was developed with always sends GPIO status data in slot 12 which
+// we receive in our (PCM-) data stream. The only chance we have is to
+// manually skip this data in the FIQ handler. With sampling rates different
+// from 48000Hz not every frame has valid receive data, so the ratio
+// between pcm data and GPIO status data changes. Our FIQ handler is not
+// able to handle this, hence this driver only works with 48000Hz sampling
+// rate.
+// Reading and writing AC97 registers is another challenge. The core
+// provides us status bits when the read register is updated with *another*
+// value. When we read the same register two times (and the register still
+// contains the same value) these status bits are not set. We work
+// around this by not polling these bits but only wait a fixed delay.
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "fsl_ssi.h"
+#include "imx-pcm.h"
+
+/* Define RX and TX to index ssi->regvals array; Can be 0 or 1 only */
+#define RX 0
+#define TX 1
+
+/**
+ * FSLSSI_I2S_FORMATS: audio formats supported by the SSI
+ *
+ * The SSI has a limitation in that the samples must be in the same byte
+ * order as the host CPU.  This is because when multiple bytes are written
+ * to the STX register, the bytes and bits must be written in the same
+ * order.  The STX is a shift register, so all the bits need to be aligned
+ * (bit-endianness must match byte-endianness).  Processors typically write
+ * the bits within a byte in the same order that the bytes of a word are
+ * written in.  So if the host CPU is big-endian, then only big-endian
+ * samples will be written to STX properly.
+ */
+#ifdef __BIG_ENDIAN
+#define FSLSSI_I2S_FORMATS \
+	(SNDRV_PCM_FMTBIT_S8 | \
+	 SNDRV_PCM_FMTBIT_S16_BE | \
+	 SNDRV_PCM_FMTBIT_S18_3BE | \
+	 SNDRV_PCM_FMTBIT_S20_3BE | \
+	 SNDRV_PCM_FMTBIT_S24_3BE | \
+	 SNDRV_PCM_FMTBIT_S24_BE)
+#else
+#define FSLSSI_I2S_FORMATS \
+	(SNDRV_PCM_FMTBIT_S8 | \
+	 SNDRV_PCM_FMTBIT_S16_LE | \
+	 SNDRV_PCM_FMTBIT_S18_3LE | \
+	 SNDRV_PCM_FMTBIT_S20_3LE | \
+	 SNDRV_PCM_FMTBIT_S24_3LE | \
+	 SNDRV_PCM_FMTBIT_S24_LE)
+#endif
+
+/*
+ * In AC97 mode, TXDIR bit is forced to 0 and TFDIR bit is forced to 1:
+ *  - SSI inputs external bit clock and outputs frame sync clock -- CBM_CFS
+ *  - Also have NB_NF to mark these two clocks will not be inverted
+ */
+#define FSLSSI_AC97_DAIFMT \
+	(SND_SOC_DAIFMT_AC97 | \
+	 SND_SOC_DAIFMT_CBM_CFS | \
+	 SND_SOC_DAIFMT_NB_NF)
+
+#define FSLSSI_SIER_DBG_RX_FLAGS \
+	(SSI_SIER_RFF0_EN | \
+	 SSI_SIER_RLS_EN | \
+	 SSI_SIER_RFS_EN | \
+	 SSI_SIER_ROE0_EN | \
+	 SSI_SIER_RFRC_EN)
+#define FSLSSI_SIER_DBG_TX_FLAGS \
+	(SSI_SIER_TFE0_EN | \
+	 SSI_SIER_TLS_EN | \
+	 SSI_SIER_TFS_EN | \
+	 SSI_SIER_TUE0_EN | \
+	 SSI_SIER_TFRC_EN)
+
+enum fsl_ssi_type {
+	FSL_SSI_MCP8610,
+	FSL_SSI_MX21,
+	FSL_SSI_MX35,
+	FSL_SSI_MX51,
+};
+
+struct fsl_ssi_regvals {
+	u32 sier;
+	u32 srcr;
+	u32 stcr;
+	u32 scr;
+};
+
+static bool fsl_ssi_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_SSI_SACCEN:
+	case REG_SSI_SACCDIS:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static bool fsl_ssi_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_SSI_STX0:
+	case REG_SSI_STX1:
+	case REG_SSI_SRX0:
+	case REG_SSI_SRX1:
+	case REG_SSI_SISR:
+	case REG_SSI_SFCSR:
+	case REG_SSI_SACNT:
+	case REG_SSI_SACADD:
+	case REG_SSI_SACDAT:
+	case REG_SSI_SATAG:
+	case REG_SSI_SACCST:
+	case REG_SSI_SOR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool fsl_ssi_precious_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_SSI_SRX0:
+	case REG_SSI_SRX1:
+	case REG_SSI_SISR:
+	case REG_SSI_SACADD:
+	case REG_SSI_SACDAT:
+	case REG_SSI_SATAG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool fsl_ssi_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_SSI_SRX0:
+	case REG_SSI_SRX1:
+	case REG_SSI_SACCST:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config fsl_ssi_regconfig = {
+	.max_register = REG_SSI_SACCDIS,
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.val_format_endian = REGMAP_ENDIAN_NATIVE,
+	.num_reg_defaults_raw = REG_SSI_SACCDIS / sizeof(uint32_t) + 1,
+	.readable_reg = fsl_ssi_readable_reg,
+	.volatile_reg = fsl_ssi_volatile_reg,
+	.precious_reg = fsl_ssi_precious_reg,
+	.writeable_reg = fsl_ssi_writeable_reg,
+	.cache_type = REGCACHE_FLAT,
+};
+
+struct fsl_ssi_soc_data {
+	bool imx;
+	bool imx21regs; /* imx21-class SSI - no SACC{ST,EN,DIS} regs */
+	bool offline_config;
+	u32 sisr_write_mask;
+};
+
+/**
+ * fsl_ssi: per-SSI private data
+ *
+ * @regs: Pointer to the regmap registers
+ * @irq: IRQ of this SSI
+ * @cpu_dai_drv: CPU DAI driver for this device
+ *
+ * @dai_fmt: DAI configuration this device is currently used with
+ * @streams: Mask of current active streams: BIT(TX) and BIT(RX)
+ * @i2s_net: I2S and Network mode configurations of SCR register
+ *           (this is the initial settings based on the DAI format)
+ * @synchronous: Use synchronous mode - both of TX and RX use STCK and SFCK
+ * @use_dma: DMA is used or FIQ with stream filter
+ * @use_dual_fifo: DMA with support for dual FIFO mode
+ * @has_ipg_clk_name: If "ipg" is in the clock name list of device tree
+ * @fifo_depth: Depth of the SSI FIFOs
+ * @slot_width: Width of each DAI slot
+ * @slots: Number of slots
+ * @regvals: Specific RX/TX register settings
+ *
+ * @clk: Clock source to access register
+ * @baudclk: Clock source to generate bit and frame-sync clocks
+ * @baudclk_streams: Active streams that are using baudclk
+ *
+ * @regcache_sfcsr: Cache sfcsr register value during suspend and resume
+ * @regcache_sacnt: Cache sacnt register value during suspend and resume
+ *
+ * @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
+ *
+ * @card_pdev: Platform_device pointer to register a sound card for PowerPC or
+ *             to register a CODEC platform device for AC97
+ * @card_name: Platform_device name to register a sound card for PowerPC or
+ *             to register a CODEC platform device for AC97
+ * @card_idx: The index of SSI to register a sound card for PowerPC or
+ *            to register a CODEC platform device for AC97
+ *
+ * @dbg_stats: Debugging statistics
+ *
+ * @soc: SoC specific data
+ * @dev: Pointer to &pdev->dev
+ *
+ * @fifo_watermark: The FIFO watermark setting. Notifies DMA when there are
+ *                  @fifo_watermark or fewer words in TX fifo or
+ *                  @fifo_watermark or more empty words in RX fifo.
+ * @dma_maxburst: Max number of words to transfer in one go. So far,
+ *                this is always the same as fifo_watermark.
+ *
+ * @ac97_reg_lock: Mutex lock to serialize AC97 register access operations
+ */
+struct fsl_ssi {
+	struct regmap *regs;
+	int irq;
+	struct snd_soc_dai_driver cpu_dai_drv;
+
+	unsigned int dai_fmt;
+	u8 streams;
+	u8 i2s_net;
+	bool synchronous;
+	bool use_dma;
+	bool use_dual_fifo;
+	bool has_ipg_clk_name;
+	unsigned int fifo_depth;
+	unsigned int slot_width;
+	unsigned int slots;
+	struct fsl_ssi_regvals regvals[2];
+
+	struct clk *clk;
+	struct clk *baudclk;
+	unsigned int baudclk_streams;
+
+	u32 regcache_sfcsr;
+	u32 regcache_sacnt;
+
+	struct snd_dmaengine_dai_dma_data dma_params_tx;
+	struct snd_dmaengine_dai_dma_data dma_params_rx;
+	dma_addr_t ssi_phys;
+
+	struct imx_pcm_fiq_params fiq_params;
+
+	struct platform_device *card_pdev;
+	char card_name[32];
+	u32 card_idx;
+
+	struct fsl_ssi_dbg dbg_stats;
+
+	const struct fsl_ssi_soc_data *soc;
+	struct device *dev;
+
+	u32 fifo_watermark;
+	u32 dma_maxburst;
+
+	struct mutex ac97_reg_lock;
+};
+
+/*
+ * SoC specific data
+ *
+ * Notes:
+ * 1) SSI in earlier SoCS has critical bits in control registers that
+ *    cannot be changed after SSI starts running -- a software reset
+ *    (set SSIEN to 0) is required to change their values. So adding
+ *    an offline_config flag for these SoCs.
+ * 2) SDMA is available since imx35. However, imx35 does not support
+ *    DMA bits changing when SSI is running, so set offline_config.
+ * 3) imx51 and later versions support register configurations when
+ *    SSI is running (SSIEN); For these versions, DMA needs to be
+ *    configured before SSI sends DMA request to avoid an undefined
+ *    DMA request on the SDMA side.
+ */
+
+static struct fsl_ssi_soc_data fsl_ssi_mpc8610 = {
+	.imx = false,
+	.offline_config = true,
+	.sisr_write_mask = SSI_SISR_RFRC | SSI_SISR_TFRC |
+			   SSI_SISR_ROE0 | SSI_SISR_ROE1 |
+			   SSI_SISR_TUE0 | SSI_SISR_TUE1,
+};
+
+static struct fsl_ssi_soc_data fsl_ssi_imx21 = {
+	.imx = true,
+	.imx21regs = 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 = SSI_SISR_RFRC | SSI_SISR_TFRC |
+			   SSI_SISR_ROE0 | SSI_SISR_ROE1 |
+			   SSI_SISR_TUE0 | SSI_SISR_TUE1,
+};
+
+static struct fsl_ssi_soc_data fsl_ssi_imx51 = {
+	.imx = true,
+	.offline_config = false,
+	.sisr_write_mask = SSI_SISR_ROE0 | SSI_SISR_ROE1 |
+			   SSI_SISR_TUE0 | 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 *ssi)
+{
+	return (ssi->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) ==
+		SND_SOC_DAIFMT_AC97;
+}
+
+static bool fsl_ssi_is_i2s_master(struct fsl_ssi *ssi)
+{
+	return (ssi->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) ==
+		SND_SOC_DAIFMT_CBS_CFS;
+}
+
+static bool fsl_ssi_is_i2s_cbm_cfs(struct fsl_ssi *ssi)
+{
+	return (ssi->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) ==
+		SND_SOC_DAIFMT_CBM_CFS;
+}
+
+/**
+ * Interrupt handler to gather states
+ */
+static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
+{
+	struct fsl_ssi *ssi = dev_id;
+	struct regmap *regs = ssi->regs;
+	u32 sisr, sisr2;
+
+	regmap_read(regs, REG_SSI_SISR, &sisr);
+
+	sisr2 = sisr & ssi->soc->sisr_write_mask;
+	/* Clear the bits that we set */
+	if (sisr2)
+		regmap_write(regs, REG_SSI_SISR, sisr2);
+
+	fsl_ssi_dbg_isr(&ssi->dbg_stats, sisr);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * Set SCR, SIER, STCR and SRCR registers with cached values in regvals
+ *
+ * Notes:
+ * 1) For offline_config SoCs, enable all necessary bits of both streams
+ *    when 1st stream starts, even if the opposite stream will not start
+ * 2) It also clears FIFO before setting regvals; SOR is safe to set online
+ */
+static void fsl_ssi_config_enable(struct fsl_ssi *ssi, bool tx)
+{
+	struct fsl_ssi_regvals *vals = ssi->regvals;
+	int dir = tx ? TX : RX;
+	u32 sier, srcr, stcr;
+
+	/* Clear dirty data in the FIFO; It also prevents channel slipping */
+	regmap_update_bits(ssi->regs, REG_SSI_SOR,
+			   SSI_SOR_xX_CLR(tx), SSI_SOR_xX_CLR(tx));
+
+	/*
+	 * On offline_config SoCs, SxCR and SIER are already configured when
+	 * the previous stream started. So skip all SxCR and SIER settings
+	 * to prevent online reconfigurations, then jump to set SCR directly
+	 */
+	if (ssi->soc->offline_config && ssi->streams)
+		goto enable_scr;
+
+	if (ssi->soc->offline_config) {
+		/*
+		 * Online reconfiguration not supported, so enable all bits for
+		 * both streams at once to avoid necessity of reconfigurations
+		 */
+		srcr = vals[RX].srcr | vals[TX].srcr;
+		stcr = vals[RX].stcr | vals[TX].stcr;
+		sier = vals[RX].sier | vals[TX].sier;
+	} else {
+		/* Otherwise, only set bits for the current stream */
+		srcr = vals[dir].srcr;
+		stcr = vals[dir].stcr;
+		sier = vals[dir].sier;
+	}
+
+	/* Configure SRCR, STCR and SIER at once */
+	regmap_update_bits(ssi->regs, REG_SSI_SRCR, srcr, srcr);
+	regmap_update_bits(ssi->regs, REG_SSI_STCR, stcr, stcr);
+	regmap_update_bits(ssi->regs, REG_SSI_SIER, sier, sier);
+
+enable_scr:
+	/*
+	 * Start DMA before setting TE to avoid FIFO underrun
+	 * which may cause a channel slip or a channel swap
+	 *
+	 * TODO: FIQ cases might also need this upon testing
+	 */
+	if (ssi->use_dma && tx) {
+		int try = 100;
+		u32 sfcsr;
+
+		/* Enable SSI first to send TX DMA request */
+		regmap_update_bits(ssi->regs, REG_SSI_SCR,
+				   SSI_SCR_SSIEN, SSI_SCR_SSIEN);
+
+		/* Busy wait until TX FIFO not empty -- DMA working */
+		do {
+			regmap_read(ssi->regs, REG_SSI_SFCSR, &sfcsr);
+			if (SSI_SFCSR_TFCNT0(sfcsr))
+				break;
+		} while (--try);
+
+		/* FIFO still empty -- something might be wrong */
+		if (!SSI_SFCSR_TFCNT0(sfcsr))
+			dev_warn(ssi->dev, "Timeout waiting TX FIFO filling\n");
+	}
+	/* Enable all remaining bits in SCR */
+	regmap_update_bits(ssi->regs, REG_SSI_SCR,
+			   vals[dir].scr, vals[dir].scr);
+
+	/* Log the enabled stream to the mask */
+	ssi->streams |= BIT(dir);
+}
+
+/**
+ * Exclude bits that are used by the opposite stream
+ *
+ * When both streams are active, disabling some bits for the current stream
+ * might break the other stream if these bits are used by it.
+ *
+ * @vals : regvals of the current stream
+ * @avals: regvals of the opposite stream
+ * @aactive: active state of the opposite stream
+ *
+ *  1) XOR vals and avals to get the differences if the other stream is active;
+ *     Otherwise, return current vals if the other stream is not active
+ *  2) AND the result of 1) with the current vals
+ */
+#define _ssi_xor_shared_bits(vals, avals, aactive) \
+	((vals) ^ ((avals) * (aactive)))
+
+#define ssi_excl_shared_bits(vals, avals, aactive) \
+	((vals) & _ssi_xor_shared_bits(vals, avals, aactive))
+
+/**
+ * Unset SCR, SIER, STCR and SRCR registers with cached values in regvals
+ *
+ * Notes:
+ * 1) For offline_config SoCs, to avoid online reconfigurations, disable all
+ *    bits of both streams at once when the last stream is abort to end
+ * 2) It also clears FIFO after unsetting regvals; SOR is safe to set online
+ */
+static void fsl_ssi_config_disable(struct fsl_ssi *ssi, bool tx)
+{
+	struct fsl_ssi_regvals *vals, *avals;
+	u32 sier, srcr, stcr, scr;
+	int adir = tx ? RX : TX;
+	int dir = tx ? TX : RX;
+	bool aactive;
+
+	/* Check if the opposite stream is active */
+	aactive = ssi->streams & BIT(adir);
+
+	vals = &ssi->regvals[dir];
+
+	/* Get regvals of the opposite stream to keep opposite stream safe */
+	avals = &ssi->regvals[adir];
+
+	/*
+	 * To keep the other stream safe, exclude shared bits between
+	 * both streams, and get safe bits to disable current stream
+	 */
+	scr = ssi_excl_shared_bits(vals->scr, avals->scr, aactive);
+
+	/* Disable safe bits of SCR register for the current stream */
+	regmap_update_bits(ssi->regs, REG_SSI_SCR, scr, 0);
+
+	/* Log the disabled stream to the mask */
+	ssi->streams &= ~BIT(dir);
+
+	/*
+	 * On offline_config SoCs, if the other stream is active, skip
+	 * SxCR and SIER settings to prevent online reconfigurations
+	 */
+	if (ssi->soc->offline_config && aactive)
+		goto fifo_clear;
+
+	if (ssi->soc->offline_config) {
+		/* Now there is only current stream active, disable all bits */
+		srcr = vals->srcr | avals->srcr;
+		stcr = vals->stcr | avals->stcr;
+		sier = vals->sier | avals->sier;
+	} else {
+		/*
+		 * To keep the other stream safe, exclude shared bits between
+		 * both streams, and get safe bits to disable current stream
+		 */
+		sier = ssi_excl_shared_bits(vals->sier, avals->sier, aactive);
+		srcr = ssi_excl_shared_bits(vals->srcr, avals->srcr, aactive);
+		stcr = ssi_excl_shared_bits(vals->stcr, avals->stcr, aactive);
+	}
+
+	/* Clear configurations of SRCR, STCR and SIER at once */
+	regmap_update_bits(ssi->regs, REG_SSI_SRCR, srcr, 0);
+	regmap_update_bits(ssi->regs, REG_SSI_STCR, stcr, 0);
+	regmap_update_bits(ssi->regs, REG_SSI_SIER, sier, 0);
+
+fifo_clear:
+	/* Clear remaining data in the FIFO */
+	regmap_update_bits(ssi->regs, REG_SSI_SOR,
+			   SSI_SOR_xX_CLR(tx), SSI_SOR_xX_CLR(tx));
+}
+
+static void fsl_ssi_tx_ac97_saccst_setup(struct fsl_ssi *ssi)
+{
+	struct regmap *regs = ssi->regs;
+
+	/* no SACC{ST,EN,DIS} regs on imx21-class SSI */
+	if (!ssi->soc->imx21regs) {
+		/* Disable all channel slots */
+		regmap_write(regs, REG_SSI_SACCDIS, 0xff);
+		/* Enable slots 3 & 4 -- PCM Playback Left & Right channels */
+		regmap_write(regs, REG_SSI_SACCEN, 0x300);
+	}
+}
+
+/**
+ * Cache critical bits of SIER, SRCR, STCR and SCR to later set them safely
+ */
+static void fsl_ssi_setup_regvals(struct fsl_ssi *ssi)
+{
+	struct fsl_ssi_regvals *vals = ssi->regvals;
+
+	vals[RX].sier = SSI_SIER_RFF0_EN | FSLSSI_SIER_DBG_RX_FLAGS;
+	vals[RX].srcr = SSI_SRCR_RFEN0;
+	vals[RX].scr = SSI_SCR_SSIEN | SSI_SCR_RE;
+	vals[TX].sier = SSI_SIER_TFE0_EN | FSLSSI_SIER_DBG_TX_FLAGS;
+	vals[TX].stcr = SSI_STCR_TFEN0;
+	vals[TX].scr = SSI_SCR_SSIEN | SSI_SCR_TE;
+
+	/* AC97 has already enabled SSIEN, RE and TE, so ignore them */
+	if (fsl_ssi_is_ac97(ssi))
+		vals[RX].scr = vals[TX].scr = 0;
+
+	if (ssi->use_dual_fifo) {
+		vals[RX].srcr |= SSI_SRCR_RFEN1;
+		vals[TX].stcr |= SSI_STCR_TFEN1;
+	}
+
+	if (ssi->use_dma) {
+		vals[RX].sier |= SSI_SIER_RDMAE;
+		vals[TX].sier |= SSI_SIER_TDMAE;
+	} else {
+		vals[RX].sier |= SSI_SIER_RIE;
+		vals[TX].sier |= SSI_SIER_TIE;
+	}
+}
+
+static void fsl_ssi_setup_ac97(struct fsl_ssi *ssi)
+{
+	struct regmap *regs = ssi->regs;
+
+	/* Setup the clock control register */
+	regmap_write(regs, REG_SSI_STCCR, SSI_SxCCR_WL(17) | SSI_SxCCR_DC(13));
+	regmap_write(regs, REG_SSI_SRCCR, SSI_SxCCR_WL(17) | SSI_SxCCR_DC(13));
+
+	/* Enable AC97 mode and startup the SSI */
+	regmap_write(regs, REG_SSI_SACNT, SSI_SACNT_AC97EN | SSI_SACNT_FV);
+
+	/* AC97 has to communicate with codec before starting a stream */
+	regmap_update_bits(regs, REG_SSI_SCR,
+			   SSI_SCR_SSIEN | SSI_SCR_TE | SSI_SCR_RE,
+			   SSI_SCR_SSIEN | SSI_SCR_TE | SSI_SCR_RE);
+
+	regmap_write(regs, REG_SSI_SOR, SSI_SOR_WAIT(3));
+}
+
+static int fsl_ssi_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	int ret;
+
+	ret = clk_prepare_enable(ssi->clk);
+	if (ret)
+		return ret;
+
+	/*
+	 * When using dual fifo mode, it is safer to ensure an even period
+	 * size. If appearing to an odd number while DMA always starts its
+	 * task from fifo0, fifo1 would be neglected at the end of each
+	 * period. But SSI would still access fifo1 with an invalid data.
+	 */
+	if (ssi->use_dual_fifo)
+		snd_pcm_hw_constraint_step(substream->runtime, 0,
+					   SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
+
+	return 0;
+}
+
+static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	clk_disable_unprepare(ssi->clk);
+}
+
+/**
+ * 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 * slots * slot_width
+ *       (In 2-channel I2S Master mode, slot_width is fixed 32)
+ */
+static int fsl_ssi_set_bclk(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai,
+			    struct snd_pcm_hw_params *hw_params)
+{
+	bool tx2, tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai);
+	struct regmap *regs = ssi->regs;
+	u32 pm = 999, div2, psr, stccr, mask, afreq, factor, i;
+	unsigned long clkrate, baudrate, tmprate;
+	unsigned int slots = params_channels(hw_params);
+	unsigned int slot_width = 32;
+	u64 sub, savesub = 100000;
+	unsigned int freq;
+	bool baudclk_is_used;
+	int ret;
+
+	/* Override slots and slot_width if being specifically set... */
+	if (ssi->slots)
+		slots = ssi->slots;
+	/* ...but keep 32 bits if slots is 2 -- I2S Master mode */
+	if (ssi->slot_width && slots != 2)
+		slot_width = ssi->slot_width;
+
+	/* Generate bit clock based on the slot number and slot width */
+	freq = slots * slot_width * params_rate(hw_params);
+
+	/* Don't apply it to any non-baudclk circumstance */
+	if (IS_ERR(ssi->baudclk))
+		return -EINVAL;
+
+	/*
+	 * Hardware limitation: The bclk rate must be
+	 * never greater than 1/5 IPG clock rate
+	 */
+	if (freq * 5 > clk_get_rate(ssi->clk)) {
+		dev_err(dai->dev, "bitclk > ipgclk / 5\n");
+		return -EINVAL;
+	}
+
+	baudclk_is_used = ssi->baudclk_streams & ~(BIT(substream->stream));
+
+	/* It should be already enough to divide clock by setting pm alone */
+	psr = 0;
+	div2 = 0;
+
+	factor = (div2 + 1) * (7 * psr + 1) * 2;
+
+	for (i = 0; i < 255; i++) {
+		tmprate = freq * factor * (i + 1);
+
+		if (baudclk_is_used)
+			clkrate = clk_get_rate(ssi->baudclk);
+		else
+			clkrate = clk_round_rate(ssi->baudclk, tmprate);
+
+		clkrate /= factor;
+		afreq = clkrate / (i + 1);
+
+		if (freq == afreq)
+			sub = 0;
+		else if (freq / afreq == 1)
+			sub = freq - afreq;
+		else if (afreq / freq == 1)
+			sub = afreq - freq;
+		else
+			continue;
+
+		/* Calculate the fraction */
+		sub *= 100000;
+		do_div(sub, freq);
+
+		if (sub < savesub && !(i == 0 && psr == 0 && div2 == 0)) {
+			baudrate = tmprate;
+			savesub = sub;
+			pm = i;
+		}
+
+		/* We are lucky */
+		if (savesub == 0)
+			break;
+	}
+
+	/* No proper pm found if it is still remaining the initial value */
+	if (pm == 999) {
+		dev_err(dai->dev, "failed to handle the required sysclk\n");
+		return -EINVAL;
+	}
+
+	stccr = SSI_SxCCR_PM(pm + 1) | (div2 ? SSI_SxCCR_DIV2 : 0) |
+		(psr ? SSI_SxCCR_PSR : 0);
+	mask = SSI_SxCCR_PM_MASK | SSI_SxCCR_DIV2 | SSI_SxCCR_PSR;
+
+	/* STCCR is used for RX in synchronous mode */
+	tx2 = tx || ssi->synchronous;
+	regmap_update_bits(regs, REG_SSI_SxCCR(tx2), mask, stccr);
+
+	if (!baudclk_is_used) {
+		ret = clk_set_rate(ssi->baudclk, baudrate);
+		if (ret) {
+			dev_err(dai->dev, "failed to set baudclk rate\n");
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * Configure SSI based on PCM hardware parameters
+ *
+ * Notes:
+ * 1) SxCCR.WL bits are critical bits that require SSI to be temporarily
+ *    disabled on offline_config SoCs. Even for online configurable SoCs
+ *    running in synchronous mode (both TX and RX use STCCR), it is not
+ *    safe to re-configure them when both two streams start running.
+ * 2) SxCCR.PM, SxCCR.DIV2 and SxCCR.PSR bits will be configured in the
+ *    fsl_ssi_set_bclk() if SSI is the DAI clock master.
+ */
+static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *hw_params,
+			     struct snd_soc_dai *dai)
+{
+	bool tx2, tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai);
+	struct regmap *regs = ssi->regs;
+	unsigned int channels = params_channels(hw_params);
+	unsigned int sample_size = params_width(hw_params);
+	u32 wl = SSI_SxCCR_WL(sample_size);
+	int ret;
+
+	/*
+	 * SSI is properly configured if it is enabled and running in
+	 * the synchronous mode; Note that AC97 mode is an exception
+	 * that should set separate configurations for STCCR and SRCCR
+	 * despite running in the synchronous mode.
+	 */
+	if (ssi->streams && ssi->synchronous)
+		return 0;
+
+	if (fsl_ssi_is_i2s_master(ssi)) {
+		ret = fsl_ssi_set_bclk(substream, dai, hw_params);
+		if (ret)
+			return ret;
+
+		/* Do not enable the clock if it is already enabled */
+		if (!(ssi->baudclk_streams & BIT(substream->stream))) {
+			ret = clk_prepare_enable(ssi->baudclk);
+			if (ret)
+				return ret;
+
+			ssi->baudclk_streams |= BIT(substream->stream);
+		}
+	}
+
+	if (!fsl_ssi_is_ac97(ssi)) {
+		/*
+		 * Keep the ssi->i2s_net intact while having a local variable
+		 * to override settings for special use cases. Otherwise, the
+		 * ssi->i2s_net will lose the settings for regular use cases.
+		 */
+		u8 i2s_net = ssi->i2s_net;
+
+		/* Normal + Network mode to send 16-bit data in 32-bit frames */
+		if (fsl_ssi_is_i2s_cbm_cfs(ssi) && sample_size == 16)
+			i2s_net = SSI_SCR_I2S_MODE_NORMAL | SSI_SCR_NET;
+
+		/* Use Normal mode to send mono data at 1st slot of 2 slots */
+		if (channels == 1)
+			i2s_net = SSI_SCR_I2S_MODE_NORMAL;
+
+		regmap_update_bits(regs, REG_SSI_SCR,
+				   SSI_SCR_I2S_NET_MASK, i2s_net);
+	}
+
+	/* In synchronous mode, the SSI uses STCCR for capture */
+	tx2 = tx || ssi->synchronous;
+	regmap_update_bits(regs, REG_SSI_SxCCR(tx2), SSI_SxCCR_WL_MASK, wl);
+
+	return 0;
+}
+
+static int fsl_ssi_hw_free(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	if (fsl_ssi_is_i2s_master(ssi) &&
+	    ssi->baudclk_streams & BIT(substream->stream)) {
+		clk_disable_unprepare(ssi->baudclk);
+		ssi->baudclk_streams &= ~BIT(substream->stream);
+	}
+
+	return 0;
+}
+
+static int _fsl_ssi_set_dai_fmt(struct fsl_ssi *ssi, unsigned int fmt)
+{
+	u32 strcr = 0, scr = 0, stcr, srcr, mask;
+
+	ssi->dai_fmt = fmt;
+
+	/* Synchronize frame sync clock for TE to avoid data slipping */
+	scr |= SSI_SCR_SYNC_TX_FS;
+
+	/* Set to default shifting settings: LSB_ALIGNED */
+	strcr |= SSI_STCR_TXBIT0;
+
+	/* Use Network mode as default */
+	ssi->i2s_net = 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:
+			if (IS_ERR(ssi->baudclk)) {
+				dev_err(ssi->dev,
+					"missing baudclk for master mode\n");
+				return -EINVAL;
+			}
+			/* fall through */
+		case SND_SOC_DAIFMT_CBM_CFS:
+			ssi->i2s_net |= SSI_SCR_I2S_MODE_MASTER;
+			break;
+		case SND_SOC_DAIFMT_CBM_CFM:
+			ssi->i2s_net |= SSI_SCR_I2S_MODE_SLAVE;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		regmap_update_bits(ssi->regs, REG_SSI_STCCR,
+				   SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(2));
+		regmap_update_bits(ssi->regs, REG_SSI_SRCCR,
+				   SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(2));
+
+		/* Data on rising edge of bclk, frame low, 1clk before data */
+		strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP | SSI_STCR_TEFS;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		/* Data on rising edge of bclk, frame high */
+		strcr |= SSI_STCR_TSCKP;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		/* Data on rising edge of bclk, frame high, 1clk before data */
+		strcr |= SSI_STCR_TFSL | SSI_STCR_TSCKP | SSI_STCR_TEFS;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		/* Data on rising edge of bclk, frame high */
+		strcr |= SSI_STCR_TFSL | SSI_STCR_TSCKP;
+		break;
+	case SND_SOC_DAIFMT_AC97:
+		/* Data on falling edge of bclk, frame high, 1clk before data */
+		strcr |= SSI_STCR_TEFS;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	scr |= ssi->i2s_net;
+
+	/* 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 ^= SSI_STCR_TSCKP;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		/* Invert frame clock */
+		strcr ^= SSI_STCR_TFSI;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		/* Invert both clocks */
+		strcr ^= SSI_STCR_TSCKP;
+		strcr ^= SSI_STCR_TFSI;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* DAI clock master masks */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		/* Output bit and frame sync clocks */
+		strcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR;
+		scr |= SSI_SCR_SYS_CLK_EN;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		/* Input bit or frame sync clocks */
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		/* Input bit clock but output frame sync clock */
+		strcr |= SSI_STCR_TFDIR;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	stcr = strcr;
+	srcr = strcr;
+
+	/* Set SYN mode and clear RXDIR bit when using SYN or AC97 mode */
+	if (ssi->synchronous || fsl_ssi_is_ac97(ssi)) {
+		srcr &= ~SSI_SRCR_RXDIR;
+		scr |= SSI_SCR_SYN;
+	}
+
+	mask = SSI_STCR_TFDIR | SSI_STCR_TXDIR | SSI_STCR_TSCKP |
+	       SSI_STCR_TFSL | SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
+
+	regmap_update_bits(ssi->regs, REG_SSI_STCR, mask, stcr);
+	regmap_update_bits(ssi->regs, REG_SSI_SRCR, mask, srcr);
+
+	mask = SSI_SCR_SYNC_TX_FS | SSI_SCR_I2S_MODE_MASK |
+	       SSI_SCR_SYS_CLK_EN | SSI_SCR_SYN;
+	regmap_update_bits(ssi->regs, REG_SSI_SCR, mask, scr);
+
+	return 0;
+}
+
+/**
+ * Configure Digital Audio Interface (DAI) Format
+ */
+static int fsl_ssi_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai);
+
+	/* AC97 configured DAIFMT earlier in the probe() */
+	if (fsl_ssi_is_ac97(ssi))
+		return 0;
+
+	return _fsl_ssi_set_dai_fmt(ssi, fmt);
+}
+
+/**
+ * Set TDM slot number and slot width
+ */
+static int fsl_ssi_set_dai_tdm_slot(struct snd_soc_dai *dai, u32 tx_mask,
+				    u32 rx_mask, int slots, int slot_width)
+{
+	struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai);
+	struct regmap *regs = ssi->regs;
+	u32 val;
+
+	/* The word length should be 8, 10, 12, 16, 18, 20, 22 or 24 */
+	if (slot_width & 1 || slot_width < 8 || slot_width > 24) {
+		dev_err(dai->dev, "invalid slot width: %d\n", slot_width);
+		return -EINVAL;
+	}
+
+	/* The slot number should be >= 2 if using Network mode or I2S mode */
+	if (ssi->i2s_net && slots < 2) {
+		dev_err(dai->dev, "slot number should be >= 2 in I2S or NET\n");
+		return -EINVAL;
+	}
+
+	regmap_update_bits(regs, REG_SSI_STCCR,
+			   SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(slots));
+	regmap_update_bits(regs, REG_SSI_SRCCR,
+			   SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(slots));
+
+	/* Save the SCR register value */
+	regmap_read(regs, REG_SSI_SCR, &val);
+	/* Temporarily enable SSI to allow SxMSKs to be configurable */
+	regmap_update_bits(regs, REG_SSI_SCR, SSI_SCR_SSIEN, SSI_SCR_SSIEN);
+
+	regmap_write(regs, REG_SSI_STMSK, ~tx_mask);
+	regmap_write(regs, REG_SSI_SRMSK, ~rx_mask);
+
+	/* Restore the value of SSIEN bit */
+	regmap_update_bits(regs, REG_SSI_SCR, SSI_SCR_SSIEN, val);
+
+	ssi->slot_width = slot_width;
+	ssi->slots = slots;
+
+	return 0;
+}
+
+/**
+ * Start or stop SSI and corresponding DMA transaction.
+ *
+ * The DMA channel is in external master start and pause mode, which
+ * means the SSI completely controls the flow of data.
+ */
+static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+			   struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		/*
+		 * SACCST might be modified via AC Link by a CODEC if it sends
+		 * extra bits in their SLOTREQ requests, which'll accidentally
+		 * send valid data to slots other than normal playback slots.
+		 *
+		 * To be safe, configure SACCST right before TX starts.
+		 */
+		if (tx && fsl_ssi_is_ac97(ssi))
+			fsl_ssi_tx_ac97_saccst_setup(ssi);
+		fsl_ssi_config_enable(ssi, tx);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		fsl_ssi_config_disable(ssi, tx);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int fsl_ssi_dai_probe(struct snd_soc_dai *dai)
+{
+	struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(dai);
+
+	if (ssi->soc->imx && ssi->use_dma)
+		snd_soc_dai_init_dma_data(dai, &ssi->dma_params_tx,
+					  &ssi->dma_params_rx);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops fsl_ssi_dai_ops = {
+	.startup = fsl_ssi_startup,
+	.shutdown = fsl_ssi_shutdown,
+	.hw_params = fsl_ssi_hw_params,
+	.hw_free = fsl_ssi_hw_free,
+	.set_fmt = fsl_ssi_set_dai_fmt,
+	.set_tdm_slot = fsl_ssi_set_dai_tdm_slot,
+	.trigger = fsl_ssi_trigger,
+};
+
+static struct snd_soc_dai_driver fsl_ssi_dai_template = {
+	.probe = fsl_ssi_dai_probe,
+	.playback = {
+		.stream_name = "CPU-Playback",
+		.channels_min = 1,
+		.channels_max = 32,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.formats = FSLSSI_I2S_FORMATS,
+	},
+	.capture = {
+		.stream_name = "CPU-Capture",
+		.channels_min = 1,
+		.channels_max = 32,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.formats = FSLSSI_I2S_FORMATS,
+	},
+	.ops = &fsl_ssi_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_ssi_component = {
+	.name = "fsl-ssi",
+};
+
+static struct snd_soc_dai_driver fsl_ssi_ac97_dai = {
+	.bus_control = true,
+	.symmetric_channels = 1,
+	.probe = fsl_ssi_dai_probe,
+	.playback = {
+		.stream_name = "AC97 Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S20,
+	},
+	.capture = {
+		.stream_name = "AC97 Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		/* 16-bit capture is broken (errata ERR003778) */
+		.formats = SNDRV_PCM_FMTBIT_S20,
+	},
+	.ops = &fsl_ssi_dai_ops,
+};
+
+static struct fsl_ssi *fsl_ac97_data;
+
+static void fsl_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+			       unsigned short val)
+{
+	struct regmap *regs = fsl_ac97_data->regs;
+	unsigned int lreg;
+	unsigned int lval;
+	int ret;
+
+	if (reg > 0x7f)
+		return;
+
+	mutex_lock(&fsl_ac97_data->ac97_reg_lock);
+
+	ret = clk_prepare_enable(fsl_ac97_data->clk);
+	if (ret) {
+		pr_err("ac97 write clk_prepare_enable failed: %d\n",
+			ret);
+		goto ret_unlock;
+	}
+
+	lreg = reg <<  12;
+	regmap_write(regs, REG_SSI_SACADD, lreg);
+
+	lval = val << 4;
+	regmap_write(regs, REG_SSI_SACDAT, lval);
+
+	regmap_update_bits(regs, REG_SSI_SACNT,
+			   SSI_SACNT_RDWR_MASK, SSI_SACNT_WR);
+	udelay(100);
+
+	clk_disable_unprepare(fsl_ac97_data->clk);
+
+ret_unlock:
+	mutex_unlock(&fsl_ac97_data->ac97_reg_lock);
+}
+
+static unsigned short fsl_ssi_ac97_read(struct snd_ac97 *ac97,
+					unsigned short reg)
+{
+	struct regmap *regs = fsl_ac97_data->regs;
+	unsigned short val = 0;
+	u32 reg_val;
+	unsigned int lreg;
+	int ret;
+
+	mutex_lock(&fsl_ac97_data->ac97_reg_lock);
+
+	ret = clk_prepare_enable(fsl_ac97_data->clk);
+	if (ret) {
+		pr_err("ac97 read clk_prepare_enable failed: %d\n", ret);
+		goto ret_unlock;
+	}
+
+	lreg = (reg & 0x7f) <<  12;
+	regmap_write(regs, REG_SSI_SACADD, lreg);
+	regmap_update_bits(regs, REG_SSI_SACNT,
+			   SSI_SACNT_RDWR_MASK, SSI_SACNT_RD);
+
+	udelay(100);
+
+	regmap_read(regs, REG_SSI_SACDAT, &reg_val);
+	val = (reg_val >> 4) & 0xffff;
+
+	clk_disable_unprepare(fsl_ac97_data->clk);
+
+ret_unlock:
+	mutex_unlock(&fsl_ac97_data->ac97_reg_lock);
+	return val;
+}
+
+static struct snd_ac97_bus_ops fsl_ssi_ac97_ops = {
+	.read = fsl_ssi_ac97_read,
+	.write = fsl_ssi_ac97_write,
+};
+
+/**
+ * Initialize SSI registers
+ */
+static int fsl_ssi_hw_init(struct fsl_ssi *ssi)
+{
+	u32 wm = ssi->fifo_watermark;
+
+	/* Initialize regvals */
+	fsl_ssi_setup_regvals(ssi);
+
+	/* Set watermarks */
+	regmap_write(ssi->regs, REG_SSI_SFCSR,
+		     SSI_SFCSR_TFWM0(wm) | SSI_SFCSR_RFWM0(wm) |
+		     SSI_SFCSR_TFWM1(wm) | SSI_SFCSR_RFWM1(wm));
+
+	/* Enable Dual FIFO mode */
+	if (ssi->use_dual_fifo)
+		regmap_update_bits(ssi->regs, REG_SSI_SCR,
+				   SSI_SCR_TCH_EN, SSI_SCR_TCH_EN);
+
+	/* AC97 should start earlier to communicate with CODECs */
+	if (fsl_ssi_is_ac97(ssi)) {
+		_fsl_ssi_set_dai_fmt(ssi, ssi->dai_fmt);
+		fsl_ssi_setup_ac97(ssi);
+	}
+
+	return 0;
+}
+
+/**
+ * Clear SSI registers
+ */
+static void fsl_ssi_hw_clean(struct fsl_ssi *ssi)
+{
+	/* Disable registers for AC97 */
+	if (fsl_ssi_is_ac97(ssi)) {
+		/* Disable TE and RE bits first */
+		regmap_update_bits(ssi->regs, REG_SSI_SCR,
+				   SSI_SCR_TE | SSI_SCR_RE, 0);
+		/* Disable AC97 mode */
+		regmap_write(ssi->regs, REG_SSI_SACNT, 0);
+		/* Unset WAIT bits */
+		regmap_write(ssi->regs, REG_SSI_SOR, 0);
+		/* Disable SSI -- software reset */
+		regmap_update_bits(ssi->regs, REG_SSI_SCR, SSI_SCR_SSIEN, 0);
+	}
+}
+/**
+ * Make every character in a string lower-case
+ */
+static void make_lowercase(char *s)
+{
+	if (!s)
+		return;
+	for (; *s; s++)
+		*s = tolower(*s);
+}
+
+static int fsl_ssi_imx_probe(struct platform_device *pdev,
+			     struct fsl_ssi *ssi, void __iomem *iomem)
+{
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	/* Backward compatible for a DT without ipg clock name assigned */
+	if (ssi->has_ipg_clk_name)
+		ssi->clk = devm_clk_get(dev, "ipg");
+	else
+		ssi->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(ssi->clk)) {
+		ret = PTR_ERR(ssi->clk);
+		dev_err(dev, "failed to get clock: %d\n", ret);
+		return ret;
+	}
+
+	/* Enable the clock since regmap will not handle it in this case */
+	if (!ssi->has_ipg_clk_name) {
+		ret = clk_prepare_enable(ssi->clk);
+		if (ret) {
+			dev_err(dev, "clk_prepare_enable failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	/* Do not error out for slave cases that live without a baud clock */
+	ssi->baudclk = devm_clk_get(dev, "baud");
+	if (IS_ERR(ssi->baudclk))
+		dev_dbg(dev, "failed to get baud clock: %ld\n",
+			 PTR_ERR(ssi->baudclk));
+
+	ssi->dma_params_tx.maxburst = ssi->dma_maxburst;
+	ssi->dma_params_rx.maxburst = ssi->dma_maxburst;
+	ssi->dma_params_tx.addr = ssi->ssi_phys + REG_SSI_STX0;
+	ssi->dma_params_rx.addr = ssi->ssi_phys + REG_SSI_SRX0;
+
+	/* Use even numbers to avoid channel swap due to SDMA script design */
+	if (ssi->use_dual_fifo) {
+		ssi->dma_params_tx.maxburst &= ~0x1;
+		ssi->dma_params_rx.maxburst &= ~0x1;
+	}
+
+	if (!ssi->use_dma) {
+		/*
+		 * Some boards use an incompatible codec. Use imx-fiq-pcm-audio
+		 * to get it working, as DMA is not possible in this situation.
+		 */
+		ssi->fiq_params.irq = ssi->irq;
+		ssi->fiq_params.base = iomem;
+		ssi->fiq_params.dma_params_rx = &ssi->dma_params_rx;
+		ssi->fiq_params.dma_params_tx = &ssi->dma_params_tx;
+
+		ret = imx_pcm_fiq_init(pdev, &ssi->fiq_params);
+		if (ret)
+			goto error_pcm;
+	} else {
+		ret = imx_pcm_dma_init(pdev, IMX_SSI_DMABUF_SIZE);
+		if (ret)
+			goto error_pcm;
+	}
+
+	return 0;
+
+error_pcm:
+	if (!ssi->has_ipg_clk_name)
+		clk_disable_unprepare(ssi->clk);
+
+	return ret;
+}
+
+static void fsl_ssi_imx_clean(struct platform_device *pdev, struct fsl_ssi *ssi)
+{
+	if (!ssi->use_dma)
+		imx_pcm_fiq_exit(pdev);
+	if (!ssi->has_ipg_clk_name)
+		clk_disable_unprepare(ssi->clk);
+}
+
+static int fsl_ssi_probe_from_dt(struct fsl_ssi *ssi)
+{
+	struct device *dev = ssi->dev;
+	struct device_node *np = dev->of_node;
+	const struct of_device_id *of_id;
+	const char *p, *sprop;
+	const __be32 *iprop;
+	u32 dmas[4];
+	int ret;
+
+	of_id = of_match_device(fsl_ssi_ids, dev);
+	if (!of_id || !of_id->data)
+		return -EINVAL;
+
+	ssi->soc = of_id->data;
+
+	ret = of_property_match_string(np, "clock-names", "ipg");
+	/* Get error code if not found */
+	ssi->has_ipg_clk_name = ret >= 0;
+
+	/* Check if being used in AC97 mode */
+	sprop = of_get_property(np, "fsl,mode", NULL);
+	if (sprop && !strcmp(sprop, "ac97-slave")) {
+		ssi->dai_fmt = FSLSSI_AC97_DAIFMT;
+
+		ret = of_property_read_u32(np, "cell-index", &ssi->card_idx);
+		if (ret) {
+			dev_err(dev, "failed to get SSI index property\n");
+			return -EINVAL;
+		}
+		strcpy(ssi->card_name, "ac97-codec");
+	} else if (!of_find_property(np, "fsl,ssi-asynchronous", NULL)) {
+		/*
+		 * In synchronous mode, STCK and STFS ports are used by RX
+		 * as well. So the software should limit the sample rates,
+		 * sample bits and channels to be symmetric.
+		 *
+		 * This is exclusive with FSLSSI_AC97_FORMATS as AC97 runs
+		 * in the SSI synchronous mode however it does not have to
+		 * limit symmetric sample rates and sample bits.
+		 */
+		ssi->synchronous = true;
+	}
+
+	/* Select DMA or FIQ */
+	ssi->use_dma = !of_property_read_bool(np, "fsl,fiq-stream-filter");
+
+	/* Fetch FIFO depth; Set to 8 for older DT without this property */
+	iprop = of_get_property(np, "fsl,fifo-depth", NULL);
+	if (iprop)
+		ssi->fifo_depth = be32_to_cpup(iprop);
+	else
+		ssi->fifo_depth = 8;
+
+	/* Use dual FIFO mode depending on the support from SDMA script */
+	ret = of_property_read_u32_array(np, "dmas", dmas, 4);
+	if (ssi->use_dma && !ret && dmas[2] == IMX_DMATYPE_SSI_DUAL)
+		ssi->use_dual_fifo = true;
+
+	/*
+	 * Backward compatible for older bindings by manually triggering the
+	 * machine driver's probe(). Use /compatible property, including the
+	 * address of CPU DAI driver structure, as the name of machine driver
+	 *
+	 * If card_name is set by AC97 earlier, bypass here since it uses a
+	 * different name to register the device.
+	 */
+	if (!ssi->card_name[0] && of_get_property(np, "codec-handle", NULL)) {
+		sprop = of_get_property(of_find_node_by_path("/"),
+					"compatible", NULL);
+		/* Strip "fsl," in the compatible name if applicable */
+		p = strrchr(sprop, ',');
+		if (p)
+			sprop = p + 1;
+		snprintf(ssi->card_name, sizeof(ssi->card_name),
+			 "snd-soc-%s", sprop);
+		make_lowercase(ssi->card_name);
+		ssi->card_idx = 0;
+	}
+
+	return 0;
+}
+
+static int fsl_ssi_probe(struct platform_device *pdev)
+{
+	struct regmap_config regconfig = fsl_ssi_regconfig;
+	struct device *dev = &pdev->dev;
+	struct fsl_ssi *ssi;
+	struct resource *res;
+	void __iomem *iomem;
+	int ret = 0;
+
+	ssi = devm_kzalloc(dev, sizeof(*ssi), GFP_KERNEL);
+	if (!ssi)
+		return -ENOMEM;
+
+	ssi->dev = dev;
+
+	/* Probe from DT */
+	ret = fsl_ssi_probe_from_dt(ssi);
+	if (ret)
+		return ret;
+
+	if (fsl_ssi_is_ac97(ssi)) {
+		memcpy(&ssi->cpu_dai_drv, &fsl_ssi_ac97_dai,
+		       sizeof(fsl_ssi_ac97_dai));
+		fsl_ac97_data = ssi;
+	} else {
+		memcpy(&ssi->cpu_dai_drv, &fsl_ssi_dai_template,
+		       sizeof(fsl_ssi_dai_template));
+	}
+	ssi->cpu_dai_drv.name = dev_name(dev);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	iomem = devm_ioremap_resource(dev, res);
+	if (IS_ERR(iomem))
+		return PTR_ERR(iomem);
+	ssi->ssi_phys = res->start;
+
+	if (ssi->soc->imx21regs) {
+		/* No SACC{ST,EN,DIS} regs in imx21-class SSI */
+		regconfig.max_register = REG_SSI_SRMSK;
+		regconfig.num_reg_defaults_raw =
+			REG_SSI_SRMSK / sizeof(uint32_t) + 1;
+	}
+
+	if (ssi->has_ipg_clk_name)
+		ssi->regs = devm_regmap_init_mmio_clk(dev, "ipg", iomem,
+						      &regconfig);
+	else
+		ssi->regs = devm_regmap_init_mmio(dev, iomem, &regconfig);
+	if (IS_ERR(ssi->regs)) {
+		dev_err(dev, "failed to init register map\n");
+		return PTR_ERR(ssi->regs);
+	}
+
+	ssi->irq = platform_get_irq(pdev, 0);
+	if (ssi->irq < 0) {
+		dev_err(dev, "no irq for node %s\n", pdev->name);
+		return ssi->irq;
+	}
+
+	/* Set software limitations for synchronous mode except AC97 */
+	if (ssi->synchronous && !fsl_ssi_is_ac97(ssi)) {
+		ssi->cpu_dai_drv.symmetric_rates = 1;
+		ssi->cpu_dai_drv.symmetric_channels = 1;
+		ssi->cpu_dai_drv.symmetric_samplebits = 1;
+	}
+
+	/*
+	 * Configure TX and RX DMA watermarks -- when to send a DMA request
+	 *
+	 * Values should be tested to avoid FIFO under/over run. Set maxburst
+	 * to fifo_watermark to maxiumize DMA transaction to reduce overhead.
+	 */
+	switch (ssi->fifo_depth) {
+	case 15:
+		/*
+		 * Set to 8 as a balanced configuration -- When TX FIFO has 8
+		 * empty slots, send a DMA request to fill these 8 slots. The
+		 * remaining 7 slots should be able to allow DMA to finish the
+		 * transaction before TX FIFO underruns; Same applies to RX.
+		 *
+		 * Tested with cases running at 48kHz @ 16 bits x 16 channels
+		 */
+		ssi->fifo_watermark = 8;
+		ssi->dma_maxburst = 8;
+		break;
+	case 8:
+	default:
+		/* Safely use old watermark configurations for older chips */
+		ssi->fifo_watermark = ssi->fifo_depth - 2;
+		ssi->dma_maxburst = ssi->fifo_depth - 2;
+		break;
+	}
+
+	dev_set_drvdata(dev, ssi);
+
+	if (ssi->soc->imx) {
+		ret = fsl_ssi_imx_probe(pdev, ssi, iomem);
+		if (ret)
+			return ret;
+	}
+
+	if (fsl_ssi_is_ac97(ssi)) {
+		mutex_init(&ssi->ac97_reg_lock);
+		ret = snd_soc_set_ac97_ops_of_reset(&fsl_ssi_ac97_ops, pdev);
+		if (ret) {
+			dev_err(dev, "failed to set AC'97 ops\n");
+			goto error_ac97_ops;
+		}
+	}
+
+	ret = devm_snd_soc_register_component(dev, &fsl_ssi_component,
+					      &ssi->cpu_dai_drv, 1);
+	if (ret) {
+		dev_err(dev, "failed to register DAI: %d\n", ret);
+		goto error_asoc_register;
+	}
+
+	if (ssi->use_dma) {
+		ret = devm_request_irq(dev, ssi->irq, fsl_ssi_isr, 0,
+				       dev_name(dev), ssi);
+		if (ret < 0) {
+			dev_err(dev, "failed to claim irq %u\n", ssi->irq);
+			goto error_asoc_register;
+		}
+	}
+
+	ret = fsl_ssi_debugfs_create(&ssi->dbg_stats, dev);
+	if (ret)
+		goto error_asoc_register;
+
+	/* Initially configures SSI registers */
+	fsl_ssi_hw_init(ssi);
+
+	/* Register a platform device for older bindings or AC97 */
+	if (ssi->card_name[0]) {
+		struct device *parent = dev;
+		/*
+		 * Do not set SSI dev as the parent of AC97 CODEC device since
+		 * it does not have a DT node. Otherwise ASoC core will assume
+		 * CODEC has the same DT node as the SSI, so it may bypass the
+		 * dai_probe() of SSI and then cause NULL DMA data pointers.
+		 */
+		if (fsl_ssi_is_ac97(ssi))
+			parent = NULL;
+
+		ssi->card_pdev = platform_device_register_data(parent,
+				ssi->card_name, ssi->card_idx, NULL, 0);
+		if (IS_ERR(ssi->card_pdev)) {
+			ret = PTR_ERR(ssi->card_pdev);
+			dev_err(dev, "failed to register %s: %d\n",
+				ssi->card_name, ret);
+			goto error_sound_card;
+		}
+	}
+
+	return 0;
+
+error_sound_card:
+	fsl_ssi_debugfs_remove(&ssi->dbg_stats);
+error_asoc_register:
+	if (fsl_ssi_is_ac97(ssi))
+		snd_soc_set_ac97_ops(NULL);
+error_ac97_ops:
+	if (fsl_ssi_is_ac97(ssi))
+		mutex_destroy(&ssi->ac97_reg_lock);
+
+	if (ssi->soc->imx)
+		fsl_ssi_imx_clean(pdev, ssi);
+
+	return ret;
+}
+
+static int fsl_ssi_remove(struct platform_device *pdev)
+{
+	struct fsl_ssi *ssi = dev_get_drvdata(&pdev->dev);
+
+	fsl_ssi_debugfs_remove(&ssi->dbg_stats);
+
+	if (ssi->card_pdev)
+		platform_device_unregister(ssi->card_pdev);
+
+	/* Clean up SSI registers */
+	fsl_ssi_hw_clean(ssi);
+
+	if (ssi->soc->imx)
+		fsl_ssi_imx_clean(pdev, ssi);
+
+	if (fsl_ssi_is_ac97(ssi)) {
+		snd_soc_set_ac97_ops(NULL);
+		mutex_destroy(&ssi->ac97_reg_lock);
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_ssi_suspend(struct device *dev)
+{
+	struct fsl_ssi *ssi = dev_get_drvdata(dev);
+	struct regmap *regs = ssi->regs;
+
+	regmap_read(regs, REG_SSI_SFCSR, &ssi->regcache_sfcsr);
+	regmap_read(regs, REG_SSI_SACNT, &ssi->regcache_sacnt);
+
+	regcache_cache_only(regs, true);
+	regcache_mark_dirty(regs);
+
+	return 0;
+}
+
+static int fsl_ssi_resume(struct device *dev)
+{
+	struct fsl_ssi *ssi = dev_get_drvdata(dev);
+	struct regmap *regs = ssi->regs;
+
+	regcache_cache_only(regs, false);
+
+	regmap_update_bits(regs, REG_SSI_SFCSR,
+			   SSI_SFCSR_RFWM1_MASK | SSI_SFCSR_TFWM1_MASK |
+			   SSI_SFCSR_RFWM0_MASK | SSI_SFCSR_TFWM0_MASK,
+			   ssi->regcache_sfcsr);
+	regmap_write(regs, REG_SSI_SACNT, ssi->regcache_sacnt);
+
+	return regcache_sync(regs);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_ssi_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(fsl_ssi_suspend, fsl_ssi_resume)
+};
+
+static struct platform_driver fsl_ssi_driver = {
+	.driver = {
+		.name = "fsl-ssi-dai",
+		.of_match_table = fsl_ssi_ids,
+		.pm = &fsl_ssi_pm,
+	},
+	.probe = fsl_ssi_probe,
+	.remove = fsl_ssi_remove,
+};
+
+module_platform_driver(fsl_ssi_driver);
+
+MODULE_ALIAS("platform:fsl-ssi-dai");
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h
new file mode 100644
index 0000000..0bdda60
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.h
@@ -0,0 +1,326 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * fsl_ssi.h - ALSA SSI interface for the Freescale MPC8610 and i.MX SoC
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2008 Freescale Semiconductor, Inc.
+ */
+
+#ifndef _MPC8610_I2S_H
+#define _MPC8610_I2S_H
+
+/* -- SSI Register Map -- */
+
+/* SSI Transmit Data Register 0 */
+#define REG_SSI_STX0			0x00
+/* SSI Transmit Data Register 1 */
+#define REG_SSI_STX1			0x04
+/* SSI Receive Data Register 0 */
+#define REG_SSI_SRX0			0x08
+/* SSI Receive Data Register 1 */
+#define REG_SSI_SRX1			0x0c
+/* SSI Control Register */
+#define REG_SSI_SCR			0x10
+/* SSI Interrupt Status Register */
+#define REG_SSI_SISR			0x14
+/* SSI Interrupt Enable Register */
+#define REG_SSI_SIER			0x18
+/* SSI Transmit Configuration Register */
+#define REG_SSI_STCR			0x1c
+/* SSI Receive Configuration Register */
+#define REG_SSI_SRCR			0x20
+#define REG_SSI_SxCR(tx)		((tx) ? REG_SSI_STCR : REG_SSI_SRCR)
+/* SSI Transmit Clock Control Register */
+#define REG_SSI_STCCR			0x24
+/* SSI Receive Clock Control Register */
+#define REG_SSI_SRCCR			0x28
+#define REG_SSI_SxCCR(tx)		((tx) ? REG_SSI_STCCR : REG_SSI_SRCCR)
+/* SSI FIFO Control/Status Register */
+#define REG_SSI_SFCSR			0x2c
+/*
+ * SSI Test Register (Intended for debugging purposes only)
+ *
+ * Note: STR is not documented in recent IMX datasheet, but
+ * is described in IMX51 reference manual at section 56.3.3.14
+ */
+#define REG_SSI_STR			0x30
+/*
+ * SSI Option Register (Intended for internal use only)
+ *
+ * Note: SOR is not documented in recent IMX datasheet, but
+ * is described in IMX51 reference manual at section 56.3.3.15
+ */
+#define REG_SSI_SOR			0x34
+/* SSI AC97 Control Register */
+#define REG_SSI_SACNT			0x38
+/* SSI AC97 Command Address Register */
+#define REG_SSI_SACADD			0x3c
+/* SSI AC97 Command Data Register */
+#define REG_SSI_SACDAT			0x40
+/* SSI AC97 Tag Register */
+#define REG_SSI_SATAG			0x44
+/* SSI Transmit Time Slot Mask Register */
+#define REG_SSI_STMSK			0x48
+/* SSI  Receive Time Slot Mask Register */
+#define REG_SSI_SRMSK			0x4c
+#define REG_SSI_SxMSK(tx)		((tx) ? REG_SSI_STMSK : REG_SSI_SRMSK)
+/*
+ * SSI AC97 Channel Status Register
+ *
+ * The status could be changed by:
+ * 1) Writing a '1' bit at some position in SACCEN sets relevant bit in SACCST
+ * 2) Writing a '1' bit at some position in SACCDIS unsets the relevant bit
+ * 3) Receivng a '1' in SLOTREQ bit from external CODEC via AC Link
+ */
+#define REG_SSI_SACCST			0x50
+/* SSI AC97 Channel Enable Register -- Set bits in SACCST */
+#define REG_SSI_SACCEN			0x54
+/* SSI AC97 Channel Disable Register -- Clear bits in SACCST */
+#define REG_SSI_SACCDIS			0x58
+
+/* -- SSI Register Field Maps -- */
+
+/* SSI Control Register -- REG_SSI_SCR 0x10 */
+#define SSI_SCR_SYNC_TX_FS		0x00001000
+#define SSI_SCR_RFR_CLK_DIS		0x00000800
+#define SSI_SCR_TFR_CLK_DIS		0x00000400
+#define SSI_SCR_TCH_EN			0x00000100
+#define SSI_SCR_SYS_CLK_EN		0x00000080
+#define SSI_SCR_I2S_MODE_MASK		0x00000060
+#define SSI_SCR_I2S_MODE_NORMAL		0x00000000
+#define SSI_SCR_I2S_MODE_MASTER		0x00000020
+#define SSI_SCR_I2S_MODE_SLAVE		0x00000040
+#define SSI_SCR_SYN			0x00000010
+#define SSI_SCR_NET			0x00000008
+#define SSI_SCR_I2S_NET_MASK		(SSI_SCR_NET | SSI_SCR_I2S_MODE_MASK)
+#define SSI_SCR_RE			0x00000004
+#define SSI_SCR_TE			0x00000002
+#define SSI_SCR_SSIEN			0x00000001
+
+/* SSI Interrupt Status Register -- REG_SSI_SISR 0x14 */
+#define SSI_SISR_RFRC			0x01000000
+#define SSI_SISR_TFRC			0x00800000
+#define SSI_SISR_CMDAU			0x00040000
+#define SSI_SISR_CMDDU			0x00020000
+#define SSI_SISR_RXT			0x00010000
+#define SSI_SISR_RDR1			0x00008000
+#define SSI_SISR_RDR0			0x00004000
+#define SSI_SISR_TDE1			0x00002000
+#define SSI_SISR_TDE0			0x00001000
+#define SSI_SISR_ROE1			0x00000800
+#define SSI_SISR_ROE0			0x00000400
+#define SSI_SISR_TUE1			0x00000200
+#define SSI_SISR_TUE0			0x00000100
+#define SSI_SISR_TFS			0x00000080
+#define SSI_SISR_RFS			0x00000040
+#define SSI_SISR_TLS			0x00000020
+#define SSI_SISR_RLS			0x00000010
+#define SSI_SISR_RFF1			0x00000008
+#define SSI_SISR_RFF0			0x00000004
+#define SSI_SISR_TFE1			0x00000002
+#define SSI_SISR_TFE0			0x00000001
+
+/* SSI Interrupt Enable Register -- REG_SSI_SIER 0x18 */
+#define SSI_SIER_RFRC_EN		0x01000000
+#define SSI_SIER_TFRC_EN		0x00800000
+#define SSI_SIER_RDMAE			0x00400000
+#define SSI_SIER_RIE			0x00200000
+#define SSI_SIER_TDMAE			0x00100000
+#define SSI_SIER_TIE			0x00080000
+#define SSI_SIER_CMDAU_EN		0x00040000
+#define SSI_SIER_CMDDU_EN		0x00020000
+#define SSI_SIER_RXT_EN			0x00010000
+#define SSI_SIER_RDR1_EN		0x00008000
+#define SSI_SIER_RDR0_EN		0x00004000
+#define SSI_SIER_TDE1_EN		0x00002000
+#define SSI_SIER_TDE0_EN		0x00001000
+#define SSI_SIER_ROE1_EN		0x00000800
+#define SSI_SIER_ROE0_EN		0x00000400
+#define SSI_SIER_TUE1_EN		0x00000200
+#define SSI_SIER_TUE0_EN		0x00000100
+#define SSI_SIER_TFS_EN			0x00000080
+#define SSI_SIER_RFS_EN			0x00000040
+#define SSI_SIER_TLS_EN			0x00000020
+#define SSI_SIER_RLS_EN			0x00000010
+#define SSI_SIER_RFF1_EN		0x00000008
+#define SSI_SIER_RFF0_EN		0x00000004
+#define SSI_SIER_TFE1_EN		0x00000002
+#define SSI_SIER_TFE0_EN		0x00000001
+
+/* SSI Transmit Configuration Register -- REG_SSI_STCR 0x1C */
+#define SSI_STCR_TXBIT0			0x00000200
+#define SSI_STCR_TFEN1			0x00000100
+#define SSI_STCR_TFEN0			0x00000080
+#define SSI_STCR_TFDIR			0x00000040
+#define SSI_STCR_TXDIR			0x00000020
+#define SSI_STCR_TSHFD			0x00000010
+#define SSI_STCR_TSCKP			0x00000008
+#define SSI_STCR_TFSI			0x00000004
+#define SSI_STCR_TFSL			0x00000002
+#define SSI_STCR_TEFS			0x00000001
+
+/* SSI Receive Configuration Register -- REG_SSI_SRCR 0x20 */
+#define SSI_SRCR_RXEXT			0x00000400
+#define SSI_SRCR_RXBIT0			0x00000200
+#define SSI_SRCR_RFEN1			0x00000100
+#define SSI_SRCR_RFEN0			0x00000080
+#define SSI_SRCR_RFDIR			0x00000040
+#define SSI_SRCR_RXDIR			0x00000020
+#define SSI_SRCR_RSHFD			0x00000010
+#define SSI_SRCR_RSCKP			0x00000008
+#define SSI_SRCR_RFSI			0x00000004
+#define SSI_SRCR_RFSL			0x00000002
+#define SSI_SRCR_REFS			0x00000001
+
+/*
+ * SSI Transmit Clock Control Register -- REG_SSI_STCCR 0x24
+ * SSI Receive Clock Control Register -- REG_SSI_SRCCR 0x28
+ */
+#define SSI_SxCCR_DIV2_SHIFT		18
+#define SSI_SxCCR_DIV2			0x00040000
+#define SSI_SxCCR_PSR_SHIFT		17
+#define SSI_SxCCR_PSR			0x00020000
+#define SSI_SxCCR_WL_SHIFT		13
+#define SSI_SxCCR_WL_MASK		0x0001E000
+#define SSI_SxCCR_WL(x) \
+	(((((x) / 2) - 1) << SSI_SxCCR_WL_SHIFT) & SSI_SxCCR_WL_MASK)
+#define SSI_SxCCR_DC_SHIFT		8
+#define SSI_SxCCR_DC_MASK		0x00001F00
+#define SSI_SxCCR_DC(x) \
+	((((x) - 1) << SSI_SxCCR_DC_SHIFT) & SSI_SxCCR_DC_MASK)
+#define SSI_SxCCR_PM_SHIFT		0
+#define SSI_SxCCR_PM_MASK		0x000000FF
+#define SSI_SxCCR_PM(x) \
+	((((x) - 1) << SSI_SxCCR_PM_SHIFT) & SSI_SxCCR_PM_MASK)
+
+/*
+ * SSI FIFO Control/Status Register -- REG_SSI_SFCSR 0x2c
+ *
+ * Tx or Rx FIFO Counter -- SSI_SFCSR_xFCNTy Read-Only
+ * Tx or Rx FIFO Watermarks -- SSI_SFCSR_xFWMy Read/Write
+ */
+#define SSI_SFCSR_RFCNT1_SHIFT		28
+#define SSI_SFCSR_RFCNT1_MASK		0xF0000000
+#define SSI_SFCSR_RFCNT1(x) \
+	(((x) & SSI_SFCSR_RFCNT1_MASK) >> SSI_SFCSR_RFCNT1_SHIFT)
+#define SSI_SFCSR_TFCNT1_SHIFT		24
+#define SSI_SFCSR_TFCNT1_MASK		0x0F000000
+#define SSI_SFCSR_TFCNT1(x) \
+	(((x) & SSI_SFCSR_TFCNT1_MASK) >> SSI_SFCSR_TFCNT1_SHIFT)
+#define SSI_SFCSR_RFWM1_SHIFT		20
+#define SSI_SFCSR_RFWM1_MASK		0x00F00000
+#define SSI_SFCSR_RFWM1(x)	\
+	(((x) << SSI_SFCSR_RFWM1_SHIFT) & SSI_SFCSR_RFWM1_MASK)
+#define SSI_SFCSR_TFWM1_SHIFT		16
+#define SSI_SFCSR_TFWM1_MASK		0x000F0000
+#define SSI_SFCSR_TFWM1(x)	\
+	(((x) << SSI_SFCSR_TFWM1_SHIFT) & SSI_SFCSR_TFWM1_MASK)
+#define SSI_SFCSR_RFCNT0_SHIFT		12
+#define SSI_SFCSR_RFCNT0_MASK		0x0000F000
+#define SSI_SFCSR_RFCNT0(x) \
+	(((x) & SSI_SFCSR_RFCNT0_MASK) >> SSI_SFCSR_RFCNT0_SHIFT)
+#define SSI_SFCSR_TFCNT0_SHIFT		8
+#define SSI_SFCSR_TFCNT0_MASK		0x00000F00
+#define SSI_SFCSR_TFCNT0(x) \
+	(((x) & SSI_SFCSR_TFCNT0_MASK) >> SSI_SFCSR_TFCNT0_SHIFT)
+#define SSI_SFCSR_RFWM0_SHIFT		4
+#define SSI_SFCSR_RFWM0_MASK		0x000000F0
+#define SSI_SFCSR_RFWM0(x)	\
+	(((x) << SSI_SFCSR_RFWM0_SHIFT) & SSI_SFCSR_RFWM0_MASK)
+#define SSI_SFCSR_TFWM0_SHIFT		0
+#define SSI_SFCSR_TFWM0_MASK		0x0000000F
+#define SSI_SFCSR_TFWM0(x)	\
+	(((x) << SSI_SFCSR_TFWM0_SHIFT) & SSI_SFCSR_TFWM0_MASK)
+
+/* SSI Test Register -- REG_SSI_STR 0x30 */
+#define SSI_STR_TEST			0x00008000
+#define SSI_STR_RCK2TCK			0x00004000
+#define SSI_STR_RFS2TFS			0x00002000
+#define SSI_STR_RXSTATE(x)		(((x) >> 8) & 0x1F)
+#define SSI_STR_TXD2RXD			0x00000080
+#define SSI_STR_TCK2RCK			0x00000040
+#define SSI_STR_TFS2RFS			0x00000020
+#define SSI_STR_TXSTATE(x)		((x) & 0x1F)
+
+/* SSI Option Register -- REG_SSI_SOR 0x34 */
+#define SSI_SOR_CLKOFF			0x00000040
+#define SSI_SOR_RX_CLR			0x00000020
+#define SSI_SOR_TX_CLR			0x00000010
+#define SSI_SOR_xX_CLR(tx)		((tx) ? SSI_SOR_TX_CLR : SSI_SOR_RX_CLR)
+#define SSI_SOR_INIT			0x00000008
+#define SSI_SOR_WAIT_SHIFT		1
+#define SSI_SOR_WAIT_MASK		0x00000006
+#define SSI_SOR_WAIT(x)			(((x) & 3) << SSI_SOR_WAIT_SHIFT)
+#define SSI_SOR_SYNRST			0x00000001
+
+/* SSI AC97 Control Register -- REG_SSI_SACNT 0x38 */
+#define SSI_SACNT_FRDIV(x)		(((x) & 0x3f) << 5)
+#define SSI_SACNT_WR			0x00000010
+#define SSI_SACNT_RD			0x00000008
+#define SSI_SACNT_RDWR_MASK		0x00000018
+#define SSI_SACNT_TIF			0x00000004
+#define SSI_SACNT_FV			0x00000002
+#define 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 0000000..1255dfe
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi_dbg.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale SSI ALSA SoC Digital Audio Interface (DAI) debugging functions
+//
+// Copyright 2014 Markus Pargmann <mpa@pengutronix.de>, Pengutronix
+//
+// Split from fsl_ssi.c
+
+#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 & SSI_SISR_RFRC)
+		dbg->stats.rfrc++;
+
+	if (sisr & SSI_SISR_TFRC)
+		dbg->stats.tfrc++;
+
+	if (sisr & SSI_SISR_CMDAU)
+		dbg->stats.cmdau++;
+
+	if (sisr & SSI_SISR_CMDDU)
+		dbg->stats.cmddu++;
+
+	if (sisr & SSI_SISR_RXT)
+		dbg->stats.rxt++;
+
+	if (sisr & SSI_SISR_RDR1)
+		dbg->stats.rdr1++;
+
+	if (sisr & SSI_SISR_RDR0)
+		dbg->stats.rdr0++;
+
+	if (sisr & SSI_SISR_TDE1)
+		dbg->stats.tde1++;
+
+	if (sisr & SSI_SISR_TDE0)
+		dbg->stats.tde0++;
+
+	if (sisr & SSI_SISR_ROE1)
+		dbg->stats.roe1++;
+
+	if (sisr & SSI_SISR_ROE0)
+		dbg->stats.roe0++;
+
+	if (sisr & SSI_SISR_TUE1)
+		dbg->stats.tue1++;
+
+	if (sisr & SSI_SISR_TUE0)
+		dbg->stats.tue0++;
+
+	if (sisr & SSI_SISR_TFS)
+		dbg->stats.tfs++;
+
+	if (sisr & SSI_SISR_RFS)
+		dbg->stats.rfs++;
+
+	if (sisr & SSI_SISR_TLS)
+		dbg->stats.tls++;
+
+	if (sisr & SSI_SISR_RLS)
+		dbg->stats.rls++;
+
+	if (sisr & SSI_SISR_RFF1)
+		dbg->stats.rff1++;
+
+	if (sisr & SSI_SISR_RFF0)
+		dbg->stats.rff0++;
+
+	if (sisr & SSI_SISR_TFE1)
+		dbg->stats.tfe1++;
+
+	if (sisr & SSI_SISR_TFE0)
+		dbg->stats.tfe0++;
+}
+
+/**
+ * Show the statistics of a flag only if its interrupt is enabled
+ *
+ * Compilers will optimize it to a no-op if the interrupt is disabled
+ */
+#define SIER_SHOW(flag, name) \
+	do { \
+		if (SSI_SIER_##flag) \
+			seq_printf(s, #name "=%u\n", ssi_dbg->stats.name); \
+	} while (0)
+
+
+/**
+ * Display the statistics for the current SSI device
+ *
+ * To avoid confusion, 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", 0444,
+						 ssi_dbg->dbg_dir, ssi_dbg,
+						 &fsl_ssi_stats_ops);
+	if (!ssi_dbg->dbg_stats) {
+		debugfs_remove(ssi_dbg->dbg_dir);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg)
+{
+	debugfs_remove(ssi_dbg->dbg_stats);
+	debugfs_remove(ssi_dbg->dbg_dir);
+}
diff --git a/sound/soc/fsl/fsl_utils.c b/sound/soc/fsl/fsl_utils.c
new file mode 100644
index 0000000..7f0fa4b
--- /dev/null
+++ b/sound/soc/fsl/fsl_utils.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale ALSA SoC Machine driver utility
+//
+// Author: Timur Tabi <timur@freescale.com>
+//
+// Copyright 2010 Freescale Semiconductor, Inc.
+
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <sound/soc.h>
+
+#include "fsl_utils.h"
+
+/**
+ * fsl_asoc_get_dma_channel - determine the dma channel for a SSI node
+ *
+ * @ssi_np: pointer to the SSI device tree node
+ * @name: name of the phandle pointing to the dma channel
+ * @dai: ASoC DAI link pointer to be filled with platform_name
+ * @dma_channel_id: dma channel id to be returned
+ * @dma_id: dma id to be returned
+ *
+ * This function determines the dma and channel id for given SSI node.  It
+ * also discovers the platform_name for the ASoC DAI link.
+ */
+int fsl_asoc_get_dma_channel(struct device_node *ssi_np,
+			     const char *name,
+			     struct snd_soc_dai_link *dai,
+			     unsigned int *dma_channel_id,
+			     unsigned int *dma_id)
+{
+	struct resource res;
+	struct device_node *dma_channel_np, *dma_np;
+	const __be32 *iprop;
+	int ret;
+
+	dma_channel_np = of_parse_phandle(ssi_np, name, 0);
+	if (!dma_channel_np)
+		return -EINVAL;
+
+	if (!of_device_is_compatible(dma_channel_np, "fsl,ssi-dma-channel")) {
+		of_node_put(dma_channel_np);
+		return -EINVAL;
+	}
+
+	/* Determine the dev_name for the device_node.  This code mimics the
+	 * behavior of of_device_make_bus_id(). We need this because ASoC uses
+	 * the dev_name() of the device to match the platform (DMA) device with
+	 * the CPU (SSI) device.  It's all ugly and hackish, but it works (for
+	 * now).
+	 *
+	 * dai->platform name should already point to an allocated buffer.
+	 */
+	ret = of_address_to_resource(dma_channel_np, 0, &res);
+	if (ret) {
+		of_node_put(dma_channel_np);
+		return ret;
+	}
+	snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s",
+		 (unsigned long long) res.start, dma_channel_np->name);
+
+	iprop = of_get_property(dma_channel_np, "cell-index", NULL);
+	if (!iprop) {
+		of_node_put(dma_channel_np);
+		return -EINVAL;
+	}
+	*dma_channel_id = be32_to_cpup(iprop);
+
+	dma_np = of_get_parent(dma_channel_np);
+	iprop = of_get_property(dma_np, "cell-index", NULL);
+	if (!iprop) {
+		of_node_put(dma_np);
+		return -EINVAL;
+	}
+	*dma_id = be32_to_cpup(iprop);
+
+	of_node_put(dma_np);
+	of_node_put(dma_channel_np);
+
+	return 0;
+}
+EXPORT_SYMBOL(fsl_asoc_get_dma_channel);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale ASoC utility code");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_utils.h b/sound/soc/fsl/fsl_utils.h
new file mode 100644
index 0000000..c5dc2a1
--- /dev/null
+++ b/sound/soc/fsl/fsl_utils.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Freescale ALSA SoC Machine driver utility
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2010 Freescale Semiconductor, Inc.
+ */
+
+#ifndef _FSL_UTILS_H
+#define _FSL_UTILS_H
+
+#define DAI_NAME_SIZE	32
+
+struct snd_soc_dai_link;
+struct device_node;
+
+int fsl_asoc_get_dma_channel(struct device_node *ssi_np, const char *name,
+			     struct snd_soc_dai_link *dai,
+			     unsigned int *dma_channel_id,
+			     unsigned int *dma_id);
+#endif /* _FSL_UTILS_H */
diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c
new file mode 100644
index 0000000..392d5ee
--- /dev/null
+++ b/sound/soc/fsl/imx-audmux.c
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012 Linaro Ltd.
+ * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * Initial development of this code was funded by
+ * Phytec Messtechnik GmbH, http://www.phytec.de
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "imx-audmux.h"
+
+#define DRIVER_NAME "imx-audmux"
+
+static struct clk *audmux_clk;
+static void __iomem *audmux_base;
+
+#define IMX_AUDMUX_V2_PTCR(x)		((x) * 8)
+#define IMX_AUDMUX_V2_PDCR(x)		((x) * 8 + 4)
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *audmux_debugfs_root;
+
+/* There is an annoying discontinuity in the SSI numbering with regard
+ * to the Linux number of the devices */
+static const char *audmux_port_string(int port)
+{
+	switch (port) {
+	case MX31_AUDMUX_PORT1_SSI0:
+		return "imx-ssi.0";
+	case MX31_AUDMUX_PORT2_SSI1:
+		return "imx-ssi.1";
+	case MX31_AUDMUX_PORT3_SSI_PINS_3:
+		return "SSI3";
+	case MX31_AUDMUX_PORT4_SSI_PINS_4:
+		return "SSI4";
+	case MX31_AUDMUX_PORT5_SSI_PINS_5:
+		return "SSI5";
+	case MX31_AUDMUX_PORT6_SSI_PINS_6:
+		return "SSI6";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+static ssize_t audmux_read_file(struct file *file, char __user *user_buf,
+				size_t count, loff_t *ppos)
+{
+	ssize_t ret;
+	char *buf;
+	uintptr_t port = (uintptr_t)file->private_data;
+	u32 pdcr, ptcr;
+
+	if (audmux_clk) {
+		ret = clk_prepare_enable(audmux_clk);
+		if (ret)
+			return ret;
+	}
+
+	ptcr = readl(audmux_base + IMX_AUDMUX_V2_PTCR(port));
+	pdcr = readl(audmux_base + IMX_AUDMUX_V2_PDCR(port));
+
+	if (audmux_clk)
+		clk_disable_unprepare(audmux_clk);
+
+	buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = snprintf(buf, PAGE_SIZE, "PDCR: %08x\nPTCR: %08x\n",
+		       pdcr, ptcr);
+
+	if (ptcr & IMX_AUDMUX_V2_PTCR_TFSDIR)
+		ret += snprintf(buf + ret, PAGE_SIZE - ret,
+				"TxFS output from %s, ",
+				audmux_port_string((ptcr >> 27) & 0x7));
+	else
+		ret += snprintf(buf + ret, PAGE_SIZE - ret,
+				"TxFS input, ");
+
+	if (ptcr & IMX_AUDMUX_V2_PTCR_TCLKDIR)
+		ret += snprintf(buf + ret, PAGE_SIZE - ret,
+				"TxClk output from %s",
+				audmux_port_string((ptcr >> 22) & 0x7));
+	else
+		ret += snprintf(buf + ret, PAGE_SIZE - ret,
+				"TxClk input");
+
+	ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
+
+	if (ptcr & IMX_AUDMUX_V2_PTCR_SYN) {
+		ret += snprintf(buf + ret, PAGE_SIZE - ret,
+				"Port is symmetric");
+	} else {
+		if (ptcr & IMX_AUDMUX_V2_PTCR_RFSDIR)
+			ret += snprintf(buf + ret, PAGE_SIZE - ret,
+					"RxFS output from %s, ",
+					audmux_port_string((ptcr >> 17) & 0x7));
+		else
+			ret += snprintf(buf + ret, PAGE_SIZE - ret,
+					"RxFS input, ");
+
+		if (ptcr & IMX_AUDMUX_V2_PTCR_RCLKDIR)
+			ret += snprintf(buf + ret, PAGE_SIZE - ret,
+					"RxClk output from %s",
+					audmux_port_string((ptcr >> 12) & 0x7));
+		else
+			ret += snprintf(buf + ret, PAGE_SIZE - ret,
+					"RxClk input");
+	}
+
+	ret += snprintf(buf + ret, PAGE_SIZE - ret,
+			"\nData received from %s\n",
+			audmux_port_string((pdcr >> 13) & 0x7));
+
+	ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	kfree(buf);
+
+	return ret;
+}
+
+static const struct file_operations audmux_debugfs_fops = {
+	.open = simple_open,
+	.read = audmux_read_file,
+	.llseek = default_llseek,
+};
+
+static void audmux_debugfs_init(void)
+{
+	uintptr_t i;
+	char buf[20];
+
+	audmux_debugfs_root = debugfs_create_dir("audmux", NULL);
+	if (!audmux_debugfs_root) {
+		pr_warning("Failed to create AUDMUX debugfs root\n");
+		return;
+	}
+
+	for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) {
+		snprintf(buf, sizeof(buf), "ssi%lu", i);
+		if (!debugfs_create_file(buf, 0444, audmux_debugfs_root,
+					 (void *)i, &audmux_debugfs_fops))
+			pr_warning("Failed to create AUDMUX port %lu debugfs file\n",
+				   i);
+	}
+}
+
+static void audmux_debugfs_remove(void)
+{
+	debugfs_remove_recursive(audmux_debugfs_root);
+}
+#else
+static inline void audmux_debugfs_init(void)
+{
+}
+
+static inline void audmux_debugfs_remove(void)
+{
+}
+#endif
+
+static enum imx_audmux_type {
+	IMX21_AUDMUX,
+	IMX31_AUDMUX,
+} audmux_type;
+
+static const struct platform_device_id imx_audmux_ids[] = {
+	{
+		.name = "imx21-audmux",
+		.driver_data = IMX21_AUDMUX,
+	}, {
+		.name = "imx31-audmux",
+		.driver_data = IMX31_AUDMUX,
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(platform, imx_audmux_ids);
+
+static const struct of_device_id imx_audmux_dt_ids[] = {
+	{ .compatible = "fsl,imx21-audmux", .data = &imx_audmux_ids[0], },
+	{ .compatible = "fsl,imx31-audmux", .data = &imx_audmux_ids[1], },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_audmux_dt_ids);
+
+static const uint8_t port_mapping[] = {
+	0x0, 0x4, 0x8, 0x10, 0x14, 0x1c,
+};
+
+int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr)
+{
+	if (audmux_type != IMX21_AUDMUX)
+		return -EINVAL;
+
+	if (!audmux_base)
+		return -ENOSYS;
+
+	if (port >= ARRAY_SIZE(port_mapping))
+		return -EINVAL;
+
+	writel(pcr, audmux_base + port_mapping[port]);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(imx_audmux_v1_configure_port);
+
+int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr,
+		unsigned int pdcr)
+{
+	int ret;
+
+	if (audmux_type != IMX31_AUDMUX)
+		return -EINVAL;
+
+	if (!audmux_base)
+		return -ENOSYS;
+
+	if (audmux_clk) {
+		ret = clk_prepare_enable(audmux_clk);
+		if (ret)
+			return ret;
+	}
+
+	writel(ptcr, audmux_base + IMX_AUDMUX_V2_PTCR(port));
+	writel(pdcr, audmux_base + IMX_AUDMUX_V2_PDCR(port));
+
+	if (audmux_clk)
+		clk_disable_unprepare(audmux_clk);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(imx_audmux_v2_configure_port);
+
+static int imx_audmux_parse_dt_defaults(struct platform_device *pdev,
+		struct device_node *of_node)
+{
+	struct device_node *child;
+
+	for_each_available_child_of_node(of_node, child) {
+		unsigned int port;
+		unsigned int ptcr = 0;
+		unsigned int pdcr = 0;
+		unsigned int pcr = 0;
+		unsigned int val;
+		int ret;
+		int i = 0;
+
+		ret = of_property_read_u32(child, "fsl,audmux-port", &port);
+		if (ret) {
+			dev_warn(&pdev->dev, "Failed to get fsl,audmux-port of child node \"%pOF\"\n",
+					child);
+			continue;
+		}
+		if (!of_property_read_bool(child, "fsl,port-config")) {
+			dev_warn(&pdev->dev, "child node \"%pOF\" does not have property fsl,port-config\n",
+					child);
+			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 %pOF\n",
+					i, child);
+			continue;
+		}
+
+		if (audmux_type == IMX31_AUDMUX) {
+			if (i % 2) {
+				dev_err(&pdev->dev, "One pdcr value is missing in child node %pOF\n",
+						child);
+				continue;
+			}
+			imx_audmux_v2_configure_port(port, ptcr, pdcr);
+		} else {
+			imx_audmux_v1_configure_port(port, pcr);
+		}
+	}
+
+	return 0;
+}
+
+static int imx_audmux_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	const struct of_device_id *of_id =
+			of_match_device(imx_audmux_dt_ids, &pdev->dev);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	audmux_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(audmux_base))
+		return PTR_ERR(audmux_base);
+
+	audmux_clk = devm_clk_get(&pdev->dev, "audmux");
+	if (IS_ERR(audmux_clk)) {
+		dev_dbg(&pdev->dev, "cannot get clock: %ld\n",
+				PTR_ERR(audmux_clk));
+		audmux_clk = NULL;
+	}
+
+	if (of_id)
+		pdev->id_entry = of_id->data;
+	audmux_type = pdev->id_entry->driver_data;
+	if (audmux_type == IMX31_AUDMUX)
+		audmux_debugfs_init();
+
+	if (of_id)
+		imx_audmux_parse_dt_defaults(pdev, pdev->dev.of_node);
+
+	return 0;
+}
+
+static int imx_audmux_remove(struct platform_device *pdev)
+{
+	if (audmux_type == IMX31_AUDMUX)
+		audmux_debugfs_remove();
+
+	return 0;
+}
+
+static struct platform_driver imx_audmux_driver = {
+	.probe		= imx_audmux_probe,
+	.remove		= imx_audmux_remove,
+	.id_table	= imx_audmux_ids,
+	.driver	= {
+		.name	= DRIVER_NAME,
+		.of_match_table = imx_audmux_dt_ids,
+	}
+};
+
+static int __init imx_audmux_init(void)
+{
+	return platform_driver_register(&imx_audmux_driver);
+}
+subsys_initcall(imx_audmux_init);
+
+static void __exit imx_audmux_exit(void)
+{
+	platform_driver_unregister(&imx_audmux_driver);
+}
+module_exit(imx_audmux_exit);
+
+MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/sound/soc/fsl/imx-audmux.h b/sound/soc/fsl/imx-audmux.h
new file mode 100644
index 0000000..f75b4d3
--- /dev/null
+++ b/sound/soc/fsl/imx-audmux.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __IMX_AUDMUX_H
+#define __IMX_AUDMUX_H
+
+#include <dt-bindings/sound/fsl-imx-audmux.h>
+
+int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr);
+
+int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr,
+		unsigned int pdcr);
+
+#endif /* __IMX_AUDMUX_H */
diff --git a/sound/soc/fsl/imx-es8328.c b/sound/soc/fsl/imx-es8328.c
new file mode 100644
index 0000000..9953438
--- /dev/null
+++ b/sound/soc/fsl/imx-es8328.c
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012 Linaro Ltd.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "imx-audmux.h"
+
+#define DAI_NAME_SIZE	32
+#define MUX_PORT_MAX	7
+
+struct imx_es8328_data {
+	struct device *dev;
+	struct snd_soc_dai_link dai;
+	struct snd_soc_card card;
+	char codec_dai_name[DAI_NAME_SIZE];
+	char platform_name[DAI_NAME_SIZE];
+	int jack_gpio;
+};
+
+static struct snd_soc_jack_gpio headset_jack_gpios[] = {
+	{
+		.gpio = -1,
+		.name = "headset-gpio",
+		.report = SND_JACK_HEADSET,
+		.invert = 0,
+		.debounce_time = 200,
+	},
+};
+
+static struct snd_soc_jack headset_jack;
+
+static int imx_es8328_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct imx_es8328_data *data = container_of(rtd->card,
+					struct imx_es8328_data, card);
+	int ret = 0;
+
+	/* Headphone jack detection */
+	if (gpio_is_valid(data->jack_gpio)) {
+		ret = snd_soc_card_jack_new(rtd->card, "Headphone",
+					    SND_JACK_HEADPHONE | SND_JACK_BTN_0,
+					    &headset_jack, NULL, 0);
+		if (ret)
+			return ret;
+
+		headset_jack_gpios[0].gpio = data->jack_gpio;
+		ret = snd_soc_jack_add_gpios(&headset_jack,
+					     ARRAY_SIZE(headset_jack_gpios),
+					     headset_jack_gpios);
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_dapm_widget imx_es8328_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+	SND_SOC_DAPM_HP("Headphone", NULL),
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+	SND_SOC_DAPM_REGULATOR_SUPPLY("audio-amp", 1, 0),
+};
+
+static int imx_es8328_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *ssi_np = NULL, *codec_np = NULL;
+	struct platform_device *ssi_pdev;
+	struct imx_es8328_data *data;
+	u32 int_port, ext_port;
+	int ret;
+	struct device *dev = &pdev->dev;
+
+	ret = of_property_read_u32(np, "mux-int-port", &int_port);
+	if (ret) {
+		dev_err(dev, "mux-int-port missing or invalid\n");
+		goto fail;
+	}
+	if (int_port > MUX_PORT_MAX || int_port == 0) {
+		dev_err(dev, "mux-int-port: hardware only has %d mux ports\n",
+			MUX_PORT_MAX);
+		goto fail;
+	}
+
+	ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
+	if (ret) {
+		dev_err(dev, "mux-ext-port missing or invalid\n");
+		goto fail;
+	}
+	if (ext_port > MUX_PORT_MAX || ext_port == 0) {
+		dev_err(dev, "mux-ext-port: hardware only has %d mux ports\n",
+			MUX_PORT_MAX);
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	/*
+	 * The port numbering in the hardware manual starts at 1, while
+	 * the audmux API expects it starts at 0.
+	 */
+	int_port--;
+	ext_port--;
+	ret = imx_audmux_v2_configure_port(int_port,
+			IMX_AUDMUX_V2_PTCR_SYN |
+			IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+			IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+			IMX_AUDMUX_V2_PTCR_TFSDIR |
+			IMX_AUDMUX_V2_PTCR_TCLKDIR,
+			IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
+	if (ret) {
+		dev_err(dev, "audmux internal port setup failed\n");
+		return ret;
+	}
+	ret = imx_audmux_v2_configure_port(ext_port,
+			IMX_AUDMUX_V2_PTCR_SYN,
+			IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
+	if (ret) {
+		dev_err(dev, "audmux external port setup failed\n");
+		return ret;
+	}
+
+	ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
+	codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+	if (!ssi_np || !codec_np) {
+		dev_err(dev, "phandle missing or invalid\n");
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	ssi_pdev = of_find_device_by_node(ssi_np);
+	if (!ssi_pdev) {
+		dev_err(dev, "failed to find SSI platform device\n");
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	data->dev = dev;
+
+	data->jack_gpio = of_get_named_gpio(pdev->dev.of_node, "jack-gpio", 0);
+
+	data->dai.name = "hifi";
+	data->dai.stream_name = "hifi";
+	data->dai.codec_dai_name = "es8328-hifi-analog";
+	data->dai.codec_of_node = codec_np;
+	data->dai.cpu_of_node = ssi_np;
+	data->dai.platform_of_node = ssi_np;
+	data->dai.init = &imx_es8328_dai_init;
+	data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			    SND_SOC_DAIFMT_CBM_CFM;
+
+	data->card.dev = dev;
+	data->card.dapm_widgets = imx_es8328_dapm_widgets;
+	data->card.num_dapm_widgets = ARRAY_SIZE(imx_es8328_dapm_widgets);
+	ret = snd_soc_of_parse_card_name(&data->card, "model");
+	if (ret) {
+		dev_err(dev, "Unable to parse card name\n");
+		goto fail;
+	}
+	ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
+	if (ret) {
+		dev_err(dev, "Unable to parse routing: %d\n", ret);
+		goto fail;
+	}
+	data->card.num_links = 1;
+	data->card.owner = THIS_MODULE;
+	data->card.dai_link = &data->dai;
+
+	ret = snd_soc_register_card(&data->card);
+	if (ret) {
+		dev_err(dev, "Unable to register: %d\n", ret);
+		goto fail;
+	}
+
+	platform_set_drvdata(pdev, data);
+fail:
+	of_node_put(ssi_np);
+	of_node_put(codec_np);
+
+	return ret;
+}
+
+static int imx_es8328_remove(struct platform_device *pdev)
+{
+	struct imx_es8328_data *data = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_card(&data->card);
+
+	return 0;
+}
+
+static const struct of_device_id imx_es8328_dt_ids[] = {
+	{ .compatible = "fsl,imx-audio-es8328", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_es8328_dt_ids);
+
+static struct platform_driver imx_es8328_driver = {
+	.driver = {
+		.name = "imx-es8328",
+		.of_match_table = imx_es8328_dt_ids,
+	},
+	.probe = imx_es8328_probe,
+	.remove = imx_es8328_remove,
+};
+module_platform_driver(imx_es8328_driver);
+
+MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
+MODULE_DESCRIPTION("Kosagi i.MX6 ES8328 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-audio-es8328");
diff --git a/sound/soc/fsl/imx-mc13783.c b/sound/soc/fsl/imx-mc13783.c
new file mode 100644
index 0000000..9d19b80
--- /dev/null
+++ b/sound/soc/fsl/imx-mc13783.c
@@ -0,0 +1,168 @@
+/*
+ * imx-mc13783.c  --  SoC audio for imx based boards with mc13783 codec
+ *
+ * Copyright 2012 Philippe Retornaz, <philippe.retornaz@epfl.ch>
+ *
+ * Heavly based on phycore-mc13783:
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+
+#include "../codecs/mc13783.h"
+#include "imx-ssi.h"
+#include "imx-audmux.h"
+
+#define FMT_SSI (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | \
+		SND_SOC_DAIFMT_CBM_CFM)
+
+static int imx_mc13783_hifi_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	int ret;
+
+	ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x3, 0x3, 4, 16);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, MC13783_CLK_CLIA, 26000000, 0);
+	if (ret)
+		return ret;
+
+	return snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 16);
+}
+
+static const struct snd_soc_ops imx_mc13783_hifi_ops = {
+	.hw_params = imx_mc13783_hifi_hw_params,
+};
+
+static struct snd_soc_dai_link imx_mc13783_dai_mc13783[] = {
+	{
+		.name = "MC13783",
+		.stream_name	 = "Sound",
+		.codec_dai_name	 = "mc13783-hifi",
+		.codec_name	 = "mc13783-codec",
+		.cpu_dai_name	 = "imx-ssi.0",
+		.platform_name	 = "imx-ssi.0",
+		.ops		 = &imx_mc13783_hifi_ops,
+		.symmetric_rates = 1,
+		.dai_fmt 	 = FMT_SSI,
+	},
+};
+
+static const struct snd_soc_dapm_widget imx_mc13783_widget[] = {
+	SND_SOC_DAPM_MIC("Mic", NULL),
+	SND_SOC_DAPM_HP("Headphone", NULL),
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+};
+
+static const struct snd_soc_dapm_route imx_mc13783_routes[] = {
+	{"Speaker", NULL, "LSP"},
+	{"Headphone", NULL, "HSL"},
+	{"Headphone", NULL, "HSR"},
+
+	{"MC1LIN", NULL, "MC1 Bias"},
+	{"MC2IN", NULL, "MC2 Bias"},
+	{"MC1 Bias", NULL, "Mic"},
+	{"MC2 Bias", NULL, "Mic"},
+};
+
+static struct snd_soc_card imx_mc13783 = {
+	.name		= "imx_mc13783",
+	.owner		= THIS_MODULE,
+	.dai_link	= imx_mc13783_dai_mc13783,
+	.num_links	= ARRAY_SIZE(imx_mc13783_dai_mc13783),
+	.dapm_widgets	= imx_mc13783_widget,
+	.num_dapm_widgets = ARRAY_SIZE(imx_mc13783_widget),
+	.dapm_routes	= imx_mc13783_routes,
+	.num_dapm_routes = ARRAY_SIZE(imx_mc13783_routes),
+};
+
+static int imx_mc13783_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	imx_mc13783.dev = &pdev->dev;
+
+	ret = snd_soc_register_card(&imx_mc13783);
+	if (ret) {
+		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
+			ret);
+		return ret;
+	}
+
+	if (machine_is_mx31_3ds() || machine_is_mx31moboard()) {
+		imx_audmux_v2_configure_port(MX31_AUDMUX_PORT4_SSI_PINS_4,
+			IMX_AUDMUX_V2_PTCR_SYN,
+			IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0) |
+			IMX_AUDMUX_V2_PDCR_MODE(1) |
+			IMX_AUDMUX_V2_PDCR_INMMASK(0xfc));
+		imx_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0,
+			IMX_AUDMUX_V2_PTCR_SYN |
+			IMX_AUDMUX_V2_PTCR_TFSDIR |
+			IMX_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) |
+			IMX_AUDMUX_V2_PTCR_TCLKDIR |
+			IMX_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) |
+			IMX_AUDMUX_V2_PTCR_RFSDIR |
+			IMX_AUDMUX_V2_PTCR_RFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) |
+			IMX_AUDMUX_V2_PTCR_RCLKDIR |
+			IMX_AUDMUX_V2_PTCR_RCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4),
+			IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT4_SSI_PINS_4));
+	} else if (machine_is_mx27_3ds()) {
+		imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
+			IMX_AUDMUX_V1_PCR_SYN |
+			IMX_AUDMUX_V1_PCR_TFSDIR |
+			IMX_AUDMUX_V1_PCR_TCLKDIR |
+			IMX_AUDMUX_V1_PCR_RFSDIR |
+			IMX_AUDMUX_V1_PCR_RCLKDIR |
+			IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) |
+			IMX_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) |
+			IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4)
+		);
+		imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR3_SSI_PINS_4,
+			IMX_AUDMUX_V1_PCR_SYN |
+			IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)
+		);
+	}
+
+	return ret;
+}
+
+static int imx_mc13783_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&imx_mc13783);
+
+	return 0;
+}
+
+static struct platform_driver imx_mc13783_audio_driver = {
+	.driver = {
+		.name = "imx_mc13783",
+	},
+	.probe = imx_mc13783_probe,
+	.remove = imx_mc13783_remove
+};
+
+module_platform_driver(imx_mc13783_audio_driver);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch");
+MODULE_DESCRIPTION("imx with mc13783 codec ALSA SoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx_mc13783");
diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c
new file mode 100644
index 0000000..314814d
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-dma.c
@@ -0,0 +1,57 @@
+/*
+ * imx-pcm-dma-mx2.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+#include <linux/platform_device.h>
+#include <linux/dmaengine.h>
+#include <linux/types.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "imx-pcm.h"
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+	if (!imx_dma_is_general_purpose(chan))
+		return false;
+
+	chan->private = param;
+
+	return true;
+}
+
+static const struct snd_dmaengine_pcm_config imx_dmaengine_pcm_config = {
+	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+	.compat_filter_fn = filter,
+};
+
+int imx_pcm_dma_init(struct platform_device *pdev, size_t size)
+{
+	struct snd_dmaengine_pcm_config *config;
+
+	config = devm_kzalloc(&pdev->dev,
+			sizeof(struct snd_dmaengine_pcm_config), GFP_KERNEL);
+	if (!config)
+		return -ENOMEM;
+	*config = imx_dmaengine_pcm_config;
+
+	return devm_snd_dmaengine_pcm_register(&pdev->dev,
+		config,
+		SND_DMAENGINE_PCM_FLAG_COMPAT);
+}
+EXPORT_SYMBOL_GPL(imx_pcm_dma_init);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-pcm-fiq.c b/sound/soc/fsl/imx-pcm-fiq.c
new file mode 100644
index 0000000..0578f34
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-fiq.c
@@ -0,0 +1,391 @@
+/*
+ * imx-pcm-fiq.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/fiq.h>
+
+#include <linux/platform_data/asoc-imx-ssi.h>
+
+#include "imx-ssi.h"
+#include "imx-pcm.h"
+
+struct imx_pcm_runtime_data {
+	unsigned int period;
+	int periods;
+	unsigned long offset;
+	struct hrtimer hrt;
+	int poll_time_ns;
+	struct snd_pcm_substream *substream;
+	atomic_t playing;
+	atomic_t capturing;
+};
+
+static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
+{
+	struct imx_pcm_runtime_data *iprtd =
+		container_of(hrt, struct imx_pcm_runtime_data, hrt);
+	struct snd_pcm_substream *substream = iprtd->substream;
+	struct pt_regs regs;
+
+	if (!atomic_read(&iprtd->playing) && !atomic_read(&iprtd->capturing))
+		return HRTIMER_NORESTART;
+
+	get_fiq_regs(&regs);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		iprtd->offset = regs.ARM_r8 & 0xffff;
+	else
+		iprtd->offset = regs.ARM_r9 & 0xffff;
+
+	snd_pcm_period_elapsed(substream);
+
+	hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns));
+
+	return HRTIMER_RESTART;
+}
+
+static struct fiq_handler fh = {
+	.name		= DRV_NAME,
+};
+
+static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	iprtd->periods = params_periods(params);
+	iprtd->period = params_period_bytes(params);
+	iprtd->offset = 0;
+	iprtd->poll_time_ns = 1000000000 / params_rate(params) *
+				params_period_size(params);
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+	return 0;
+}
+
+static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+	struct pt_regs regs;
+
+	get_fiq_regs(&regs);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16;
+	else
+		regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16;
+
+	set_fiq_regs(&regs);
+
+	return 0;
+}
+
+static int imx_pcm_fiq;
+
+static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			atomic_set(&iprtd->playing, 1);
+		else
+			atomic_set(&iprtd->capturing, 1);
+		hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns),
+		      HRTIMER_MODE_REL);
+		enable_fiq(imx_pcm_fiq);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			atomic_set(&iprtd->playing, 0);
+		else
+			atomic_set(&iprtd->capturing, 0);
+		if (!atomic_read(&iprtd->playing) &&
+				!atomic_read(&iprtd->capturing))
+			disable_fiq(imx_pcm_fiq);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	return bytes_to_frames(substream->runtime, iprtd->offset);
+}
+
+static const struct snd_pcm_hardware snd_imx_hardware = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_RESUME,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
+	.period_bytes_min = 128,
+	.period_bytes_max = 16 * 1024,
+	.periods_min = 4,
+	.periods_max = 255,
+	.fifo_size = 0,
+};
+
+static int snd_imx_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd;
+	int ret;
+
+	iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+	if (iprtd == NULL)
+		return -ENOMEM;
+	runtime->private_data = iprtd;
+
+	iprtd->substream = substream;
+
+	atomic_set(&iprtd->playing, 0);
+	atomic_set(&iprtd->capturing, 0);
+	hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	iprtd->hrt.function = snd_hrtimer_callback;
+
+	ret = snd_pcm_hw_constraint_integer(substream->runtime,
+			SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0) {
+		kfree(iprtd);
+		return ret;
+	}
+
+	snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+	return 0;
+}
+
+static int snd_imx_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+	hrtimer_cancel(&iprtd->hrt);
+
+	kfree(iprtd);
+
+	return 0;
+}
+
+static int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,
+		struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret;
+
+	ret = dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area,
+			  runtime->dma_addr, runtime->dma_bytes);
+
+	pr_debug("%s: ret: %d %p %pad 0x%08zx\n", __func__, ret,
+			runtime->dma_area,
+			&runtime->dma_addr,
+			runtime->dma_bytes);
+	return ret;
+}
+
+static const struct snd_pcm_ops imx_pcm_ops = {
+	.open		= snd_imx_open,
+	.close		= snd_imx_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= snd_imx_pcm_hw_params,
+	.prepare	= snd_imx_pcm_prepare,
+	.trigger	= snd_imx_pcm_trigger,
+	.pointer	= snd_imx_pcm_pointer,
+	.mmap		= snd_imx_pcm_mmap,
+};
+
+static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	struct snd_dma_buffer *buf = &substream->dma_buffer;
+	size_t size = IMX_SSI_DMABUF_SIZE;
+
+	buf->dev.type = SNDRV_DMA_TYPE_DEV;
+	buf->dev.dev = pcm->card->dev;
+	buf->private_data = NULL;
+	buf->area = dma_alloc_wc(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;
+
+static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_pcm *pcm = rtd->pcm;
+	struct snd_pcm_substream *substream;
+	int ret;
+
+	ret = imx_pcm_new(rtd);
+	if (ret)
+		return ret;
+
+	substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+	if (substream) {
+		struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+		imx_ssi_fiq_tx_buffer = (unsigned long)buf->area;
+	}
+
+	substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+	if (substream) {
+		struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+		imx_ssi_fiq_rx_buffer = (unsigned long)buf->area;
+	}
+
+	set_fiq_handler(&imx_ssi_fiq_start,
+		&imx_ssi_fiq_end - &imx_ssi_fiq_start);
+
+	return 0;
+}
+
+static void imx_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_pcm_substream *substream;
+	struct snd_dma_buffer *buf;
+	int stream;
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (!substream)
+			continue;
+
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		dma_free_wc(pcm->card->dev, buf->bytes, buf->area, buf->addr);
+		buf->area = NULL;
+	}
+}
+
+static void imx_pcm_fiq_free(struct snd_pcm *pcm)
+{
+	mxc_set_irq_fiq(ssi_irq, 0);
+	release_fiq(&fh);
+	imx_pcm_free(pcm);
+}
+
+static const struct snd_soc_component_driver imx_soc_component_fiq = {
+	.ops		= &imx_pcm_ops,
+	.pcm_new	= imx_pcm_fiq_new,
+	.pcm_free	= imx_pcm_fiq_free,
+};
+
+int imx_pcm_fiq_init(struct platform_device *pdev,
+		struct imx_pcm_fiq_params *params)
+{
+	int ret;
+
+	ret = claim_fiq(&fh);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to claim fiq: %d", ret);
+		return ret;
+	}
+
+	mxc_set_irq_fiq(params->irq, 1);
+	ssi_irq = params->irq;
+
+	imx_pcm_fiq = params->irq;
+
+	imx_ssi_fiq_base = (unsigned long)params->base;
+
+	params->dma_params_tx->maxburst = 4;
+	params->dma_params_rx->maxburst = 6;
+
+	ret = devm_snd_soc_register_component(&pdev->dev, &imx_soc_component_fiq,
+					      NULL, 0);
+	if (ret)
+		goto failed_register;
+
+	return 0;
+
+failed_register:
+	mxc_set_irq_fiq(ssi_irq, 0);
+	release_fiq(&fh);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(imx_pcm_fiq_init);
+
+void imx_pcm_fiq_exit(struct platform_device *pdev)
+{
+}
+EXPORT_SYMBOL_GPL(imx_pcm_fiq_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h
new file mode 100644
index 0000000..133c447
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#ifndef _IMX_PCM_H
+#define _IMX_PCM_H
+
+#include <linux/platform_data/dma-imx.h>
+
+/*
+ * Do not change this as the FIQ handler depends on this size
+ */
+#define IMX_SSI_DMABUF_SIZE	(64 * 1024)
+
+#define IMX_DEFAULT_DMABUF_SIZE	(64 * 1024)
+#define IMX_SAI_DMABUF_SIZE	(64 * 1024)
+#define IMX_SPDIF_DMABUF_SIZE	(64 * 1024)
+#define IMX_ESAI_DMABUF_SIZE	(256 * 1024)
+
+static inline void
+imx_pcm_dma_params_init_data(struct imx_dma_data *dma_data,
+	int dma, enum sdma_peripheral_type peripheral_type)
+{
+	dma_data->dma_request = dma;
+	dma_data->priority = DMA_PRIO_HIGH;
+	dma_data->peripheral_type = peripheral_type;
+}
+
+struct imx_pcm_fiq_params {
+	int irq;
+	void __iomem *base;
+
+	/* Pointer to original ssi driver to setup tx rx sizes */
+	struct snd_dmaengine_dai_dma_data *dma_params_rx;
+	struct snd_dmaengine_dai_dma_data *dma_params_tx;
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA)
+int imx_pcm_dma_init(struct platform_device *pdev, size_t size);
+#else
+static inline int imx_pcm_dma_init(struct platform_device *pdev, size_t size)
+{
+	return -ENODEV;
+}
+#endif
+
+#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ)
+int imx_pcm_fiq_init(struct platform_device *pdev,
+		struct imx_pcm_fiq_params *params);
+void imx_pcm_fiq_exit(struct platform_device *pdev);
+#else
+static inline int imx_pcm_fiq_init(struct platform_device *pdev,
+		struct imx_pcm_fiq_params *params)
+{
+	return -ENODEV;
+}
+
+static inline void imx_pcm_fiq_exit(struct platform_device *pdev)
+{
+}
+#endif
+
+#endif /* _IMX_PCM_H */
diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c
new file mode 100644
index 0000000..c29200c
--- /dev/null
+++ b/sound/soc/fsl/imx-sgtl5000.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright 2012 Freescale Semiconductor, Inc.
+// Copyright 2012 Linaro Ltd.
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+
+#include "../codecs/sgtl5000.h"
+#include "imx-audmux.h"
+
+#define DAI_NAME_SIZE	32
+
+struct imx_sgtl5000_data {
+	struct snd_soc_dai_link dai;
+	struct snd_soc_card card;
+	char codec_dai_name[DAI_NAME_SIZE];
+	char platform_name[DAI_NAME_SIZE];
+	struct clk *codec_clk;
+	unsigned int clk_frequency;
+};
+
+static int imx_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(rtd->card);
+	struct device *dev = rtd->card->dev;
+	int ret;
+
+	ret = snd_soc_dai_set_sysclk(rtd->codec_dai, SGTL5000_SYSCLK,
+				     data->clk_frequency, SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(dev, "could not set codec driver clock params\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget imx_sgtl5000_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In Jack", NULL),
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_SPK("Line Out Jack", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+static int imx_sgtl5000_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *ssi_np, *codec_np;
+	struct platform_device *ssi_pdev;
+	struct i2c_client *codec_dev;
+	struct imx_sgtl5000_data *data = NULL;
+	int int_port, ext_port;
+	int ret;
+
+	ret = of_property_read_u32(np, "mux-int-port", &int_port);
+	if (ret) {
+		dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
+		return ret;
+	}
+	ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
+	if (ret) {
+		dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
+		return ret;
+	}
+
+	/*
+	 * The port numbering in the hardware manual starts at 1, while
+	 * the audmux API expects it starts at 0.
+	 */
+	int_port--;
+	ext_port--;
+	ret = imx_audmux_v2_configure_port(int_port,
+			IMX_AUDMUX_V2_PTCR_SYN |
+			IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+			IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+			IMX_AUDMUX_V2_PTCR_TFSDIR |
+			IMX_AUDMUX_V2_PTCR_TCLKDIR,
+			IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
+	if (ret) {
+		dev_err(&pdev->dev, "audmux internal port setup failed\n");
+		return ret;
+	}
+	ret = imx_audmux_v2_configure_port(ext_port,
+			IMX_AUDMUX_V2_PTCR_SYN,
+			IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
+	if (ret) {
+		dev_err(&pdev->dev, "audmux external port setup failed\n");
+		return ret;
+	}
+
+	ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
+	codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+	if (!ssi_np || !codec_np) {
+		dev_err(&pdev->dev, "phandle missing or invalid\n");
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	ssi_pdev = of_find_device_by_node(ssi_np);
+	if (!ssi_pdev) {
+		dev_err(&pdev->dev, "failed to find SSI platform device\n");
+		ret = -EPROBE_DEFER;
+		goto fail;
+	}
+	codec_dev = of_find_i2c_device_by_node(codec_np);
+	if (!codec_dev) {
+		dev_err(&pdev->dev, "failed to find codec platform device\n");
+		return -EPROBE_DEFER;
+	}
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	data->codec_clk = clk_get(&codec_dev->dev, NULL);
+	if (IS_ERR(data->codec_clk)) {
+		ret = PTR_ERR(data->codec_clk);
+		goto fail;
+	}
+
+	data->clk_frequency = clk_get_rate(data->codec_clk);
+
+	data->dai.name = "HiFi";
+	data->dai.stream_name = "HiFi";
+	data->dai.codec_dai_name = "sgtl5000";
+	data->dai.codec_of_node = codec_np;
+	data->dai.cpu_of_node = ssi_np;
+	data->dai.platform_of_node = ssi_np;
+	data->dai.init = &imx_sgtl5000_dai_init;
+	data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			    SND_SOC_DAIFMT_CBM_CFM;
+
+	data->card.dev = &pdev->dev;
+	ret = snd_soc_of_parse_card_name(&data->card, "model");
+	if (ret)
+		goto fail;
+	ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
+	if (ret)
+		goto fail;
+	data->card.num_links = 1;
+	data->card.owner = THIS_MODULE;
+	data->card.dai_link = &data->dai;
+	data->card.dapm_widgets = imx_sgtl5000_dapm_widgets;
+	data->card.num_dapm_widgets = ARRAY_SIZE(imx_sgtl5000_dapm_widgets);
+
+	platform_set_drvdata(pdev, &data->card);
+	snd_soc_card_set_drvdata(&data->card, data);
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+	if (ret) {
+		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+		goto fail;
+	}
+
+	of_node_put(ssi_np);
+	of_node_put(codec_np);
+
+	return 0;
+
+fail:
+	if (data && !IS_ERR(data->codec_clk))
+		clk_put(data->codec_clk);
+	of_node_put(ssi_np);
+	of_node_put(codec_np);
+
+	return ret;
+}
+
+static int imx_sgtl5000_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card);
+
+	clk_put(data->codec_clk);
+
+	return 0;
+}
+
+static const struct of_device_id imx_sgtl5000_dt_ids[] = {
+	{ .compatible = "fsl,imx-audio-sgtl5000", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_sgtl5000_dt_ids);
+
+static struct platform_driver imx_sgtl5000_driver = {
+	.driver = {
+		.name = "imx-sgtl5000",
+		.pm = &snd_soc_pm_ops,
+		.of_match_table = imx_sgtl5000_dt_ids,
+	},
+	.probe = imx_sgtl5000_probe,
+	.remove = imx_sgtl5000_remove,
+};
+module_platform_driver(imx_sgtl5000_driver);
+
+MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
+MODULE_DESCRIPTION("Freescale i.MX SGTL5000 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-sgtl5000");
diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c
new file mode 100644
index 0000000..fb896b2
--- /dev/null
+++ b/sound/soc/fsl/imx-spdif.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+
+struct imx_spdif_data {
+	struct snd_soc_dai_link dai;
+	struct snd_soc_card card;
+};
+
+static int imx_spdif_audio_probe(struct platform_device *pdev)
+{
+	struct device_node *spdif_np, *np = pdev->dev.of_node;
+	struct imx_spdif_data *data;
+	int ret = 0;
+
+	spdif_np = of_parse_phandle(np, "spdif-controller", 0);
+	if (!spdif_np) {
+		dev_err(&pdev->dev, "failed to find spdif-controller\n");
+		ret = -EINVAL;
+		goto end;
+	}
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		ret = -ENOMEM;
+		goto end;
+	}
+
+	data->dai.name = "S/PDIF PCM";
+	data->dai.stream_name = "S/PDIF PCM";
+	data->dai.codec_dai_name = "snd-soc-dummy-dai";
+	data->dai.codec_name = "snd-soc-dummy";
+	data->dai.cpu_of_node = spdif_np;
+	data->dai.platform_of_node = spdif_np;
+	data->dai.playback_only = true;
+	data->dai.capture_only = true;
+
+	if (of_property_read_bool(np, "spdif-out"))
+		data->dai.capture_only = false;
+
+	if (of_property_read_bool(np, "spdif-in"))
+		data->dai.playback_only = false;
+
+	if (data->dai.playback_only && data->dai.capture_only) {
+		dev_err(&pdev->dev, "no enabled S/PDIF DAI link\n");
+		goto end;
+	}
+
+	data->card.dev = &pdev->dev;
+	data->card.dai_link = &data->dai;
+	data->card.num_links = 1;
+	data->card.owner = THIS_MODULE;
+
+	ret = snd_soc_of_parse_card_name(&data->card, "model");
+	if (ret)
+		goto end;
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+	if (ret) {
+		dev_err(&pdev->dev, "snd_soc_register_card failed: %d\n", ret);
+		goto end;
+	}
+
+end:
+	of_node_put(spdif_np);
+
+	return ret;
+}
+
+static const struct of_device_id imx_spdif_dt_ids[] = {
+	{ .compatible = "fsl,imx-audio-spdif", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_spdif_dt_ids);
+
+static struct platform_driver imx_spdif_driver = {
+	.driver = {
+		.name = "imx-spdif",
+		.pm = &snd_soc_pm_ops,
+		.of_match_table = imx_spdif_dt_ids,
+	},
+	.probe = imx_spdif_audio_probe,
+};
+
+module_platform_driver(imx_spdif_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX S/PDIF machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-spdif");
diff --git a/sound/soc/fsl/imx-ssi.c b/sound/soc/fsl/imx-ssi.c
new file mode 100644
index 0000000..0679061
--- /dev/null
+++ b/sound/soc/fsl/imx-ssi.c
@@ -0,0 +1,661 @@
+/*
+ * imx-ssi.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *
+ * The i.MX SSI core has some nasty limitations in AC97 mode. While most
+ * sane processor vendors have a FIFO per AC97 slot, the i.MX has only
+ * one FIFO which combines all valid receive slots. We cannot even select
+ * which slots we want to receive. The WM9712 with which this driver
+ * was developed with always sends GPIO status data in slot 12 which
+ * we receive in our (PCM-) data stream. The only chance we have is to
+ * manually skip this data in the FIQ handler. With sampling rates different
+ * from 48000Hz not every frame has valid receive data, so the ratio
+ * between pcm data and GPIO status data changes. Our FIQ handler is not
+ * able to handle this, hence this driver only works with 48000Hz sampling
+ * rate.
+ * Reading and writing AC97 registers is another challenge. The core
+ * provides us status bits when the read register is updated with *another*
+ * value. When we read the same register two times (and the register still
+ * contains the same value) these status bits are not set. We work
+ * around this by not polling these bits but only wait a fixed delay.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <linux/platform_data/asoc-imx-ssi.h>
+
+#include "imx-ssi.h"
+#include "fsl_utils.h"
+
+#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV)
+
+/*
+ * SSI Network Mode or TDM slots configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+	unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 sccr;
+
+	sccr = readl(ssi->base + SSI_STCCR);
+	sccr &= ~SSI_STCCR_DC_MASK;
+	sccr |= SSI_STCCR_DC(slots - 1);
+	writel(sccr, ssi->base + SSI_STCCR);
+
+	sccr = readl(ssi->base + SSI_SRCCR);
+	sccr &= ~SSI_STCCR_DC_MASK;
+	sccr |= SSI_STCCR_DC(slots - 1);
+	writel(sccr, ssi->base + SSI_SRCCR);
+
+	writel(~tx_mask, ssi->base + SSI_STMSK);
+	writel(~rx_mask, ssi->base + SSI_SRMSK);
+
+	return 0;
+}
+
+/*
+ * SSI DAI format configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 strcr = 0, scr;
+
+	scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET);
+
+	/* DAI mode */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		/* data on rising edge of bclk, frame low 1clk before data */
+		strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSI |
+			SSI_STCR_TEFS;
+		scr |= SSI_SCR_NET;
+		if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) {
+			scr &= ~SSI_I2S_MODE_MASK;
+			scr |= SSI_SCR_I2S_MODE_SLAVE;
+		}
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		/* data on rising edge of bclk, frame high with data */
+		strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		/* data on rising edge of bclk, frame high with data */
+		strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSL;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		/* data on rising edge of bclk, frame high 1clk before data */
+		strcr |= SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSL |
+			SSI_STCR_TEFS;
+		break;
+	}
+
+	/* DAI clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_IB_IF:
+		strcr ^= SSI_STCR_TSCKP | SSI_STCR_TFSI;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		strcr ^= SSI_STCR_TSCKP;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		strcr ^= SSI_STCR_TFSI;
+		break;
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	}
+
+	/* DAI clock master masks */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		break;
+	default:
+		/* Master mode not implemented, needs handling of clocks. */
+		return -EINVAL;
+	}
+
+	strcr |= SSI_STCR_TFEN0;
+
+	if (ssi->flags & IMX_SSI_NET)
+		scr |= SSI_SCR_NET;
+	if (ssi->flags & IMX_SSI_SYN)
+		scr |= SSI_SCR_SYN;
+
+	writel(strcr, ssi->base + SSI_STCR);
+	writel(strcr, ssi->base + SSI_SRCR);
+	writel(scr, ssi->base + SSI_SCR);
+
+	return 0;
+}
+
+/*
+ * SSI system clock configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 scr;
+
+	scr = readl(ssi->base + SSI_SCR);
+
+	switch (clk_id) {
+	case IMX_SSP_SYS_CLK:
+		if (dir == SND_SOC_CLOCK_OUT)
+			scr |= SSI_SCR_SYS_CLK_EN;
+		else
+			scr &= ~SSI_SCR_SYS_CLK_EN;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	writel(scr, ssi->base + SSI_SCR);
+
+	return 0;
+}
+
+/*
+ * SSI Clock dividers
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+				  int div_id, int div)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 stccr, srccr;
+
+	stccr = readl(ssi->base + SSI_STCCR);
+	srccr = readl(ssi->base + SSI_SRCCR);
+
+	switch (div_id) {
+	case IMX_SSI_TX_DIV_2:
+		stccr &= ~SSI_STCCR_DIV2;
+		stccr |= div;
+		break;
+	case IMX_SSI_TX_DIV_PSR:
+		stccr &= ~SSI_STCCR_PSR;
+		stccr |= div;
+		break;
+	case IMX_SSI_TX_DIV_PM:
+		stccr &= ~0xff;
+		stccr |= SSI_STCCR_PM(div);
+		break;
+	case IMX_SSI_RX_DIV_2:
+		stccr &= ~SSI_STCCR_DIV2;
+		stccr |= div;
+		break;
+	case IMX_SSI_RX_DIV_PSR:
+		stccr &= ~SSI_STCCR_PSR;
+		stccr |= div;
+		break;
+	case IMX_SSI_RX_DIV_PM:
+		stccr &= ~0xff;
+		stccr |= SSI_STCCR_PM(div);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	writel(stccr, ssi->base + SSI_STCCR);
+	writel(srccr, ssi->base + SSI_SRCCR);
+
+	return 0;
+}
+
+/*
+ * Should only be called when port is inactive (i.e. SSIEN = 0),
+ * although can be called multiple times by upper layers.
+ */
+static int imx_ssi_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *cpu_dai)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 reg, sccr;
+
+	/* Tx/Rx config */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		reg = SSI_STCCR;
+	else
+		reg = SSI_SRCCR;
+
+	if (ssi->flags & IMX_SSI_SYN)
+		reg = SSI_STCCR;
+
+	sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK;
+
+	/* DAI data (word) size */
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		sccr |= SSI_SRCCR_WL(16);
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		sccr |= SSI_SRCCR_WL(20);
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		sccr |= SSI_SRCCR_WL(24);
+		break;
+	}
+
+	writel(sccr, ssi->base + reg);
+
+	return 0;
+}
+
+static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
+		struct snd_soc_dai *dai)
+{
+	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai);
+	unsigned int sier_bits, sier;
+	unsigned int scr;
+
+	scr = readl(ssi->base + SSI_SCR);
+	sier = readl(ssi->base + SSI_SIER);
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (ssi->flags & IMX_SSI_DMA)
+			sier_bits = SSI_SIER_TDMAE;
+		else
+			sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN;
+	} else {
+		if (ssi->flags & IMX_SSI_DMA)
+			sier_bits = SSI_SIER_RDMAE;
+		else
+			sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN;
+	}
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			scr |= SSI_SCR_TE;
+		else
+			scr |= SSI_SCR_RE;
+		sier |= sier_bits;
+
+		scr |= SSI_SCR_SSIEN;
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			scr &= ~SSI_SCR_TE;
+		else
+			scr &= ~SSI_SCR_RE;
+		sier &= ~sier_bits;
+
+		if (!(scr & (SSI_SCR_TE | SSI_SCR_RE)))
+			scr &= ~SSI_SCR_SSIEN;
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (!(ssi->flags & IMX_SSI_USE_AC97))
+		/* rx/tx are always enabled to access ac97 registers */
+		writel(scr, ssi->base + SSI_SCR);
+
+	writel(sier, ssi->base + SSI_SIER);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
+	.hw_params	= imx_ssi_hw_params,
+	.set_fmt	= imx_ssi_set_dai_fmt,
+	.set_clkdiv	= imx_ssi_set_dai_clkdiv,
+	.set_sysclk	= imx_ssi_set_dai_sysclk,
+	.set_tdm_slot	= imx_ssi_set_dai_tdm_slot,
+	.trigger	= imx_ssi_trigger,
+};
+
+static int imx_ssi_dai_probe(struct snd_soc_dai *dai)
+{
+	struct imx_ssi *ssi = dev_get_drvdata(dai->dev);
+	uint32_t val;
+
+	snd_soc_dai_set_drvdata(dai, ssi);
+
+	val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.maxburst) |
+		SSI_SFCSR_RFWM0(ssi->dma_params_rx.maxburst);
+	writel(val, ssi->base + SSI_SFCSR);
+
+	/* Tx/Rx config */
+	dai->playback_dma_data = &ssi->dma_params_tx;
+	dai->capture_dma_data = &ssi->dma_params_rx;
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver imx_ssi_dai = {
+	.probe = imx_ssi_dai_probe,
+	.playback = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture = {
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops = &imx_ssi_pcm_dai_ops,
+};
+
+static struct snd_soc_dai_driver imx_ac97_dai = {
+	.probe = imx_ssi_dai_probe,
+	.bus_control = true,
+	.playback = {
+		.stream_name = "AC97 Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.capture = {
+		.stream_name = "AC97 Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops = &imx_ssi_pcm_dai_ops,
+};
+
+static const struct snd_soc_component_driver imx_component = {
+	.name		= DRV_NAME,
+};
+
+static void setup_channel_to_ac97(struct imx_ssi *imx_ssi)
+{
+	void __iomem *base = imx_ssi->base;
+
+	writel(0x0, base + SSI_SCR);
+	writel(0x0, base + SSI_STCR);
+	writel(0x0, base + SSI_SRCR);
+
+	writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR);
+
+	writel(SSI_SFCSR_RFWM0(8) |
+		SSI_SFCSR_TFWM0(8) |
+		SSI_SFCSR_RFWM1(8) |
+		SSI_SFCSR_TFWM1(8), base + SSI_SFCSR);
+
+	writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR);
+	writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR);
+
+	writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR);
+	writel(SSI_SOR_WAIT(3), base + SSI_SOR);
+
+	writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN |
+			SSI_SCR_TE | SSI_SCR_RE,
+			base + SSI_SCR);
+
+	writel(SSI_SACNT_DEFAULT, base + SSI_SACNT);
+	writel(0xff, base + SSI_SACCDIS);
+	writel(0x300, base + SSI_SACCEN);
+}
+
+static struct imx_ssi *ac97_ssi;
+
+static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+		unsigned short val)
+{
+	struct imx_ssi *imx_ssi = ac97_ssi;
+	void __iomem *base = imx_ssi->base;
+	unsigned int lreg;
+	unsigned int lval;
+
+	if (reg > 0x7f)
+		return;
+
+	pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+	lreg = reg <<  12;
+	writel(lreg, base + SSI_SACADD);
+
+	lval = val << 4;
+	writel(lval , base + SSI_SACDAT);
+
+	writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT);
+	udelay(100);
+}
+
+static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97,
+		unsigned short reg)
+{
+	struct imx_ssi *imx_ssi = ac97_ssi;
+	void __iomem *base = imx_ssi->base;
+
+	unsigned short val = -1;
+	unsigned int lreg;
+
+	lreg = (reg & 0x7f) <<  12 ;
+	writel(lreg, base + SSI_SACADD);
+	writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT);
+
+	udelay(100);
+
+	val = (readl(base + SSI_SACDAT) >> 4) & 0xffff;
+
+	pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+	return val;
+}
+
+static void imx_ssi_ac97_reset(struct snd_ac97 *ac97)
+{
+	struct imx_ssi *imx_ssi = ac97_ssi;
+
+	if (imx_ssi->ac97_reset)
+		imx_ssi->ac97_reset(ac97);
+	/* First read sometimes fails, do a dummy read */
+	imx_ssi_ac97_read(ac97, 0);
+}
+
+static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	struct imx_ssi *imx_ssi = ac97_ssi;
+
+	if (imx_ssi->ac97_warm_reset)
+		imx_ssi->ac97_warm_reset(ac97);
+
+	/* First read sometimes fails, do a dummy read */
+	imx_ssi_ac97_read(ac97, 0);
+}
+
+static struct snd_ac97_bus_ops imx_ssi_ac97_ops = {
+	.read		= imx_ssi_ac97_read,
+	.write		= imx_ssi_ac97_write,
+	.reset		= imx_ssi_ac97_reset,
+	.warm_reset	= imx_ssi_ac97_warm_reset
+};
+
+static int imx_ssi_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct imx_ssi *ssi;
+	struct imx_ssi_platform_data *pdata = pdev->dev.platform_data;
+	int ret = 0;
+	struct snd_soc_dai_driver *dai;
+
+	ssi = devm_kzalloc(&pdev->dev, sizeof(*ssi), GFP_KERNEL);
+	if (!ssi)
+		return -ENOMEM;
+	dev_set_drvdata(&pdev->dev, ssi);
+
+	if (pdata) {
+		ssi->ac97_reset = pdata->ac97_reset;
+		ssi->ac97_warm_reset = pdata->ac97_warm_reset;
+		ssi->flags = pdata->flags;
+	}
+
+	ssi->irq = platform_get_irq(pdev, 0);
+	if (ssi->irq < 0) {
+		dev_err(&pdev->dev, "Failed to get IRQ: %d\n", ssi->irq);
+		return ssi->irq;
+	}
+
+	ssi->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(ssi->clk)) {
+		ret = PTR_ERR(ssi->clk);
+		dev_err(&pdev->dev, "Cannot get the clock: %d\n",
+			ret);
+		goto failed_clk;
+	}
+	ret = clk_prepare_enable(ssi->clk);
+	if (ret)
+		goto failed_clk;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	ssi->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(ssi->base)) {
+		ret = PTR_ERR(ssi->base);
+		goto failed_register;
+	}
+
+	if (ssi->flags & IMX_SSI_USE_AC97) {
+		if (ac97_ssi) {
+			dev_err(&pdev->dev, "AC'97 SSI already registered\n");
+			ret = -EBUSY;
+			goto failed_register;
+		}
+		ac97_ssi = ssi;
+		setup_channel_to_ac97(ssi);
+		dai = &imx_ac97_dai;
+	} else
+		dai = &imx_ssi_dai;
+
+	writel(0x0, ssi->base + SSI_SIER);
+
+	ssi->dma_params_rx.addr = res->start + SSI_SRX0;
+	ssi->dma_params_tx.addr = res->start + SSI_STX0;
+
+	ssi->dma_params_tx.maxburst = 6;
+	ssi->dma_params_rx.maxburst = 4;
+
+	ssi->dma_params_tx.filter_data = &ssi->filter_data_tx;
+	ssi->dma_params_rx.filter_data = &ssi->filter_data_rx;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
+	if (res) {
+		imx_pcm_dma_params_init_data(&ssi->filter_data_tx, res->start,
+			IMX_DMATYPE_SSI);
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
+	if (res) {
+		imx_pcm_dma_params_init_data(&ssi->filter_data_rx, res->start,
+			IMX_DMATYPE_SSI);
+	}
+
+	platform_set_drvdata(pdev, ssi);
+
+	ret = snd_soc_set_ac97_ops(&imx_ssi_ac97_ops);
+	if (ret != 0) {
+		dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);
+		goto failed_register;
+	}
+
+	ret = snd_soc_register_component(&pdev->dev, &imx_component,
+					 dai, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "register DAI failed\n");
+		goto failed_register;
+	}
+
+	ssi->fiq_params.irq = ssi->irq;
+	ssi->fiq_params.base = ssi->base;
+	ssi->fiq_params.dma_params_rx = &ssi->dma_params_rx;
+	ssi->fiq_params.dma_params_tx = &ssi->dma_params_tx;
+
+	ssi->fiq_init = imx_pcm_fiq_init(pdev, &ssi->fiq_params);
+	ssi->dma_init = imx_pcm_dma_init(pdev, IMX_SSI_DMABUF_SIZE);
+
+	if (ssi->fiq_init && ssi->dma_init) {
+		ret = ssi->fiq_init;
+		goto failed_pcm;
+	}
+
+	return 0;
+
+failed_pcm:
+	snd_soc_unregister_component(&pdev->dev);
+failed_register:
+	clk_disable_unprepare(ssi->clk);
+failed_clk:
+	snd_soc_set_ac97_ops(NULL);
+
+	return ret;
+}
+
+static int imx_ssi_remove(struct platform_device *pdev)
+{
+	struct imx_ssi *ssi = platform_get_drvdata(pdev);
+
+	if (!ssi->fiq_init)
+		imx_pcm_fiq_exit(pdev);
+
+	snd_soc_unregister_component(&pdev->dev);
+
+	if (ssi->flags & IMX_SSI_USE_AC97)
+		ac97_ssi = NULL;
+
+	clk_disable_unprepare(ssi->clk);
+	snd_soc_set_ac97_ops(NULL);
+
+	return 0;
+}
+
+static struct platform_driver imx_ssi_driver = {
+	.probe = imx_ssi_probe,
+	.remove = imx_ssi_remove,
+
+	.driver = {
+		.name = "imx-ssi",
+	},
+};
+
+module_platform_driver(imx_ssi_driver);
+
+/* Module information */
+MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-ssi");
diff --git a/sound/soc/fsl/imx-ssi.h b/sound/soc/fsl/imx-ssi.h
new file mode 100644
index 0000000..be65623
--- /dev/null
+++ b/sound/soc/fsl/imx-ssi.h
@@ -0,0 +1,218 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _IMX_SSI_H
+#define _IMX_SSI_H
+
+#define SSI_STX0	0x00
+#define SSI_STX1	0x04
+#define SSI_SRX0	0x08
+#define SSI_SRX1	0x0c
+
+#define SSI_SCR		0x10
+#define SSI_SCR_CLK_IST		(1 << 9)
+#define SSI_SCR_CLK_IST_SHIFT	9
+#define SSI_SCR_TCH_EN		(1 << 8)
+#define SSI_SCR_SYS_CLK_EN	(1 << 7)
+#define SSI_SCR_I2S_MODE_NORM	(0 << 5)
+#define SSI_SCR_I2S_MODE_MSTR	(1 << 5)
+#define SSI_SCR_I2S_MODE_SLAVE	(2 << 5)
+#define SSI_I2S_MODE_MASK	(3 << 5)
+#define SSI_SCR_SYN		(1 << 4)
+#define SSI_SCR_NET		(1 << 3)
+#define SSI_SCR_RE		(1 << 2)
+#define SSI_SCR_TE		(1 << 1)
+#define SSI_SCR_SSIEN		(1 << 0)
+
+#define SSI_SISR	0x14
+#define SSI_SISR_MASK		((1 << 19) - 1)
+#define SSI_SISR_CMDAU		(1 << 18)
+#define SSI_SISR_CMDDU		(1 << 17)
+#define SSI_SISR_RXT		(1 << 16)
+#define SSI_SISR_RDR1		(1 << 15)
+#define SSI_SISR_RDR0		(1 << 14)
+#define SSI_SISR_TDE1		(1 << 13)
+#define SSI_SISR_TDE0		(1 << 12)
+#define SSI_SISR_ROE1		(1 << 11)
+#define SSI_SISR_ROE0		(1 << 10)
+#define SSI_SISR_TUE1		(1 << 9)
+#define SSI_SISR_TUE0		(1 << 8)
+#define SSI_SISR_TFS		(1 << 7)
+#define SSI_SISR_RFS		(1 << 6)
+#define SSI_SISR_TLS		(1 << 5)
+#define SSI_SISR_RLS		(1 << 4)
+#define SSI_SISR_RFF1		(1 << 3)
+#define SSI_SISR_RFF0		(1 << 2)
+#define SSI_SISR_TFE1		(1 << 1)
+#define SSI_SISR_TFE0		(1 << 0)
+
+#define SSI_SIER	0x18
+#define SSI_SIER_RDMAE		(1 << 22)
+#define SSI_SIER_RIE		(1 << 21)
+#define SSI_SIER_TDMAE		(1 << 20)
+#define SSI_SIER_TIE		(1 << 19)
+#define SSI_SIER_CMDAU_EN	(1 << 18)
+#define SSI_SIER_CMDDU_EN	(1 << 17)
+#define SSI_SIER_RXT_EN		(1 << 16)
+#define SSI_SIER_RDR1_EN	(1 << 15)
+#define SSI_SIER_RDR0_EN	(1 << 14)
+#define SSI_SIER_TDE1_EN	(1 << 13)
+#define SSI_SIER_TDE0_EN	(1 << 12)
+#define SSI_SIER_ROE1_EN	(1 << 11)
+#define SSI_SIER_ROE0_EN	(1 << 10)
+#define SSI_SIER_TUE1_EN	(1 << 9)
+#define SSI_SIER_TUE0_EN	(1 << 8)
+#define SSI_SIER_TFS_EN		(1 << 7)
+#define SSI_SIER_RFS_EN		(1 << 6)
+#define SSI_SIER_TLS_EN		(1 << 5)
+#define SSI_SIER_RLS_EN		(1 << 4)
+#define SSI_SIER_RFF1_EN	(1 << 3)
+#define SSI_SIER_RFF0_EN	(1 << 2)
+#define SSI_SIER_TFE1_EN	(1 << 1)
+#define SSI_SIER_TFE0_EN	(1 << 0)
+
+#define SSI_STCR	0x1c
+#define SSI_STCR_TXBIT0		(1 << 9)
+#define SSI_STCR_TFEN1		(1 << 8)
+#define SSI_STCR_TFEN0		(1 << 7)
+#define SSI_FIFO_ENABLE_0_SHIFT 7
+#define SSI_STCR_TFDIR		(1 << 6)
+#define SSI_STCR_TXDIR		(1 << 5)
+#define SSI_STCR_TSHFD		(1 << 4)
+#define SSI_STCR_TSCKP		(1 << 3)
+#define SSI_STCR_TFSI		(1 << 2)
+#define SSI_STCR_TFSL		(1 << 1)
+#define SSI_STCR_TEFS		(1 << 0)
+
+#define SSI_SRCR	0x20
+#define SSI_SRCR_RXBIT0		(1 << 9)
+#define SSI_SRCR_RFEN1		(1 << 8)
+#define SSI_SRCR_RFEN0		(1 << 7)
+#define SSI_FIFO_ENABLE_0_SHIFT 7
+#define SSI_SRCR_RFDIR		(1 << 6)
+#define SSI_SRCR_RXDIR		(1 << 5)
+#define SSI_SRCR_RSHFD		(1 << 4)
+#define SSI_SRCR_RSCKP		(1 << 3)
+#define SSI_SRCR_RFSI		(1 << 2)
+#define SSI_SRCR_RFSL		(1 << 1)
+#define SSI_SRCR_REFS		(1 << 0)
+
+#define SSI_SRCCR		0x28
+#define SSI_SRCCR_DIV2		(1 << 18)
+#define SSI_SRCCR_PSR		(1 << 17)
+#define SSI_SRCCR_WL(x)		((((x) - 2) >> 1) << 13)
+#define SSI_SRCCR_DC(x)		(((x) & 0x1f) << 8)
+#define SSI_SRCCR_PM(x)		(((x) & 0xff) << 0)
+#define SSI_SRCCR_WL_MASK	(0xf << 13)
+#define SSI_SRCCR_DC_MASK	(0x1f << 8)
+#define SSI_SRCCR_PM_MASK	(0xff << 0)
+
+#define SSI_STCCR		0x24
+#define SSI_STCCR_DIV2		(1 << 18)
+#define SSI_STCCR_PSR		(1 << 17)
+#define SSI_STCCR_WL(x)		((((x) - 2) >> 1) << 13)
+#define SSI_STCCR_DC(x)		(((x) & 0x1f) << 8)
+#define SSI_STCCR_PM(x)		(((x) & 0xff) << 0)
+#define SSI_STCCR_WL_MASK	(0xf << 13)
+#define SSI_STCCR_DC_MASK	(0x1f << 8)
+#define SSI_STCCR_PM_MASK	(0xff << 0)
+
+#define SSI_SFCSR	0x2c
+#define SSI_SFCSR_RFCNT1(x)	(((x) & 0xf) << 28)
+#define SSI_RX_FIFO_1_COUNT_SHIFT 28
+#define SSI_SFCSR_TFCNT1(x)	(((x) & 0xf) << 24)
+#define SSI_TX_FIFO_1_COUNT_SHIFT 24
+#define SSI_SFCSR_RFWM1(x)	(((x) & 0xf) << 20)
+#define SSI_SFCSR_TFWM1(x)	(((x) & 0xf) << 16)
+#define SSI_SFCSR_RFCNT0(x)	(((x) & 0xf) << 12)
+#define SSI_RX_FIFO_0_COUNT_SHIFT 12
+#define SSI_SFCSR_TFCNT0(x)	(((x) & 0xf) <<  8)
+#define SSI_TX_FIFO_0_COUNT_SHIFT 8
+#define SSI_SFCSR_RFWM0(x)	(((x) & 0xf) <<  4)
+#define SSI_SFCSR_TFWM0(x)	(((x) & 0xf) <<  0)
+#define SSI_SFCSR_RFWM0_MASK	(0xf <<  4)
+#define SSI_SFCSR_TFWM0_MASK	(0xf <<  0)
+
+#define SSI_STR		0x30
+#define SSI_STR_TEST		(1 << 15)
+#define SSI_STR_RCK2TCK		(1 << 14)
+#define SSI_STR_RFS2TFS		(1 << 13)
+#define SSI_STR_RXSTATE(x)	(((x) & 0xf) << 8)
+#define SSI_STR_TXD2RXD		(1 <<  7)
+#define SSI_STR_TCK2RCK		(1 <<  6)
+#define SSI_STR_TFS2RFS		(1 <<  5)
+#define SSI_STR_TXSTATE(x)	(((x) & 0xf) << 0)
+
+#define SSI_SOR		0x34
+#define SSI_SOR_CLKOFF		(1 << 6)
+#define SSI_SOR_RX_CLR		(1 << 5)
+#define SSI_SOR_TX_CLR		(1 << 4)
+#define SSI_SOR_INIT		(1 << 3)
+#define SSI_SOR_WAIT(x)		(((x) & 0x3) << 1)
+#define SSI_SOR_WAIT_MASK	(0x3 << 1)
+#define SSI_SOR_SYNRST		(1 << 0)
+
+#define SSI_SACNT	0x38
+#define SSI_SACNT_FRDIV(x)	(((x) & 0x3f) << 5)
+#define SSI_SACNT_WR		(1 << 4)
+#define SSI_SACNT_RD		(1 << 3)
+#define SSI_SACNT_TIF		(1 << 2)
+#define SSI_SACNT_FV		(1 << 1)
+#define SSI_SACNT_AC97EN	(1 << 0)
+
+#define SSI_SACADD	0x3c
+#define SSI_SACDAT	0x40
+#define SSI_SATAG	0x44
+#define SSI_STMSK	0x48
+#define SSI_SRMSK	0x4c
+#define SSI_SACCST	0x50
+#define SSI_SACCEN	0x54
+#define SSI_SACCDIS	0x58
+
+/* SSI clock sources */
+#define IMX_SSP_SYS_CLK		0
+
+/* SSI audio dividers */
+#define IMX_SSI_TX_DIV_2	0
+#define IMX_SSI_TX_DIV_PSR	1
+#define IMX_SSI_TX_DIV_PM	2
+#define IMX_SSI_RX_DIV_2	3
+#define IMX_SSI_RX_DIV_PSR	4
+#define IMX_SSI_RX_DIV_PM	5
+
+#define DRV_NAME "imx-ssi"
+
+#include <linux/dmaengine.h>
+#include <linux/platform_data/dma-imx.h>
+#include <sound/dmaengine_pcm.h>
+#include "imx-pcm.h"
+
+struct imx_ssi {
+	struct platform_device *ac97_dev;
+
+	struct snd_soc_dai *imx_ac97;
+	struct clk *clk;
+	void __iomem *base;
+	int irq;
+	int fiq_enable;
+	unsigned int offset;
+
+	unsigned int flags;
+
+	void (*ac97_reset) (struct snd_ac97 *ac97);
+	void (*ac97_warm_reset)(struct snd_ac97 *ac97);
+
+	struct snd_dmaengine_dai_dma_data dma_params_rx;
+	struct snd_dmaengine_dai_dma_data dma_params_tx;
+	struct imx_dma_data filter_data_tx;
+	struct imx_dma_data filter_data_rx;
+	struct imx_pcm_fiq_params fiq_params;
+
+	int fiq_init;
+	int dma_init;
+};
+
+#endif /* _IMX_SSI_H */
diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c
new file mode 100644
index 0000000..c1a4544
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_dma.c
@@ -0,0 +1,514 @@
+/*
+ * Freescale MPC5200 PSC DMA
+ * ALSA SoC Platform driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ * Copyright (C) 2009 Jon Smirl, Digispeaker
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+
+#include <sound/soc.h>
+
+#include <linux/fsl/bestcomm/bestcomm.h>
+#include <linux/fsl/bestcomm/gen_bd.h>
+#include <asm/mpc52xx_psc.h>
+
+#include "mpc5200_dma.h"
+
+#define DRV_NAME "mpc5200_dma"
+
+/*
+ * Interrupt handlers
+ */
+static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma)
+{
+	struct psc_dma *psc_dma = _psc_dma;
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+	u16 isr;
+
+	isr = in_be16(&regs->mpc52xx_psc_isr);
+
+	/* Playback underrun error */
+	if (psc_dma->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP))
+		psc_dma->stats.underrun_count++;
+
+	/* Capture overrun error */
+	if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR))
+		psc_dma->stats.overrun_count++;
+
+	out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * psc_dma_bcom_enqueue_next_buffer - Enqueue another audio buffer
+ * @s: pointer to stream private data structure
+ *
+ * Enqueues another audio period buffer into the bestcomm queue.
+ *
+ * Note: The routine must only be called when there is space available in
+ * the queue.  Otherwise the enqueue will fail and the audio ring buffer
+ * will get out of sync
+ */
+static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s)
+{
+	struct bcom_bd *bd;
+
+	/* Prepare and enqueue the next buffer descriptor */
+	bd = bcom_prepare_next_buffer(s->bcom_task);
+	bd->status = s->period_bytes;
+	bd->data[0] = s->runtime->dma_addr + (s->period_next * s->period_bytes);
+	bcom_submit_next_buffer(s->bcom_task, NULL);
+
+	/* Update for next period */
+	s->period_next = (s->period_next + 1) % s->runtime->periods;
+}
+
+/* Bestcomm DMA irq handler */
+static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream)
+{
+	struct psc_dma_stream *s = _psc_dma_stream;
+
+	spin_lock(&s->psc_dma->lock);
+	/* For each finished period, dequeue the completed period buffer
+	 * and enqueue a new one in it's place. */
+	while (bcom_buffer_done(s->bcom_task)) {
+		bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
+
+		s->period_current = (s->period_current+1) % s->runtime->periods;
+		s->period_count++;
+
+		psc_dma_bcom_enqueue_next_buffer(s);
+	}
+	spin_unlock(&s->psc_dma->lock);
+
+	/* If the stream is active, then also inform the PCM middle layer
+	 * of the period finished event. */
+	if (s->active)
+		snd_pcm_period_elapsed(s->stream);
+
+	return IRQ_HANDLED;
+}
+
+static int psc_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_set_runtime_buffer(substream, NULL);
+	return 0;
+}
+
+/**
+ * psc_dma_trigger: start and stop the DMA transfer.
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ */
+static int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+	u16 imr;
+	unsigned long flags;
+	int i;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		dev_dbg(psc_dma->dev, "START: stream=%i fbits=%u ps=%u #p=%u\n",
+			substream->pstr->stream, runtime->frame_bits,
+			(int)runtime->period_size, runtime->periods);
+		s->period_bytes = frames_to_bytes(runtime,
+						  runtime->period_size);
+		s->period_next = 0;
+		s->period_current = 0;
+		s->active = 1;
+		s->period_count = 0;
+		s->runtime = runtime;
+
+		/* Fill up the bestcomm bd queue and enable DMA.
+		 * This will begin filling the PSC's fifo.
+		 */
+		spin_lock_irqsave(&psc_dma->lock, flags);
+
+		if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+			bcom_gen_bd_rx_reset(s->bcom_task);
+		else
+			bcom_gen_bd_tx_reset(s->bcom_task);
+
+		for (i = 0; i < runtime->periods; i++)
+			if (!bcom_queue_full(s->bcom_task))
+				psc_dma_bcom_enqueue_next_buffer(s);
+
+		bcom_enable(s->bcom_task);
+		spin_unlock_irqrestore(&psc_dma->lock, flags);
+
+		out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
+
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		dev_dbg(psc_dma->dev, "STOP: stream=%i periods_count=%i\n",
+			substream->pstr->stream, s->period_count);
+		s->active = 0;
+
+		spin_lock_irqsave(&psc_dma->lock, flags);
+		bcom_disable(s->bcom_task);
+		if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+			bcom_gen_bd_rx_reset(s->bcom_task);
+		else
+			bcom_gen_bd_tx_reset(s->bcom_task);
+		spin_unlock_irqrestore(&psc_dma->lock, flags);
+
+		break;
+
+	default:
+		dev_dbg(psc_dma->dev, "unhandled trigger: stream=%i cmd=%i\n",
+			substream->pstr->stream, cmd);
+		return -EINVAL;
+	}
+
+	/* Update interrupt enable settings */
+	imr = 0;
+	if (psc_dma->playback.active)
+		imr |= MPC52xx_PSC_IMR_TXEMP;
+	if (psc_dma->capture.active)
+		imr |= MPC52xx_PSC_IMR_ORERR;
+	out_be16(&regs->isr_imr.imr, psc_dma->imr | imr);
+
+	return 0;
+}
+
+
+/* ---------------------------------------------------------------------
+ * The PSC DMA 'ASoC platform' driver
+ *
+ * Can be referenced by an 'ASoC machine' driver
+ * This driver only deals with the audio bus; it doesn't have any
+ * interaction with the attached codec
+ */
+
+static const struct snd_pcm_hardware psc_dma_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_BATCH,
+	.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |
+		SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
+	.period_bytes_max	= 1024 * 1024,
+	.period_bytes_min	= 32,
+	.periods_min		= 2,
+	.periods_max		= 256,
+	.buffer_bytes_max	= 2 * 1024 * 1024,
+	.fifo_size		= 512,
+};
+
+static int psc_dma_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct psc_dma_stream *s;
+	int rc;
+
+	dev_dbg(psc_dma->dev, "psc_dma_open(substream=%p)\n", substream);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_dma->capture;
+	else
+		s = &psc_dma->playback;
+
+	snd_soc_set_runtime_hwparams(substream, &psc_dma_hardware);
+
+	rc = snd_pcm_hw_constraint_integer(runtime,
+		SNDRV_PCM_HW_PARAM_PERIODS);
+	if (rc < 0) {
+		dev_err(substream->pcm->card->dev, "invalid buffer size\n");
+		return rc;
+	}
+
+	s->stream = substream;
+	return 0;
+}
+
+static int psc_dma_close(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct psc_dma_stream *s;
+
+	dev_dbg(psc_dma->dev, "psc_dma_close(substream=%p)\n", substream);
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_dma->capture;
+	else
+		s = &psc_dma->playback;
+
+	if (!psc_dma->playback.active &&
+	    !psc_dma->capture.active) {
+
+		/* Disable all interrupts and reset the PSC */
+		out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
+		out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */
+	}
+	s->stream = NULL;
+	return 0;
+}
+
+static snd_pcm_uframes_t
+psc_dma_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	struct psc_dma_stream *s;
+	dma_addr_t count;
+
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		s = &psc_dma->capture;
+	else
+		s = &psc_dma->playback;
+
+	count = s->period_current * s->period_bytes;
+
+	return bytes_to_frames(substream->runtime, count);
+}
+
+static int
+psc_dma_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *params)
+{
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+	return 0;
+}
+
+static const struct snd_pcm_ops psc_dma_ops = {
+	.open		= psc_dma_open,
+	.close		= psc_dma_close,
+	.hw_free	= psc_dma_hw_free,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.pointer	= psc_dma_pointer,
+	.trigger	= psc_dma_trigger,
+	.hw_params	= psc_dma_hw_params,
+};
+
+static int psc_dma_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_card *card = rtd->card->snd_card;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct snd_soc_dai *dai = rtd->cpu_dai;
+	struct snd_pcm *pcm = rtd->pcm;
+	size_t size = psc_dma_hardware.buffer_bytes_max;
+	int rc;
+
+	dev_dbg(component->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n",
+		card, dai, pcm);
+
+	rc = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+	if (rc)
+		return rc;
+
+	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+		rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+				size, &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
+		if (rc)
+			goto playback_alloc_err;
+	}
+
+	if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+		rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+				size, &pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer);
+		if (rc)
+			goto capture_alloc_err;
+	}
+
+	return 0;
+
+ capture_alloc_err:
+	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
+		snd_dma_free_pages(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
+
+ playback_alloc_err:
+	dev_err(card->dev, "Cannot allocate buffer(s)\n");
+
+	return -ENOMEM;
+}
+
+static void psc_dma_free(struct snd_pcm *pcm)
+{
+	struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+	struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+	struct snd_pcm_substream *substream;
+	int stream;
+
+	dev_dbg(component->dev, "psc_dma_free(pcm=%p)\n", pcm);
+
+	for (stream = 0; stream < 2; stream++) {
+		substream = pcm->streams[stream].substream;
+		if (substream) {
+			snd_dma_free_pages(&substream->dma_buffer);
+			substream->dma_buffer.area = NULL;
+			substream->dma_buffer.addr = 0;
+		}
+	}
+}
+
+static const struct snd_soc_component_driver mpc5200_audio_dma_component = {
+	.name		= DRV_NAME,
+	.ops		= &psc_dma_ops,
+	.pcm_new	= &psc_dma_new,
+	.pcm_free	= &psc_dma_free,
+};
+
+int mpc5200_audio_dma_create(struct platform_device *op)
+{
+	phys_addr_t fifo;
+	struct psc_dma *psc_dma;
+	struct resource res;
+	int size, irq, rc;
+	const __be32 *prop;
+	void __iomem *regs;
+	int ret;
+
+	/* Fetch the registers and IRQ of the PSC */
+	irq = irq_of_parse_and_map(op->dev.of_node, 0);
+	if (of_address_to_resource(op->dev.of_node, 0, &res)) {
+		dev_err(&op->dev, "Missing reg property\n");
+		return -ENODEV;
+	}
+	regs = ioremap(res.start, resource_size(&res));
+	if (!regs) {
+		dev_err(&op->dev, "Could not map registers\n");
+		return -ENODEV;
+	}
+
+	/* Allocate and initialize the driver private data */
+	psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL);
+	if (!psc_dma) {
+		ret = -ENOMEM;
+		goto out_unmap;
+	}
+
+	/* Get the PSC ID */
+	prop = of_get_property(op->dev.of_node, "cell-index", &size);
+	if (!prop || size < sizeof *prop) {
+		ret = -ENODEV;
+		goto out_free;
+	}
+
+	spin_lock_init(&psc_dma->lock);
+	mutex_init(&psc_dma->mutex);
+	psc_dma->id = be32_to_cpu(*prop);
+	psc_dma->irq = irq;
+	psc_dma->psc_regs = regs;
+	psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs;
+	psc_dma->dev = &op->dev;
+	psc_dma->playback.psc_dma = psc_dma;
+	psc_dma->capture.psc_dma = psc_dma;
+	snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_dma->id);
+
+	/* Find the address of the fifo data registers and setup the
+	 * DMA tasks */
+	fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
+	psc_dma->capture.bcom_task =
+		bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512);
+	psc_dma->playback.bcom_task =
+		bcom_psc_gen_bd_tx_init(psc_dma->id, 10, fifo);
+	if (!psc_dma->capture.bcom_task ||
+	    !psc_dma->playback.bcom_task) {
+		dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
+		ret = -ENODEV;
+		goto out_free;
+	}
+
+	/* Disable all interrupts and reset the PSC */
+	out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
+	 /* reset receiver */
+	out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_RX);
+	 /* reset transmitter */
+	out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_TX);
+	 /* reset error */
+	out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_ERR_STAT);
+	 /* reset mode */
+	out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_SEL_MODE_REG_1);
+
+	/* Set up mode register;
+	 * First write: RxRdy (FIFO Alarm) generates rx FIFO irq
+	 * Second write: register Normal mode for non loopback
+	 */
+	out_8(&psc_dma->psc_regs->mode, 0);
+	out_8(&psc_dma->psc_regs->mode, 0);
+
+	/* Set the TX and RX fifo alarm thresholds */
+	out_be16(&psc_dma->fifo_regs->rfalarm, 0x100);
+	out_8(&psc_dma->fifo_regs->rfcntl, 0x4);
+	out_be16(&psc_dma->fifo_regs->tfalarm, 0x100);
+	out_8(&psc_dma->fifo_regs->tfcntl, 0x7);
+
+	/* Lookup the IRQ numbers */
+	psc_dma->playback.irq =
+		bcom_get_task_irq(psc_dma->playback.bcom_task);
+	psc_dma->capture.irq =
+		bcom_get_task_irq(psc_dma->capture.bcom_task);
+
+	rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED,
+			 "psc-dma-status", psc_dma);
+	rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED,
+			  "psc-dma-capture", &psc_dma->capture);
+	rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED,
+			  "psc-dma-playback", &psc_dma->playback);
+	if (rc) {
+		ret = -ENODEV;
+		goto out_irq;
+	}
+
+	/* Save what we've done so it can be found again later */
+	dev_set_drvdata(&op->dev, psc_dma);
+
+	/* Tell the ASoC OF helpers about it */
+	return devm_snd_soc_register_component(&op->dev,
+					&mpc5200_audio_dma_component, NULL, 0);
+out_irq:
+	free_irq(psc_dma->irq, psc_dma);
+	free_irq(psc_dma->capture.irq, &psc_dma->capture);
+	free_irq(psc_dma->playback.irq, &psc_dma->playback);
+out_free:
+	kfree(psc_dma);
+out_unmap:
+	iounmap(regs);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create);
+
+int mpc5200_audio_dma_destroy(struct platform_device *op)
+{
+	struct psc_dma *psc_dma = dev_get_drvdata(&op->dev);
+
+	dev_dbg(&op->dev, "mpc5200_audio_dma_destroy()\n");
+
+	bcom_gen_bd_rx_release(psc_dma->capture.bcom_task);
+	bcom_gen_bd_tx_release(psc_dma->playback.bcom_task);
+
+	/* Release irqs */
+	free_irq(psc_dma->irq, psc_dma);
+	free_irq(psc_dma->capture.irq, &psc_dma->capture);
+	free_irq(psc_dma->playback.irq, &psc_dma->playback);
+
+	iounmap(psc_dma->psc_regs);
+	kfree(psc_dma);
+	dev_set_drvdata(&op->dev, NULL);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(mpc5200_audio_dma_destroy);
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h
new file mode 100644
index 0000000..d7ee33b
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_dma.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Freescale MPC5200 Audio DMA driver
+ */
+
+#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__
+#define __SOUND_SOC_FSL_MPC5200_DMA_H__
+
+#define PSC_STREAM_NAME_LEN 32
+
+/**
+ * psc_ac97_stream - Data specific to a single stream (playback or capture)
+ * @active:		flag indicating if the stream is active
+ * @psc_dma:		pointer back to parent psc_dma data structure
+ * @bcom_task:		bestcomm task structure
+ * @irq:		irq number for bestcomm task
+ * @period_end:		physical address of end of DMA region
+ * @period_next_pt:	physical address of next DMA buffer to enqueue
+ * @period_bytes:	size of DMA period in bytes
+ * @ac97_slot_bits:	Enable bits for turning on the correct AC97 slot
+ */
+struct psc_dma_stream {
+	struct snd_pcm_runtime *runtime;
+	int active;
+	struct psc_dma *psc_dma;
+	struct bcom_task *bcom_task;
+	int irq;
+	struct snd_pcm_substream *stream;
+	int period_next;
+	int period_current;
+	int period_bytes;
+	int period_count;
+
+	/* AC97 state */
+	u32 ac97_slot_bits;
+};
+
+/**
+ * psc_dma - Private driver data
+ * @name: short name for this device ("PSC0", "PSC1", etc)
+ * @psc_regs: pointer to the PSC's registers
+ * @fifo_regs: pointer to the PSC's FIFO registers
+ * @irq: IRQ of this PSC
+ * @dev: struct device pointer
+ * @dai: the CPU DAI for this device
+ * @sicr: Base value used in serial interface control register; mode is ORed
+ *        with this value.
+ * @playback: Playback stream context data
+ * @capture: Capture stream context data
+ */
+struct psc_dma {
+	char name[32];
+	struct mpc52xx_psc __iomem *psc_regs;
+	struct mpc52xx_psc_fifo __iomem *fifo_regs;
+	unsigned int irq;
+	struct device *dev;
+	spinlock_t lock;
+	struct mutex mutex;
+	u32 sicr;
+	uint sysclk;
+	int imr;
+	int id;
+	unsigned int slots;
+
+	/* per-stream data */
+	struct psc_dma_stream playback;
+	struct psc_dma_stream capture;
+
+	/* Statistics */
+	struct {
+		unsigned long overrun_count;
+		unsigned long underrun_count;
+	} stats;
+};
+
+/* Utility for retrieving psc_dma_stream structure from a substream */
+static inline struct psc_dma_stream *
+to_psc_dma_stream(struct snd_pcm_substream *substream, struct psc_dma *psc_dma)
+{
+	if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
+		return &psc_dma->capture;
+	return &psc_dma->playback;
+}
+
+int mpc5200_audio_dma_create(struct platform_device *op);
+int mpc5200_audio_dma_destroy(struct platform_device *op);
+
+#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */
diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c
new file mode 100644
index 0000000..07ee355
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_psc_ac97.c
@@ -0,0 +1,350 @@
+/*
+ * linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip.
+ *
+ * Copyright (C) 2009 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/time.h>
+#include <asm/delay.h>
+#include <asm/mpc52xx.h>
+#include <asm/mpc52xx_psc.h>
+
+#include "mpc5200_dma.h"
+
+#define DRV_NAME "mpc5200-psc-ac97"
+
+/* ALSA only supports a single AC97 device so static is recommend here */
+static struct psc_dma *psc_dma;
+
+static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+	int status;
+	unsigned int val;
+
+	mutex_lock(&psc_dma->mutex);
+
+	/* Wait for command send status zero = ready */
+	status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
+				MPC52xx_PSC_SR_CMDSEND), 100, 0);
+	if (status == 0) {
+		pr_err("timeout on ac97 bus (rdy)\n");
+		mutex_unlock(&psc_dma->mutex);
+		return -ENODEV;
+	}
+
+	/* Force clear the data valid bit */
+	in_be32(&psc_dma->psc_regs->ac97_data);
+
+	/* Send the read */
+	out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24));
+
+	/* Wait for the answer */
+	status = spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) &
+				MPC52xx_PSC_SR_DATA_VAL), 100, 0);
+	if (status == 0) {
+		pr_err("timeout on ac97 read (val) %x\n",
+				in_be16(&psc_dma->psc_regs->sr_csr.status));
+		mutex_unlock(&psc_dma->mutex);
+		return -ENODEV;
+	}
+	/* Get the data */
+	val = in_be32(&psc_dma->psc_regs->ac97_data);
+	if (((val >> 24) & 0x7f) != reg) {
+		pr_err("reg echo error on ac97 read\n");
+		mutex_unlock(&psc_dma->mutex);
+		return -ENODEV;
+	}
+	val = (val >> 8) & 0xffff;
+
+	mutex_unlock(&psc_dma->mutex);
+	return (unsigned short) val;
+}
+
+static void psc_ac97_write(struct snd_ac97 *ac97,
+				unsigned short reg, unsigned short val)
+{
+	int status;
+
+	mutex_lock(&psc_dma->mutex);
+
+	/* Wait for command status zero = ready */
+	status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
+				MPC52xx_PSC_SR_CMDSEND), 100, 0);
+	if (status == 0) {
+		pr_err("timeout on ac97 bus (write)\n");
+		goto out;
+	}
+	/* Write data */
+	out_be32(&psc_dma->psc_regs->ac97_cmd,
+			((reg & 0x7f) << 24) | (val << 8));
+
+ out:
+	mutex_unlock(&psc_dma->mutex);
+}
+
+static void psc_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+
+	mutex_lock(&psc_dma->mutex);
+
+	out_be32(&regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR);
+	udelay(3);
+	out_be32(&regs->sicr, psc_dma->sicr);
+
+	mutex_unlock(&psc_dma->mutex);
+}
+
+static void psc_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+
+	mutex_lock(&psc_dma->mutex);
+	dev_dbg(psc_dma->dev, "cold reset\n");
+
+	mpc5200_psc_ac97_gpio_reset(psc_dma->id);
+
+	/* Notify the PSC that a reset has occurred */
+	out_be32(&regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB);
+
+	/* Re-enable RX and TX */
+	out_8(&regs->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+
+	mutex_unlock(&psc_dma->mutex);
+
+	usleep_range(1000, 2000);
+	psc_ac97_warm_reset(ac97);
+}
+
+static struct snd_ac97_bus_ops psc_ac97_ops = {
+	.read		= psc_ac97_read,
+	.write		= psc_ac97_write,
+	.reset		= psc_ac97_cold_reset,
+	.warm_reset	= psc_ac97_warm_reset,
+};
+
+static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *cpu_dai)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+	struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
+
+	dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
+		" periods=%i buffer_size=%i  buffer_bytes=%i channels=%i"
+		" rate=%i format=%i\n",
+		__func__, substream, params_period_size(params),
+		params_period_bytes(params), params_periods(params),
+		params_buffer_size(params), params_buffer_bytes(params),
+		params_channels(params), params_rate(params),
+		params_format(params));
+
+	/* Determine the set of enable bits to turn on */
+	s->ac97_slot_bits = (params_channels(params) == 1) ? 0x100 : 0x300;
+	if (substream->pstr->stream != SNDRV_PCM_STREAM_CAPTURE)
+		s->ac97_slot_bits <<= 16;
+	return 0;
+}
+
+static int psc_ac97_hw_digital_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *cpu_dai)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+
+	dev_dbg(psc_dma->dev, "%s(substream=%p)\n", __func__, substream);
+
+	if (params_channels(params) == 1)
+		out_be32(&psc_dma->psc_regs->ac97_slots, 0x01000000);
+	else
+		out_be32(&psc_dma->psc_regs->ac97_slots, 0x03000000);
+
+	return 0;
+}
+
+static int psc_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
+							struct snd_soc_dai *dai)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(dai);
+	struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		dev_dbg(psc_dma->dev, "AC97 START: stream=%i\n",
+			substream->pstr->stream);
+
+		/* Set the slot enable bits */
+		psc_dma->slots |= s->ac97_slot_bits;
+		out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		dev_dbg(psc_dma->dev, "AC97 STOP: stream=%i\n",
+			substream->pstr->stream);
+
+		/* Clear the slot enable bits */
+		psc_dma->slots &= ~(s->ac97_slot_bits);
+		out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
+		break;
+	}
+	return 0;
+}
+
+static int psc_ac97_probe(struct snd_soc_dai *cpu_dai)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+	struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
+
+	/* Go */
+	out_8(&regs->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+	return 0;
+}
+
+/* ---------------------------------------------------------------------
+ * ALSA SoC Bindings
+ *
+ * - Digital Audio Interface (DAI) template
+ * - create/destroy dai hooks
+ */
+
+/**
+ * psc_ac97_dai_template: template CPU Digital Audio Interface
+ */
+static const struct snd_soc_dai_ops psc_ac97_analog_ops = {
+	.hw_params	= psc_ac97_hw_analog_params,
+	.trigger	= psc_ac97_trigger,
+};
+
+static const struct snd_soc_dai_ops psc_ac97_digital_ops = {
+	.hw_params	= psc_ac97_hw_digital_params,
+};
+
+static struct snd_soc_dai_driver psc_ac97_dai[] = {
+{
+	.name = "mpc5200-psc-ac97.0",
+	.bus_control = true,
+	.probe	= psc_ac97_probe,
+	.playback = {
+		.stream_name	= "AC97 Playback",
+		.channels_min   = 1,
+		.channels_max   = 6,
+		.rates          = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_BE,
+	},
+	.capture = {
+		.stream_name	= "AC97 Capture",
+		.channels_min   = 1,
+		.channels_max   = 2,
+		.rates          = SNDRV_PCM_RATE_8000_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_BE,
+	},
+	.ops = &psc_ac97_analog_ops,
+},
+{
+	.name = "mpc5200-psc-ac97.1",
+	.bus_control = true,
+	.playback = {
+		.stream_name	= "AC97 SPDIF",
+		.channels_min   = 1,
+		.channels_max   = 2,
+		.rates          = SNDRV_PCM_RATE_32000 | \
+			SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE,
+	},
+	.ops = &psc_ac97_digital_ops,
+} };
+
+static const struct snd_soc_component_driver psc_ac97_component = {
+	.name		= DRV_NAME,
+};
+
+
+/* ---------------------------------------------------------------------
+ * OF platform bus binding code:
+ * - Probe/remove operations
+ * - OF device match table
+ */
+static int psc_ac97_of_probe(struct platform_device *op)
+{
+	int rc;
+	struct mpc52xx_psc __iomem *regs;
+
+	rc = mpc5200_audio_dma_create(op);
+	if (rc != 0)
+		return rc;
+
+	rc = snd_soc_set_ac97_ops(&psc_ac97_ops);
+	if (rc != 0) {
+		dev_err(&op->dev, "Failed to set AC'97 ops: %d\n", rc);
+		return rc;
+	}
+
+	rc = snd_soc_register_component(&op->dev, &psc_ac97_component,
+					psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai));
+	if (rc != 0) {
+		dev_err(&op->dev, "Failed to register DAI\n");
+		return rc;
+	}
+
+	psc_dma = dev_get_drvdata(&op->dev);
+	regs = psc_dma->psc_regs;
+
+	psc_dma->imr = 0;
+	out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
+
+	/* Configure the serial interface mode to AC97 */
+	psc_dma->sicr = MPC52xx_PSC_SICR_SIM_AC97 | MPC52xx_PSC_SICR_ENAC97;
+	out_be32(&regs->sicr, psc_dma->sicr);
+
+	/* No slots active */
+	out_be32(&regs->ac97_slots, 0x00000000);
+
+	return 0;
+}
+
+static int psc_ac97_of_remove(struct platform_device *op)
+{
+	mpc5200_audio_dma_destroy(op);
+	snd_soc_unregister_component(&op->dev);
+	snd_soc_set_ac97_ops(NULL);
+	return 0;
+}
+
+/* Match table for of_platform binding */
+static const struct of_device_id psc_ac97_match[] = {
+	{ .compatible = "fsl,mpc5200-psc-ac97", },
+	{ .compatible = "fsl,mpc5200b-psc-ac97", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, psc_ac97_match);
+
+static struct platform_driver psc_ac97_driver = {
+	.probe = psc_ac97_of_probe,
+	.remove = psc_ac97_of_remove,
+	.driver = {
+		.name = "mpc5200-psc-ac97",
+		.of_match_table = psc_ac97_match,
+	},
+};
+
+module_platform_driver(psc_ac97_driver);
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION("mpc5200 AC97 module");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c
new file mode 100644
index 0000000..d823294
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_psc_i2s.c
@@ -0,0 +1,241 @@
+/*
+ * Freescale MPC5200 PSC in I2S mode
+ * ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ * Copyright (C) 2009 Jon Smirl, Digispeaker
+ */
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mpc52xx_psc.h>
+
+#include "mpc5200_dma.h"
+
+/**
+ * PSC_I2S_RATES: sample rates supported by the I2S
+ *
+ * This driver currently only supports the PSC running in I2S slave mode,
+ * which means the codec determines the sample rate.  Therefore, we tell
+ * ALSA that we support all rates and let the codec driver decide what rates
+ * are really supported.
+ */
+#define PSC_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS
+
+/**
+ * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
+ */
+#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
+			 SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
+
+static int psc_i2s_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params,
+				 struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	u32 mode;
+
+	dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
+		" periods=%i buffer_size=%i  buffer_bytes=%i\n",
+		__func__, substream, params_period_size(params),
+		params_period_bytes(params), params_periods(params),
+		params_buffer_size(params), params_buffer_bytes(params));
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S8:
+		mode = MPC52xx_PSC_SICR_SIM_CODEC_8;
+		break;
+	case SNDRV_PCM_FORMAT_S16_BE:
+		mode = MPC52xx_PSC_SICR_SIM_CODEC_16;
+		break;
+	case SNDRV_PCM_FORMAT_S24_BE:
+		mode = MPC52xx_PSC_SICR_SIM_CODEC_24;
+		break;
+	case SNDRV_PCM_FORMAT_S32_BE:
+		mode = MPC52xx_PSC_SICR_SIM_CODEC_32;
+		break;
+	default:
+		dev_dbg(psc_dma->dev, "invalid format\n");
+		return -EINVAL;
+	}
+	out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode);
+
+	return 0;
+}
+
+/**
+ * psc_i2s_set_sysclk: set the clock frequency and direction
+ *
+ * This function is called by the machine driver to tell us what the clock
+ * frequency and direction are.
+ *
+ * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
+ * and we don't care about the frequency.  Return an error if the direction
+ * is not SND_SOC_CLOCK_IN.
+ *
+ * @clk_id: reserved, should be zero
+ * @freq: the frequency of the given clock ID, currently ignored
+ * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
+ */
+static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+			      int clk_id, unsigned int freq, int dir)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+	dev_dbg(psc_dma->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n",
+				cpu_dai, dir);
+	return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
+}
+
+/**
+ * psc_i2s_set_fmt: set the serial format.
+ *
+ * This function is called by the machine driver to tell us what serial
+ * format to use.
+ *
+ * This driver only supports I2S mode.  Return an error if the format is
+ * not SND_SOC_DAIFMT_I2S.
+ *
+ * @format: one of SND_SOC_DAIFMT_xxx
+ */
+static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
+{
+	struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
+	dev_dbg(psc_dma->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
+				cpu_dai, format);
+	return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
+}
+
+/* ---------------------------------------------------------------------
+ * ALSA SoC Bindings
+ *
+ * - Digital Audio Interface (DAI) template
+ * - create/destroy dai hooks
+ */
+
+/**
+ * psc_i2s_dai_template: template CPU Digital Audio Interface
+ */
+static const struct snd_soc_dai_ops psc_i2s_dai_ops = {
+	.hw_params	= psc_i2s_hw_params,
+	.set_sysclk	= psc_i2s_set_sysclk,
+	.set_fmt	= psc_i2s_set_fmt,
+};
+
+static struct snd_soc_dai_driver psc_i2s_dai[] = {{
+	.name = "mpc5200-psc-i2s.0",
+	.playback = {
+		.stream_name = "I2S Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PSC_I2S_RATES,
+		.formats = PSC_I2S_FORMATS,
+	},
+	.capture = {
+		.stream_name = "I2S Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = PSC_I2S_RATES,
+		.formats = PSC_I2S_FORMATS,
+	},
+	.ops = &psc_i2s_dai_ops,
+} };
+
+static const struct snd_soc_component_driver psc_i2s_component = {
+	.name		= "mpc5200-i2s",
+};
+
+/* ---------------------------------------------------------------------
+ * OF platform bus binding code:
+ * - Probe/remove operations
+ * - OF device match table
+ */
+static int psc_i2s_of_probe(struct platform_device *op)
+{
+	int rc;
+	struct psc_dma *psc_dma;
+	struct mpc52xx_psc __iomem *regs;
+
+	rc = mpc5200_audio_dma_create(op);
+	if (rc != 0)
+		return rc;
+
+	rc = snd_soc_register_component(&op->dev, &psc_i2s_component,
+					psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai));
+	if (rc != 0) {
+		pr_err("Failed to register DAI\n");
+		return rc;
+	}
+
+	psc_dma = dev_get_drvdata(&op->dev);
+	regs = psc_dma->psc_regs;
+
+	/* Configure the serial interface mode; defaulting to CODEC8 mode */
+	psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
+			MPC52xx_PSC_SICR_CLKPOL;
+	out_be32(&psc_dma->psc_regs->sicr,
+		 psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8);
+
+	/* Check for the codec handle.  If it is not present then we
+	 * are done */
+	if (!of_get_property(op->dev.of_node, "codec-handle", NULL))
+		return 0;
+
+	/* Due to errata in the dma mode; need to line up enabling
+	 * the transmitter with a transition on the frame sync
+	 * line */
+
+	/* first make sure it is low */
+	while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) != 0)
+		;
+	/* then wait for the transition to high */
+	while ((in_8(&regs->ipcr_acr.ipcr) & 0x80) == 0)
+		;
+	/* Finally, enable the PSC.
+	 * Receiver must always be enabled; even when we only want
+	 * transmit.  (see 15.3.2.3 of MPC5200B User's Guide) */
+
+	/* Go */
+	out_8(&psc_dma->psc_regs->command,
+			MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+
+	return 0;
+
+}
+
+static int psc_i2s_of_remove(struct platform_device *op)
+{
+	mpc5200_audio_dma_destroy(op);
+	snd_soc_unregister_component(&op->dev);
+	return 0;
+}
+
+/* Match table for of_platform binding */
+static const struct of_device_id psc_i2s_match[] = {
+	{ .compatible = "fsl,mpc5200-psc-i2s", },
+	{ .compatible = "fsl,mpc5200b-psc-i2s", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, psc_i2s_match);
+
+static struct platform_driver psc_i2s_driver = {
+	.probe = psc_i2s_of_probe,
+	.remove = psc_i2s_of_remove,
+	.driver = {
+		.name = "mpc5200-psc-i2s",
+		.of_match_table = psc_i2s_match,
+	},
+};
+
+module_platform_driver(psc_i2s_driver);
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c
new file mode 100644
index 0000000..a639b52
--- /dev/null
+++ b/sound/soc/fsl/mpc8610_hpcd.c
@@ -0,0 +1,432 @@
+/**
+ * Freescale MPC8610HPCD ALSA SoC Machine driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/fsl/guts.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+#include "fsl_utils.h"
+
+/* There's only one global utilities register */
+static phys_addr_t guts_phys;
+
+/**
+ * mpc8610_hpcd_data: machine-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * MPC8610 HPCD.  Some of the data is taken from the device tree.
+ */
+struct mpc8610_hpcd_data {
+	struct snd_soc_dai_link dai[2];
+	struct snd_soc_card card;
+	unsigned int dai_format;
+	unsigned int codec_clk_direction;
+	unsigned int cpu_clk_direction;
+	unsigned int clk_frequency;
+	unsigned int ssi_id;		/* 0 = SSI1, 1 = SSI2, etc */
+	unsigned int dma_id[2];		/* 0 = DMA1, 1 = DMA2, etc */
+	unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
+	char codec_dai_name[DAI_NAME_SIZE];
+	char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
+};
+
+/**
+ * mpc8610_hpcd_machine_probe: initialize the board
+ *
+ * This function is used to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card)
+{
+	struct mpc8610_hpcd_data *machine_data =
+		container_of(card, struct mpc8610_hpcd_data, card);
+	struct ccsr_guts __iomem *guts;
+
+	guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
+	if (!guts) {
+		dev_err(card->dev, "could not map global utilities\n");
+		return -ENOMEM;
+	}
+
+	/* Program the signal routing between the SSI and the DMA */
+	guts_set_dmacr(guts, machine_data->dma_id[0],
+		       machine_data->dma_channel_id[0],
+		       CCSR_GUTS_DMACR_DEV_SSI);
+	guts_set_dmacr(guts, machine_data->dma_id[1],
+		       machine_data->dma_channel_id[1],
+		       CCSR_GUTS_DMACR_DEV_SSI);
+
+	guts_set_pmuxcr_dma(guts, machine_data->dma_id[0],
+			    machine_data->dma_channel_id[0], 0);
+	guts_set_pmuxcr_dma(guts, machine_data->dma_id[1],
+			    machine_data->dma_channel_id[1], 0);
+
+	switch (machine_data->ssi_id) {
+	case 0:
+		clrsetbits_be32(&guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
+		break;
+	case 1:
+		clrsetbits_be32(&guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
+		break;
+	}
+
+	iounmap(guts);
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct mpc8610_hpcd_data *machine_data =
+		container_of(rtd->card, struct mpc8610_hpcd_data, card);
+	struct device *dev = rtd->card->dev;
+	int ret = 0;
+
+	/* Tell the codec driver what the serial protocol is. */
+	ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format);
+	if (ret < 0) {
+		dev_err(dev, "could not set codec driver audio format\n");
+		return ret;
+	}
+
+	/*
+	 * Tell the codec driver what the MCLK frequency is, and whether it's
+	 * a slave or master.
+	 */
+	ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0,
+				     machine_data->clk_frequency,
+				     machine_data->codec_clk_direction);
+	if (ret < 0) {
+		dev_err(dev, "could not set codec driver clock params\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_machine_remove: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI.  We
+ * de-program the DMACR and PMUXCR register.
+ */
+static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card)
+{
+	struct mpc8610_hpcd_data *machine_data =
+		container_of(card, struct mpc8610_hpcd_data, card);
+	struct ccsr_guts __iomem *guts;
+
+	guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
+	if (!guts) {
+		dev_err(card->dev, "could not map global utilities\n");
+		return -ENOMEM;
+	}
+
+	/* Restore the signal routing */
+
+	guts_set_dmacr(guts, machine_data->dma_id[0],
+		       machine_data->dma_channel_id[0], 0);
+	guts_set_dmacr(guts, machine_data->dma_id[1],
+		       machine_data->dma_channel_id[1], 0);
+
+	switch (machine_data->ssi_id) {
+	case 0:
+		clrsetbits_be32(&guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
+		break;
+	case 1:
+		clrsetbits_be32(&guts->pmuxcr,
+			CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
+		break;
+	}
+
+	iounmap(guts);
+
+	return 0;
+}
+
+/**
+ * mpc8610_hpcd_ops: ASoC machine driver operations
+ */
+static const struct snd_soc_ops mpc8610_hpcd_ops = {
+	.startup = mpc8610_hpcd_startup,
+};
+
+/**
+ * mpc8610_hpcd_probe: platform probe function for the machine driver
+ *
+ * Although this is a machine driver, the SSI node is the "master" node with
+ * respect to audio hardware connections.  Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ */
+static int mpc8610_hpcd_probe(struct platform_device *pdev)
+{
+	struct device *dev = pdev->dev.parent;
+	/* ssi_pdev is the platform device for the SSI node that probed us */
+	struct platform_device *ssi_pdev = to_platform_device(dev);
+	struct device_node *np = ssi_pdev->dev.of_node;
+	struct device_node *codec_np = NULL;
+	struct mpc8610_hpcd_data *machine_data;
+	int ret = -ENODEV;
+	const char *sprop;
+	const u32 *iprop;
+
+	/* Find the codec node for this SSI. */
+	codec_np = of_parse_phandle(np, "codec-handle", 0);
+	if (!codec_np) {
+		dev_err(dev, "invalid codec node\n");
+		return -EINVAL;
+	}
+
+	machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
+	if (!machine_data) {
+		ret = -ENOMEM;
+		goto error_alloc;
+	}
+
+	machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
+	machine_data->dai[0].ops = &mpc8610_hpcd_ops;
+
+	/* ASoC core can match codec with device node */
+	machine_data->dai[0].codec_of_node = codec_np;
+
+	/* The DAI name from the codec (snd_soc_dai_driver.name) */
+	machine_data->dai[0].codec_dai_name = "cs4270-hifi";
+
+	/* We register two DAIs per SSI, one for playback and the other for
+	 * capture.  Currently, we only support codecs that have one DAI for
+	 * both playback and capture.
+	 */
+	memcpy(&machine_data->dai[1], &machine_data->dai[0],
+	       sizeof(struct snd_soc_dai_link));
+
+	/* Get the device ID */
+	iprop = of_get_property(np, "cell-index", NULL);
+	if (!iprop) {
+		dev_err(&pdev->dev, "cell-index property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+	machine_data->ssi_id = be32_to_cpup(iprop);
+
+	/* Get the serial format and clock direction. */
+	sprop = of_get_property(np, "fsl,mode", NULL);
+	if (!sprop) {
+		dev_err(&pdev->dev, "fsl,mode property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (strcasecmp(sprop, "i2s-slave") == 0) {
+		machine_data->dai_format =
+			SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+		/* In i2s-slave mode, the codec has its own clock source, so we
+		 * need to get the frequency from the device tree and pass it to
+		 * the codec driver.
+		 */
+		iprop = of_get_property(codec_np, "clock-frequency", NULL);
+		if (!iprop || !*iprop) {
+			dev_err(&pdev->dev, "codec bus-frequency "
+				"property is missing or invalid\n");
+			ret = -EINVAL;
+			goto error;
+		}
+		machine_data->clk_frequency = be32_to_cpup(iprop);
+	} else if (strcasecmp(sprop, "i2s-master") == 0) {
+		machine_data->dai_format =
+			SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "lj-slave") == 0) {
+		machine_data->dai_format =
+			SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "lj-master") == 0) {
+		machine_data->dai_format =
+			SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "rj-slave") == 0) {
+		machine_data->dai_format =
+			SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "rj-master") == 0) {
+		machine_data->dai_format =
+			SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "ac97-slave") == 0) {
+		machine_data->dai_format =
+			SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "ac97-master") == 0) {
+		machine_data->dai_format =
+			SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS;
+		machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
+		machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else {
+		dev_err(&pdev->dev,
+			"unrecognized fsl,mode property '%s'\n", sprop);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (!machine_data->clk_frequency) {
+		dev_err(&pdev->dev, "unknown clock frequency\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* Find the playback DMA channel to use. */
+	machine_data->dai[0].platform_name = machine_data->platform_name[0];
+	ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma",
+				       &machine_data->dai[0],
+				       &machine_data->dma_channel_id[0],
+				       &machine_data->dma_id[0]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
+		goto error;
+	}
+
+	/* Find the capture DMA channel to use. */
+	machine_data->dai[1].platform_name = machine_data->platform_name[1];
+	ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma",
+				       &machine_data->dai[1],
+				       &machine_data->dma_channel_id[1],
+				       &machine_data->dma_id[1]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
+		goto error;
+	}
+
+	/* Initialize our DAI data structure.  */
+	machine_data->dai[0].stream_name = "playback";
+	machine_data->dai[1].stream_name = "capture";
+	machine_data->dai[0].name = machine_data->dai[0].stream_name;
+	machine_data->dai[1].name = machine_data->dai[1].stream_name;
+
+	machine_data->card.probe = mpc8610_hpcd_machine_probe;
+	machine_data->card.remove = mpc8610_hpcd_machine_remove;
+	machine_data->card.name = pdev->name; /* The platform driver name */
+	machine_data->card.owner = THIS_MODULE;
+	machine_data->card.dev = &pdev->dev;
+	machine_data->card.num_links = 2;
+	machine_data->card.dai_link = machine_data->dai;
+
+	/* Register with ASoC */
+	ret = snd_soc_register_card(&machine_data->card);
+	if (ret) {
+		dev_err(&pdev->dev, "could not register card\n");
+		goto error;
+	}
+
+	of_node_put(codec_np);
+
+	return 0;
+
+error:
+	kfree(machine_data);
+error_alloc:
+	of_node_put(codec_np);
+	return ret;
+}
+
+/**
+ * mpc8610_hpcd_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int mpc8610_hpcd_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	struct mpc8610_hpcd_data *machine_data =
+		container_of(card, struct mpc8610_hpcd_data, card);
+
+	snd_soc_unregister_card(card);
+	kfree(machine_data);
+
+	return 0;
+}
+
+static struct platform_driver mpc8610_hpcd_driver = {
+	.probe = mpc8610_hpcd_probe,
+	.remove = mpc8610_hpcd_remove,
+	.driver = {
+		/* The name must match 'compatible' property in the device tree,
+		 * in lowercase letters.
+		 */
+		.name = "snd-soc-mpc8610hpcd",
+	},
+};
+
+/**
+ * mpc8610_hpcd_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init mpc8610_hpcd_init(void)
+{
+	struct device_node *guts_np;
+	struct resource res;
+
+	pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n");
+
+	/* Get the physical address of the global utilities registers */
+	guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
+	if (of_address_to_resource(guts_np, 0, &res)) {
+		pr_err("mpc8610-hpcd: missing/invalid global utilities node\n");
+		return -EINVAL;
+	}
+	guts_phys = res.start;
+
+	return platform_driver_register(&mpc8610_hpcd_driver);
+}
+
+/**
+ * mpc8610_hpcd_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit mpc8610_hpcd_exit(void)
+{
+	platform_driver_unregister(&mpc8610_hpcd_driver);
+}
+
+module_init(mpc8610_hpcd_init);
+module_exit(mpc8610_hpcd_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/mx27vis-aic32x4.c b/sound/soc/fsl/mx27vis-aic32x4.c
new file mode 100644
index 0000000..d7ec3d2
--- /dev/null
+++ b/sound/soc/fsl/mx27vis-aic32x4.c
@@ -0,0 +1,234 @@
+/*
+ * mx27vis-aic32x4.c
+ *
+ * Copyright 2011 Vista Silicon S.L.
+ *
+ * Author: Javier Martin <javier.martin@vista-silicon.com>
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/platform_data/asoc-mx27vis.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <asm/mach-types.h>
+
+#include "../codecs/tlv320aic32x4.h"
+#include "imx-ssi.h"
+#include "imx-audmux.h"
+
+#define MX27VIS_AMP_GAIN	0
+#define MX27VIS_AMP_MUTE	1
+
+static int mx27vis_amp_gain;
+static int mx27vis_amp_mute;
+static int mx27vis_amp_gain0_gpio;
+static int mx27vis_amp_gain1_gpio;
+static int mx27vis_amp_mutel_gpio;
+static int mx27vis_amp_muter_gpio;
+
+static int mx27vis_aic32x4_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+				     25000000, SND_SOC_CLOCK_OUT);
+	if (ret) {
+		pr_err("%s: failed setting codec sysclk\n", __func__);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
+				SND_SOC_CLOCK_IN);
+	if (ret) {
+		pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_ops mx27vis_aic32x4_snd_ops = {
+	.hw_params	= mx27vis_aic32x4_hw_params,
+};
+
+static int mx27vis_amp_set(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	int value = ucontrol->value.integer.value[0];
+	unsigned int reg = mc->reg;
+	int max = mc->max;
+
+	if (value > max)
+		return -EINVAL;
+
+	switch (reg) {
+	case MX27VIS_AMP_GAIN:
+		gpio_set_value(mx27vis_amp_gain0_gpio, value & 1);
+		gpio_set_value(mx27vis_amp_gain1_gpio, value >> 1);
+		mx27vis_amp_gain = value;
+		break;
+	case MX27VIS_AMP_MUTE:
+		gpio_set_value(mx27vis_amp_mutel_gpio, value & 1);
+		gpio_set_value(mx27vis_amp_muter_gpio, value >> 1);
+		mx27vis_amp_mute = value;
+		break;
+	}
+	return 0;
+}
+
+static int mx27vis_amp_get(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	unsigned int reg = mc->reg;
+
+	switch (reg) {
+	case MX27VIS_AMP_GAIN:
+		ucontrol->value.integer.value[0] = mx27vis_amp_gain;
+		break;
+	case MX27VIS_AMP_MUTE:
+		ucontrol->value.integer.value[0] = mx27vis_amp_mute;
+		break;
+	}
+	return 0;
+}
+
+/* From 6dB to 24dB in steps of 6dB */
+static const DECLARE_TLV_DB_SCALE(mx27vis_amp_tlv, 600, 600, 0);
+
+static const struct snd_kcontrol_new mx27vis_aic32x4_controls[] = {
+	SOC_DAPM_PIN_SWITCH("External Mic"),
+	SOC_SINGLE_EXT_TLV("LO Ext Boost", MX27VIS_AMP_GAIN, 0, 3, 0,
+		       mx27vis_amp_get, mx27vis_amp_set, mx27vis_amp_tlv),
+	SOC_DOUBLE_EXT("LO Ext Mute Switch", MX27VIS_AMP_MUTE, 0, 1, 1, 0,
+		       mx27vis_amp_get, mx27vis_amp_set),
+};
+
+static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("External Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = {
+	{"Mic Bias", NULL, "External Mic"},
+	{"IN1_R", NULL, "Mic Bias"},
+	{"IN2_R", NULL, "Mic Bias"},
+	{"IN3_R", NULL, "Mic Bias"},
+	{"IN1_L", NULL, "Mic Bias"},
+	{"IN2_L", NULL, "Mic Bias"},
+	{"IN3_L", NULL, "Mic Bias"},
+};
+
+static struct snd_soc_dai_link mx27vis_aic32x4_dai = {
+	.name		= "tlv320aic32x4",
+	.stream_name	= "TLV320AIC32X4",
+	.codec_dai_name	= "tlv320aic32x4-hifi",
+	.platform_name	= "imx-ssi.0",
+	.codec_name	= "tlv320aic32x4.0-0018",
+	.cpu_dai_name	= "imx-ssi.0",
+	.dai_fmt	= SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBM_CFM,
+	.ops		= &mx27vis_aic32x4_snd_ops,
+};
+
+static struct snd_soc_card mx27vis_aic32x4 = {
+	.name		= "visstrim_m10-audio",
+	.owner		= THIS_MODULE,
+	.dai_link	= &mx27vis_aic32x4_dai,
+	.num_links	= 1,
+	.controls	= mx27vis_aic32x4_controls,
+	.num_controls	= ARRAY_SIZE(mx27vis_aic32x4_controls),
+	.dapm_widgets	= aic32x4_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(aic32x4_dapm_widgets),
+	.dapm_routes	= aic32x4_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(aic32x4_dapm_routes),
+};
+
+static int mx27vis_aic32x4_probe(struct platform_device *pdev)
+{
+	struct snd_mx27vis_platform_data *pdata = pdev->dev.platform_data;
+	int ret;
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "No platform data supplied\n");
+		return -EINVAL;
+	}
+
+	mx27vis_amp_gain0_gpio = pdata->amp_gain0_gpio;
+	mx27vis_amp_gain1_gpio = pdata->amp_gain1_gpio;
+	mx27vis_amp_mutel_gpio = pdata->amp_mutel_gpio;
+	mx27vis_amp_muter_gpio = pdata->amp_muter_gpio;
+
+	mx27vis_aic32x4.dev = &pdev->dev;
+	ret = snd_soc_register_card(&mx27vis_aic32x4);
+	if (ret) {
+		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
+			ret);
+		return ret;
+	}
+
+	/* Connect SSI0 as clock slave to SSI1 external pins */
+	imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
+			IMX_AUDMUX_V1_PCR_SYN |
+			IMX_AUDMUX_V1_PCR_TFSDIR |
+			IMX_AUDMUX_V1_PCR_TCLKDIR |
+			IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) |
+			IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1)
+	);
+	imx_audmux_v1_configure_port(MX27_AUDMUX_PPCR1_SSI_PINS_1,
+			IMX_AUDMUX_V1_PCR_SYN |
+			IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)
+	);
+
+	return ret;
+}
+
+static int mx27vis_aic32x4_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&mx27vis_aic32x4);
+
+	return 0;
+}
+
+static struct platform_driver mx27vis_aic32x4_audio_driver = {
+	.driver = {
+		.name = "mx27vis",
+	},
+	.probe = mx27vis_aic32x4_probe,
+	.remove = mx27vis_aic32x4_remove,
+};
+
+module_platform_driver(mx27vis_aic32x4_audio_driver);
+
+MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>");
+MODULE_DESCRIPTION("ALSA SoC AIC32X4 mx27 visstrim");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mx27vis");
diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c
new file mode 100644
index 0000000..41c623c
--- /dev/null
+++ b/sound/soc/fsl/p1022_ds.c
@@ -0,0 +1,441 @@
+/**
+ * Freescale P1022DS ALSA SoC Machine driver
+ *
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * Copyright 2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/fsl/guts.h>
+#include <linux/interrupt.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+
+#include "fsl_dma.h"
+#include "fsl_ssi.h"
+#include "fsl_utils.h"
+
+/* P1022-specific PMUXCR and DMUXCR bit definitions */
+
+#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK	0x0001c000
+#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI	0x00010000
+#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI		0x00018000
+
+#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK	0x00000c00
+#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI	0x00000000
+
+#define CCSR_GUTS_DMUXCR_PAD	1	/* DMA controller/channel set to pad */
+#define CCSR_GUTS_DMUXCR_SSI	2	/* DMA controller/channel set to SSI */
+
+/*
+ * Set the DMACR register in the GUTS
+ *
+ * The DMACR register determines the source of initiated transfers for each
+ * channel on each DMA controller.  Rather than have a bunch of repetitive
+ * macros for the bit patterns, we just have a function that calculates
+ * them.
+ *
+ * guts: Pointer to GUTS structure
+ * co: The DMA controller (0 or 1)
+ * ch: The channel on the DMA controller (0, 1, 2, or 3)
+ * device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx)
+ */
+static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts,
+	unsigned int co, unsigned int ch, unsigned int device)
+{
+	unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch));
+
+	clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift);
+}
+
+/* There's only one global utilities register */
+static phys_addr_t guts_phys;
+
+/**
+ * machine_data: machine-specific ASoC device data
+ *
+ * This structure contains data for a single sound platform device on an
+ * P1022 DS.  Some of the data is taken from the device tree.
+ */
+struct machine_data {
+	struct snd_soc_dai_link dai[2];
+	struct snd_soc_card card;
+	unsigned int dai_format;
+	unsigned int codec_clk_direction;
+	unsigned int cpu_clk_direction;
+	unsigned int clk_frequency;
+	unsigned int ssi_id;		/* 0 = SSI1, 1 = SSI2, etc */
+	unsigned int dma_id[2];		/* 0 = DMA1, 1 = DMA2, etc */
+	unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
+	char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
+};
+
+/**
+ * p1022_ds_machine_probe: initialize the board
+ *
+ * This function is used to initialize the board-specific hardware.
+ *
+ * Here we program the DMACR and PMUXCR registers.
+ */
+static int p1022_ds_machine_probe(struct snd_soc_card *card)
+{
+	struct machine_data *mdata =
+		container_of(card, struct machine_data, card);
+	struct ccsr_guts __iomem *guts;
+
+	guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
+	if (!guts) {
+		dev_err(card->dev, "could not map global utilities\n");
+		return -ENOMEM;
+	}
+
+	/* Enable SSI Tx signal */
+	clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK,
+			CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI);
+
+	/* Enable SSI Rx signal */
+	clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK,
+			CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI);
+
+	/* Enable DMA Channel for SSI */
+	guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0],
+			CCSR_GUTS_DMUXCR_SSI);
+
+	guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1],
+			CCSR_GUTS_DMUXCR_SSI);
+
+	iounmap(guts);
+
+	return 0;
+}
+
+/**
+ * p1022_ds_startup: program the board with various hardware parameters
+ *
+ * This function takes board-specific information, like clock frequencies
+ * and serial data formats, and passes that information to the codec and
+ * transport drivers.
+ */
+static int p1022_ds_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct machine_data *mdata =
+		container_of(rtd->card, struct machine_data, card);
+	struct device *dev = rtd->card->dev;
+	int ret = 0;
+
+	/* Tell the codec driver what the serial protocol is. */
+	ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format);
+	if (ret < 0) {
+		dev_err(dev, "could not set codec driver audio format\n");
+		return ret;
+	}
+
+	/*
+	 * Tell the codec driver what the MCLK frequency is, and whether it's
+	 * a slave or master.
+	 */
+	ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency,
+				     mdata->codec_clk_direction);
+	if (ret < 0) {
+		dev_err(dev, "could not set codec driver clock params\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * p1022_ds_machine_remove: Remove the sound device
+ *
+ * This function is called to remove the sound device for one SSI.  We
+ * de-program the DMACR and PMUXCR register.
+ */
+static int p1022_ds_machine_remove(struct snd_soc_card *card)
+{
+	struct machine_data *mdata =
+		container_of(card, struct machine_data, card);
+	struct ccsr_guts __iomem *guts;
+
+	guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
+	if (!guts) {
+		dev_err(card->dev, "could not map global utilities\n");
+		return -ENOMEM;
+	}
+
+	/* Restore the signal routing */
+	clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK);
+	clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK);
+	guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0);
+	guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0);
+
+	iounmap(guts);
+
+	return 0;
+}
+
+/**
+ * p1022_ds_ops: ASoC machine driver operations
+ */
+static const struct snd_soc_ops p1022_ds_ops = {
+	.startup = p1022_ds_startup,
+};
+
+/**
+ * p1022_ds_probe: platform probe function for the machine driver
+ *
+ * Although this is a machine driver, the SSI node is the "master" node with
+ * respect to audio hardware connections.  Therefore, we create a new ASoC
+ * device for each new SSI node that has a codec attached.
+ */
+static int p1022_ds_probe(struct platform_device *pdev)
+{
+	struct device *dev = pdev->dev.parent;
+	/* ssi_pdev is the platform device for the SSI node that probed us */
+	struct platform_device *ssi_pdev = to_platform_device(dev);
+	struct device_node *np = ssi_pdev->dev.of_node;
+	struct device_node *codec_np = NULL;
+	struct machine_data *mdata;
+	int ret = -ENODEV;
+	const char *sprop;
+	const u32 *iprop;
+
+	/* Find the codec node for this SSI. */
+	codec_np = of_parse_phandle(np, "codec-handle", 0);
+	if (!codec_np) {
+		dev_err(dev, "could not find codec node\n");
+		return -EINVAL;
+	}
+
+	mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL);
+	if (!mdata) {
+		ret = -ENOMEM;
+		goto error_put;
+	}
+
+	mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
+	mdata->dai[0].ops = &p1022_ds_ops;
+
+	/* ASoC core can match codec with device node */
+	mdata->dai[0].codec_of_node = codec_np;
+
+	/* We register two DAIs per SSI, one for playback and the other for
+	 * capture.  We support codecs that have separate DAIs for both playback
+	 * and capture.
+	 */
+	memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link));
+
+	/* The DAI names from the codec (snd_soc_dai_driver.name) */
+	mdata->dai[0].codec_dai_name = "wm8776-hifi-playback";
+	mdata->dai[1].codec_dai_name = "wm8776-hifi-capture";
+
+	/* Get the device ID */
+	iprop = of_get_property(np, "cell-index", NULL);
+	if (!iprop) {
+		dev_err(&pdev->dev, "cell-index property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+	mdata->ssi_id = be32_to_cpup(iprop);
+
+	/* Get the serial format and clock direction. */
+	sprop = of_get_property(np, "fsl,mode", NULL);
+	if (!sprop) {
+		dev_err(&pdev->dev, "fsl,mode property not found\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (strcasecmp(sprop, "i2s-slave") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+		/* In i2s-slave mode, the codec has its own clock source, so we
+		 * need to get the frequency from the device tree and pass it to
+		 * the codec driver.
+		 */
+		iprop = of_get_property(codec_np, "clock-frequency", NULL);
+		if (!iprop || !*iprop) {
+			dev_err(&pdev->dev, "codec bus-frequency "
+				"property is missing or invalid\n");
+			ret = -EINVAL;
+			goto error;
+		}
+		mdata->clk_frequency = be32_to_cpup(iprop);
+	} else if (strcasecmp(sprop, "i2s-master") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "lj-slave") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "lj-master") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "rj-slave") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "rj-master") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else if (strcasecmp(sprop, "ac97-slave") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+	} else if (strcasecmp(sprop, "ac97-master") == 0) {
+		mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS;
+		mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
+		mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
+	} else {
+		dev_err(&pdev->dev,
+			"unrecognized fsl,mode property '%s'\n", sprop);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	if (!mdata->clk_frequency) {
+		dev_err(&pdev->dev, "unknown clock frequency\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* Find the playback DMA channel to use. */
+	mdata->dai[0].platform_name = mdata->platform_name[0];
+	ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0],
+				       &mdata->dma_channel_id[0],
+				       &mdata->dma_id[0]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
+		goto error;
+	}
+
+	/* Find the capture DMA channel to use. */
+	mdata->dai[1].platform_name = mdata->platform_name[1];
+	ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1],
+				       &mdata->dma_channel_id[1],
+				       &mdata->dma_id[1]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
+		goto error;
+	}
+
+	/* Initialize our DAI data structure.  */
+	mdata->dai[0].stream_name = "playback";
+	mdata->dai[1].stream_name = "capture";
+	mdata->dai[0].name = mdata->dai[0].stream_name;
+	mdata->dai[1].name = mdata->dai[1].stream_name;
+
+	mdata->card.probe = p1022_ds_machine_probe;
+	mdata->card.remove = p1022_ds_machine_remove;
+	mdata->card.name = pdev->name; /* The platform driver name */
+	mdata->card.owner = THIS_MODULE;
+	mdata->card.dev = &pdev->dev;
+	mdata->card.num_links = 2;
+	mdata->card.dai_link = mdata->dai;
+
+	/* Register with ASoC */
+	ret = snd_soc_register_card(&mdata->card);
+	if (ret) {
+		dev_err(&pdev->dev, "could not register card\n");
+		goto error;
+	}
+
+	of_node_put(codec_np);
+
+	return 0;
+
+error:
+	kfree(mdata);
+error_put:
+	of_node_put(codec_np);
+	return ret;
+}
+
+/**
+ * p1022_ds_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int p1022_ds_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	struct machine_data *mdata =
+		container_of(card, struct machine_data, card);
+
+	snd_soc_unregister_card(card);
+	kfree(mdata);
+
+	return 0;
+}
+
+static struct platform_driver p1022_ds_driver = {
+	.probe = p1022_ds_probe,
+	.remove = p1022_ds_remove,
+	.driver = {
+		/*
+		 * The name must match 'compatible' property in the device tree,
+		 * in lowercase letters.
+		 */
+		.name = "snd-soc-p1022ds",
+	},
+};
+
+/**
+ * p1022_ds_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init p1022_ds_init(void)
+{
+	struct device_node *guts_np;
+	struct resource res;
+
+	/* Get the physical address of the global utilities registers */
+	guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
+	if (of_address_to_resource(guts_np, 0, &res)) {
+		pr_err("snd-soc-p1022ds: missing/invalid global utils node\n");
+		of_node_put(guts_np);
+		return -EINVAL;
+	}
+	guts_phys = res.start;
+	of_node_put(guts_np);
+
+	return platform_driver_register(&p1022_ds_driver);
+}
+
+/**
+ * p1022_ds_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit p1022_ds_exit(void)
+{
+	platform_driver_unregister(&p1022_ds_driver);
+}
+
+module_init(p1022_ds_init);
+module_exit(p1022_ds_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c
new file mode 100644
index 0000000..4afbdd6
--- /dev/null
+++ b/sound/soc/fsl/p1022_rdk.c
@@ -0,0 +1,391 @@
+/**
+ * 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/fsl/guts.h>
+#include <linux/interrupt.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <sound/soc.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 const 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 = to_platform_device(dev);
+	struct device_node *np = ssi_pdev->dev.of_node;
+	struct device_node *codec_np = NULL;
+	struct machine_data *mdata;
+	const u32 *iprop;
+	int ret;
+
+	/* Find the codec node for this SSI. */
+	codec_np = of_parse_phandle(np, "codec-handle", 0);
+	if (!codec_np) {
+		dev_err(dev, "could not find codec node\n");
+		return -EINVAL;
+	}
+
+	mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL);
+	if (!mdata) {
+		ret = -ENOMEM;
+		goto error_put;
+	}
+
+	mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
+	mdata->dai[0].ops = &p1022_rdk_ops;
+
+	/* ASoC core can match codec with device node */
+	mdata->dai[0].codec_of_node = codec_np;
+
+	/*
+	 * We register two DAIs per SSI, one for playback and the other for
+	 * capture.  We support codecs that have separate DAIs for both playback
+	 * and capture.
+	 */
+	memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link));
+
+	/* The DAI names from the codec (snd_soc_dai_driver.name) */
+	mdata->dai[0].codec_dai_name = "wm8960-hifi";
+	mdata->dai[1].codec_dai_name = mdata->dai[0].codec_dai_name;
+
+	/*
+	 * Configure the SSI for I2S slave mode.  Older device trees have
+	 * an fsl,mode property, but we ignore that since there's really
+	 * only one way to configure the SSI.
+	 */
+	mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM;
+	mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
+	mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
+
+	/*
+	 * In i2s-slave mode, the codec has its own clock source, so we
+	 * need to get the frequency from the device tree and pass it to
+	 * the codec driver.
+	 */
+	iprop = of_get_property(codec_np, "clock-frequency", NULL);
+	if (!iprop || !*iprop) {
+		dev_err(&pdev->dev, "codec bus-frequency property is missing or invalid\n");
+		ret = -EINVAL;
+		goto error;
+	}
+	mdata->clk_frequency = be32_to_cpup(iprop);
+
+	if (!mdata->clk_frequency) {
+		dev_err(&pdev->dev, "unknown clock frequency\n");
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/* Find the playback DMA channel to use. */
+	mdata->dai[0].platform_name = mdata->platform_name[0];
+	ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0],
+				       &mdata->dma_channel_id[0],
+				       &mdata->dma_id[0]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid playback DMA phandle (ret=%i)\n",
+			ret);
+		goto error;
+	}
+
+	/* Find the capture DMA channel to use. */
+	mdata->dai[1].platform_name = mdata->platform_name[1];
+	ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1],
+				       &mdata->dma_channel_id[1],
+				       &mdata->dma_id[1]);
+	if (ret) {
+		dev_err(&pdev->dev, "missing/invalid capture DMA phandle (ret=%i)\n",
+			ret);
+		goto error;
+	}
+
+	/* Initialize our DAI data structure.  */
+	mdata->dai[0].stream_name = "playback";
+	mdata->dai[1].stream_name = "capture";
+	mdata->dai[0].name = mdata->dai[0].stream_name;
+	mdata->dai[1].name = mdata->dai[1].stream_name;
+
+	mdata->card.probe = p1022_rdk_machine_probe;
+	mdata->card.remove = p1022_rdk_machine_remove;
+	mdata->card.name = pdev->name; /* The platform driver name */
+	mdata->card.owner = THIS_MODULE;
+	mdata->card.dev = &pdev->dev;
+	mdata->card.num_links = 2;
+	mdata->card.dai_link = mdata->dai;
+
+	/* Register with ASoC */
+	ret = snd_soc_register_card(&mdata->card);
+	if (ret) {
+		dev_err(&pdev->dev, "could not register card (ret=%i)\n", ret);
+		goto error;
+	}
+
+	return 0;
+
+error:
+	kfree(mdata);
+error_put:
+	of_node_put(codec_np);
+	return ret;
+}
+
+/**
+ * p1022_rdk_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int p1022_rdk_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	struct machine_data *mdata =
+		container_of(card, struct machine_data, card);
+
+	snd_soc_unregister_card(card);
+	kfree(mdata);
+
+	return 0;
+}
+
+static struct platform_driver p1022_rdk_driver = {
+	.probe = p1022_rdk_probe,
+	.remove = p1022_rdk_remove,
+	.driver = {
+		/*
+		 * The name must match 'compatible' property in the device tree,
+		 * in lowercase letters.
+		 */
+		.name = "snd-soc-p1022rdk",
+	},
+};
+
+/**
+ * p1022_rdk_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init p1022_rdk_init(void)
+{
+	struct device_node *guts_np;
+	struct resource res;
+
+	/* Get the physical address of the global utilities registers */
+	guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
+	if (of_address_to_resource(guts_np, 0, &res)) {
+		pr_err("snd-soc-p1022rdk: missing/invalid global utils node\n");
+		of_node_put(guts_np);
+		return -EINVAL;
+	}
+	guts_phys = res.start;
+	of_node_put(guts_np);
+
+	return platform_driver_register(&p1022_rdk_driver);
+}
+
+/**
+ * p1022_rdk_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit p1022_rdk_exit(void)
+{
+	platform_driver_unregister(&p1022_rdk_driver);
+}
+
+late_initcall(p1022_rdk_init);
+module_exit(p1022_rdk_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale / iVeia P1022 RDK ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c
new file mode 100644
index 0000000..ec73122
--- /dev/null
+++ b/sound/soc/fsl/pcm030-audio-fabric.c
@@ -0,0 +1,137 @@
+/*
+ * Phytec pcm030 driver for the PSC of the Freescale MPC52xx
+ * configured as AC97 interface
+ *
+ * Copyright 2008 Jon Smirl, Digispeaker
+ * Author: Jon Smirl <jonsmirl@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+
+#include <sound/soc.h>
+
+#include "mpc5200_dma.h"
+
+#define DRV_NAME "pcm030-audio-fabric"
+
+struct pcm030_audio_data {
+	struct snd_soc_card *card;
+	struct platform_device *codec_device;
+};
+
+static struct snd_soc_dai_link pcm030_fabric_dai[] = {
+{
+	.name = "AC97.0",
+	.stream_name = "AC97 Analog",
+	.codec_dai_name = "wm9712-hifi",
+	.cpu_dai_name = "mpc5200-psc-ac97.0",
+	.codec_name = "wm9712-codec",
+},
+{
+	.name = "AC97.1",
+	.stream_name = "AC97 IEC958",
+	.codec_dai_name = "wm9712-aux",
+	.cpu_dai_name = "mpc5200-psc-ac97.1",
+	.codec_name = "wm9712-codec",
+},
+};
+
+static struct snd_soc_card pcm030_card = {
+	.name = "pcm030",
+	.owner = THIS_MODULE,
+	.dai_link = pcm030_fabric_dai,
+	.num_links = ARRAY_SIZE(pcm030_fabric_dai),
+};
+
+static int pcm030_fabric_probe(struct platform_device *op)
+{
+	struct device_node *np = op->dev.of_node;
+	struct device_node *platform_np;
+	struct snd_soc_card *card = &pcm030_card;
+	struct pcm030_audio_data *pdata;
+	int ret;
+	int i;
+
+	if (!of_machine_is_compatible("phytec,pcm030"))
+		return -ENODEV;
+
+	pdata = devm_kzalloc(&op->dev, sizeof(struct pcm030_audio_data),
+			     GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	card->dev = &op->dev;
+
+	pdata->card = card;
+
+	platform_np = of_parse_phandle(np, "asoc-platform", 0);
+	if (!platform_np) {
+		dev_err(&op->dev, "ac97 not registered\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; i < card->num_links; i++)
+		card->dai_link[i].platform_of_node = platform_np;
+
+	ret = request_module("snd-soc-wm9712");
+	if (ret)
+		dev_err(&op->dev, "request_module returned: %d\n", ret);
+
+	pdata->codec_device = platform_device_alloc("wm9712-codec", -1);
+	if (!pdata->codec_device)
+		dev_err(&op->dev, "platform_device_alloc() failed\n");
+
+	ret = platform_device_add(pdata->codec_device);
+	if (ret)
+		dev_err(&op->dev, "platform_device_add() failed: %d\n", ret);
+
+	ret = snd_soc_register_card(card);
+	if (ret)
+		dev_err(&op->dev, "snd_soc_register_card() failed: %d\n", ret);
+
+	platform_set_drvdata(op, pdata);
+
+	return ret;
+}
+
+static int pcm030_fabric_remove(struct platform_device *op)
+{
+	struct pcm030_audio_data *pdata = platform_get_drvdata(op);
+	int ret;
+
+	ret = snd_soc_unregister_card(pdata->card);
+	platform_device_unregister(pdata->codec_device);
+
+	return ret;
+}
+
+static const struct of_device_id pcm030_audio_match[] = {
+	{ .compatible = "phytec,pcm030-audio-fabric", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, pcm030_audio_match);
+
+static struct platform_driver pcm030_fabric_driver = {
+	.probe		= pcm030_fabric_probe,
+	.remove		= pcm030_fabric_remove,
+	.driver		= {
+		.name	= DRV_NAME,
+		.of_match_table    = pcm030_audio_match,
+	},
+};
+
+module_platform_driver(pcm030_fabric_driver);
+
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION(DRV_NAME ": mpc5200 pcm030 fabric driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/phycore-ac97.c b/sound/soc/fsl/phycore-ac97.c
new file mode 100644
index 0000000..66fb6c4
--- /dev/null
+++ b/sound/soc/fsl/phycore-ac97.c
@@ -0,0 +1,125 @@
+/*
+ * phycore-ac97.c  --  SoC audio for imx_phycore in AC97 mode
+ *
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <asm/mach-types.h>
+
+#include "imx-audmux.h"
+
+static struct snd_soc_card imx_phycore;
+
+static const struct snd_soc_ops imx_phycore_hifi_ops = {
+};
+
+static struct snd_soc_dai_link imx_phycore_dai_ac97[] = {
+	{
+		.name		= "HiFi",
+		.stream_name	= "HiFi",
+		.codec_dai_name		= "wm9712-hifi",
+		.codec_name	= "wm9712-codec",
+		.cpu_dai_name	= "imx-ssi.0",
+		.platform_name	= "imx-ssi.0",
+		.ops		= &imx_phycore_hifi_ops,
+	},
+};
+
+static struct snd_soc_card imx_phycore = {
+	.name		= "PhyCORE-ac97-audio",
+	.owner		= THIS_MODULE,
+	.dai_link	= imx_phycore_dai_ac97,
+	.num_links	= ARRAY_SIZE(imx_phycore_dai_ac97),
+};
+
+static struct platform_device *imx_phycore_snd_ac97_device;
+static struct platform_device *imx_phycore_snd_device;
+
+static int __init imx_phycore_init(void)
+{
+	int ret;
+
+	if (machine_is_pca100()) {
+		imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
+			IMX_AUDMUX_V1_PCR_SYN | /* 4wire mode */
+			IMX_AUDMUX_V1_PCR_TFCSEL(3) |
+			IMX_AUDMUX_V1_PCR_TCLKDIR | /* clock is output */
+			IMX_AUDMUX_V1_PCR_RXDSEL(3));
+		imx_audmux_v1_configure_port(3,
+			IMX_AUDMUX_V1_PCR_SYN | /* 4wire mode */
+			IMX_AUDMUX_V1_PCR_TFCSEL(0) |
+			IMX_AUDMUX_V1_PCR_TFSDIR |
+			IMX_AUDMUX_V1_PCR_RXDSEL(0));
+	} else if (machine_is_pcm043()) {
+		imx_audmux_v2_configure_port(3,
+			IMX_AUDMUX_V2_PTCR_SYN | /* 4wire mode */
+			IMX_AUDMUX_V2_PTCR_TFSEL(0) |
+			IMX_AUDMUX_V2_PTCR_TFSDIR,
+			IMX_AUDMUX_V2_PDCR_RXDSEL(0));
+		imx_audmux_v2_configure_port(0,
+			IMX_AUDMUX_V2_PTCR_SYN | /* 4wire mode */
+			IMX_AUDMUX_V2_PTCR_TCSEL(3) |
+			IMX_AUDMUX_V2_PTCR_TCLKDIR, /* clock is output */
+			IMX_AUDMUX_V2_PDCR_RXDSEL(3));
+	} else {
+		/* return happy. We might run on a totally different machine */
+		return 0;
+	}
+
+	imx_phycore_snd_ac97_device = platform_device_alloc("soc-audio", -1);
+	if (!imx_phycore_snd_ac97_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(imx_phycore_snd_ac97_device, &imx_phycore);
+	ret = platform_device_add(imx_phycore_snd_ac97_device);
+	if (ret)
+		goto fail1;
+
+	imx_phycore_snd_device = platform_device_alloc("wm9712-codec", -1);
+	if (!imx_phycore_snd_device) {
+		ret = -ENOMEM;
+		goto fail2;
+	}
+	ret = platform_device_add(imx_phycore_snd_device);
+
+	if (ret) {
+		printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+		goto fail3;
+	}
+
+	return 0;
+
+fail3:
+	platform_device_put(imx_phycore_snd_device);
+fail2:
+	platform_device_del(imx_phycore_snd_ac97_device);
+fail1:
+	platform_device_put(imx_phycore_snd_ac97_device);
+	return ret;
+}
+
+static void __exit imx_phycore_exit(void)
+{
+	platform_device_unregister(imx_phycore_snd_device);
+	platform_device_unregister(imx_phycore_snd_ac97_device);
+}
+
+late_initcall(imx_phycore_init);
+module_exit(imx_phycore_exit);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("PhyCORE ALSA SoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/wm1133-ev1.c b/sound/soc/fsl/wm1133-ev1.c
new file mode 100644
index 0000000..2f80b21
--- /dev/null
+++ b/sound/soc/fsl/wm1133-ev1.c
@@ -0,0 +1,292 @@
+/*
+ *  wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS
+ *
+ *  Copyright (c) 2010 Wolfson Microelectronics plc
+ *  Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ *  Based on an earlier driver for the same hardware by Liam Girdwood.
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "imx-ssi.h"
+#include "../codecs/wm8350.h"
+#include "imx-audmux.h"
+
+/* There is a silicon mic on the board optionally connected via a solder pad
+ * SP1.  Define this to enable it.
+ */
+#undef USE_SIMIC
+
+struct _wm8350_audio {
+	unsigned int channels;
+	snd_pcm_format_t format;
+	unsigned int rate;
+	unsigned int sysclk;
+	unsigned int bclkdiv;
+	unsigned int clkdiv;
+	unsigned int lr_rate;
+};
+
+/* in order of power consumption per rate (lowest first) */
+static const struct _wm8350_audio wm8350_audio[] = {
+	/* 16bit mono modes */
+	{1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000 >> 1,
+	 WM8350_BCLK_DIV_48, WM8350_DACDIV_3, 16,},
+
+	/* 16 bit stereo modes */
+	{2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000,
+	 WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000,
+	 WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000,
+	 WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600,
+	 WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600,
+	 WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+
+	/* 24bit stereo modes */
+	{2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+	{2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+	{2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+	{2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+};
+
+static int wm1133_ev1_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = rtd->codec_dai;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int i, found = 0;
+	snd_pcm_format_t format = params_format(params);
+	unsigned int rate = params_rate(params);
+	unsigned int channels = params_channels(params);
+
+	/* find the correct audio parameters */
+	for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) {
+		if (rate == wm8350_audio[i].rate &&
+		    format == wm8350_audio[i].format &&
+		    channels == wm8350_audio[i].channels) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found)
+		return -EINVAL;
+
+	/* codec FLL input is 14.75 MHz from MCLK */
+	snd_soc_dai_set_pll(codec_dai, 0, 0, 14750000, wm8350_audio[i].sysclk);
+
+	/* TODO: The SSI driver should figure this out for us */
+	switch (channels) {
+	case 2:
+		snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 0);
+		break;
+	case 1:
+		snd_soc_dai_set_tdm_slot(cpu_dai, 0x1, 0x1, 1, 0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* set MCLK as the codec system clock for DAC and ADC */
+	snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_MCLK,
+			       wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN);
+
+	/* set codec BCLK division for sample rate */
+	snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV,
+			       wm8350_audio[i].bclkdiv);
+
+	/* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_DACLR_CLKDIV, wm8350_audio[i].lr_rate);
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_ADCLR_CLKDIV, wm8350_audio[i].lr_rate);
+
+	/* now configure DAC and ADC clocks */
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv);
+
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv);
+
+	return 0;
+}
+
+static const struct snd_soc_ops wm1133_ev1_ops = {
+	.hw_params = wm1133_ev1_hw_params,
+};
+
+static const struct snd_soc_dapm_widget wm1133_ev1_widgets[] = {
+#ifdef USE_SIMIC
+	SND_SOC_DAPM_MIC("SiMIC", NULL),
+#endif
+	SND_SOC_DAPM_MIC("Mic1 Jack", NULL),
+	SND_SOC_DAPM_MIC("Mic2 Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In Jack", NULL),
+	SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+/* imx32ads soc_card audio map */
+static const struct snd_soc_dapm_route wm1133_ev1_map[] = {
+
+#ifdef USE_SIMIC
+	/* SiMIC --> IN1LN (with automatic bias) via SP1 */
+	{ "IN1LN", NULL, "Mic Bias" },
+	{ "Mic Bias", NULL, "SiMIC" },
+#endif
+
+	/* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */
+	{ "IN1LN", NULL, "Mic Bias" },
+	{ "IN1LP", NULL, "Mic1 Jack" },
+	{ "Mic Bias", NULL, "Mic1 Jack" },
+
+	/* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */
+	{ "IN1RN", NULL, "Mic Bias" },
+	{ "IN1RP", NULL, "Mic2 Jack" },
+	{ "Mic Bias", NULL, "Mic2 Jack" },
+
+	/* Line in Jack --> AUX (L+R) */
+	{ "IN3R", NULL, "Line In Jack" },
+	{ "IN3L", NULL, "Line In Jack" },
+
+	/* Out1 --> Headphone Jack */
+	{ "Headphone Jack", NULL, "OUT1R" },
+	{ "Headphone Jack", NULL, "OUT1L" },
+
+	/* Out1 --> Line Out Jack */
+	{ "Line Out Jack", NULL, "OUT2R" },
+	{ "Line Out Jack", NULL, "OUT2L" },
+};
+
+static struct snd_soc_jack hp_jack;
+
+static struct snd_soc_jack_pin hp_jack_pins[] = {
+	{ .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE },
+};
+
+static struct snd_soc_jack mic_jack;
+
+static struct snd_soc_jack_pin mic_jack_pins[] = {
+	{ .pin = "Mic1 Jack", .mask = SND_JACK_MICROPHONE },
+	{ .pin = "Mic2 Jack", .mask = SND_JACK_MICROPHONE },
+};
+
+static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = rtd->codec_dai->component;
+
+	/* Headphone jack detection */
+	snd_soc_card_jack_new(rtd->card, "Headphone", SND_JACK_HEADPHONE,
+			      &hp_jack, hp_jack_pins, ARRAY_SIZE(hp_jack_pins));
+	wm8350_hp_jack_detect(component, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE);
+
+	/* Microphone jack detection */
+	snd_soc_card_jack_new(rtd->card, "Microphone",
+			      SND_JACK_MICROPHONE | SND_JACK_BTN_0, &mic_jack,
+			      mic_jack_pins, ARRAY_SIZE(mic_jack_pins));
+	wm8350_mic_jack_detect(component, &mic_jack, SND_JACK_MICROPHONE,
+			       SND_JACK_BTN_0);
+
+	snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias");
+
+	return 0;
+}
+
+
+static struct snd_soc_dai_link wm1133_ev1_dai = {
+	.name = "WM1133-EV1",
+	.stream_name = "Audio",
+	.cpu_dai_name = "imx-ssi.0",
+	.codec_dai_name = "wm8350-hifi",
+	.platform_name = "imx-ssi.0",
+	.codec_name = "wm8350-codec.0-0x1a",
+	.init = wm1133_ev1_init,
+	.ops = &wm1133_ev1_ops,
+	.symmetric_rates = 1,
+	.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+		   SND_SOC_DAIFMT_CBM_CFM,
+};
+
+static struct snd_soc_card wm1133_ev1 = {
+	.name = "WM1133-EV1",
+	.owner = THIS_MODULE,
+	.dai_link = &wm1133_ev1_dai,
+	.num_links = 1,
+
+	.dapm_widgets = wm1133_ev1_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(wm1133_ev1_widgets),
+	.dapm_routes = wm1133_ev1_map,
+	.num_dapm_routes = ARRAY_SIZE(wm1133_ev1_map),
+};
+
+static struct platform_device *wm1133_ev1_snd_device;
+
+static int __init wm1133_ev1_audio_init(void)
+{
+	int ret;
+	unsigned int ptcr, pdcr;
+
+	/* SSI0 mastered by port 5 */
+	ptcr = IMX_AUDMUX_V2_PTCR_SYN |
+		IMX_AUDMUX_V2_PTCR_TFSDIR |
+		IMX_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) |
+		IMX_AUDMUX_V2_PTCR_TCLKDIR |
+		IMX_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5);
+	pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5);
+	imx_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, ptcr, pdcr);
+
+	ptcr = IMX_AUDMUX_V2_PTCR_SYN;
+	pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0);
+	imx_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, ptcr, pdcr);
+
+	wm1133_ev1_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!wm1133_ev1_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(wm1133_ev1_snd_device, &wm1133_ev1);
+	ret = platform_device_add(wm1133_ev1_snd_device);
+
+	if (ret)
+		platform_device_put(wm1133_ev1_snd_device);
+
+	return ret;
+}
+module_init(wm1133_ev1_audio_init);
+
+static void __exit wm1133_ev1_audio_exit(void)
+{
+	platform_device_unregister(wm1133_ev1_snd_device);
+}
+module_exit(wm1133_ev1_audio_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("Audio for WM1133-EV1 on i.MX31ADS");
+MODULE_LICENSE("GPL");