summaryrefslogtreecommitdiffstats
path: root/sound/soc/fsl
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/fsl')
-rw-r--r--sound/soc/fsl/Kconfig373
-rw-r--r--sound/soc/fsl/Makefile83
-rw-r--r--sound/soc/fsl/efika-audio-fabric.c95
-rw-r--r--sound/soc/fsl/eukrea-tlv320.c235
-rw-r--r--sound/soc/fsl/fsl-asoc-card.c928
-rw-r--r--sound/soc/fsl/fsl_asrc.c1409
-rw-r--r--sound/soc/fsl/fsl_asrc.h464
-rw-r--r--sound/soc/fsl/fsl_asrc_common.h108
-rw-r--r--sound/soc/fsl/fsl_asrc_dma.c464
-rw-r--r--sound/soc/fsl/fsl_aud2htx.c314
-rw-r--r--sound/soc/fsl/fsl_aud2htx.h67
-rw-r--r--sound/soc/fsl/fsl_audmix.c573
-rw-r--r--sound/soc/fsl/fsl_audmix.h103
-rw-r--r--sound/soc/fsl/fsl_dma.c923
-rw-r--r--sound/soc/fsl/fsl_dma.h126
-rw-r--r--sound/soc/fsl/fsl_easrc.c2110
-rw-r--r--sound/soc/fsl/fsl_easrc.h651
-rw-r--r--sound/soc/fsl/fsl_esai.c1216
-rw-r--r--sound/soc/fsl/fsl_esai.h351
-rw-r--r--sound/soc/fsl/fsl_micfil.c810
-rw-r--r--sound/soc/fsl/fsl_micfil.h145
-rw-r--r--sound/soc/fsl/fsl_mqs.c371
-rw-r--r--sound/soc/fsl/fsl_rpmsg.c323
-rw-r--r--sound/soc/fsl/fsl_rpmsg.h47
-rw-r--r--sound/soc/fsl/fsl_sai.c1741
-rw-r--r--sound/soc/fsl/fsl_sai.h315
-rw-r--r--sound/soc/fsl/fsl_spdif.c1778
-rw-r--r--sound/soc/fsl/fsl_spdif.h218
-rw-r--r--sound/soc/fsl/fsl_ssi.c1748
-rw-r--r--sound/soc/fsl/fsl_ssi.h324
-rw-r--r--sound/soc/fsl/fsl_ssi_dbg.c140
-rw-r--r--sound/soc/fsl/fsl_utils.c157
-rw-r--r--sound/soc/fsl/fsl_utils.h29
-rw-r--r--sound/soc/fsl/fsl_xcvr.c1388
-rw-r--r--sound/soc/fsl/fsl_xcvr.h266
-rw-r--r--sound/soc/fsl/imx-audio-rpmsg.c130
-rw-r--r--sound/soc/fsl/imx-audmix.c363
-rw-r--r--sound/soc/fsl/imx-audmux.c386
-rw-r--r--sound/soc/fsl/imx-audmux.h12
-rw-r--r--sound/soc/fsl/imx-card.c869
-rw-r--r--sound/soc/fsl/imx-es8328.c231
-rw-r--r--sound/soc/fsl/imx-hdmi.c235
-rw-r--r--sound/soc/fsl/imx-pcm-dma.c53
-rw-r--r--sound/soc/fsl/imx-pcm-fiq.c322
-rw-r--r--sound/soc/fsl/imx-pcm-rpmsg.c834
-rw-r--r--sound/soc/fsl/imx-pcm-rpmsg.h512
-rw-r--r--sound/soc/fsl/imx-pcm.h55
-rw-r--r--sound/soc/fsl/imx-rpmsg.c186
-rw-r--r--sound/soc/fsl/imx-sgtl5000.c226
-rw-r--r--sound/soc/fsl/imx-spdif.c102
-rw-r--r--sound/soc/fsl/imx-ssi.h214
-rw-r--r--sound/soc/fsl/mpc5200_dma.c457
-rw-r--r--sound/soc/fsl/mpc5200_dma.h88
-rw-r--r--sound/soc/fsl/mpc5200_psc_ac97.c344
-rw-r--r--sound/soc/fsl/mpc5200_psc_i2s.c242
-rw-r--r--sound/soc/fsl/mpc8610_hpcd.c453
-rw-r--r--sound/soc/fsl/p1022_ds.c461
-rw-r--r--sound/soc/fsl/p1022_rdk.c410
-rw-r--r--sound/soc/fsl/pcm030-audio-fabric.c143
59 files changed, 27721 insertions, 0 deletions
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
new file mode 100644
index 000000000..614eceda6
--- /dev/null
+++ b/sound/soc/fsl/Kconfig
@@ -0,0 +1,373 @@
+# SPDX-License-Identifier: GPL-2.0-only
+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
+ select SND_SOC_FSL_UTILS
+ 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_MQS
+ tristate "Medium Quality Sound (MQS) module support"
+ depends on SND_SOC_FSL_SAI
+ select REGMAP_MMIO
+ help
+ Say Y if you want to add Medium Quality Sound (MQS)
+ 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_AUDMIX
+ tristate "Audio Mixer (AUDMIX) module support"
+ select REGMAP_MMIO
+ help
+ Say Y if you want to add Audio Mixer (AUDMIX)
+ support for the NXP iMX CPUs.
+
+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
+ select SND_SOC_FSL_UTILS
+ 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_MICFIL
+ tristate "Pulse Density Modulation Microphone Interface (MICFIL) module support"
+ select REGMAP_MMIO
+ select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select SND_SOC_FSL_UTILS
+ help
+ Say Y if you want to add Pulse Density Modulation microphone
+ interface (MICFIL) support for NXP.
+
+config SND_SOC_FSL_EASRC
+ tristate "Enhanced Asynchronous Sample Rate Converter (EASRC) module support"
+ depends on SND_SOC_FSL_ASRC
+ select REGMAP_MMIO
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Say Y if you want to add Enhanced ASRC support for NXP. The ASRC is
+ a digital module that converts audio from a source sample rate to a
+ destination sample rate. It is a new design module compare with the
+ old ASRC.
+
+config SND_SOC_FSL_XCVR
+ tristate "NXP Audio Transceiver (XCVR) 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 Audio Transceiver (XCVR) support for NXP
+ iMX CPUs. XCVR is a digital module that supports HDMI2.1 eARC,
+ HDMI1.4 ARC and SPDIF.
+
+config SND_SOC_FSL_AUD2HTX
+ tristate "AUDIO TO HDMI TX module support"
+ depends on ARCH_MXC || COMPILE_TEST
+ select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
+ help
+ Say Y if you want to add AUDIO TO HDMI TX support for NXP.
+
+config SND_SOC_FSL_UTILS
+ tristate
+
+config SND_SOC_FSL_RPMSG
+ tristate "NXP Audio Base On RPMSG support"
+ depends on COMMON_CLK
+ depends on RPMSG
+ depends on SND_IMX_SOC || SND_IMX_SOC = n
+ select SND_SOC_IMX_RPMSG if SND_IMX_SOC != n
+ help
+ Say Y if you want to add rpmsg audio 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_IMX_PCM_DMA
+ tristate
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+
+config SND_SOC_IMX_AUDIO_RPMSG
+ tristate
+ depends on RPMSG
+
+config SND_SOC_IMX_PCM_RPMSG
+ tristate
+ depends on SND_SOC_IMX_AUDIO_RPMSG
+ 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
+
+config SND_SOC_IMX_PCM_FIQ
+ tristate
+ default y if (SND_SOC_FSL_SSI=m || SND_SOC_FSL_SPDIF=m) && (MXC_TZIC || MXC_AVIC)
+ select FIQ
+
+if SND_IMX_SOC
+
+comment "SoC Audio support for Freescale i.MX boards:"
+
+config SND_SOC_EUKREA_TLV320
+ tristate "Eukrea TLV320"
+ depends on ARCH_MXC && !ARM64 && I2C
+ select SND_SOC_TLV320AIC23_I2C
+ select SND_SOC_IMX_AUDMUX
+ 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_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_SIMPLE_CARD_UTILS
+ 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
+ select SND_SOC_TLV320AIC31XX
+ select SND_SOC_WM8994
+ select MFD_WM8994
+ 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, SGTL5000 and TLV320AIC32x4.
+ Say Y if you want to add support for Freescale Generic ASoC Sound Card.
+
+config SND_SOC_IMX_AUDMIX
+ tristate "SoC Audio support for i.MX boards with AUDMIX"
+ select SND_SOC_FSL_AUDMIX
+ select SND_SOC_FSL_SAI
+ help
+ SoC Audio support for i.MX boards with Audio Mixer
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ an Audio Mixer.
+
+config SND_SOC_IMX_HDMI
+ tristate "SoC Audio support for i.MX boards with HDMI port"
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_AUD2HTX
+ select SND_SOC_HDMI_CODEC
+ help
+ ALSA SoC Audio support with HDMI feature for Freescale SoCs that have
+ SAI/AUD2HTX and connect with internal HDMI IP or external module
+ SII902X.
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ IMX HDMI.
+
+config SND_SOC_IMX_RPMSG
+ tristate "SoC Audio support for i.MX boards with rpmsg"
+ depends on RPMSG
+ select SND_SOC_IMX_PCM_RPMSG
+ select SND_SOC_IMX_AUDIO_RPMSG
+ help
+ SoC Audio support for i.MX boards with rpmsg.
+ There should be rpmsg devices defined in other core (M core)
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a rpmsg devices.
+
+config SND_SOC_IMX_CARD
+ tristate "SoC Audio Graph Sound Card support for i.MX boards"
+ depends on OF && I2C
+ select SND_SOC_AK4458
+ select SND_SOC_AK5558
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SIMPLE_CARD_UTILS
+ help
+ This option enables audio sound card support for i.MX boards
+ with OF-graph DT bindings.
+ It also support DPCM of single CPU multi Codec ststem.
+
+endif # SND_IMX_SOC
+
+endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
new file mode 100644
index 000000000..b54beb1a6
--- /dev/null
+++ b/sound/soc/fsl/Makefile
@@ -0,0 +1,83 @@
+# 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-audmix-objs := fsl_audmix.o
+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-micfil-objs := fsl_micfil.o
+snd-soc-fsl-utils-objs := fsl_utils.o
+snd-soc-fsl-dma-objs := fsl_dma.o
+snd-soc-fsl-mqs-objs := fsl_mqs.o
+snd-soc-fsl-easrc-objs := fsl_easrc.o
+snd-soc-fsl-xcvr-objs := fsl_xcvr.o
+snd-soc-fsl-aud2htx-objs := fsl_aud2htx.o
+snd-soc-fsl-rpmsg-objs := fsl_rpmsg.o
+
+obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.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_MICFIL) += snd-soc-fsl-micfil.o
+obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o
+obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o
+obj-$(CONFIG_SND_SOC_FSL_EASRC) += snd-soc-fsl-easrc.o
+obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
+obj-$(CONFIG_SND_SOC_FSL_XCVR) += snd-soc-fsl-xcvr.o
+obj-$(CONFIG_SND_SOC_FSL_AUD2HTX) += snd-soc-fsl-aud2htx.o
+obj-$(CONFIG_SND_SOC_FSL_RPMSG) += snd-soc-fsl-rpmsg.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-audmux-objs := imx-audmux.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
+obj-$(CONFIG_SND_SOC_IMX_AUDIO_RPMSG) += imx-audio-rpmsg.o
+obj-$(CONFIG_SND_SOC_IMX_PCM_RPMSG) += imx-pcm-rpmsg.o
+
+# i.MX Machine Support
+snd-soc-eukrea-tlv320-objs := eukrea-tlv320.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-audmix-objs := imx-audmix.o
+snd-soc-imx-hdmi-objs := imx-hdmi.o
+snd-soc-imx-rpmsg-objs := imx-rpmsg.o
+snd-soc-imx-card-objs := imx-card.o
+
+obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.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_AUDMIX) += snd-soc-imx-audmix.o
+obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o
+obj-$(CONFIG_SND_SOC_IMX_RPMSG) += snd-soc-imx-rpmsg.o
+obj-$(CONFIG_SND_SOC_IMX_CARD) += snd-soc-imx-card.o
diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c
new file mode 100644
index 000000000..8f6396fae
--- /dev/null
+++ b/sound/soc/fsl/efika-audio-fabric.c
@@ -0,0 +1,95 @@
+/*
+ * 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"
+
+SND_SOC_DAILINK_DEFS(analog,
+ DAILINK_COMP_ARRAY(COMP_CPU("mpc5200-psc-ac97.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("stac9766-codec",
+ "stac9766-hifi-analog")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("mpc5200-pcm-audio")));
+
+SND_SOC_DAILINK_DEFS(iec958,
+ DAILINK_COMP_ARRAY(COMP_CPU("mpc5200-psc-ac97.1")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("stac9766-codec",
+ "stac9766-hifi-IEC958")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("mpc5200-pcm-audio")));
+
+static struct snd_soc_dai_link efika_fabric_dai[] = {
+{
+ .name = "AC97",
+ .stream_name = "AC97 Analog",
+ SND_SOC_DAILINK_REG(analog),
+},
+{
+ .name = "AC97",
+ .stream_name = "AC97 IEC958",
+ SND_SOC_DAILINK_REG(iec958),
+},
+};
+
+static struct snd_soc_card card = {
+ .name = "Efika",
+ .owner = THIS_MODULE,
+ .dai_link = efika_fabric_dai,
+ .num_links = ARRAY_SIZE(efika_fabric_dai),
+};
+
+static __init int efika_fabric_init(void)
+{
+ struct platform_device *pdev;
+ int rc;
+
+ if (!of_machine_is_compatible("bplan,efika"))
+ return -ENODEV;
+
+ pdev = platform_device_alloc("soc-audio", 1);
+ if (!pdev) {
+ pr_err("efika_fabric_init: platform_device_alloc() failed\n");
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, &card);
+
+ rc = platform_device_add(pdev);
+ if (rc) {
+ pr_err("efika_fabric_init: platform_device_add() failed\n");
+ platform_device_put(pdev);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+module_init(efika_fabric_init);
+
+
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
+MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/fsl/eukrea-tlv320.c b/sound/soc/fsl/eukrea-tlv320.c
new file mode 100644
index 000000000..9af4c4a35
--- /dev/null
+++ b/sound/soc/fsl/eukrea-tlv320.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// 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>
+
+#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 = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ 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,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "tlv320aic23-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link eukrea_tlv320_dai = {
+ .name = "tlv320aic23",
+ .stream_name = "TLV320AIC23",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBP_CFP,
+ .ops = &eukrea_tlv320_snd_ops,
+ SND_SOC_DAILINK_REG(hifi),
+};
+
+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, *tmp_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.codecs->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");
+ goto err;
+ }
+ 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");
+ goto err;
+ }
+
+ /*
+ * 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.cpus->of_node = ssi_np;
+ eukrea_tlv320_dai.platforms->of_node = ssi_np;
+ } else {
+ eukrea_tlv320_dai.cpus->dai_name = "imx-ssi.0";
+ eukrea_tlv320_dai.platforms->name = "imx-ssi.0";
+ eukrea_tlv320_dai.codecs->name = "tlv320aic23-codec.0-001a";
+ eukrea_tlv320.name = "cpuimx-audio";
+ }
+
+ if (machine_is_eukrea_cpuimx27() ||
+ (tmp_np = 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)
+ );
+ of_node_put(tmp_np);
+ } else if (machine_is_eukrea_cpuimx25sd() ||
+ machine_is_eukrea_cpuimx35sd() ||
+ machine_is_eukrea_cpuimx51sd() ||
+ (tmp_np = 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)
+ );
+ of_node_put(tmp_np);
+ } 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 000000000..8d14b5593
--- /dev/null
+++ b/sound/soc/fsl/fsl-asoc-card.c
@@ -0,0 +1,928 @@
+// 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 <sound/jack.h>
+#include <sound/simple_card_utils.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"
+#include "../codecs/wm8994.h"
+#include "../codecs/tlv320aic31xx.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)
+
+/**
+ * struct codec_priv - CODEC private data
+ * @mclk_freq: Clock rate of MCLK
+ * @free_freq: Clock rate of MCLK for hw_free()
+ * @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;
+ unsigned long free_freq;
+ u32 mclk_id;
+ u32 fll_id;
+ u32 pll_id;
+};
+
+/**
+ * struct cpu_priv - CPU private data
+ * @sysclk_freq: SYSCLK rates for set_sysclk()
+ * @sysclk_dir: SYSCLK directions for set_sysclk()
+ * @sysclk_id: 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;
+};
+
+/**
+ * struct fsl_asoc_card_priv - Freescale Generic ASOC card private data
+ * @dai_link: DAI link structure including normal one and DPCM link
+ * @hp_jack: Headphone Jack structure
+ * @mic_jack: Microphone Jack structure
+ * @pdev: platform device pointer
+ * @codec_priv: CODEC private data
+ * @cpu_priv: CPU private data
+ * @card: ASoC card structure
+ * @streams: Mask of current active streams
+ * @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 asoc_simple_jack hp_jack;
+ struct asoc_simple_jack mic_jack;
+ struct platform_device *pdev;
+ struct codec_priv codec_priv;
+ struct cpu_priv cpu_priv;
+ struct snd_soc_card card;
+ u8 streams;
+ 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 exists 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 */
+ {"AC97 Playback", NULL, "CPU AC97 Playback"},
+ {"CPU AC97 Capture", NULL, "AC97 Capture"},
+ /* 2nd half -- ASRC DAPM routes */
+ {"CPU AC97 Playback", NULL, "ASRC-Playback"},
+ {"ASRC-Capture", NULL, "CPU AC97 Capture"},
+};
+
+static const struct snd_soc_dapm_route audio_map_tx[] = {
+ /* 1st half -- Normal DAPM routes */
+ {"Playback", NULL, "CPU-Playback"},
+ /* 2nd half -- ASRC DAPM routes */
+ {"CPU-Playback", NULL, "ASRC-Playback"},
+};
+
+static const struct snd_soc_dapm_route audio_map_rx[] = {
+ /* 1st half -- Normal DAPM routes */
+ {"CPU-Capture", NULL, "Capture"},
+ /* 2nd half -- ASRC DAPM routes */
+ {"ASRC-Capture", NULL, "CPU-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 = asoc_substream_to_rtd(substream);
+ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct codec_priv *codec_priv = &priv->codec_priv;
+ struct cpu_priv *cpu_priv = &priv->cpu_priv;
+ struct device *dev = rtd->card->dev;
+ unsigned int pll_out;
+ int ret;
+
+ priv->sample_rate = params_rate(params);
+ priv->sample_format = params_format(params);
+ priv->streams |= BIT(substream->stream);
+
+ if (fsl_asoc_card_is_ac97(priv))
+ return 0;
+
+ /* Specific configurations of DAIs starts from here */
+ ret = snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(rtd, 0), 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");
+ goto fail;
+ }
+
+ if (cpu_priv->slot_width) {
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2,
+ cpu_priv->slot_width);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set TDM slot for cpu dai\n");
+ goto fail;
+ }
+ }
+
+ /* Specific configuration for PLL */
+ if (codec_priv->pll_id && codec_priv->fll_id) {
+ 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(asoc_rtd_to_codec(rtd, 0),
+ 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);
+ goto fail;
+ }
+
+ ret = snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0),
+ codec_priv->fll_id,
+ pll_out, SND_SOC_CLOCK_IN);
+
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set SYSCLK: %d\n", ret);
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ priv->streams &= ~BIT(substream->stream);
+ return ret;
+}
+
+static int fsl_asoc_card_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct codec_priv *codec_priv = &priv->codec_priv;
+ struct device *dev = rtd->card->dev;
+ int ret;
+
+ priv->streams &= ~BIT(substream->stream);
+
+ if (!priv->streams && codec_priv->pll_id && codec_priv->fll_id) {
+ /* Force freq to be free_freq to avoid error message in codec */
+ ret = snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0),
+ codec_priv->mclk_id,
+ codec_priv->free_freq,
+ SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to switch away from FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(asoc_rtd_to_codec(rtd, 0),
+ codec_priv->pll_id, 0, 0, 0);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to stop FLL: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops fsl_asoc_card_ops = {
+ .hw_params = fsl_asoc_card_hw_params,
+ .hw_free = fsl_asoc_card_hw_free,
+};
+
+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;
+}
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(hifi_fe,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(hifi_be,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+static struct snd_soc_dai_link fsl_asoc_card_dai[] = {
+ /* Default ASoC DAI Link*/
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .ops = &fsl_asoc_card_ops,
+ SND_SOC_DAILINK_REG(hifi),
+ },
+ /* DPCM Link between Front-End and Back-End (Optional) */
+ {
+ .name = "HiFi-ASRC-FE",
+ .stream_name = "HiFi-ASRC-FE",
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .dynamic = 1,
+ SND_SOC_DAILINK_REG(hifi_fe),
+ },
+ {
+ .name = "HiFi-ASRC-BE",
+ .stream_name = "HiFi-ASRC-BE",
+ .be_hw_params_fixup = be_hw_params_fixup,
+ .ops = &fsl_asoc_card_ops,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ SND_SOC_DAILINK_REG(hifi_be),
+ },
+};
+
+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_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_CBP_CFP:
+ 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_CBP_CFC:
+ 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_CBC_CFP:
+ 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_CBC_CFC:
+ 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 hp_jack_event(struct notifier_block *nb, unsigned long event,
+ void *data)
+{
+ struct snd_soc_jack *jack = (struct snd_soc_jack *)data;
+ struct snd_soc_dapm_context *dapm = &jack->card->dapm;
+
+ if (event & SND_JACK_HEADPHONE)
+ /* Disable speaker if headphone is plugged in */
+ return snd_soc_dapm_disable_pin(dapm, "Ext Spk");
+ else
+ return snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+}
+
+static struct notifier_block hp_jack_nb = {
+ .notifier_call = hp_jack_event,
+};
+
+static int mic_jack_event(struct notifier_block *nb, unsigned long event,
+ void *data)
+{
+ struct snd_soc_jack *jack = (struct snd_soc_jack *)data;
+ struct snd_soc_dapm_context *dapm = &jack->card->dapm;
+
+ if (event & SND_JACK_MICROPHONE)
+ /* Disable dmic if microphone is plugged in */
+ return snd_soc_dapm_disable_pin(dapm, "DMIC");
+ else
+ return snd_soc_dapm_enable_pin(dapm, "DMIC");
+}
+
+static struct notifier_block mic_jack_nb = {
+ .notifier_call = mic_jack_event,
+};
+
+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 = asoc_rtd_to_codec(rtd, 0);
+ 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 = asoc_rtd_to_codec(rtd, 0)->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 device_node *bitclkprovider = NULL;
+ struct device_node *frameprovider = NULL;
+ struct platform_device *cpu_pdev;
+ struct fsl_asoc_card_priv *priv;
+ struct device *codec_dev = NULL;
+ const char *codec_dai_name;
+ const char *codec_dev_name;
+ u32 asrc_fmt = 0;
+ 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) {
+ struct platform_device *codec_pdev;
+ struct i2c_client *codec_i2c;
+
+ codec_i2c = of_find_i2c_device_by_node(codec_np);
+ if (codec_i2c) {
+ codec_dev = &codec_i2c->dev;
+ codec_dev_name = codec_i2c->name;
+ }
+ if (!codec_dev) {
+ codec_pdev = of_find_device_by_node(codec_np);
+ if (codec_pdev) {
+ codec_dev = &codec_pdev->dev;
+ codec_dev_name = codec_pdev->name;
+ }
+ }
+ }
+
+ 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, 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;
+
+ memcpy(priv->dai_link, fsl_asoc_card_dai,
+ sizeof(struct snd_soc_dai_link) * ARRAY_SIZE(priv->dai_link));
+
+ priv->card.dapm_routes = audio_map;
+ priv->card.num_dapm_routes = ARRAY_SIZE(audio_map);
+ /* Diversify the card configurations */
+ if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) {
+ codec_dai_name = "cs42888";
+ 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_CBC_CFC;
+ } 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_CBP_CFP;
+ } 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_CBP_CFP;
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic32x4")) {
+ codec_dai_name = "tlv320aic32x4-hifi";
+ priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP;
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-tlv320aic31xx")) {
+ codec_dai_name = "tlv320dac31xx-hifi";
+ priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
+ priv->dai_link[1].dpcm_capture = 0;
+ priv->dai_link[2].dpcm_capture = 0;
+ priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT;
+ priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT;
+ priv->card.dapm_routes = audio_map_tx;
+ priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx);
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) {
+ codec_dai_name = "wm8962";
+ 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_CBP_CFP;
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) {
+ codec_dai_name = "wm8960-hifi";
+ priv->codec_priv.fll_id = WM8960_SYSCLK_AUTO;
+ priv->codec_priv.pll_id = WM8960_SYSCLK_AUTO;
+ priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP;
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) {
+ codec_dai_name = "ac97-hifi";
+ priv->dai_fmt = SND_SOC_DAIFMT_AC97;
+ priv->card.dapm_routes = audio_map_ac97;
+ priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_ac97);
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-mqs")) {
+ codec_dai_name = "fsl-mqs-dai";
+ priv->dai_fmt = SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_CBC_CFC |
+ SND_SOC_DAIFMT_NB_NF;
+ priv->dai_link[1].dpcm_capture = 0;
+ priv->dai_link[2].dpcm_capture = 0;
+ priv->card.dapm_routes = audio_map_tx;
+ priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx);
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8524")) {
+ codec_dai_name = "wm8524-hifi";
+ priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC;
+ priv->dai_link[1].dpcm_capture = 0;
+ priv->dai_link[2].dpcm_capture = 0;
+ priv->cpu_priv.slot_width = 32;
+ priv->card.dapm_routes = audio_map_tx;
+ priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_tx);
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-si476x")) {
+ codec_dai_name = "si476x-codec";
+ priv->dai_fmt |= SND_SOC_DAIFMT_CBC_CFC;
+ priv->card.dapm_routes = audio_map_rx;
+ priv->card.num_dapm_routes = ARRAY_SIZE(audio_map_rx);
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-wm8958")) {
+ codec_dai_name = "wm8994-aif1";
+ priv->dai_fmt |= SND_SOC_DAIFMT_CBP_CFP;
+ priv->codec_priv.mclk_id = WM8994_FLL_SRC_MCLK1;
+ priv->codec_priv.fll_id = WM8994_SYSCLK_FLL1;
+ priv->codec_priv.pll_id = WM8994_FLL1;
+ priv->codec_priv.free_freq = priv->codec_priv.mclk_freq;
+ priv->card.dapm_routes = NULL;
+ priv->card.num_dapm_routes = 0;
+ } else {
+ dev_err(&pdev->dev, "unknown Device Tree compatible\n");
+ ret = -EINVAL;
+ goto asrc_fail;
+ }
+
+ /*
+ * Allow setting mclk-id from the device-tree node. Otherwise, the
+ * default value for each card configuration is used.
+ */
+ of_property_read_u32(np, "mclk-id", &priv->codec_priv.mclk_id);
+
+ /* Format info from DT is optional. */
+ snd_soc_daifmt_parse_clock_provider_as_phandle(np, NULL, &bitclkprovider, &frameprovider);
+ if (bitclkprovider || frameprovider) {
+ unsigned int daifmt = snd_soc_daifmt_parse_format(np, NULL);
+
+ if (codec_np == bitclkprovider)
+ daifmt |= (codec_np == frameprovider) ?
+ SND_SOC_DAIFMT_CBP_CFP : SND_SOC_DAIFMT_CBP_CFC;
+ else
+ daifmt |= (codec_np == frameprovider) ?
+ SND_SOC_DAIFMT_CBC_CFP : SND_SOC_DAIFMT_CBC_CFC;
+
+ /* Override dai_fmt with value from DT */
+ priv->dai_fmt = daifmt;
+ }
+
+ /* Change direction according to format */
+ if (priv->dai_fmt & SND_SOC_DAIFMT_CBP_CFP) {
+ priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_IN;
+ priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_IN;
+ }
+
+ of_node_put(bitclkprovider);
+ of_node_put(frameprovider);
+
+ if (!fsl_asoc_card_is_ac97(priv) && !codec_dev) {
+ dev_dbg(&pdev->dev, "failed to find codec device\n");
+ ret = -EPROBE_DEFER;
+ goto asrc_fail;
+ }
+
+ /* Common settings for corresponding Freescale CPU DAI driver */
+ if (of_node_name_eq(cpu_np, "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 (of_node_name_eq(cpu_np, "esai")) {
+ struct clk *esai_clk = clk_get(&cpu_pdev->dev, "extal");
+
+ if (!IS_ERR(esai_clk)) {
+ priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk);
+ priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk);
+ clk_put(esai_clk);
+ } else if (PTR_ERR(esai_clk) == -EPROBE_DEFER) {
+ ret = -EPROBE_DEFER;
+ goto asrc_fail;
+ }
+
+ priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL;
+ priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL;
+ } else if (of_node_name_eq(cpu_np, "sai")) {
+ priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1;
+ priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1;
+ }
+
+ /* Initialize sound card */
+ priv->pdev = pdev;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret) {
+ snprintf(priv->name, sizeof(priv->name), "%s-audio",
+ fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name);
+ priv->card.name = priv->name;
+ }
+ priv->card.dai_link = priv->dai_link;
+ priv->card.late_probe = fsl_asoc_card_late_probe;
+ 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;
+
+ if (of_property_read_bool(np, "audio-routing")) {
+ 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].cpus->of_node = cpu_np;
+ priv->dai_link[0].codecs->dai_name = codec_dai_name;
+
+ if (!fsl_asoc_card_is_ac97(priv))
+ priv->dai_link[0].codecs->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].codecs->name =
+ devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "ac97-codec.%u",
+ (unsigned int)idx);
+ if (!priv->dai_link[0].codecs->name) {
+ ret = -ENOMEM;
+ goto asrc_fail;
+ }
+ }
+
+ priv->dai_link[0].platforms->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].cpus->of_node = asrc_np;
+ priv->dai_link[1].platforms->of_node = asrc_np;
+ priv->dai_link[2].codecs->dai_name = codec_dai_name;
+ priv->dai_link[2].codecs->of_node = codec_np;
+ priv->dai_link[2].codecs->name =
+ priv->dai_link[0].codecs->name;
+ priv->dai_link[2].cpus->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-format", &asrc_fmt);
+ priv->asrc_format = (__force snd_pcm_format_t)asrc_fmt;
+ if (ret) {
+ /* Fallback to old binding; translate to asrc_format */
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width",
+ &width);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to decide output format\n");
+ goto asrc_fail;
+ }
+
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+ }
+
+ /* Finish card registering */
+ platform_set_drvdata(pdev, priv);
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n");
+ goto asrc_fail;
+ }
+
+ /*
+ * Properties "hp-det-gpio" and "mic-det-gpio" are optional, and
+ * asoc_simple_init_jack uses these properties for creating
+ * Headphone Jack and Microphone Jack.
+ *
+ * The notifier is initialized in snd_soc_card_jack_new(), then
+ * snd_soc_jack_notifier_register can be called.
+ */
+ if (of_property_read_bool(np, "hp-det-gpio")) {
+ ret = asoc_simple_init_jack(&priv->card, &priv->hp_jack,
+ 1, NULL, "Headphone Jack");
+ if (ret)
+ goto asrc_fail;
+
+ snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb);
+ }
+
+ if (of_property_read_bool(np, "mic-det-gpio")) {
+ ret = asoc_simple_init_jack(&priv->card, &priv->mic_jack,
+ 0, NULL, "Mic Jack");
+ if (ret)
+ goto asrc_fail;
+
+ snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb);
+ }
+
+asrc_fail:
+ of_node_put(asrc_np);
+ of_node_put(codec_np);
+ put_device(&cpu_pdev->dev);
+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-tlv320aic32x4", },
+ { .compatible = "fsl,imx-audio-tlv320aic31xx", },
+ { .compatible = "fsl,imx-audio-sgtl5000", },
+ { .compatible = "fsl,imx-audio-wm8962", },
+ { .compatible = "fsl,imx-audio-wm8960", },
+ { .compatible = "fsl,imx-audio-mqs", },
+ { .compatible = "fsl,imx-audio-wm8524", },
+ { .compatible = "fsl,imx-audio-si476x", },
+ { .compatible = "fsl,imx-audio-wm8958", },
+ {}
+};
+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 000000000..e16e7b3fa
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc.c
@@ -0,0 +1,1409 @@
+// 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/dma/imx-dma.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 DIVIDER_NUM 64
+#define INIT_RETRY_NUM 50
+
+#define pair_err(fmt, ...) \
+ dev_err(&asrc->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+
+#define pair_dbg(fmt, ...) \
+ dev_dbg(&asrc->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+
+#define pair_warn(fmt, ...) \
+ dev_warn(&asrc->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+
+/* Corresponding to process_option */
+static unsigned int supported_asrc_rate[] = {
+ 5512, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
+ 64000, 88200, 96000, 128000, 176400, 192000,
+};
+
+static struct snd_pcm_hw_constraint_list fsl_asrc_rate_constraints = {
+ .count = ARRAY_SIZE(supported_asrc_rate),
+ .list = supported_asrc_rate,
+};
+
+/*
+ * 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[ASRC_CLK_MAP_LEN] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+};
+
+static unsigned char output_clk_map_imx35[ASRC_CLK_MAP_LEN] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+};
+
+/* i.MX53 uses the same map for input and output */
+static unsigned char input_clk_map_imx53[ASRC_CLK_MAP_LEN] = {
+/* 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,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
+};
+
+static unsigned char output_clk_map_imx53[ASRC_CLK_MAP_LEN] = {
+/* 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,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
+};
+
+/*
+ * i.MX8QM/i.MX8QXP uses the same map for input and output.
+ * clk_map_imx8qm[0] is for i.MX8QM asrc0
+ * clk_map_imx8qm[1] is for i.MX8QM asrc1
+ * clk_map_imx8qxp[0] is for i.MX8QXP asrc0
+ * clk_map_imx8qxp[1] is for i.MX8QXP asrc1
+ */
+static unsigned char clk_map_imx8qm[2][ASRC_CLK_MAP_LEN] = {
+ {
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0,
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+ },
+ {
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0,
+ 0x0, 0x1, 0x2, 0x3, 0xb, 0xc, 0xf, 0xf, 0xd, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+ 0x4, 0x5, 0x6, 0xf, 0x8, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+ },
+};
+
+static unsigned char clk_map_imx8qxp[2][ASRC_CLK_MAP_LEN] = {
+ {
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0,
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xf, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xf,
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+ },
+ {
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0,
+ 0x0, 0x1, 0x2, 0x3, 0x7, 0x8, 0xf, 0xf, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+ 0xf, 0xf, 0x6, 0xf, 0xf, 0xf, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+ },
+};
+
+/*
+ * According to RM, the divider range is 1 ~ 8,
+ * prescaler is power of 2 from 1 ~ 128.
+ */
+static int asrc_clk_divider[DIVIDER_NUM] = {
+ 1, 2, 4, 8, 16, 32, 64, 128, /* divider = 1 */
+ 2, 4, 8, 16, 32, 64, 128, 256, /* divider = 2 */
+ 3, 6, 12, 24, 48, 96, 192, 384, /* divider = 3 */
+ 4, 8, 16, 32, 64, 128, 256, 512, /* divider = 4 */
+ 5, 10, 20, 40, 80, 160, 320, 640, /* divider = 5 */
+ 6, 12, 24, 48, 96, 192, 384, 768, /* divider = 6 */
+ 7, 14, 28, 56, 112, 224, 448, 896, /* divider = 7 */
+ 8, 16, 32, 64, 128, 256, 512, 1024, /* divider = 8 */
+};
+
+/*
+ * Check if the divider is available for internal ratio mode
+ */
+static bool fsl_asrc_divider_avail(int clk_rate, int rate, int *div)
+{
+ u32 rem, i;
+ u64 n;
+
+ if (div)
+ *div = 0;
+
+ if (clk_rate == 0 || rate == 0)
+ return false;
+
+ n = clk_rate;
+ rem = do_div(n, rate);
+
+ if (div)
+ *div = n;
+
+ if (rem != 0)
+ return false;
+
+ for (i = 0; i < DIVIDER_NUM; i++) {
+ if (n == asrc_clk_divider[i])
+ break;
+ }
+
+ if (i == DIVIDER_NUM)
+ return false;
+
+ return true;
+}
+
+/**
+ * fsl_asrc_sel_proc - Select the pre-processing and post-processing options
+ * @inrate: input sample rate
+ * @outrate: output sample rate
+ * @pre_proc: return value for pre-processing option
+ * @post_proc: return value for post-processing option
+ *
+ * Make sure to exclude following unsupported cases before
+ * calling this function:
+ * 1) inrate > 8.125 * outrate
+ * 2) inrate > 16.125 * outrate
+ *
+ */
+static void fsl_asrc_sel_proc(int inrate, int outrate,
+ int *pre_proc, int *post_proc)
+{
+ bool post_proc_cond2;
+ bool post_proc_cond0;
+
+ /* select pre_proc between [0, 2] */
+ if (inrate * 8 > 33 * outrate)
+ *pre_proc = 2;
+ else if (inrate * 8 > 15 * outrate) {
+ if (inrate > 152000)
+ *pre_proc = 2;
+ else
+ *pre_proc = 1;
+ } else if (inrate < 76000)
+ *pre_proc = 0;
+ else if (inrate > 152000)
+ *pre_proc = 2;
+ else
+ *pre_proc = 1;
+
+ /* Condition for selection of post-processing */
+ post_proc_cond2 = (inrate * 15 > outrate * 16 && outrate < 56000) ||
+ (inrate > 56000 && outrate < 56000);
+ post_proc_cond0 = inrate * 23 < outrate * 8;
+
+ if (post_proc_cond2)
+ *post_proc = 2;
+ else if (post_proc_cond0)
+ *post_proc = 0;
+ else
+ *post_proc = 1;
+}
+
+/**
+ * fsl_asrc_request_pair - Request ASRC pair
+ * @channels: number of channels
+ * @pair: pointer to 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 = pair->asrc;
+ struct device *dev = &asrc->pdev->dev;
+ unsigned long lock_flags;
+ int i, ret = 0;
+
+ spin_lock_irqsave(&asrc->lock, lock_flags);
+
+ for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) {
+ if (asrc->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->channel_avail < channels) {
+ dev_err(dev, "can't afford required channels: %d\n", channels);
+ ret = -EINVAL;
+ } else {
+ asrc->channel_avail -= channels;
+ asrc->pair[index] = pair;
+ pair->channels = channels;
+ pair->index = index;
+ }
+
+ spin_unlock_irqrestore(&asrc->lock, lock_flags);
+
+ return ret;
+}
+
+/**
+ * fsl_asrc_release_pair - Release ASRC pair
+ * @pair: pair to release
+ *
+ * It clears the resource from asrc and releases the occupied channels.
+ */
+static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc *asrc = pair->asrc;
+ enum asrc_pair_index index = pair->index;
+ unsigned long lock_flags;
+
+ /* Make sure the pair is disabled */
+ regmap_update_bits(asrc->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_MASK(index), 0);
+
+ spin_lock_irqsave(&asrc->lock, lock_flags);
+
+ asrc->channel_avail += pair->channels;
+ asrc->pair[index] = NULL;
+ pair->error = 0;
+
+ spin_unlock_irqrestore(&asrc->lock, lock_flags);
+}
+
+/**
+ * fsl_asrc_set_watermarks- configure input and output thresholds
+ * @pair: pointer to pair
+ * @in: input threshold
+ * @out: output threshold
+ */
+static void fsl_asrc_set_watermarks(struct fsl_asrc_pair *pair, u32 in, u32 out)
+{
+ struct fsl_asrc *asrc = pair->asrc;
+ enum asrc_pair_index index = pair->index;
+
+ regmap_update_bits(asrc->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));
+}
+
+/**
+ * fsl_asrc_cal_asrck_divisor - Calculate the total divisor between asrck clock rate and sample rate
+ * @pair: pointer to pair
+ * @div: divider
+ *
+ * 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;
+}
+
+/**
+ * fsl_asrc_set_ideal_ratio - Calculate and set the ratio for Ideal Ratio mode only
+ * @pair: pointer to pair
+ * @inrate: input rate
+ * @outrate: output rate
+ *
+ * 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 = pair->asrc;
+ 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->regmap, REG_ASRIDRL(index), ratio);
+ regmap_write(asrc->regmap, REG_ASRIDRH(index), ratio >> 24);
+
+ return 0;
+}
+
+/**
+ * fsl_asrc_config_pair - Configure the assigned ASRC pair
+ * @pair: pointer to pair
+ * @use_ideal_rate: boolean configuration
+ *
+ * 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.
+ *
+ * Note:
+ * The ideal ratio configuration can work with a flexible clock rate setting.
+ * Using IDEAL_RATIO_RATE gives a faster converting speed but overloads ASRC.
+ * For a regular audio playback, the clock rate should not be slower than an
+ * clock rate aligning with the output sample rate; For a use case requiring
+ * faster conversion, set use_ideal_rate to have the faster speed.
+ */
+static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool use_ideal_rate)
+{
+ struct fsl_asrc_pair_priv *pair_priv = pair->private;
+ struct asrc_config *config = pair_priv->config;
+ struct fsl_asrc *asrc = pair->asrc;
+ struct fsl_asrc_priv *asrc_priv = asrc->private;
+ enum asrc_pair_index index = pair->index;
+ enum asrc_word_width input_word_width;
+ enum asrc_word_width output_word_width;
+ u32 inrate, outrate, indiv, outdiv;
+ u32 clk_index[2], div[2];
+ u64 clk_rate;
+ int in, out, channels;
+ int pre_proc, post_proc;
+ struct clk *clk;
+ bool ideal, div_avail;
+
+ 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;
+ }
+
+ switch (snd_pcm_format_width(config->input_format)) {
+ case 8:
+ input_word_width = ASRC_WIDTH_8_BIT;
+ break;
+ case 16:
+ input_word_width = ASRC_WIDTH_16_BIT;
+ break;
+ case 24:
+ input_word_width = ASRC_WIDTH_24_BIT;
+ break;
+ default:
+ pair_err("does not support this input format, %d\n",
+ config->input_format);
+ return -EINVAL;
+ }
+
+ switch (snd_pcm_format_width(config->output_format)) {
+ case 16:
+ output_word_width = ASRC_WIDTH_16_BIT;
+ break;
+ case 24:
+ output_word_width = ASRC_WIDTH_24_BIT;
+ break;
+ default:
+ pair_err("does not support this output format, %d\n",
+ config->output_format);
+ 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_asrc_rate); in++)
+ if (inrate == supported_asrc_rate[in])
+ break;
+
+ if (in == ARRAY_SIZE(supported_asrc_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 >= 5512 && outrate <= 30000) &&
+ (outrate > 24 * inrate || inrate > 8 * outrate)) {
+ 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] = asrc_priv->clk_map[IN][config->inclk];
+ clk_index[OUT] = asrc_priv->clk_map[OUT][config->outclk];
+
+ /* We only have output clock for ideal ratio mode */
+ clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]];
+
+ clk_rate = clk_get_rate(clk);
+ div_avail = fsl_asrc_divider_avail(clk_rate, inrate, &div[IN]);
+
+ /*
+ * The divider range is [1, 1024], defined by the hardware. For non-
+ * ideal ratio configuration, clock rate has to be strictly aligned
+ * with the sample rate. For ideal ratio configuration, clock rates
+ * only result in different converting speeds. So remainder does not
+ * matter, as long as we keep the divider within its valid range.
+ */
+ if (div[IN] == 0 || (!ideal && !div_avail)) {
+ pair_err("failed to support input sample rate %dHz by asrck_%x\n",
+ inrate, clk_index[ideal ? OUT : IN]);
+ return -EINVAL;
+ }
+
+ div[IN] = min_t(u32, 1024, div[IN]);
+
+ clk = asrc_priv->asrck_clk[clk_index[OUT]];
+ clk_rate = clk_get_rate(clk);
+ if (ideal && use_ideal_rate)
+ div_avail = fsl_asrc_divider_avail(clk_rate, IDEAL_RATIO_RATE, &div[OUT]);
+ else
+ div_avail = fsl_asrc_divider_avail(clk_rate, outrate, &div[OUT]);
+
+ /* Output divider has the same limitation as the input one */
+ if (div[OUT] == 0 || (!ideal && !div_avail)) {
+ pair_err("failed to support output sample rate %dHz by asrck_%x\n",
+ outrate, clk_index[OUT]);
+ return -EINVAL;
+ }
+
+ div[OUT] = min_t(u32, 1024, div[OUT]);
+
+ /* Set the channel number */
+ channels = config->channel_num;
+
+ if (asrc_priv->soc->channel_bits < 4)
+ channels /= 2;
+
+ /* Update channels for current pair */
+ regmap_update_bits(asrc->regmap, REG_ASRCNCR,
+ ASRCNCR_ANCi_MASK(index, asrc_priv->soc->channel_bits),
+ ASRCNCR_ANCi(index, channels, asrc_priv->soc->channel_bits));
+
+ /* Default setting: Automatic selection for processing mode */
+ regmap_update_bits(asrc->regmap, REG_ASRCTR,
+ ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index));
+ regmap_update_bits(asrc->regmap, REG_ASRCTR,
+ ASRCTR_USRi_MASK(index), 0);
+
+ /* Set the input and output clock sources */
+ regmap_update_bits(asrc->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->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->regmap, REG_ASRMCR1(index),
+ ASRMCR1i_OW16_MASK | ASRMCR1i_IWD_MASK,
+ ASRMCR1i_OW16(output_word_width) |
+ ASRMCR1i_IWD(input_word_width));
+
+ /* Enable BUFFER STALL */
+ regmap_update_bits(asrc->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->regmap, REG_ASRCTR,
+ ASRCTR_ATSi_MASK(index), 0);
+
+ /* Enable Ideal Ratio mode */
+ regmap_update_bits(asrc->regmap, REG_ASRCTR,
+ ASRCTR_IDRi_MASK(index) | ASRCTR_USRi_MASK(index),
+ ASRCTR_IDR(index) | ASRCTR_USR(index));
+
+ fsl_asrc_sel_proc(inrate, outrate, &pre_proc, &post_proc);
+
+ /* Apply configurations for pre- and post-processing */
+ regmap_update_bits(asrc->regmap, REG_ASRCFG,
+ ASRCFG_PREMODi_MASK(index) | ASRCFG_POSTMODi_MASK(index),
+ ASRCFG_PREMOD(index, pre_proc) |
+ ASRCFG_POSTMOD(index, post_proc));
+
+ return fsl_asrc_set_ideal_ratio(pair, inrate, outrate);
+}
+
+/**
+ * fsl_asrc_start_pair - Start the assigned ASRC pair
+ * @pair: pointer to 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 = pair->asrc;
+ enum asrc_pair_index index = pair->index;
+ int reg, retry = INIT_RETRY_NUM, i;
+
+ /* Enable the current pair */
+ regmap_update_bits(asrc->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_MASK(index), ASRCTR_ASRCE(index));
+
+ /* Wait for status of initialization */
+ do {
+ udelay(5);
+ regmap_read(asrc->regmap, REG_ASRCFG, &reg);
+ reg &= ASRCFG_INIRQi_MASK(index);
+ } while (!reg && --retry);
+
+ /* NOTE: Doesn't treat initialization timeout as an error */
+ if (!retry)
+ pair_warn("initialization isn't finished\n");
+
+ /* Make the input fifo to ASRC STALL level */
+ regmap_read(asrc->regmap, REG_ASRCNCR, &reg);
+ for (i = 0; i < pair->channels * 4; i++)
+ regmap_write(asrc->regmap, REG_ASRDI(index), 0);
+
+ /* Enable overload interrupt */
+ regmap_write(asrc->regmap, REG_ASRIER, ASRIER_AOLIE);
+}
+
+/**
+ * fsl_asrc_stop_pair - Stop the assigned ASRC pair
+ * @pair: pointer to pair
+ */
+static void fsl_asrc_stop_pair(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc *asrc = pair->asrc;
+ enum asrc_pair_index index = pair->index;
+
+ /* Stop the current pair */
+ regmap_update_bits(asrc->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_MASK(index), 0);
+}
+
+/**
+ * fsl_asrc_get_dma_channel- Get DMA channel according to the pair and direction.
+ * @pair: pointer to pair
+ * @dir: DMA direction
+ */
+static struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair,
+ bool dir)
+{
+ struct fsl_asrc *asrc = pair->asrc;
+ 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->pdev->dev, name);
+}
+
+static int fsl_asrc_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_asrc *asrc = snd_soc_dai_get_drvdata(dai);
+ struct fsl_asrc_priv *asrc_priv = asrc->private;
+
+ /* Odd channel number is not valid for older ASRC (channel_bits==3) */
+ if (asrc_priv->soc->channel_bits == 3)
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &fsl_asrc_rate_constraints);
+}
+
+/* Select proper clock source for internal ratio mode */
+static void fsl_asrc_select_clk(struct fsl_asrc_priv *asrc_priv,
+ struct fsl_asrc_pair *pair,
+ int in_rate,
+ int out_rate)
+{
+ struct fsl_asrc_pair_priv *pair_priv = pair->private;
+ struct asrc_config *config = pair_priv->config;
+ int rate[2], select_clk[2]; /* Array size 2 means IN and OUT */
+ int clk_rate, clk_index;
+ int i, j;
+
+ rate[IN] = in_rate;
+ rate[OUT] = out_rate;
+
+ /* Select proper clock source for internal ratio mode */
+ for (j = 0; j < 2; j++) {
+ for (i = 0; i < ASRC_CLK_MAP_LEN; i++) {
+ clk_index = asrc_priv->clk_map[j][i];
+ clk_rate = clk_get_rate(asrc_priv->asrck_clk[clk_index]);
+ /* Only match a perfect clock source with no remainder */
+ if (fsl_asrc_divider_avail(clk_rate, rate[j], NULL))
+ break;
+ }
+
+ select_clk[j] = i;
+ }
+
+ /* Switch to ideal ratio mode if there is no proper clock source */
+ if (select_clk[IN] == ASRC_CLK_MAP_LEN || select_clk[OUT] == ASRC_CLK_MAP_LEN) {
+ select_clk[IN] = INCLK_NONE;
+ select_clk[OUT] = OUTCLK_ASRCK1_CLK;
+ }
+
+ config->inclk = select_clk[IN];
+ config->outclk = select_clk[OUT];
+}
+
+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 = snd_soc_dai_get_drvdata(dai);
+ struct fsl_asrc_priv *asrc_priv = asrc->private;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ struct fsl_asrc_pair_priv *pair_priv = pair->private;
+ unsigned int channels = params_channels(params);
+ unsigned int rate = params_rate(params);
+ struct asrc_config config;
+ int ret;
+
+ ret = fsl_asrc_request_pair(channels, pair);
+ if (ret) {
+ dev_err(dai->dev, "fail to request asrc pair\n");
+ return ret;
+ }
+
+ pair_priv->config = &config;
+
+ config.pair = pair->index;
+ config.channel_num = channels;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ config.input_format = params_format(params);
+ config.output_format = asrc->asrc_format;
+ config.input_sample_rate = rate;
+ config.output_sample_rate = asrc->asrc_rate;
+ } else {
+ config.input_format = asrc->asrc_format;
+ config.output_format = params_format(params);
+ config.input_sample_rate = asrc->asrc_rate;
+ config.output_sample_rate = rate;
+ }
+
+ fsl_asrc_select_clk(asrc_priv, pair,
+ config.input_sample_rate,
+ config.output_sample_rate);
+
+ ret = fsl_asrc_config_pair(pair, false);
+ 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 = {
+ .startup = fsl_asrc_dai_startup,
+ .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 = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai, &asrc->dma_params_tx,
+ &asrc->dma_params_rx);
+
+ return 0;
+}
+
+#define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_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,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_ASRC_FORMATS |
+ SNDRV_PCM_FMTBIT_S8,
+ },
+ .capture = {
+ .stream_name = "ASRC-Capture",
+ .channels_min = 1,
+ .channels_max = 10,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .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,
+};
+
+/**
+ * fsl_asrc_init - Initialize ASRC registers with a default configuration
+ * @asrc: ASRC context
+ */
+static int fsl_asrc_init(struct fsl_asrc *asrc)
+{
+ unsigned long ipg_rate;
+
+ /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */
+ regmap_write(asrc->regmap, REG_ASRCTR, ASRCTR_ASRCEN);
+
+ /* Disable interrupt by default */
+ regmap_write(asrc->regmap, REG_ASRIER, 0x0);
+
+ /* Apply recommended settings for parameters from Reference Manual */
+ regmap_write(asrc->regmap, REG_ASRPM1, 0x7fffff);
+ regmap_write(asrc->regmap, REG_ASRPM2, 0x255555);
+ regmap_write(asrc->regmap, REG_ASRPM3, 0xff7280);
+ regmap_write(asrc->regmap, REG_ASRPM4, 0xff7280);
+ regmap_write(asrc->regmap, REG_ASRPM5, 0xff7280);
+
+ /* Base address for task queue FIFO. Set to 0x7C */
+ regmap_update_bits(asrc->regmap, REG_ASRTFR1,
+ ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc));
+
+ /*
+ * Set the period of the 76KHz and 56KHz sampling clocks based on
+ * the ASRC processing clock.
+ * On iMX6, ipg_clk = 133MHz, REG_ASR76K = 0x06D6, REG_ASR56K = 0x0947
+ */
+ ipg_rate = clk_get_rate(asrc->ipg_clk);
+ regmap_write(asrc->regmap, REG_ASR76K, ipg_rate / 76000);
+ return regmap_write(asrc->regmap, REG_ASR56K, ipg_rate / 56000);
+}
+
+/**
+ * fsl_asrc_isr- Interrupt handler for ASRC
+ * @irq: irq number
+ * @dev_id: ASRC context
+ */
+static irqreturn_t fsl_asrc_isr(int irq, void *dev_id)
+{
+ struct fsl_asrc *asrc = (struct fsl_asrc *)dev_id;
+ struct device *dev = &asrc->pdev->dev;
+ enum asrc_pair_index index;
+ u32 status;
+
+ regmap_read(asrc->regmap, REG_ASRSTR, &status);
+
+ /* Clean overload error */
+ regmap_write(asrc->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->pair[index])
+ continue;
+
+ if (status & ASRSTR_ATQOL) {
+ asrc->pair[index]->error |= ASRC_TASK_Q_OVERLOAD;
+ dev_dbg(dev, "ASRC Task Queue FIFO overload\n");
+ }
+
+ if (status & ASRSTR_AOOL(index)) {
+ asrc->pair[index]->error |= ASRC_OUTPUT_TASK_OVERLOAD;
+ pair_dbg("Output Task Overload\n");
+ }
+
+ if (status & ASRSTR_AIOL(index)) {
+ asrc->pair[index]->error |= ASRC_INPUT_TASK_OVERLOAD;
+ pair_dbg("Input Task Overload\n");
+ }
+
+ if (status & ASRSTR_AODO(index)) {
+ asrc->pair[index]->error |= ASRC_OUTPUT_BUFFER_OVERFLOW;
+ pair_dbg("Output Data Buffer has overflowed\n");
+ }
+
+ if (status & ASRSTR_AIDU(index)) {
+ asrc->pair[index]->error |= ASRC_INPUT_BUFFER_UNDERRUN;
+ pair_dbg("Input Data Buffer has underflowed\n");
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int fsl_asrc_get_fifo_addr(u8 dir, enum asrc_pair_index index)
+{
+ return REG_ASRDx(dir, index);
+}
+
+static int fsl_asrc_runtime_resume(struct device *dev);
+static int fsl_asrc_runtime_suspend(struct device *dev);
+
+static int fsl_asrc_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_asrc_priv *asrc_priv;
+ struct fsl_asrc *asrc;
+ struct resource *res;
+ void __iomem *regs;
+ int irq, ret, i;
+ u32 asrc_fmt = 0;
+ u32 map_idx;
+ char tmp[16];
+ u32 width;
+
+ asrc = devm_kzalloc(&pdev->dev, sizeof(*asrc), GFP_KERNEL);
+ if (!asrc)
+ return -ENOMEM;
+
+ asrc_priv = devm_kzalloc(&pdev->dev, sizeof(*asrc_priv), GFP_KERNEL);
+ if (!asrc_priv)
+ return -ENOMEM;
+
+ asrc->pdev = pdev;
+ asrc->private = asrc_priv;
+
+ /* Get the addresses and IRQ */
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ asrc->paddr = res->start;
+
+ asrc->regmap = devm_regmap_init_mmio(&pdev->dev, regs, &fsl_asrc_regmap_config);
+ if (IS_ERR(asrc->regmap)) {
+ dev_err(&pdev->dev, "failed to init regmap\n");
+ return PTR_ERR(asrc->regmap);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(&pdev->dev, irq, fsl_asrc_isr, 0,
+ dev_name(&pdev->dev), asrc);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret);
+ return ret;
+ }
+
+ asrc->mem_clk = devm_clk_get(&pdev->dev, "mem");
+ if (IS_ERR(asrc->mem_clk)) {
+ dev_err(&pdev->dev, "failed to get mem clock\n");
+ return PTR_ERR(asrc->mem_clk);
+ }
+
+ asrc->ipg_clk = devm_clk_get(&pdev->dev, "ipg");
+ if (IS_ERR(asrc->ipg_clk)) {
+ dev_err(&pdev->dev, "failed to get ipg clock\n");
+ return PTR_ERR(asrc->ipg_clk);
+ }
+
+ asrc->spba_clk = devm_clk_get(&pdev->dev, "spba");
+ if (IS_ERR(asrc->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]);
+ }
+ }
+
+ asrc_priv->soc = of_device_get_match_data(&pdev->dev);
+ asrc->use_edma = asrc_priv->soc->use_edma;
+ asrc->get_dma_channel = fsl_asrc_get_dma_channel;
+ asrc->request_pair = fsl_asrc_request_pair;
+ asrc->release_pair = fsl_asrc_release_pair;
+ asrc->get_fifo_addr = fsl_asrc_get_fifo_addr;
+ asrc->pair_priv_size = sizeof(struct fsl_asrc_pair_priv);
+
+ if (of_device_is_compatible(np, "fsl,imx35-asrc")) {
+ asrc_priv->clk_map[IN] = input_clk_map_imx35;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx35;
+ } else if (of_device_is_compatible(np, "fsl,imx53-asrc")) {
+ asrc_priv->clk_map[IN] = input_clk_map_imx53;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx53;
+ } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc") ||
+ of_device_is_compatible(np, "fsl,imx8qxp-asrc")) {
+ ret = of_property_read_u32(np, "fsl,asrc-clk-map", &map_idx);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get clk map index\n");
+ return ret;
+ }
+
+ if (map_idx > 1) {
+ dev_err(&pdev->dev, "unsupported clk map index\n");
+ return -EINVAL;
+ }
+ if (of_device_is_compatible(np, "fsl,imx8qm-asrc")) {
+ asrc_priv->clk_map[IN] = clk_map_imx8qm[map_idx];
+ asrc_priv->clk_map[OUT] = clk_map_imx8qm[map_idx];
+ } else {
+ asrc_priv->clk_map[IN] = clk_map_imx8qxp[map_idx];
+ asrc_priv->clk_map[OUT] = clk_map_imx8qxp[map_idx];
+ }
+ }
+
+ asrc->channel_avail = 10;
+
+ ret = of_property_read_u32(np, "fsl,asrc-rate",
+ &asrc->asrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt);
+ asrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt;
+ if (ret) {
+ ret = of_property_read_u32(np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to decide output format\n");
+ return ret;
+ }
+
+ switch (width) {
+ case 16:
+ asrc->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ break;
+ case 24:
+ asrc->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ break;
+ default:
+ dev_warn(&pdev->dev,
+ "unsupported width, use default S24_LE\n");
+ asrc->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ break;
+ }
+ }
+
+ if (!(FSL_ASRC_FORMATS & pcm_format_to_bits(asrc->asrc_format))) {
+ dev_warn(&pdev->dev, "unsupported width, use default S24_LE\n");
+ asrc->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ }
+
+ platform_set_drvdata(pdev, asrc);
+ spin_lock_init(&asrc->lock);
+ pm_runtime_enable(&pdev->dev);
+ if (!pm_runtime_enabled(&pdev->dev)) {
+ ret = fsl_asrc_runtime_resume(&pdev->dev);
+ if (ret)
+ goto err_pm_disable;
+ }
+
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret < 0)
+ goto err_pm_get_sync;
+
+ ret = fsl_asrc_init(asrc);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init asrc %d\n", ret);
+ goto err_pm_get_sync;
+ }
+
+ ret = pm_runtime_put_sync(&pdev->dev);
+ if (ret < 0 && ret != -ENOSYS)
+ goto err_pm_get_sync;
+
+ 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");
+ goto err_pm_get_sync;
+ }
+
+ return 0;
+
+err_pm_get_sync:
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ fsl_asrc_runtime_suspend(&pdev->dev);
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static int fsl_asrc_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ fsl_asrc_runtime_suspend(&pdev->dev);
+
+ return 0;
+}
+
+static int fsl_asrc_runtime_resume(struct device *dev)
+{
+ struct fsl_asrc *asrc = dev_get_drvdata(dev);
+ struct fsl_asrc_priv *asrc_priv = asrc->private;
+ int reg, retry = INIT_RETRY_NUM;
+ int i, ret;
+ u32 asrctr;
+
+ ret = clk_prepare_enable(asrc->mem_clk);
+ if (ret)
+ return ret;
+ ret = clk_prepare_enable(asrc->ipg_clk);
+ if (ret)
+ goto disable_mem_clk;
+ if (!IS_ERR(asrc->spba_clk)) {
+ ret = clk_prepare_enable(asrc->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;
+ }
+
+ /* Stop all pairs provisionally */
+ regmap_read(asrc->regmap, REG_ASRCTR, &asrctr);
+ regmap_update_bits(asrc->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_ALL_MASK, 0);
+
+ /* Restore all registers */
+ regcache_cache_only(asrc->regmap, false);
+ regcache_mark_dirty(asrc->regmap);
+ regcache_sync(asrc->regmap);
+
+ regmap_update_bits(asrc->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->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_ALL_MASK, asrctr);
+
+ /* Wait for status of initialization for all enabled pairs */
+ do {
+ udelay(5);
+ regmap_read(asrc->regmap, REG_ASRCFG, &reg);
+ reg = (reg >> ASRCFG_INIRQi_SHIFT(0)) & 0x7;
+ } while ((reg != ((asrctr >> ASRCTR_ASRCEi_SHIFT(0)) & 0x7)) && --retry);
+
+ /*
+ * NOTE: Doesn't treat initialization timeout as an error
+ * Some of the pairs may success, then still can continue.
+ */
+ if (!retry) {
+ for (i = ASRC_PAIR_A; i < ASRC_PAIR_MAX_NUM; i++) {
+ if ((asrctr & ASRCTR_ASRCEi_MASK(i)) && !(reg & (1 << i)))
+ dev_warn(dev, "Pair %c initialization isn't finished\n", 'A' + i);
+ }
+ }
+
+ return 0;
+
+disable_asrck_clk:
+ for (i--; i >= 0; i--)
+ clk_disable_unprepare(asrc_priv->asrck_clk[i]);
+ if (!IS_ERR(asrc->spba_clk))
+ clk_disable_unprepare(asrc->spba_clk);
+disable_ipg_clk:
+ clk_disable_unprepare(asrc->ipg_clk);
+disable_mem_clk:
+ clk_disable_unprepare(asrc->mem_clk);
+ return ret;
+}
+
+static int fsl_asrc_runtime_suspend(struct device *dev)
+{
+ struct fsl_asrc *asrc = dev_get_drvdata(dev);
+ struct fsl_asrc_priv *asrc_priv = asrc->private;
+ int i;
+
+ regmap_read(asrc->regmap, REG_ASRCFG,
+ &asrc_priv->regcache_cfg);
+
+ regcache_cache_only(asrc->regmap, true);
+
+ for (i = 0; i < ASRC_CLK_MAX_NUM; i++)
+ clk_disable_unprepare(asrc_priv->asrck_clk[i]);
+ if (!IS_ERR(asrc->spba_clk))
+ clk_disable_unprepare(asrc->spba_clk);
+ clk_disable_unprepare(asrc->ipg_clk);
+ clk_disable_unprepare(asrc->mem_clk);
+
+ return 0;
+}
+
+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(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static const struct fsl_asrc_soc_data fsl_asrc_imx35_data = {
+ .use_edma = false,
+ .channel_bits = 3,
+};
+
+static const struct fsl_asrc_soc_data fsl_asrc_imx53_data = {
+ .use_edma = false,
+ .channel_bits = 4,
+};
+
+static const struct fsl_asrc_soc_data fsl_asrc_imx8qm_data = {
+ .use_edma = true,
+ .channel_bits = 4,
+};
+
+static const struct fsl_asrc_soc_data fsl_asrc_imx8qxp_data = {
+ .use_edma = true,
+ .channel_bits = 4,
+};
+
+static const struct of_device_id fsl_asrc_ids[] = {
+ { .compatible = "fsl,imx35-asrc", .data = &fsl_asrc_imx35_data },
+ { .compatible = "fsl,imx53-asrc", .data = &fsl_asrc_imx53_data },
+ { .compatible = "fsl,imx8qm-asrc", .data = &fsl_asrc_imx8qm_data },
+ { .compatible = "fsl,imx8qxp-asrc", .data = &fsl_asrc_imx8qxp_data },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_asrc_ids);
+
+static struct platform_driver fsl_asrc_driver = {
+ .probe = fsl_asrc_probe,
+ .remove = fsl_asrc_remove,
+ .driver = {
+ .name = "fsl-asrc",
+ .of_match_table = fsl_asrc_ids,
+ .pm = &fsl_asrc_pm,
+ },
+};
+module_platform_driver(fsl_asrc_driver);
+
+MODULE_DESCRIPTION("Freescale ASRC ASoC driver");
+MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>");
+MODULE_ALIAS("platform:fsl-asrc");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h
new file mode 100644
index 000000000..86d2422ad
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc.h
@@ -0,0 +1,464 @@
+/* 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
+
+#include "fsl_asrc_common.h"
+
+#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)
+
+#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,
+
+ /* clocks for imx8 */
+ INCLK_AUD_PLL_DIV_CLK0 = 0x10,
+ INCLK_AUD_PLL_DIV_CLK1 = 0x11,
+ INCLK_AUD_CLK0 = 0x12,
+ INCLK_AUD_CLK1 = 0x13,
+ INCLK_ESAI0_RX_CLK = 0x14,
+ INCLK_ESAI0_TX_CLK = 0x15,
+ INCLK_SPDIF0_RX = 0x16,
+ INCLK_SPDIF1_RX = 0x17,
+ INCLK_SAI0_RX_BCLK = 0x18,
+ INCLK_SAI0_TX_BCLK = 0x19,
+ INCLK_SAI1_RX_BCLK = 0x1a,
+ INCLK_SAI1_TX_BCLK = 0x1b,
+ INCLK_SAI2_RX_BCLK = 0x1c,
+ INCLK_SAI3_RX_BCLK = 0x1d,
+ INCLK_ASRC0_MUX_CLK = 0x1e,
+
+ INCLK_ESAI1_RX_CLK = 0x20,
+ INCLK_ESAI1_TX_CLK = 0x21,
+ INCLK_SAI6_TX_BCLK = 0x22,
+ INCLK_HDMI_RX_SAI0_RX_BCLK = 0x24,
+ INCLK_HDMI_TX_SAI0_TX_BCLK = 0x25,
+};
+
+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,
+
+ /* clocks for imx8 */
+ OUTCLK_AUD_PLL_DIV_CLK0 = 0x10,
+ OUTCLK_AUD_PLL_DIV_CLK1 = 0x11,
+ OUTCLK_AUD_CLK0 = 0x12,
+ OUTCLK_AUD_CLK1 = 0x13,
+ OUTCLK_ESAI0_RX_CLK = 0x14,
+ OUTCLK_ESAI0_TX_CLK = 0x15,
+ OUTCLK_SPDIF0_RX = 0x16,
+ OUTCLK_SPDIF1_RX = 0x17,
+ OUTCLK_SAI0_RX_BCLK = 0x18,
+ OUTCLK_SAI0_TX_BCLK = 0x19,
+ OUTCLK_SAI1_RX_BCLK = 0x1a,
+ OUTCLK_SAI1_TX_BCLK = 0x1b,
+ OUTCLK_SAI2_RX_BCLK = 0x1c,
+ OUTCLK_SAI3_RX_BCLK = 0x1d,
+ OUTCLK_ASRCO_MUX_CLK = 0x1e,
+
+ OUTCLK_ESAI1_RX_CLK = 0x20,
+ OUTCLK_ESAI1_TX_CLK = 0x21,
+ OUTCLK_SAI6_TX_BCLK = 0x22,
+ OUTCLK_HDMI_RX_SAI0_RX_BCLK = 0x24,
+ OUTCLK_HDMI_TX_SAI0_TX_BCLK = 0x25,
+};
+
+#define ASRC_CLK_MAX_NUM 16
+#define ASRC_CLK_MAP_LEN 0x30
+
+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;
+ snd_pcm_format_t input_format;
+ snd_pcm_format_t output_format;
+ 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_soc_data: soc specific data
+ *
+ * @use_edma: using edma as dma device or not
+ * @channel_bits: width of ASRCNCR register for each pair
+ */
+struct fsl_asrc_soc_data {
+ bool use_edma;
+ unsigned int channel_bits;
+};
+
+/**
+ * fsl_asrc_pair_priv: ASRC Pair private data
+ *
+ * @config: configuration profile
+ */
+struct fsl_asrc_pair_priv {
+ struct asrc_config *config;
+};
+
+/**
+ * fsl_asrc_priv: ASRC private data
+ *
+ * @asrck_clk: clock sources to driver ASRC internal logic
+ * @soc: soc specific data
+ * @clk_map: clock map for input/output clock
+ * @regcache_cfg: store register value of REG_ASRCFG
+ */
+struct fsl_asrc_priv {
+ struct clk *asrck_clk[ASRC_CLK_MAX_NUM];
+ const struct fsl_asrc_soc_data *soc;
+ unsigned char *clk_map[2];
+
+ u32 regcache_cfg;
+};
+
+#endif /* _FSL_ASRC_H */
diff --git a/sound/soc/fsl/fsl_asrc_common.h b/sound/soc/fsl/fsl_asrc_common.h
new file mode 100644
index 000000000..7e1c13ca3
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc_common.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2019 NXP
+ *
+ */
+
+#ifndef _FSL_ASRC_COMMON_H
+#define _FSL_ASRC_COMMON_H
+
+/* directions */
+#define IN 0
+#define OUT 1
+
+enum asrc_pair_index {
+ ASRC_INVALID_PAIR = -1,
+ ASRC_PAIR_A = 0,
+ ASRC_PAIR_B = 1,
+ ASRC_PAIR_C = 2,
+ ASRC_PAIR_D = 3,
+};
+
+#define PAIR_CTX_NUM 0x4
+
+/**
+ * fsl_asrc_pair: ASRC Pair common data
+ *
+ * @asrc: pointer to its parent module
+ * @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
+ * @req_dma_chan: flag to release dev_to_dev chan
+ * @private: pair private area
+ */
+struct fsl_asrc_pair {
+ struct fsl_asrc *asrc;
+ 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;
+ bool req_dma_chan;
+
+ void *private;
+};
+
+/**
+ * fsl_asrc: ASRC common 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)
+ * @lock: spin lock for resource protection
+ * @pair: pair pointers
+ * @channel_avail: non-occupied channel numbers
+ * @asrc_rate: default sample rate for ASoC Back-Ends
+ * @asrc_format: default sample format for ASoC Back-Ends
+ * @use_edma: edma is used
+ * @get_dma_channel: function pointer
+ * @request_pair: function pointer
+ * @release_pair: function pointer
+ * @get_fifo_addr: function pointer
+ * @pair_priv_size: size of pair private struct.
+ * @private: private data structure
+ */
+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;
+ spinlock_t lock; /* spin lock for resource protection */
+
+ struct fsl_asrc_pair *pair[PAIR_CTX_NUM];
+ unsigned int channel_avail;
+
+ int asrc_rate;
+ snd_pcm_format_t asrc_format;
+ bool use_edma;
+
+ struct dma_chan *(*get_dma_channel)(struct fsl_asrc_pair *pair, bool dir);
+ int (*request_pair)(int channels, struct fsl_asrc_pair *pair);
+ void (*release_pair)(struct fsl_asrc_pair *pair);
+ int (*get_fifo_addr)(u8 dir, enum asrc_pair_index index);
+ size_t pair_priv_size;
+
+ void *private;
+};
+
+#define DRV_NAME "fsl-asrc-dai"
+extern struct snd_soc_component_driver fsl_asrc_component;
+
+#endif /* _FSL_ASRC_COMMON_H */
diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c
new file mode 100644
index 000000000..05a7d1588
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc_dma.c
@@ -0,0 +1,464 @@
+// 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/dma/imx-dma.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_asrc_common.h"
+
+#define FSL_ASRC_DMABUF_SIZE (256 * 1024)
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .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,
+ struct snd_soc_component *component)
+{
+ u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ 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_soc_component *component,
+ 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, component);
+ 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_async(pair->dma_chan[OUT]);
+ dmaengine_terminate_async(pair->dma_chan[IN]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_asrc_dma_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ enum sdma_peripheral_type be_peripheral_type = IMX_DMATYPE_SSI;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL;
+ struct snd_dmaengine_dai_dma_data *dma_params_be = NULL;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ struct dma_chan *tmp_chan = NULL, *be_chan = NULL;
+ struct snd_soc_component *component_be = NULL;
+ struct fsl_asrc *asrc = pair->asrc;
+ struct dma_slave_config config_fe = {}, config_be = {};
+ struct sdma_peripheral_config audio_config;
+ enum asrc_pair_index index = pair->index;
+ struct device *dev = component->dev;
+ struct device_node *of_dma_node;
+ int stream = substream->stream;
+ struct imx_dma_data *tmp_data;
+ struct snd_soc_dpcm *dpcm;
+ struct device *dev_be;
+ u8 dir = tx ? OUT : IN;
+ dma_cap_mask_t mask;
+ int ret, width;
+
+ /* Fetch the Back-End dma_data from DPCM */
+ for_each_dpcm_be(rtd, stream, dpcm) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_pcm_substream *substream_be;
+ struct snd_soc_dai *dai = asoc_rtd_to_cpu(be, 0);
+
+ 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(asoc_rtd_to_cpu(rtd, 0), substream);
+ dma_params_fe->addr = asrc->paddr + asrc->get_fifo_addr(!dir, index);
+ dma_params_fe->maxburst = dma_params_be->maxburst;
+
+ pair->dma_chan[!dir] = asrc->get_dma_channel(pair, !dir);
+ if (!pair->dma_chan[!dir]) {
+ dev_err(dev, "failed to request DMA channel\n");
+ return -EINVAL;
+ }
+
+ 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);
+
+ /*
+ * The Back-End device might have already requested a DMA channel,
+ * so try to reuse it first, and then request a new one upon NULL.
+ */
+ component_be = snd_soc_lookup_component_nolocked(dev_be, SND_DMAENGINE_PCM_DRV_NAME);
+ if (component_be) {
+ be_chan = soc_component_to_pcm(component_be)->chan[substream->stream];
+ tmp_chan = be_chan;
+ }
+ if (!tmp_chan) {
+ tmp_chan = dma_request_chan(dev_be, tx ? "tx" : "rx");
+ if (IS_ERR(tmp_chan)) {
+ dev_err(dev, "failed to request DMA channel for Back-End\n");
+ return -EINVAL;
+ }
+ }
+
+ /*
+ * An EDMA DEV_TO_DEV channel is fixed and bound with DMA event of each
+ * peripheral, unlike SDMA channel that is allocated dynamically. So no
+ * need to configure dma_request and dma_request2, but get dma_chan of
+ * Back-End device directly via dma_request_chan.
+ */
+ if (!asrc->use_edma) {
+ /* Get DMA request of Back-End */
+ tmp_data = tmp_chan->private;
+ pair->dma_data.dma_request = tmp_data->dma_request;
+ be_peripheral_type = tmp_data->peripheral_type;
+ if (!be_chan)
+ dma_release_channel(tmp_chan);
+
+ /* Get DMA request of Front-End */
+ tmp_chan = 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);
+
+ of_dma_node = pair->dma_chan[!dir]->device->dev->of_node;
+ pair->dma_chan[dir] =
+ __dma_request_channel(&mask, filter, &pair->dma_data,
+ of_dma_node);
+ pair->req_dma_chan = true;
+ } else {
+ pair->dma_chan[dir] = tmp_chan;
+ /* Do not flag to release if we are reusing the Back-End one */
+ pair->req_dma_chan = !be_chan;
+ }
+
+ if (!pair->dma_chan[dir]) {
+ dev_err(dev, "failed to request DMA channel for Back-End\n");
+ return -EINVAL;
+ }
+
+ width = snd_pcm_format_physical_width(asrc->asrc_format);
+ if (width < 8 || width > 64)
+ return -EINVAL;
+ else if (width == 8)
+ buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ else if (width == 16)
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ else if (width == 24)
+ buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES;
+ else if (width <= 32)
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ else
+ buswidth = DMA_SLAVE_BUSWIDTH_8_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;
+
+ memset(&audio_config, 0, sizeof(audio_config));
+ config_be.peripheral_config = &audio_config;
+ config_be.peripheral_size = sizeof(audio_config);
+
+ if (tx && (be_peripheral_type == IMX_DMATYPE_SSI_DUAL ||
+ be_peripheral_type == IMX_DMATYPE_SPDIF))
+ audio_config.n_fifos_dst = 2;
+ if (!tx && (be_peripheral_type == IMX_DMATYPE_SSI_DUAL ||
+ be_peripheral_type == IMX_DMATYPE_SPDIF))
+ audio_config.n_fifos_src = 2;
+
+ if (tx) {
+ config_be.src_addr = asrc->paddr + asrc->get_fifo_addr(OUT, index);
+ config_be.dst_addr = dma_params_be->addr;
+ } else {
+ config_be.dst_addr = asrc->paddr + asrc->get_fifo_addr(IN, 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");
+ if (pair->req_dma_chan)
+ dma_release_channel(pair->dma_chan[dir]);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_asrc_dma_hw_free(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ u8 dir = tx ? OUT : IN;
+
+ if (pair->dma_chan[!dir])
+ dma_release_channel(pair->dma_chan[!dir]);
+
+ /* release dev_to_dev chan if we aren't reusing the Back-End one */
+ if (pair->dma_chan[dir] && pair->req_dma_chan)
+ dma_release_channel(pair->dma_chan[dir]);
+
+ pair->dma_chan[!dir] = NULL;
+ pair->dma_chan[dir] = NULL;
+
+ return 0;
+}
+
+static int fsl_asrc_dma_startup(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ struct device *dev = component->dev;
+ struct fsl_asrc *asrc = dev_get_drvdata(dev);
+ struct fsl_asrc_pair *pair;
+ struct dma_chan *tmp_chan = NULL;
+ u8 dir = tx ? OUT : IN;
+ bool release_pair = true;
+ int ret = 0;
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ dev_err(dev, "failed to set pcm hw params periods\n");
+ return ret;
+ }
+
+ pair = kzalloc(sizeof(*pair) + asrc->pair_priv_size, GFP_KERNEL);
+ if (!pair)
+ return -ENOMEM;
+
+ pair->asrc = asrc;
+ pair->private = (void *)pair + sizeof(struct fsl_asrc_pair);
+
+ runtime->private_data = pair;
+
+ /* Request a dummy pair, which will be released later.
+ * Request pair function needs channel num as input, for this
+ * dummy pair, we just request "1" channel temporarily.
+ */
+ ret = asrc->request_pair(1, pair);
+ if (ret < 0) {
+ dev_err(dev, "failed to request asrc pair\n");
+ goto req_pair_err;
+ }
+
+ /* Request a dummy dma channel, which will be released later. */
+ tmp_chan = asrc->get_dma_channel(pair, dir);
+ if (!tmp_chan) {
+ dev_err(dev, "failed to get dma channel\n");
+ ret = -EINVAL;
+ goto dma_chan_err;
+ }
+
+ dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+
+ /* Refine the snd_imx_hardware according to caps of DMA. */
+ ret = snd_dmaengine_pcm_refine_runtime_hwparams(substream,
+ dma_data,
+ &snd_imx_hardware,
+ tmp_chan);
+ if (ret < 0) {
+ dev_err(dev, "failed to refine runtime hwparams\n");
+ goto out;
+ }
+
+ release_pair = false;
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+out:
+ dma_release_channel(tmp_chan);
+
+dma_chan_err:
+ asrc->release_pair(pair);
+
+req_pair_err:
+ if (release_pair)
+ kfree(pair);
+
+ return ret;
+}
+
+static int fsl_asrc_dma_shutdown(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *pair = runtime->private_data;
+ struct fsl_asrc *asrc;
+
+ if (!pair)
+ return 0;
+
+ asrc = pair->asrc;
+
+ if (asrc->pair[pair->index] == pair)
+ asrc->pair[pair->index] = NULL;
+
+ kfree(pair);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+fsl_asrc_dma_pcm_pointer(struct snd_soc_component *component,
+ 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 int fsl_asrc_dma_pcm_new(struct snd_soc_component *component,
+ 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) {
+ dev_err(card->dev, "failed to set DMA mask\n");
+ return ret;
+ }
+
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, FSL_ASRC_DMABUF_SIZE);
+}
+
+struct snd_soc_component_driver fsl_asrc_component = {
+ .name = DRV_NAME,
+ .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,
+ .pcm_construct = fsl_asrc_dma_pcm_new,
+ .legacy_dai_naming = 1,
+};
+EXPORT_SYMBOL_GPL(fsl_asrc_component);
diff --git a/sound/soc/fsl/fsl_aud2htx.c b/sound/soc/fsl/fsl_aud2htx.c
new file mode 100644
index 000000000..1e421d9a0
--- /dev/null
+++ b/sound/soc/fsl/fsl_aud2htx.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2020 NXP
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/pm_qos.h>
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/dma-mapping.h>
+
+#include "fsl_aud2htx.h"
+#include "imx-pcm.h"
+
+static int fsl_aud2htx_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_aud2htx *aud2htx = snd_soc_dai_get_drvdata(dai);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL,
+ AUD2HTX_CTRL_EN, AUD2HTX_CTRL_EN);
+ regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT,
+ AUD2HTX_CTRE_DE, AUD2HTX_CTRE_DE);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT,
+ AUD2HTX_CTRE_DE, 0);
+ regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL,
+ AUD2HTX_CTRL_EN, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct snd_soc_dai_ops fsl_aud2htx_dai_ops = {
+ .trigger = fsl_aud2htx_trigger,
+};
+
+static int fsl_aud2htx_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_aud2htx *aud2htx = dev_get_drvdata(cpu_dai->dev);
+
+ /* DMA request when number of entries < WTMK_LOW */
+ regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT,
+ AUD2HTX_CTRE_DT_MASK, 0);
+
+ /* Disable interrupts*/
+ regmap_update_bits(aud2htx->regmap, AUD2HTX_IRQ_MASK,
+ AUD2HTX_WM_HIGH_IRQ_MASK |
+ AUD2HTX_WM_LOW_IRQ_MASK |
+ AUD2HTX_OVF_MASK,
+ AUD2HTX_WM_HIGH_IRQ_MASK |
+ AUD2HTX_WM_LOW_IRQ_MASK |
+ AUD2HTX_OVF_MASK);
+
+ /* Configure watermark */
+ regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT,
+ AUD2HTX_CTRE_WL_MASK,
+ AUD2HTX_WTMK_LOW << AUD2HTX_CTRE_WL_SHIFT);
+ regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT,
+ AUD2HTX_CTRE_WH_MASK,
+ AUD2HTX_WTMK_HIGH << AUD2HTX_CTRE_WH_SHIFT);
+
+ snd_soc_dai_init_dma_data(cpu_dai, &aud2htx->dma_params_tx,
+ &aud2htx->dma_params_rx);
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_aud2htx_dai = {
+ .probe = fsl_aud2htx_dai_probe,
+ .playback = {
+ .stream_name = "CPU-Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+ .formats = FSL_AUD2HTX_FORMATS,
+ },
+ .ops = &fsl_aud2htx_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_aud2htx_component = {
+ .name = "fsl-aud2htx",
+ .legacy_dai_naming = 1,
+};
+
+static const struct reg_default fsl_aud2htx_reg_defaults[] = {
+ {AUD2HTX_CTRL, 0x00000000},
+ {AUD2HTX_CTRL_EXT, 0x00000000},
+ {AUD2HTX_WR, 0x00000000},
+ {AUD2HTX_STATUS, 0x00000000},
+ {AUD2HTX_IRQ_NOMASK, 0x00000000},
+ {AUD2HTX_IRQ_MASKED, 0x00000000},
+ {AUD2HTX_IRQ_MASK, 0x00000000},
+};
+
+static bool fsl_aud2htx_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case AUD2HTX_CTRL:
+ case AUD2HTX_CTRL_EXT:
+ case AUD2HTX_STATUS:
+ case AUD2HTX_IRQ_NOMASK:
+ case AUD2HTX_IRQ_MASKED:
+ case AUD2HTX_IRQ_MASK:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_aud2htx_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case AUD2HTX_CTRL:
+ case AUD2HTX_CTRL_EXT:
+ case AUD2HTX_WR:
+ case AUD2HTX_IRQ_NOMASK:
+ case AUD2HTX_IRQ_MASKED:
+ case AUD2HTX_IRQ_MASK:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_aud2htx_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case AUD2HTX_STATUS:
+ case AUD2HTX_IRQ_NOMASK:
+ case AUD2HTX_IRQ_MASKED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config fsl_aud2htx_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .max_register = AUD2HTX_IRQ_MASK,
+ .reg_defaults = fsl_aud2htx_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_aud2htx_reg_defaults),
+ .readable_reg = fsl_aud2htx_readable_reg,
+ .volatile_reg = fsl_aud2htx_volatile_reg,
+ .writeable_reg = fsl_aud2htx_writeable_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const struct of_device_id fsl_aud2htx_dt_ids[] = {
+ { .compatible = "fsl,imx8mp-aud2htx",},
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_aud2htx_dt_ids);
+
+static irqreturn_t fsl_aud2htx_isr(int irq, void *dev_id)
+{
+ return IRQ_HANDLED;
+}
+
+static int fsl_aud2htx_probe(struct platform_device *pdev)
+{
+ struct fsl_aud2htx *aud2htx;
+ struct resource *res;
+ void __iomem *regs;
+ int ret, irq;
+
+ aud2htx = devm_kzalloc(&pdev->dev, sizeof(*aud2htx), GFP_KERNEL);
+ if (!aud2htx)
+ return -ENOMEM;
+
+ aud2htx->pdev = pdev;
+
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ aud2htx->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
+ &fsl_aud2htx_regmap_config);
+ if (IS_ERR(aud2htx->regmap)) {
+ dev_err(&pdev->dev, "failed to init regmap");
+ return PTR_ERR(aud2htx->regmap);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(&pdev->dev, irq, fsl_aud2htx_isr, 0,
+ dev_name(&pdev->dev), aud2htx);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret);
+ return ret;
+ }
+
+ aud2htx->bus_clk = devm_clk_get(&pdev->dev, "bus");
+ if (IS_ERR(aud2htx->bus_clk)) {
+ dev_err(&pdev->dev, "failed to get mem clock\n");
+ return PTR_ERR(aud2htx->bus_clk);
+ }
+
+ aud2htx->dma_params_tx.chan_name = "tx";
+ aud2htx->dma_params_tx.maxburst = AUD2HTX_MAXBURST;
+ aud2htx->dma_params_tx.addr = res->start + AUD2HTX_WR;
+
+ platform_set_drvdata(pdev, aud2htx);
+ pm_runtime_enable(&pdev->dev);
+
+ regcache_cache_only(aud2htx->regmap, true);
+
+ /*
+ * Register platform component before registering cpu dai for there
+ * is not defer probe for platform component in snd_soc_add_pcm_runtime().
+ */
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to pcm register\n");
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &fsl_aud2htx_component,
+ &fsl_aud2htx_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ASoC DAI\n");
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int fsl_aud2htx_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static int __maybe_unused fsl_aud2htx_runtime_suspend(struct device *dev)
+{
+ struct fsl_aud2htx *aud2htx = dev_get_drvdata(dev);
+
+ regcache_cache_only(aud2htx->regmap, true);
+ clk_disable_unprepare(aud2htx->bus_clk);
+
+ return 0;
+}
+
+static int __maybe_unused fsl_aud2htx_runtime_resume(struct device *dev)
+{
+ struct fsl_aud2htx *aud2htx = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(aud2htx->bus_clk);
+ if (ret)
+ return ret;
+
+ regcache_cache_only(aud2htx->regmap, false);
+ regcache_mark_dirty(aud2htx->regmap);
+ regcache_sync(aud2htx->regmap);
+
+ return 0;
+}
+
+static const struct dev_pm_ops fsl_aud2htx_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_aud2htx_runtime_suspend,
+ fsl_aud2htx_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_aud2htx_driver = {
+ .probe = fsl_aud2htx_probe,
+ .remove = fsl_aud2htx_remove,
+ .driver = {
+ .name = "fsl-aud2htx",
+ .pm = &fsl_aud2htx_pm_ops,
+ .of_match_table = fsl_aud2htx_dt_ids,
+ },
+};
+module_platform_driver(fsl_aud2htx_driver);
+
+MODULE_AUTHOR("Shengjiu Wang <Shengjiu.Wang@nxp.com>");
+MODULE_DESCRIPTION("NXP AUD2HTX driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_aud2htx.h b/sound/soc/fsl/fsl_aud2htx.h
new file mode 100644
index 000000000..ad70d6a76
--- /dev/null
+++ b/sound/soc/fsl/fsl_aud2htx.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2020 NXP
+ */
+
+#ifndef _FSL_AUD2HTX_H
+#define _FSL_AUD2HTX_H
+
+#define FSL_AUD2HTX_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+/* AUD2HTX Register Map */
+#define AUD2HTX_CTRL 0x0 /* AUD2HTX Control Register */
+#define AUD2HTX_CTRL_EXT 0x4 /* AUD2HTX Control Extended Register */
+#define AUD2HTX_WR 0x8 /* AUD2HTX Write Register */
+#define AUD2HTX_STATUS 0xC /* AUD2HTX Status Register */
+#define AUD2HTX_IRQ_NOMASK 0x10 /* AUD2HTX Nonmasked Interrupt Flags Register */
+#define AUD2HTX_IRQ_MASKED 0x14 /* AUD2HTX Masked Interrupt Flags Register */
+#define AUD2HTX_IRQ_MASK 0x18 /* AUD2HTX IRQ Masks Register */
+
+/* AUD2HTX Control Register */
+#define AUD2HTX_CTRL_EN BIT(0)
+
+/* AUD2HTX Control Extended Register */
+#define AUD2HTX_CTRE_DE BIT(0)
+#define AUD2HTX_CTRE_DT_SHIFT 0x1
+#define AUD2HTX_CTRE_DT_WIDTH 0x2
+#define AUD2HTX_CTRE_DT_MASK ((BIT(AUD2HTX_CTRE_DT_WIDTH) - 1) \
+ << AUD2HTX_CTRE_DT_SHIFT)
+#define AUD2HTX_CTRE_WL_SHIFT 16
+#define AUD2HTX_CTRE_WL_WIDTH 5
+#define AUD2HTX_CTRE_WL_MASK ((BIT(AUD2HTX_CTRE_WL_WIDTH) - 1) \
+ << AUD2HTX_CTRE_WL_SHIFT)
+#define AUD2HTX_CTRE_WH_SHIFT 24
+#define AUD2HTX_CTRE_WH_WIDTH 5
+#define AUD2HTX_CTRE_WH_MASK ((BIT(AUD2HTX_CTRE_WH_WIDTH) - 1) \
+ << AUD2HTX_CTRE_WH_SHIFT)
+
+/* AUD2HTX IRQ Masks Register */
+#define AUD2HTX_WM_HIGH_IRQ_MASK BIT(2)
+#define AUD2HTX_WM_LOW_IRQ_MASK BIT(1)
+#define AUD2HTX_OVF_MASK BIT(0)
+
+#define AUD2HTX_FIFO_DEPTH 0x20
+#define AUD2HTX_WTMK_LOW 0x10
+#define AUD2HTX_WTMK_HIGH 0x10
+#define AUD2HTX_MAXBURST 0x10
+
+/**
+ * fsl_aud2htx: AUD2HTX private data
+ *
+ * @pdev: platform device pointer
+ * @regmap: regmap handler
+ * @bus_clk: clock source to access register
+ * @dma_params_rx: DMA parameters for receive channel
+ * @dma_params_tx: DMA parameters for transmit channel
+ */
+struct fsl_aud2htx {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct clk *bus_clk;
+
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct snd_dmaengine_dai_dma_data dma_params_tx;
+};
+
+#endif /* _FSL_AUD2HTX_H */
diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c
new file mode 100644
index 000000000..672148dd4
--- /dev/null
+++ b/sound/soc/fsl/fsl_audmix.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NXP AUDMIX ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright 2017 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_audmix.h"
+
+#define SOC_ENUM_SINGLE_S(xreg, xshift, xtexts) \
+ SOC_ENUM_SINGLE(xreg, xshift, ARRAY_SIZE(xtexts), xtexts)
+
+static const char
+ *tdm_sel[] = { "TDM1", "TDM2", },
+ *mode_sel[] = { "Disabled", "TDM1", "TDM2", "Mixed", },
+ *width_sel[] = { "16b", "18b", "20b", "24b", "32b", },
+ *endis_sel[] = { "Disabled", "Enabled", },
+ *updn_sel[] = { "Downward", "Upward", },
+ *mask_sel[] = { "Unmask", "Mask", };
+
+static const struct soc_enum fsl_audmix_enum[] = {
+/* FSL_AUDMIX_CTR enums */
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MIXCLK_SHIFT, tdm_sel),
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_OUTSRC_SHIFT, mode_sel),
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_OUTWIDTH_SHIFT, width_sel),
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MASKRTDF_SHIFT, mask_sel),
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_MASKCKDF_SHIFT, mask_sel),
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_SYNCMODE_SHIFT, endis_sel),
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_CTR, FSL_AUDMIX_CTR_SYNCSRC_SHIFT, tdm_sel),
+/* FSL_AUDMIX_ATCR0 enums */
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR0, 0, endis_sel),
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR0, 1, updn_sel),
+/* FSL_AUDMIX_ATCR1 enums */
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR1, 0, endis_sel),
+SOC_ENUM_SINGLE_S(FSL_AUDMIX_ATCR1, 1, updn_sel),
+};
+
+struct fsl_audmix_state {
+ u8 tdms;
+ u8 clk;
+ char msg[64];
+};
+
+static const struct fsl_audmix_state prms[4][4] = {{
+ /* DIS->DIS, do nothing */
+ { .tdms = 0, .clk = 0, .msg = "" },
+ /* DIS->TDM1*/
+ { .tdms = 1, .clk = 1, .msg = "DIS->TDM1: TDM1 not started!\n" },
+ /* DIS->TDM2*/
+ { .tdms = 2, .clk = 2, .msg = "DIS->TDM2: TDM2 not started!\n" },
+ /* DIS->MIX */
+ { .tdms = 3, .clk = 0, .msg = "DIS->MIX: Please start both TDMs!\n" }
+}, { /* TDM1->DIS */
+ { .tdms = 1, .clk = 0, .msg = "TDM1->DIS: TDM1 not started!\n" },
+ /* TDM1->TDM1, do nothing */
+ { .tdms = 0, .clk = 0, .msg = "" },
+ /* TDM1->TDM2 */
+ { .tdms = 3, .clk = 2, .msg = "TDM1->TDM2: Please start both TDMs!\n" },
+ /* TDM1->MIX */
+ { .tdms = 3, .clk = 0, .msg = "TDM1->MIX: Please start both TDMs!\n" }
+}, { /* TDM2->DIS */
+ { .tdms = 2, .clk = 0, .msg = "TDM2->DIS: TDM2 not started!\n" },
+ /* TDM2->TDM1 */
+ { .tdms = 3, .clk = 1, .msg = "TDM2->TDM1: Please start both TDMs!\n" },
+ /* TDM2->TDM2, do nothing */
+ { .tdms = 0, .clk = 0, .msg = "" },
+ /* TDM2->MIX */
+ { .tdms = 3, .clk = 0, .msg = "TDM2->MIX: Please start both TDMs!\n" }
+}, { /* MIX->DIS */
+ { .tdms = 3, .clk = 0, .msg = "MIX->DIS: Please start both TDMs!\n" },
+ /* MIX->TDM1 */
+ { .tdms = 3, .clk = 1, .msg = "MIX->TDM1: Please start both TDMs!\n" },
+ /* MIX->TDM2 */
+ { .tdms = 3, .clk = 2, .msg = "MIX->TDM2: Please start both TDMs!\n" },
+ /* MIX->MIX, do nothing */
+ { .tdms = 0, .clk = 0, .msg = "" }
+}, };
+
+static int fsl_audmix_state_trans(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr,
+ const struct fsl_audmix_state prm)
+{
+ struct fsl_audmix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce all required TDMs are started */
+ if ((priv->tdms & prm.tdms) != prm.tdms) {
+ dev_dbg(comp->dev, "%s", prm.msg);
+ return -EINVAL;
+ }
+
+ switch (prm.clk) {
+ case 1:
+ case 2:
+ /* Set mix clock */
+ (*mask) |= FSL_AUDMIX_CTR_MIXCLK_MASK;
+ (*ctr) |= FSL_AUDMIX_CTR_MIXCLK(prm.clk - 1);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int fsl_audmix_put_mix_clk_src(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_audmix *priv = snd_soc_component_get_drvdata(comp);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ unsigned int reg_val, val, mix_clk;
+
+ /* Get current state */
+ reg_val = snd_soc_component_read(comp, FSL_AUDMIX_CTR);
+ mix_clk = ((reg_val & FSL_AUDMIX_CTR_MIXCLK_MASK)
+ >> FSL_AUDMIX_CTR_MIXCLK_SHIFT);
+ val = snd_soc_enum_item_to_val(e, item[0]);
+
+ dev_dbg(comp->dev, "TDMs=x%08x, val=x%08x\n", priv->tdms, val);
+
+ /**
+ * Ensure the current selected mixer clock is available
+ * for configuration propagation
+ */
+ if (!(priv->tdms & BIT(mix_clk))) {
+ dev_err(comp->dev,
+ "Started TDM%d needed for config propagation!\n",
+ mix_clk + 1);
+ return -EINVAL;
+ }
+
+ if (!(priv->tdms & BIT(val))) {
+ dev_err(comp->dev,
+ "The selected clock source has no TDM%d enabled!\n",
+ val + 1);
+ return -EINVAL;
+ }
+
+ return snd_soc_put_enum_double(kcontrol, ucontrol);
+}
+
+static int fsl_audmix_put_out_src(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_audmix *priv = snd_soc_component_get_drvdata(comp);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ u32 out_src, mix_clk;
+ unsigned int reg_val, val, mask = 0, ctr = 0;
+ int ret;
+
+ /* Get current state */
+ reg_val = snd_soc_component_read(comp, FSL_AUDMIX_CTR);
+
+ /* "From" state */
+ out_src = ((reg_val & FSL_AUDMIX_CTR_OUTSRC_MASK)
+ >> FSL_AUDMIX_CTR_OUTSRC_SHIFT);
+ mix_clk = ((reg_val & FSL_AUDMIX_CTR_MIXCLK_MASK)
+ >> FSL_AUDMIX_CTR_MIXCLK_SHIFT);
+
+ /* "To" state */
+ val = snd_soc_enum_item_to_val(e, item[0]);
+
+ dev_dbg(comp->dev, "TDMs=x%08x, val=x%08x\n", priv->tdms, val);
+
+ /* Check if state is changing ... */
+ if (out_src == val)
+ return 0;
+ /**
+ * Ensure the current selected mixer clock is available
+ * for configuration propagation
+ */
+ if (!(priv->tdms & BIT(mix_clk))) {
+ dev_err(comp->dev,
+ "Started TDM%d needed for config propagation!\n",
+ mix_clk + 1);
+ return -EINVAL;
+ }
+
+ /* Check state transition constraints */
+ ret = fsl_audmix_state_trans(comp, &mask, &ctr, prms[out_src][val]);
+ if (ret)
+ return ret;
+
+ /* Complete transition to new state */
+ mask |= FSL_AUDMIX_CTR_OUTSRC_MASK;
+ ctr |= FSL_AUDMIX_CTR_OUTSRC(val);
+
+ return snd_soc_component_update_bits(comp, FSL_AUDMIX_CTR, mask, ctr);
+}
+
+static const struct snd_kcontrol_new fsl_audmix_snd_controls[] = {
+ /* FSL_AUDMIX_CTR controls */
+ SOC_ENUM_EXT("Mixing Clock Source", fsl_audmix_enum[0],
+ snd_soc_get_enum_double, fsl_audmix_put_mix_clk_src),
+ SOC_ENUM_EXT("Output Source", fsl_audmix_enum[1],
+ snd_soc_get_enum_double, fsl_audmix_put_out_src),
+ SOC_ENUM("Output Width", fsl_audmix_enum[2]),
+ SOC_ENUM("Frame Rate Diff Error", fsl_audmix_enum[3]),
+ SOC_ENUM("Clock Freq Diff Error", fsl_audmix_enum[4]),
+ SOC_ENUM("Sync Mode Config", fsl_audmix_enum[5]),
+ SOC_ENUM("Sync Mode Clk Source", fsl_audmix_enum[6]),
+ /* TDM1 Attenuation controls */
+ SOC_ENUM("TDM1 Attenuation", fsl_audmix_enum[7]),
+ SOC_ENUM("TDM1 Attenuation Direction", fsl_audmix_enum[8]),
+ SOC_SINGLE("TDM1 Attenuation Step Divider", FSL_AUDMIX_ATCR0,
+ 2, 0x00fff, 0),
+ SOC_SINGLE("TDM1 Attenuation Initial Value", FSL_AUDMIX_ATIVAL0,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM1 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP0,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM1 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN0,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM1 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT0,
+ 0, 0x3ffff, 0),
+ /* TDM2 Attenuation controls */
+ SOC_ENUM("TDM2 Attenuation", fsl_audmix_enum[9]),
+ SOC_ENUM("TDM2 Attenuation Direction", fsl_audmix_enum[10]),
+ SOC_SINGLE("TDM2 Attenuation Step Divider", FSL_AUDMIX_ATCR1,
+ 2, 0x00fff, 0),
+ SOC_SINGLE("TDM2 Attenuation Initial Value", FSL_AUDMIX_ATIVAL1,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM2 Attenuation Step Up Factor", FSL_AUDMIX_ATSTPUP1,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM2 Attenuation Step Down Factor", FSL_AUDMIX_ATSTPDN1,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM2 Attenuation Step Target", FSL_AUDMIX_ATSTPTGT1,
+ 0, 0x3ffff, 0),
+};
+
+static int fsl_audmix_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *comp = dai->component;
+ u32 mask = 0, ctr = 0;
+
+ /* AUDMIX is working in DSP_A format only */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* For playback the AUDMIX is consumer, and for record is provider */
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ case SND_SOC_DAIFMT_BP_FP:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ /* Output data will be written on positive edge of the clock */
+ ctr |= FSL_AUDMIX_CTR_OUTCKPOL(0);
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ /* Output data will be written on negative edge of the clock */
+ ctr |= FSL_AUDMIX_CTR_OUTCKPOL(1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask |= FSL_AUDMIX_CTR_OUTCKPOL_MASK;
+
+ return snd_soc_component_update_bits(comp, FSL_AUDMIX_CTR, mask, ctr);
+}
+
+static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_audmix *priv = snd_soc_dai_get_drvdata(dai);
+ unsigned long lock_flags;
+
+ /* Capture stream shall not be handled */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ return 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&priv->lock, lock_flags);
+ priv->tdms |= BIT(dai->driver->id);
+ spin_unlock_irqrestore(&priv->lock, lock_flags);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&priv->lock, lock_flags);
+ priv->tdms &= ~BIT(dai->driver->id);
+ spin_unlock_irqrestore(&priv->lock, lock_flags);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops fsl_audmix_dai_ops = {
+ .set_fmt = fsl_audmix_dai_set_fmt,
+ .trigger = fsl_audmix_dai_trigger,
+};
+
+static struct snd_soc_dai_driver fsl_audmix_dai[] = {
+ {
+ .id = 0,
+ .name = "audmix-0",
+ .playback = {
+ .stream_name = "AUDMIX-Playback-0",
+ .channels_min = 8,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_AUDMIX_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AUDMIX-Capture-0",
+ .channels_min = 8,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_AUDMIX_FORMATS,
+ },
+ .ops = &fsl_audmix_dai_ops,
+ },
+ {
+ .id = 1,
+ .name = "audmix-1",
+ .playback = {
+ .stream_name = "AUDMIX-Playback-1",
+ .channels_min = 8,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_AUDMIX_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AUDMIX-Capture-1",
+ .channels_min = 8,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_AUDMIX_FORMATS,
+ },
+ .ops = &fsl_audmix_dai_ops,
+ },
+};
+
+static const struct snd_soc_component_driver fsl_audmix_component = {
+ .name = "fsl-audmix-dai",
+ .controls = fsl_audmix_snd_controls,
+ .num_controls = ARRAY_SIZE(fsl_audmix_snd_controls),
+};
+
+static bool fsl_audmix_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_AUDMIX_CTR:
+ case FSL_AUDMIX_STR:
+ case FSL_AUDMIX_ATCR0:
+ case FSL_AUDMIX_ATIVAL0:
+ case FSL_AUDMIX_ATSTPUP0:
+ case FSL_AUDMIX_ATSTPDN0:
+ case FSL_AUDMIX_ATSTPTGT0:
+ case FSL_AUDMIX_ATTNVAL0:
+ case FSL_AUDMIX_ATSTP0:
+ case FSL_AUDMIX_ATCR1:
+ case FSL_AUDMIX_ATIVAL1:
+ case FSL_AUDMIX_ATSTPUP1:
+ case FSL_AUDMIX_ATSTPDN1:
+ case FSL_AUDMIX_ATSTPTGT1:
+ case FSL_AUDMIX_ATTNVAL1:
+ case FSL_AUDMIX_ATSTP1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_audmix_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_AUDMIX_CTR:
+ case FSL_AUDMIX_ATCR0:
+ case FSL_AUDMIX_ATIVAL0:
+ case FSL_AUDMIX_ATSTPUP0:
+ case FSL_AUDMIX_ATSTPDN0:
+ case FSL_AUDMIX_ATSTPTGT0:
+ case FSL_AUDMIX_ATCR1:
+ case FSL_AUDMIX_ATIVAL1:
+ case FSL_AUDMIX_ATSTPUP1:
+ case FSL_AUDMIX_ATSTPDN1:
+ case FSL_AUDMIX_ATSTPTGT1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct reg_default fsl_audmix_reg[] = {
+ { FSL_AUDMIX_CTR, 0x00060 },
+ { FSL_AUDMIX_STR, 0x00003 },
+ { FSL_AUDMIX_ATCR0, 0x00000 },
+ { FSL_AUDMIX_ATIVAL0, 0x3FFFF },
+ { FSL_AUDMIX_ATSTPUP0, 0x2AAAA },
+ { FSL_AUDMIX_ATSTPDN0, 0x30000 },
+ { FSL_AUDMIX_ATSTPTGT0, 0x00010 },
+ { FSL_AUDMIX_ATTNVAL0, 0x00000 },
+ { FSL_AUDMIX_ATSTP0, 0x00000 },
+ { FSL_AUDMIX_ATCR1, 0x00000 },
+ { FSL_AUDMIX_ATIVAL1, 0x3FFFF },
+ { FSL_AUDMIX_ATSTPUP1, 0x2AAAA },
+ { FSL_AUDMIX_ATSTPDN1, 0x30000 },
+ { FSL_AUDMIX_ATSTPTGT1, 0x00010 },
+ { FSL_AUDMIX_ATTNVAL1, 0x00000 },
+ { FSL_AUDMIX_ATSTP1, 0x00000 },
+};
+
+static const struct regmap_config fsl_audmix_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = FSL_AUDMIX_ATSTP1,
+ .reg_defaults = fsl_audmix_reg,
+ .num_reg_defaults = ARRAY_SIZE(fsl_audmix_reg),
+ .readable_reg = fsl_audmix_readable_reg,
+ .writeable_reg = fsl_audmix_writeable_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct of_device_id fsl_audmix_ids[] = {
+ {
+ .compatible = "fsl,imx8qm-audmix",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_audmix_ids);
+
+static int fsl_audmix_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fsl_audmix *priv;
+ void __iomem *regs;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Get the addresses */
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ priv->regmap = devm_regmap_init_mmio(dev, regs, &fsl_audmix_regmap_config);
+ if (IS_ERR(priv->regmap)) {
+ dev_err(dev, "failed to init regmap\n");
+ return PTR_ERR(priv->regmap);
+ }
+
+ priv->ipg_clk = devm_clk_get(dev, "ipg");
+ if (IS_ERR(priv->ipg_clk)) {
+ dev_err(dev, "failed to get ipg clock\n");
+ return PTR_ERR(priv->ipg_clk);
+ }
+
+ spin_lock_init(&priv->lock);
+ platform_set_drvdata(pdev, priv);
+ pm_runtime_enable(dev);
+
+ ret = devm_snd_soc_register_component(dev, &fsl_audmix_component,
+ fsl_audmix_dai,
+ ARRAY_SIZE(fsl_audmix_dai));
+ if (ret) {
+ dev_err(dev, "failed to register ASoC DAI\n");
+ goto err_disable_pm;
+ }
+
+ priv->pdev = platform_device_register_data(dev, "imx-audmix", 0, NULL, 0);
+ if (IS_ERR(priv->pdev)) {
+ ret = PTR_ERR(priv->pdev);
+ dev_err(dev, "failed to register platform: %d\n", ret);
+ goto err_disable_pm;
+ }
+
+ return 0;
+
+err_disable_pm:
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static int fsl_audmix_remove(struct platform_device *pdev)
+{
+ struct fsl_audmix *priv = dev_get_drvdata(&pdev->dev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ if (priv->pdev)
+ platform_device_unregister(priv->pdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_audmix_runtime_resume(struct device *dev)
+{
+ struct fsl_audmix *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(priv->ipg_clk);
+ if (ret) {
+ dev_err(dev, "Failed to enable IPG clock: %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(priv->regmap, false);
+ regcache_mark_dirty(priv->regmap);
+
+ return regcache_sync(priv->regmap);
+}
+
+static int fsl_audmix_runtime_suspend(struct device *dev)
+{
+ struct fsl_audmix *priv = dev_get_drvdata(dev);
+
+ regcache_cache_only(priv->regmap, true);
+
+ clk_disable_unprepare(priv->ipg_clk);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops fsl_audmix_pm = {
+ SET_RUNTIME_PM_OPS(fsl_audmix_runtime_suspend,
+ fsl_audmix_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_audmix_driver = {
+ .probe = fsl_audmix_probe,
+ .remove = fsl_audmix_remove,
+ .driver = {
+ .name = "fsl-audmix",
+ .of_match_table = fsl_audmix_ids,
+ .pm = &fsl_audmix_pm,
+ },
+};
+module_platform_driver(fsl_audmix_driver);
+
+MODULE_DESCRIPTION("NXP AUDMIX ASoC DAI driver");
+MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>");
+MODULE_ALIAS("platform:fsl-audmix");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_audmix.h b/sound/soc/fsl/fsl_audmix.h
new file mode 100644
index 000000000..479f05695
--- /dev/null
+++ b/sound/soc/fsl/fsl_audmix.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * NXP AUDMIX ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright 2017 NXP
+ */
+
+#ifndef __FSL_AUDMIX_H
+#define __FSL_AUDMIX_H
+
+#define FSL_AUDMIX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+/* AUDMIX Registers */
+#define FSL_AUDMIX_CTR 0x200 /* Control */
+#define FSL_AUDMIX_STR 0x204 /* Status */
+
+#define FSL_AUDMIX_ATCR0 0x208 /* Attenuation Control */
+#define FSL_AUDMIX_ATIVAL0 0x20c /* Attenuation Initial Value */
+#define FSL_AUDMIX_ATSTPUP0 0x210 /* Attenuation step up factor */
+#define FSL_AUDMIX_ATSTPDN0 0x214 /* Attenuation step down factor */
+#define FSL_AUDMIX_ATSTPTGT0 0x218 /* Attenuation step target */
+#define FSL_AUDMIX_ATTNVAL0 0x21c /* Attenuation Value */
+#define FSL_AUDMIX_ATSTP0 0x220 /* Attenuation step number */
+
+#define FSL_AUDMIX_ATCR1 0x228 /* Attenuation Control */
+#define FSL_AUDMIX_ATIVAL1 0x22c /* Attenuation Initial Value */
+#define FSL_AUDMIX_ATSTPUP1 0x230 /* Attenuation step up factor */
+#define FSL_AUDMIX_ATSTPDN1 0x234 /* Attenuation step down factor */
+#define FSL_AUDMIX_ATSTPTGT1 0x238 /* Attenuation step target */
+#define FSL_AUDMIX_ATTNVAL1 0x23c /* Attenuation Value */
+#define FSL_AUDMIX_ATSTP1 0x240 /* Attenuation step number */
+
+/* AUDMIX Control Register */
+#define FSL_AUDMIX_CTR_MIXCLK_SHIFT 0
+#define FSL_AUDMIX_CTR_MIXCLK_MASK BIT(FSL_AUDMIX_CTR_MIXCLK_SHIFT)
+#define FSL_AUDMIX_CTR_MIXCLK(i) ((i) << FSL_AUDMIX_CTR_MIXCLK_SHIFT)
+#define FSL_AUDMIX_CTR_OUTSRC_SHIFT 1
+#define FSL_AUDMIX_CTR_OUTSRC_MASK (0x3 << FSL_AUDMIX_CTR_OUTSRC_SHIFT)
+#define FSL_AUDMIX_CTR_OUTSRC(i) (((i) << FSL_AUDMIX_CTR_OUTSRC_SHIFT)\
+ & FSL_AUDMIX_CTR_OUTSRC_MASK)
+#define FSL_AUDMIX_CTR_OUTWIDTH_SHIFT 3
+#define FSL_AUDMIX_CTR_OUTWIDTH_MASK (0x7 << FSL_AUDMIX_CTR_OUTWIDTH_SHIFT)
+#define FSL_AUDMIX_CTR_OUTWIDTH(i) (((i) << FSL_AUDMIX_CTR_OUTWIDTH_SHIFT)\
+ & FSL_AUDMIX_CTR_OUTWIDTH_MASK)
+#define FSL_AUDMIX_CTR_OUTCKPOL_SHIFT 6
+#define FSL_AUDMIX_CTR_OUTCKPOL_MASK BIT(FSL_AUDMIX_CTR_OUTCKPOL_SHIFT)
+#define FSL_AUDMIX_CTR_OUTCKPOL(i) ((i) << FSL_AUDMIX_CTR_OUTCKPOL_SHIFT)
+#define FSL_AUDMIX_CTR_MASKRTDF_SHIFT 7
+#define FSL_AUDMIX_CTR_MASKRTDF_MASK BIT(FSL_AUDMIX_CTR_MASKRTDF_SHIFT)
+#define FSL_AUDMIX_CTR_MASKRTDF(i) ((i) << FSL_AUDMIX_CTR_MASKRTDF_SHIFT)
+#define FSL_AUDMIX_CTR_MASKCKDF_SHIFT 8
+#define FSL_AUDMIX_CTR_MASKCKDF_MASK BIT(FSL_AUDMIX_CTR_MASKCKDF_SHIFT)
+#define FSL_AUDMIX_CTR_MASKCKDF(i) ((i) << FSL_AUDMIX_CTR_MASKCKDF_SHIFT)
+#define FSL_AUDMIX_CTR_SYNCMODE_SHIFT 9
+#define FSL_AUDMIX_CTR_SYNCMODE_MASK BIT(FSL_AUDMIX_CTR_SYNCMODE_SHIFT)
+#define FSL_AUDMIX_CTR_SYNCMODE(i) ((i) << FSL_AUDMIX_CTR_SYNCMODE_SHIFT)
+#define FSL_AUDMIX_CTR_SYNCSRC_SHIFT 10
+#define FSL_AUDMIX_CTR_SYNCSRC_MASK BIT(FSL_AUDMIX_CTR_SYNCSRC_SHIFT)
+#define FSL_AUDMIX_CTR_SYNCSRC(i) ((i) << FSL_AUDMIX_CTR_SYNCSRC_SHIFT)
+
+/* AUDMIX Status Register */
+#define FSL_AUDMIX_STR_RATEDIFF BIT(0)
+#define FSL_AUDMIX_STR_CLKDIFF BIT(1)
+#define FSL_AUDMIX_STR_MIXSTAT_SHIFT 2
+#define FSL_AUDMIX_STR_MIXSTAT_MASK (0x3 << FSL_AUDMIX_STR_MIXSTAT_SHIFT)
+#define FSL_AUDMIX_STR_MIXSTAT(i) (((i) & FSL_AUDMIX_STR_MIXSTAT_MASK) \
+ >> FSL_AUDMIX_STR_MIXSTAT_SHIFT)
+/* AUDMIX Attenuation Control Register */
+#define FSL_AUDMIX_ATCR_AT_EN BIT(0)
+#define FSL_AUDMIX_ATCR_AT_UPDN BIT(1)
+#define FSL_AUDMIX_ATCR_ATSTPDIF_SHIFT 2
+#define FSL_AUDMIX_ATCR_ATSTPDFI_MASK \
+ (0xfff << FSL_AUDMIX_ATCR_ATSTPDIF_SHIFT)
+
+/* AUDMIX Attenuation Initial Value Register */
+#define FSL_AUDMIX_ATIVAL_ATINVAL_MASK 0x3FFFF
+
+/* AUDMIX Attenuation Step Up Factor Register */
+#define FSL_AUDMIX_ATSTPUP_ATSTEPUP_MASK 0x3FFFF
+
+/* AUDMIX Attenuation Step Down Factor Register */
+#define FSL_AUDMIX_ATSTPDN_ATSTEPDN_MASK 0x3FFFF
+
+/* AUDMIX Attenuation Step Target Register */
+#define FSL_AUDMIX_ATSTPTGT_ATSTPTG_MASK 0x3FFFF
+
+/* AUDMIX Attenuation Value Register */
+#define FSL_AUDMIX_ATTNVAL_ATCURVAL_MASK 0x3FFFF
+
+/* AUDMIX Attenuation Step Number Register */
+#define FSL_AUDMIX_ATSTP_STPCTR_MASK 0x3FFFF
+
+#define FSL_AUDMIX_MAX_DAIS 2
+struct fsl_audmix {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct clk *ipg_clk;
+ spinlock_t lock; /* Protect tdms */
+ u8 tdms;
+};
+
+#endif /* __FSL_AUDMIX_H */
diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c
new file mode 100644
index 000000000..808fb61a7
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.c
@@ -0,0 +1,923 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale DMA ALSA SoC PCM driver
+//
+// Author: Timur Tabi <timur@freescale.com>
+//
+// Copyright 2007-2010 Freescale Semiconductor, Inc.
+//
+// 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 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 = asoc_substream_to_rtd(substream);
+ struct device *dev = rtd->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_component *component,
+ 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;
+
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev,
+ fsl_dma_hardware.buffer_bytes_max);
+}
+
+/**
+ * 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_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ 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;
+ 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;
+ }
+
+ 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_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_soc_component *component,
+ 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 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_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+ 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_soc_component *component,
+ 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_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_dma_private *dma_private = runtime->private_data;
+ 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;
+}
+
+/**
+ * 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 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.open = fsl_dma_open;
+ dma->dai.close = fsl_dma_close;
+ dma->dai.hw_params = fsl_dma_hw_params;
+ dma->dai.hw_free = fsl_dma_hw_free;
+ dma->dai.pointer = fsl_dma_pointer;
+ dma->dai.pcm_construct = fsl_dma_new;
+
+ /* 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 000000000..f19ae765b
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC
+ */
+
+#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_easrc.c b/sound/soc/fsl/fsl_easrc.c
new file mode 100644
index 000000000..84e6f9eb7
--- /dev/null
+++ b/sound/soc/fsl/fsl_easrc.c
@@ -0,0 +1,2110 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2019 NXP
+
+#include <linux/atomic.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/kobject.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/sched/signal.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/gcd.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/core.h>
+
+#include "fsl_easrc.h"
+#include "imx-pcm.h"
+
+#define FSL_EASRC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_U16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | \
+ SNDRV_PCM_FMTBIT_U24_LE | \
+ SNDRV_PCM_FMTBIT_U24_3LE | \
+ SNDRV_PCM_FMTBIT_S32_LE | \
+ SNDRV_PCM_FMTBIT_U32_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_U20_3LE | \
+ SNDRV_PCM_FMTBIT_FLOAT_LE)
+
+static int fsl_easrc_iec958_put_bits(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_asrc *easrc = snd_soc_component_get_drvdata(comp);
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ struct soc_mreg_control *mc =
+ (struct soc_mreg_control *)kcontrol->private_value;
+ unsigned int regval = ucontrol->value.integer.value[0];
+
+ easrc_priv->bps_iec958[mc->regbase] = regval;
+
+ return 0;
+}
+
+static int fsl_easrc_iec958_get_bits(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_asrc *easrc = snd_soc_component_get_drvdata(comp);
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ struct soc_mreg_control *mc =
+ (struct soc_mreg_control *)kcontrol->private_value;
+
+ ucontrol->value.enumerated.item[0] = easrc_priv->bps_iec958[mc->regbase];
+
+ return 0;
+}
+
+static int fsl_easrc_get_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct soc_mreg_control *mc =
+ (struct soc_mreg_control *)kcontrol->private_value;
+ unsigned int regval;
+
+ regval = snd_soc_component_read(component, mc->regbase);
+
+ ucontrol->value.integer.value[0] = regval;
+
+ return 0;
+}
+
+static int fsl_easrc_set_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct soc_mreg_control *mc =
+ (struct soc_mreg_control *)kcontrol->private_value;
+ unsigned int regval = ucontrol->value.integer.value[0];
+ int ret;
+
+ ret = snd_soc_component_write(component, mc->regbase, regval);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+#define SOC_SINGLE_REG_RW(xname, xreg) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_soc_info_xr_sx, .get = fsl_easrc_get_reg, \
+ .put = fsl_easrc_set_reg, \
+ .private_value = (unsigned long)&(struct soc_mreg_control) \
+ { .regbase = xreg, .regcount = 1, .nbits = 32, \
+ .invert = 0, .min = 0, .max = 0xffffffff, } }
+
+#define SOC_SINGLE_VAL_RW(xname, xreg) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_soc_info_xr_sx, .get = fsl_easrc_iec958_get_bits, \
+ .put = fsl_easrc_iec958_put_bits, \
+ .private_value = (unsigned long)&(struct soc_mreg_control) \
+ { .regbase = xreg, .regcount = 1, .nbits = 32, \
+ .invert = 0, .min = 0, .max = 2, } }
+
+static const struct snd_kcontrol_new fsl_easrc_snd_controls[] = {
+ SOC_SINGLE("Context 0 Dither Switch", REG_EASRC_COC(0), 0, 1, 0),
+ SOC_SINGLE("Context 1 Dither Switch", REG_EASRC_COC(1), 0, 1, 0),
+ SOC_SINGLE("Context 2 Dither Switch", REG_EASRC_COC(2), 0, 1, 0),
+ SOC_SINGLE("Context 3 Dither Switch", REG_EASRC_COC(3), 0, 1, 0),
+
+ SOC_SINGLE("Context 0 IEC958 Validity", REG_EASRC_COC(0), 2, 1, 0),
+ SOC_SINGLE("Context 1 IEC958 Validity", REG_EASRC_COC(1), 2, 1, 0),
+ SOC_SINGLE("Context 2 IEC958 Validity", REG_EASRC_COC(2), 2, 1, 0),
+ SOC_SINGLE("Context 3 IEC958 Validity", REG_EASRC_COC(3), 2, 1, 0),
+
+ SOC_SINGLE_VAL_RW("Context 0 IEC958 Bits Per Sample", 0),
+ SOC_SINGLE_VAL_RW("Context 1 IEC958 Bits Per Sample", 1),
+ SOC_SINGLE_VAL_RW("Context 2 IEC958 Bits Per Sample", 2),
+ SOC_SINGLE_VAL_RW("Context 3 IEC958 Bits Per Sample", 3),
+
+ SOC_SINGLE_REG_RW("Context 0 IEC958 CS0", REG_EASRC_CS0(0)),
+ SOC_SINGLE_REG_RW("Context 1 IEC958 CS0", REG_EASRC_CS0(1)),
+ SOC_SINGLE_REG_RW("Context 2 IEC958 CS0", REG_EASRC_CS0(2)),
+ SOC_SINGLE_REG_RW("Context 3 IEC958 CS0", REG_EASRC_CS0(3)),
+ SOC_SINGLE_REG_RW("Context 0 IEC958 CS1", REG_EASRC_CS1(0)),
+ SOC_SINGLE_REG_RW("Context 1 IEC958 CS1", REG_EASRC_CS1(1)),
+ SOC_SINGLE_REG_RW("Context 2 IEC958 CS1", REG_EASRC_CS1(2)),
+ SOC_SINGLE_REG_RW("Context 3 IEC958 CS1", REG_EASRC_CS1(3)),
+ SOC_SINGLE_REG_RW("Context 0 IEC958 CS2", REG_EASRC_CS2(0)),
+ SOC_SINGLE_REG_RW("Context 1 IEC958 CS2", REG_EASRC_CS2(1)),
+ SOC_SINGLE_REG_RW("Context 2 IEC958 CS2", REG_EASRC_CS2(2)),
+ SOC_SINGLE_REG_RW("Context 3 IEC958 CS2", REG_EASRC_CS2(3)),
+ SOC_SINGLE_REG_RW("Context 0 IEC958 CS3", REG_EASRC_CS3(0)),
+ SOC_SINGLE_REG_RW("Context 1 IEC958 CS3", REG_EASRC_CS3(1)),
+ SOC_SINGLE_REG_RW("Context 2 IEC958 CS3", REG_EASRC_CS3(2)),
+ SOC_SINGLE_REG_RW("Context 3 IEC958 CS3", REG_EASRC_CS3(3)),
+ SOC_SINGLE_REG_RW("Context 0 IEC958 CS4", REG_EASRC_CS4(0)),
+ SOC_SINGLE_REG_RW("Context 1 IEC958 CS4", REG_EASRC_CS4(1)),
+ SOC_SINGLE_REG_RW("Context 2 IEC958 CS4", REG_EASRC_CS4(2)),
+ SOC_SINGLE_REG_RW("Context 3 IEC958 CS4", REG_EASRC_CS4(3)),
+ SOC_SINGLE_REG_RW("Context 0 IEC958 CS5", REG_EASRC_CS5(0)),
+ SOC_SINGLE_REG_RW("Context 1 IEC958 CS5", REG_EASRC_CS5(1)),
+ SOC_SINGLE_REG_RW("Context 2 IEC958 CS5", REG_EASRC_CS5(2)),
+ SOC_SINGLE_REG_RW("Context 3 IEC958 CS5", REG_EASRC_CS5(3)),
+};
+
+/*
+ * fsl_easrc_set_rs_ratio
+ *
+ * According to the resample taps, calculate the resample ratio
+ * ratio = in_rate / out_rate
+ */
+static int fsl_easrc_set_rs_ratio(struct fsl_asrc_pair *ctx)
+{
+ struct fsl_asrc *easrc = ctx->asrc;
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ struct fsl_easrc_ctx_priv *ctx_priv = ctx->private;
+ unsigned int in_rate = ctx_priv->in_params.norm_rate;
+ unsigned int out_rate = ctx_priv->out_params.norm_rate;
+ unsigned int frac_bits;
+ u64 val;
+ u32 *r;
+
+ switch (easrc_priv->rs_num_taps) {
+ case EASRC_RS_32_TAPS:
+ /* integer bits = 5; */
+ frac_bits = 39;
+ break;
+ case EASRC_RS_64_TAPS:
+ /* integer bits = 6; */
+ frac_bits = 38;
+ break;
+ case EASRC_RS_128_TAPS:
+ /* integer bits = 7; */
+ frac_bits = 37;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val = (u64)in_rate << frac_bits;
+ do_div(val, out_rate);
+ r = (uint32_t *)&val;
+
+ if (r[1] & 0xFFFFF000) {
+ dev_err(&easrc->pdev->dev, "ratio exceed range\n");
+ return -EINVAL;
+ }
+
+ regmap_write(easrc->regmap, REG_EASRC_RRL(ctx->index),
+ EASRC_RRL_RS_RL(r[0]));
+ regmap_write(easrc->regmap, REG_EASRC_RRH(ctx->index),
+ EASRC_RRH_RS_RH(r[1]));
+
+ return 0;
+}
+
+/* Normalize input and output sample rates */
+static void fsl_easrc_normalize_rates(struct fsl_asrc_pair *ctx)
+{
+ struct fsl_easrc_ctx_priv *ctx_priv;
+ int a, b;
+
+ if (!ctx)
+ return;
+
+ ctx_priv = ctx->private;
+
+ a = ctx_priv->in_params.sample_rate;
+ b = ctx_priv->out_params.sample_rate;
+
+ a = gcd(a, b);
+
+ /* Divide by gcd to normalize the rate */
+ ctx_priv->in_params.norm_rate = ctx_priv->in_params.sample_rate / a;
+ ctx_priv->out_params.norm_rate = ctx_priv->out_params.sample_rate / a;
+}
+
+/* Resets the pointer of the coeff memory pointers */
+static int fsl_easrc_coeff_mem_ptr_reset(struct fsl_asrc *easrc,
+ unsigned int ctx_id, int mem_type)
+{
+ struct device *dev;
+ u32 reg, mask, val;
+
+ if (!easrc)
+ return -ENODEV;
+
+ dev = &easrc->pdev->dev;
+
+ switch (mem_type) {
+ case EASRC_PF_COEFF_MEM:
+ /* This resets the prefilter memory pointer addr */
+ if (ctx_id >= EASRC_CTX_MAX_NUM) {
+ dev_err(dev, "Invalid context id[%d]\n", ctx_id);
+ return -EINVAL;
+ }
+
+ reg = REG_EASRC_CCE1(ctx_id);
+ mask = EASRC_CCE1_COEF_MEM_RST_MASK;
+ val = EASRC_CCE1_COEF_MEM_RST;
+ break;
+ case EASRC_RS_COEFF_MEM:
+ /* This resets the resampling memory pointer addr */
+ reg = REG_EASRC_CRCC;
+ mask = EASRC_CRCC_RS_CPR_MASK;
+ val = EASRC_CRCC_RS_CPR;
+ break;
+ default:
+ dev_err(dev, "Unknown memory type\n");
+ return -EINVAL;
+ }
+
+ /*
+ * To reset the write pointer back to zero, the register field
+ * ASRC_CTX_CTRL_EXT1x[PF_COEFF_MEM_RST] can be toggled from
+ * 0x0 to 0x1 to 0x0.
+ */
+ regmap_update_bits(easrc->regmap, reg, mask, 0);
+ regmap_update_bits(easrc->regmap, reg, mask, val);
+ regmap_update_bits(easrc->regmap, reg, mask, 0);
+
+ return 0;
+}
+
+static inline uint32_t bits_taps_to_val(unsigned int t)
+{
+ switch (t) {
+ case EASRC_RS_32_TAPS:
+ return 32;
+ case EASRC_RS_64_TAPS:
+ return 64;
+ case EASRC_RS_128_TAPS:
+ return 128;
+ }
+
+ return 0;
+}
+
+static int fsl_easrc_resampler_config(struct fsl_asrc *easrc)
+{
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ struct asrc_firmware_hdr *hdr = easrc_priv->firmware_hdr;
+ struct interp_params *interp = easrc_priv->interp;
+ struct interp_params *selected_interp = NULL;
+ unsigned int num_coeff;
+ unsigned int i;
+ u64 *coef;
+ u32 *r;
+ int ret;
+
+ if (!hdr) {
+ dev_err(dev, "firmware not loaded!\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < hdr->interp_scen; i++) {
+ if ((interp[i].num_taps - 1) !=
+ bits_taps_to_val(easrc_priv->rs_num_taps))
+ continue;
+
+ coef = interp[i].coeff;
+ selected_interp = &interp[i];
+ dev_dbg(dev, "Selected interp_filter: %u taps - %u phases\n",
+ selected_interp->num_taps,
+ selected_interp->num_phases);
+ break;
+ }
+
+ if (!selected_interp) {
+ dev_err(dev, "failed to get interpreter configuration\n");
+ return -EINVAL;
+ }
+
+ /*
+ * RS_LOW - first half of center tap of the sinc function
+ * RS_HIGH - second half of center tap of the sinc function
+ * This is due to the fact the resampling function must be
+ * symetrical - i.e. odd number of taps
+ */
+ r = (uint32_t *)&selected_interp->center_tap;
+ regmap_write(easrc->regmap, REG_EASRC_RCTCL, EASRC_RCTCL_RS_CL(r[0]));
+ regmap_write(easrc->regmap, REG_EASRC_RCTCH, EASRC_RCTCH_RS_CH(r[1]));
+
+ /*
+ * Write Number of Resampling Coefficient Taps
+ * 00b - 32-Tap Resampling Filter
+ * 01b - 64-Tap Resampling Filter
+ * 10b - 128-Tap Resampling Filter
+ * 11b - N/A
+ */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CRCC,
+ EASRC_CRCC_RS_TAPS_MASK,
+ EASRC_CRCC_RS_TAPS(easrc_priv->rs_num_taps));
+
+ /* Reset prefilter coefficient pointer back to 0 */
+ ret = fsl_easrc_coeff_mem_ptr_reset(easrc, 0, EASRC_RS_COEFF_MEM);
+ if (ret)
+ return ret;
+
+ /*
+ * When the filter is programmed to run in:
+ * 32-tap mode, 16-taps, 128-phases 4-coefficients per phase
+ * 64-tap mode, 32-taps, 64-phases 4-coefficients per phase
+ * 128-tap mode, 64-taps, 32-phases 4-coefficients per phase
+ * This means the number of writes is constant no matter
+ * the mode we are using
+ */
+ num_coeff = 16 * 128 * 4;
+
+ for (i = 0; i < num_coeff; i++) {
+ r = (uint32_t *)&coef[i];
+ regmap_write(easrc->regmap, REG_EASRC_CRCM,
+ EASRC_CRCM_RS_CWD(r[0]));
+ regmap_write(easrc->regmap, REG_EASRC_CRCM,
+ EASRC_CRCM_RS_CWD(r[1]));
+ }
+
+ return 0;
+}
+
+/**
+ * fsl_easrc_normalize_filter - Scale filter coefficients (64 bits float)
+ * For input float32 normalized range (1.0,-1.0) -> output int[16,24,32]:
+ * scale it by multiplying filter coefficients by 2^31
+ * For input int[16, 24, 32] -> output float32
+ * scale it by multiplying filter coefficients by 2^-15, 2^-23, 2^-31
+ * input:
+ * @easrc: Structure pointer of fsl_asrc
+ * @infilter : Pointer to non-scaled input filter
+ * @shift: The multiply factor
+ * output:
+ * @outfilter: scaled filter
+ */
+static int fsl_easrc_normalize_filter(struct fsl_asrc *easrc,
+ u64 *infilter,
+ u64 *outfilter,
+ int shift)
+{
+ struct device *dev = &easrc->pdev->dev;
+ u64 coef = *infilter;
+ s64 exp = (coef & 0x7ff0000000000000ll) >> 52;
+ u64 outcoef;
+
+ /*
+ * If exponent is zero (value == 0), or 7ff (value == NaNs)
+ * dont touch the content
+ */
+ if (exp == 0 || exp == 0x7ff) {
+ *outfilter = coef;
+ return 0;
+ }
+
+ /* coef * 2^shift ==> exp + shift */
+ exp += shift;
+
+ if ((shift > 0 && exp >= 0x7ff) || (shift < 0 && exp <= 0)) {
+ dev_err(dev, "coef out of range\n");
+ return -EINVAL;
+ }
+
+ outcoef = (u64)(coef & 0x800FFFFFFFFFFFFFll) + ((u64)exp << 52);
+ *outfilter = outcoef;
+
+ return 0;
+}
+
+static int fsl_easrc_write_pf_coeff_mem(struct fsl_asrc *easrc, int ctx_id,
+ u64 *coef, int n_taps, int shift)
+{
+ struct device *dev = &easrc->pdev->dev;
+ int ret = 0;
+ int i;
+ u32 *r;
+ u64 tmp;
+
+ /* If STx_NUM_TAPS is set to 0x0 then return */
+ if (!n_taps)
+ return 0;
+
+ if (!coef) {
+ dev_err(dev, "coef table is NULL\n");
+ return -EINVAL;
+ }
+
+ /*
+ * When switching between stages, the address pointer
+ * should be reset back to 0x0 before performing a write
+ */
+ ret = fsl_easrc_coeff_mem_ptr_reset(easrc, ctx_id, EASRC_PF_COEFF_MEM);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < (n_taps + 1) / 2; i++) {
+ ret = fsl_easrc_normalize_filter(easrc, &coef[i], &tmp, shift);
+ if (ret)
+ return ret;
+
+ r = (uint32_t *)&tmp;
+ regmap_write(easrc->regmap, REG_EASRC_PCF(ctx_id),
+ EASRC_PCF_CD(r[0]));
+ regmap_write(easrc->regmap, REG_EASRC_PCF(ctx_id),
+ EASRC_PCF_CD(r[1]));
+ }
+
+ return 0;
+}
+
+static int fsl_easrc_prefilter_config(struct fsl_asrc *easrc,
+ unsigned int ctx_id)
+{
+ struct prefil_params *prefil, *selected_prefil = NULL;
+ struct fsl_easrc_ctx_priv *ctx_priv;
+ struct fsl_easrc_priv *easrc_priv;
+ struct asrc_firmware_hdr *hdr;
+ struct fsl_asrc_pair *ctx;
+ struct device *dev;
+ u32 inrate, outrate, offset = 0;
+ u32 in_s_rate, out_s_rate;
+ snd_pcm_format_t in_s_fmt, out_s_fmt;
+ int ret, i;
+
+ if (!easrc)
+ return -ENODEV;
+
+ dev = &easrc->pdev->dev;
+
+ if (ctx_id >= EASRC_CTX_MAX_NUM) {
+ dev_err(dev, "Invalid context id[%d]\n", ctx_id);
+ return -EINVAL;
+ }
+
+ easrc_priv = easrc->private;
+
+ ctx = easrc->pair[ctx_id];
+ ctx_priv = ctx->private;
+
+ in_s_rate = ctx_priv->in_params.sample_rate;
+ out_s_rate = ctx_priv->out_params.sample_rate;
+ in_s_fmt = ctx_priv->in_params.sample_format;
+ out_s_fmt = ctx_priv->out_params.sample_format;
+
+ ctx_priv->in_filled_sample = bits_taps_to_val(easrc_priv->rs_num_taps) / 2;
+ ctx_priv->out_missed_sample = ctx_priv->in_filled_sample * out_s_rate / in_s_rate;
+
+ ctx_priv->st1_num_taps = 0;
+ ctx_priv->st2_num_taps = 0;
+
+ regmap_write(easrc->regmap, REG_EASRC_CCE1(ctx_id), 0);
+ regmap_write(easrc->regmap, REG_EASRC_CCE2(ctx_id), 0);
+
+ /*
+ * The audio float point data range is (-1, 1), the asrc would output
+ * all zero for float point input and integer output case, that is to
+ * drop the fractional part of the data directly.
+ *
+ * In order to support float to int conversion or int to float
+ * conversion we need to do special operation on the coefficient to
+ * enlarge/reduce the data to the expected range.
+ *
+ * For float to int case:
+ * Up sampling:
+ * 1. Create a 1 tap filter with center tap (only tap) of 2^31
+ * in 64 bits floating point.
+ * double value = (double)(((uint64_t)1) << 31)
+ * 2. Program 1 tap prefilter with center tap above.
+ *
+ * Down sampling,
+ * 1. If the filter is single stage filter, add "shift" to the exponent
+ * of stage 1 coefficients.
+ * 2. If the filter is two stage filter , add "shift" to the exponent
+ * of stage 2 coefficients.
+ *
+ * The "shift" is 31, same for int16, int24, int32 case.
+ *
+ * For int to float case:
+ * Up sampling:
+ * 1. Create a 1 tap filter with center tap (only tap) of 2^-31
+ * in 64 bits floating point.
+ * 2. Program 1 tap prefilter with center tap above.
+ *
+ * Down sampling,
+ * 1. If the filter is single stage filter, subtract "shift" to the
+ * exponent of stage 1 coefficients.
+ * 2. If the filter is two stage filter , subtract "shift" to the
+ * exponent of stage 2 coefficients.
+ *
+ * The "shift" is 15,23,31, different for int16, int24, int32 case.
+ *
+ */
+ if (out_s_rate >= in_s_rate) {
+ if (out_s_rate == in_s_rate)
+ regmap_update_bits(easrc->regmap,
+ REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_RS_BYPASS_MASK,
+ EASRC_CCE1_RS_BYPASS);
+
+ ctx_priv->st1_num_taps = 1;
+ ctx_priv->st1_coeff = &easrc_priv->const_coeff;
+ ctx_priv->st1_num_exp = 1;
+ ctx_priv->st2_num_taps = 0;
+
+ if (in_s_fmt == SNDRV_PCM_FORMAT_FLOAT_LE &&
+ out_s_fmt != SNDRV_PCM_FORMAT_FLOAT_LE)
+ ctx_priv->st1_addexp = 31;
+ else if (in_s_fmt != SNDRV_PCM_FORMAT_FLOAT_LE &&
+ out_s_fmt == SNDRV_PCM_FORMAT_FLOAT_LE)
+ ctx_priv->st1_addexp -= ctx_priv->in_params.fmt.addexp;
+ } else {
+ inrate = ctx_priv->in_params.norm_rate;
+ outrate = ctx_priv->out_params.norm_rate;
+
+ hdr = easrc_priv->firmware_hdr;
+ prefil = easrc_priv->prefil;
+
+ for (i = 0; i < hdr->prefil_scen; i++) {
+ if (inrate == prefil[i].insr &&
+ outrate == prefil[i].outsr) {
+ selected_prefil = &prefil[i];
+ dev_dbg(dev, "Selected prefilter: %u insr, %u outsr, %u st1_taps, %u st2_taps\n",
+ selected_prefil->insr,
+ selected_prefil->outsr,
+ selected_prefil->st1_taps,
+ selected_prefil->st2_taps);
+ break;
+ }
+ }
+
+ if (!selected_prefil) {
+ dev_err(dev, "Conversion from in ratio %u(%u) to out ratio %u(%u) is not supported\n",
+ in_s_rate, inrate,
+ out_s_rate, outrate);
+ return -EINVAL;
+ }
+
+ /*
+ * In prefilter coeff array, first st1_num_taps represent the
+ * stage1 prefilter coefficients followed by next st2_num_taps
+ * representing stage 2 coefficients
+ */
+ ctx_priv->st1_num_taps = selected_prefil->st1_taps;
+ ctx_priv->st1_coeff = selected_prefil->coeff;
+ ctx_priv->st1_num_exp = selected_prefil->st1_exp;
+
+ offset = ((selected_prefil->st1_taps + 1) / 2);
+ ctx_priv->st2_num_taps = selected_prefil->st2_taps;
+ ctx_priv->st2_coeff = selected_prefil->coeff + offset;
+
+ if (in_s_fmt == SNDRV_PCM_FORMAT_FLOAT_LE &&
+ out_s_fmt != SNDRV_PCM_FORMAT_FLOAT_LE) {
+ /* only change stage2 coefficient for 2 stage case */
+ if (ctx_priv->st2_num_taps > 0)
+ ctx_priv->st2_addexp = 31;
+ else
+ ctx_priv->st1_addexp = 31;
+ } else if (in_s_fmt != SNDRV_PCM_FORMAT_FLOAT_LE &&
+ out_s_fmt == SNDRV_PCM_FORMAT_FLOAT_LE) {
+ if (ctx_priv->st2_num_taps > 0)
+ ctx_priv->st2_addexp -= ctx_priv->in_params.fmt.addexp;
+ else
+ ctx_priv->st1_addexp -= ctx_priv->in_params.fmt.addexp;
+ }
+ }
+
+ ctx_priv->in_filled_sample += (ctx_priv->st1_num_taps / 2) * ctx_priv->st1_num_exp +
+ ctx_priv->st2_num_taps / 2;
+ ctx_priv->out_missed_sample = ctx_priv->in_filled_sample * out_s_rate / in_s_rate;
+
+ if (ctx_priv->in_filled_sample * out_s_rate % in_s_rate != 0)
+ ctx_priv->out_missed_sample += 1;
+ /*
+ * To modify the value of a prefilter coefficient, the user must
+ * perform a write to the register ASRC_PRE_COEFF_FIFOn[COEFF_DATA]
+ * while the respective context RUN_EN bit is set to 0b0
+ */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx_id),
+ EASRC_CC_EN_MASK, 0);
+
+ if (ctx_priv->st1_num_taps > EASRC_MAX_PF_TAPS) {
+ dev_err(dev, "ST1 taps [%d] mus be lower than %d\n",
+ ctx_priv->st1_num_taps, EASRC_MAX_PF_TAPS);
+ ret = -EINVAL;
+ goto ctx_error;
+ }
+
+ /* Update ctx ST1_NUM_TAPS in Context Control Extended 2 register */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CCE2(ctx_id),
+ EASRC_CCE2_ST1_TAPS_MASK,
+ EASRC_CCE2_ST1_TAPS(ctx_priv->st1_num_taps - 1));
+
+ /* Prefilter Coefficient Write Select to write in ST1 coeff */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_COEF_WS_MASK,
+ EASRC_PF_ST1_COEFF_WR << EASRC_CCE1_COEF_WS_SHIFT);
+
+ ret = fsl_easrc_write_pf_coeff_mem(easrc, ctx_id,
+ ctx_priv->st1_coeff,
+ ctx_priv->st1_num_taps,
+ ctx_priv->st1_addexp);
+ if (ret)
+ goto ctx_error;
+
+ if (ctx_priv->st2_num_taps > 0) {
+ if (ctx_priv->st2_num_taps + ctx_priv->st1_num_taps > EASRC_MAX_PF_TAPS) {
+ dev_err(dev, "ST2 taps [%d] mus be lower than %d\n",
+ ctx_priv->st2_num_taps, EASRC_MAX_PF_TAPS);
+ ret = -EINVAL;
+ goto ctx_error;
+ }
+
+ regmap_update_bits(easrc->regmap, REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_PF_TSEN_MASK,
+ EASRC_CCE1_PF_TSEN);
+ /*
+ * Enable prefilter stage1 writeback floating point
+ * which is used for FLOAT_LE case
+ */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_PF_ST1_WBFP_MASK,
+ EASRC_CCE1_PF_ST1_WBFP);
+
+ regmap_update_bits(easrc->regmap, REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_PF_EXP_MASK,
+ EASRC_CCE1_PF_EXP(ctx_priv->st1_num_exp - 1));
+
+ /* Update ctx ST2_NUM_TAPS in Context Control Extended 2 reg */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CCE2(ctx_id),
+ EASRC_CCE2_ST2_TAPS_MASK,
+ EASRC_CCE2_ST2_TAPS(ctx_priv->st2_num_taps - 1));
+
+ /* Prefilter Coefficient Write Select to write in ST2 coeff */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_COEF_WS_MASK,
+ EASRC_PF_ST2_COEFF_WR << EASRC_CCE1_COEF_WS_SHIFT);
+
+ ret = fsl_easrc_write_pf_coeff_mem(easrc, ctx_id,
+ ctx_priv->st2_coeff,
+ ctx_priv->st2_num_taps,
+ ctx_priv->st2_addexp);
+ if (ret)
+ goto ctx_error;
+ }
+
+ return 0;
+
+ctx_error:
+ return ret;
+}
+
+static int fsl_easrc_max_ch_for_slot(struct fsl_asrc_pair *ctx,
+ struct fsl_easrc_slot *slot)
+{
+ struct fsl_easrc_ctx_priv *ctx_priv = ctx->private;
+ int st1_mem_alloc = 0, st2_mem_alloc = 0;
+ int pf_mem_alloc = 0;
+ int max_channels = 8 - slot->num_channel;
+ int channels = 0;
+
+ if (ctx_priv->st1_num_taps > 0) {
+ if (ctx_priv->st2_num_taps > 0)
+ st1_mem_alloc =
+ (ctx_priv->st1_num_taps - 1) * ctx_priv->st1_num_exp + 1;
+ else
+ st1_mem_alloc = ctx_priv->st1_num_taps;
+ }
+
+ if (ctx_priv->st2_num_taps > 0)
+ st2_mem_alloc = ctx_priv->st2_num_taps;
+
+ pf_mem_alloc = st1_mem_alloc + st2_mem_alloc;
+
+ if (pf_mem_alloc != 0)
+ channels = (6144 - slot->pf_mem_used) / pf_mem_alloc;
+ else
+ channels = 8;
+
+ if (channels < max_channels)
+ max_channels = channels;
+
+ return max_channels;
+}
+
+static int fsl_easrc_config_one_slot(struct fsl_asrc_pair *ctx,
+ struct fsl_easrc_slot *slot,
+ unsigned int slot_ctx_idx,
+ unsigned int *req_channels,
+ unsigned int *start_channel,
+ unsigned int *avail_channel)
+{
+ struct fsl_asrc *easrc = ctx->asrc;
+ struct fsl_easrc_ctx_priv *ctx_priv = ctx->private;
+ int st1_chanxexp, st1_mem_alloc = 0, st2_mem_alloc;
+ unsigned int reg0, reg1, reg2, reg3;
+ unsigned int addr;
+
+ if (slot->slot_index == 0) {
+ reg0 = REG_EASRC_DPCS0R0(slot_ctx_idx);
+ reg1 = REG_EASRC_DPCS0R1(slot_ctx_idx);
+ reg2 = REG_EASRC_DPCS0R2(slot_ctx_idx);
+ reg3 = REG_EASRC_DPCS0R3(slot_ctx_idx);
+ } else {
+ reg0 = REG_EASRC_DPCS1R0(slot_ctx_idx);
+ reg1 = REG_EASRC_DPCS1R1(slot_ctx_idx);
+ reg2 = REG_EASRC_DPCS1R2(slot_ctx_idx);
+ reg3 = REG_EASRC_DPCS1R3(slot_ctx_idx);
+ }
+
+ if (*req_channels <= *avail_channel) {
+ slot->num_channel = *req_channels;
+ *req_channels = 0;
+ } else {
+ slot->num_channel = *avail_channel;
+ *req_channels -= *avail_channel;
+ }
+
+ slot->min_channel = *start_channel;
+ slot->max_channel = *start_channel + slot->num_channel - 1;
+ slot->ctx_index = ctx->index;
+ slot->busy = true;
+ *start_channel += slot->num_channel;
+
+ regmap_update_bits(easrc->regmap, reg0,
+ EASRC_DPCS0R0_MAXCH_MASK,
+ EASRC_DPCS0R0_MAXCH(slot->max_channel));
+
+ regmap_update_bits(easrc->regmap, reg0,
+ EASRC_DPCS0R0_MINCH_MASK,
+ EASRC_DPCS0R0_MINCH(slot->min_channel));
+
+ regmap_update_bits(easrc->regmap, reg0,
+ EASRC_DPCS0R0_NUMCH_MASK,
+ EASRC_DPCS0R0_NUMCH(slot->num_channel - 1));
+
+ regmap_update_bits(easrc->regmap, reg0,
+ EASRC_DPCS0R0_CTXNUM_MASK,
+ EASRC_DPCS0R0_CTXNUM(slot->ctx_index));
+
+ if (ctx_priv->st1_num_taps > 0) {
+ if (ctx_priv->st2_num_taps > 0)
+ st1_mem_alloc =
+ (ctx_priv->st1_num_taps - 1) * slot->num_channel *
+ ctx_priv->st1_num_exp + slot->num_channel;
+ else
+ st1_mem_alloc = ctx_priv->st1_num_taps * slot->num_channel;
+
+ slot->pf_mem_used = st1_mem_alloc;
+ regmap_update_bits(easrc->regmap, reg2,
+ EASRC_DPCS0R2_ST1_MA_MASK,
+ EASRC_DPCS0R2_ST1_MA(st1_mem_alloc));
+
+ if (slot->slot_index == 1)
+ addr = PREFILTER_MEM_LEN - st1_mem_alloc;
+ else
+ addr = 0;
+
+ regmap_update_bits(easrc->regmap, reg2,
+ EASRC_DPCS0R2_ST1_SA_MASK,
+ EASRC_DPCS0R2_ST1_SA(addr));
+ }
+
+ if (ctx_priv->st2_num_taps > 0) {
+ st1_chanxexp = slot->num_channel * (ctx_priv->st1_num_exp - 1);
+
+ regmap_update_bits(easrc->regmap, reg1,
+ EASRC_DPCS0R1_ST1_EXP_MASK,
+ EASRC_DPCS0R1_ST1_EXP(st1_chanxexp));
+
+ st2_mem_alloc = slot->num_channel * ctx_priv->st2_num_taps;
+ slot->pf_mem_used += st2_mem_alloc;
+ regmap_update_bits(easrc->regmap, reg3,
+ EASRC_DPCS0R3_ST2_MA_MASK,
+ EASRC_DPCS0R3_ST2_MA(st2_mem_alloc));
+
+ if (slot->slot_index == 1)
+ addr = PREFILTER_MEM_LEN - st1_mem_alloc - st2_mem_alloc;
+ else
+ addr = st1_mem_alloc;
+
+ regmap_update_bits(easrc->regmap, reg3,
+ EASRC_DPCS0R3_ST2_SA_MASK,
+ EASRC_DPCS0R3_ST2_SA(addr));
+ }
+
+ regmap_update_bits(easrc->regmap, reg0,
+ EASRC_DPCS0R0_EN_MASK, EASRC_DPCS0R0_EN);
+
+ return 0;
+}
+
+/*
+ * fsl_easrc_config_slot
+ *
+ * A single context can be split amongst any of the 4 context processing pipes
+ * in the design.
+ * The total number of channels consumed within the context processor must be
+ * less than or equal to 8. if a single context is configured to contain more
+ * than 8 channels then it must be distributed across multiple context
+ * processing pipe slots.
+ *
+ */
+static int fsl_easrc_config_slot(struct fsl_asrc *easrc, unsigned int ctx_id)
+{
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ struct fsl_asrc_pair *ctx = easrc->pair[ctx_id];
+ int req_channels = ctx->channels;
+ int start_channel = 0, avail_channel;
+ struct fsl_easrc_slot *slot0, *slot1;
+ struct fsl_easrc_slot *slota, *slotb;
+ int i, ret;
+
+ if (req_channels <= 0)
+ return -EINVAL;
+
+ for (i = 0; i < EASRC_CTX_MAX_NUM; i++) {
+ slot0 = &easrc_priv->slot[i][0];
+ slot1 = &easrc_priv->slot[i][1];
+
+ if (slot0->busy && slot1->busy) {
+ continue;
+ } else if ((slot0->busy && slot0->ctx_index == ctx->index) ||
+ (slot1->busy && slot1->ctx_index == ctx->index)) {
+ continue;
+ } else if (!slot0->busy) {
+ slota = slot0;
+ slotb = slot1;
+ slota->slot_index = 0;
+ } else if (!slot1->busy) {
+ slota = slot1;
+ slotb = slot0;
+ slota->slot_index = 1;
+ }
+
+ if (!slota || !slotb)
+ continue;
+
+ avail_channel = fsl_easrc_max_ch_for_slot(ctx, slotb);
+ if (avail_channel <= 0)
+ continue;
+
+ ret = fsl_easrc_config_one_slot(ctx, slota, i, &req_channels,
+ &start_channel, &avail_channel);
+ if (ret)
+ return ret;
+
+ if (req_channels > 0)
+ continue;
+ else
+ break;
+ }
+
+ if (req_channels > 0) {
+ dev_err(&easrc->pdev->dev, "no avail slot.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * fsl_easrc_release_slot
+ *
+ * Clear the slot configuration
+ */
+static int fsl_easrc_release_slot(struct fsl_asrc *easrc, unsigned int ctx_id)
+{
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ struct fsl_asrc_pair *ctx = easrc->pair[ctx_id];
+ int i;
+
+ for (i = 0; i < EASRC_CTX_MAX_NUM; i++) {
+ if (easrc_priv->slot[i][0].busy &&
+ easrc_priv->slot[i][0].ctx_index == ctx->index) {
+ easrc_priv->slot[i][0].busy = false;
+ easrc_priv->slot[i][0].num_channel = 0;
+ easrc_priv->slot[i][0].pf_mem_used = 0;
+ /* set registers */
+ regmap_write(easrc->regmap, REG_EASRC_DPCS0R0(i), 0);
+ regmap_write(easrc->regmap, REG_EASRC_DPCS0R1(i), 0);
+ regmap_write(easrc->regmap, REG_EASRC_DPCS0R2(i), 0);
+ regmap_write(easrc->regmap, REG_EASRC_DPCS0R3(i), 0);
+ }
+
+ if (easrc_priv->slot[i][1].busy &&
+ easrc_priv->slot[i][1].ctx_index == ctx->index) {
+ easrc_priv->slot[i][1].busy = false;
+ easrc_priv->slot[i][1].num_channel = 0;
+ easrc_priv->slot[i][1].pf_mem_used = 0;
+ /* set registers */
+ regmap_write(easrc->regmap, REG_EASRC_DPCS1R0(i), 0);
+ regmap_write(easrc->regmap, REG_EASRC_DPCS1R1(i), 0);
+ regmap_write(easrc->regmap, REG_EASRC_DPCS1R2(i), 0);
+ regmap_write(easrc->regmap, REG_EASRC_DPCS1R3(i), 0);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * fsl_easrc_config_context
+ *
+ * Configure the register relate with context.
+ */
+static int fsl_easrc_config_context(struct fsl_asrc *easrc, unsigned int ctx_id)
+{
+ struct fsl_easrc_ctx_priv *ctx_priv;
+ struct fsl_asrc_pair *ctx;
+ struct device *dev;
+ unsigned long lock_flags;
+ int ret;
+
+ if (!easrc)
+ return -ENODEV;
+
+ dev = &easrc->pdev->dev;
+
+ if (ctx_id >= EASRC_CTX_MAX_NUM) {
+ dev_err(dev, "Invalid context id[%d]\n", ctx_id);
+ return -EINVAL;
+ }
+
+ ctx = easrc->pair[ctx_id];
+
+ ctx_priv = ctx->private;
+
+ fsl_easrc_normalize_rates(ctx);
+
+ ret = fsl_easrc_set_rs_ratio(ctx);
+ if (ret)
+ return ret;
+
+ /* Initialize the context coeficients */
+ ret = fsl_easrc_prefilter_config(easrc, ctx->index);
+ if (ret)
+ return ret;
+
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+ ret = fsl_easrc_config_slot(easrc, ctx->index);
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+ if (ret)
+ return ret;
+
+ /*
+ * Both prefilter and resampling filters can use following
+ * initialization modes:
+ * 2 - zero-fil mode
+ * 1 - replication mode
+ * 0 - software control
+ */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_RS_INIT_MASK,
+ EASRC_CCE1_RS_INIT(ctx_priv->rs_init_mode));
+
+ regmap_update_bits(easrc->regmap, REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_PF_INIT_MASK,
+ EASRC_CCE1_PF_INIT(ctx_priv->pf_init_mode));
+
+ /*
+ * Context Input FIFO Watermark
+ * DMA request is generated when input FIFO < FIFO_WTMK
+ */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx_id),
+ EASRC_CC_FIFO_WTMK_MASK,
+ EASRC_CC_FIFO_WTMK(ctx_priv->in_params.fifo_wtmk));
+
+ /*
+ * Context Output FIFO Watermark
+ * DMA request is generated when output FIFO > FIFO_WTMK
+ * So we set fifo_wtmk -1 to register.
+ */
+ regmap_update_bits(easrc->regmap, REG_EASRC_COC(ctx_id),
+ EASRC_COC_FIFO_WTMK_MASK,
+ EASRC_COC_FIFO_WTMK(ctx_priv->out_params.fifo_wtmk - 1));
+
+ /* Number of channels */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx_id),
+ EASRC_CC_CHEN_MASK,
+ EASRC_CC_CHEN(ctx->channels - 1));
+ return 0;
+}
+
+static int fsl_easrc_process_format(struct fsl_asrc_pair *ctx,
+ struct fsl_easrc_data_fmt *fmt,
+ snd_pcm_format_t raw_fmt)
+{
+ struct fsl_asrc *easrc = ctx->asrc;
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ int ret;
+
+ if (!fmt)
+ return -EINVAL;
+
+ /*
+ * Context Input Floating Point Format
+ * 0 - Integer Format
+ * 1 - Single Precision FP Format
+ */
+ fmt->floating_point = !snd_pcm_format_linear(raw_fmt);
+ fmt->sample_pos = 0;
+ fmt->iec958 = 0;
+
+ /* Get the data width */
+ switch (snd_pcm_format_width(raw_fmt)) {
+ case 16:
+ fmt->width = EASRC_WIDTH_16_BIT;
+ fmt->addexp = 15;
+ break;
+ case 20:
+ fmt->width = EASRC_WIDTH_20_BIT;
+ fmt->addexp = 19;
+ break;
+ case 24:
+ fmt->width = EASRC_WIDTH_24_BIT;
+ fmt->addexp = 23;
+ break;
+ case 32:
+ fmt->width = EASRC_WIDTH_32_BIT;
+ fmt->addexp = 31;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (raw_fmt) {
+ case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
+ fmt->width = easrc_priv->bps_iec958[ctx->index];
+ fmt->iec958 = 1;
+ fmt->floating_point = 0;
+ if (fmt->width == EASRC_WIDTH_16_BIT) {
+ fmt->sample_pos = 12;
+ fmt->addexp = 15;
+ } else if (fmt->width == EASRC_WIDTH_20_BIT) {
+ fmt->sample_pos = 8;
+ fmt->addexp = 19;
+ } else if (fmt->width == EASRC_WIDTH_24_BIT) {
+ fmt->sample_pos = 4;
+ fmt->addexp = 23;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Data Endianness
+ * 0 - Little-Endian
+ * 1 - Big-Endian
+ */
+ ret = snd_pcm_format_big_endian(raw_fmt);
+ if (ret < 0)
+ return ret;
+
+ fmt->endianness = ret;
+
+ /*
+ * Input Data sign
+ * 0b - Signed Format
+ * 1b - Unsigned Format
+ */
+ fmt->unsign = snd_pcm_format_unsigned(raw_fmt) > 0 ? 1 : 0;
+
+ return 0;
+}
+
+static int fsl_easrc_set_ctx_format(struct fsl_asrc_pair *ctx,
+ snd_pcm_format_t *in_raw_format,
+ snd_pcm_format_t *out_raw_format)
+{
+ struct fsl_asrc *easrc = ctx->asrc;
+ struct fsl_easrc_ctx_priv *ctx_priv = ctx->private;
+ struct fsl_easrc_data_fmt *in_fmt = &ctx_priv->in_params.fmt;
+ struct fsl_easrc_data_fmt *out_fmt = &ctx_priv->out_params.fmt;
+ int ret = 0;
+
+ /* Get the bitfield values for input data format */
+ if (in_raw_format && out_raw_format) {
+ ret = fsl_easrc_process_format(ctx, in_fmt, *in_raw_format);
+ if (ret)
+ return ret;
+ }
+
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx->index),
+ EASRC_CC_BPS_MASK,
+ EASRC_CC_BPS(in_fmt->width));
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx->index),
+ EASRC_CC_ENDIANNESS_MASK,
+ in_fmt->endianness << EASRC_CC_ENDIANNESS_SHIFT);
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx->index),
+ EASRC_CC_FMT_MASK,
+ in_fmt->floating_point << EASRC_CC_FMT_SHIFT);
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx->index),
+ EASRC_CC_INSIGN_MASK,
+ in_fmt->unsign << EASRC_CC_INSIGN_SHIFT);
+
+ /* In Sample Position */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx->index),
+ EASRC_CC_SAMPLE_POS_MASK,
+ EASRC_CC_SAMPLE_POS(in_fmt->sample_pos));
+
+ /* Get the bitfield values for input data format */
+ if (in_raw_format && out_raw_format) {
+ ret = fsl_easrc_process_format(ctx, out_fmt, *out_raw_format);
+ if (ret)
+ return ret;
+ }
+
+ regmap_update_bits(easrc->regmap, REG_EASRC_COC(ctx->index),
+ EASRC_COC_BPS_MASK,
+ EASRC_COC_BPS(out_fmt->width));
+ regmap_update_bits(easrc->regmap, REG_EASRC_COC(ctx->index),
+ EASRC_COC_ENDIANNESS_MASK,
+ out_fmt->endianness << EASRC_COC_ENDIANNESS_SHIFT);
+ regmap_update_bits(easrc->regmap, REG_EASRC_COC(ctx->index),
+ EASRC_COC_FMT_MASK,
+ out_fmt->floating_point << EASRC_COC_FMT_SHIFT);
+ regmap_update_bits(easrc->regmap, REG_EASRC_COC(ctx->index),
+ EASRC_COC_OUTSIGN_MASK,
+ out_fmt->unsign << EASRC_COC_OUTSIGN_SHIFT);
+
+ /* Out Sample Position */
+ regmap_update_bits(easrc->regmap, REG_EASRC_COC(ctx->index),
+ EASRC_COC_SAMPLE_POS_MASK,
+ EASRC_COC_SAMPLE_POS(out_fmt->sample_pos));
+
+ regmap_update_bits(easrc->regmap, REG_EASRC_COC(ctx->index),
+ EASRC_COC_IEC_EN_MASK,
+ out_fmt->iec958 << EASRC_COC_IEC_EN_SHIFT);
+
+ return ret;
+}
+
+/*
+ * The ASRC provides interleaving support in hardware to ensure that a
+ * variety of sample sources can be internally combined
+ * to conform with this format. Interleaving parameters are accessed
+ * through the ASRC_CTRL_IN_ACCESSa and ASRC_CTRL_OUT_ACCESSa registers
+ */
+static int fsl_easrc_set_ctx_organziation(struct fsl_asrc_pair *ctx)
+{
+ struct fsl_easrc_ctx_priv *ctx_priv;
+ struct fsl_asrc *easrc;
+
+ if (!ctx)
+ return -ENODEV;
+
+ easrc = ctx->asrc;
+ ctx_priv = ctx->private;
+
+ /* input interleaving parameters */
+ regmap_update_bits(easrc->regmap, REG_EASRC_CIA(ctx->index),
+ EASRC_CIA_ITER_MASK,
+ EASRC_CIA_ITER(ctx_priv->in_params.iterations));
+ regmap_update_bits(easrc->regmap, REG_EASRC_CIA(ctx->index),
+ EASRC_CIA_GRLEN_MASK,
+ EASRC_CIA_GRLEN(ctx_priv->in_params.group_len));
+ regmap_update_bits(easrc->regmap, REG_EASRC_CIA(ctx->index),
+ EASRC_CIA_ACCLEN_MASK,
+ EASRC_CIA_ACCLEN(ctx_priv->in_params.access_len));
+
+ /* output interleaving parameters */
+ regmap_update_bits(easrc->regmap, REG_EASRC_COA(ctx->index),
+ EASRC_COA_ITER_MASK,
+ EASRC_COA_ITER(ctx_priv->out_params.iterations));
+ regmap_update_bits(easrc->regmap, REG_EASRC_COA(ctx->index),
+ EASRC_COA_GRLEN_MASK,
+ EASRC_COA_GRLEN(ctx_priv->out_params.group_len));
+ regmap_update_bits(easrc->regmap, REG_EASRC_COA(ctx->index),
+ EASRC_COA_ACCLEN_MASK,
+ EASRC_COA_ACCLEN(ctx_priv->out_params.access_len));
+
+ return 0;
+}
+
+/*
+ * Request one of the available contexts
+ *
+ * Returns a negative number on error and >=0 as context id
+ * on success
+ */
+static int fsl_easrc_request_context(int channels, struct fsl_asrc_pair *ctx)
+{
+ enum asrc_pair_index index = ASRC_INVALID_PAIR;
+ struct fsl_asrc *easrc = ctx->asrc;
+ struct device *dev;
+ unsigned long lock_flags;
+ int ret = 0;
+ int i;
+
+ dev = &easrc->pdev->dev;
+
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+
+ for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) {
+ if (easrc->pair[i])
+ continue;
+
+ index = i;
+ break;
+ }
+
+ if (index == ASRC_INVALID_PAIR) {
+ dev_err(dev, "all contexts are busy\n");
+ ret = -EBUSY;
+ } else if (channels > easrc->channel_avail) {
+ dev_err(dev, "can't give the required channels: %d\n",
+ channels);
+ ret = -EINVAL;
+ } else {
+ ctx->index = index;
+ ctx->channels = channels;
+ easrc->pair[index] = ctx;
+ easrc->channel_avail -= channels;
+ }
+
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+
+ return ret;
+}
+
+/*
+ * Release the context
+ *
+ * This funciton is mainly doing the revert thing in request context
+ */
+static void fsl_easrc_release_context(struct fsl_asrc_pair *ctx)
+{
+ unsigned long lock_flags;
+ struct fsl_asrc *easrc;
+
+ if (!ctx)
+ return;
+
+ easrc = ctx->asrc;
+
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+
+ fsl_easrc_release_slot(easrc, ctx->index);
+
+ easrc->channel_avail += ctx->channels;
+ easrc->pair[ctx->index] = NULL;
+
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+}
+
+/*
+ * Start the context
+ *
+ * Enable the DMA request and context
+ */
+static int fsl_easrc_start_context(struct fsl_asrc_pair *ctx)
+{
+ struct fsl_asrc *easrc = ctx->asrc;
+
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx->index),
+ EASRC_CC_FWMDE_MASK, EASRC_CC_FWMDE);
+ regmap_update_bits(easrc->regmap, REG_EASRC_COC(ctx->index),
+ EASRC_COC_FWMDE_MASK, EASRC_COC_FWMDE);
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx->index),
+ EASRC_CC_EN_MASK, EASRC_CC_EN);
+ return 0;
+}
+
+/*
+ * Stop the context
+ *
+ * Disable the DMA request and context
+ */
+static int fsl_easrc_stop_context(struct fsl_asrc_pair *ctx)
+{
+ struct fsl_asrc *easrc = ctx->asrc;
+ int val, i;
+ int size;
+ int retry = 200;
+
+ regmap_read(easrc->regmap, REG_EASRC_CC(ctx->index), &val);
+
+ if (val & EASRC_CC_EN_MASK) {
+ regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_STOP_MASK, EASRC_CC_STOP);
+ do {
+ regmap_read(easrc->regmap, REG_EASRC_SFS(ctx->index), &val);
+ val &= EASRC_SFS_NSGO_MASK;
+ size = val >> EASRC_SFS_NSGO_SHIFT;
+
+ /* Read FIFO, drop the data */
+ for (i = 0; i < size * ctx->channels; i++)
+ regmap_read(easrc->regmap, REG_EASRC_RDFIFO(ctx->index), &val);
+ /* Check RUN_STOP_DONE */
+ regmap_read(easrc->regmap, REG_EASRC_IRQF, &val);
+ if (val & EASRC_IRQF_RSD(1 << ctx->index)) {
+ /*Clear RUN_STOP_DONE*/
+ regmap_write_bits(easrc->regmap,
+ REG_EASRC_IRQF,
+ EASRC_IRQF_RSD(1 << ctx->index),
+ EASRC_IRQF_RSD(1 << ctx->index));
+ break;
+ }
+ udelay(100);
+ } while (--retry);
+
+ if (retry == 0)
+ dev_warn(&easrc->pdev->dev, "RUN STOP fail\n");
+ }
+
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx->index),
+ EASRC_CC_EN_MASK | EASRC_CC_STOP_MASK, 0);
+ regmap_update_bits(easrc->regmap, REG_EASRC_CC(ctx->index),
+ EASRC_CC_FWMDE_MASK, 0);
+ regmap_update_bits(easrc->regmap, REG_EASRC_COC(ctx->index),
+ EASRC_COC_FWMDE_MASK, 0);
+ return 0;
+}
+
+static struct dma_chan *fsl_easrc_get_dma_channel(struct fsl_asrc_pair *ctx,
+ bool dir)
+{
+ struct fsl_asrc *easrc = ctx->asrc;
+ enum asrc_pair_index index = ctx->index;
+ char name[8];
+
+ /* Example of dma name: ctx0_rx */
+ sprintf(name, "ctx%c_%cx", index + '0', dir == IN ? 'r' : 't');
+
+ return dma_request_slave_channel(&easrc->pdev->dev, name);
+};
+
+static const unsigned int easrc_rates[] = {
+ 8000, 11025, 12000, 16000,
+ 22050, 24000, 32000, 44100,
+ 48000, 64000, 88200, 96000,
+ 128000, 176400, 192000, 256000,
+ 352800, 384000, 705600, 768000,
+};
+
+static const struct snd_pcm_hw_constraint_list easrc_rate_constraints = {
+ .count = ARRAY_SIZE(easrc_rates),
+ .list = easrc_rates,
+};
+
+static int fsl_easrc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &easrc_rate_constraints);
+}
+
+static int fsl_easrc_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *ctx = 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_easrc_start_context(ctx);
+ if (ret)
+ return ret;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = fsl_easrc_stop_context(ctx);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_easrc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_asrc *easrc = snd_soc_dai_get_drvdata(dai);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_asrc_pair *ctx = runtime->private_data;
+ struct fsl_easrc_ctx_priv *ctx_priv = ctx->private;
+ unsigned int channels = params_channels(params);
+ unsigned int rate = params_rate(params);
+ snd_pcm_format_t format = params_format(params);
+ int ret;
+
+ ret = fsl_easrc_request_context(channels, ctx);
+ if (ret) {
+ dev_err(dev, "failed to request context\n");
+ return ret;
+ }
+
+ ctx_priv->ctx_streams |= BIT(substream->stream);
+
+ /*
+ * Set the input and output ratio so we can compute
+ * the resampling ratio in RS_LOW/HIGH
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ctx_priv->in_params.sample_rate = rate;
+ ctx_priv->in_params.sample_format = format;
+ ctx_priv->out_params.sample_rate = easrc->asrc_rate;
+ ctx_priv->out_params.sample_format = easrc->asrc_format;
+ } else {
+ ctx_priv->out_params.sample_rate = rate;
+ ctx_priv->out_params.sample_format = format;
+ ctx_priv->in_params.sample_rate = easrc->asrc_rate;
+ ctx_priv->in_params.sample_format = easrc->asrc_format;
+ }
+
+ ctx->channels = channels;
+ ctx_priv->in_params.fifo_wtmk = 0x20;
+ ctx_priv->out_params.fifo_wtmk = 0x20;
+
+ /*
+ * Do only rate conversion and keep the same format for input
+ * and output data
+ */
+ ret = fsl_easrc_set_ctx_format(ctx,
+ &ctx_priv->in_params.sample_format,
+ &ctx_priv->out_params.sample_format);
+ if (ret) {
+ dev_err(dev, "failed to set format %d", ret);
+ return ret;
+ }
+
+ ret = fsl_easrc_config_context(easrc, ctx->index);
+ if (ret) {
+ dev_err(dev, "failed to config context\n");
+ return ret;
+ }
+
+ ctx_priv->in_params.iterations = 1;
+ ctx_priv->in_params.group_len = ctx->channels;
+ ctx_priv->in_params.access_len = ctx->channels;
+ ctx_priv->out_params.iterations = 1;
+ ctx_priv->out_params.group_len = ctx->channels;
+ ctx_priv->out_params.access_len = ctx->channels;
+
+ ret = fsl_easrc_set_ctx_organziation(ctx);
+ if (ret) {
+ dev_err(dev, "failed to set fifo organization\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_easrc_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_asrc_pair *ctx = runtime->private_data;
+ struct fsl_easrc_ctx_priv *ctx_priv;
+
+ if (!ctx)
+ return -EINVAL;
+
+ ctx_priv = ctx->private;
+
+ if (ctx_priv->ctx_streams & BIT(substream->stream)) {
+ ctx_priv->ctx_streams &= ~BIT(substream->stream);
+ fsl_easrc_release_context(ctx);
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops fsl_easrc_dai_ops = {
+ .startup = fsl_easrc_startup,
+ .trigger = fsl_easrc_trigger,
+ .hw_params = fsl_easrc_hw_params,
+ .hw_free = fsl_easrc_hw_free,
+};
+
+static int fsl_easrc_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_asrc *easrc = dev_get_drvdata(cpu_dai->dev);
+
+ snd_soc_dai_init_dma_data(cpu_dai,
+ &easrc->dma_params_tx,
+ &easrc->dma_params_rx);
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_easrc_dai = {
+ .probe = fsl_easrc_dai_probe,
+ .playback = {
+ .stream_name = "ASRC-Playback",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rate_min = 8000,
+ .rate_max = 768000,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_EASRC_FORMATS,
+ },
+ .capture = {
+ .stream_name = "ASRC-Capture",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rate_min = 8000,
+ .rate_max = 768000,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_EASRC_FORMATS |
+ SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
+ },
+ .ops = &fsl_easrc_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_easrc_component = {
+ .name = "fsl-easrc-dai",
+ .controls = fsl_easrc_snd_controls,
+ .num_controls = ARRAY_SIZE(fsl_easrc_snd_controls),
+ .legacy_dai_naming = 1,
+};
+
+static const struct reg_default fsl_easrc_reg_defaults[] = {
+ {REG_EASRC_WRFIFO(0), 0x00000000},
+ {REG_EASRC_WRFIFO(1), 0x00000000},
+ {REG_EASRC_WRFIFO(2), 0x00000000},
+ {REG_EASRC_WRFIFO(3), 0x00000000},
+ {REG_EASRC_RDFIFO(0), 0x00000000},
+ {REG_EASRC_RDFIFO(1), 0x00000000},
+ {REG_EASRC_RDFIFO(2), 0x00000000},
+ {REG_EASRC_RDFIFO(3), 0x00000000},
+ {REG_EASRC_CC(0), 0x00000000},
+ {REG_EASRC_CC(1), 0x00000000},
+ {REG_EASRC_CC(2), 0x00000000},
+ {REG_EASRC_CC(3), 0x00000000},
+ {REG_EASRC_CCE1(0), 0x00000000},
+ {REG_EASRC_CCE1(1), 0x00000000},
+ {REG_EASRC_CCE1(2), 0x00000000},
+ {REG_EASRC_CCE1(3), 0x00000000},
+ {REG_EASRC_CCE2(0), 0x00000000},
+ {REG_EASRC_CCE2(1), 0x00000000},
+ {REG_EASRC_CCE2(2), 0x00000000},
+ {REG_EASRC_CCE2(3), 0x00000000},
+ {REG_EASRC_CIA(0), 0x00000000},
+ {REG_EASRC_CIA(1), 0x00000000},
+ {REG_EASRC_CIA(2), 0x00000000},
+ {REG_EASRC_CIA(3), 0x00000000},
+ {REG_EASRC_DPCS0R0(0), 0x00000000},
+ {REG_EASRC_DPCS0R0(1), 0x00000000},
+ {REG_EASRC_DPCS0R0(2), 0x00000000},
+ {REG_EASRC_DPCS0R0(3), 0x00000000},
+ {REG_EASRC_DPCS0R1(0), 0x00000000},
+ {REG_EASRC_DPCS0R1(1), 0x00000000},
+ {REG_EASRC_DPCS0R1(2), 0x00000000},
+ {REG_EASRC_DPCS0R1(3), 0x00000000},
+ {REG_EASRC_DPCS0R2(0), 0x00000000},
+ {REG_EASRC_DPCS0R2(1), 0x00000000},
+ {REG_EASRC_DPCS0R2(2), 0x00000000},
+ {REG_EASRC_DPCS0R2(3), 0x00000000},
+ {REG_EASRC_DPCS0R3(0), 0x00000000},
+ {REG_EASRC_DPCS0R3(1), 0x00000000},
+ {REG_EASRC_DPCS0R3(2), 0x00000000},
+ {REG_EASRC_DPCS0R3(3), 0x00000000},
+ {REG_EASRC_DPCS1R0(0), 0x00000000},
+ {REG_EASRC_DPCS1R0(1), 0x00000000},
+ {REG_EASRC_DPCS1R0(2), 0x00000000},
+ {REG_EASRC_DPCS1R0(3), 0x00000000},
+ {REG_EASRC_DPCS1R1(0), 0x00000000},
+ {REG_EASRC_DPCS1R1(1), 0x00000000},
+ {REG_EASRC_DPCS1R1(2), 0x00000000},
+ {REG_EASRC_DPCS1R1(3), 0x00000000},
+ {REG_EASRC_DPCS1R2(0), 0x00000000},
+ {REG_EASRC_DPCS1R2(1), 0x00000000},
+ {REG_EASRC_DPCS1R2(2), 0x00000000},
+ {REG_EASRC_DPCS1R2(3), 0x00000000},
+ {REG_EASRC_DPCS1R3(0), 0x00000000},
+ {REG_EASRC_DPCS1R3(1), 0x00000000},
+ {REG_EASRC_DPCS1R3(2), 0x00000000},
+ {REG_EASRC_DPCS1R3(3), 0x00000000},
+ {REG_EASRC_COC(0), 0x00000000},
+ {REG_EASRC_COC(1), 0x00000000},
+ {REG_EASRC_COC(2), 0x00000000},
+ {REG_EASRC_COC(3), 0x00000000},
+ {REG_EASRC_COA(0), 0x00000000},
+ {REG_EASRC_COA(1), 0x00000000},
+ {REG_EASRC_COA(2), 0x00000000},
+ {REG_EASRC_COA(3), 0x00000000},
+ {REG_EASRC_SFS(0), 0x00000000},
+ {REG_EASRC_SFS(1), 0x00000000},
+ {REG_EASRC_SFS(2), 0x00000000},
+ {REG_EASRC_SFS(3), 0x00000000},
+ {REG_EASRC_RRL(0), 0x00000000},
+ {REG_EASRC_RRL(1), 0x00000000},
+ {REG_EASRC_RRL(2), 0x00000000},
+ {REG_EASRC_RRL(3), 0x00000000},
+ {REG_EASRC_RRH(0), 0x00000000},
+ {REG_EASRC_RRH(1), 0x00000000},
+ {REG_EASRC_RRH(2), 0x00000000},
+ {REG_EASRC_RRH(3), 0x00000000},
+ {REG_EASRC_RUC(0), 0x00000000},
+ {REG_EASRC_RUC(1), 0x00000000},
+ {REG_EASRC_RUC(2), 0x00000000},
+ {REG_EASRC_RUC(3), 0x00000000},
+ {REG_EASRC_RUR(0), 0x7FFFFFFF},
+ {REG_EASRC_RUR(1), 0x7FFFFFFF},
+ {REG_EASRC_RUR(2), 0x7FFFFFFF},
+ {REG_EASRC_RUR(3), 0x7FFFFFFF},
+ {REG_EASRC_RCTCL, 0x00000000},
+ {REG_EASRC_RCTCH, 0x00000000},
+ {REG_EASRC_PCF(0), 0x00000000},
+ {REG_EASRC_PCF(1), 0x00000000},
+ {REG_EASRC_PCF(2), 0x00000000},
+ {REG_EASRC_PCF(3), 0x00000000},
+ {REG_EASRC_CRCM, 0x00000000},
+ {REG_EASRC_CRCC, 0x00000000},
+ {REG_EASRC_IRQC, 0x00000FFF},
+ {REG_EASRC_IRQF, 0x00000000},
+ {REG_EASRC_CS0(0), 0x00000000},
+ {REG_EASRC_CS0(1), 0x00000000},
+ {REG_EASRC_CS0(2), 0x00000000},
+ {REG_EASRC_CS0(3), 0x00000000},
+ {REG_EASRC_CS1(0), 0x00000000},
+ {REG_EASRC_CS1(1), 0x00000000},
+ {REG_EASRC_CS1(2), 0x00000000},
+ {REG_EASRC_CS1(3), 0x00000000},
+ {REG_EASRC_CS2(0), 0x00000000},
+ {REG_EASRC_CS2(1), 0x00000000},
+ {REG_EASRC_CS2(2), 0x00000000},
+ {REG_EASRC_CS2(3), 0x00000000},
+ {REG_EASRC_CS3(0), 0x00000000},
+ {REG_EASRC_CS3(1), 0x00000000},
+ {REG_EASRC_CS3(2), 0x00000000},
+ {REG_EASRC_CS3(3), 0x00000000},
+ {REG_EASRC_CS4(0), 0x00000000},
+ {REG_EASRC_CS4(1), 0x00000000},
+ {REG_EASRC_CS4(2), 0x00000000},
+ {REG_EASRC_CS4(3), 0x00000000},
+ {REG_EASRC_CS5(0), 0x00000000},
+ {REG_EASRC_CS5(1), 0x00000000},
+ {REG_EASRC_CS5(2), 0x00000000},
+ {REG_EASRC_CS5(3), 0x00000000},
+ {REG_EASRC_DBGC, 0x00000000},
+ {REG_EASRC_DBGS, 0x00000000},
+};
+
+static const struct regmap_range fsl_easrc_readable_ranges[] = {
+ regmap_reg_range(REG_EASRC_RDFIFO(0), REG_EASRC_RCTCH),
+ regmap_reg_range(REG_EASRC_PCF(0), REG_EASRC_PCF(3)),
+ regmap_reg_range(REG_EASRC_CRCC, REG_EASRC_DBGS),
+};
+
+static const struct regmap_access_table fsl_easrc_readable_table = {
+ .yes_ranges = fsl_easrc_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(fsl_easrc_readable_ranges),
+};
+
+static const struct regmap_range fsl_easrc_writeable_ranges[] = {
+ regmap_reg_range(REG_EASRC_WRFIFO(0), REG_EASRC_WRFIFO(3)),
+ regmap_reg_range(REG_EASRC_CC(0), REG_EASRC_COA(3)),
+ regmap_reg_range(REG_EASRC_RRL(0), REG_EASRC_RCTCH),
+ regmap_reg_range(REG_EASRC_PCF(0), REG_EASRC_DBGC),
+};
+
+static const struct regmap_access_table fsl_easrc_writeable_table = {
+ .yes_ranges = fsl_easrc_writeable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(fsl_easrc_writeable_ranges),
+};
+
+static const struct regmap_range fsl_easrc_volatileable_ranges[] = {
+ regmap_reg_range(REG_EASRC_RDFIFO(0), REG_EASRC_RDFIFO(3)),
+ regmap_reg_range(REG_EASRC_SFS(0), REG_EASRC_SFS(3)),
+ regmap_reg_range(REG_EASRC_IRQF, REG_EASRC_IRQF),
+ regmap_reg_range(REG_EASRC_DBGS, REG_EASRC_DBGS),
+};
+
+static const struct regmap_access_table fsl_easrc_volatileable_table = {
+ .yes_ranges = fsl_easrc_volatileable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(fsl_easrc_volatileable_ranges),
+};
+
+static const struct regmap_config fsl_easrc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .max_register = REG_EASRC_DBGS,
+ .reg_defaults = fsl_easrc_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_easrc_reg_defaults),
+ .rd_table = &fsl_easrc_readable_table,
+ .wr_table = &fsl_easrc_writeable_table,
+ .volatile_table = &fsl_easrc_volatileable_table,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+#ifdef DEBUG
+static void fsl_easrc_dump_firmware(struct fsl_asrc *easrc)
+{
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ struct asrc_firmware_hdr *firm = easrc_priv->firmware_hdr;
+ struct interp_params *interp = easrc_priv->interp;
+ struct prefil_params *prefil = easrc_priv->prefil;
+ struct device *dev = &easrc->pdev->dev;
+ int i;
+
+ if (firm->magic != FIRMWARE_MAGIC) {
+ dev_err(dev, "Wrong magic. Something went wrong!");
+ return;
+ }
+
+ dev_dbg(dev, "Firmware v%u dump:\n", firm->firmware_version);
+ dev_dbg(dev, "Num prefilter scenarios: %u\n", firm->prefil_scen);
+ dev_dbg(dev, "Num interpolation scenarios: %u\n", firm->interp_scen);
+ dev_dbg(dev, "\nInterpolation scenarios:\n");
+
+ for (i = 0; i < firm->interp_scen; i++) {
+ if (interp[i].magic != FIRMWARE_MAGIC) {
+ dev_dbg(dev, "%d. wrong interp magic: %x\n",
+ i, interp[i].magic);
+ continue;
+ }
+ dev_dbg(dev, "%d. taps: %u, phases: %u, center: %llu\n", i,
+ interp[i].num_taps, interp[i].num_phases,
+ interp[i].center_tap);
+ }
+
+ for (i = 0; i < firm->prefil_scen; i++) {
+ if (prefil[i].magic != FIRMWARE_MAGIC) {
+ dev_dbg(dev, "%d. wrong prefil magic: %x\n",
+ i, prefil[i].magic);
+ continue;
+ }
+ dev_dbg(dev, "%d. insr: %u, outsr: %u, st1: %u, st2: %u\n", i,
+ prefil[i].insr, prefil[i].outsr,
+ prefil[i].st1_taps, prefil[i].st2_taps);
+ }
+
+ dev_dbg(dev, "end of firmware dump\n");
+}
+#endif
+
+static int fsl_easrc_get_firmware(struct fsl_asrc *easrc)
+{
+ struct fsl_easrc_priv *easrc_priv;
+ const struct firmware **fw_p;
+ u32 pnum, inum, offset;
+ const u8 *data;
+ int ret;
+
+ if (!easrc)
+ return -EINVAL;
+
+ easrc_priv = easrc->private;
+ fw_p = &easrc_priv->fw;
+
+ ret = request_firmware(fw_p, easrc_priv->fw_name, &easrc->pdev->dev);
+ if (ret)
+ return ret;
+
+ data = easrc_priv->fw->data;
+
+ easrc_priv->firmware_hdr = (struct asrc_firmware_hdr *)data;
+ pnum = easrc_priv->firmware_hdr->prefil_scen;
+ inum = easrc_priv->firmware_hdr->interp_scen;
+
+ if (inum) {
+ offset = sizeof(struct asrc_firmware_hdr);
+ easrc_priv->interp = (struct interp_params *)(data + offset);
+ }
+
+ if (pnum) {
+ offset = sizeof(struct asrc_firmware_hdr) +
+ inum * sizeof(struct interp_params);
+ easrc_priv->prefil = (struct prefil_params *)(data + offset);
+ }
+
+#ifdef DEBUG
+ fsl_easrc_dump_firmware(easrc);
+#endif
+
+ return 0;
+}
+
+static irqreturn_t fsl_easrc_isr(int irq, void *dev_id)
+{
+ struct fsl_asrc *easrc = (struct fsl_asrc *)dev_id;
+ struct device *dev = &easrc->pdev->dev;
+ int val;
+
+ regmap_read(easrc->regmap, REG_EASRC_IRQF, &val);
+
+ if (val & EASRC_IRQF_OER_MASK)
+ dev_dbg(dev, "output FIFO underflow\n");
+
+ if (val & EASRC_IRQF_IFO_MASK)
+ dev_dbg(dev, "input FIFO overflow\n");
+
+ return IRQ_HANDLED;
+}
+
+static int fsl_easrc_get_fifo_addr(u8 dir, enum asrc_pair_index index)
+{
+ return REG_EASRC_FIFO(dir, index);
+}
+
+static const struct of_device_id fsl_easrc_dt_ids[] = {
+ { .compatible = "fsl,imx8mn-easrc",},
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_easrc_dt_ids);
+
+static int fsl_easrc_probe(struct platform_device *pdev)
+{
+ struct fsl_easrc_priv *easrc_priv;
+ struct device *dev = &pdev->dev;
+ struct fsl_asrc *easrc;
+ struct resource *res;
+ struct device_node *np;
+ void __iomem *regs;
+ u32 asrc_fmt = 0;
+ int ret, irq;
+
+ easrc = devm_kzalloc(dev, sizeof(*easrc), GFP_KERNEL);
+ if (!easrc)
+ return -ENOMEM;
+
+ easrc_priv = devm_kzalloc(dev, sizeof(*easrc_priv), GFP_KERNEL);
+ if (!easrc_priv)
+ return -ENOMEM;
+
+ easrc->pdev = pdev;
+ easrc->private = easrc_priv;
+ np = dev->of_node;
+
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ easrc->paddr = res->start;
+
+ easrc->regmap = devm_regmap_init_mmio(dev, regs, &fsl_easrc_regmap_config);
+ if (IS_ERR(easrc->regmap)) {
+ dev_err(dev, "failed to init regmap");
+ return PTR_ERR(easrc->regmap);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(&pdev->dev, irq, fsl_easrc_isr, 0,
+ dev_name(dev), easrc);
+ if (ret) {
+ dev_err(dev, "failed to claim irq %u: %d\n", irq, ret);
+ return ret;
+ }
+
+ easrc->mem_clk = devm_clk_get(dev, "mem");
+ if (IS_ERR(easrc->mem_clk)) {
+ dev_err(dev, "failed to get mem clock\n");
+ return PTR_ERR(easrc->mem_clk);
+ }
+
+ /* Set default value */
+ easrc->channel_avail = 32;
+ easrc->get_dma_channel = fsl_easrc_get_dma_channel;
+ easrc->request_pair = fsl_easrc_request_context;
+ easrc->release_pair = fsl_easrc_release_context;
+ easrc->get_fifo_addr = fsl_easrc_get_fifo_addr;
+ easrc->pair_priv_size = sizeof(struct fsl_easrc_ctx_priv);
+
+ easrc_priv->rs_num_taps = EASRC_RS_32_TAPS;
+ easrc_priv->const_coeff = 0x3FF0000000000000;
+
+ ret = of_property_read_u32(np, "fsl,asrc-rate", &easrc->asrc_rate);
+ if (ret) {
+ dev_err(dev, "failed to asrc rate\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "fsl,asrc-format", &asrc_fmt);
+ easrc->asrc_format = (__force snd_pcm_format_t)asrc_fmt;
+ if (ret) {
+ dev_err(dev, "failed to asrc format\n");
+ return ret;
+ }
+
+ if (!(FSL_EASRC_FORMATS & (pcm_format_to_bits(easrc->asrc_format)))) {
+ dev_warn(dev, "unsupported format, switching to S24_LE\n");
+ easrc->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ }
+
+ ret = of_property_read_string(np, "firmware-name",
+ &easrc_priv->fw_name);
+ if (ret) {
+ dev_err(dev, "failed to get firmware name\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, easrc);
+ pm_runtime_enable(dev);
+
+ spin_lock_init(&easrc->lock);
+
+ regcache_cache_only(easrc->regmap, true);
+
+ ret = devm_snd_soc_register_component(dev, &fsl_easrc_component,
+ &fsl_easrc_dai, 1);
+ if (ret) {
+ dev_err(dev, "failed to register ASoC DAI\n");
+ goto err_pm_disable;
+ }
+
+ ret = devm_snd_soc_register_component(dev, &fsl_asrc_component,
+ NULL, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ASoC platform\n");
+ goto err_pm_disable;
+ }
+
+ return 0;
+
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static int fsl_easrc_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static __maybe_unused int fsl_easrc_runtime_suspend(struct device *dev)
+{
+ struct fsl_asrc *easrc = dev_get_drvdata(dev);
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ unsigned long lock_flags;
+
+ regcache_cache_only(easrc->regmap, true);
+
+ clk_disable_unprepare(easrc->mem_clk);
+
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+ easrc_priv->firmware_loaded = 0;
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+
+ return 0;
+}
+
+static __maybe_unused int fsl_easrc_runtime_resume(struct device *dev)
+{
+ struct fsl_asrc *easrc = dev_get_drvdata(dev);
+ struct fsl_easrc_priv *easrc_priv = easrc->private;
+ struct fsl_easrc_ctx_priv *ctx_priv;
+ struct fsl_asrc_pair *ctx;
+ unsigned long lock_flags;
+ int ret;
+ int i;
+
+ ret = clk_prepare_enable(easrc->mem_clk);
+ if (ret)
+ return ret;
+
+ regcache_cache_only(easrc->regmap, false);
+ regcache_mark_dirty(easrc->regmap);
+ regcache_sync(easrc->regmap);
+
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+ if (easrc_priv->firmware_loaded) {
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+ goto skip_load;
+ }
+ easrc_priv->firmware_loaded = 1;
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+
+ ret = fsl_easrc_get_firmware(easrc);
+ if (ret) {
+ dev_err(dev, "failed to get firmware\n");
+ goto disable_mem_clk;
+ }
+
+ /*
+ * Write Resampling Coefficients
+ * The coefficient RAM must be configured prior to beginning of
+ * any context processing within the ASRC
+ */
+ ret = fsl_easrc_resampler_config(easrc);
+ if (ret) {
+ dev_err(dev, "resampler config failed\n");
+ goto disable_mem_clk;
+ }
+
+ for (i = ASRC_PAIR_A; i < EASRC_CTX_MAX_NUM; i++) {
+ ctx = easrc->pair[i];
+ if (!ctx)
+ continue;
+
+ ctx_priv = ctx->private;
+ fsl_easrc_set_rs_ratio(ctx);
+ ctx_priv->out_missed_sample = ctx_priv->in_filled_sample *
+ ctx_priv->out_params.sample_rate /
+ ctx_priv->in_params.sample_rate;
+ if (ctx_priv->in_filled_sample * ctx_priv->out_params.sample_rate
+ % ctx_priv->in_params.sample_rate != 0)
+ ctx_priv->out_missed_sample += 1;
+
+ ret = fsl_easrc_write_pf_coeff_mem(easrc, i,
+ ctx_priv->st1_coeff,
+ ctx_priv->st1_num_taps,
+ ctx_priv->st1_addexp);
+ if (ret)
+ goto disable_mem_clk;
+
+ ret = fsl_easrc_write_pf_coeff_mem(easrc, i,
+ ctx_priv->st2_coeff,
+ ctx_priv->st2_num_taps,
+ ctx_priv->st2_addexp);
+ if (ret)
+ goto disable_mem_clk;
+ }
+
+skip_load:
+ return 0;
+
+disable_mem_clk:
+ clk_disable_unprepare(easrc->mem_clk);
+ return ret;
+}
+
+static const struct dev_pm_ops fsl_easrc_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_easrc_runtime_suspend,
+ fsl_easrc_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_easrc_driver = {
+ .probe = fsl_easrc_probe,
+ .remove = fsl_easrc_remove,
+ .driver = {
+ .name = "fsl-easrc",
+ .pm = &fsl_easrc_pm_ops,
+ .of_match_table = fsl_easrc_dt_ids,
+ },
+};
+module_platform_driver(fsl_easrc_driver);
+
+MODULE_DESCRIPTION("NXP Enhanced Asynchronous Sample Rate (eASRC) driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_easrc.h b/sound/soc/fsl/fsl_easrc.h
new file mode 100644
index 000000000..7c70dac52
--- /dev/null
+++ b/sound/soc/fsl/fsl_easrc.h
@@ -0,0 +1,651 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 NXP
+ */
+
+#ifndef _FSL_EASRC_H
+#define _FSL_EASRC_H
+
+#include <sound/asound.h>
+#include <linux/dma/imx-dma.h>
+
+#include "fsl_asrc_common.h"
+
+/* EASRC Register Map */
+
+/* ASRC Input Write FIFO */
+#define REG_EASRC_WRFIFO(ctx) (0x000 + 4 * (ctx))
+/* ASRC Output Read FIFO */
+#define REG_EASRC_RDFIFO(ctx) (0x010 + 4 * (ctx))
+/* ASRC Context Control */
+#define REG_EASRC_CC(ctx) (0x020 + 4 * (ctx))
+/* ASRC Context Control Extended 1 */
+#define REG_EASRC_CCE1(ctx) (0x030 + 4 * (ctx))
+/* ASRC Context Control Extended 2 */
+#define REG_EASRC_CCE2(ctx) (0x040 + 4 * (ctx))
+/* ASRC Control Input Access */
+#define REG_EASRC_CIA(ctx) (0x050 + 4 * (ctx))
+/* ASRC Datapath Processor Control Slot0 */
+#define REG_EASRC_DPCS0R0(ctx) (0x060 + 4 * (ctx))
+#define REG_EASRC_DPCS0R1(ctx) (0x070 + 4 * (ctx))
+#define REG_EASRC_DPCS0R2(ctx) (0x080 + 4 * (ctx))
+#define REG_EASRC_DPCS0R3(ctx) (0x090 + 4 * (ctx))
+/* ASRC Datapath Processor Control Slot1 */
+#define REG_EASRC_DPCS1R0(ctx) (0x0A0 + 4 * (ctx))
+#define REG_EASRC_DPCS1R1(ctx) (0x0B0 + 4 * (ctx))
+#define REG_EASRC_DPCS1R2(ctx) (0x0C0 + 4 * (ctx))
+#define REG_EASRC_DPCS1R3(ctx) (0x0D0 + 4 * (ctx))
+/* ASRC Context Output Control */
+#define REG_EASRC_COC(ctx) (0x0E0 + 4 * (ctx))
+/* ASRC Control Output Access */
+#define REG_EASRC_COA(ctx) (0x0F0 + 4 * (ctx))
+/* ASRC Sample FIFO Status */
+#define REG_EASRC_SFS(ctx) (0x100 + 4 * (ctx))
+/* ASRC Resampling Ratio Low */
+#define REG_EASRC_RRL(ctx) (0x110 + 8 * (ctx))
+/* ASRC Resampling Ratio High */
+#define REG_EASRC_RRH(ctx) (0x114 + 8 * (ctx))
+/* ASRC Resampling Ratio Update Control */
+#define REG_EASRC_RUC(ctx) (0x130 + 4 * (ctx))
+/* ASRC Resampling Ratio Update Rate */
+#define REG_EASRC_RUR(ctx) (0x140 + 4 * (ctx))
+/* ASRC Resampling Center Tap Coefficient Low */
+#define REG_EASRC_RCTCL (0x150)
+/* ASRC Resampling Center Tap Coefficient High */
+#define REG_EASRC_RCTCH (0x154)
+/* ASRC Prefilter Coefficient FIFO */
+#define REG_EASRC_PCF(ctx) (0x160 + 4 * (ctx))
+/* ASRC Context Resampling Coefficient Memory */
+#define REG_EASRC_CRCM 0x170
+/* ASRC Context Resampling Coefficient Control*/
+#define REG_EASRC_CRCC 0x174
+/* ASRC Interrupt Control */
+#define REG_EASRC_IRQC 0x178
+/* ASRC Interrupt Status Flags */
+#define REG_EASRC_IRQF 0x17C
+/* ASRC Channel Status 0 */
+#define REG_EASRC_CS0(ctx) (0x180 + 4 * (ctx))
+/* ASRC Channel Status 1 */
+#define REG_EASRC_CS1(ctx) (0x190 + 4 * (ctx))
+/* ASRC Channel Status 2 */
+#define REG_EASRC_CS2(ctx) (0x1A0 + 4 * (ctx))
+/* ASRC Channel Status 3 */
+#define REG_EASRC_CS3(ctx) (0x1B0 + 4 * (ctx))
+/* ASRC Channel Status 4 */
+#define REG_EASRC_CS4(ctx) (0x1C0 + 4 * (ctx))
+/* ASRC Channel Status 5 */
+#define REG_EASRC_CS5(ctx) (0x1D0 + 4 * (ctx))
+/* ASRC Debug Control Register */
+#define REG_EASRC_DBGC 0x1E0
+/* ASRC Debug Status Register */
+#define REG_EASRC_DBGS 0x1E4
+
+#define REG_EASRC_FIFO(x, ctx) (x == IN ? REG_EASRC_WRFIFO(ctx) \
+ : REG_EASRC_RDFIFO(ctx))
+
+/* ASRC Context Control (CC) */
+#define EASRC_CC_EN_SHIFT 31
+#define EASRC_CC_EN_MASK BIT(EASRC_CC_EN_SHIFT)
+#define EASRC_CC_EN BIT(EASRC_CC_EN_SHIFT)
+#define EASRC_CC_STOP_SHIFT 29
+#define EASRC_CC_STOP_MASK BIT(EASRC_CC_STOP_SHIFT)
+#define EASRC_CC_STOP BIT(EASRC_CC_STOP_SHIFT)
+#define EASRC_CC_FWMDE_SHIFT 28
+#define EASRC_CC_FWMDE_MASK BIT(EASRC_CC_FWMDE_SHIFT)
+#define EASRC_CC_FWMDE BIT(EASRC_CC_FWMDE_SHIFT)
+#define EASRC_CC_FIFO_WTMK_SHIFT 16
+#define EASRC_CC_FIFO_WTMK_WIDTH 7
+#define EASRC_CC_FIFO_WTMK_MASK ((BIT(EASRC_CC_FIFO_WTMK_WIDTH) - 1) \
+ << EASRC_CC_FIFO_WTMK_SHIFT)
+#define EASRC_CC_FIFO_WTMK(v) (((v) << EASRC_CC_FIFO_WTMK_SHIFT) \
+ & EASRC_CC_FIFO_WTMK_MASK)
+#define EASRC_CC_SAMPLE_POS_SHIFT 11
+#define EASRC_CC_SAMPLE_POS_WIDTH 5
+#define EASRC_CC_SAMPLE_POS_MASK ((BIT(EASRC_CC_SAMPLE_POS_WIDTH) - 1) \
+ << EASRC_CC_SAMPLE_POS_SHIFT)
+#define EASRC_CC_SAMPLE_POS(v) (((v) << EASRC_CC_SAMPLE_POS_SHIFT) \
+ & EASRC_CC_SAMPLE_POS_MASK)
+#define EASRC_CC_ENDIANNESS_SHIFT 10
+#define EASRC_CC_ENDIANNESS_MASK BIT(EASRC_CC_ENDIANNESS_SHIFT)
+#define EASRC_CC_ENDIANNESS BIT(EASRC_CC_ENDIANNESS_SHIFT)
+#define EASRC_CC_BPS_SHIFT 8
+#define EASRC_CC_BPS_WIDTH 2
+#define EASRC_CC_BPS_MASK ((BIT(EASRC_CC_BPS_WIDTH) - 1) \
+ << EASRC_CC_BPS_SHIFT)
+#define EASRC_CC_BPS(v) (((v) << EASRC_CC_BPS_SHIFT) \
+ & EASRC_CC_BPS_MASK)
+#define EASRC_CC_FMT_SHIFT 7
+#define EASRC_CC_FMT_MASK BIT(EASRC_CC_FMT_SHIFT)
+#define EASRC_CC_FMT BIT(EASRC_CC_FMT_SHIFT)
+#define EASRC_CC_INSIGN_SHIFT 6
+#define EASRC_CC_INSIGN_MASK BIT(EASRC_CC_INSIGN_SHIFT)
+#define EASRC_CC_INSIGN BIT(EASRC_CC_INSIGN_SHIFT)
+#define EASRC_CC_CHEN_SHIFT 0
+#define EASRC_CC_CHEN_WIDTH 5
+#define EASRC_CC_CHEN_MASK ((BIT(EASRC_CC_CHEN_WIDTH) - 1) \
+ << EASRC_CC_CHEN_SHIFT)
+#define EASRC_CC_CHEN(v) (((v) << EASRC_CC_CHEN_SHIFT) \
+ & EASRC_CC_CHEN_MASK)
+
+/* ASRC Context Control Extended 1 (CCE1) */
+#define EASRC_CCE1_COEF_WS_SHIFT 25
+#define EASRC_CCE1_COEF_WS_MASK BIT(EASRC_CCE1_COEF_WS_SHIFT)
+#define EASRC_CCE1_COEF_WS BIT(EASRC_CCE1_COEF_WS_SHIFT)
+#define EASRC_CCE1_COEF_MEM_RST_SHIFT 24
+#define EASRC_CCE1_COEF_MEM_RST_MASK BIT(EASRC_CCE1_COEF_MEM_RST_SHIFT)
+#define EASRC_CCE1_COEF_MEM_RST BIT(EASRC_CCE1_COEF_MEM_RST_SHIFT)
+#define EASRC_CCE1_PF_EXP_SHIFT 16
+#define EASRC_CCE1_PF_EXP_WIDTH 8
+#define EASRC_CCE1_PF_EXP_MASK ((BIT(EASRC_CCE1_PF_EXP_WIDTH) - 1) \
+ << EASRC_CCE1_PF_EXP_SHIFT)
+#define EASRC_CCE1_PF_EXP(v) (((v) << EASRC_CCE1_PF_EXP_SHIFT) \
+ & EASRC_CCE1_PF_EXP_MASK)
+#define EASRC_CCE1_PF_ST1_WBFP_SHIFT 9
+#define EASRC_CCE1_PF_ST1_WBFP_MASK BIT(EASRC_CCE1_PF_ST1_WBFP_SHIFT)
+#define EASRC_CCE1_PF_ST1_WBFP BIT(EASRC_CCE1_PF_ST1_WBFP_SHIFT)
+#define EASRC_CCE1_PF_TSEN_SHIFT 8
+#define EASRC_CCE1_PF_TSEN_MASK BIT(EASRC_CCE1_PF_TSEN_SHIFT)
+#define EASRC_CCE1_PF_TSEN BIT(EASRC_CCE1_PF_TSEN_SHIFT)
+#define EASRC_CCE1_RS_BYPASS_SHIFT 7
+#define EASRC_CCE1_RS_BYPASS_MASK BIT(EASRC_CCE1_RS_BYPASS_SHIFT)
+#define EASRC_CCE1_RS_BYPASS BIT(EASRC_CCE1_RS_BYPASS_SHIFT)
+#define EASRC_CCE1_PF_BYPASS_SHIFT 6
+#define EASRC_CCE1_PF_BYPASS_MASK BIT(EASRC_CCE1_PF_BYPASS_SHIFT)
+#define EASRC_CCE1_PF_BYPASS BIT(EASRC_CCE1_PF_BYPASS_SHIFT)
+#define EASRC_CCE1_RS_STOP_SHIFT 5
+#define EASRC_CCE1_RS_STOP_MASK BIT(EASRC_CCE1_RS_STOP_SHIFT)
+#define EASRC_CCE1_RS_STOP BIT(EASRC_CCE1_RS_STOP_SHIFT)
+#define EASRC_CCE1_PF_STOP_SHIFT 4
+#define EASRC_CCE1_PF_STOP_MASK BIT(EASRC_CCE1_PF_STOP_SHIFT)
+#define EASRC_CCE1_PF_STOP BIT(EASRC_CCE1_PF_STOP_SHIFT)
+#define EASRC_CCE1_RS_INIT_SHIFT 2
+#define EASRC_CCE1_RS_INIT_WIDTH 2
+#define EASRC_CCE1_RS_INIT_MASK ((BIT(EASRC_CCE1_RS_INIT_WIDTH) - 1) \
+ << EASRC_CCE1_RS_INIT_SHIFT)
+#define EASRC_CCE1_RS_INIT(v) (((v) << EASRC_CCE1_RS_INIT_SHIFT) \
+ & EASRC_CCE1_RS_INIT_MASK)
+#define EASRC_CCE1_PF_INIT_SHIFT 0
+#define EASRC_CCE1_PF_INIT_WIDTH 2
+#define EASRC_CCE1_PF_INIT_MASK ((BIT(EASRC_CCE1_PF_INIT_WIDTH) - 1) \
+ << EASRC_CCE1_PF_INIT_SHIFT)
+#define EASRC_CCE1_PF_INIT(v) (((v) << EASRC_CCE1_PF_INIT_SHIFT) \
+ & EASRC_CCE1_PF_INIT_MASK)
+
+/* ASRC Context Control Extended 2 (CCE2) */
+#define EASRC_CCE2_ST2_TAPS_SHIFT 16
+#define EASRC_CCE2_ST2_TAPS_WIDTH 9
+#define EASRC_CCE2_ST2_TAPS_MASK ((BIT(EASRC_CCE2_ST2_TAPS_WIDTH) - 1) \
+ << EASRC_CCE2_ST2_TAPS_SHIFT)
+#define EASRC_CCE2_ST2_TAPS(v) (((v) << EASRC_CCE2_ST2_TAPS_SHIFT) \
+ & EASRC_CCE2_ST2_TAPS_MASK)
+#define EASRC_CCE2_ST1_TAPS_SHIFT 0
+#define EASRC_CCE2_ST1_TAPS_WIDTH 9
+#define EASRC_CCE2_ST1_TAPS_MASK ((BIT(EASRC_CCE2_ST1_TAPS_WIDTH) - 1) \
+ << EASRC_CCE2_ST1_TAPS_SHIFT)
+#define EASRC_CCE2_ST1_TAPS(v) (((v) << EASRC_CCE2_ST1_TAPS_SHIFT) \
+ & EASRC_CCE2_ST1_TAPS_MASK)
+
+/* ASRC Control Input Access (CIA) */
+#define EASRC_CIA_ITER_SHIFT 16
+#define EASRC_CIA_ITER_WIDTH 6
+#define EASRC_CIA_ITER_MASK ((BIT(EASRC_CIA_ITER_WIDTH) - 1) \
+ << EASRC_CIA_ITER_SHIFT)
+#define EASRC_CIA_ITER(v) (((v) << EASRC_CIA_ITER_SHIFT) \
+ & EASRC_CIA_ITER_MASK)
+#define EASRC_CIA_GRLEN_SHIFT 8
+#define EASRC_CIA_GRLEN_WIDTH 6
+#define EASRC_CIA_GRLEN_MASK ((BIT(EASRC_CIA_GRLEN_WIDTH) - 1) \
+ << EASRC_CIA_GRLEN_SHIFT)
+#define EASRC_CIA_GRLEN(v) (((v) << EASRC_CIA_GRLEN_SHIFT) \
+ & EASRC_CIA_GRLEN_MASK)
+#define EASRC_CIA_ACCLEN_SHIFT 0
+#define EASRC_CIA_ACCLEN_WIDTH 6
+#define EASRC_CIA_ACCLEN_MASK ((BIT(EASRC_CIA_ACCLEN_WIDTH) - 1) \
+ << EASRC_CIA_ACCLEN_SHIFT)
+#define EASRC_CIA_ACCLEN(v) (((v) << EASRC_CIA_ACCLEN_SHIFT) \
+ & EASRC_CIA_ACCLEN_MASK)
+
+/* ASRC Datapath Processor Control Slot0 Register0 (DPCS0R0) */
+#define EASRC_DPCS0R0_MAXCH_SHIFT 24
+#define EASRC_DPCS0R0_MAXCH_WIDTH 5
+#define EASRC_DPCS0R0_MAXCH_MASK ((BIT(EASRC_DPCS0R0_MAXCH_WIDTH) - 1) \
+ << EASRC_DPCS0R0_MAXCH_SHIFT)
+#define EASRC_DPCS0R0_MAXCH(v) (((v) << EASRC_DPCS0R0_MAXCH_SHIFT) \
+ & EASRC_DPCS0R0_MAXCH_MASK)
+#define EASRC_DPCS0R0_MINCH_SHIFT 16
+#define EASRC_DPCS0R0_MINCH_WIDTH 5
+#define EASRC_DPCS0R0_MINCH_MASK ((BIT(EASRC_DPCS0R0_MINCH_WIDTH) - 1) \
+ << EASRC_DPCS0R0_MINCH_SHIFT)
+#define EASRC_DPCS0R0_MINCH(v) (((v) << EASRC_DPCS0R0_MINCH_SHIFT) \
+ & EASRC_DPCS0R0_MINCH_MASK)
+#define EASRC_DPCS0R0_NUMCH_SHIFT 8
+#define EASRC_DPCS0R0_NUMCH_WIDTH 5
+#define EASRC_DPCS0R0_NUMCH_MASK ((BIT(EASRC_DPCS0R0_NUMCH_WIDTH) - 1) \
+ << EASRC_DPCS0R0_NUMCH_SHIFT)
+#define EASRC_DPCS0R0_NUMCH(v) (((v) << EASRC_DPCS0R0_NUMCH_SHIFT) \
+ & EASRC_DPCS0R0_NUMCH_MASK)
+#define EASRC_DPCS0R0_CTXNUM_SHIFT 1
+#define EASRC_DPCS0R0_CTXNUM_WIDTH 2
+#define EASRC_DPCS0R0_CTXNUM_MASK ((BIT(EASRC_DPCS0R0_CTXNUM_WIDTH) - 1) \
+ << EASRC_DPCS0R0_CTXNUM_SHIFT)
+#define EASRC_DPCS0R0_CTXNUM(v) (((v) << EASRC_DPCS0R0_CTXNUM_SHIFT) \
+ & EASRC_DPCS0R0_CTXNUM_MASK)
+#define EASRC_DPCS0R0_EN_SHIFT 0
+#define EASRC_DPCS0R0_EN_MASK BIT(EASRC_DPCS0R0_EN_SHIFT)
+#define EASRC_DPCS0R0_EN BIT(EASRC_DPCS0R0_EN_SHIFT)
+
+/* ASRC Datapath Processor Control Slot0 Register1 (DPCS0R1) */
+#define EASRC_DPCS0R1_ST1_EXP_SHIFT 0
+#define EASRC_DPCS0R1_ST1_EXP_WIDTH 13
+#define EASRC_DPCS0R1_ST1_EXP_MASK ((BIT(EASRC_DPCS0R1_ST1_EXP_WIDTH) - 1) \
+ << EASRC_DPCS0R1_ST1_EXP_SHIFT)
+#define EASRC_DPCS0R1_ST1_EXP(v) (((v) << EASRC_DPCS0R1_ST1_EXP_SHIFT) \
+ & EASRC_DPCS0R1_ST1_EXP_MASK)
+
+/* ASRC Datapath Processor Control Slot0 Register2 (DPCS0R2) */
+#define EASRC_DPCS0R2_ST1_MA_SHIFT 16
+#define EASRC_DPCS0R2_ST1_MA_WIDTH 13
+#define EASRC_DPCS0R2_ST1_MA_MASK ((BIT(EASRC_DPCS0R2_ST1_MA_WIDTH) - 1) \
+ << EASRC_DPCS0R2_ST1_MA_SHIFT)
+#define EASRC_DPCS0R2_ST1_MA(v) (((v) << EASRC_DPCS0R2_ST1_MA_SHIFT) \
+ & EASRC_DPCS0R2_ST1_MA_MASK)
+#define EASRC_DPCS0R2_ST1_SA_SHIFT 0
+#define EASRC_DPCS0R2_ST1_SA_WIDTH 13
+#define EASRC_DPCS0R2_ST1_SA_MASK ((BIT(EASRC_DPCS0R2_ST1_SA_WIDTH) - 1) \
+ << EASRC_DPCS0R2_ST1_SA_SHIFT)
+#define EASRC_DPCS0R2_ST1_SA(v) (((v) << EASRC_DPCS0R2_ST1_SA_SHIFT) \
+ & EASRC_DPCS0R2_ST1_SA_MASK)
+
+/* ASRC Datapath Processor Control Slot0 Register3 (DPCS0R3) */
+#define EASRC_DPCS0R3_ST2_MA_SHIFT 16
+#define EASRC_DPCS0R3_ST2_MA_WIDTH 13
+#define EASRC_DPCS0R3_ST2_MA_MASK ((BIT(EASRC_DPCS0R3_ST2_MA_WIDTH) - 1) \
+ << EASRC_DPCS0R3_ST2_MA_SHIFT)
+#define EASRC_DPCS0R3_ST2_MA(v) (((v) << EASRC_DPCS0R3_ST2_MA_SHIFT) \
+ & EASRC_DPCS0R3_ST2_MA_MASK)
+#define EASRC_DPCS0R3_ST2_SA_SHIFT 0
+#define EASRC_DPCS0R3_ST2_SA_WIDTH 13
+#define EASRC_DPCS0R3_ST2_SA_MASK ((BIT(EASRC_DPCS0R3_ST2_SA_WIDTH) - 1) \
+ << EASRC_DPCS0R3_ST2_SA_SHIFT)
+#define EASRC_DPCS0R3_ST2_SA(v) (((v) << EASRC_DPCS0R3_ST2_SA_SHIFT) \
+ & EASRC_DPCS0R3_ST2_SA_MASK)
+
+/* ASRC Context Output Control (COC) */
+#define EASRC_COC_FWMDE_SHIFT 28
+#define EASRC_COC_FWMDE_MASK BIT(EASRC_COC_FWMDE_SHIFT)
+#define EASRC_COC_FWMDE BIT(EASRC_COC_FWMDE_SHIFT)
+#define EASRC_COC_FIFO_WTMK_SHIFT 16
+#define EASRC_COC_FIFO_WTMK_WIDTH 7
+#define EASRC_COC_FIFO_WTMK_MASK ((BIT(EASRC_COC_FIFO_WTMK_WIDTH) - 1) \
+ << EASRC_COC_FIFO_WTMK_SHIFT)
+#define EASRC_COC_FIFO_WTMK(v) (((v) << EASRC_COC_FIFO_WTMK_SHIFT) \
+ & EASRC_COC_FIFO_WTMK_MASK)
+#define EASRC_COC_SAMPLE_POS_SHIFT 11
+#define EASRC_COC_SAMPLE_POS_WIDTH 5
+#define EASRC_COC_SAMPLE_POS_MASK ((BIT(EASRC_COC_SAMPLE_POS_WIDTH) - 1) \
+ << EASRC_COC_SAMPLE_POS_SHIFT)
+#define EASRC_COC_SAMPLE_POS(v) (((v) << EASRC_COC_SAMPLE_POS_SHIFT) \
+ & EASRC_COC_SAMPLE_POS_MASK)
+#define EASRC_COC_ENDIANNESS_SHIFT 10
+#define EASRC_COC_ENDIANNESS_MASK BIT(EASRC_COC_ENDIANNESS_SHIFT)
+#define EASRC_COC_ENDIANNESS BIT(EASRC_COC_ENDIANNESS_SHIFT)
+#define EASRC_COC_BPS_SHIFT 8
+#define EASRC_COC_BPS_WIDTH 2
+#define EASRC_COC_BPS_MASK ((BIT(EASRC_COC_BPS_WIDTH) - 1) \
+ << EASRC_COC_BPS_SHIFT)
+#define EASRC_COC_BPS(v) (((v) << EASRC_COC_BPS_SHIFT) \
+ & EASRC_COC_BPS_MASK)
+#define EASRC_COC_FMT_SHIFT 7
+#define EASRC_COC_FMT_MASK BIT(EASRC_COC_FMT_SHIFT)
+#define EASRC_COC_FMT BIT(EASRC_COC_FMT_SHIFT)
+#define EASRC_COC_OUTSIGN_SHIFT 6
+#define EASRC_COC_OUTSIGN_MASK BIT(EASRC_COC_OUTSIGN_SHIFT)
+#define EASRC_COC_OUTSIGN_OUT BIT(EASRC_COC_OUTSIGN_SHIFT)
+#define EASRC_COC_IEC_VDATA_SHIFT 2
+#define EASRC_COC_IEC_VDATA_MASK BIT(EASRC_COC_IEC_VDATA_SHIFT)
+#define EASRC_COC_IEC_VDATA BIT(EASRC_COC_IEC_VDATA_SHIFT)
+#define EASRC_COC_IEC_EN_SHIFT 1
+#define EASRC_COC_IEC_EN_MASK BIT(EASRC_COC_IEC_EN_SHIFT)
+#define EASRC_COC_IEC_EN BIT(EASRC_COC_IEC_EN_SHIFT)
+#define EASRC_COC_DITHER_EN_SHIFT 0
+#define EASRC_COC_DITHER_EN_MASK BIT(EASRC_COC_DITHER_EN_SHIFT)
+#define EASRC_COC_DITHER_EN BIT(EASRC_COC_DITHER_EN_SHIFT)
+
+/* ASRC Control Output Access (COA) */
+#define EASRC_COA_ITER_SHIFT 16
+#define EASRC_COA_ITER_WIDTH 6
+#define EASRC_COA_ITER_MASK ((BIT(EASRC_COA_ITER_WIDTH) - 1) \
+ << EASRC_COA_ITER_SHIFT)
+#define EASRC_COA_ITER(v) (((v) << EASRC_COA_ITER_SHIFT) \
+ & EASRC_COA_ITER_MASK)
+#define EASRC_COA_GRLEN_SHIFT 8
+#define EASRC_COA_GRLEN_WIDTH 6
+#define EASRC_COA_GRLEN_MASK ((BIT(EASRC_COA_GRLEN_WIDTH) - 1) \
+ << EASRC_COA_GRLEN_SHIFT)
+#define EASRC_COA_GRLEN(v) (((v) << EASRC_COA_GRLEN_SHIFT) \
+ & EASRC_COA_GRLEN_MASK)
+#define EASRC_COA_ACCLEN_SHIFT 0
+#define EASRC_COA_ACCLEN_WIDTH 6
+#define EASRC_COA_ACCLEN_MASK ((BIT(EASRC_COA_ACCLEN_WIDTH) - 1) \
+ << EASRC_COA_ACCLEN_SHIFT)
+#define EASRC_COA_ACCLEN(v) (((v) << EASRC_COA_ACCLEN_SHIFT) \
+ & EASRC_COA_ACCLEN_MASK)
+
+/* ASRC Sample FIFO Status (SFS) */
+#define EASRC_SFS_IWTMK_SHIFT 23
+#define EASRC_SFS_IWTMK_MASK BIT(EASRC_SFS_IWTMK_SHIFT)
+#define EASRC_SFS_IWTMK BIT(EASRC_SFS_IWTMK_SHIFT)
+#define EASRC_SFS_NSGI_SHIFT 16
+#define EASRC_SFS_NSGI_WIDTH 7
+#define EASRC_SFS_NSGI_MASK ((BIT(EASRC_SFS_NSGI_WIDTH) - 1) \
+ << EASRC_SFS_NSGI_SHIFT)
+#define EASRC_SFS_NSGI(v) (((v) << EASRC_SFS_NSGI_SHIFT) \
+ & EASRC_SFS_NSGI_MASK)
+#define EASRC_SFS_OWTMK_SHIFT 7
+#define EASRC_SFS_OWTMK_MASK BIT(EASRC_SFS_OWTMK_SHIFT)
+#define EASRC_SFS_OWTMK BIT(EASRC_SFS_OWTMK_SHIFT)
+#define EASRC_SFS_NSGO_SHIFT 0
+#define EASRC_SFS_NSGO_WIDTH 7
+#define EASRC_SFS_NSGO_MASK ((BIT(EASRC_SFS_NSGO_WIDTH) - 1) \
+ << EASRC_SFS_NSGO_SHIFT)
+#define EASRC_SFS_NSGO(v) (((v) << EASRC_SFS_NSGO_SHIFT) \
+ & EASRC_SFS_NSGO_MASK)
+
+/* ASRC Resampling Ratio Low (RRL) */
+#define EASRC_RRL_RS_RL_SHIFT 0
+#define EASRC_RRL_RS_RL_WIDTH 32
+#define EASRC_RRL_RS_RL(v) ((v) << EASRC_RRL_RS_RL_SHIFT)
+
+/* ASRC Resampling Ratio High (RRH) */
+#define EASRC_RRH_RS_VLD_SHIFT 31
+#define EASRC_RRH_RS_VLD_MASK BIT(EASRC_RRH_RS_VLD_SHIFT)
+#define EASRC_RRH_RS_VLD BIT(EASRC_RRH_RS_VLD_SHIFT)
+#define EASRC_RRH_RS_RH_SHIFT 0
+#define EASRC_RRH_RS_RH_WIDTH 12
+#define EASRC_RRH_RS_RH_MASK ((BIT(EASRC_RRH_RS_RH_WIDTH) - 1) \
+ << EASRC_RRH_RS_RH_SHIFT)
+#define EASRC_RRH_RS_RH(v) (((v) << EASRC_RRH_RS_RH_SHIFT) \
+ & EASRC_RRH_RS_RH_MASK)
+
+/* ASRC Resampling Ratio Update Control (RSUC) */
+#define EASRC_RSUC_RS_RM_SHIFT 0
+#define EASRC_RSUC_RS_RM_WIDTH 32
+#define EASRC_RSUC_RS_RM(v) ((v) << EASRC_RSUC_RS_RM_SHIFT)
+
+/* ASRC Resampling Ratio Update Rate (RRUR) */
+#define EASRC_RRUR_RRR_SHIFT 0
+#define EASRC_RRUR_RRR_WIDTH 31
+#define EASRC_RRUR_RRR_MASK ((BIT(EASRC_RRUR_RRR_WIDTH) - 1) \
+ << EASRC_RRUR_RRR_SHIFT)
+#define EASRC_RRUR_RRR(v) (((v) << EASRC_RRUR_RRR_SHIFT) \
+ & EASRC_RRUR_RRR_MASK)
+
+/* ASRC Resampling Center Tap Coefficient Low (RCTCL) */
+#define EASRC_RCTCL_RS_CL_SHIFT 0
+#define EASRC_RCTCL_RS_CL_WIDTH 32
+#define EASRC_RCTCL_RS_CL(v) ((v) << EASRC_RCTCL_RS_CL_SHIFT)
+
+/* ASRC Resampling Center Tap Coefficient High (RCTCH) */
+#define EASRC_RCTCH_RS_CH_SHIFT 0
+#define EASRC_RCTCH_RS_CH_WIDTH 32
+#define EASRC_RCTCH_RS_CH(v) ((v) << EASRC_RCTCH_RS_CH_SHIFT)
+
+/* ASRC Prefilter Coefficient FIFO (PCF) */
+#define EASRC_PCF_CD_SHIFT 0
+#define EASRC_PCF_CD_WIDTH 32
+#define EASRC_PCF_CD(v) ((v) << EASRC_PCF_CD_SHIFT)
+
+/* ASRC Context Resampling Coefficient Memory (CRCM) */
+#define EASRC_CRCM_RS_CWD_SHIFT 0
+#define EASRC_CRCM_RS_CWD_WIDTH 32
+#define EASRC_CRCM_RS_CWD(v) ((v) << EASRC_CRCM_RS_CWD_SHIFT)
+
+/* ASRC Context Resampling Coefficient Control (CRCC) */
+#define EASRC_CRCC_RS_CA_SHIFT 16
+#define EASRC_CRCC_RS_CA_WIDTH 11
+#define EASRC_CRCC_RS_CA_MASK ((BIT(EASRC_CRCC_RS_CA_WIDTH) - 1) \
+ << EASRC_CRCC_RS_CA_SHIFT)
+#define EASRC_CRCC_RS_CA(v) (((v) << EASRC_CRCC_RS_CA_SHIFT) \
+ & EASRC_CRCC_RS_CA_MASK)
+#define EASRC_CRCC_RS_TAPS_SHIFT 1
+#define EASRC_CRCC_RS_TAPS_WIDTH 2
+#define EASRC_CRCC_RS_TAPS_MASK ((BIT(EASRC_CRCC_RS_TAPS_WIDTH) - 1) \
+ << EASRC_CRCC_RS_TAPS_SHIFT)
+#define EASRC_CRCC_RS_TAPS(v) (((v) << EASRC_CRCC_RS_TAPS_SHIFT) \
+ & EASRC_CRCC_RS_TAPS_MASK)
+#define EASRC_CRCC_RS_CPR_SHIFT 0
+#define EASRC_CRCC_RS_CPR_MASK BIT(EASRC_CRCC_RS_CPR_SHIFT)
+#define EASRC_CRCC_RS_CPR BIT(EASRC_CRCC_RS_CPR_SHIFT)
+
+/* ASRC Interrupt_Control (IC) */
+#define EASRC_IRQC_RSDM_SHIFT 8
+#define EASRC_IRQC_RSDM_WIDTH 4
+#define EASRC_IRQC_RSDM_MASK ((BIT(EASRC_IRQC_RSDM_WIDTH) - 1) \
+ << EASRC_IRQC_RSDM_SHIFT)
+#define EASRC_IRQC_RSDM(v) (((v) << EASRC_IRQC_RSDM_SHIFT) \
+ & EASRC_IRQC_RSDM_MASK)
+#define EASRC_IRQC_OERM_SHIFT 4
+#define EASRC_IRQC_OERM_WIDTH 4
+#define EASRC_IRQC_OERM_MASK ((BIT(EASRC_IRQC_OERM_WIDTH) - 1) \
+ << EASRC_IRQC_OERM_SHIFT)
+#define EASRC_IRQC_OERM(v) (((v) << EASRC_IRQC_OERM_SHIFT) \
+ & EASRC_IEQC_OERM_MASK)
+#define EASRC_IRQC_IOM_SHIFT 0
+#define EASRC_IRQC_IOM_WIDTH 4
+#define EASRC_IRQC_IOM_MASK ((BIT(EASRC_IRQC_IOM_WIDTH) - 1) \
+ << EASRC_IRQC_IOM_SHIFT)
+#define EASRC_IRQC_IOM(v) (((v) << EASRC_IRQC_IOM_SHIFT) \
+ & EASRC_IRQC_IOM_MASK)
+
+/* ASRC Interrupt Status Flags (ISF) */
+#define EASRC_IRQF_RSD_SHIFT 8
+#define EASRC_IRQF_RSD_WIDTH 4
+#define EASRC_IRQF_RSD_MASK ((BIT(EASRC_IRQF_RSD_WIDTH) - 1) \
+ << EASRC_IRQF_RSD_SHIFT)
+#define EASRC_IRQF_RSD(v) (((v) << EASRC_IRQF_RSD_SHIFT) \
+ & EASRC_IRQF_RSD_MASK)
+#define EASRC_IRQF_OER_SHIFT 4
+#define EASRC_IRQF_OER_WIDTH 4
+#define EASRC_IRQF_OER_MASK ((BIT(EASRC_IRQF_OER_WIDTH) - 1) \
+ << EASRC_IRQF_OER_SHIFT)
+#define EASRC_IRQF_OER(v) (((v) << EASRC_IRQF_OER_SHIFT) \
+ & EASRC_IRQF_OER_MASK)
+#define EASRC_IRQF_IFO_SHIFT 0
+#define EASRC_IRQF_IFO_WIDTH 4
+#define EASRC_IRQF_IFO_MASK ((BIT(EASRC_IRQF_IFO_WIDTH) - 1) \
+ << EASRC_IRQF_IFO_SHIFT)
+#define EASRC_IRQF_IFO(v) (((v) << EASRC_IRQF_IFO_SHIFT) \
+ & EASRC_IRQF_IFO_MASK)
+
+/* ASRC Context Channel STAT */
+#define EASRC_CSx_CSx_SHIFT 0
+#define EASRC_CSx_CSx_WIDTH 32
+#define EASRC_CSx_CSx(v) ((v) << EASRC_CSx_CSx_SHIFT)
+
+/* ASRC Debug Control Register */
+#define EASRC_DBGC_DMS_SHIFT 0
+#define EASRC_DBGC_DMS_WIDTH 6
+#define EASRC_DBGC_DMS_MASK ((BIT(EASRC_DBGC_DMS_WIDTH) - 1) \
+ << EASRC_DBGC_DMS_SHIFT)
+#define EASRC_DBGC_DMS(v) (((v) << EASRC_DBGC_DMS_SHIFT) \
+ & EASRC_DBGC_DMS_MASK)
+
+/* ASRC Debug Status Register */
+#define EASRC_DBGS_DS_SHIFT 0
+#define EASRC_DBGS_DS_WIDTH 32
+#define EASRC_DBGS_DS(v) ((v) << EASRC_DBGS_DS_SHIFT)
+
+/* General Constants */
+#define EASRC_CTX_MAX_NUM 4
+#define EASRC_RS_COEFF_MEM 0
+#define EASRC_PF_COEFF_MEM 1
+
+/* Prefilter constants */
+#define EASRC_PF_ST1_ONLY 0
+#define EASRC_PF_TWO_STAGE_MODE 1
+#define EASRC_PF_ST1_COEFF_WR 0
+#define EASRC_PF_ST2_COEFF_WR 1
+#define EASRC_MAX_PF_TAPS 384
+
+/* Resampling constants */
+#define EASRC_RS_32_TAPS 0
+#define EASRC_RS_64_TAPS 1
+#define EASRC_RS_128_TAPS 2
+
+/* Initialization mode */
+#define EASRC_INIT_MODE_SW_CONTROL 0
+#define EASRC_INIT_MODE_REPLICATE 1
+#define EASRC_INIT_MODE_ZERO_FILL 2
+
+/* FIFO watermarks */
+#define FSL_EASRC_INPUTFIFO_WML 0x4
+#define FSL_EASRC_OUTPUTFIFO_WML 0x1
+
+#define EASRC_INPUTFIFO_THRESHOLD_MIN 0
+#define EASRC_INPUTFIFO_THRESHOLD_MAX 127
+#define EASRC_OUTPUTFIFO_THRESHOLD_MIN 0
+#define EASRC_OUTPUTFIFO_THRESHOLD_MAX 63
+
+#define EASRC_DMA_BUFFER_SIZE (1024 * 48 * 9)
+#define EASRC_MAX_BUFFER_SIZE (1024 * 48)
+
+#define FIRMWARE_MAGIC 0xDEAD
+#define FIRMWARE_VERSION 1
+
+#define PREFILTER_MEM_LEN 0x1800
+
+enum easrc_word_width {
+ EASRC_WIDTH_16_BIT = 0,
+ EASRC_WIDTH_20_BIT = 1,
+ EASRC_WIDTH_24_BIT = 2,
+ EASRC_WIDTH_32_BIT = 3,
+};
+
+struct __attribute__((__packed__)) asrc_firmware_hdr {
+ u32 magic;
+ u32 interp_scen;
+ u32 prefil_scen;
+ u32 firmware_version;
+};
+
+struct __attribute__((__packed__)) interp_params {
+ u32 magic;
+ u32 num_taps;
+ u32 num_phases;
+ u64 center_tap;
+ u64 coeff[8192];
+};
+
+struct __attribute__((__packed__)) prefil_params {
+ u32 magic;
+ u32 insr;
+ u32 outsr;
+ u32 st1_taps;
+ u32 st2_taps;
+ u32 st1_exp;
+ u64 coeff[256];
+};
+
+struct dma_block {
+ void *dma_vaddr;
+ unsigned int length;
+ unsigned int max_buf_size;
+};
+
+struct fsl_easrc_data_fmt {
+ unsigned int width : 2;
+ unsigned int endianness : 1;
+ unsigned int unsign : 1;
+ unsigned int floating_point : 1;
+ unsigned int iec958: 1;
+ unsigned int sample_pos: 5;
+ unsigned int addexp;
+};
+
+struct fsl_easrc_io_params {
+ struct fsl_easrc_data_fmt fmt;
+ unsigned int group_len;
+ unsigned int iterations;
+ unsigned int access_len;
+ unsigned int fifo_wtmk;
+ unsigned int sample_rate;
+ snd_pcm_format_t sample_format;
+ unsigned int norm_rate;
+};
+
+struct fsl_easrc_slot {
+ bool busy;
+ int ctx_index;
+ int slot_index;
+ int num_channel; /* maximum is 8 */
+ int min_channel;
+ int max_channel;
+ int pf_mem_used;
+};
+
+/**
+ * fsl_easrc_ctx_priv: EASRC context private data
+ *
+ * @in_params: input parameter
+ * @out_params: output parameter
+ * @st1_num_taps: tap number of stage 1
+ * @st2_num_taps: tap number of stage 2
+ * @st1_num_exp: exponent number of stage 1
+ * @pf_init_mode: prefilter init mode
+ * @rs_init_mode: resample filter init mode
+ * @ctx_streams: stream flag of ctx
+ * @rs_ratio: resampler ratio
+ * @st1_coeff: pointer of stage 1 coeff
+ * @st2_coeff: pointer of stage 2 coeff
+ * @in_filled_sample: input filled sample
+ * @out_missed_sample: sample missed in output
+ * @st1_addexp: exponent added for stage1
+ * @st2_addexp: exponent added for stage2
+ */
+struct fsl_easrc_ctx_priv {
+ struct fsl_easrc_io_params in_params;
+ struct fsl_easrc_io_params out_params;
+ unsigned int st1_num_taps;
+ unsigned int st2_num_taps;
+ unsigned int st1_num_exp;
+ unsigned int pf_init_mode;
+ unsigned int rs_init_mode;
+ unsigned int ctx_streams;
+ u64 rs_ratio;
+ u64 *st1_coeff;
+ u64 *st2_coeff;
+ int in_filled_sample;
+ int out_missed_sample;
+ int st1_addexp;
+ int st2_addexp;
+};
+
+/**
+ * fsl_easrc_priv: EASRC private data
+ *
+ * @slot: slot setting
+ * @firmware_hdr: the header of firmware
+ * @interp: pointer to interpolation filter coeff
+ * @prefil: pointer to prefilter coeff
+ * @fw: firmware of coeff table
+ * @fw_name: firmware name
+ * @rs_num_taps: resample filter taps, 32, 64, or 128
+ * @bps_iec958: bits per sample of iec958
+ * @rs_coeff: resampler coefficient
+ * @const_coeff: one tap prefilter coefficient
+ * @firmware_loaded: firmware is loaded
+ */
+struct fsl_easrc_priv {
+ struct fsl_easrc_slot slot[EASRC_CTX_MAX_NUM][2];
+ struct asrc_firmware_hdr *firmware_hdr;
+ struct interp_params *interp;
+ struct prefil_params *prefil;
+ const struct firmware *fw;
+ const char *fw_name;
+ unsigned int rs_num_taps;
+ unsigned int bps_iec958[EASRC_CTX_MAX_NUM];
+ u64 *rs_coeff;
+ u64 const_coeff;
+ int firmware_loaded;
+};
+#endif /* _FSL_EASRC_H */
diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c
new file mode 100644
index 000000000..17fefd27e
--- /dev/null
+++ b/sound/soc/fsl/fsl_esai.c
@@ -0,0 +1,1216 @@
+// 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 <linux/pm_runtime.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)
+
+/**
+ * struct fsl_esai_soc_data - soc specific data
+ * @reset_at_xrun: flags for enable reset operaton
+ */
+struct fsl_esai_soc_data {
+ bool reset_at_xrun;
+};
+
+/**
+ * struct 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)
+ * @work: work to handle the reset operation
+ * @soc: soc specific data
+ * @lock: spin lock between hw_reset() and trigger()
+ * @fifo_depth: depth of tx/rx FIFO
+ * @slot_width: width of each DAI slot
+ * @slots: number of slots
+ * @tx_mask: slot mask for TX
+ * @rx_mask: slot mask for RX
+ * @channels: channel num for tx or rx
+ * @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
+ * @consumer_mode: if fully using DAI clock consumer 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;
+ struct work_struct work;
+ const struct fsl_esai_soc_data *soc;
+ spinlock_t lock; /* Protect hw_reset and trigger */
+ u32 fifo_depth;
+ u32 slot_width;
+ u32 slots;
+ u32 tx_mask;
+ u32 rx_mask;
+ u32 channels[2];
+ u32 hck_rate[2];
+ u32 sck_rate[2];
+ bool hck_dir[2];
+ bool sck_div[2];
+ bool consumer_mode;
+ bool synchronous;
+ char name[32];
+};
+
+static struct fsl_esai_soc_data fsl_esai_vf610 = {
+ .reset_at_xrun = true,
+};
+
+static struct fsl_esai_soc_data fsl_esai_imx35 = {
+ .reset_at_xrun = true,
+};
+
+static struct fsl_esai_soc_data fsl_esai_imx6ull = {
+ .reset_at_xrun = false,
+};
+
+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;
+ u32 saisr;
+
+ regmap_read(esai_priv->regmap, REG_ESAI_ESR, &esr);
+ regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr);
+
+ if ((saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE)) &&
+ esai_priv->soc->reset_at_xrun) {
+ dev_dbg(&pdev->dev, "reset module for xrun\n");
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_xEIE_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR,
+ ESAI_xCR_xEIE_MASK, 0);
+ schedule_work(&esai_priv->work);
+ }
+
+ 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;
+}
+
+/**
+ * fsl_esai_divisor_cal - 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().
+ *
+ * @dai: pointer to DAI
+ * @tx: current setting is for playback or capture
+ * @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
+ */
+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;
+}
+
+/**
+ * fsl_esai_set_dai_sysclk - configure the clock frequency of MCLK (HCKT/HCKR)
+ * @dai: pointer to DAI
+ * @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 || esai_priv->synchronous);
+ 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;
+ break;
+ case ESAI_HCKR_EXTAL:
+ ecr |= esai_priv->synchronous ? ESAI_ECR_ETI : 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;
+}
+
+/**
+ * fsl_esai_set_bclk - configure the related dividers according to the bclk rate
+ * @dai: pointer to DAI
+ * @tx: direction boolean
+ * @freq: bclk freq
+ */
+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 consumer mode or unchanged bclk */
+ if (esai_priv->consumer_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_RCCR,
+ ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots));
+
+ esai_priv->slot_width = slot_width;
+ esai_priv->slots = slots;
+ esai_priv->tx_mask = tx_mask;
+ esai_priv->rx_mask = rx_mask;
+
+ 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;
+ xcr |= 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->consumer_mode = false;
+
+ /* DAI clock provider masks */
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ esai_priv->consumer_mode = true;
+ break;
+ case SND_SOC_DAIFMT_BP_FC:
+ xccr |= ESAI_xCCR_xCKD;
+ break;
+ case SND_SOC_DAIFMT_BC_FP:
+ xccr |= ESAI_xCCR_xFSD;
+ break;
+ case SND_SOC_DAIFMT_BP_FP:
+ xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask = ESAI_xCR_xFSL | ESAI_xCR_xFSR | ESAI_xCR_xWA;
+ 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;
+ 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);
+
+ if (!snd_soc_dai_active(dai)) {
+ /* Set synchronous mode */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_SAICR,
+ ESAI_SAICR_SYNC, esai_priv->synchronous ?
+ ESAI_SAICR_SYNC : 0);
+
+ /* Set slots count */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR,
+ ESAI_xCCR_xDC_MASK,
+ ESAI_xCCR_xDC(esai_priv->slots));
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR,
+ ESAI_xCCR_xDC_MASK,
+ ESAI_xCCR_xDC(esai_priv->slots));
+ }
+
+ return 0;
+
+}
+
+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, esai_priv->synchronous || tx, bclk);
+ if (ret)
+ return ret;
+
+ mask = ESAI_xCR_xSWS_MASK;
+ val = ESAI_xCR_xSWS(slot_width, width);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val);
+ /* Recording in synchronous mode needs to set TCR also */
+ if (!tx && esai_priv->synchronous)
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, val);
+
+ /* 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);
+
+ if (tx)
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_PADC, ESAI_xCR_PADC);
+
+ /* 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 int fsl_esai_hw_init(struct fsl_esai *esai_priv)
+{
+ struct platform_device *pdev = esai_priv->pdev;
+ int ret;
+
+ /* Reset ESAI unit */
+ ret = regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR,
+ ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK,
+ ESAI_ECR_ESAIEN | 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_update_bits(esai_priv->regmap, REG_ESAI_ECR,
+ ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK,
+ ESAI_ECR_ESAIEN);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable ESAI: %d\n", ret);
+ return ret;
+ }
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC,
+ ESAI_PRRC_PDC_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC,
+ ESAI_PCRC_PC_MASK, 0);
+
+ return 0;
+}
+
+static int fsl_esai_register_restore(struct fsl_esai *esai_priv)
+{
+ int ret;
+
+ /* FIFO reset for safety */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR,
+ ESAI_xFCR_xFR, ESAI_xFCR_xFR);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR,
+ ESAI_xFCR_xFR, ESAI_xFCR_xFR);
+
+ regcache_mark_dirty(esai_priv->regmap);
+ ret = regcache_sync(esai_priv->regmap);
+ if (ret)
+ return ret;
+
+ /* FIFO reset done */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR, 0);
+
+ return 0;
+}
+
+static void fsl_esai_trigger_start(struct fsl_esai *esai_priv, bool tx)
+{
+ u8 i, channels = esai_priv->channels[tx];
+ u32 pins = DIV_ROUND_UP(channels, esai_priv->slots);
+ u32 mask;
+
+ 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);
+
+ /*
+ * When set the TE/RE in the end of enablement flow, there
+ * will be channel swap issue for multi data line case.
+ * In order to workaround this issue, we switch the bit
+ * enablement sequence to below sequence
+ * 1) clear the xSMB & xSMA: which is done in probe and
+ * stop state.
+ * 2) set TE/RE
+ * 3) set xSMB
+ * 4) set xSMA: xSMA is the last one in this flow, which
+ * will trigger esai to start.
+ */
+ 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));
+ mask = tx ? esai_priv->tx_mask : esai_priv->rx_mask;
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMB(tx),
+ ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(mask));
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx),
+ ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(mask));
+
+ /* Enable Exception interrupt */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
+ ESAI_xCR_xEIE_MASK, ESAI_xCR_xEIE);
+}
+
+static void fsl_esai_trigger_stop(struct fsl_esai *esai_priv, bool tx)
+{
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
+ ESAI_xCR_xEIE_MASK, 0);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
+ tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx),
+ ESAI_xSMA_xS_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMB(tx),
+ ESAI_xSMB_xS_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);
+}
+
+static void fsl_esai_hw_reset(struct work_struct *work)
+{
+ struct fsl_esai *esai_priv = container_of(work, struct fsl_esai, work);
+ bool tx = true, rx = false, enabled[2];
+ unsigned long lock_flags;
+ u32 tfcr, rfcr;
+
+ spin_lock_irqsave(&esai_priv->lock, lock_flags);
+ /* Save the registers */
+ regmap_read(esai_priv->regmap, REG_ESAI_TFCR, &tfcr);
+ regmap_read(esai_priv->regmap, REG_ESAI_RFCR, &rfcr);
+ enabled[tx] = tfcr & ESAI_xFCR_xFEN;
+ enabled[rx] = rfcr & ESAI_xFCR_xFEN;
+
+ /* Stop the tx & rx */
+ fsl_esai_trigger_stop(esai_priv, tx);
+ fsl_esai_trigger_stop(esai_priv, rx);
+
+ /* Reset the esai, and ignore return value */
+ fsl_esai_hw_init(esai_priv);
+
+ /* Enforce ESAI personal resets for both TX and RX */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_xPR_MASK, ESAI_xCR_xPR);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR,
+ ESAI_xCR_xPR_MASK, ESAI_xCR_xPR);
+
+ /* Restore registers by regcache_sync, and ignore return value */
+ fsl_esai_register_restore(esai_priv);
+
+ /* Remove ESAI personal resets by configuring PCRC and PRRC also */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_xPR_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR,
+ ESAI_xCR_xPR_MASK, 0);
+ 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));
+
+ /* Restart tx / rx, if they already enabled */
+ if (enabled[tx])
+ fsl_esai_trigger_start(esai_priv, tx);
+ if (enabled[rx])
+ fsl_esai_trigger_start(esai_priv, rx);
+
+ spin_unlock_irqrestore(&esai_priv->lock, lock_flags);
+}
+
+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;
+ unsigned long lock_flags;
+
+ esai_priv->channels[tx] = substream->runtime->channels;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&esai_priv->lock, lock_flags);
+ fsl_esai_trigger_start(esai_priv, tx);
+ spin_unlock_irqrestore(&esai_priv->lock, lock_flags);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&esai_priv->lock, lock_flags);
+ fsl_esai_trigger_stop(esai_priv, tx);
+ spin_unlock_irqrestore(&esai_priv->lock, lock_flags);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops fsl_esai_dai_ops = {
+ .startup = fsl_esai_startup,
+ .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",
+ .legacy_dai_naming = 1,
+};
+
+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_runtime_resume(struct device *dev);
+static int fsl_esai_runtime_suspend(struct device *dev);
+
+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;
+ snprintf(esai_priv->name, sizeof(esai_priv->name), "%pOFn", np);
+
+ esai_priv->soc = of_device_get_match_data(&pdev->dev);
+
+ /* Get the addresses and IRQ */
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ esai_priv->regmap = devm_regmap_init_mmio(&pdev->dev, 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)
+ return irq;
+
+ ret = devm_request_irq(&pdev->dev, irq, esai_isr, IRQF_SHARED,
+ 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 clock provider state */
+ esai_priv->consumer_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_rate = 1;
+ fsl_esai_dai.symmetric_channels = 1;
+ fsl_esai_dai.symmetric_sample_bits = 1;
+ }
+
+ dev_set_drvdata(&pdev->dev, esai_priv);
+ spin_lock_init(&esai_priv->lock);
+ pm_runtime_enable(&pdev->dev);
+ if (!pm_runtime_enabled(&pdev->dev)) {
+ ret = fsl_esai_runtime_resume(&pdev->dev);
+ if (ret)
+ goto err_pm_disable;
+ }
+
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret < 0)
+ goto err_pm_get_sync;
+
+ ret = fsl_esai_hw_init(esai_priv);
+ if (ret)
+ goto err_pm_get_sync;
+
+ esai_priv->tx_mask = 0xFFFFFFFF;
+ esai_priv->rx_mask = 0xFFFFFFFF;
+
+ /* Clear the TSMA, TSMB, RSMA, RSMB */
+ regmap_write(esai_priv->regmap, REG_ESAI_TSMA, 0);
+ regmap_write(esai_priv->regmap, REG_ESAI_TSMB, 0);
+ regmap_write(esai_priv->regmap, REG_ESAI_RSMA, 0);
+ regmap_write(esai_priv->regmap, REG_ESAI_RSMB, 0);
+
+ ret = pm_runtime_put_sync(&pdev->dev);
+ if (ret < 0 && ret != -ENOSYS)
+ goto err_pm_get_sync;
+
+ /*
+ * Register platform component before registering cpu dai for there
+ * is not defer probe for platform component in snd_soc_add_pcm_runtime().
+ */
+ ret = imx_pcm_dma_init(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret);
+ goto err_pm_get_sync;
+ }
+
+ 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);
+ goto err_pm_get_sync;
+ }
+
+ INIT_WORK(&esai_priv->work, fsl_esai_hw_reset);
+
+ return ret;
+
+err_pm_get_sync:
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ fsl_esai_runtime_suspend(&pdev->dev);
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static int fsl_esai_remove(struct platform_device *pdev)
+{
+ struct fsl_esai *esai_priv = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ fsl_esai_runtime_suspend(&pdev->dev);
+
+ cancel_work_sync(&esai_priv->work);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_esai_dt_ids[] = {
+ { .compatible = "fsl,imx35-esai", .data = &fsl_esai_imx35 },
+ { .compatible = "fsl,vf610-esai", .data = &fsl_esai_vf610 },
+ { .compatible = "fsl,imx6ull-esai", .data = &fsl_esai_imx6ull },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids);
+
+static int fsl_esai_runtime_resume(struct device *dev)
+{
+ struct fsl_esai *esai = dev_get_drvdata(dev);
+ 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->coreclk);
+ if (ret)
+ return ret;
+ if (!IS_ERR(esai->spbaclk)) {
+ ret = clk_prepare_enable(esai->spbaclk);
+ if (ret)
+ goto err_spbaclk;
+ }
+ if (!IS_ERR(esai->extalclk)) {
+ ret = clk_prepare_enable(esai->extalclk);
+ if (ret)
+ goto err_extalclk;
+ }
+ if (!IS_ERR(esai->fsysclk)) {
+ ret = clk_prepare_enable(esai->fsysclk);
+ if (ret)
+ goto err_fsysclk;
+ }
+
+ regcache_cache_only(esai->regmap, false);
+
+ ret = fsl_esai_register_restore(esai);
+ if (ret)
+ goto err_regcache_sync;
+
+ return 0;
+
+err_regcache_sync:
+ if (!IS_ERR(esai->fsysclk))
+ clk_disable_unprepare(esai->fsysclk);
+err_fsysclk:
+ if (!IS_ERR(esai->extalclk))
+ clk_disable_unprepare(esai->extalclk);
+err_extalclk:
+ if (!IS_ERR(esai->spbaclk))
+ clk_disable_unprepare(esai->spbaclk);
+err_spbaclk:
+ clk_disable_unprepare(esai->coreclk);
+
+ return ret;
+}
+
+static int fsl_esai_runtime_suspend(struct device *dev)
+{
+ struct fsl_esai *esai = dev_get_drvdata(dev);
+
+ regcache_cache_only(esai->regmap, true);
+
+ if (!IS_ERR(esai->fsysclk))
+ clk_disable_unprepare(esai->fsysclk);
+ if (!IS_ERR(esai->extalclk))
+ clk_disable_unprepare(esai->extalclk);
+ if (!IS_ERR(esai->spbaclk))
+ clk_disable_unprepare(esai->spbaclk);
+ clk_disable_unprepare(esai->coreclk);
+
+ return 0;
+}
+
+static const struct dev_pm_ops fsl_esai_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_esai_runtime_suspend,
+ fsl_esai_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_esai_driver = {
+ .probe = fsl_esai_probe,
+ .remove = fsl_esai_remove,
+ .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 000000000..f873588d9
--- /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_micfil.c b/sound/soc/fsl/fsl_micfil.c
new file mode 100644
index 000000000..3a03f4945
--- /dev/null
+++ b/sound/soc/fsl/fsl_micfil.c
@@ -0,0 +1,810 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2018 NXP
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/kobject.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/dma/imx-dma.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/core.h>
+
+#include "fsl_micfil.h"
+#include "fsl_utils.h"
+
+#define MICFIL_OSR_DEFAULT 16
+
+enum quality {
+ QUALITY_HIGH,
+ QUALITY_MEDIUM,
+ QUALITY_LOW,
+ QUALITY_VLOW0,
+ QUALITY_VLOW1,
+ QUALITY_VLOW2,
+};
+
+struct fsl_micfil {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ const struct fsl_micfil_soc_data *soc;
+ struct clk *busclk;
+ struct clk *mclk;
+ struct clk *pll8k_clk;
+ struct clk *pll11k_clk;
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct sdma_peripheral_config sdmacfg;
+ unsigned int dataline;
+ char name[32];
+ int irq[MICFIL_IRQ_LINES];
+ enum quality quality;
+ int dc_remover;
+};
+
+struct fsl_micfil_soc_data {
+ unsigned int fifos;
+ unsigned int fifo_depth;
+ unsigned int dataline;
+ bool imx;
+ u64 formats;
+};
+
+static struct fsl_micfil_soc_data fsl_micfil_imx8mm = {
+ .imx = true,
+ .fifos = 8,
+ .fifo_depth = 8,
+ .dataline = 0xf,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+};
+
+static struct fsl_micfil_soc_data fsl_micfil_imx8mp = {
+ .imx = true,
+ .fifos = 8,
+ .fifo_depth = 32,
+ .dataline = 0xf,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+};
+
+static const struct of_device_id fsl_micfil_dt_ids[] = {
+ { .compatible = "fsl,imx8mm-micfil", .data = &fsl_micfil_imx8mm },
+ { .compatible = "fsl,imx8mp-micfil", .data = &fsl_micfil_imx8mp },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_micfil_dt_ids);
+
+static const char * const micfil_quality_select_texts[] = {
+ [QUALITY_HIGH] = "High",
+ [QUALITY_MEDIUM] = "Medium",
+ [QUALITY_LOW] = "Low",
+ [QUALITY_VLOW0] = "VLow0",
+ [QUALITY_VLOW1] = "Vlow1",
+ [QUALITY_VLOW2] = "Vlow2",
+};
+
+static const struct soc_enum fsl_micfil_quality_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_quality_select_texts),
+ micfil_quality_select_texts);
+
+static DECLARE_TLV_DB_SCALE(gain_tlv, 0, 100, 0);
+
+static int micfil_set_quality(struct fsl_micfil *micfil)
+{
+ u32 qsel;
+
+ switch (micfil->quality) {
+ case QUALITY_HIGH:
+ qsel = MICFIL_QSEL_HIGH_QUALITY;
+ break;
+ case QUALITY_MEDIUM:
+ qsel = MICFIL_QSEL_MEDIUM_QUALITY;
+ break;
+ case QUALITY_LOW:
+ qsel = MICFIL_QSEL_LOW_QUALITY;
+ break;
+ case QUALITY_VLOW0:
+ qsel = MICFIL_QSEL_VLOW0_QUALITY;
+ break;
+ case QUALITY_VLOW1:
+ qsel = MICFIL_QSEL_VLOW1_QUALITY;
+ break;
+ case QUALITY_VLOW2:
+ qsel = MICFIL_QSEL_VLOW2_QUALITY;
+ break;
+ }
+
+ return regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2,
+ MICFIL_CTRL2_QSEL,
+ FIELD_PREP(MICFIL_CTRL2_QSEL, qsel));
+}
+
+static int micfil_quality_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(cmpnt);
+
+ ucontrol->value.integer.value[0] = micfil->quality;
+
+ return 0;
+}
+
+static int micfil_quality_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(cmpnt);
+
+ micfil->quality = ucontrol->value.integer.value[0];
+
+ return micfil_set_quality(micfil);
+}
+
+static const struct snd_kcontrol_new fsl_micfil_snd_controls[] = {
+ SOC_SINGLE_SX_TLV("CH0 Volume", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(0), 0x8, 0xF, gain_tlv),
+ SOC_SINGLE_SX_TLV("CH1 Volume", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(1), 0x8, 0xF, gain_tlv),
+ SOC_SINGLE_SX_TLV("CH2 Volume", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(2), 0x8, 0xF, gain_tlv),
+ SOC_SINGLE_SX_TLV("CH3 Volume", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(3), 0x8, 0xF, gain_tlv),
+ SOC_SINGLE_SX_TLV("CH4 Volume", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(4), 0x8, 0xF, gain_tlv),
+ SOC_SINGLE_SX_TLV("CH5 Volume", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(5), 0x8, 0xF, gain_tlv),
+ SOC_SINGLE_SX_TLV("CH6 Volume", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(6), 0x8, 0xF, gain_tlv),
+ SOC_SINGLE_SX_TLV("CH7 Volume", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(7), 0x8, 0xF, gain_tlv),
+ SOC_ENUM_EXT("MICFIL Quality Select",
+ fsl_micfil_quality_enum,
+ micfil_quality_get, micfil_quality_set),
+};
+
+/* The SRES is a self-negated bit which provides the CPU with the
+ * capability to initialize the PDM Interface module through the
+ * slave-bus interface. This bit always reads as zero, and this
+ * bit is only effective when MDIS is cleared
+ */
+static int fsl_micfil_reset(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regmap_clear_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_MDIS);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_SRES);
+ if (ret)
+ return ret;
+
+ /*
+ * SRES is self-cleared bit, but REG_MICFIL_CTRL1 is defined
+ * as non-volatile register, so SRES still remain in regmap
+ * cache after set, that every update of REG_MICFIL_CTRL1,
+ * software reset happens. so clear it explicitly.
+ */
+ ret = regmap_clear_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_SRES);
+ if (ret)
+ return ret;
+
+ /*
+ * Set SRES should clear CHnF flags, But even add delay here
+ * the CHnF may not be cleared sometimes, so clear CHnF explicitly.
+ */
+ ret = regmap_write_bits(micfil->regmap, REG_MICFIL_STAT, 0xFF, 0xFF);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int fsl_micfil_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+
+ if (!micfil) {
+ dev_err(dai->dev, "micfil dai priv_data not set\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_micfil_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+ struct device *dev = &micfil->pdev->dev;
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = fsl_micfil_reset(dev);
+ if (ret) {
+ dev_err(dev, "failed to soft reset\n");
+ return ret;
+ }
+
+ /* DMA Interrupt Selection - DISEL bits
+ * 00 - DMA and IRQ disabled
+ * 01 - DMA req enabled
+ * 10 - IRQ enabled
+ * 11 - reserved
+ */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_DISEL,
+ FIELD_PREP(MICFIL_CTRL1_DISEL, MICFIL_CTRL1_DISEL_DMA));
+ if (ret)
+ return ret;
+
+ /* Enable the module */
+ ret = regmap_set_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN);
+ if (ret)
+ return ret;
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ /* Disable the module */
+ ret = regmap_clear_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_DISEL,
+ FIELD_PREP(MICFIL_CTRL1_DISEL, MICFIL_CTRL1_DISEL_DISABLE));
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int fsl_micfil_reparent_rootclk(struct fsl_micfil *micfil, unsigned int sample_rate)
+{
+ struct device *dev = &micfil->pdev->dev;
+ u64 ratio = sample_rate;
+ struct clk *clk;
+ int ret;
+
+ /* Get root clock */
+ clk = micfil->mclk;
+
+ /* Disable clock first, for it was enabled by pm_runtime */
+ clk_disable_unprepare(clk);
+ fsl_asoc_reparent_pll_clocks(dev, clk, micfil->pll8k_clk,
+ micfil->pll11k_clk, ratio);
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int fsl_micfil_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+ unsigned int channels = params_channels(params);
+ unsigned int rate = params_rate(params);
+ int clk_div = 8;
+ int osr = MICFIL_OSR_DEFAULT;
+ int ret;
+
+ /* 1. Disable the module */
+ ret = regmap_clear_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN);
+ if (ret)
+ return ret;
+
+ /* enable channels */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ 0xFF, ((1 << channels) - 1));
+ if (ret)
+ return ret;
+
+ ret = fsl_micfil_reparent_rootclk(micfil, rate);
+ if (ret)
+ return ret;
+
+ ret = clk_set_rate(micfil->mclk, rate * clk_div * osr * 8);
+ if (ret)
+ return ret;
+
+ ret = micfil_set_quality(micfil);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2,
+ MICFIL_CTRL2_CLKDIV | MICFIL_CTRL2_CICOSR,
+ FIELD_PREP(MICFIL_CTRL2_CLKDIV, clk_div) |
+ FIELD_PREP(MICFIL_CTRL2_CICOSR, 16 - osr));
+
+ micfil->dma_params_rx.peripheral_config = &micfil->sdmacfg;
+ micfil->dma_params_rx.peripheral_size = sizeof(micfil->sdmacfg);
+ micfil->sdmacfg.n_fifos_src = channels;
+ micfil->sdmacfg.sw_done = true;
+ micfil->dma_params_rx.maxburst = channels * MICFIL_DMA_MAXBURST_RX;
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops fsl_micfil_dai_ops = {
+ .startup = fsl_micfil_startup,
+ .trigger = fsl_micfil_trigger,
+ .hw_params = fsl_micfil_hw_params,
+};
+
+static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(cpu_dai->dev);
+ struct device *dev = cpu_dai->dev;
+ unsigned int val = 0;
+ int ret, i;
+
+ micfil->quality = QUALITY_VLOW0;
+
+ /* set default gain to 2 */
+ regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x22222222);
+
+ /* set DC Remover in bypass mode*/
+ for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++)
+ val |= MICFIL_DC_BYPASS << MICFIL_DC_CHX_SHIFT(i);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_DC_CTRL,
+ MICFIL_DC_CTRL_CONFIG, val);
+ if (ret) {
+ dev_err(dev, "failed to set DC Remover mode bits\n");
+ return ret;
+ }
+ micfil->dc_remover = MICFIL_DC_BYPASS;
+
+ snd_soc_dai_init_dma_data(cpu_dai, NULL,
+ &micfil->dma_params_rx);
+
+ /* FIFO Watermark Control - FIFOWMK*/
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_FIFO_CTRL,
+ MICFIL_FIFO_CTRL_FIFOWMK,
+ FIELD_PREP(MICFIL_FIFO_CTRL_FIFOWMK, micfil->soc->fifo_depth - 1));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_micfil_dai = {
+ .probe = fsl_micfil_dai_probe,
+ .capture = {
+ .stream_name = "CPU-Capture",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &fsl_micfil_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_micfil_component = {
+ .name = "fsl-micfil-dai",
+ .controls = fsl_micfil_snd_controls,
+ .num_controls = ARRAY_SIZE(fsl_micfil_snd_controls),
+ .legacy_dai_naming = 1,
+};
+
+/* REGMAP */
+static const struct reg_default fsl_micfil_reg_defaults[] = {
+ {REG_MICFIL_CTRL1, 0x00000000},
+ {REG_MICFIL_CTRL2, 0x00000000},
+ {REG_MICFIL_STAT, 0x00000000},
+ {REG_MICFIL_FIFO_CTRL, 0x00000007},
+ {REG_MICFIL_FIFO_STAT, 0x00000000},
+ {REG_MICFIL_DATACH0, 0x00000000},
+ {REG_MICFIL_DATACH1, 0x00000000},
+ {REG_MICFIL_DATACH2, 0x00000000},
+ {REG_MICFIL_DATACH3, 0x00000000},
+ {REG_MICFIL_DATACH4, 0x00000000},
+ {REG_MICFIL_DATACH5, 0x00000000},
+ {REG_MICFIL_DATACH6, 0x00000000},
+ {REG_MICFIL_DATACH7, 0x00000000},
+ {REG_MICFIL_DC_CTRL, 0x00000000},
+ {REG_MICFIL_OUT_CTRL, 0x00000000},
+ {REG_MICFIL_OUT_STAT, 0x00000000},
+ {REG_MICFIL_VAD0_CTRL1, 0x00000000},
+ {REG_MICFIL_VAD0_CTRL2, 0x000A0000},
+ {REG_MICFIL_VAD0_STAT, 0x00000000},
+ {REG_MICFIL_VAD0_SCONFIG, 0x00000000},
+ {REG_MICFIL_VAD0_NCONFIG, 0x80000000},
+ {REG_MICFIL_VAD0_NDATA, 0x00000000},
+ {REG_MICFIL_VAD0_ZCD, 0x00000004},
+};
+
+static bool fsl_micfil_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_MICFIL_CTRL1:
+ case REG_MICFIL_CTRL2:
+ case REG_MICFIL_STAT:
+ case REG_MICFIL_FIFO_CTRL:
+ case REG_MICFIL_FIFO_STAT:
+ case REG_MICFIL_DATACH0:
+ case REG_MICFIL_DATACH1:
+ case REG_MICFIL_DATACH2:
+ case REG_MICFIL_DATACH3:
+ case REG_MICFIL_DATACH4:
+ case REG_MICFIL_DATACH5:
+ case REG_MICFIL_DATACH6:
+ case REG_MICFIL_DATACH7:
+ case REG_MICFIL_DC_CTRL:
+ case REG_MICFIL_OUT_CTRL:
+ case REG_MICFIL_OUT_STAT:
+ case REG_MICFIL_VAD0_CTRL1:
+ case REG_MICFIL_VAD0_CTRL2:
+ case REG_MICFIL_VAD0_STAT:
+ case REG_MICFIL_VAD0_SCONFIG:
+ case REG_MICFIL_VAD0_NCONFIG:
+ case REG_MICFIL_VAD0_NDATA:
+ case REG_MICFIL_VAD0_ZCD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_micfil_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_MICFIL_CTRL1:
+ case REG_MICFIL_CTRL2:
+ case REG_MICFIL_STAT: /* Write 1 to Clear */
+ case REG_MICFIL_FIFO_CTRL:
+ case REG_MICFIL_FIFO_STAT: /* Write 1 to Clear */
+ case REG_MICFIL_DC_CTRL:
+ case REG_MICFIL_OUT_CTRL:
+ case REG_MICFIL_OUT_STAT: /* Write 1 to Clear */
+ case REG_MICFIL_VAD0_CTRL1:
+ case REG_MICFIL_VAD0_CTRL2:
+ case REG_MICFIL_VAD0_STAT: /* Write 1 to Clear */
+ case REG_MICFIL_VAD0_SCONFIG:
+ case REG_MICFIL_VAD0_NCONFIG:
+ case REG_MICFIL_VAD0_ZCD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_micfil_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_MICFIL_STAT:
+ case REG_MICFIL_DATACH0:
+ case REG_MICFIL_DATACH1:
+ case REG_MICFIL_DATACH2:
+ case REG_MICFIL_DATACH3:
+ case REG_MICFIL_DATACH4:
+ case REG_MICFIL_DATACH5:
+ case REG_MICFIL_DATACH6:
+ case REG_MICFIL_DATACH7:
+ case REG_MICFIL_VAD0_STAT:
+ case REG_MICFIL_VAD0_NDATA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config fsl_micfil_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .max_register = REG_MICFIL_VAD0_ZCD,
+ .reg_defaults = fsl_micfil_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_micfil_reg_defaults),
+ .readable_reg = fsl_micfil_readable_reg,
+ .volatile_reg = fsl_micfil_volatile_reg,
+ .writeable_reg = fsl_micfil_writeable_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+/* END OF REGMAP */
+
+static irqreturn_t micfil_isr(int irq, void *devid)
+{
+ struct fsl_micfil *micfil = (struct fsl_micfil *)devid;
+ struct platform_device *pdev = micfil->pdev;
+ u32 stat_reg;
+ u32 fifo_stat_reg;
+ u32 ctrl1_reg;
+ bool dma_enabled;
+ int i;
+
+ regmap_read(micfil->regmap, REG_MICFIL_STAT, &stat_reg);
+ regmap_read(micfil->regmap, REG_MICFIL_CTRL1, &ctrl1_reg);
+ regmap_read(micfil->regmap, REG_MICFIL_FIFO_STAT, &fifo_stat_reg);
+
+ dma_enabled = FIELD_GET(MICFIL_CTRL1_DISEL, ctrl1_reg) == MICFIL_CTRL1_DISEL_DMA;
+
+ /* Channel 0-7 Output Data Flags */
+ for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++) {
+ if (stat_reg & MICFIL_STAT_CHXF(i))
+ dev_dbg(&pdev->dev,
+ "Data available in Data Channel %d\n", i);
+ /* if DMA is not enabled, field must be written with 1
+ * to clear
+ */
+ if (!dma_enabled)
+ regmap_write_bits(micfil->regmap,
+ REG_MICFIL_STAT,
+ MICFIL_STAT_CHXF(i),
+ 1);
+ }
+
+ for (i = 0; i < MICFIL_FIFO_NUM; i++) {
+ if (fifo_stat_reg & MICFIL_FIFO_STAT_FIFOX_OVER(i))
+ dev_dbg(&pdev->dev,
+ "FIFO Overflow Exception flag for channel %d\n",
+ i);
+
+ if (fifo_stat_reg & MICFIL_FIFO_STAT_FIFOX_UNDER(i))
+ dev_dbg(&pdev->dev,
+ "FIFO Underflow Exception flag for channel %d\n",
+ i);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t micfil_err_isr(int irq, void *devid)
+{
+ struct fsl_micfil *micfil = (struct fsl_micfil *)devid;
+ struct platform_device *pdev = micfil->pdev;
+ u32 stat_reg;
+
+ regmap_read(micfil->regmap, REG_MICFIL_STAT, &stat_reg);
+
+ if (stat_reg & MICFIL_STAT_BSY_FIL)
+ dev_dbg(&pdev->dev, "isr: Decimation Filter is running\n");
+
+ if (stat_reg & MICFIL_STAT_FIR_RDY)
+ dev_dbg(&pdev->dev, "isr: FIR Filter Data ready\n");
+
+ if (stat_reg & MICFIL_STAT_LOWFREQF) {
+ dev_dbg(&pdev->dev, "isr: ipg_clk_app is too low\n");
+ regmap_write_bits(micfil->regmap, REG_MICFIL_STAT,
+ MICFIL_STAT_LOWFREQF, 1);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int fsl_micfil_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_micfil *micfil;
+ struct resource *res;
+ void __iomem *regs;
+ int ret, i;
+
+ micfil = devm_kzalloc(&pdev->dev, sizeof(*micfil), GFP_KERNEL);
+ if (!micfil)
+ return -ENOMEM;
+
+ micfil->pdev = pdev;
+ strncpy(micfil->name, np->name, sizeof(micfil->name) - 1);
+
+ micfil->soc = of_device_get_match_data(&pdev->dev);
+
+ /* ipg_clk is used to control the registers
+ * ipg_clk_app is used to operate the filter
+ */
+ micfil->mclk = devm_clk_get(&pdev->dev, "ipg_clk_app");
+ if (IS_ERR(micfil->mclk)) {
+ dev_err(&pdev->dev, "failed to get core clock: %ld\n",
+ PTR_ERR(micfil->mclk));
+ return PTR_ERR(micfil->mclk);
+ }
+
+ micfil->busclk = devm_clk_get(&pdev->dev, "ipg_clk");
+ if (IS_ERR(micfil->busclk)) {
+ dev_err(&pdev->dev, "failed to get ipg clock: %ld\n",
+ PTR_ERR(micfil->busclk));
+ return PTR_ERR(micfil->busclk);
+ }
+
+ fsl_asoc_get_pll_clocks(&pdev->dev, &micfil->pll8k_clk,
+ &micfil->pll11k_clk);
+
+ /* init regmap */
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ micfil->regmap = devm_regmap_init_mmio(&pdev->dev,
+ regs,
+ &fsl_micfil_regmap_config);
+ if (IS_ERR(micfil->regmap)) {
+ dev_err(&pdev->dev, "failed to init MICFIL regmap: %ld\n",
+ PTR_ERR(micfil->regmap));
+ return PTR_ERR(micfil->regmap);
+ }
+
+ /* dataline mask for RX */
+ ret = of_property_read_u32_index(np,
+ "fsl,dataline",
+ 0,
+ &micfil->dataline);
+ if (ret)
+ micfil->dataline = 1;
+
+ if (micfil->dataline & ~micfil->soc->dataline) {
+ dev_err(&pdev->dev, "dataline setting error, Mask is 0x%X\n",
+ micfil->soc->dataline);
+ return -EINVAL;
+ }
+
+ /* get IRQs */
+ for (i = 0; i < MICFIL_IRQ_LINES; i++) {
+ micfil->irq[i] = platform_get_irq(pdev, i);
+ if (micfil->irq[i] < 0)
+ return micfil->irq[i];
+ }
+
+ /* Digital Microphone interface interrupt */
+ ret = devm_request_irq(&pdev->dev, micfil->irq[0],
+ micfil_isr, IRQF_SHARED,
+ micfil->name, micfil);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim mic interface irq %u\n",
+ micfil->irq[0]);
+ return ret;
+ }
+
+ /* Digital Microphone interface error interrupt */
+ ret = devm_request_irq(&pdev->dev, micfil->irq[1],
+ micfil_err_isr, IRQF_SHARED,
+ micfil->name, micfil);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim mic interface error irq %u\n",
+ micfil->irq[1]);
+ return ret;
+ }
+
+ micfil->dma_params_rx.chan_name = "rx";
+ micfil->dma_params_rx.addr = res->start + REG_MICFIL_DATACH0;
+ micfil->dma_params_rx.maxburst = MICFIL_DMA_MAXBURST_RX;
+
+ platform_set_drvdata(pdev, micfil);
+
+ pm_runtime_enable(&pdev->dev);
+ regcache_cache_only(micfil->regmap, true);
+
+ /*
+ * Register platform component before registering cpu dai for there
+ * is not defer probe for platform component in snd_soc_add_pcm_runtime().
+ */
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to pcm register\n");
+ goto err_pm_disable;
+ }
+
+ fsl_micfil_dai.capture.formats = micfil->soc->formats;
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_micfil_component,
+ &fsl_micfil_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register component %s\n",
+ fsl_micfil_component.name);
+ goto err_pm_disable;
+ }
+
+ return ret;
+
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static void fsl_micfil_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+}
+
+static int __maybe_unused fsl_micfil_runtime_suspend(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+
+ regcache_cache_only(micfil->regmap, true);
+
+ clk_disable_unprepare(micfil->mclk);
+ clk_disable_unprepare(micfil->busclk);
+
+ return 0;
+}
+
+static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(micfil->busclk);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_prepare_enable(micfil->mclk);
+ if (ret < 0) {
+ clk_disable_unprepare(micfil->busclk);
+ return ret;
+ }
+
+ regcache_cache_only(micfil->regmap, false);
+ regcache_mark_dirty(micfil->regmap);
+ regcache_sync(micfil->regmap);
+
+ return 0;
+}
+
+static int __maybe_unused fsl_micfil_suspend(struct device *dev)
+{
+ pm_runtime_force_suspend(dev);
+
+ return 0;
+}
+
+static int __maybe_unused fsl_micfil_resume(struct device *dev)
+{
+ pm_runtime_force_resume(dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops fsl_micfil_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_micfil_runtime_suspend,
+ fsl_micfil_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_micfil_suspend,
+ fsl_micfil_resume)
+};
+
+static struct platform_driver fsl_micfil_driver = {
+ .probe = fsl_micfil_probe,
+ .remove_new = fsl_micfil_remove,
+ .driver = {
+ .name = "fsl-micfil-dai",
+ .pm = &fsl_micfil_pm_ops,
+ .of_match_table = fsl_micfil_dt_ids,
+ },
+};
+module_platform_driver(fsl_micfil_driver);
+
+MODULE_AUTHOR("Cosmin-Gabriel Samoila <cosmin.samoila@nxp.com>");
+MODULE_DESCRIPTION("NXP PDM Microphone Interface (MICFIL) driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_micfil.h b/sound/soc/fsl/fsl_micfil.h
new file mode 100644
index 000000000..d60285dd0
--- /dev/null
+++ b/sound/soc/fsl/fsl_micfil.h
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PDM Microphone Interface for the NXP i.MX SoC
+ * Copyright 2018 NXP
+ */
+
+#ifndef _FSL_MICFIL_H
+#define _FSL_MICFIL_H
+
+/* MICFIL Register Map */
+#define REG_MICFIL_CTRL1 0x00
+#define REG_MICFIL_CTRL2 0x04
+#define REG_MICFIL_STAT 0x08
+#define REG_MICFIL_FIFO_CTRL 0x10
+#define REG_MICFIL_FIFO_STAT 0x14
+#define REG_MICFIL_DATACH0 0x24
+#define REG_MICFIL_DATACH1 0x28
+#define REG_MICFIL_DATACH2 0x2C
+#define REG_MICFIL_DATACH3 0x30
+#define REG_MICFIL_DATACH4 0x34
+#define REG_MICFIL_DATACH5 0x38
+#define REG_MICFIL_DATACH6 0x3C
+#define REG_MICFIL_DATACH7 0x40
+#define REG_MICFIL_DC_CTRL 0x64
+#define REG_MICFIL_OUT_CTRL 0x74
+#define REG_MICFIL_OUT_STAT 0x7C
+#define REG_MICFIL_VAD0_CTRL1 0x90
+#define REG_MICFIL_VAD0_CTRL2 0x94
+#define REG_MICFIL_VAD0_STAT 0x98
+#define REG_MICFIL_VAD0_SCONFIG 0x9C
+#define REG_MICFIL_VAD0_NCONFIG 0xA0
+#define REG_MICFIL_VAD0_NDATA 0xA4
+#define REG_MICFIL_VAD0_ZCD 0xA8
+
+/* MICFIL Control Register 1 -- REG_MICFILL_CTRL1 0x00 */
+#define MICFIL_CTRL1_MDIS BIT(31)
+#define MICFIL_CTRL1_DOZEN BIT(30)
+#define MICFIL_CTRL1_PDMIEN BIT(29)
+#define MICFIL_CTRL1_DBG BIT(28)
+#define MICFIL_CTRL1_SRES BIT(27)
+#define MICFIL_CTRL1_DBGE BIT(26)
+
+#define MICFIL_CTRL1_DISEL_DISABLE 0
+#define MICFIL_CTRL1_DISEL_DMA 1
+#define MICFIL_CTRL1_DISEL_IRQ 2
+#define MICFIL_CTRL1_DISEL GENMASK(25, 24)
+#define MICFIL_CTRL1_ERREN BIT(23)
+#define MICFIL_CTRL1_CHEN(ch) BIT(ch)
+
+/* MICFIL Control Register 2 -- REG_MICFILL_CTRL2 0x04 */
+#define MICFIL_CTRL2_QSEL_SHIFT 25
+#define MICFIL_CTRL2_QSEL GENMASK(27, 25)
+#define MICFIL_QSEL_MEDIUM_QUALITY 0
+#define MICFIL_QSEL_HIGH_QUALITY 1
+#define MICFIL_QSEL_LOW_QUALITY 7
+#define MICFIL_QSEL_VLOW0_QUALITY 6
+#define MICFIL_QSEL_VLOW1_QUALITY 5
+#define MICFIL_QSEL_VLOW2_QUALITY 4
+
+#define MICFIL_CTRL2_CICOSR GENMASK(19, 16)
+#define MICFIL_CTRL2_CLKDIV GENMASK(7, 0)
+
+/* MICFIL Status Register -- REG_MICFIL_STAT 0x08 */
+#define MICFIL_STAT_BSY_FIL BIT(31)
+#define MICFIL_STAT_FIR_RDY BIT(30)
+#define MICFIL_STAT_LOWFREQF BIT(29)
+#define MICFIL_STAT_CHXF(ch) BIT(ch)
+
+/* MICFIL FIFO Control Register -- REG_MICFIL_FIFO_CTRL 0x10 */
+#define MICFIL_FIFO_CTRL_FIFOWMK GENMASK(2, 0)
+
+/* MICFIL FIFO Status Register -- REG_MICFIL_FIFO_STAT 0x14 */
+#define MICFIL_FIFO_STAT_FIFOX_OVER(ch) BIT(ch)
+#define MICFIL_FIFO_STAT_FIFOX_UNDER(ch) BIT((ch) + 8)
+
+/* MICFIL DC Remover Control Register -- REG_MICFIL_DC_CTRL */
+#define MICFIL_DC_CTRL_CONFIG GENMASK(15, 0)
+#define MICFIL_DC_CHX_SHIFT(ch) ((ch) << 1)
+#define MICFIL_DC_CHX(ch) GENMASK((((ch) << 1) + 1), ((ch) << 1))
+#define MICFIL_DC_CUTOFF_21HZ 0
+#define MICFIL_DC_CUTOFF_83HZ 1
+#define MICFIL_DC_CUTOFF_152Hz 2
+#define MICFIL_DC_BYPASS 3
+
+/* MICFIL HWVAD0 Control 1 Register -- REG_MICFIL_VAD0_CTRL1*/
+#define MICFIL_VAD0_CTRL1_CHSEL GENMASK(26, 24)
+#define MICFIL_VAD0_CTRL1_CICOSR GENMASK(19, 16)
+#define MICFIL_VAD0_CTRL1_INITT GENMASK(12, 8)
+#define MICFIL_VAD0_CTRL1_ST10 BIT(4)
+#define MICFIL_VAD0_CTRL1_ERIE BIT(3)
+#define MICFIL_VAD0_CTRL1_IE BIT(2)
+#define MICFIL_VAD0_CTRL1_RST BIT(1)
+#define MICFIL_VAD0_CTRL1_EN BIT(0)
+
+/* MICFIL HWVAD0 Control 2 Register -- REG_MICFIL_VAD0_CTRL2*/
+#define MICFIL_VAD0_CTRL2_FRENDIS BIT(31)
+#define MICFIL_VAD0_CTRL2_PREFEN BIT(30)
+#define MICFIL_VAD0_CTRL2_FOUTDIS BIT(28)
+#define MICFIL_VAD0_CTRL2_FRAMET GENMASK(21, 16)
+#define MICFIL_VAD0_CTRL2_INPGAIN GENMASK(11, 8)
+#define MICFIL_VAD0_CTRL2_HPF GENMASK(1, 0)
+
+/* MICFIL HWVAD0 Signal CONFIG Register -- REG_MICFIL_VAD0_SCONFIG */
+#define MICFIL_VAD0_SCONFIG_SFILEN BIT(31)
+#define MICFIL_VAD0_SCONFIG_SMAXEN BIT(30)
+#define MICFIL_VAD0_SCONFIG_SGAIN GENMASK(3, 0)
+
+/* MICFIL HWVAD0 Noise CONFIG Register -- REG_MICFIL_VAD0_NCONFIG */
+#define MICFIL_VAD0_NCONFIG_NFILAUT BIT(31)
+#define MICFIL_VAD0_NCONFIG_NMINEN BIT(30)
+#define MICFIL_VAD0_NCONFIG_NDECEN BIT(29)
+#define MICFIL_VAD0_NCONFIG_NOREN BIT(28)
+#define MICFIL_VAD0_NCONFIG_NFILADJ GENMASK(12, 8)
+#define MICFIL_VAD0_NCONFIG_NGAIN GENMASK(3, 0)
+
+/* MICFIL HWVAD0 Zero-Crossing Detector - REG_MICFIL_VAD0_ZCD */
+#define MICFIL_VAD0_ZCD_ZCDTH GENMASK(25, 16)
+#define MICFIL_VAD0_ZCD_ZCDADJ GENMASK(11, 8)
+#define MICFIL_VAD0_ZCD_ZCDAND BIT(4)
+#define MICFIL_VAD0_ZCD_ZCDAUT BIT(2)
+#define MICFIL_VAD0_ZCD_ZCDEN BIT(0)
+
+/* MICFIL HWVAD0 Status Register - REG_MICFIL_VAD0_STAT */
+#define MICFIL_VAD0_STAT_INITF BIT(31)
+#define MICFIL_VAD0_STAT_INSATF BIT(16)
+#define MICFIL_VAD0_STAT_EF BIT(15)
+#define MICFIL_VAD0_STAT_IF BIT(0)
+
+/* MICFIL Output Control Register */
+#define MICFIL_OUTGAIN_CHX_SHIFT(v) (4 * (v))
+
+/* Constants */
+#define MICFIL_OUTPUT_CHANNELS 8
+#define MICFIL_FIFO_NUM 8
+
+#define FIFO_PTRWID 3
+#define FIFO_LEN BIT(FIFO_PTRWID)
+
+#define MICFIL_IRQ_LINES 2
+#define MICFIL_MAX_RETRY 25
+#define MICFIL_SLEEP_MIN 90000 /* in us */
+#define MICFIL_SLEEP_MAX 100000 /* in us */
+#define MICFIL_DMA_MAXBURST_RX 6
+
+#endif /* _FSL_MICFIL_H */
diff --git a/sound/soc/fsl/fsl_mqs.c b/sound/soc/fsl/fsl_mqs.c
new file mode 100644
index 000000000..32d20d351
--- /dev/null
+++ b/sound/soc/fsl/fsl_mqs.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC IMX MQS driver
+//
+// Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
+// Copyright 2019 NXP
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+
+#define REG_MQS_CTRL 0x00
+
+#define MQS_EN_MASK (0x1 << 28)
+#define MQS_EN_SHIFT (28)
+#define MQS_SW_RST_MASK (0x1 << 24)
+#define MQS_SW_RST_SHIFT (24)
+#define MQS_OVERSAMPLE_MASK (0x1 << 20)
+#define MQS_OVERSAMPLE_SHIFT (20)
+#define MQS_CLK_DIV_MASK (0xFF << 0)
+#define MQS_CLK_DIV_SHIFT (0)
+
+/**
+ * struct fsl_mqs_soc_data - soc specific data
+ *
+ * @use_gpr: control register is in General Purpose Register group
+ * @ctrl_off: control register offset
+ * @en_mask: enable bit mask
+ * @en_shift: enable bit shift
+ * @rst_mask: reset bit mask
+ * @rst_shift: reset bit shift
+ * @osr_mask: oversample bit mask
+ * @osr_shift: oversample bit shift
+ * @div_mask: clock divider mask
+ * @div_shift: clock divider bit shift
+ */
+struct fsl_mqs_soc_data {
+ bool use_gpr;
+ int ctrl_off;
+ int en_mask;
+ int en_shift;
+ int rst_mask;
+ int rst_shift;
+ int osr_mask;
+ int osr_shift;
+ int div_mask;
+ int div_shift;
+};
+
+/* codec private data */
+struct fsl_mqs {
+ struct regmap *regmap;
+ struct clk *mclk;
+ struct clk *ipg;
+ const struct fsl_mqs_soc_data *soc;
+
+ unsigned int reg_mqs_ctrl;
+};
+
+#define FSL_MQS_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+#define FSL_MQS_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static int fsl_mqs_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component);
+ unsigned long mclk_rate;
+ int div, res;
+ int lrclk;
+
+ mclk_rate = clk_get_rate(mqs_priv->mclk);
+ lrclk = params_rate(params);
+
+ /*
+ * mclk_rate / (oversample(32,64) * FS * 2 * divider ) = repeat_rate;
+ * if repeat_rate is 8, mqs can achieve better quality.
+ * oversample rate is fix to 32 currently.
+ */
+ div = mclk_rate / (32 * lrclk * 2 * 8);
+ res = mclk_rate % (32 * lrclk * 2 * 8);
+
+ if (res == 0 && div > 0 && div <= 256) {
+ regmap_update_bits(mqs_priv->regmap, mqs_priv->soc->ctrl_off,
+ mqs_priv->soc->div_mask,
+ (div - 1) << mqs_priv->soc->div_shift);
+ regmap_update_bits(mqs_priv->regmap, mqs_priv->soc->ctrl_off,
+ mqs_priv->soc->osr_mask, 0);
+ } else {
+ dev_err(component->dev, "can't get proper divider\n");
+ }
+
+ return 0;
+}
+
+static int fsl_mqs_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ /* Only LEFT_J & SLAVE mode is supported. */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_CBC_CFC:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_mqs_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component);
+
+ regmap_update_bits(mqs_priv->regmap, mqs_priv->soc->ctrl_off,
+ mqs_priv->soc->en_mask,
+ 1 << mqs_priv->soc->en_shift);
+ return 0;
+}
+
+static void fsl_mqs_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component);
+
+ regmap_update_bits(mqs_priv->regmap, mqs_priv->soc->ctrl_off,
+ mqs_priv->soc->en_mask, 0);
+}
+
+static const struct snd_soc_component_driver soc_codec_fsl_mqs = {
+ .idle_bias_on = 1,
+};
+
+static const struct snd_soc_dai_ops fsl_mqs_dai_ops = {
+ .startup = fsl_mqs_startup,
+ .shutdown = fsl_mqs_shutdown,
+ .hw_params = fsl_mqs_hw_params,
+ .set_fmt = fsl_mqs_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver fsl_mqs_dai = {
+ .name = "fsl-mqs-dai",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSL_MQS_RATES,
+ .formats = FSL_MQS_FORMATS,
+ },
+ .ops = &fsl_mqs_dai_ops,
+};
+
+static const struct regmap_config fsl_mqs_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = REG_MQS_CTRL,
+ .cache_type = REGCACHE_NONE,
+};
+
+static int fsl_mqs_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *gpr_np = NULL;
+ struct fsl_mqs *mqs_priv;
+ void __iomem *regs;
+ int ret;
+
+ mqs_priv = devm_kzalloc(&pdev->dev, sizeof(*mqs_priv), GFP_KERNEL);
+ if (!mqs_priv)
+ return -ENOMEM;
+
+ /* On i.MX6sx the MQS control register is in GPR domain
+ * But in i.MX8QM/i.MX8QXP the control register is moved
+ * to its own domain.
+ */
+ mqs_priv->soc = of_device_get_match_data(&pdev->dev);
+
+ if (mqs_priv->soc->use_gpr) {
+ gpr_np = of_parse_phandle(np, "gpr", 0);
+ if (!gpr_np) {
+ dev_err(&pdev->dev, "failed to get gpr node by phandle\n");
+ return -EINVAL;
+ }
+
+ mqs_priv->regmap = syscon_node_to_regmap(gpr_np);
+ of_node_put(gpr_np);
+ if (IS_ERR(mqs_priv->regmap)) {
+ dev_err(&pdev->dev, "failed to get gpr regmap\n");
+ return PTR_ERR(mqs_priv->regmap);
+ }
+ } else {
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ mqs_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+ "core",
+ regs,
+ &fsl_mqs_regmap_config);
+ if (IS_ERR(mqs_priv->regmap)) {
+ dev_err(&pdev->dev, "failed to init regmap: %ld\n",
+ PTR_ERR(mqs_priv->regmap));
+ return PTR_ERR(mqs_priv->regmap);
+ }
+
+ mqs_priv->ipg = devm_clk_get(&pdev->dev, "core");
+ if (IS_ERR(mqs_priv->ipg)) {
+ dev_err(&pdev->dev, "failed to get the clock: %ld\n",
+ PTR_ERR(mqs_priv->ipg));
+ return PTR_ERR(mqs_priv->ipg);
+ }
+ }
+
+ mqs_priv->mclk = devm_clk_get(&pdev->dev, "mclk");
+ if (IS_ERR(mqs_priv->mclk)) {
+ dev_err(&pdev->dev, "failed to get the clock: %ld\n",
+ PTR_ERR(mqs_priv->mclk));
+ return PTR_ERR(mqs_priv->mclk);
+ }
+
+ dev_set_drvdata(&pdev->dev, mqs_priv);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &soc_codec_fsl_mqs,
+ &fsl_mqs_dai, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int fsl_mqs_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_mqs_runtime_resume(struct device *dev)
+{
+ struct fsl_mqs *mqs_priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(mqs_priv->ipg);
+ if (ret) {
+ dev_err(dev, "failed to enable ipg clock\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mqs_priv->mclk);
+ if (ret) {
+ dev_err(dev, "failed to enable mclk clock\n");
+ clk_disable_unprepare(mqs_priv->ipg);
+ return ret;
+ }
+
+ regmap_write(mqs_priv->regmap, mqs_priv->soc->ctrl_off, mqs_priv->reg_mqs_ctrl);
+ return 0;
+}
+
+static int fsl_mqs_runtime_suspend(struct device *dev)
+{
+ struct fsl_mqs *mqs_priv = dev_get_drvdata(dev);
+
+ regmap_read(mqs_priv->regmap, mqs_priv->soc->ctrl_off, &mqs_priv->reg_mqs_ctrl);
+
+ clk_disable_unprepare(mqs_priv->mclk);
+ clk_disable_unprepare(mqs_priv->ipg);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops fsl_mqs_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_mqs_runtime_suspend,
+ fsl_mqs_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static const struct fsl_mqs_soc_data fsl_mqs_imx8qm_data = {
+ .use_gpr = false,
+ .ctrl_off = REG_MQS_CTRL,
+ .en_mask = MQS_EN_MASK,
+ .en_shift = MQS_EN_SHIFT,
+ .rst_mask = MQS_SW_RST_MASK,
+ .rst_shift = MQS_SW_RST_SHIFT,
+ .osr_mask = MQS_OVERSAMPLE_MASK,
+ .osr_shift = MQS_OVERSAMPLE_SHIFT,
+ .div_mask = MQS_CLK_DIV_MASK,
+ .div_shift = MQS_CLK_DIV_SHIFT,
+};
+
+static const struct fsl_mqs_soc_data fsl_mqs_imx6sx_data = {
+ .use_gpr = true,
+ .ctrl_off = IOMUXC_GPR2,
+ .en_mask = IMX6SX_GPR2_MQS_EN_MASK,
+ .en_shift = IMX6SX_GPR2_MQS_EN_SHIFT,
+ .rst_mask = IMX6SX_GPR2_MQS_SW_RST_MASK,
+ .rst_shift = IMX6SX_GPR2_MQS_SW_RST_SHIFT,
+ .osr_mask = IMX6SX_GPR2_MQS_OVERSAMPLE_MASK,
+ .osr_shift = IMX6SX_GPR2_MQS_OVERSAMPLE_SHIFT,
+ .div_mask = IMX6SX_GPR2_MQS_CLK_DIV_MASK,
+ .div_shift = IMX6SX_GPR2_MQS_CLK_DIV_SHIFT,
+};
+
+static const struct fsl_mqs_soc_data fsl_mqs_imx93_data = {
+ .use_gpr = true,
+ .ctrl_off = 0x20,
+ .en_mask = BIT(1),
+ .en_shift = 1,
+ .rst_mask = BIT(2),
+ .rst_shift = 2,
+ .osr_mask = BIT(3),
+ .osr_shift = 3,
+ .div_mask = GENMASK(15, 8),
+ .div_shift = 8,
+};
+
+static const struct of_device_id fsl_mqs_dt_ids[] = {
+ { .compatible = "fsl,imx8qm-mqs", .data = &fsl_mqs_imx8qm_data },
+ { .compatible = "fsl,imx6sx-mqs", .data = &fsl_mqs_imx6sx_data },
+ { .compatible = "fsl,imx93-mqs", .data = &fsl_mqs_imx93_data },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_mqs_dt_ids);
+
+static struct platform_driver fsl_mqs_driver = {
+ .probe = fsl_mqs_probe,
+ .remove = fsl_mqs_remove,
+ .driver = {
+ .name = "fsl-mqs",
+ .of_match_table = fsl_mqs_dt_ids,
+ .pm = &fsl_mqs_pm_ops,
+ },
+};
+
+module_platform_driver(fsl_mqs_driver);
+
+MODULE_AUTHOR("Shengjiu Wang <Shengjiu.Wang@nxp.com>");
+MODULE_DESCRIPTION("MQS codec driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:fsl-mqs");
diff --git a/sound/soc/fsl/fsl_rpmsg.c b/sound/soc/fsl/fsl_rpmsg.c
new file mode 100644
index 000000000..5c07a8ff0
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2018-2021 NXP
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
+#include <linux/rpmsg.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_rpmsg.h"
+#include "imx-pcm.h"
+
+#define FSL_RPMSG_RATES (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_48000)
+#define FSL_RPMSG_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+/* 192kHz/32bit/2ch/60s size is 0x574e00 */
+#define LPA_LARGE_BUFFER_SIZE (0x6000000)
+
+static const unsigned int fsl_rpmsg_rates[] = {
+ 8000, 11025, 16000, 22050, 44100,
+ 32000, 48000, 96000, 88200, 176400, 192000,
+ 352800, 384000, 705600, 768000, 1411200, 2822400,
+};
+
+static const struct snd_pcm_hw_constraint_list fsl_rpmsg_rate_constraints = {
+ .count = ARRAY_SIZE(fsl_rpmsg_rates),
+ .list = fsl_rpmsg_rates,
+};
+
+static int fsl_rpmsg_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_rpmsg *rpmsg = snd_soc_dai_get_drvdata(dai);
+ struct clk *p = rpmsg->mclk, *pll = NULL, *npll = NULL;
+ u64 rate = params_rate(params);
+ int ret = 0;
+
+ /* Get current pll parent */
+ while (p && rpmsg->pll8k && rpmsg->pll11k) {
+ struct clk *pp = clk_get_parent(p);
+
+ if (clk_is_match(pp, rpmsg->pll8k) ||
+ clk_is_match(pp, rpmsg->pll11k)) {
+ pll = pp;
+ break;
+ }
+ p = pp;
+ }
+
+ /* Switch to another pll parent if needed. */
+ if (pll) {
+ npll = (do_div(rate, 8000) ? rpmsg->pll11k : rpmsg->pll8k);
+ if (!clk_is_match(pll, npll)) {
+ ret = clk_set_parent(p, npll);
+ if (ret < 0)
+ dev_warn(dai->dev, "failed to set parent %s: %d\n",
+ __clk_get_name(npll), ret);
+ }
+ }
+
+ if (!(rpmsg->mclk_streams & BIT(substream->stream))) {
+ ret = clk_prepare_enable(rpmsg->mclk);
+ if (ret) {
+ dev_err(dai->dev, "failed to enable mclk: %d\n", ret);
+ return ret;
+ }
+
+ rpmsg->mclk_streams |= BIT(substream->stream);
+ }
+
+ return ret;
+}
+
+static int fsl_rpmsg_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_rpmsg *rpmsg = snd_soc_dai_get_drvdata(dai);
+
+ if (rpmsg->mclk_streams & BIT(substream->stream)) {
+ clk_disable_unprepare(rpmsg->mclk);
+ rpmsg->mclk_streams &= ~BIT(substream->stream);
+ }
+
+ return 0;
+}
+
+static int fsl_rpmsg_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &fsl_rpmsg_rate_constraints);
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops fsl_rpmsg_dai_ops = {
+ .startup = fsl_rpmsg_startup,
+ .hw_params = fsl_rpmsg_hw_params,
+ .hw_free = fsl_rpmsg_hw_free,
+};
+
+static struct snd_soc_dai_driver fsl_rpmsg_dai = {
+ .playback = {
+ .stream_name = "CPU-Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_RPMSG_FORMATS,
+ },
+ .capture = {
+ .stream_name = "CPU-Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_RPMSG_FORMATS,
+ },
+ .symmetric_rate = 1,
+ .symmetric_channels = 1,
+ .symmetric_sample_bits = 1,
+ .ops = &fsl_rpmsg_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_component = {
+ .name = "fsl-rpmsg",
+ .legacy_dai_naming = 1,
+};
+
+static const struct fsl_rpmsg_soc_data imx7ulp_data = {
+ .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+};
+
+static const struct fsl_rpmsg_soc_data imx8mm_data = {
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_DSD_U8 |
+ SNDRV_PCM_FMTBIT_DSD_U16_LE | SNDRV_PCM_FMTBIT_DSD_U32_LE,
+};
+
+static const struct fsl_rpmsg_soc_data imx8mn_data = {
+ .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+};
+
+static const struct fsl_rpmsg_soc_data imx8mp_data = {
+ .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |
+ SNDRV_PCM_RATE_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+};
+
+static const struct of_device_id fsl_rpmsg_ids[] = {
+ { .compatible = "fsl,imx7ulp-rpmsg-audio", .data = &imx7ulp_data},
+ { .compatible = "fsl,imx8mm-rpmsg-audio", .data = &imx8mm_data},
+ { .compatible = "fsl,imx8mn-rpmsg-audio", .data = &imx8mn_data},
+ { .compatible = "fsl,imx8mp-rpmsg-audio", .data = &imx8mp_data},
+ { .compatible = "fsl,imx8ulp-rpmsg-audio", .data = &imx7ulp_data},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_rpmsg_ids);
+
+static int fsl_rpmsg_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_rpmsg *rpmsg;
+ int ret;
+
+ rpmsg = devm_kzalloc(&pdev->dev, sizeof(struct fsl_rpmsg), GFP_KERNEL);
+ if (!rpmsg)
+ return -ENOMEM;
+
+ rpmsg->soc_data = of_device_get_match_data(&pdev->dev);
+
+ fsl_rpmsg_dai.playback.rates = rpmsg->soc_data->rates;
+ fsl_rpmsg_dai.capture.rates = rpmsg->soc_data->rates;
+ fsl_rpmsg_dai.playback.formats = rpmsg->soc_data->formats;
+ fsl_rpmsg_dai.capture.formats = rpmsg->soc_data->formats;
+
+ if (of_property_read_bool(np, "fsl,enable-lpa")) {
+ rpmsg->enable_lpa = 1;
+ rpmsg->buffer_size = LPA_LARGE_BUFFER_SIZE;
+ } else {
+ rpmsg->buffer_size = IMX_DEFAULT_DMABUF_SIZE;
+ }
+
+ /* Get the optional clocks */
+ rpmsg->ipg = devm_clk_get_optional(&pdev->dev, "ipg");
+ if (IS_ERR(rpmsg->ipg))
+ return PTR_ERR(rpmsg->ipg);
+
+ rpmsg->mclk = devm_clk_get_optional(&pdev->dev, "mclk");
+ if (IS_ERR(rpmsg->mclk))
+ return PTR_ERR(rpmsg->mclk);
+
+ rpmsg->dma = devm_clk_get_optional(&pdev->dev, "dma");
+ if (IS_ERR(rpmsg->dma))
+ return PTR_ERR(rpmsg->dma);
+
+ rpmsg->pll8k = devm_clk_get_optional(&pdev->dev, "pll8k");
+ if (IS_ERR(rpmsg->pll8k))
+ return PTR_ERR(rpmsg->pll8k);
+
+ rpmsg->pll11k = devm_clk_get_optional(&pdev->dev, "pll11k");
+ if (IS_ERR(rpmsg->pll11k))
+ return PTR_ERR(rpmsg->pll11k);
+
+ platform_set_drvdata(pdev, rpmsg);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
+ &fsl_rpmsg_dai, 1);
+ if (ret)
+ goto err_pm_disable;
+
+ rpmsg->card_pdev = platform_device_register_data(&pdev->dev,
+ "imx-audio-rpmsg",
+ PLATFORM_DEVID_NONE,
+ NULL,
+ 0);
+ if (IS_ERR(rpmsg->card_pdev)) {
+ dev_err(&pdev->dev, "failed to register rpmsg card\n");
+ ret = PTR_ERR(rpmsg->card_pdev);
+ goto err_pm_disable;
+ }
+
+ return 0;
+
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static int fsl_rpmsg_remove(struct platform_device *pdev)
+{
+ struct fsl_rpmsg *rpmsg = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ if (rpmsg->card_pdev)
+ platform_device_unregister(rpmsg->card_pdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_rpmsg_runtime_resume(struct device *dev)
+{
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(rpmsg->ipg);
+ if (ret) {
+ dev_err(dev, "failed to enable ipg clock: %d\n", ret);
+ goto ipg_err;
+ }
+
+ ret = clk_prepare_enable(rpmsg->dma);
+ if (ret) {
+ dev_err(dev, "Failed to enable dma clock %d\n", ret);
+ goto dma_err;
+ }
+
+ return 0;
+
+dma_err:
+ clk_disable_unprepare(rpmsg->ipg);
+ipg_err:
+ return ret;
+}
+
+static int fsl_rpmsg_runtime_suspend(struct device *dev)
+{
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(rpmsg->dma);
+ clk_disable_unprepare(rpmsg->ipg);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops fsl_rpmsg_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_rpmsg_runtime_suspend,
+ fsl_rpmsg_runtime_resume,
+ NULL)
+};
+
+static struct platform_driver fsl_rpmsg_driver = {
+ .probe = fsl_rpmsg_probe,
+ .remove = fsl_rpmsg_remove,
+ .driver = {
+ .name = "fsl_rpmsg",
+ .pm = &fsl_rpmsg_pm_ops,
+ .of_match_table = fsl_rpmsg_ids,
+ },
+};
+module_platform_driver(fsl_rpmsg_driver);
+
+MODULE_DESCRIPTION("Freescale SoC Audio PRMSG CPU Interface");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_ALIAS("platform:fsl_rpmsg");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_rpmsg.h b/sound/soc/fsl/fsl_rpmsg.h
new file mode 100644
index 000000000..b04086fbf
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2017-2021 NXP
+ */
+
+#ifndef __FSL_RPMSG_H
+#define __FSL_RPMSG_H
+
+/*
+ * struct fsl_rpmsg_soc_data
+ * @rates: supported rates
+ * @formats: supported formats
+ */
+struct fsl_rpmsg_soc_data {
+ int rates;
+ u64 formats;
+};
+
+/*
+ * struct fsl_rpmsg - rpmsg private data
+ *
+ * @ipg: ipg clock for cpu dai (SAI)
+ * @mclk: master clock for cpu dai (SAI)
+ * @dma: clock for dma device
+ * @pll8k: parent clock for multiple of 8kHz frequency
+ * @pll11k: parent clock for multiple of 11kHz frequency
+ * @card_pdev: Platform_device pointer to register a sound card
+ * @soc_data: soc specific data
+ * @mclk_streams: Active streams that are using baudclk
+ * @force_lpa: force enable low power audio routine if condition satisfy
+ * @enable_lpa: enable low power audio routine according to dts setting
+ * @buffer_size: pre allocated dma buffer size
+ */
+struct fsl_rpmsg {
+ struct clk *ipg;
+ struct clk *mclk;
+ struct clk *dma;
+ struct clk *pll8k;
+ struct clk *pll11k;
+ struct platform_device *card_pdev;
+ const struct fsl_rpmsg_soc_data *soc_data;
+ unsigned int mclk_streams;
+ int force_lpa;
+ int enable_lpa;
+ int buffer_size;
+};
+#endif /* __FSL_RPMSG_H */
diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c
new file mode 100644
index 000000000..cf1cd0460
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai.c
@@ -0,0 +1,1741 @@
+// 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/of_device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pm_qos.h>
+#include <linux/pm_runtime.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 "fsl_utils.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, 352800,
+ 384000, 705600, 768000, 1411200, 2822400,
+};
+
+static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = {
+ .count = ARRAY_SIZE(fsl_sai_rates),
+ .list = fsl_sai_rates,
+};
+
+/**
+ * fsl_sai_dir_is_synced - Check if stream is synced by the opposite stream
+ *
+ * SAI supports synchronous mode using bit/frame clocks of either Transmitter's
+ * or Receiver's for both streams. This function is used to check if clocks of
+ * the stream's are synced by the opposite stream.
+ *
+ * @sai: SAI context
+ * @dir: stream direction
+ */
+static inline bool fsl_sai_dir_is_synced(struct fsl_sai *sai, int dir)
+{
+ int adir = (dir == TX) ? RX : TX;
+
+ /* current dir in async mode while opposite dir in sync mode */
+ return !sai->synchronous[dir] && sai->synchronous[adir];
+}
+
+static struct pinctrl_state *fsl_sai_get_pins_state(struct fsl_sai *sai, u32 bclk)
+{
+ struct pinctrl_state *state = NULL;
+
+ if (sai->is_pdm_mode) {
+ /* DSD512@44.1kHz, DSD512@48kHz */
+ if (bclk >= 22579200)
+ state = pinctrl_lookup_state(sai->pinctrl, "dsd512");
+
+ /* Get default DSD state */
+ if (IS_ERR_OR_NULL(state))
+ state = pinctrl_lookup_state(sai->pinctrl, "dsd");
+ } else {
+ /* 706k32b2c, 768k32b2c, etc */
+ if (bclk >= 45158400)
+ state = pinctrl_lookup_state(sai->pinctrl, "pcm_b2m");
+ }
+
+ /* Get default state */
+ if (IS_ERR_OR_NULL(state))
+ state = pinctrl_lookup_state(sai->pinctrl, "default");
+
+ return state;
+}
+
+static irqreturn_t fsl_sai_isr(int irq, void *devid)
+{
+ struct fsl_sai *sai = (struct fsl_sai *)devid;
+ unsigned int ofs = sai->soc_data->reg_offset;
+ struct device *dev = &sai->pdev->dev;
+ u32 flags, xcsr, mask;
+ irqreturn_t iret = IRQ_NONE;
+
+ /*
+ * 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(ofs), &xcsr);
+ flags = xcsr & mask;
+
+ if (flags)
+ iret = IRQ_HANDLED;
+ 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_dbg(dev, "isr: Tx Frame sync error detected\n");
+
+ if (flags & FSL_SAI_CSR_FEF)
+ dev_dbg(dev, "isr: Transmit underrun detected\n");
+
+ 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(ofs), flags | xcsr);
+
+irq_rx:
+ /* Rx IRQ */
+ regmap_read(sai->regmap, FSL_SAI_RCSR(ofs), &xcsr);
+ flags = xcsr & mask;
+
+ if (flags)
+ iret = IRQ_HANDLED;
+ 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_dbg(dev, "isr: Rx Frame sync error detected\n");
+
+ if (flags & FSL_SAI_CSR_FEF)
+ dev_dbg(dev, "isr: Receive overflow detected\n");
+
+ 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(ofs), flags | xcsr);
+
+out:
+ return iret;
+}
+
+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_bclk_ratio(struct snd_soc_dai *dai,
+ unsigned int ratio)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai);
+
+ sai->bclk_ratio = ratio;
+
+ return 0;
+}
+
+static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, bool tx)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned int ofs = sai->soc_data->reg_offset;
+ 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, ofs),
+ FSL_SAI_CR2_MSEL_MASK, val_cr2);
+
+ return 0;
+}
+
+static int fsl_sai_set_mclk_rate(struct snd_soc_dai *dai, int clk_id, unsigned int freq)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ fsl_asoc_reparent_pll_clocks(dai->dev, sai->mclk_clk[clk_id],
+ sai->pll8k_clk, sai->pll11k_clk, freq);
+
+ ret = clk_set_rate(sai->mclk_clk[clk_id], freq);
+ if (ret < 0)
+ dev_err(dai->dev, "failed to set clock rate (%u): %d\n", freq, ret);
+
+ return ret;
+}
+
+static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ int ret;
+
+ if (dir == SND_SOC_CLOCK_IN)
+ return 0;
+
+ if (freq > 0 && clk_id != FSL_SAI_CLK_BUS) {
+ if (clk_id < 0 || clk_id >= FSL_SAI_MCLK_MAX) {
+ dev_err(cpu_dai->dev, "Unknown clock id: %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ if (IS_ERR_OR_NULL(sai->mclk_clk[clk_id])) {
+ dev_err(cpu_dai->dev, "Unassigned clock: %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ if (sai->mclk_streams == 0) {
+ ret = fsl_sai_set_mclk_rate(cpu_dai, clk_id, freq);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, true);
+ 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, false);
+ 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, bool tx)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned int ofs = sai->soc_data->reg_offset;
+ u32 val_cr2 = 0, val_cr4 = 0;
+
+ if (!sai->is_lsb_first)
+ val_cr4 |= FSL_SAI_CR4_MF;
+
+ sai->is_pdm_mode = false;
+ sai->is_dsp_mode = false;
+ /* 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_PDM:
+ val_cr2 |= FSL_SAI_CR2_BCP;
+ val_cr4 &= ~FSL_SAI_CR4_MF;
+ sai->is_pdm_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 provider masks */
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
+ val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
+ sai->is_consumer_mode = false;
+ break;
+ case SND_SOC_DAIFMT_BC_FC:
+ sai->is_consumer_mode = true;
+ break;
+ case SND_SOC_DAIFMT_BP_FC:
+ val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
+ sai->is_consumer_mode = false;
+ break;
+ case SND_SOC_DAIFMT_BC_FP:
+ val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
+ sai->is_consumer_mode = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs),
+ FSL_SAI_CR2_BCP | FSL_SAI_CR2_BCD_MSTR, val_cr2);
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
+ 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, true);
+ 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, false);
+ 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 int reg, ofs = sai->soc_data->reg_offset;
+ unsigned long clk_rate;
+ u32 savediv = 0, ratio, bestdiff = freq;
+ int adir = tx ? RX : TX;
+ int dir = tx ? TX : RX;
+ u32 id;
+ bool support_1_1_ratio = sai->verid.version >= 0x0301;
+
+ /* Don't apply to consumer mode */
+ if (sai->is_consumer_mode)
+ return 0;
+
+ /*
+ * There is no point in polling MCLK0 if it is identical to MCLK1.
+ * And given that MQS use case has to use MCLK1 though two clocks
+ * are the same, we simply skip MCLK0 and start to find from MCLK1.
+ */
+ id = sai->soc_data->mclk0_is_mclk1 ? 1 : 0;
+
+ for (; id < FSL_SAI_MCLK_MAX; id++) {
+ int diff;
+
+ clk_rate = clk_get_rate(sai->mclk_clk[id]);
+ if (!clk_rate)
+ continue;
+
+ ratio = DIV_ROUND_CLOSEST(clk_rate, freq);
+ if (!ratio || ratio > 512)
+ continue;
+ if (ratio == 1 && !support_1_1_ratio)
+ continue;
+ if ((ratio & 1) && ratio > 1)
+ continue;
+
+ diff = abs((long)clk_rate - ratio * freq);
+
+ /*
+ * Drop the source that can not be
+ * divided into the required rate.
+ */
+ if (diff != 0 && clk_rate / diff < 1000)
+ continue;
+
+ dev_dbg(dai->dev,
+ "ratio %d for freq %dHz based on clock %ldHz\n",
+ ratio, freq, clk_rate);
+
+
+ if (diff < bestdiff) {
+ savediv = ratio;
+ sai->mclk_id[tx] = id;
+ bestdiff = diff;
+ }
+
+ if (diff == 0)
+ break;
+ }
+
+ if (savediv == 0) {
+ dev_err(dai->dev, "failed to derive required %cx rate: %d\n",
+ tx ? 'T' : 'R', freq);
+ return -EINVAL;
+ }
+
+ dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n",
+ sai->mclk_id[tx], savediv, bestdiff);
+
+ /*
+ * 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 (fsl_sai_dir_is_synced(sai, adir))
+ reg = FSL_SAI_xCR2(!tx, ofs);
+ else if (!sai->synchronous[dir])
+ reg = FSL_SAI_xCR2(tx, ofs);
+ else
+ return 0;
+
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_MSEL_MASK,
+ FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
+
+ if (savediv == 1) {
+ regmap_update_bits(sai->regmap, reg,
+ FSL_SAI_CR2_DIV_MASK | FSL_SAI_CR2_BYP,
+ FSL_SAI_CR2_BYP);
+ if (fsl_sai_dir_is_synced(sai, adir))
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs),
+ FSL_SAI_CR2_BCI, FSL_SAI_CR2_BCI);
+ else
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs),
+ FSL_SAI_CR2_BCI, 0);
+ } else {
+ regmap_update_bits(sai->regmap, reg,
+ FSL_SAI_CR2_DIV_MASK | FSL_SAI_CR2_BYP,
+ savediv / 2 - 1);
+ }
+
+ 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);
+ unsigned int ofs = sai->soc_data->reg_offset;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ unsigned int channels = params_channels(params);
+ struct snd_dmaengine_dai_dma_data *dma_params;
+ struct fsl_sai_dl_cfg *dl_cfg = sai->dl_cfg;
+ u32 word_width = params_width(params);
+ int trce_mask = 0, dl_cfg_idx = 0;
+ int dl_cfg_cnt = sai->dl_cfg_cnt;
+ u32 dl_type = FSL_SAI_DL_I2S;
+ u32 val_cr4 = 0, val_cr5 = 0;
+ u32 slots = (channels == 1) ? 2 : channels;
+ u32 slot_width = word_width;
+ int adir = tx ? RX : TX;
+ u32 pins, bclk;
+ u32 watermark;
+ int ret, i;
+
+ if (sai->slot_width)
+ slot_width = sai->slot_width;
+
+ if (sai->slots)
+ slots = sai->slots;
+ else if (sai->bclk_ratio)
+ slots = sai->bclk_ratio / slot_width;
+
+ pins = DIV_ROUND_UP(channels, slots);
+
+ /*
+ * PDM mode, channels are independent
+ * each channels are on one dataline/FIFO.
+ */
+ if (sai->is_pdm_mode) {
+ pins = channels;
+ dl_type = FSL_SAI_DL_PDM;
+ }
+
+ for (i = 0; i < dl_cfg_cnt; i++) {
+ if (dl_cfg[i].type == dl_type && dl_cfg[i].pins[tx] == pins) {
+ dl_cfg_idx = i;
+ break;
+ }
+ }
+
+ if (hweight8(dl_cfg[dl_cfg_idx].mask[tx]) < pins) {
+ dev_err(cpu_dai->dev, "channel not supported\n");
+ return -EINVAL;
+ }
+
+ bclk = params_rate(params) * (sai->bclk_ratio ? sai->bclk_ratio : slots * slot_width);
+
+ if (!IS_ERR_OR_NULL(sai->pinctrl)) {
+ sai->pins_state = fsl_sai_get_pins_state(sai, bclk);
+ if (!IS_ERR_OR_NULL(sai->pins_state)) {
+ ret = pinctrl_select_state(sai->pinctrl, sai->pins_state);
+ if (ret) {
+ dev_err(cpu_dai->dev, "failed to set proper pins state: %d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+ if (!sai->is_consumer_mode) {
+ ret = fsl_sai_set_bclk(cpu_dai, tx, bclk);
+ 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 && !sai->is_pdm_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 || sai->is_pdm_mode)
+ val_cr5 |= FSL_SAI_CR5_FBT(0);
+ else
+ val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1);
+
+ val_cr4 |= FSL_SAI_CR4_FRSZ(slots);
+
+ /* Set to output mode to avoid tri-stated data pins */
+ if (tx)
+ val_cr4 |= FSL_SAI_CR4_CHMOD;
+
+ /*
+ * For SAI provider 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) for playback(capture), or there will be sync error.
+ */
+
+ if (!sai->is_consumer_mode && fsl_sai_dir_is_synced(sai, adir)) {
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(!tx, ofs),
+ FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
+ FSL_SAI_CR4_CHMOD_MASK,
+ val_cr4);
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR5(!tx, ofs),
+ FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
+ FSL_SAI_CR5_FBT_MASK, val_cr5);
+ }
+
+ /*
+ * Combine mode has limation:
+ * - Can't used for singel dataline/FIFO case except the FIFO0
+ * - Can't used for multi dataline/FIFO case except the enabled FIFOs
+ * are successive and start from FIFO0
+ *
+ * So for common usage, all multi fifo case disable the combine mode.
+ */
+ if (hweight8(dl_cfg[dl_cfg_idx].mask[tx]) <= 1 || sai->is_multi_fifo_dma)
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
+ FSL_SAI_CR4_FCOMB_MASK, 0);
+ else
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
+ FSL_SAI_CR4_FCOMB_MASK, FSL_SAI_CR4_FCOMB_SOFT);
+
+ dma_params = tx ? &sai->dma_params_tx : &sai->dma_params_rx;
+ dma_params->addr = sai->res->start + FSL_SAI_xDR0(tx) +
+ dl_cfg[dl_cfg_idx].start_off[tx] * 0x4;
+
+ if (sai->is_multi_fifo_dma) {
+ sai->audio_config[tx].words_per_fifo = min(slots, channels);
+ if (tx) {
+ sai->audio_config[tx].n_fifos_dst = pins;
+ sai->audio_config[tx].stride_fifos_dst = dl_cfg[dl_cfg_idx].next_off[tx];
+ } else {
+ sai->audio_config[tx].n_fifos_src = pins;
+ sai->audio_config[tx].stride_fifos_src = dl_cfg[dl_cfg_idx].next_off[tx];
+ }
+ dma_params->maxburst = sai->audio_config[tx].words_per_fifo * pins;
+ dma_params->peripheral_config = &sai->audio_config[tx];
+ dma_params->peripheral_size = sizeof(sai->audio_config[tx]);
+
+ watermark = tx ? (sai->soc_data->fifo_depth - dma_params->maxburst) :
+ (dma_params->maxburst - 1);
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR1(tx, ofs),
+ FSL_SAI_CR1_RFW_MASK(sai->soc_data->fifo_depth),
+ watermark);
+ }
+
+ /* Find a proper tcre setting */
+ for (i = 0; i < sai->soc_data->pins; i++) {
+ trce_mask = (1 << (i + 1)) - 1;
+ if (hweight8(dl_cfg[dl_cfg_idx].mask[tx] & trce_mask) == pins)
+ break;
+ }
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs),
+ FSL_SAI_CR3_TRCE_MASK,
+ FSL_SAI_CR3_TRCE((dl_cfg[dl_cfg_idx].mask[tx] & trce_mask)));
+
+ /*
+ * When the TERE and FSD_MSTR enabled before configuring the word width
+ * There will be no frame sync clock issue, because word width impact
+ * the generation of frame sync clock.
+ *
+ * TERE enabled earlier only for i.MX8MP case for the hardware limitation,
+ * We need to disable FSD_MSTR before configuring word width, then enable
+ * FSD_MSTR bit for this specific case.
+ */
+ if (sai->soc_data->mclk_with_tere && sai->mclk_direction_output &&
+ !sai->is_consumer_mode)
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
+ FSL_SAI_CR4_FSD_MSTR, 0);
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
+ FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
+ FSL_SAI_CR4_CHMOD_MASK,
+ val_cr4);
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx, ofs),
+ FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
+ FSL_SAI_CR5_FBT_MASK, val_cr5);
+
+ /* Enable FSD_MSTR after configuring word width */
+ if (sai->soc_data->mclk_with_tere && sai->mclk_direction_output &&
+ !sai->is_consumer_mode)
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
+ FSL_SAI_CR4_FSD_MSTR, FSL_SAI_CR4_FSD_MSTR);
+
+ regmap_write(sai->regmap, FSL_SAI_xMR(tx),
+ ~0UL - ((1 << min(channels, slots)) - 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;
+ unsigned int ofs = sai->soc_data->reg_offset;
+
+ /* Clear xMR to avoid channel swap with mclk_with_tere enabled case */
+ regmap_write(sai->regmap, FSL_SAI_xMR(tx), 0);
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs),
+ FSL_SAI_CR3_TRCE_MASK, 0);
+
+ if (!sai->is_consumer_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 void fsl_sai_config_disable(struct fsl_sai *sai, int dir)
+{
+ unsigned int ofs = sai->soc_data->reg_offset;
+ bool tx = dir == TX;
+ u32 xcsr, count = 100, mask;
+
+ if (sai->soc_data->mclk_with_tere && sai->mclk_direction_output)
+ mask = FSL_SAI_CSR_TERE;
+ else
+ mask = FSL_SAI_CSR_TERE | FSL_SAI_CSR_BCE;
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
+ mask, 0);
+
+ /* TERE will remain set till the end of current frame */
+ do {
+ udelay(10);
+ regmap_read(sai->regmap, FSL_SAI_xCSR(tx, ofs), &xcsr);
+ } while (--count && xcsr & FSL_SAI_CSR_TERE);
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
+ 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_consumer_mode) {
+ /* Software Reset */
+ regmap_write(sai->regmap, FSL_SAI_xCSR(tx, ofs), FSL_SAI_CSR_SR);
+ /* Clear SR bit to finish the reset */
+ regmap_write(sai->regmap, FSL_SAI_xCSR(tx, ofs), 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);
+ unsigned int ofs = sai->soc_data->reg_offset;
+
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int adir = tx ? RX : TX;
+ int dir = tx ? TX : RX;
+ u32 xcsr;
+
+ /*
+ * 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(ofs), FSL_SAI_CR2_SYNC,
+ sai->synchronous[TX] ? FSL_SAI_CR2_SYNC : 0);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR2(ofs), 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, ofs),
+ FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE);
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
+ FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+ /*
+ * Enable the opposite direction for synchronous mode
+ * 1. Tx sync with Rx: only set RE for Rx; set TE & RE for Tx
+ * 2. Rx sync with Tx: only set TE for Tx; set RE & TE for Rx
+ *
+ * RM recommends to enable RE after TE for case 1 and to enable
+ * TE after RE for case 2, but we here may not always guarantee
+ * that happens: "arecord 1.wav; aplay 2.wav" in case 1 enables
+ * TE after RE, which is against what RM recommends but should
+ * be safe to do, judging by years of testing results.
+ */
+ if (fsl_sai_dir_is_synced(sai, adir))
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), ofs),
+ FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
+ 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, ofs),
+ FSL_SAI_CSR_FRDE, 0);
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
+ FSL_SAI_CSR_xIE_MASK, 0);
+
+ /* Check if the opposite FRDE is also disabled */
+ regmap_read(sai->regmap, FSL_SAI_xCSR(!tx, ofs), &xcsr);
+
+ /*
+ * If opposite stream provides clocks for synchronous mode and
+ * it is inactive, disable it before disabling the current one
+ */
+ if (fsl_sai_dir_is_synced(sai, adir) && !(xcsr & FSL_SAI_CSR_FRDE))
+ fsl_sai_config_disable(sai, adir);
+
+ /*
+ * Disable current stream if either of:
+ * 1. current stream doesn't provide clocks for synchronous mode
+ * 2. current stream provides clocks for synchronous mode but no
+ * more stream is active.
+ */
+ if (!fsl_sai_dir_is_synced(sai, dir) || !(xcsr & FSL_SAI_CSR_FRDE))
+ fsl_sai_config_disable(sai, dir);
+
+ 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;
+ int ret;
+
+ /*
+ * EDMA controller needs period size to be a multiple of
+ * tx/rx maxburst
+ */
+ if (sai->soc_data->use_edma)
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ tx ? sai->dma_params_tx.maxburst :
+ sai->dma_params_rx.maxburst);
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &fsl_sai_rate_constraints);
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
+ .set_bclk_ratio = fsl_sai_set_dai_bclk_ratio,
+ .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,
+};
+
+static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+ unsigned int ofs = sai->soc_data->reg_offset;
+
+ /* Software Reset for both Tx and Rx */
+ regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), FSL_SAI_CSR_SR);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), FSL_SAI_CSR_SR);
+ /* Clear SR bit to finish the reset */
+ regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), 0);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), 0);
+
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR1(ofs),
+ FSL_SAI_CR1_RFW_MASK(sai->soc_data->fifo_depth),
+ sai->soc_data->fifo_depth - FSL_SAI_MAXBURST_TX);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR1(ofs),
+ FSL_SAI_CR1_RFW_MASK(sai->soc_data->fifo_depth),
+ FSL_SAI_MAXBURST_RX - 1);
+
+ snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx,
+ &sai->dma_params_rx);
+
+ return 0;
+}
+
+static int fsl_sai_dai_resume(struct snd_soc_component *component)
+{
+ struct fsl_sai *sai = snd_soc_component_get_drvdata(component);
+ struct device *dev = &sai->pdev->dev;
+ int ret;
+
+ if (!IS_ERR_OR_NULL(sai->pinctrl) && !IS_ERR_OR_NULL(sai->pins_state)) {
+ ret = pinctrl_select_state(sai->pinctrl, sai->pins_state);
+ if (ret) {
+ dev_err(dev, "failed to set proper pins state: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_sai_dai_template = {
+ .probe = fsl_sai_dai_probe,
+ .playback = {
+ .stream_name = "CPU-Playback",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rate_min = 8000,
+ .rate_max = 2822400,
+ .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 = 2822400,
+ .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",
+ .resume = fsl_sai_dai_resume,
+ .legacy_dai_naming = 1,
+};
+
+static struct reg_default fsl_sai_reg_defaults_ofs0[] = {
+ {FSL_SAI_TCR1(0), 0},
+ {FSL_SAI_TCR2(0), 0},
+ {FSL_SAI_TCR3(0), 0},
+ {FSL_SAI_TCR4(0), 0},
+ {FSL_SAI_TCR5(0), 0},
+ {FSL_SAI_TDR0, 0},
+ {FSL_SAI_TDR1, 0},
+ {FSL_SAI_TDR2, 0},
+ {FSL_SAI_TDR3, 0},
+ {FSL_SAI_TDR4, 0},
+ {FSL_SAI_TDR5, 0},
+ {FSL_SAI_TDR6, 0},
+ {FSL_SAI_TDR7, 0},
+ {FSL_SAI_TMR, 0},
+ {FSL_SAI_RCR1(0), 0},
+ {FSL_SAI_RCR2(0), 0},
+ {FSL_SAI_RCR3(0), 0},
+ {FSL_SAI_RCR4(0), 0},
+ {FSL_SAI_RCR5(0), 0},
+ {FSL_SAI_RMR, 0},
+};
+
+static struct reg_default fsl_sai_reg_defaults_ofs8[] = {
+ {FSL_SAI_TCR1(8), 0},
+ {FSL_SAI_TCR2(8), 0},
+ {FSL_SAI_TCR3(8), 0},
+ {FSL_SAI_TCR4(8), 0},
+ {FSL_SAI_TCR5(8), 0},
+ {FSL_SAI_TDR0, 0},
+ {FSL_SAI_TDR1, 0},
+ {FSL_SAI_TDR2, 0},
+ {FSL_SAI_TDR3, 0},
+ {FSL_SAI_TDR4, 0},
+ {FSL_SAI_TDR5, 0},
+ {FSL_SAI_TDR6, 0},
+ {FSL_SAI_TDR7, 0},
+ {FSL_SAI_TMR, 0},
+ {FSL_SAI_RCR1(8), 0},
+ {FSL_SAI_RCR2(8), 0},
+ {FSL_SAI_RCR3(8), 0},
+ {FSL_SAI_RCR4(8), 0},
+ {FSL_SAI_RCR5(8), 0},
+ {FSL_SAI_RMR, 0},
+ {FSL_SAI_MCTL, 0},
+ {FSL_SAI_MDIV, 0},
+};
+
+static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+ unsigned int ofs = sai->soc_data->reg_offset;
+
+ if (reg >= FSL_SAI_TCSR(ofs) && reg <= FSL_SAI_TCR5(ofs))
+ return true;
+
+ if (reg >= FSL_SAI_RCSR(ofs) && reg <= FSL_SAI_RCR5(ofs))
+ return true;
+
+ switch (reg) {
+ case FSL_SAI_TFR0:
+ case FSL_SAI_TFR1:
+ case FSL_SAI_TFR2:
+ case FSL_SAI_TFR3:
+ case FSL_SAI_TFR4:
+ case FSL_SAI_TFR5:
+ case FSL_SAI_TFR6:
+ case FSL_SAI_TFR7:
+ case FSL_SAI_TMR:
+ case FSL_SAI_RDR0:
+ case FSL_SAI_RDR1:
+ case FSL_SAI_RDR2:
+ case FSL_SAI_RDR3:
+ case FSL_SAI_RDR4:
+ case FSL_SAI_RDR5:
+ case FSL_SAI_RDR6:
+ case FSL_SAI_RDR7:
+ case FSL_SAI_RFR0:
+ case FSL_SAI_RFR1:
+ case FSL_SAI_RFR2:
+ case FSL_SAI_RFR3:
+ case FSL_SAI_RFR4:
+ case FSL_SAI_RFR5:
+ case FSL_SAI_RFR6:
+ case FSL_SAI_RFR7:
+ case FSL_SAI_RMR:
+ case FSL_SAI_MCTL:
+ case FSL_SAI_MDIV:
+ case FSL_SAI_VERID:
+ case FSL_SAI_PARAM:
+ case FSL_SAI_TTCTN:
+ case FSL_SAI_RTCTN:
+ case FSL_SAI_TTCTL:
+ case FSL_SAI_TBCTN:
+ case FSL_SAI_TTCAP:
+ case FSL_SAI_RTCTL:
+ case FSL_SAI_RBCTN:
+ case FSL_SAI_RTCAP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+ unsigned int ofs = sai->soc_data->reg_offset;
+
+ if (reg == FSL_SAI_TCSR(ofs) || reg == FSL_SAI_RCSR(ofs))
+ return true;
+
+ /* Set VERID and PARAM be volatile for reading value in probe */
+ if (ofs == 8 && (reg == FSL_SAI_VERID || reg == FSL_SAI_PARAM))
+ return true;
+
+ switch (reg) {
+ case FSL_SAI_TFR0:
+ case FSL_SAI_TFR1:
+ case FSL_SAI_TFR2:
+ case FSL_SAI_TFR3:
+ case FSL_SAI_TFR4:
+ case FSL_SAI_TFR5:
+ case FSL_SAI_TFR6:
+ case FSL_SAI_TFR7:
+ case FSL_SAI_RFR0:
+ case FSL_SAI_RFR1:
+ case FSL_SAI_RFR2:
+ case FSL_SAI_RFR3:
+ case FSL_SAI_RFR4:
+ case FSL_SAI_RFR5:
+ case FSL_SAI_RFR6:
+ case FSL_SAI_RFR7:
+ case FSL_SAI_RDR0:
+ case FSL_SAI_RDR1:
+ case FSL_SAI_RDR2:
+ case FSL_SAI_RDR3:
+ case FSL_SAI_RDR4:
+ case FSL_SAI_RDR5:
+ case FSL_SAI_RDR6:
+ case FSL_SAI_RDR7:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+ unsigned int ofs = sai->soc_data->reg_offset;
+
+ if (reg >= FSL_SAI_TCSR(ofs) && reg <= FSL_SAI_TCR5(ofs))
+ return true;
+
+ if (reg >= FSL_SAI_RCSR(ofs) && reg <= FSL_SAI_RCR5(ofs))
+ return true;
+
+ switch (reg) {
+ case FSL_SAI_TDR0:
+ case FSL_SAI_TDR1:
+ case FSL_SAI_TDR2:
+ case FSL_SAI_TDR3:
+ case FSL_SAI_TDR4:
+ case FSL_SAI_TDR5:
+ case FSL_SAI_TDR6:
+ case FSL_SAI_TDR7:
+ case FSL_SAI_TMR:
+ case FSL_SAI_RMR:
+ case FSL_SAI_MCTL:
+ case FSL_SAI_MDIV:
+ case FSL_SAI_TTCTL:
+ case FSL_SAI_RTCTL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static struct regmap_config fsl_sai_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .fast_io = true,
+
+ .max_register = FSL_SAI_RMR,
+ .reg_defaults = fsl_sai_reg_defaults_ofs0,
+ .num_reg_defaults = ARRAY_SIZE(fsl_sai_reg_defaults_ofs0),
+ .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_check_version(struct device *dev)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+ unsigned char ofs = sai->soc_data->reg_offset;
+ unsigned int val;
+ int ret;
+
+ if (FSL_SAI_TCSR(ofs) == FSL_SAI_VERID)
+ return 0;
+
+ ret = regmap_read(sai->regmap, FSL_SAI_VERID, &val);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "VERID: 0x%016X\n", val);
+
+ sai->verid.version = val &
+ (FSL_SAI_VERID_MAJOR_MASK | FSL_SAI_VERID_MINOR_MASK);
+ sai->verid.version >>= FSL_SAI_VERID_MINOR_SHIFT;
+ sai->verid.feature = val & FSL_SAI_VERID_FEATURE_MASK;
+
+ ret = regmap_read(sai->regmap, FSL_SAI_PARAM, &val);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "PARAM: 0x%016X\n", val);
+
+ /* Max slots per frame, power of 2 */
+ sai->param.slot_num = 1 <<
+ ((val & FSL_SAI_PARAM_SPF_MASK) >> FSL_SAI_PARAM_SPF_SHIFT);
+
+ /* Words per fifo, power of 2 */
+ sai->param.fifo_depth = 1 <<
+ ((val & FSL_SAI_PARAM_WPF_MASK) >> FSL_SAI_PARAM_WPF_SHIFT);
+
+ /* Number of datalines implemented */
+ sai->param.dataline = val & FSL_SAI_PARAM_DLN_MASK;
+
+ return 0;
+}
+
+/*
+ * Calculate the offset between first two datalines, don't
+ * different offset in one case.
+ */
+static unsigned int fsl_sai_calc_dl_off(unsigned long dl_mask)
+{
+ int fbidx, nbidx, offset;
+
+ fbidx = find_first_bit(&dl_mask, FSL_SAI_DL_NUM);
+ nbidx = find_next_bit(&dl_mask, FSL_SAI_DL_NUM, fbidx + 1);
+ offset = nbidx - fbidx - 1;
+
+ return (offset < 0 || offset >= (FSL_SAI_DL_NUM - 1) ? 0 : offset);
+}
+
+/*
+ * read the fsl,dataline property from dts file.
+ * It has 3 value for each configuration, first one means the type:
+ * I2S(1) or PDM(2), second one is dataline mask for 'rx', third one is
+ * dataline mask for 'tx'. for example
+ *
+ * fsl,dataline = <1 0xff 0xff 2 0xff 0x11>,
+ *
+ * It means I2S type rx mask is 0xff, tx mask is 0xff, PDM type
+ * rx mask is 0xff, tx mask is 0x11 (dataline 1 and 4 enabled).
+ *
+ */
+static int fsl_sai_read_dlcfg(struct fsl_sai *sai)
+{
+ struct platform_device *pdev = sai->pdev;
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ int ret, elems, i, index, num_cfg;
+ char *propname = "fsl,dataline";
+ struct fsl_sai_dl_cfg *cfg;
+ unsigned long dl_mask;
+ unsigned int soc_dl;
+ u32 rx, tx, type;
+
+ elems = of_property_count_u32_elems(np, propname);
+
+ if (elems <= 0) {
+ elems = 0;
+ } else if (elems % 3) {
+ dev_err(dev, "Number of elements must be divisible to 3.\n");
+ return -EINVAL;
+ }
+
+ num_cfg = elems / 3;
+ /* Add one more for default value */
+ cfg = devm_kzalloc(&pdev->dev, (num_cfg + 1) * sizeof(*cfg), GFP_KERNEL);
+ if (!cfg)
+ return -ENOMEM;
+
+ /* Consider default value "0 0xFF 0xFF" if property is missing */
+ soc_dl = BIT(sai->soc_data->pins) - 1;
+ cfg[0].type = FSL_SAI_DL_DEFAULT;
+ cfg[0].pins[0] = sai->soc_data->pins;
+ cfg[0].mask[0] = soc_dl;
+ cfg[0].start_off[0] = 0;
+ cfg[0].next_off[0] = 0;
+
+ cfg[0].pins[1] = sai->soc_data->pins;
+ cfg[0].mask[1] = soc_dl;
+ cfg[0].start_off[1] = 0;
+ cfg[0].next_off[1] = 0;
+ for (i = 1, index = 0; i < num_cfg + 1; i++) {
+ /*
+ * type of dataline
+ * 0 means default mode
+ * 1 means I2S mode
+ * 2 means PDM mode
+ */
+ ret = of_property_read_u32_index(np, propname, index++, &type);
+ if (ret)
+ return -EINVAL;
+
+ ret = of_property_read_u32_index(np, propname, index++, &rx);
+ if (ret)
+ return -EINVAL;
+
+ ret = of_property_read_u32_index(np, propname, index++, &tx);
+ if (ret)
+ return -EINVAL;
+
+ if ((rx & ~soc_dl) || (tx & ~soc_dl)) {
+ dev_err(dev, "dataline cfg[%d] setting error, mask is 0x%x\n", i, soc_dl);
+ return -EINVAL;
+ }
+
+ rx = rx & soc_dl;
+ tx = tx & soc_dl;
+
+ cfg[i].type = type;
+ cfg[i].pins[0] = hweight8(rx);
+ cfg[i].mask[0] = rx;
+ dl_mask = rx;
+ cfg[i].start_off[0] = find_first_bit(&dl_mask, FSL_SAI_DL_NUM);
+ cfg[i].next_off[0] = fsl_sai_calc_dl_off(rx);
+
+ cfg[i].pins[1] = hweight8(tx);
+ cfg[i].mask[1] = tx;
+ dl_mask = tx;
+ cfg[i].start_off[1] = find_first_bit(&dl_mask, FSL_SAI_DL_NUM);
+ cfg[i].next_off[1] = fsl_sai_calc_dl_off(tx);
+ }
+
+ sai->dl_cfg = cfg;
+ sai->dl_cfg_cnt = num_cfg + 1;
+ return 0;
+}
+
+static int fsl_sai_runtime_suspend(struct device *dev);
+static int fsl_sai_runtime_resume(struct device *dev);
+
+static int fsl_sai_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct fsl_sai *sai;
+ struct regmap *gpr;
+ void __iomem *base;
+ char tmp[8];
+ int irq, ret, i;
+ int index;
+ u32 dmas[4];
+
+ sai = devm_kzalloc(dev, sizeof(*sai), GFP_KERNEL);
+ if (!sai)
+ return -ENOMEM;
+
+ sai->pdev = pdev;
+ sai->soc_data = of_device_get_match_data(dev);
+
+ sai->is_lsb_first = of_property_read_bool(np, "lsb-first");
+
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &sai->res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ if (sai->soc_data->reg_offset == 8) {
+ fsl_sai_regmap_config.reg_defaults = fsl_sai_reg_defaults_ofs8;
+ fsl_sai_regmap_config.max_register = FSL_SAI_MDIV;
+ fsl_sai_regmap_config.num_reg_defaults =
+ ARRAY_SIZE(fsl_sai_reg_defaults_ofs8);
+ }
+
+ sai->regmap = devm_regmap_init_mmio(dev, base, &fsl_sai_regmap_config);
+ if (IS_ERR(sai->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(sai->regmap);
+ }
+
+ sai->bus_clk = devm_clk_get(dev, "bus");
+ /* Compatible with old DTB cases */
+ if (IS_ERR(sai->bus_clk) && PTR_ERR(sai->bus_clk) != -EPROBE_DEFER)
+ sai->bus_clk = devm_clk_get(dev, "sai");
+ if (IS_ERR(sai->bus_clk)) {
+ dev_err(dev, "failed to get bus clock: %ld\n",
+ PTR_ERR(sai->bus_clk));
+ /* -EPROBE_DEFER */
+ return PTR_ERR(sai->bus_clk);
+ }
+
+ for (i = 1; i < FSL_SAI_MCLK_MAX; i++) {
+ sprintf(tmp, "mclk%d", i);
+ sai->mclk_clk[i] = devm_clk_get(dev, tmp);
+ if (IS_ERR(sai->mclk_clk[i])) {
+ dev_err(dev, "failed to get mclk%d clock: %ld\n",
+ i, PTR_ERR(sai->mclk_clk[i]));
+ sai->mclk_clk[i] = NULL;
+ }
+ }
+
+ if (sai->soc_data->mclk0_is_mclk1)
+ sai->mclk_clk[0] = sai->mclk_clk[1];
+ else
+ sai->mclk_clk[0] = sai->bus_clk;
+
+ fsl_asoc_get_pll_clocks(&pdev->dev, &sai->pll8k_clk,
+ &sai->pll11k_clk);
+
+ /* Use Multi FIFO mode depending on the support from SDMA script */
+ ret = of_property_read_u32_array(np, "dmas", dmas, 4);
+ if (!sai->soc_data->use_edma && !ret && dmas[2] == IMX_DMATYPE_MULTI_SAI)
+ sai->is_multi_fifo_dma = true;
+
+ /* read dataline mask for rx and tx*/
+ ret = fsl_sai_read_dlcfg(sai);
+ if (ret < 0) {
+ dev_err(dev, "failed to read dlcfg %d\n", ret);
+ return ret;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(dev, irq, fsl_sai_isr, IRQF_SHARED,
+ np->name, sai);
+ if (ret) {
+ dev_err(dev, "failed to claim irq %u\n", irq);
+ return ret;
+ }
+
+ memcpy(&sai->cpu_dai_drv, &fsl_sai_dai_template,
+ sizeof(fsl_sai_dai_template));
+
+ /* Sync Tx with Rx as default by following old DT binding */
+ sai->synchronous[RX] = true;
+ sai->synchronous[TX] = false;
+ sai->cpu_dai_drv.symmetric_rate = 1;
+ sai->cpu_dai_drv.symmetric_channels = 1;
+ sai->cpu_dai_drv.symmetric_sample_bits = 1;
+
+ if (of_property_read_bool(np, "fsl,sai-synchronous-rx") &&
+ of_property_read_bool(np, "fsl,sai-asynchronous")) {
+ /* error out if both synchronous and asynchronous are present */
+ dev_err(dev, "invalid binding for synchronous mode\n");
+ return -EINVAL;
+ }
+
+ if (of_property_read_bool(np, "fsl,sai-synchronous-rx")) {
+ /* Sync Rx with Tx */
+ sai->synchronous[RX] = false;
+ sai->synchronous[TX] = true;
+ } else if (of_property_read_bool(np, "fsl,sai-asynchronous")) {
+ /* Discard all settings for asynchronous mode */
+ sai->synchronous[RX] = false;
+ sai->synchronous[TX] = false;
+ sai->cpu_dai_drv.symmetric_rate = 0;
+ sai->cpu_dai_drv.symmetric_channels = 0;
+ sai->cpu_dai_drv.symmetric_sample_bits = 0;
+ }
+
+ sai->mclk_direction_output = of_property_read_bool(np, "fsl,sai-mclk-direction-output");
+
+ if (sai->mclk_direction_output &&
+ of_device_is_compatible(np, "fsl,imx6ul-sai")) {
+ gpr = syscon_regmap_lookup_by_compatible("fsl,imx6ul-iomuxc-gpr");
+ if (IS_ERR(gpr)) {
+ dev_err(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 = sai->res->start + FSL_SAI_RDR0;
+ sai->dma_params_tx.addr = sai->res->start + FSL_SAI_TDR0;
+ sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX;
+ sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX;
+
+ sai->pinctrl = devm_pinctrl_get(&pdev->dev);
+
+ platform_set_drvdata(pdev, sai);
+ pm_runtime_enable(dev);
+ if (!pm_runtime_enabled(dev)) {
+ ret = fsl_sai_runtime_resume(dev);
+ if (ret)
+ goto err_pm_disable;
+ }
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ goto err_pm_get_sync;
+
+ /* Get sai version */
+ ret = fsl_sai_check_version(dev);
+ if (ret < 0)
+ dev_warn(dev, "Error reading SAI version: %d\n", ret);
+
+ /* Select MCLK direction */
+ if (sai->mclk_direction_output &&
+ sai->soc_data->max_register >= FSL_SAI_MCTL) {
+ regmap_update_bits(sai->regmap, FSL_SAI_MCTL,
+ FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN);
+ }
+
+ ret = pm_runtime_put_sync(dev);
+ if (ret < 0 && ret != -ENOSYS)
+ goto err_pm_get_sync;
+
+ /*
+ * Register platform component before registering cpu dai for there
+ * is not defer probe for platform component in snd_soc_add_pcm_runtime().
+ */
+ if (sai->soc_data->use_imx_pcm) {
+ ret = imx_pcm_dma_init(pdev);
+ if (ret) {
+ if (!IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA))
+ dev_err(dev, "Error: You must enable the imx-pcm-dma support!\n");
+ goto err_pm_get_sync;
+ }
+ } else {
+ ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0);
+ if (ret)
+ goto err_pm_get_sync;
+ }
+
+ ret = devm_snd_soc_register_component(dev, &fsl_component,
+ &sai->cpu_dai_drv, 1);
+ if (ret)
+ goto err_pm_get_sync;
+
+ return ret;
+
+err_pm_get_sync:
+ if (!pm_runtime_status_suspended(dev))
+ fsl_sai_runtime_suspend(dev);
+err_pm_disable:
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static int fsl_sai_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ fsl_sai_runtime_suspend(&pdev->dev);
+
+ return 0;
+}
+
+static const struct fsl_sai_soc_data fsl_sai_vf610_data = {
+ .use_imx_pcm = false,
+ .use_edma = false,
+ .fifo_depth = 32,
+ .pins = 1,
+ .reg_offset = 0,
+ .mclk0_is_mclk1 = false,
+ .flags = 0,
+ .max_register = FSL_SAI_RMR,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx6sx_data = {
+ .use_imx_pcm = true,
+ .use_edma = false,
+ .fifo_depth = 32,
+ .pins = 1,
+ .reg_offset = 0,
+ .mclk0_is_mclk1 = true,
+ .flags = 0,
+ .max_register = FSL_SAI_RMR,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx7ulp_data = {
+ .use_imx_pcm = true,
+ .use_edma = false,
+ .fifo_depth = 16,
+ .pins = 2,
+ .reg_offset = 8,
+ .mclk0_is_mclk1 = false,
+ .flags = PMQOS_CPU_LATENCY,
+ .max_register = FSL_SAI_RMR,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx8mq_data = {
+ .use_imx_pcm = true,
+ .use_edma = false,
+ .fifo_depth = 128,
+ .pins = 8,
+ .reg_offset = 8,
+ .mclk0_is_mclk1 = false,
+ .flags = 0,
+ .max_register = FSL_SAI_RMR,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx8qm_data = {
+ .use_imx_pcm = true,
+ .use_edma = true,
+ .fifo_depth = 64,
+ .pins = 4,
+ .reg_offset = 0,
+ .mclk0_is_mclk1 = false,
+ .flags = 0,
+ .max_register = FSL_SAI_RMR,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx8mm_data = {
+ .use_imx_pcm = true,
+ .use_edma = false,
+ .fifo_depth = 128,
+ .reg_offset = 8,
+ .mclk0_is_mclk1 = false,
+ .pins = 8,
+ .flags = 0,
+ .max_register = FSL_SAI_MCTL,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx8mn_data = {
+ .use_imx_pcm = true,
+ .use_edma = false,
+ .fifo_depth = 128,
+ .reg_offset = 8,
+ .mclk0_is_mclk1 = false,
+ .pins = 8,
+ .flags = 0,
+ .max_register = FSL_SAI_MDIV,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx8mp_data = {
+ .use_imx_pcm = true,
+ .use_edma = false,
+ .fifo_depth = 128,
+ .reg_offset = 8,
+ .mclk0_is_mclk1 = false,
+ .pins = 8,
+ .flags = 0,
+ .max_register = FSL_SAI_MDIV,
+ .mclk_with_tere = true,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx8ulp_data = {
+ .use_imx_pcm = true,
+ .use_edma = true,
+ .fifo_depth = 16,
+ .reg_offset = 8,
+ .mclk0_is_mclk1 = false,
+ .pins = 4,
+ .flags = PMQOS_CPU_LATENCY,
+ .max_register = FSL_SAI_RTCAP,
+};
+
+static const struct of_device_id fsl_sai_ids[] = {
+ { .compatible = "fsl,vf610-sai", .data = &fsl_sai_vf610_data },
+ { .compatible = "fsl,imx6sx-sai", .data = &fsl_sai_imx6sx_data },
+ { .compatible = "fsl,imx6ul-sai", .data = &fsl_sai_imx6sx_data },
+ { .compatible = "fsl,imx7ulp-sai", .data = &fsl_sai_imx7ulp_data },
+ { .compatible = "fsl,imx8mq-sai", .data = &fsl_sai_imx8mq_data },
+ { .compatible = "fsl,imx8qm-sai", .data = &fsl_sai_imx8qm_data },
+ { .compatible = "fsl,imx8mm-sai", .data = &fsl_sai_imx8mm_data },
+ { .compatible = "fsl,imx8mp-sai", .data = &fsl_sai_imx8mp_data },
+ { .compatible = "fsl,imx8ulp-sai", .data = &fsl_sai_imx8ulp_data },
+ { .compatible = "fsl,imx8mn-sai", .data = &fsl_sai_imx8mn_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_sai_ids);
+
+static int fsl_sai_runtime_suspend(struct device *dev)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE))
+ clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]);
+
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_PLAYBACK))
+ clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[1]]);
+
+ clk_disable_unprepare(sai->bus_clk);
+
+ if (sai->soc_data->flags & PMQOS_CPU_LATENCY)
+ cpu_latency_qos_remove_request(&sai->pm_qos_req);
+
+ regcache_cache_only(sai->regmap, true);
+
+ return 0;
+}
+
+static int fsl_sai_runtime_resume(struct device *dev)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+ unsigned int ofs = sai->soc_data->reg_offset;
+ int ret;
+
+ ret = clk_prepare_enable(sai->bus_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable bus clock: %d\n", ret);
+ return ret;
+ }
+
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_PLAYBACK)) {
+ ret = clk_prepare_enable(sai->mclk_clk[sai->mclk_id[1]]);
+ if (ret)
+ goto disable_bus_clk;
+ }
+
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) {
+ ret = clk_prepare_enable(sai->mclk_clk[sai->mclk_id[0]]);
+ if (ret)
+ goto disable_tx_clk;
+ }
+
+ if (sai->soc_data->flags & PMQOS_CPU_LATENCY)
+ cpu_latency_qos_add_request(&sai->pm_qos_req, 0);
+
+ regcache_cache_only(sai->regmap, false);
+ regcache_mark_dirty(sai->regmap);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), FSL_SAI_CSR_SR);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), FSL_SAI_CSR_SR);
+ usleep_range(1000, 2000);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), 0);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), 0);
+
+ ret = regcache_sync(sai->regmap);
+ if (ret)
+ goto disable_rx_clk;
+
+ if (sai->soc_data->mclk_with_tere && sai->mclk_direction_output)
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs),
+ FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+
+ return 0;
+
+disable_rx_clk:
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE))
+ clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]);
+disable_tx_clk:
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_PLAYBACK))
+ clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[1]]);
+disable_bus_clk:
+ clk_disable_unprepare(sai->bus_clk);
+
+ return ret;
+}
+
+static const struct dev_pm_ops fsl_sai_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_sai_runtime_suspend,
+ fsl_sai_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_sai_driver = {
+ .probe = fsl_sai_probe,
+ .remove = fsl_sai_remove,
+ .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 000000000..b4d616a44
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai.h
@@ -0,0 +1,315 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2012-2013 Freescale Semiconductor, Inc.
+ */
+
+#ifndef __FSL_SAI_H
+#define __FSL_SAI_H
+
+#include <linux/dma/imx-dma.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 |\
+ SNDRV_PCM_FMTBIT_DSD_U8 |\
+ SNDRV_PCM_FMTBIT_DSD_U16_LE |\
+ SNDRV_PCM_FMTBIT_DSD_U32_LE)
+
+/* SAI Register Map Register */
+#define FSL_SAI_VERID 0x00 /* SAI Version ID Register */
+#define FSL_SAI_PARAM 0x04 /* SAI Parameter Register */
+#define FSL_SAI_TCSR(ofs) (0x00 + ofs) /* SAI Transmit Control */
+#define FSL_SAI_TCR1(ofs) (0x04 + ofs) /* SAI Transmit Configuration 1 */
+#define FSL_SAI_TCR2(ofs) (0x08 + ofs) /* SAI Transmit Configuration 2 */
+#define FSL_SAI_TCR3(ofs) (0x0c + ofs) /* SAI Transmit Configuration 3 */
+#define FSL_SAI_TCR4(ofs) (0x10 + ofs) /* SAI Transmit Configuration 4 */
+#define FSL_SAI_TCR5(ofs) (0x14 + ofs) /* SAI Transmit Configuration 5 */
+#define FSL_SAI_TDR0 0x20 /* SAI Transmit Data 0 */
+#define FSL_SAI_TDR1 0x24 /* SAI Transmit Data 1 */
+#define FSL_SAI_TDR2 0x28 /* SAI Transmit Data 2 */
+#define FSL_SAI_TDR3 0x2C /* SAI Transmit Data 3 */
+#define FSL_SAI_TDR4 0x30 /* SAI Transmit Data 4 */
+#define FSL_SAI_TDR5 0x34 /* SAI Transmit Data 5 */
+#define FSL_SAI_TDR6 0x38 /* SAI Transmit Data 6 */
+#define FSL_SAI_TDR7 0x3C /* SAI Transmit Data 7 */
+#define FSL_SAI_TFR0 0x40 /* SAI Transmit FIFO 0 */
+#define FSL_SAI_TFR1 0x44 /* SAI Transmit FIFO 1 */
+#define FSL_SAI_TFR2 0x48 /* SAI Transmit FIFO 2 */
+#define FSL_SAI_TFR3 0x4C /* SAI Transmit FIFO 3 */
+#define FSL_SAI_TFR4 0x50 /* SAI Transmit FIFO 4 */
+#define FSL_SAI_TFR5 0x54 /* SAI Transmit FIFO 5 */
+#define FSL_SAI_TFR6 0x58 /* SAI Transmit FIFO 6 */
+#define FSL_SAI_TFR7 0x5C /* SAI Transmit FIFO 7 */
+#define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */
+#define FSL_SAI_TTCTL 0x70 /* SAI Transmit Timestamp Control Register */
+#define FSL_SAI_TTCTN 0x74 /* SAI Transmit Timestamp Counter Register */
+#define FSL_SAI_TBCTN 0x78 /* SAI Transmit Bit Counter Register */
+#define FSL_SAI_TTCAP 0x7C /* SAI Transmit Timestamp Capture */
+#define FSL_SAI_RCSR(ofs) (0x80 + ofs) /* SAI Receive Control */
+#define FSL_SAI_RCR1(ofs) (0x84 + ofs)/* SAI Receive Configuration 1 */
+#define FSL_SAI_RCR2(ofs) (0x88 + ofs) /* SAI Receive Configuration 2 */
+#define FSL_SAI_RCR3(ofs) (0x8c + ofs) /* SAI Receive Configuration 3 */
+#define FSL_SAI_RCR4(ofs) (0x90 + ofs) /* SAI Receive Configuration 4 */
+#define FSL_SAI_RCR5(ofs) (0x94 + ofs) /* SAI Receive Configuration 5 */
+#define FSL_SAI_RDR0 0xa0 /* SAI Receive Data 0 */
+#define FSL_SAI_RDR1 0xa4 /* SAI Receive Data 1 */
+#define FSL_SAI_RDR2 0xa8 /* SAI Receive Data 2 */
+#define FSL_SAI_RDR3 0xac /* SAI Receive Data 3 */
+#define FSL_SAI_RDR4 0xb0 /* SAI Receive Data 4 */
+#define FSL_SAI_RDR5 0xb4 /* SAI Receive Data 5 */
+#define FSL_SAI_RDR6 0xb8 /* SAI Receive Data 6 */
+#define FSL_SAI_RDR7 0xbc /* SAI Receive Data 7 */
+#define FSL_SAI_RFR0 0xc0 /* SAI Receive FIFO 0 */
+#define FSL_SAI_RFR1 0xc4 /* SAI Receive FIFO 1 */
+#define FSL_SAI_RFR2 0xc8 /* SAI Receive FIFO 2 */
+#define FSL_SAI_RFR3 0xcc /* SAI Receive FIFO 3 */
+#define FSL_SAI_RFR4 0xd0 /* SAI Receive FIFO 4 */
+#define FSL_SAI_RFR5 0xd4 /* SAI Receive FIFO 5 */
+#define FSL_SAI_RFR6 0xd8 /* SAI Receive FIFO 6 */
+#define FSL_SAI_RFR7 0xdc /* SAI Receive FIFO 7 */
+#define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */
+#define FSL_SAI_RTCTL 0xf0 /* SAI Receive Timestamp Control Register */
+#define FSL_SAI_RTCTN 0xf4 /* SAI Receive Timestamp Counter Register */
+#define FSL_SAI_RBCTN 0xf8 /* SAI Receive Bit Counter Register */
+#define FSL_SAI_RTCAP 0xfc /* SAI Receive Timestamp Capture */
+
+#define FSL_SAI_MCTL 0x100 /* SAI MCLK Control Register */
+#define FSL_SAI_MDIV 0x104 /* SAI MCLK Divide Register */
+
+#define FSL_SAI_xCSR(tx, ofs) (tx ? FSL_SAI_TCSR(ofs) : FSL_SAI_RCSR(ofs))
+#define FSL_SAI_xCR1(tx, ofs) (tx ? FSL_SAI_TCR1(ofs) : FSL_SAI_RCR1(ofs))
+#define FSL_SAI_xCR2(tx, ofs) (tx ? FSL_SAI_TCR2(ofs) : FSL_SAI_RCR2(ofs))
+#define FSL_SAI_xCR3(tx, ofs) (tx ? FSL_SAI_TCR3(ofs) : FSL_SAI_RCR3(ofs))
+#define FSL_SAI_xCR4(tx, ofs) (tx ? FSL_SAI_TCR4(ofs) : FSL_SAI_RCR4(ofs))
+#define FSL_SAI_xCR5(tx, ofs) (tx ? FSL_SAI_TCR5(ofs) : FSL_SAI_RCR5(ofs))
+#define FSL_SAI_xDR0(tx) (tx ? FSL_SAI_TDR0 : FSL_SAI_RDR0)
+#define FSL_SAI_xFR0(tx) (tx ? FSL_SAI_TFR0 : FSL_SAI_RFR0)
+#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_SE BIT(30)
+#define FSL_SAI_CSR_BCE BIT(28)
+#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(x) ((x) - 1)
+
+/* SAI Transmit and Receive Configuration 2 Register */
+#define FSL_SAI_CR2_SYNC BIT(30)
+#define FSL_SAI_CR2_BCI BIT(28)
+#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_BYP BIT(23) /* BCLK bypass */
+#define FSL_SAI_CR2_DIV_MASK 0xff
+
+/* SAI Transmit and Receive Configuration 3 Register */
+#define FSL_SAI_CR3_TRCE(x) ((x) << 16)
+#define FSL_SAI_CR3_TRCE_MASK GENMASK(23, 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_FCONT BIT(28)
+#define FSL_SAI_CR4_FCOMB_SHIFT BIT(26)
+#define FSL_SAI_CR4_FCOMB_SOFT BIT(27)
+#define FSL_SAI_CR4_FCOMB_MASK (0x3 << 26)
+#define FSL_SAI_CR4_FPACK_8 (0x2 << 24)
+#define FSL_SAI_CR4_FPACK_16 (0x3 << 24)
+#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_CHMOD BIT(5)
+#define FSL_SAI_CR4_CHMOD_MASK BIT(5)
+#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 MCLK Control Register */
+#define FSL_SAI_MCTL_MCLK_EN BIT(30) /* MCLK Enable */
+#define FSL_SAI_MCTL_MSEL_MASK (0x3 << 24)
+#define FSL_SAI_MCTL_MSEL(ID) ((ID) << 24)
+#define FSL_SAI_MCTL_MSEL_BUS 0
+#define FSL_SAI_MCTL_MSEL_MCLK1 BIT(24)
+#define FSL_SAI_MCTL_MSEL_MCLK2 BIT(25)
+#define FSL_SAI_MCTL_MSEL_MCLK3 (BIT(24) | BIT(25))
+#define FSL_SAI_MCTL_DIV_EN BIT(23)
+#define FSL_SAI_MCTL_DIV_MASK 0xFF
+
+/* SAI VERID Register */
+#define FSL_SAI_VERID_MAJOR_SHIFT 24
+#define FSL_SAI_VERID_MAJOR_MASK GENMASK(31, 24)
+#define FSL_SAI_VERID_MINOR_SHIFT 16
+#define FSL_SAI_VERID_MINOR_MASK GENMASK(23, 16)
+#define FSL_SAI_VERID_FEATURE_SHIFT 0
+#define FSL_SAI_VERID_FEATURE_MASK GENMASK(15, 0)
+#define FSL_SAI_VERID_EFIFO_EN BIT(0)
+#define FSL_SAI_VERID_TSTMP_EN BIT(1)
+
+/* SAI PARAM Register */
+#define FSL_SAI_PARAM_SPF_SHIFT 16
+#define FSL_SAI_PARAM_SPF_MASK GENMASK(19, 16)
+#define FSL_SAI_PARAM_WPF_SHIFT 8
+#define FSL_SAI_PARAM_WPF_MASK GENMASK(11, 8)
+#define FSL_SAI_PARAM_DLN_MASK GENMASK(3, 0)
+
+/* SAI MCLK Divide Register */
+#define FSL_SAI_MDIV_MASK 0xFFFFF
+
+/* SAI timestamp and bitcounter */
+#define FSL_SAI_xTCTL_TSEN BIT(0)
+#define FSL_SAI_xTCTL_TSINC BIT(1)
+#define FSL_SAI_xTCTL_RTSC BIT(8)
+#define FSL_SAI_xTCTL_RBC BIT(9)
+
+/* 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)
+
+/* 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
+
+#define PMQOS_CPU_LATENCY BIT(0)
+
+/* Max number of dataline */
+#define FSL_SAI_DL_NUM (8)
+/* default dataline type is zero */
+#define FSL_SAI_DL_DEFAULT (0)
+#define FSL_SAI_DL_I2S BIT(0)
+#define FSL_SAI_DL_PDM BIT(1)
+
+struct fsl_sai_soc_data {
+ bool use_imx_pcm;
+ bool use_edma;
+ bool mclk0_is_mclk1;
+ bool mclk_with_tere;
+ unsigned int fifo_depth;
+ unsigned int pins;
+ unsigned int reg_offset;
+ unsigned int flags;
+ unsigned int max_register;
+};
+
+/**
+ * struct fsl_sai_verid - version id data
+ * @version: version number
+ * @feature: feature specification number
+ * 0000000000000000b - Standard feature set
+ * 0000000000000000b - Standard feature set
+ */
+struct fsl_sai_verid {
+ u32 version;
+ u32 feature;
+};
+
+/**
+ * struct fsl_sai_param - parameter data
+ * @slot_num: The maximum number of slots per frame
+ * @fifo_depth: The number of words in each FIFO (depth)
+ * @dataline: The number of datalines implemented
+ */
+struct fsl_sai_param {
+ u32 slot_num;
+ u32 fifo_depth;
+ u32 dataline;
+};
+
+struct fsl_sai_dl_cfg {
+ unsigned int type;
+ unsigned int pins[2];
+ unsigned int mask[2];
+ unsigned int start_off[2];
+ unsigned int next_off[2];
+};
+
+struct fsl_sai {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct clk *bus_clk;
+ struct clk *mclk_clk[FSL_SAI_MCLK_MAX];
+ struct clk *pll8k_clk;
+ struct clk *pll11k_clk;
+ struct resource *res;
+
+ bool is_consumer_mode;
+ bool is_lsb_first;
+ bool is_dsp_mode;
+ bool is_pdm_mode;
+ bool is_multi_fifo_dma;
+ bool synchronous[2];
+ struct fsl_sai_dl_cfg *dl_cfg;
+ unsigned int dl_cfg_cnt;
+ bool mclk_direction_output;
+
+ unsigned int mclk_id[2];
+ unsigned int mclk_streams;
+ unsigned int slots;
+ unsigned int slot_width;
+ unsigned int bclk_ratio;
+
+ const struct fsl_sai_soc_data *soc_data;
+ struct snd_soc_dai_driver cpu_dai_drv;
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct snd_dmaengine_dai_dma_data dma_params_tx;
+ struct fsl_sai_verid verid;
+ struct fsl_sai_param param;
+ struct pm_qos_request pm_qos_req;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pins_state;
+ struct sdma_peripheral_config audio_config[2];
+};
+
+#define TX 1
+#define RX 0
+
+#endif /* __FSL_SAI_H */
diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c
new file mode 100644
index 000000000..fb6806b2d
--- /dev/null
+++ b/sound/soc/fsl/fsl_spdif.c
@@ -0,0 +1,1778 @@
+// 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 <linux/pm_runtime.h>
+
+#include <sound/asoundef.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+#include "fsl_spdif.h"
+#include "fsl_utils.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
+
+#define RX_SAMPLE_RATE_KCONTROL "RX Sample Rate"
+
+/**
+ * struct fsl_spdif_soc_data: soc specific data
+ *
+ * @imx: for imx platform
+ * @shared_root_clock: flag of sharing a clock source with others;
+ * so the driver shouldn't set root clock rate
+ * @raw_capture_mode: if raw capture mode support
+ * @cchannel_192b: if there are registers for 192bits C channel data
+ * @interrupts: interrupt number
+ * @tx_burst: tx maxburst size
+ * @rx_burst: rx maxburst size
+ * @tx_formats: tx supported data format
+ */
+struct fsl_spdif_soc_data {
+ bool imx;
+ bool shared_root_clock;
+ bool raw_capture_mode;
+ bool cchannel_192b;
+ u32 interrupts;
+ u32 tx_burst;
+ u32 rx_burst;
+ u64 tx_formats;
+};
+
+/*
+ * 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;
+};
+
+/**
+ * struct fsl_spdif_priv - Freescale SPDIF private data
+ * @soc: SPDIF soc data
+ * @fsl_spdif_control: SPDIF control data
+ * @cpu_dai_drv: cpu dai driver
+ * @snd_card: sound card pointer
+ * @rxrate_kcontrol: kcontrol for RX Sample Rate
+ * @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
+ * @regcache_srpc: regcache for SRPC
+ * @bypass: status of bypass input to output
+ * @pll8k_clk: PLL clock for the rate of multiply of 8kHz
+ * @pll11k_clk: PLL clock for the rate of multiply of 11kHz
+ */
+struct fsl_spdif_priv {
+ const struct fsl_spdif_soc_data *soc;
+ struct spdif_mixer_control fsl_spdif_control;
+ struct snd_soc_dai_driver cpu_dai_drv;
+ struct snd_card *snd_card;
+ struct snd_kcontrol *rxrate_kcontrol;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ bool dpll_locked;
+ u32 txrate[SPDIF_TXRATE_MAX];
+ u8 txclk_df[SPDIF_TXRATE_MAX];
+ u16 sysclk_df[SPDIF_TXRATE_MAX];
+ u8 txclk_src[SPDIF_TXRATE_MAX];
+ u8 rxclk_src;
+ struct clk *txclk[STC_TXCLK_SRC_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;
+ bool bypass;
+ struct clk *pll8k_clk;
+ struct clk *pll11k_clk;
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_vf610 = {
+ .imx = false,
+ .shared_root_clock = false,
+ .raw_capture_mode = false,
+ .interrupts = 1,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx35 = {
+ .imx = true,
+ .shared_root_clock = false,
+ .raw_capture_mode = false,
+ .interrupts = 1,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx6sx = {
+ .imx = true,
+ .shared_root_clock = true,
+ .raw_capture_mode = false,
+ .interrupts = 1,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx8qm = {
+ .imx = true,
+ .shared_root_clock = true,
+ .raw_capture_mode = false,
+ .interrupts = 2,
+ .tx_burst = 2, /* Applied for EDMA */
+ .rx_burst = 2, /* Applied for EDMA */
+ .tx_formats = SNDRV_PCM_FMTBIT_S24_LE, /* Applied for EDMA */
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx8mm = {
+ .imx = true,
+ .shared_root_clock = false,
+ .raw_capture_mode = true,
+ .interrupts = 1,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx8ulp = {
+ .imx = true,
+ .shared_root_clock = true,
+ .raw_capture_mode = false,
+ .interrupts = 1,
+ .tx_burst = 2, /* Applied for EDMA */
+ .rx_burst = 2, /* Applied for EDMA */
+ .tx_formats = SNDRV_PCM_FMTBIT_S24_LE, /* Applied for EDMA */
+ .cchannel_192b = true,
+};
+
+/* Check if clk is a root clock that does not share clock source with others */
+static inline bool fsl_spdif_can_set_clk_rate(struct fsl_spdif_priv *spdif, int clk)
+{
+ return (clk == STC_TXCLK_SPDIF_ROOT) && !spdif->soc->shared_root_clock;
+}
+
+/* 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;
+
+ if (spdif_priv->snd_card && spdif_priv->rxrate_kcontrol) {
+ snd_ctl_notify(spdif_priv->snd_card,
+ SNDRV_CTL_EVENT_MASK_VALUE,
+ &spdif_priv->rxrate_kcontrol->id);
+ }
+}
+
+/* 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);
+
+ if (spdif_priv->soc->cchannel_192b) {
+ ch_status = (bitrev8(ctrl->ch_status[0]) << 24) |
+ (bitrev8(ctrl->ch_status[1]) << 16) |
+ (bitrev8(ctrl->ch_status[2]) << 8) |
+ bitrev8(ctrl->ch_status[3]);
+
+ regmap_update_bits(regmap, REG_SPDIF_SCR, 0x1000000, 0x1000000);
+
+ /*
+ * The first 32bit should be in REG_SPDIF_STCCA_31_0 register,
+ * but here we need to set REG_SPDIF_STCCA_191_160 on 8ULP
+ * then can get correct result with HDMI analyzer capture.
+ * There is a hardware bug here.
+ */
+ regmap_write(regmap, REG_SPDIF_STCCA_191_160, 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 fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, enum spdif_txrate index);
+
+static int spdif_set_sample_rate(struct snd_pcm_substream *substream,
+ int sample_rate)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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;
+ u16 sysclk_df;
+ u8 clk, txclk_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 88200:
+ rate = SPDIF_TXRATE_88200;
+ csfs = IEC958_AES3_CON_FS_88200;
+ break;
+ case 96000:
+ rate = SPDIF_TXRATE_96000;
+ csfs = IEC958_AES3_CON_FS_96000;
+ break;
+ case 176400:
+ rate = SPDIF_TXRATE_176400;
+ csfs = IEC958_AES3_CON_FS_176400;
+ 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;
+ }
+
+ ret = fsl_spdif_probe_txclk(spdif_priv, rate);
+ if (ret)
+ return ret;
+
+ 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];
+
+ if (!fsl_spdif_can_set_clk_rate(spdif_priv, clk))
+ goto clk_set_bypass;
+
+ /* The S/PDIF block needs a clock of 64 * fs * txclk_df */
+ ret = clk_set_rate(spdif_priv->txclk[clk],
+ 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[clk]));
+
+ /* 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 = asoc_substream_to_rtd(substream);
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ struct platform_device *pdev = spdif_priv->pdev;
+ struct regmap *regmap = spdif_priv->regmap;
+ u32 scr, mask;
+ int ret;
+
+ /* Reset module and interrupts only for first initialization */
+ if (!snd_soc_dai_active(cpu_dai)) {
+ ret = spdif_softreset(spdif_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to soft reset\n");
+ return ret;
+ }
+
+ /* 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;
+ } 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;
+ }
+ 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;
+}
+
+static void fsl_spdif_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ struct regmap *regmap = spdif_priv->regmap;
+ u32 scr, mask;
+
+ 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;
+ /* Disable TX clock */
+ regmap_update_bits(regmap, REG_SPDIF_STC, STC_TXCLK_ALL_EN_MASK, 0);
+ } 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;
+ }
+ regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr);
+
+ /* Power down SPDIF module only if tx&rx are both inactive */
+ if (!snd_soc_dai_active(cpu_dai)) {
+ spdif_intr_status_clear(spdif_priv);
+ regmap_update_bits(regmap, REG_SPDIF_SCR,
+ SCR_LOW_POWER, SCR_LOW_POWER);
+ }
+}
+
+static int spdif_reparent_rootclk(struct fsl_spdif_priv *spdif_priv, unsigned int sample_rate)
+{
+ struct platform_device *pdev = spdif_priv->pdev;
+ struct clk *clk;
+ int ret;
+
+ /* Reparent clock if required condition is true */
+ if (!fsl_spdif_can_set_clk_rate(spdif_priv, STC_TXCLK_SPDIF_ROOT))
+ return 0;
+
+ /* Get root clock */
+ clk = spdif_priv->txclk[STC_TXCLK_SPDIF_ROOT];
+
+ /* Disable clock first, for it was enabled by pm_runtime */
+ clk_disable_unprepare(clk);
+ fsl_asoc_reparent_pll_clocks(&pdev->dev, clk, spdif_priv->pll8k_clk,
+ spdif_priv->pll11k_clk, sample_rate);
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+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 = asoc_substream_to_rtd(substream);
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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_reparent_rootclk(spdif_priv, sample_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: reparent root clk failed: %d\n",
+ __func__, sample_rate);
+ return ret;
+ }
+
+ 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 = asoc_substream_to_rtd(substream);
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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);
+ regmap_write(regmap, REG_SPDIF_STL, 0x0);
+ regmap_write(regmap, REG_SPDIF_STR, 0x0);
+ 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;
+}
+
+/* Get valid good bit from interrupt status register */
+static int fsl_spdif_rx_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;
+}
+
+static int fsl_spdif_tx_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_SCR, &val);
+ val = (val & SCR_VAL_MASK) >> SCR_VAL_OFFSET;
+ val = 1 - val;
+ ucontrol->value.integer.value[0] = val;
+
+ return 0;
+}
+
+static int fsl_spdif_tx_vbit_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 = (1 - ucontrol->value.integer.value[0]) << SCR_VAL_OFFSET;
+
+ regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_VAL_MASK, val);
+
+ return 0;
+}
+
+static int fsl_spdif_rx_rcm_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_SCR, &val);
+ val = (val & SCR_RAW_CAPTURE_MODE) ? 1 : 0;
+ ucontrol->value.integer.value[0] = val;
+
+ return 0;
+}
+
+static int fsl_spdif_rx_rcm_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] ? SCR_RAW_CAPTURE_MODE : 0);
+
+ if (val)
+ cpu_dai->driver->capture.formats |= SNDRV_PCM_FMTBIT_S32_LE;
+ else
+ cpu_dai->driver->capture.formats &= ~SNDRV_PCM_FMTBIT_S32_LE;
+
+ regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_RAW_CAPTURE_MODE, val);
+
+ return 0;
+}
+
+static int fsl_spdif_bypass_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_spdif_priv *priv = snd_soc_dai_get_drvdata(dai);
+
+ ucontrol->value.integer.value[0] = priv->bypass ? 1 : 0;
+
+ return 0;
+}
+
+static int fsl_spdif_bypass_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_spdif_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct snd_soc_card *card = dai->component->card;
+ bool set = (ucontrol->value.integer.value[0] != 0);
+ struct regmap *regmap = priv->regmap;
+ struct snd_soc_pcm_runtime *rtd;
+ u32 scr, mask;
+ int stream;
+
+ rtd = snd_soc_get_pcm_runtime(card, card->dai_link);
+
+ if (priv->bypass == set)
+ return 0; /* nothing to do */
+
+ if (snd_soc_dai_active(dai)) {
+ dev_err(dai->dev, "Cannot change BYPASS mode while stream is running.\n");
+ return -EBUSY;
+ }
+
+ pm_runtime_get_sync(dai->dev);
+
+ if (set) {
+ /* Disable interrupts */
+ regmap_update_bits(regmap, REG_SPDIF_SIE, 0xffffff, 0);
+
+ /* Configure BYPASS mode */
+ scr = SCR_TXSEL_RX | SCR_RXFIFO_OFF;
+ mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK |
+ SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK | SCR_TXSEL_MASK;
+ /* Power up SPDIF module */
+ mask |= SCR_LOW_POWER;
+ } else {
+ /* Power down SPDIF module, disable TX */
+ scr = SCR_LOW_POWER | SCR_TXSEL_OFF;
+ mask = SCR_LOW_POWER | SCR_TXSEL_MASK;
+ }
+
+ regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr);
+
+ /* Disable playback & capture if BYPASS mode is enabled, enable otherwise */
+ for_each_pcm_streams(stream)
+ rtd->pcm->streams[stream].substream_count = (set ? 0 : 1);
+
+ priv->bypass = set;
+ pm_runtime_put_sync(dai->dev);
+
+ 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 = 192000;
+
+ 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:
+ * 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 RX V-Bit Errors",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_ctl_boolean_mono_info,
+ .get = fsl_spdif_rx_vbit_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 TX V-Bit",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_ctl_boolean_mono_info,
+ .get = fsl_spdif_tx_vbit_get,
+ .put = fsl_spdif_tx_vbit_put,
+ },
+ /* DPLL lock info get controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = RX_SAMPLE_RATE_KCONTROL,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = fsl_spdif_rxrate_info,
+ .get = fsl_spdif_rxrate_get,
+ },
+ /* RX bypass controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Bypass Mode",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_ctl_boolean_mono_info,
+ .get = fsl_spdif_bypass_get,
+ .put = fsl_spdif_bypass_put,
+ },
+ /* User bit sync mode set/get controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 USyncMode CDText",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_ctl_boolean_mono_info,
+ .get = fsl_spdif_usync_get,
+ .put = fsl_spdif_usync_put,
+ },
+};
+
+static struct snd_kcontrol_new fsl_spdif_ctrls_rcm[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Raw Capture Mode",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_ctl_boolean_mono_info,
+ .get = fsl_spdif_rx_rcm_get,
+ .put = fsl_spdif_rx_rcm_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));
+
+ if (spdif_private->soc->raw_capture_mode)
+ snd_soc_add_dai_controls(dai, fsl_spdif_ctrls_rcm,
+ ARRAY_SIZE(fsl_spdif_ctrls_rcm));
+
+ spdif_private->snd_card = dai->component->card->snd_card;
+ spdif_private->rxrate_kcontrol = snd_soc_card_get_kcontrol(dai->component->card,
+ RX_SAMPLE_RATE_KCONTROL);
+ if (!spdif_private->rxrate_kcontrol)
+ dev_err(&spdif_private->pdev->dev, "failed to get %s kcontrol\n",
+ RX_SAMPLE_RATE_KCONTROL);
+
+ /*Clear the val bit for Tx*/
+ regmap_update_bits(spdif_private->regmap, REG_SPDIF_SCR,
+ SCR_VAL_MASK, SCR_VAL_CLEAR);
+
+ 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",
+ .legacy_dai_naming = 1,
+};
+
+/* 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_STCSPH, 0x00000000},
+ {REG_SPDIF_STCSPL, 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_STCSPH:
+ case REG_SPDIF_STCSPL:
+ case REG_SPDIF_SRFM:
+ case REG_SPDIF_STC:
+ case REG_SPDIF_SRCCA_31_0:
+ case REG_SPDIF_SRCCA_63_32:
+ case REG_SPDIF_SRCCA_95_64:
+ case REG_SPDIF_SRCCA_127_96:
+ case REG_SPDIF_SRCCA_159_128:
+ case REG_SPDIF_SRCCA_191_160:
+ case REG_SPDIF_STCCA_31_0:
+ case REG_SPDIF_STCCA_63_32:
+ case REG_SPDIF_STCCA_95_64:
+ case REG_SPDIF_STCCA_127_96:
+ case REG_SPDIF_STCCA_159_128:
+ case REG_SPDIF_STCCA_191_160:
+ 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:
+ case REG_SPDIF_SRCCA_31_0:
+ case REG_SPDIF_SRCCA_63_32:
+ case REG_SPDIF_SRCCA_95_64:
+ case REG_SPDIF_SRCCA_127_96:
+ case REG_SPDIF_SRCCA_159_128:
+ case REG_SPDIF_SRCCA_191_160:
+ 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_STCSPH:
+ case REG_SPDIF_STCSPL:
+ case REG_SPDIF_STC:
+ case REG_SPDIF_STCCA_31_0:
+ case REG_SPDIF_STCCA_63_32:
+ case REG_SPDIF_STCCA_95_64:
+ case REG_SPDIF_STCCA_127_96:
+ case REG_SPDIF_STCCA_159_128:
+ case REG_SPDIF_STCCA_191_160:
+ 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_STCCA_191_160,
+ .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, 88200, 96000, 176400,
+ 192000, };
+ bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
+ u64 rate_ideal, rate_actual, sub;
+ u32 arate;
+ u16 sysclk_dfmin, sysclk_dfmax, sysclk_df;
+ u8 txclk_df;
+
+ /* 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, 88200, 96000, 176400,
+ 192000, };
+ struct platform_device *pdev = spdif_priv->pdev;
+ struct device *dev = &pdev->dev;
+ u64 savesub = 100000, ret;
+ struct clk *clk;
+ int i;
+
+ for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
+ clk = spdif_priv->txclk[i];
+ 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,
+ fsl_spdif_can_set_clk_rate(spdif_priv, i));
+ if (savesub == ret)
+ continue;
+
+ savesub = ret;
+ spdif_priv->txclk_src[index] = i;
+
+ /* To quick catch a divisor, we allow a 0.1% deviation */
+ if (savesub < 100)
+ break;
+ }
+
+ dev_dbg(dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
+ spdif_priv->txclk_src[index], rate[index]);
+ dev_dbg(dev, "use txclk df %d for %dHz sample rate\n",
+ spdif_priv->txclk_df[index], rate[index]);
+ if (clk_is_match(spdif_priv->txclk[spdif_priv->txclk_src[index]], spdif_priv->sysclk))
+ dev_dbg(dev, "use sysclk df %d for %dHz sample rate\n",
+ spdif_priv->sysclk_df[index], rate[index]);
+ dev_dbg(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 fsl_spdif_priv *spdif_priv;
+ struct spdif_mixer_control *ctrl;
+ struct resource *res;
+ void __iomem *regs;
+ int irq, ret, i;
+ char tmp[16];
+
+ spdif_priv = devm_kzalloc(&pdev->dev, sizeof(*spdif_priv), GFP_KERNEL);
+ if (!spdif_priv)
+ return -ENOMEM;
+
+ spdif_priv->pdev = pdev;
+
+ spdif_priv->soc = of_device_get_match_data(&pdev->dev);
+
+ /* 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);
+ spdif_priv->cpu_dai_drv.playback.formats =
+ spdif_priv->soc->tx_formats;
+
+ /* Get the addresses and IRQ */
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ spdif_priv->regmap = devm_regmap_init_mmio(&pdev->dev, 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);
+ }
+
+ for (i = 0; i < spdif_priv->soc->interrupts; i++) {
+ irq = platform_get_irq(pdev, i);
+ if (irq < 0)
+ 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;
+ }
+ }
+
+ for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
+ sprintf(tmp, "rxtx%d", i);
+ spdif_priv->txclk[i] = devm_clk_get(&pdev->dev, tmp);
+ if (IS_ERR(spdif_priv->txclk[i])) {
+ dev_err(&pdev->dev, "no rxtx%d clock in devicetree\n", i);
+ return PTR_ERR(spdif_priv->txclk[i]);
+ }
+ }
+
+ /* Get system clock for rx clock rate calculation */
+ spdif_priv->sysclk = spdif_priv->txclk[5];
+ 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 = spdif_priv->txclk[1];
+ 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;
+
+ fsl_asoc_get_pll_clocks(&pdev->dev, &spdif_priv->pll8k_clk,
+ &spdif_priv->pll11k_clk);
+
+ /* 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 = spdif_priv->soc->tx_burst;
+ spdif_priv->dma_params_rx.maxburst = spdif_priv->soc->rx_burst;
+ 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);
+ pm_runtime_enable(&pdev->dev);
+ regcache_cache_only(spdif_priv->regmap, true);
+
+ /*
+ * Register platform component before registering cpu dai for there
+ * is not defer probe for platform component in snd_soc_add_pcm_runtime().
+ */
+ ret = imx_pcm_dma_init(pdev);
+ if (ret) {
+ dev_err_probe(&pdev->dev, ret, "imx_pcm_dma_init failed\n");
+ goto err_pm_disable;
+ }
+
+ 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);
+ goto err_pm_disable;
+ }
+
+ return ret;
+
+err_pm_disable:
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static int fsl_spdif_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_spdif_runtime_suspend(struct device *dev)
+{
+ struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
+ int i;
+
+ /* Disable all the interrupts */
+ regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SIE, 0xffffff, 0);
+
+ regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC,
+ &spdif_priv->regcache_srpc);
+ regcache_cache_only(spdif_priv->regmap, true);
+
+ for (i = 0; i < STC_TXCLK_SRC_MAX; i++)
+ clk_disable_unprepare(spdif_priv->txclk[i]);
+
+ if (!IS_ERR(spdif_priv->spbaclk))
+ clk_disable_unprepare(spdif_priv->spbaclk);
+ clk_disable_unprepare(spdif_priv->coreclk);
+
+ return 0;
+}
+
+static int fsl_spdif_runtime_resume(struct device *dev)
+{
+ struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
+ int ret;
+ int i;
+
+ ret = clk_prepare_enable(spdif_priv->coreclk);
+ if (ret) {
+ dev_err(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(dev, "failed to enable spba clock\n");
+ goto disable_core_clk;
+ }
+ }
+
+ for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
+ ret = clk_prepare_enable(spdif_priv->txclk[i]);
+ if (ret)
+ goto disable_tx_clk;
+ }
+
+ regcache_cache_only(spdif_priv->regmap, false);
+ regcache_mark_dirty(spdif_priv->regmap);
+
+ regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SRPC,
+ SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK,
+ spdif_priv->regcache_srpc);
+
+ ret = regcache_sync(spdif_priv->regmap);
+ if (ret)
+ goto disable_tx_clk;
+
+ return 0;
+
+disable_tx_clk:
+ for (i--; i >= 0; i--)
+ clk_disable_unprepare(spdif_priv->txclk[i]);
+ if (!IS_ERR(spdif_priv->spbaclk))
+ clk_disable_unprepare(spdif_priv->spbaclk);
+disable_core_clk:
+ clk_disable_unprepare(spdif_priv->coreclk);
+
+ return ret;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops fsl_spdif_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(fsl_spdif_runtime_suspend, fsl_spdif_runtime_resume,
+ NULL)
+};
+
+static const struct of_device_id fsl_spdif_dt_ids[] = {
+ { .compatible = "fsl,imx35-spdif", .data = &fsl_spdif_imx35, },
+ { .compatible = "fsl,vf610-spdif", .data = &fsl_spdif_vf610, },
+ { .compatible = "fsl,imx6sx-spdif", .data = &fsl_spdif_imx6sx, },
+ { .compatible = "fsl,imx8qm-spdif", .data = &fsl_spdif_imx8qm, },
+ { .compatible = "fsl,imx8mm-spdif", .data = &fsl_spdif_imx8mm, },
+ { .compatible = "fsl,imx8ulp-spdif", .data = &fsl_spdif_imx8ulp, },
+ {}
+};
+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,
+ .remove = fsl_spdif_remove,
+};
+
+module_platform_driver(fsl_spdif_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale S/PDIF CPU DAI Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:fsl-spdif-dai");
diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h
new file mode 100644
index 000000000..75b42a692
--- /dev/null
+++ b/sound/soc/fsl/fsl_spdif.h
@@ -0,0 +1,218 @@
+/* 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_STCSPH 0x3C /* SPDIFTxCChannel_Prof_h Register */
+#define REG_SPDIF_STCSPL 0x40 /* SPDIFTxCChannel_Prof_l Register */
+#define REG_SPDIF_SRFM 0x44 /* FreqMeas Register */
+#define REG_SPDIF_STC 0x50 /* SPDIFTxClk Register */
+
+#define REG_SPDIF_SRCCA_31_0 0x60 /* SPDIF receive C channel register, bits 31-0 */
+#define REG_SPDIF_SRCCA_63_32 0x64 /* SPDIF receive C channel register, bits 63-32 */
+#define REG_SPDIF_SRCCA_95_64 0x68 /* SPDIF receive C channel register, bits 95-64 */
+#define REG_SPDIF_SRCCA_127_96 0x6C /* SPDIF receive C channel register, bits 127-96 */
+#define REG_SPDIF_SRCCA_159_128 0x70 /* SPDIF receive C channel register, bits 159-128 */
+#define REG_SPDIF_SRCCA_191_160 0x74 /* SPDIF receive C channel register, bits 191-160 */
+#define REG_SPDIF_STCCA_31_0 0x78 /* SPDIF transmit C channel register, bits 31-0 */
+#define REG_SPDIF_STCCA_63_32 0x7C /* SPDIF transmit C channel register, bits 63-32 */
+#define REG_SPDIF_STCCA_95_64 0x80 /* SPDIF transmit C channel register, bits 95-64 */
+#define REG_SPDIF_STCCA_127_96 0x84 /* SPDIF transmit C channel register, bits 127-96 */
+#define REG_SPDIF_STCCA_159_128 0x88 /* SPDIF transmit C channel register, bits 159-128 */
+#define REG_SPDIF_STCCA_191_160 0x8C /* SPDIF transmit C channel register, bits 191-160 */
+
+/* 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_RAW_CAPTURE_MODE BIT(14)
+#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 (0x7f << 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_88200,
+ SPDIF_TXRATE_96000,
+ SPDIF_TXRATE_176400,
+ 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_88200 | \
+ SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_176400 | \
+ 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_88200 | \
+ SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_176400 | \
+ SNDRV_PCM_RATE_192000)
+
+#define FSL_SPDIF_FORMATS_PLAYBACK (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+#define FSL_SPDIF_FORMATS_CAPTURE (SNDRV_PCM_FMTBIT_S24_LE)
+
+#endif /* _FSL_SPDIF_DAI_H */
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
new file mode 100644
index 000000000..6af00b62a
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -0,0 +1,1748 @@
+// 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 <linux/dma/imx-dma.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_BC_FP | \
+ 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;
+};
+
+/**
+ * struct 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
+ * @use_dyna_fifo: DMA with support for multi FIFO script
+ * @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
+ * @audio_config: configure for dma multi fifo script
+ */
+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 use_dyna_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;
+ struct sdma_peripheral_config audio_config[2];
+};
+
+/*
+ * 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_clock_provider(struct fsl_ssi *ssi)
+{
+ return (ssi->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) ==
+ SND_SOC_DAIFMT_BP_FP;
+}
+
+static bool fsl_ssi_is_i2s_bc_fp(struct fsl_ssi *ssi)
+{
+ return (ssi->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) ==
+ SND_SOC_DAIFMT_BC_FP;
+}
+
+/**
+ * fsl_ssi_isr - Interrupt handler to gather states
+ * @irq: irq number
+ * @dev_id: context
+ */
+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;
+}
+
+/**
+ * fsl_ssi_config_enable - Set SCR, SIER, STCR and SRCR registers with
+ * cached values in regvals
+ * @ssi: SSI context
+ * @tx: direction
+ *
+ * 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))
+
+/**
+ * fsl_ssi_config_disable - Unset SCR, SIER, STCR and SRCR registers
+ * with cached values in regvals
+ * @ssi: SSI context
+ * @tx: direction
+ *
+ * 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);
+ }
+}
+
+/**
+ * fsl_ssi_setup_regvals - Cache critical bits of SIER, SRCR, STCR and
+ * SCR to later set them safely
+ * @ssi: SSI context
+ */
+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 = asoc_substream_to_rtd(substream);
+ struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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 || ssi->use_dyna_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 = asoc_substream_to_rtd(substream);
+ struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+
+ clk_disable_unprepare(ssi->clk);
+}
+
+/**
+ * fsl_ssi_set_bclk - Configure Digital Audio Interface bit clock
+ * @substream: ASoC substream
+ * @dai: pointer to DAI
+ * @hw_params: pointers to hw_params
+ *
+ * Notes: 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 channels = params_channels(hw_params);
+ unsigned int slot_width = params_width(hw_params);
+ unsigned int slots = 2;
+ 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;
+ if (ssi->slot_width)
+ slot_width = ssi->slot_width;
+
+ /* ...but force 32 bits for stereo audio using I2S Master Mode */
+ if (channels == 2 &&
+ (ssi->i2s_net & SSI_SCR_I2S_MODE_MASK) == SSI_SCR_I2S_MODE_MASTER)
+ slot_width = 32;
+
+ /* 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)) {
+ 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);
+ 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;
+}
+
+/**
+ * fsl_ssi_hw_params - Configure SSI based on PCM hardware parameters
+ * @substream: ASoC substream
+ * @hw_params: pointers to hw_params
+ * @dai: pointer to DAI
+ *
+ * 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 fsl_ssi_regvals *vals = ssi->regvals;
+ 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;
+
+ if (fsl_ssi_is_i2s_clock_provider(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);
+ }
+ }
+
+ /*
+ * 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_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_bc_fp(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);
+
+ if (ssi->use_dyna_fifo) {
+ if (channels == 1) {
+ ssi->audio_config[0].n_fifos_dst = 1;
+ ssi->audio_config[1].n_fifos_src = 1;
+ vals[RX].srcr &= ~SSI_SRCR_RFEN1;
+ vals[TX].stcr &= ~SSI_STCR_TFEN1;
+ vals[RX].scr &= ~SSI_SCR_TCH_EN;
+ vals[TX].scr &= ~SSI_SCR_TCH_EN;
+ } else {
+ ssi->audio_config[0].n_fifos_dst = 2;
+ ssi->audio_config[1].n_fifos_src = 2;
+ vals[RX].srcr |= SSI_SRCR_RFEN1;
+ vals[TX].stcr |= SSI_STCR_TFEN1;
+ vals[RX].scr |= SSI_SCR_TCH_EN;
+ vals[TX].scr |= SSI_SCR_TCH_EN;
+ }
+ ssi->dma_params_tx.peripheral_config = &ssi->audio_config[0];
+ ssi->dma_params_tx.peripheral_size = sizeof(ssi->audio_config[0]);
+ ssi->dma_params_rx.peripheral_config = &ssi->audio_config[1];
+ ssi->dma_params_rx.peripheral_size = sizeof(ssi->audio_config[1]);
+ }
+
+ return 0;
+}
+
+static int fsl_ssi_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+
+ if (fsl_ssi_is_i2s_clock_provider(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;
+ unsigned int slots;
+
+ 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_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ if (IS_ERR(ssi->baudclk)) {
+ dev_err(ssi->dev,
+ "missing baudclk for master mode\n");
+ return -EINVAL;
+ }
+ fallthrough;
+ case SND_SOC_DAIFMT_BC_FP:
+ ssi->i2s_net |= SSI_SCR_I2S_MODE_MASTER;
+ break;
+ case SND_SOC_DAIFMT_BC_FC:
+ ssi->i2s_net |= SSI_SCR_I2S_MODE_SLAVE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ slots = ssi->slots ? : 2;
+ regmap_update_bits(ssi->regs, REG_SSI_STCCR,
+ SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(slots));
+ regmap_update_bits(ssi->regs, REG_SSI_SRCCR,
+ SSI_SxCCR_DC_MASK, SSI_SxCCR_DC(slots));
+
+ /* 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 provider masks */
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ /* Output bit and frame sync clocks */
+ strcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR;
+ scr |= SSI_SCR_SYS_CLK_EN;
+ break;
+ case SND_SOC_DAIFMT_BC_FC:
+ /* Input bit or frame sync clocks */
+ break;
+ case SND_SOC_DAIFMT_BC_FP:
+ /* 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;
+}
+
+/**
+ * fsl_ssi_set_dai_fmt - Configure Digital Audio Interface (DAI) Format
+ * @dai: pointer to DAI
+ * @fmt: format mask
+ */
+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);
+}
+
+/**
+ * fsl_ssi_set_dai_tdm_slot - Set TDM slot number and slot width
+ * @dai: pointer to DAI
+ * @tx_mask: mask for TX
+ * @rx_mask: mask for RX
+ * @slots: number of slots
+ * @slot_width: number of bits per slot
+ */
+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;
+}
+
+/**
+ * fsl_ssi_trigger - Start or stop SSI and corresponding DMA transaction.
+ * @substream: ASoC substream
+ * @cmd: trigger command
+ * @dai: pointer to DAI
+ *
+ * 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 = asoc_substream_to_rtd(substream);
+ struct fsl_ssi *ssi = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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",
+ .legacy_dai_naming = 1,
+};
+
+static struct snd_soc_dai_driver fsl_ssi_ac97_dai = {
+ .symmetric_channels = 1,
+ .probe = fsl_ssi_dai_probe,
+ .playback = {
+ .stream_name = "CPU 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 = "CPU 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,
+};
+
+/**
+ * fsl_ssi_hw_init - Initialize SSI registers
+ * @ssi: SSI context
+ */
+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;
+}
+
+/**
+ * fsl_ssi_hw_clean - Clear SSI registers
+ * @ssi: SSI context
+ */
+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 consumer 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->use_dyna_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);
+ 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 char *p, *sprop;
+ const __be32 *iprop;
+ u32 dmas[4];
+ int ret;
+
+ 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_property_read_bool(np, "fsl,ssi-asynchronous")) {
+ /*
+ * 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;
+
+ if (ssi->use_dma && !ret && dmas[2] == IMX_DMATYPE_MULTI_SAI)
+ ssi->use_dyna_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)) {
+ struct device_node *root = of_find_node_by_path("/");
+
+ sprop = of_get_property(root, "compatible", NULL);
+ of_node_put(root);
+ /* 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;
+ ssi->soc = of_device_get_match_data(&pdev->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);
+
+ iomem = devm_platform_get_and_ioremap_resource(pdev, 0, &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)
+ return ssi->irq;
+
+ /* Set software limitations for synchronous mode except AC97 */
+ if (ssi->synchronous && !fsl_ssi_is_ac97(ssi)) {
+ ssi->cpu_dai_drv.symmetric_rate = 1;
+ ssi->cpu_dai_drv.symmetric_channels = 1;
+ ssi->cpu_dai_drv.symmetric_sample_bits = 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;
+ }
+ }
+
+ fsl_ssi_debugfs_create(&ssi->dbg_stats, dev);
+
+ /* 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 000000000..db57cad80
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi.h
@@ -0,0 +1,324 @@
+/* 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 {
+ 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);
+
+void 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 void fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg,
+ struct device *dev)
+{
+}
+
+static inline void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg)
+{
+}
+#endif /* ! IS_ENABLED(CONFIG_DEBUG_FS) */
+
+#endif
diff --git a/sound/soc/fsl/fsl_ssi_dbg.c b/sound/soc/fsl/fsl_ssi_dbg.c
new file mode 100644
index 000000000..2c46c55f0
--- /dev/null
+++ b/sound/soc/fsl/fsl_ssi_dbg.c
@@ -0,0 +1,140 @@
+// 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;
+}
+
+DEFINE_SHOW_ATTRIBUTE(fsl_ssi_stats);
+
+void fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, struct device *dev)
+{
+ ssi_dbg->dbg_dir = debugfs_create_dir(dev_name(dev), NULL);
+
+ debugfs_create_file("stats", 0444, ssi_dbg->dbg_dir, ssi_dbg,
+ &fsl_ssi_stats_fops);
+}
+
+void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg)
+{
+ debugfs_remove_recursive(ssi_dbg->dbg_dir);
+}
diff --git a/sound/soc/fsl/fsl_utils.c b/sound/soc/fsl/fsl_utils.c
new file mode 100644
index 000000000..a5ab27c2f
--- /dev/null
+++ b/sound/soc/fsl/fsl_utils.c
@@ -0,0 +1,157 @@
+// 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/clk.h>
+#include <linux/clk-provider.h>
+#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->platforms->name, DAI_NAME_SIZE, "%llx.%pOFn",
+ (unsigned long long) res.start, dma_channel_np);
+
+ 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);
+ of_node_put(dma_channel_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);
+
+/**
+ * fsl_asoc_get_pll_clocks - get two PLL clock source
+ *
+ * @dev: device pointer
+ * @pll8k_clk: PLL clock pointer for 8kHz
+ * @pll11k_clk: PLL clock pointer for 11kHz
+ *
+ * This function get two PLL clock source
+ */
+void fsl_asoc_get_pll_clocks(struct device *dev, struct clk **pll8k_clk,
+ struct clk **pll11k_clk)
+{
+ *pll8k_clk = devm_clk_get(dev, "pll8k");
+ if (IS_ERR(*pll8k_clk))
+ *pll8k_clk = NULL;
+
+ *pll11k_clk = devm_clk_get(dev, "pll11k");
+ if (IS_ERR(*pll11k_clk))
+ *pll11k_clk = NULL;
+}
+EXPORT_SYMBOL(fsl_asoc_get_pll_clocks);
+
+/**
+ * fsl_asoc_reparent_pll_clocks - set clock parent if necessary
+ *
+ * @dev: device pointer
+ * @clk: root clock pointer
+ * @pll8k_clk: PLL clock pointer for 8kHz
+ * @pll11k_clk: PLL clock pointer for 11kHz
+ * @ratio: target requency for root clock
+ *
+ * This function set root clock parent according to the target ratio
+ */
+void fsl_asoc_reparent_pll_clocks(struct device *dev, struct clk *clk,
+ struct clk *pll8k_clk,
+ struct clk *pll11k_clk, u64 ratio)
+{
+ struct clk *p, *pll = NULL, *npll = NULL;
+ bool reparent = false;
+ int ret;
+
+ if (!clk || !pll8k_clk || !pll11k_clk)
+ return;
+
+ p = clk;
+ while (p && pll8k_clk && pll11k_clk) {
+ struct clk *pp = clk_get_parent(p);
+
+ if (clk_is_match(pp, pll8k_clk) ||
+ clk_is_match(pp, pll11k_clk)) {
+ pll = pp;
+ break;
+ }
+ p = pp;
+ }
+
+ npll = (do_div(ratio, 8000) ? pll11k_clk : pll8k_clk);
+ reparent = (pll && !clk_is_match(pll, npll));
+
+ if (reparent) {
+ ret = clk_set_parent(p, npll);
+ if (ret < 0)
+ dev_warn(dev, "failed to set parent:%d\n", ret);
+ }
+}
+EXPORT_SYMBOL(fsl_asoc_reparent_pll_clocks);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale ASoC utility code");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_utils.h b/sound/soc/fsl/fsl_utils.h
new file mode 100644
index 000000000..4d5f3d93b
--- /dev/null
+++ b/sound/soc/fsl/fsl_utils.h
@@ -0,0 +1,29 @@
+/* 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);
+
+void fsl_asoc_get_pll_clocks(struct device *dev, struct clk **pll8k_clk,
+ struct clk **pll11k_clk);
+
+void fsl_asoc_reparent_pll_clocks(struct device *dev, struct clk *clk,
+ struct clk *pll8k_clk,
+ struct clk *pll11k_clk, u64 ratio);
+#endif /* _FSL_UTILS_H */
diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c
new file mode 100644
index 000000000..c043efe45
--- /dev/null
+++ b/sound/soc/fsl/fsl_xcvr.c
@@ -0,0 +1,1388 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2019 NXP
+
+#include <linux/bitrev.h>
+#include <linux/clk.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_iec958.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_xcvr.h"
+#include "imx-pcm.h"
+
+#define FSL_XCVR_CAPDS_SIZE 256
+
+struct fsl_xcvr_soc_data {
+ const char *fw_name;
+};
+
+struct fsl_xcvr {
+ const struct fsl_xcvr_soc_data *soc_data;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct clk *ipg_clk;
+ struct clk *pll_ipg_clk;
+ struct clk *phy_clk;
+ struct clk *spba_clk;
+ struct reset_control *reset;
+ u8 streams;
+ u32 mode;
+ u32 arc_mode;
+ void __iomem *ram_addr;
+ struct snd_dmaengine_dai_dma_data dma_prms_rx;
+ struct snd_dmaengine_dai_dma_data dma_prms_tx;
+ struct snd_aes_iec958 rx_iec958;
+ struct snd_aes_iec958 tx_iec958;
+ u8 cap_ds[FSL_XCVR_CAPDS_SIZE];
+};
+
+static const struct fsl_xcvr_pll_conf {
+ u8 mfi; /* min=0x18, max=0x38 */
+ u32 mfn; /* signed int, 2's compl., min=0x3FFF0000, max=0x00010000 */
+ u32 mfd; /* unsigned int */
+ u32 fout; /* Fout = Fref*(MFI + MFN/MFD), Fref is 24MHz */
+} fsl_xcvr_pll_cfg[] = {
+ { .mfi = 54, .mfn = 1, .mfd = 6, .fout = 1300000000, }, /* 1.3 GHz */
+ { .mfi = 32, .mfn = 96, .mfd = 125, .fout = 786432000, }, /* 8000 Hz */
+ { .mfi = 30, .mfn = 66, .mfd = 625, .fout = 722534400, }, /* 11025 Hz */
+ { .mfi = 29, .mfn = 1, .mfd = 6, .fout = 700000000, }, /* 700 MHz */
+};
+
+/*
+ * HDMI2.1 spec defines 6- and 12-channels layout for one bit audio
+ * stream. Todo: to check how this case can be considered below
+ */
+static const u32 fsl_xcvr_earc_channels[] = { 1, 2, 8, 16, 32, };
+static const struct snd_pcm_hw_constraint_list fsl_xcvr_earc_channels_constr = {
+ .count = ARRAY_SIZE(fsl_xcvr_earc_channels),
+ .list = fsl_xcvr_earc_channels,
+};
+
+static const u32 fsl_xcvr_earc_rates[] = {
+ 32000, 44100, 48000, 64000, 88200, 96000,
+ 128000, 176400, 192000, 256000, 352800, 384000,
+ 512000, 705600, 768000, 1024000, 1411200, 1536000,
+};
+static const struct snd_pcm_hw_constraint_list fsl_xcvr_earc_rates_constr = {
+ .count = ARRAY_SIZE(fsl_xcvr_earc_rates),
+ .list = fsl_xcvr_earc_rates,
+};
+
+static const u32 fsl_xcvr_spdif_channels[] = { 2, };
+static const struct snd_pcm_hw_constraint_list fsl_xcvr_spdif_channels_constr = {
+ .count = ARRAY_SIZE(fsl_xcvr_spdif_channels),
+ .list = fsl_xcvr_spdif_channels,
+};
+
+static const u32 fsl_xcvr_spdif_rates[] = {
+ 32000, 44100, 48000, 88200, 96000, 176400, 192000,
+};
+static const struct snd_pcm_hw_constraint_list fsl_xcvr_spdif_rates_constr = {
+ .count = ARRAY_SIZE(fsl_xcvr_spdif_rates),
+ .list = fsl_xcvr_spdif_rates,
+};
+
+static int fsl_xcvr_arc_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+
+ xcvr->arc_mode = snd_soc_enum_item_to_val(e, item[0]);
+
+ return 0;
+}
+
+static int fsl_xcvr_arc_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ ucontrol->value.enumerated.item[0] = xcvr->arc_mode;
+
+ return 0;
+}
+
+static const u32 fsl_xcvr_phy_arc_cfg[] = {
+ FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN, FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN,
+};
+
+static const char * const fsl_xcvr_arc_mode[] = { "Single Ended", "Common", };
+static const struct soc_enum fsl_xcvr_arc_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fsl_xcvr_arc_mode), fsl_xcvr_arc_mode);
+static struct snd_kcontrol_new fsl_xcvr_arc_mode_kctl =
+ SOC_ENUM_EXT("ARC Mode", fsl_xcvr_arc_mode_enum,
+ fsl_xcvr_arc_mode_get, fsl_xcvr_arc_mode_put);
+
+/* Capabilities data structure, bytes */
+static int fsl_xcvr_type_capds_bytes_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = FSL_XCVR_CAPDS_SIZE;
+
+ return 0;
+}
+
+static int fsl_xcvr_capds_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(ucontrol->value.bytes.data, xcvr->cap_ds, FSL_XCVR_CAPDS_SIZE);
+
+ return 0;
+}
+
+static int fsl_xcvr_capds_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(xcvr->cap_ds, ucontrol->value.bytes.data, FSL_XCVR_CAPDS_SIZE);
+
+ return 0;
+}
+
+static struct snd_kcontrol_new fsl_xcvr_earc_capds_kctl = {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Capabilities Data Structure",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = fsl_xcvr_type_capds_bytes_info,
+ .get = fsl_xcvr_capds_get,
+ .put = fsl_xcvr_capds_put,
+};
+
+static int fsl_xcvr_activate_ctl(struct snd_soc_dai *dai, const char *name,
+ bool active)
+{
+ struct snd_soc_card *card = dai->component->card;
+ struct snd_kcontrol *kctl;
+ bool enabled;
+
+ kctl = snd_soc_card_get_kcontrol(card, name);
+ if (kctl == NULL)
+ return -ENOENT;
+
+ enabled = ((kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_WRITE) != 0);
+ if (active == enabled)
+ return 0; /* nothing to do */
+
+ if (active)
+ kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
+ else
+ kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE;
+
+ snd_ctl_notify(card->snd_card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
+
+ return 1;
+}
+
+static int fsl_xcvr_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct snd_soc_card *card = dai->component->card;
+ struct snd_soc_pcm_runtime *rtd;
+
+ xcvr->mode = snd_soc_enum_item_to_val(e, item[0]);
+
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name,
+ (xcvr->mode == FSL_XCVR_MODE_ARC));
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name,
+ (xcvr->mode == FSL_XCVR_MODE_EARC));
+ /* Allow playback for SPDIF only */
+ rtd = snd_soc_get_pcm_runtime(card, card->dai_link);
+ rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count =
+ (xcvr->mode == FSL_XCVR_MODE_SPDIF ? 1 : 0);
+ return 0;
+}
+
+static int fsl_xcvr_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ ucontrol->value.enumerated.item[0] = xcvr->mode;
+
+ return 0;
+}
+
+static const char * const fsl_xcvr_mode[] = { "SPDIF", "ARC RX", "eARC", };
+static const struct soc_enum fsl_xcvr_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fsl_xcvr_mode), fsl_xcvr_mode);
+static struct snd_kcontrol_new fsl_xcvr_mode_kctl =
+ SOC_ENUM_EXT("XCVR Mode", fsl_xcvr_mode_enum,
+ fsl_xcvr_mode_get, fsl_xcvr_mode_put);
+
+/** phy: true => phy, false => pll */
+static int fsl_xcvr_ai_write(struct fsl_xcvr *xcvr, u8 reg, u32 data, bool phy)
+{
+ struct device *dev = &xcvr->pdev->dev;
+ u32 val, idx, tidx;
+ int ret;
+
+ idx = BIT(phy ? 26 : 24);
+ tidx = BIT(phy ? 27 : 25);
+
+ regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_CLR, 0xFF);
+ regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, reg);
+ regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_WDATA, data);
+ regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_TOG, idx);
+
+ ret = regmap_read_poll_timeout(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL, val,
+ (val & idx) == ((val & tidx) >> 1),
+ 10, 10000);
+ if (ret)
+ dev_err(dev, "AI timeout: failed to set %s reg 0x%02x=0x%08x\n",
+ phy ? "PHY" : "PLL", reg, data);
+ return ret;
+}
+
+static int fsl_xcvr_en_phy_pll(struct fsl_xcvr *xcvr, u32 freq, bool tx)
+{
+ struct device *dev = &xcvr->pdev->dev;
+ u32 i, div = 0, log2;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(fsl_xcvr_pll_cfg); i++) {
+ if (fsl_xcvr_pll_cfg[i].fout % freq == 0) {
+ div = fsl_xcvr_pll_cfg[i].fout / freq;
+ break;
+ }
+ }
+
+ if (!div || i >= ARRAY_SIZE(fsl_xcvr_pll_cfg))
+ return -EINVAL;
+
+ log2 = ilog2(div);
+
+ /* Release AI interface from reset */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET,
+ FSL_XCVR_PHY_AI_CTRL_AI_RESETN);
+ if (ret < 0) {
+ dev_err(dev, "Error while setting IER0: %d\n", ret);
+ return ret;
+ }
+
+ /* PLL: BANDGAP_SET: EN_VBG (enable bandgap) */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_BANDGAP_SET,
+ FSL_XCVR_PLL_BANDGAP_EN_VBG, 0);
+
+ /* PLL: CTRL0: DIV_INTEGER */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0, fsl_xcvr_pll_cfg[i].mfi, 0);
+ /* PLL: NUMERATOR: MFN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_NUM, fsl_xcvr_pll_cfg[i].mfn, 0);
+ /* PLL: DENOMINATOR: MFD */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_DEN, fsl_xcvr_pll_cfg[i].mfd, 0);
+ /* PLL: CTRL0_SET: HOLD_RING_OFF, POWER_UP */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET,
+ FSL_XCVR_PLL_CTRL0_HROFF | FSL_XCVR_PLL_CTRL0_PWP, 0);
+ udelay(25);
+ /* PLL: CTRL0: Clear Hold Ring Off */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_CLR,
+ FSL_XCVR_PLL_CTRL0_HROFF, 0);
+ udelay(100);
+ if (tx) { /* TX is enabled for SPDIF only */
+ /* PLL: POSTDIV: PDIV0 */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV,
+ FSL_XCVR_PLL_PDIVx(log2, 0), 0);
+ /* PLL: CTRL_SET: CLKMUX0_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET,
+ FSL_XCVR_PLL_CTRL0_CM0_EN, 0);
+ } else if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC RX */
+ /* PLL: POSTDIV: PDIV1 */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV,
+ FSL_XCVR_PLL_PDIVx(log2, 1), 0);
+ /* PLL: CTRL_SET: CLKMUX1_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET,
+ FSL_XCVR_PLL_CTRL0_CM1_EN, 0);
+ } else { /* SPDIF / ARC RX */
+ /* PLL: POSTDIV: PDIV2 */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV,
+ FSL_XCVR_PLL_PDIVx(log2, 2), 0);
+ /* PLL: CTRL_SET: CLKMUX2_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET,
+ FSL_XCVR_PLL_CTRL0_CM2_EN, 0);
+ }
+
+ if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC mode */
+ /* PHY: CTRL_SET: TX_DIFF_OE, PHY_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_TSDIFF_OE |
+ FSL_XCVR_PHY_CTRL_PHY_EN, 1);
+ /* PHY: CTRL2_SET: EARC_TX_MODE */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL2_SET,
+ FSL_XCVR_PHY_CTRL2_EARC_TXMS, 1);
+ } else if (!tx) { /* SPDIF / ARC RX mode */
+ if (xcvr->mode == FSL_XCVR_MODE_SPDIF)
+ /* PHY: CTRL_SET: SPDIF_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_SPDIF_EN, 1);
+ else /* PHY: CTRL_SET: ARC RX setup */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_PHY_EN |
+ FSL_XCVR_PHY_CTRL_RX_CM_EN |
+ fsl_xcvr_phy_arc_cfg[xcvr->arc_mode], 1);
+ }
+
+ dev_dbg(dev, "PLL Fexp: %u, Fout: %u, mfi: %u, mfn: %u, mfd: %d, div: %u, pdiv0: %u\n",
+ freq, fsl_xcvr_pll_cfg[i].fout, fsl_xcvr_pll_cfg[i].mfi,
+ fsl_xcvr_pll_cfg[i].mfn, fsl_xcvr_pll_cfg[i].mfd, div, log2);
+ return 0;
+}
+
+static int fsl_xcvr_en_aud_pll(struct fsl_xcvr *xcvr, u32 freq)
+{
+ struct device *dev = &xcvr->pdev->dev;
+ int ret;
+
+ clk_disable_unprepare(xcvr->phy_clk);
+ ret = clk_set_rate(xcvr->phy_clk, freq);
+ if (ret < 0) {
+ dev_err(dev, "Error while setting AUD PLL rate: %d\n", ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(xcvr->phy_clk);
+ if (ret) {
+ dev_err(dev, "failed to start PHY clock: %d\n", ret);
+ return ret;
+ }
+
+ /* Release AI interface from reset */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET,
+ FSL_XCVR_PHY_AI_CTRL_AI_RESETN);
+ if (ret < 0) {
+ dev_err(dev, "Error while setting IER0: %d\n", ret);
+ return ret;
+ }
+
+ if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC mode */
+ /* PHY: CTRL_SET: TX_DIFF_OE, PHY_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_TSDIFF_OE |
+ FSL_XCVR_PHY_CTRL_PHY_EN, 1);
+ /* PHY: CTRL2_SET: EARC_TX_MODE */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL2_SET,
+ FSL_XCVR_PHY_CTRL2_EARC_TXMS, 1);
+ } else { /* SPDIF mode */
+ /* PHY: CTRL_SET: TX_CLK_AUD_SS | SPDIF_EN */
+ fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET,
+ FSL_XCVR_PHY_CTRL_TX_CLK_AUD_SS |
+ FSL_XCVR_PHY_CTRL_SPDIF_EN, 1);
+ }
+
+ dev_dbg(dev, "PLL Fexp: %u\n", freq);
+
+ return 0;
+}
+
+#define FSL_XCVR_SPDIF_RX_FREQ 175000000
+static int fsl_xcvr_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 m_ctl = 0, v_ctl = 0;
+ u32 r = substream->runtime->rate, ch = substream->runtime->channels;
+ u32 fout = 32 * r * ch * 10 * 2;
+ int ret = 0;
+
+ switch (xcvr->mode) {
+ case FSL_XCVR_MODE_SPDIF:
+ case FSL_XCVR_MODE_ARC:
+ if (tx) {
+ ret = fsl_xcvr_en_aud_pll(xcvr, fout);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set TX freq %u: %d\n",
+ fout, ret);
+ return ret;
+ }
+
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_TX_DPTH_CTRL_SET,
+ FSL_XCVR_TX_DPTH_CTRL_FRM_FMT);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set TX_DPTH: %d\n", ret);
+ return ret;
+ }
+
+ /**
+ * set SPDIF MODE - this flag is used to gate
+ * SPDIF output, useless for SPDIF RX
+ */
+ m_ctl |= FSL_XCVR_EXT_CTRL_SPDIF_MODE;
+ v_ctl |= FSL_XCVR_EXT_CTRL_SPDIF_MODE;
+ } else {
+ /**
+ * Clear RX FIFO, flip RX FIFO bits,
+ * disable eARC related HW mode detects
+ */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_SET,
+ FSL_XCVR_RX_DPTH_CTRL_STORE_FMT |
+ FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO |
+ FSL_XCVR_RX_DPTH_CTRL_COMP |
+ FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set RX_DPTH: %d\n", ret);
+ return ret;
+ }
+
+ ret = fsl_xcvr_en_phy_pll(xcvr, FSL_XCVR_SPDIF_RX_FREQ, tx);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set RX freq %u: %d\n",
+ FSL_XCVR_SPDIF_RX_FREQ, ret);
+ return ret;
+ }
+ }
+ break;
+ case FSL_XCVR_MODE_EARC:
+ if (!tx) {
+ /** Clear RX FIFO, flip RX FIFO bits */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_SET,
+ FSL_XCVR_RX_DPTH_CTRL_STORE_FMT |
+ FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set RX_DPTH: %d\n", ret);
+ return ret;
+ }
+
+ /** Enable eARC related HW mode detects */
+ ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_CLR,
+ FSL_XCVR_RX_DPTH_CTRL_COMP |
+ FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to clr TX_DPTH: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* clear CMDC RESET */
+ m_ctl |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx);
+ /* set TX_RX_MODE */
+ m_ctl |= FSL_XCVR_EXT_CTRL_TX_RX_MODE;
+ v_ctl |= (tx ? FSL_XCVR_EXT_CTRL_TX_RX_MODE : 0);
+ break;
+ }
+
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0,
+ FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL);
+ if (ret < 0) {
+ dev_err(dai->dev, "Error while setting IER0: %d\n", ret);
+ return ret;
+ }
+
+ /* set DPATH RESET */
+ m_ctl |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx);
+ v_ctl |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx);
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, m_ctl, v_ctl);
+ if (ret < 0) {
+ dev_err(dai->dev, "Error while setting EXT_CTRL: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_xcvr_constr(const struct snd_pcm_substream *substream,
+ const struct snd_pcm_hw_constraint_list *channels,
+ const struct snd_pcm_hw_constraint_list *rates)
+{
+ struct snd_pcm_runtime *rt = substream->runtime;
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ channels);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE,
+ rates);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int fsl_xcvr_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret = 0;
+
+ if (xcvr->streams & BIT(substream->stream)) {
+ dev_err(dai->dev, "%sX busy\n", tx ? "T" : "R");
+ return -EBUSY;
+ }
+
+ switch (xcvr->mode) {
+ case FSL_XCVR_MODE_SPDIF:
+ case FSL_XCVR_MODE_ARC:
+ ret = fsl_xcvr_constr(substream, &fsl_xcvr_spdif_channels_constr,
+ &fsl_xcvr_spdif_rates_constr);
+ break;
+ case FSL_XCVR_MODE_EARC:
+ ret = fsl_xcvr_constr(substream, &fsl_xcvr_earc_channels_constr,
+ &fsl_xcvr_earc_rates_constr);
+ break;
+ }
+ if (ret < 0)
+ return ret;
+
+ xcvr->streams |= BIT(substream->stream);
+
+ /* Disable XCVR controls if there is stream started */
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_mode_kctl.name, false);
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, false);
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, false);
+
+ return 0;
+}
+
+static void fsl_xcvr_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 mask = 0, val = 0;
+ int ret;
+
+ xcvr->streams &= ~BIT(substream->stream);
+
+ /* Enable XCVR controls if there is no stream started */
+ if (!xcvr->streams) {
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_mode_kctl.name, true);
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name,
+ (xcvr->mode == FSL_XCVR_MODE_ARC));
+ fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name,
+ (xcvr->mode == FSL_XCVR_MODE_EARC));
+
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0,
+ FSL_XCVR_IRQ_EARC_ALL, 0);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to set IER0: %d\n", ret);
+ return;
+ }
+
+ /* clear SPDIF MODE */
+ if (xcvr->mode == FSL_XCVR_MODE_SPDIF)
+ mask |= FSL_XCVR_EXT_CTRL_SPDIF_MODE;
+ }
+
+ if (xcvr->mode == FSL_XCVR_MODE_EARC) {
+ /* set CMDC RESET */
+ mask |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx);
+ val |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx);
+ }
+
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, mask, val);
+ if (ret < 0) {
+ dev_err(dai->dev, "Err setting DPATH RESET: %d\n", ret);
+ return;
+ }
+}
+
+static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (tx) {
+ switch (xcvr->mode) {
+ case FSL_XCVR_MODE_EARC:
+ /* set isr_cmdc_tx_en, w1c */
+ ret = regmap_write(xcvr->regmap,
+ FSL_XCVR_ISR_SET,
+ FSL_XCVR_ISR_CMDC_TX_EN);
+ if (ret < 0) {
+ dev_err(dai->dev, "err updating isr %d\n", ret);
+ return ret;
+ }
+ fallthrough;
+ case FSL_XCVR_MODE_SPDIF:
+ ret = regmap_write(xcvr->regmap,
+ FSL_XCVR_TX_DPTH_CTRL_SET,
+ FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to start DATA_TX: %d\n", ret);
+ return ret;
+ }
+ break;
+ }
+ }
+
+ /* enable DMA RD/WR */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_DMA_DIS(tx), 0);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to enable DMA: %d\n", ret);
+ return ret;
+ }
+
+ /* clear DPATH RESET */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_DPTH_RESET(tx),
+ 0);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to clear DPATH RESET: %d\n", ret);
+ return ret;
+ }
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ /* disable DMA RD/WR */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_DMA_DIS(tx),
+ FSL_XCVR_EXT_CTRL_DMA_DIS(tx));
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to disable DMA: %d\n", ret);
+ return ret;
+ }
+
+ if (tx) {
+ switch (xcvr->mode) {
+ case FSL_XCVR_MODE_SPDIF:
+ ret = regmap_write(xcvr->regmap,
+ FSL_XCVR_TX_DPTH_CTRL_CLR,
+ FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to stop DATA_TX: %d\n", ret);
+ return ret;
+ }
+ fallthrough;
+ case FSL_XCVR_MODE_EARC:
+ /* clear ISR_CMDC_TX_EN, W1C */
+ ret = regmap_write(xcvr->regmap,
+ FSL_XCVR_ISR_CLR,
+ FSL_XCVR_ISR_CMDC_TX_EN);
+ if (ret < 0) {
+ dev_err(dai->dev,
+ "Err updating ISR %d\n", ret);
+ return ret;
+ }
+ break;
+ }
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr)
+{
+ struct device *dev = &xcvr->pdev->dev;
+ const struct firmware *fw;
+ int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET;
+ u32 mask, val;
+
+ ret = request_firmware(&fw, xcvr->soc_data->fw_name, dev);
+ if (ret) {
+ dev_err(dev, "failed to request firmware.\n");
+ return ret;
+ }
+
+ rem = fw->size;
+
+ /* RAM is 20KiB = 16KiB code + 4KiB data => max 10 pages 2KiB each */
+ if (rem > 16384) {
+ dev_err(dev, "FW size %d is bigger than 16KiB.\n", rem);
+ release_firmware(fw);
+ return -ENOMEM;
+ }
+
+ for (page = 0; page < 10; page++) {
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_PAGE_MASK,
+ FSL_XCVR_EXT_CTRL_PAGE(page));
+ if (ret < 0) {
+ dev_err(dev, "FW: failed to set page %d, err=%d\n",
+ page, ret);
+ goto err_firmware;
+ }
+
+ off = page * size;
+ out = min(rem, size);
+ /* IPG clock is assumed to be running, otherwise it will hang */
+ if (out > 0) {
+ /* write firmware into code memory */
+ memcpy_toio(xcvr->ram_addr, fw->data + off, out);
+ rem -= out;
+ if (rem == 0) {
+ /* last part of firmware written */
+ /* clean remaining part of code memory page */
+ memset_io(xcvr->ram_addr + out, 0, size - out);
+ }
+ } else {
+ /* clean current page, including data memory */
+ memset_io(xcvr->ram_addr, 0, size);
+ }
+ }
+
+err_firmware:
+ release_firmware(fw);
+ if (ret < 0)
+ return ret;
+
+ /* configure watermarks */
+ mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK;
+ val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX);
+ val |= FSL_XCVR_EXT_CTRL_TX_FWM(FSL_XCVR_FIFO_WMK_TX);
+ /* disable DMA RD/WR */
+ mask |= FSL_XCVR_EXT_CTRL_DMA_RD_DIS | FSL_XCVR_EXT_CTRL_DMA_WR_DIS;
+ val |= FSL_XCVR_EXT_CTRL_DMA_RD_DIS | FSL_XCVR_EXT_CTRL_DMA_WR_DIS;
+ /* Data RAM is 4KiB, last two pages: 8 and 9. Select page 8. */
+ mask |= FSL_XCVR_EXT_CTRL_PAGE_MASK;
+ val |= FSL_XCVR_EXT_CTRL_PAGE(8);
+
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, mask, val);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set watermarks: %d\n", ret);
+ return ret;
+ }
+
+ /* Store Capabilities Data Structure into Data RAM */
+ memcpy_toio(xcvr->ram_addr + FSL_XCVR_CAP_DATA_STR, xcvr->cap_ds,
+ FSL_XCVR_CAPDS_SIZE);
+ return 0;
+}
+
+static int fsl_xcvr_type_iec958_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_xcvr_type_iec958_bytes_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = sizeof_field(struct snd_aes_iec958, status);
+
+ return 0;
+}
+
+static int fsl_xcvr_rx_cs_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(ucontrol->value.iec958.status, xcvr->rx_iec958.status, 24);
+
+ return 0;
+}
+
+static int fsl_xcvr_tx_cs_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(ucontrol->value.iec958.status, xcvr->tx_iec958.status, 24);
+
+ return 0;
+}
+
+static int fsl_xcvr_tx_cs_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ memcpy(xcvr->tx_iec958.status, ucontrol->value.iec958.status, 24);
+
+ return 0;
+}
+
+static struct snd_kcontrol_new fsl_xcvr_rx_ctls[] = {
+ /* Channel status controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = fsl_xcvr_type_iec958_info,
+ .get = fsl_xcvr_rx_cs_get,
+ },
+ /* Capture channel status, bytes */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Capture Channel Status",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = fsl_xcvr_type_iec958_bytes_info,
+ .get = fsl_xcvr_rx_cs_get,
+ },
+};
+
+static struct snd_kcontrol_new fsl_xcvr_tx_ctls[] = {
+ /* Channel status controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = fsl_xcvr_type_iec958_info,
+ .get = fsl_xcvr_tx_cs_get,
+ .put = fsl_xcvr_tx_cs_put,
+ },
+ /* Playback channel status, bytes */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Playback Channel Status",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = fsl_xcvr_type_iec958_bytes_info,
+ .get = fsl_xcvr_tx_cs_get,
+ .put = fsl_xcvr_tx_cs_put,
+ },
+};
+
+static const struct snd_soc_dai_ops fsl_xcvr_dai_ops = {
+ .prepare = fsl_xcvr_prepare,
+ .startup = fsl_xcvr_startup,
+ .shutdown = fsl_xcvr_shutdown,
+ .trigger = fsl_xcvr_trigger,
+};
+
+static int fsl_xcvr_dai_probe(struct snd_soc_dai *dai)
+{
+ struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai, &xcvr->dma_prms_tx, &xcvr->dma_prms_rx);
+
+ snd_soc_add_dai_controls(dai, &fsl_xcvr_mode_kctl, 1);
+ snd_soc_add_dai_controls(dai, &fsl_xcvr_arc_mode_kctl, 1);
+ snd_soc_add_dai_controls(dai, &fsl_xcvr_earc_capds_kctl, 1);
+ snd_soc_add_dai_controls(dai, fsl_xcvr_tx_ctls,
+ ARRAY_SIZE(fsl_xcvr_tx_ctls));
+ snd_soc_add_dai_controls(dai, fsl_xcvr_rx_ctls,
+ ARRAY_SIZE(fsl_xcvr_rx_ctls));
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_xcvr_dai = {
+ .probe = fsl_xcvr_dai_probe,
+ .ops = &fsl_xcvr_dai_ops,
+ .playback = {
+ .stream_name = "CPU-Playback",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rate_min = 32000,
+ .rate_max = 1536000,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
+ },
+ .capture = {
+ .stream_name = "CPU-Capture",
+ .channels_min = 1,
+ .channels_max = 32,
+ .rate_min = 32000,
+ .rate_max = 1536000,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
+ },
+};
+
+static const struct snd_soc_component_driver fsl_xcvr_comp = {
+ .name = "fsl-xcvr-dai",
+ .legacy_dai_naming = 1,
+};
+
+static const struct reg_default fsl_xcvr_reg_defaults[] = {
+ { FSL_XCVR_VERSION, 0x00000000 },
+ { FSL_XCVR_EXT_CTRL, 0xF8204040 },
+ { FSL_XCVR_EXT_STATUS, 0x00000000 },
+ { FSL_XCVR_EXT_IER0, 0x00000000 },
+ { FSL_XCVR_EXT_IER1, 0x00000000 },
+ { FSL_XCVR_EXT_ISR, 0x00000000 },
+ { FSL_XCVR_EXT_ISR_SET, 0x00000000 },
+ { FSL_XCVR_EXT_ISR_CLR, 0x00000000 },
+ { FSL_XCVR_EXT_ISR_TOG, 0x00000000 },
+ { FSL_XCVR_IER, 0x00000000 },
+ { FSL_XCVR_ISR, 0x00000000 },
+ { FSL_XCVR_ISR_SET, 0x00000000 },
+ { FSL_XCVR_ISR_CLR, 0x00000000 },
+ { FSL_XCVR_ISR_TOG, 0x00000000 },
+ { FSL_XCVR_RX_DPTH_CTRL, 0x00002C89 },
+ { FSL_XCVR_RX_DPTH_CTRL_SET, 0x00002C89 },
+ { FSL_XCVR_RX_DPTH_CTRL_CLR, 0x00002C89 },
+ { FSL_XCVR_RX_DPTH_CTRL_TOG, 0x00002C89 },
+ { FSL_XCVR_TX_DPTH_CTRL, 0x00000000 },
+ { FSL_XCVR_TX_DPTH_CTRL_SET, 0x00000000 },
+ { FSL_XCVR_TX_DPTH_CTRL_CLR, 0x00000000 },
+ { FSL_XCVR_TX_DPTH_CTRL_TOG, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_0, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_1, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_2, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_3, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_4, 0x00000000 },
+ { FSL_XCVR_TX_CS_DATA_5, 0x00000000 },
+ { FSL_XCVR_DEBUG_REG_0, 0x00000000 },
+ { FSL_XCVR_DEBUG_REG_1, 0x00000000 },
+};
+
+static bool fsl_xcvr_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_XCVR_VERSION:
+ case FSL_XCVR_EXT_CTRL:
+ case FSL_XCVR_EXT_STATUS:
+ case FSL_XCVR_EXT_IER0:
+ case FSL_XCVR_EXT_IER1:
+ case FSL_XCVR_EXT_ISR:
+ case FSL_XCVR_EXT_ISR_SET:
+ case FSL_XCVR_EXT_ISR_CLR:
+ case FSL_XCVR_EXT_ISR_TOG:
+ case FSL_XCVR_IER:
+ case FSL_XCVR_ISR:
+ case FSL_XCVR_ISR_SET:
+ case FSL_XCVR_ISR_CLR:
+ case FSL_XCVR_ISR_TOG:
+ case FSL_XCVR_PHY_AI_CTRL:
+ case FSL_XCVR_PHY_AI_CTRL_SET:
+ case FSL_XCVR_PHY_AI_CTRL_CLR:
+ case FSL_XCVR_PHY_AI_CTRL_TOG:
+ case FSL_XCVR_PHY_AI_RDATA:
+ case FSL_XCVR_CLK_CTRL:
+ case FSL_XCVR_RX_DPTH_CTRL:
+ case FSL_XCVR_RX_DPTH_CTRL_SET:
+ case FSL_XCVR_RX_DPTH_CTRL_CLR:
+ case FSL_XCVR_RX_DPTH_CTRL_TOG:
+ case FSL_XCVR_TX_DPTH_CTRL:
+ case FSL_XCVR_TX_DPTH_CTRL_SET:
+ case FSL_XCVR_TX_DPTH_CTRL_CLR:
+ case FSL_XCVR_TX_DPTH_CTRL_TOG:
+ case FSL_XCVR_TX_CS_DATA_0:
+ case FSL_XCVR_TX_CS_DATA_1:
+ case FSL_XCVR_TX_CS_DATA_2:
+ case FSL_XCVR_TX_CS_DATA_3:
+ case FSL_XCVR_TX_CS_DATA_4:
+ case FSL_XCVR_TX_CS_DATA_5:
+ case FSL_XCVR_DEBUG_REG_0:
+ case FSL_XCVR_DEBUG_REG_1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_xcvr_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_XCVR_EXT_CTRL:
+ case FSL_XCVR_EXT_IER0:
+ case FSL_XCVR_EXT_IER1:
+ case FSL_XCVR_EXT_ISR:
+ case FSL_XCVR_EXT_ISR_SET:
+ case FSL_XCVR_EXT_ISR_CLR:
+ case FSL_XCVR_EXT_ISR_TOG:
+ case FSL_XCVR_IER:
+ case FSL_XCVR_ISR_SET:
+ case FSL_XCVR_ISR_CLR:
+ case FSL_XCVR_ISR_TOG:
+ case FSL_XCVR_PHY_AI_CTRL:
+ case FSL_XCVR_PHY_AI_CTRL_SET:
+ case FSL_XCVR_PHY_AI_CTRL_CLR:
+ case FSL_XCVR_PHY_AI_CTRL_TOG:
+ case FSL_XCVR_PHY_AI_WDATA:
+ case FSL_XCVR_CLK_CTRL:
+ case FSL_XCVR_RX_DPTH_CTRL:
+ case FSL_XCVR_RX_DPTH_CTRL_SET:
+ case FSL_XCVR_RX_DPTH_CTRL_CLR:
+ case FSL_XCVR_RX_DPTH_CTRL_TOG:
+ case FSL_XCVR_TX_DPTH_CTRL_SET:
+ case FSL_XCVR_TX_DPTH_CTRL_CLR:
+ case FSL_XCVR_TX_DPTH_CTRL_TOG:
+ case FSL_XCVR_TX_CS_DATA_0:
+ case FSL_XCVR_TX_CS_DATA_1:
+ case FSL_XCVR_TX_CS_DATA_2:
+ case FSL_XCVR_TX_CS_DATA_3:
+ case FSL_XCVR_TX_CS_DATA_4:
+ case FSL_XCVR_TX_CS_DATA_5:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_xcvr_volatile_reg(struct device *dev, unsigned int reg)
+{
+ return fsl_xcvr_readable_reg(dev, reg);
+}
+
+static const struct regmap_config fsl_xcvr_regmap_cfg = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = FSL_XCVR_MAX_REG,
+ .reg_defaults = fsl_xcvr_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_xcvr_reg_defaults),
+ .readable_reg = fsl_xcvr_readable_reg,
+ .volatile_reg = fsl_xcvr_volatile_reg,
+ .writeable_reg = fsl_xcvr_writeable_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static irqreturn_t irq0_isr(int irq, void *devid)
+{
+ struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid;
+ struct device *dev = &xcvr->pdev->dev;
+ struct regmap *regmap = xcvr->regmap;
+ void __iomem *reg_ctrl, *reg_buff;
+ u32 isr, isr_clr = 0, val, i;
+
+ regmap_read(regmap, FSL_XCVR_EXT_ISR, &isr);
+
+ if (isr & FSL_XCVR_IRQ_NEW_CS) {
+ dev_dbg(dev, "Received new CS block\n");
+ isr_clr |= FSL_XCVR_IRQ_NEW_CS;
+ /* Data RAM is 4KiB, last two pages: 8 and 9. Select page 8. */
+ regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_PAGE_MASK,
+ FSL_XCVR_EXT_CTRL_PAGE(8));
+
+ /* Find updated CS buffer */
+ reg_ctrl = xcvr->ram_addr + FSL_XCVR_RX_CS_CTRL_0;
+ reg_buff = xcvr->ram_addr + FSL_XCVR_RX_CS_BUFF_0;
+ memcpy_fromio(&val, reg_ctrl, sizeof(val));
+ if (!val) {
+ reg_ctrl = xcvr->ram_addr + FSL_XCVR_RX_CS_CTRL_1;
+ reg_buff = xcvr->ram_addr + FSL_XCVR_RX_CS_BUFF_1;
+ memcpy_fromio(&val, reg_ctrl, sizeof(val));
+ }
+
+ if (val) {
+ /* copy CS buffer */
+ memcpy_fromio(&xcvr->rx_iec958.status, reg_buff,
+ sizeof(xcvr->rx_iec958.status));
+ for (i = 0; i < 6; i++) {
+ val = *(u32 *)(xcvr->rx_iec958.status + i*4);
+ *(u32 *)(xcvr->rx_iec958.status + i*4) =
+ bitrev32(val);
+ }
+ /* clear CS control register */
+ memset_io(reg_ctrl, 0, sizeof(val));
+ }
+ }
+ if (isr & FSL_XCVR_IRQ_NEW_UD) {
+ dev_dbg(dev, "Received new UD block\n");
+ isr_clr |= FSL_XCVR_IRQ_NEW_UD;
+ }
+ if (isr & FSL_XCVR_IRQ_MUTE) {
+ dev_dbg(dev, "HW mute bit detected\n");
+ isr_clr |= FSL_XCVR_IRQ_MUTE;
+ }
+ if (isr & FSL_XCVR_IRQ_FIFO_UOFL_ERR) {
+ dev_dbg(dev, "RX/TX FIFO full/empty\n");
+ isr_clr |= FSL_XCVR_IRQ_FIFO_UOFL_ERR;
+ }
+ if (isr & FSL_XCVR_IRQ_ARC_MODE) {
+ dev_dbg(dev, "CMDC SM falls out of eARC mode\n");
+ isr_clr |= FSL_XCVR_IRQ_ARC_MODE;
+ }
+ if (isr & FSL_XCVR_IRQ_DMA_RD_REQ) {
+ dev_dbg(dev, "DMA read request\n");
+ isr_clr |= FSL_XCVR_IRQ_DMA_RD_REQ;
+ }
+ if (isr & FSL_XCVR_IRQ_DMA_WR_REQ) {
+ dev_dbg(dev, "DMA write request\n");
+ isr_clr |= FSL_XCVR_IRQ_DMA_WR_REQ;
+ }
+
+ if (isr_clr) {
+ regmap_write(regmap, FSL_XCVR_EXT_ISR_CLR, isr_clr);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static const struct fsl_xcvr_soc_data fsl_xcvr_imx8mp_data = {
+ .fw_name = "imx/xcvr/xcvr-imx8mp.bin",
+};
+
+static const struct of_device_id fsl_xcvr_dt_ids[] = {
+ { .compatible = "fsl,imx8mp-xcvr", .data = &fsl_xcvr_imx8mp_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_xcvr_dt_ids);
+
+static int fsl_xcvr_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fsl_xcvr *xcvr;
+ struct resource *rx_res, *tx_res;
+ void __iomem *regs;
+ int ret, irq;
+
+ xcvr = devm_kzalloc(dev, sizeof(*xcvr), GFP_KERNEL);
+ if (!xcvr)
+ return -ENOMEM;
+
+ xcvr->pdev = pdev;
+ xcvr->soc_data = of_device_get_match_data(&pdev->dev);
+
+ xcvr->ipg_clk = devm_clk_get(dev, "ipg");
+ if (IS_ERR(xcvr->ipg_clk)) {
+ dev_err(dev, "failed to get ipg clock\n");
+ return PTR_ERR(xcvr->ipg_clk);
+ }
+
+ xcvr->phy_clk = devm_clk_get(dev, "phy");
+ if (IS_ERR(xcvr->phy_clk)) {
+ dev_err(dev, "failed to get phy clock\n");
+ return PTR_ERR(xcvr->phy_clk);
+ }
+
+ xcvr->spba_clk = devm_clk_get(dev, "spba");
+ if (IS_ERR(xcvr->spba_clk)) {
+ dev_err(dev, "failed to get spba clock\n");
+ return PTR_ERR(xcvr->spba_clk);
+ }
+
+ xcvr->pll_ipg_clk = devm_clk_get(dev, "pll_ipg");
+ if (IS_ERR(xcvr->pll_ipg_clk)) {
+ dev_err(dev, "failed to get pll_ipg clock\n");
+ return PTR_ERR(xcvr->pll_ipg_clk);
+ }
+
+ xcvr->ram_addr = devm_platform_ioremap_resource_byname(pdev, "ram");
+ if (IS_ERR(xcvr->ram_addr))
+ return PTR_ERR(xcvr->ram_addr);
+
+ regs = devm_platform_ioremap_resource_byname(pdev, "regs");
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ xcvr->regmap = devm_regmap_init_mmio_clk(dev, NULL, regs,
+ &fsl_xcvr_regmap_cfg);
+ if (IS_ERR(xcvr->regmap)) {
+ dev_err(dev, "failed to init XCVR regmap: %ld\n",
+ PTR_ERR(xcvr->regmap));
+ return PTR_ERR(xcvr->regmap);
+ }
+
+ xcvr->reset = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(xcvr->reset)) {
+ dev_err(dev, "failed to get XCVR reset control\n");
+ return PTR_ERR(xcvr->reset);
+ }
+
+ /* get IRQs */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(dev, irq, irq0_isr, 0, pdev->name, xcvr);
+ if (ret) {
+ dev_err(dev, "failed to claim IRQ0: %i\n", ret);
+ return ret;
+ }
+
+ rx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rxfifo");
+ tx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "txfifo");
+ if (!rx_res || !tx_res) {
+ dev_err(dev, "could not find rxfifo or txfifo resource\n");
+ return -EINVAL;
+ }
+ xcvr->dma_prms_rx.chan_name = "rx";
+ xcvr->dma_prms_tx.chan_name = "tx";
+ xcvr->dma_prms_rx.addr = rx_res->start;
+ xcvr->dma_prms_tx.addr = tx_res->start;
+ xcvr->dma_prms_rx.maxburst = FSL_XCVR_MAXBURST_RX;
+ xcvr->dma_prms_tx.maxburst = FSL_XCVR_MAXBURST_TX;
+
+ platform_set_drvdata(pdev, xcvr);
+ pm_runtime_enable(dev);
+ regcache_cache_only(xcvr->regmap, true);
+
+ /*
+ * Register platform component before registering cpu dai for there
+ * is not defer probe for platform component in snd_soc_add_pcm_runtime().
+ */
+ ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0);
+ if (ret) {
+ pm_runtime_disable(dev);
+ dev_err(dev, "failed to pcm register\n");
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(dev, &fsl_xcvr_comp,
+ &fsl_xcvr_dai, 1);
+ if (ret) {
+ pm_runtime_disable(dev);
+ dev_err(dev, "failed to register component %s\n",
+ fsl_xcvr_comp.name);
+ }
+
+ return ret;
+}
+
+static int fsl_xcvr_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+
+static __maybe_unused int fsl_xcvr_runtime_suspend(struct device *dev)
+{
+ struct fsl_xcvr *xcvr = dev_get_drvdata(dev);
+ int ret;
+
+ /*
+ * Clear interrupts, when streams starts or resumes after
+ * suspend, interrupts are enabled in prepare(), so no need
+ * to enable interrupts in resume().
+ */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0,
+ FSL_XCVR_IRQ_EARC_ALL, 0);
+ if (ret < 0)
+ dev_err(dev, "Failed to clear IER0: %d\n", ret);
+
+ /* Assert M0+ reset */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_CORE_RESET,
+ FSL_XCVR_EXT_CTRL_CORE_RESET);
+ if (ret < 0)
+ dev_err(dev, "Failed to assert M0+ core: %d\n", ret);
+
+ regcache_cache_only(xcvr->regmap, true);
+
+ clk_disable_unprepare(xcvr->spba_clk);
+ clk_disable_unprepare(xcvr->phy_clk);
+ clk_disable_unprepare(xcvr->pll_ipg_clk);
+ clk_disable_unprepare(xcvr->ipg_clk);
+
+ return 0;
+}
+
+static __maybe_unused int fsl_xcvr_runtime_resume(struct device *dev)
+{
+ struct fsl_xcvr *xcvr = dev_get_drvdata(dev);
+ int ret;
+
+ ret = reset_control_assert(xcvr->reset);
+ if (ret < 0) {
+ dev_err(dev, "Failed to assert M0+ reset: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(xcvr->ipg_clk);
+ if (ret) {
+ dev_err(dev, "failed to start IPG clock.\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(xcvr->pll_ipg_clk);
+ if (ret) {
+ dev_err(dev, "failed to start PLL IPG clock.\n");
+ goto stop_ipg_clk;
+ }
+
+ ret = clk_prepare_enable(xcvr->phy_clk);
+ if (ret) {
+ dev_err(dev, "failed to start PHY clock: %d\n", ret);
+ goto stop_pll_ipg_clk;
+ }
+
+ ret = clk_prepare_enable(xcvr->spba_clk);
+ if (ret) {
+ dev_err(dev, "failed to start SPBA clock.\n");
+ goto stop_phy_clk;
+ }
+
+ regcache_cache_only(xcvr->regmap, false);
+ regcache_mark_dirty(xcvr->regmap);
+ ret = regcache_sync(xcvr->regmap);
+
+ if (ret) {
+ dev_err(dev, "failed to sync regcache.\n");
+ goto stop_spba_clk;
+ }
+
+ ret = reset_control_deassert(xcvr->reset);
+ if (ret) {
+ dev_err(dev, "failed to deassert M0+ reset.\n");
+ goto stop_spba_clk;
+ }
+
+ ret = fsl_xcvr_load_firmware(xcvr);
+ if (ret) {
+ dev_err(dev, "failed to load firmware.\n");
+ goto stop_spba_clk;
+ }
+
+ /* Release M0+ reset */
+ ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL,
+ FSL_XCVR_EXT_CTRL_CORE_RESET, 0);
+ if (ret < 0) {
+ dev_err(dev, "M0+ core release failed: %d\n", ret);
+ goto stop_spba_clk;
+ }
+
+ /* Let M0+ core complete firmware initialization */
+ msleep(50);
+
+ return 0;
+
+stop_spba_clk:
+ clk_disable_unprepare(xcvr->spba_clk);
+stop_phy_clk:
+ clk_disable_unprepare(xcvr->phy_clk);
+stop_pll_ipg_clk:
+ clk_disable_unprepare(xcvr->pll_ipg_clk);
+stop_ipg_clk:
+ clk_disable_unprepare(xcvr->ipg_clk);
+
+ return ret;
+}
+
+static const struct dev_pm_ops fsl_xcvr_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_xcvr_runtime_suspend,
+ fsl_xcvr_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_xcvr_driver = {
+ .probe = fsl_xcvr_probe,
+ .driver = {
+ .name = "fsl,imx8mp-audio-xcvr",
+ .pm = &fsl_xcvr_pm_ops,
+ .of_match_table = fsl_xcvr_dt_ids,
+ },
+ .remove = fsl_xcvr_remove,
+};
+module_platform_driver(fsl_xcvr_driver);
+
+MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>");
+MODULE_DESCRIPTION("NXP Audio Transceiver (XCVR) driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_xcvr.h b/sound/soc/fsl/fsl_xcvr.h
new file mode 100644
index 000000000..7f2853c60
--- /dev/null
+++ b/sound/soc/fsl/fsl_xcvr.h
@@ -0,0 +1,266 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * NXP XCVR ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright 2019 NXP
+ */
+
+#ifndef __FSL_XCVR_H
+#define __FSL_XCVR_H
+
+#define FSL_XCVR_MODE_SPDIF 0
+#define FSL_XCVR_MODE_ARC 1
+#define FSL_XCVR_MODE_EARC 2
+
+/* XCVR Registers */
+#define FSL_XCVR_REG_OFFSET 0x800 /* regs offset */
+#define FSL_XCVR_FIFO_SIZE 0x80 /* 128 */
+#define FSL_XCVR_FIFO_WMK_RX (FSL_XCVR_FIFO_SIZE >> 1) /* 64 */
+#define FSL_XCVR_FIFO_WMK_TX (FSL_XCVR_FIFO_SIZE >> 1) /* 64 */
+#define FSL_XCVR_MAXBURST_RX (FSL_XCVR_FIFO_WMK_RX >> 2) /* 16 */
+#define FSL_XCVR_MAXBURST_TX (FSL_XCVR_FIFO_WMK_TX >> 2) /* 16 */
+
+#define FSL_XCVR_RX_FIFO_ADDR 0x0C00
+#define FSL_XCVR_TX_FIFO_ADDR 0x0E00
+
+#define FSL_XCVR_VERSION 0x00 /* Version */
+#define FSL_XCVR_EXT_CTRL 0x10 /* Control */
+#define FSL_XCVR_EXT_STATUS 0x20 /* Status */
+#define FSL_XCVR_EXT_IER0 0x30 /* Interrupt en 0 */
+#define FSL_XCVR_EXT_IER1 0x40 /* Interrupt en 1 */
+#define FSL_XCVR_EXT_ISR 0x50 /* Interrupt status */
+#define FSL_XCVR_EXT_ISR_SET 0x54 /* Interrupt status */
+#define FSL_XCVR_EXT_ISR_CLR 0x58 /* Interrupt status */
+#define FSL_XCVR_EXT_ISR_TOG 0x5C /* Interrupt status */
+#define FSL_XCVR_IER 0x70 /* Interrupt en for M0+ */
+#define FSL_XCVR_ISR 0x80 /* Interrupt status */
+#define FSL_XCVR_ISR_SET 0x84 /* Interrupt status set */
+#define FSL_XCVR_ISR_CLR 0x88 /* Interrupt status clear */
+#define FSL_XCVR_ISR_TOG 0x8C /* Interrupt status toggle */
+#define FSL_XCVR_PHY_AI_CTRL 0x90
+#define FSL_XCVR_PHY_AI_CTRL_SET 0x94
+#define FSL_XCVR_PHY_AI_CTRL_CLR 0x98
+#define FSL_XCVR_PHY_AI_CTRL_TOG 0x9C
+#define FSL_XCVR_PHY_AI_WDATA 0xA0
+#define FSL_XCVR_PHY_AI_RDATA 0xA4
+#define FSL_XCVR_CLK_CTRL 0xB0
+#define FSL_XCVR_RX_DPTH_CTRL 0x180 /* RX datapath ctrl reg */
+#define FSL_XCVR_RX_DPTH_CTRL_SET 0x184
+#define FSL_XCVR_RX_DPTH_CTRL_CLR 0x188
+#define FSL_XCVR_RX_DPTH_CTRL_TOG 0x18c
+
+#define FSL_XCVR_TX_DPTH_CTRL 0x220 /* TX datapath ctrl reg */
+#define FSL_XCVR_TX_DPTH_CTRL_SET 0x224
+#define FSL_XCVR_TX_DPTH_CTRL_CLR 0x228
+#define FSL_XCVR_TX_DPTH_CTRL_TOG 0x22C
+#define FSL_XCVR_TX_CS_DATA_0 0x230 /* TX channel status bits regs */
+#define FSL_XCVR_TX_CS_DATA_1 0x234
+#define FSL_XCVR_TX_CS_DATA_2 0x238
+#define FSL_XCVR_TX_CS_DATA_3 0x23C
+#define FSL_XCVR_TX_CS_DATA_4 0x240
+#define FSL_XCVR_TX_CS_DATA_5 0x244
+#define FSL_XCVR_DEBUG_REG_0 0x2E0
+#define FSL_XCVR_DEBUG_REG_1 0x2F0
+
+#define FSL_XCVR_MAX_REG FSL_XCVR_DEBUG_REG_1
+
+#define FSL_XCVR_EXT_CTRL_CORE_RESET BIT(31)
+
+#define FSL_XCVR_EXT_CTRL_RX_CMDC_RESET BIT(30)
+#define FSL_XCVR_EXT_CTRL_TX_CMDC_RESET BIT(29)
+#define FSL_XCVR_EXT_CTRL_CMDC_RESET(t) (t ? BIT(29) : BIT(30))
+
+#define FSL_XCVR_EXT_CTRL_RX_DPTH_RESET BIT(28)
+#define FSL_XCVR_EXT_CTRL_TX_DPTH_RESET BIT(27)
+#define FSL_XCVR_EXT_CTRL_DPTH_RESET(t) (t ? BIT(27) : BIT(28))
+
+#define FSL_XCVR_EXT_CTRL_TX_RX_MODE BIT(26)
+#define FSL_XCVR_EXT_CTRL_DMA_RD_DIS BIT(25)
+#define FSL_XCVR_EXT_CTRL_DMA_WR_DIS BIT(24)
+#define FSL_XCVR_EXT_CTRL_DMA_DIS(t) (t ? BIT(24) : BIT(25))
+#define FSL_XCVR_EXT_CTRL_SPDIF_MODE BIT(23)
+#define FSL_XCVR_EXT_CTRL_SLEEP_MODE BIT(21)
+
+#define FSL_XCVR_EXT_CTRL_TX_FWM_SHFT 0
+#define FSL_XCVR_EXT_CTRL_TX_FWM_MASK GENMASK(6, 0)
+#define FSL_XCVR_EXT_CTRL_TX_FWM(i) (((i) << FSL_XCVR_EXT_CTRL_TX_FWM_SHFT) \
+ & FSL_XCVR_EXT_CTRL_TX_FWM_MASK)
+#define FSL_XCVR_EXT_CTRL_RX_FWM_SHFT 8
+#define FSL_XCVR_EXT_CTRL_RX_FWM_MASK GENMASK(14, 8)
+#define FSL_XCVR_EXT_CTRL_RX_FWM(i) (((i) << FSL_XCVR_EXT_CTRL_RX_FWM_SHFT) \
+ & FSL_XCVR_EXT_CTRL_RX_FWM_MASK)
+#define FSL_XCVR_EXT_CTRL_PAGE_SHFT 16
+#define FSL_XCVR_EXT_CTRL_PAGE_MASK GENMASK(19, 16)
+#define FSL_XCVR_EXT_CTRL_PAGE(i) (((i) << FSL_XCVR_EXT_CTRL_PAGE_SHFT) \
+ & FSL_XCVR_EXT_CTRL_PAGE_MASK)
+
+#define FSL_XCVR_EXT_STUS_NT_FIFO_ENTR GENMASK(7, 0)
+#define FSL_XCVR_EXT_STUS_NR_FIFO_ENTR GENMASK(15, 8)
+#define FSL_XCVR_EXT_STUS_CM0_SLEEPING BIT(16)
+#define FSL_XCVR_EXT_STUS_CM0_DEEP_SLP BIT(17)
+#define FSL_XCVR_EXT_STUS_CM0_SLP_HACK BIT(18)
+#define FSL_XCVR_EXT_STUS_RX_CMDC_RSTO BIT(23)
+#define FSL_XCVR_EXT_STUS_TX_CMDC_RSTO BIT(24)
+#define FSL_XCVR_EXT_STUS_RX_CMDC_COTO BIT(25)
+#define FSL_XCVR_EXT_STUS_TX_CMDC_COTO BIT(26)
+#define FSL_XCVR_EXT_STUS_HB_STATUS BIT(27)
+#define FSL_XCVR_EXT_STUS_NEW_UD4_REC BIT(28)
+#define FSL_XCVR_EXT_STUS_NEW_UD5_REC BIT(29)
+#define FSL_XCVR_EXT_STUS_NEW_UD6_REC BIT(30)
+#define FSL_XCVR_EXT_STUS_HPD_INPUT BIT(31)
+
+#define FSL_XCVR_IRQ_NEW_CS BIT(0)
+#define FSL_XCVR_IRQ_NEW_UD BIT(1)
+#define FSL_XCVR_IRQ_MUTE BIT(2)
+#define FSL_XCVR_IRQ_CMDC_RESP_TO BIT(3)
+#define FSL_XCVR_IRQ_ECC_ERR BIT(4)
+#define FSL_XCVR_IRQ_PREAMBLE_MISMATCH BIT(5)
+#define FSL_XCVR_IRQ_FIFO_UOFL_ERR BIT(6)
+#define FSL_XCVR_IRQ_HOST_WAKEUP BIT(7)
+#define FSL_XCVR_IRQ_HOST_OHPD BIT(8)
+#define FSL_XCVR_IRQ_DMAC_NO_DATA_REC BIT(9)
+#define FSL_XCVR_IRQ_DMAC_FMT_CHG_DET BIT(10)
+#define FSL_XCVR_IRQ_HB_STATE_CHG BIT(11)
+#define FSL_XCVR_IRQ_CMDC_STATUS_UPD BIT(12)
+#define FSL_XCVR_IRQ_TEMP_UPD BIT(13)
+#define FSL_XCVR_IRQ_DMA_RD_REQ BIT(14)
+#define FSL_XCVR_IRQ_DMA_WR_REQ BIT(15)
+#define FSL_XCVR_IRQ_DMAC_BME_BIT_ERR BIT(16)
+#define FSL_XCVR_IRQ_PREAMBLE_MATCH BIT(17)
+#define FSL_XCVR_IRQ_M_W_PRE_MISMATCH BIT(18)
+#define FSL_XCVR_IRQ_B_PRE_MISMATCH BIT(19)
+#define FSL_XCVR_IRQ_UNEXP_PRE_REC BIT(20)
+#define FSL_XCVR_IRQ_ARC_MODE BIT(21)
+#define FSL_XCVR_IRQ_CH_UD_OFLOW BIT(22)
+#define FSL_XCVR_IRQ_EARC_ALL (FSL_XCVR_IRQ_NEW_CS | \
+ FSL_XCVR_IRQ_NEW_UD | \
+ FSL_XCVR_IRQ_MUTE | \
+ FSL_XCVR_IRQ_FIFO_UOFL_ERR | \
+ FSL_XCVR_IRQ_HOST_WAKEUP | \
+ FSL_XCVR_IRQ_ARC_MODE)
+
+#define FSL_XCVR_ISR_CMDC_TX_EN BIT(3)
+#define FSL_XCVR_ISR_HPD_TGL BIT(15)
+#define FSL_XCVR_ISR_DMAC_SPARE_INT BIT(19)
+#define FSL_XCVR_ISR_SET_SPDIF_RX_INT BIT(20)
+#define FSL_XCVR_ISR_SET_SPDIF_TX_INT BIT(21)
+#define FSL_XCVR_ISR_SET_SPDIF_MODE(t) (t ? BIT(21) : BIT(20))
+#define FSL_XCVR_ISR_SET_ARC_CM_INT BIT(22)
+#define FSL_XCVR_ISR_SET_ARC_SE_INT BIT(23)
+
+#define FSL_XCVR_PHY_AI_ADDR_MASK GENMASK(7, 0)
+#define FSL_XCVR_PHY_AI_RESETN BIT(15)
+#define FSL_XCVR_PHY_AI_TOG_PLL BIT(24)
+#define FSL_XCVR_PHY_AI_TOG_DONE_PLL BIT(25)
+#define FSL_XCVR_PHY_AI_TOG_PHY BIT(26)
+#define FSL_XCVR_PHY_AI_TOG_DONE_PHY BIT(27)
+#define FSL_XCVR_PHY_AI_RW_MASK BIT(31)
+
+#define FSL_XCVR_RX_DPTH_CTRL_PAPB_FIFO_STATUS BIT(0)
+#define FSL_XCVR_RX_DPTH_CTRL_DIS_PRE_ERR_CHK BIT(1)
+#define FSL_XCVR_RX_DPTH_CTRL_DIS_NOD_REC_CHK BIT(2)
+#define FSL_XCVR_RX_DPTH_CTRL_ECC_VUC_BIT_CHK BIT(3)
+#define FSL_XCVR_RX_DPTH_CTRL_EN_CMP_PAR_CALC BIT(4)
+#define FSL_XCVR_RX_DPTH_CTRL_RST_PKT_CNT_FIFO BIT(5)
+#define FSL_XCVR_RX_DPTH_CTRL_STORE_FMT BIT(6)
+#define FSL_XCVR_RX_DPTH_CTRL_EN_PAR_CALC BIT(7)
+#define FSL_XCVR_RX_DPTH_CTRL_UDR BIT(8)
+#define FSL_XCVR_RX_DPTH_CTRL_CSR BIT(9)
+#define FSL_XCVR_RX_DPTH_CTRL_UDA BIT(10)
+#define FSL_XCVR_RX_DPTH_CTRL_CSA BIT(11)
+#define FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO BIT(12)
+#define FSL_XCVR_RX_DPTH_CTRL_DIS_B_PRE_ERR_CHK BIT(13)
+#define FSL_XCVR_RX_DPTH_CTRL_PABS BIT(19)
+#define FSL_XCVR_RX_DPTH_CTRL_DTS_CDS BIT(20)
+#define FSL_XCVR_RX_DPTH_CTRL_BLKC BIT(21)
+#define FSL_XCVR_RX_DPTH_CTRL_MUTE_CTRL BIT(22)
+#define FSL_XCVR_RX_DPTH_CTRL_MUTE_MODE BIT(23)
+#define FSL_XCVR_RX_DPTH_CTRL_FMT_CHG_CTRL BIT(24)
+#define FSL_XCVR_RX_DPTH_CTRL_FMT_CHG_MODE BIT(25)
+#define FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL BIT(26)
+#define FSL_XCVR_RX_DPTH_CTRL_LAYB_MODE BIT(27)
+#define FSL_XCVR_RX_DPTH_CTRL_PRC BIT(28)
+#define FSL_XCVR_RX_DPTH_CTRL_COMP BIT(29)
+#define FSL_XCVR_RX_DPTH_CTRL_FSM GENMASK(31, 30)
+
+#define FSL_XCVR_TX_DPTH_CTRL_CS_ACK BIT(0)
+#define FSL_XCVR_TX_DPTH_CTRL_UD_ACK BIT(1)
+#define FSL_XCVR_TX_DPTH_CTRL_CS_MOD BIT(2)
+#define FSL_XCVR_TX_DPTH_CTRL_UD_MOD BIT(3)
+#define FSL_XCVR_TX_DPTH_CTRL_VLD_MOD BIT(4)
+#define FSL_XCVR_TX_DPTH_CTRL_FRM_VLD BIT(5)
+#define FSL_XCVR_TX_DPTH_CTRL_EN_PARITY BIT(6)
+#define FSL_XCVR_TX_DPTH_CTRL_EN_PREAMBLE BIT(7)
+#define FSL_XCVR_TX_DPTH_CTRL_EN_ECC_INTER BIT(8)
+#define FSL_XCVR_TX_DPTH_CTRL_BYPASS_FEM BIT(10)
+#define FSL_XCVR_TX_DPTH_CTRL_FRM_FMT BIT(11)
+#define FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX BIT(14)
+#define FSL_XCVR_TX_DPTH_CTRL_ADD_CYC_TX_OE_STR BIT(15)
+#define FSL_XCVR_TX_DPTH_CTRL_ADD_CYC_TX_OE_END BIT(16)
+#define FSL_XCVR_TX_DPTH_CTRL_CLK_RATIO BIT(29)
+#define FSL_XCVR_TX_DPTH_CTRL_TM_NO_PRE_BME GENMASK(31, 30)
+
+#define FSL_XCVR_PHY_AI_CTRL_AI_RESETN BIT(15)
+
+#define FSL_XCVR_PLL_CTRL0 0x00
+#define FSL_XCVR_PLL_CTRL0_SET 0x04
+#define FSL_XCVR_PLL_CTRL0_CLR 0x08
+#define FSL_XCVR_PLL_NUM 0x20
+#define FSL_XCVR_PLL_DEN 0x30
+#define FSL_XCVR_PLL_PDIV 0x40
+#define FSL_XCVR_PLL_BANDGAP_SET 0x54
+#define FSL_XCVR_PHY_CTRL 0x00
+#define FSL_XCVR_PHY_CTRL_SET 0x04
+#define FSL_XCVR_PHY_CTRL_CLR 0x08
+#define FSL_XCVR_PHY_CTRL2 0x70
+#define FSL_XCVR_PHY_CTRL2_SET 0x74
+#define FSL_XCVR_PHY_CTRL2_CLR 0x78
+
+#define FSL_XCVR_PLL_BANDGAP_EN_VBG BIT(0)
+#define FSL_XCVR_PLL_CTRL0_HROFF BIT(13)
+#define FSL_XCVR_PLL_CTRL0_PWP BIT(14)
+#define FSL_XCVR_PLL_CTRL0_CM0_EN BIT(24)
+#define FSL_XCVR_PLL_CTRL0_CM1_EN BIT(25)
+#define FSL_XCVR_PLL_CTRL0_CM2_EN BIT(26)
+#define FSL_XCVR_PLL_PDIVx(v, i) ((v & 0x7) << (4 * i))
+
+#define FSL_XCVR_PHY_CTRL_PHY_EN BIT(0)
+#define FSL_XCVR_PHY_CTRL_RX_CM_EN BIT(1)
+#define FSL_XCVR_PHY_CTRL_TSDIFF_OE BIT(5)
+#define FSL_XCVR_PHY_CTRL_SPDIF_EN BIT(8)
+#define FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN BIT(9)
+#define FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN BIT(10)
+#define FSL_XCVR_PHY_CTRL_TX_CLK_MASK GENMASK(26, 25)
+#define FSL_XCVR_PHY_CTRL_TX_CLK_HDMI_SS BIT(25)
+#define FSL_XCVR_PHY_CTRL_TX_CLK_AUD_SS BIT(26)
+#define FSL_XCVR_PHY_CTRL2_EARC_TXMS BIT(14)
+
+#define FSL_XCVR_CS_DATA_0_FS_MASK GENMASK(31, 24)
+#define FSL_XCVR_CS_DATA_0_FS_32000 0x3000000
+#define FSL_XCVR_CS_DATA_0_FS_44100 0x0000000
+#define FSL_XCVR_CS_DATA_0_FS_48000 0x2000000
+#define FSL_XCVR_CS_DATA_0_FS_64000 0xB000000
+#define FSL_XCVR_CS_DATA_0_FS_88200 0x8000000
+#define FSL_XCVR_CS_DATA_0_FS_96000 0xA000000
+#define FSL_XCVR_CS_DATA_0_FS_176400 0xC000000
+#define FSL_XCVR_CS_DATA_0_FS_192000 0xE000000
+
+#define FSL_XCVR_CS_DATA_0_CH_MASK 0x3A
+#define FSL_XCVR_CS_DATA_0_CH_U2LPCM 0x00
+#define FSL_XCVR_CS_DATA_0_CH_UMLPCM 0x20
+#define FSL_XCVR_CS_DATA_0_CH_U1BAUD 0x30
+
+#define FSL_XCVR_CS_DATA_1_CH_MASK 0xF000
+#define FSL_XCVR_CS_DATA_1_CH_2 0x0000
+#define FSL_XCVR_CS_DATA_1_CH_8 0x7000
+#define FSL_XCVR_CS_DATA_1_CH_16 0xB000
+#define FSL_XCVR_CS_DATA_1_CH_32 0x3000
+
+/* Data memory structures */
+#define FSL_XCVR_RX_CS_CTRL_0 0x20 /* First RX CS control register */
+#define FSL_XCVR_RX_CS_CTRL_1 0x24 /* Second RX CS control register */
+#define FSL_XCVR_RX_CS_BUFF_0 0x80 /* First RX CS buffer */
+#define FSL_XCVR_RX_CS_BUFF_1 0xA0 /* Second RX CS buffer */
+#define FSL_XCVR_CAP_DATA_STR 0x300 /* Capabilities data structure */
+
+#endif /* __FSL_XCVR_H */
diff --git a/sound/soc/fsl/imx-audio-rpmsg.c b/sound/soc/fsl/imx-audio-rpmsg.c
new file mode 100644
index 000000000..905c3a071
--- /dev/null
+++ b/sound/soc/fsl/imx-audio-rpmsg.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2017-2020 NXP
+
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include "imx-pcm-rpmsg.h"
+
+/*
+ * struct imx_audio_rpmsg: private data
+ *
+ * @rpmsg_pdev: pointer of platform device
+ */
+struct imx_audio_rpmsg {
+ struct platform_device *rpmsg_pdev;
+};
+
+static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ struct imx_audio_rpmsg *rpmsg = dev_get_drvdata(&rpdev->dev);
+ struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data;
+ struct rpmsg_info *info;
+ struct rpmsg_msg *msg;
+ unsigned long flags;
+
+ if (!rpmsg->rpmsg_pdev)
+ return 0;
+
+ info = platform_get_drvdata(rpmsg->rpmsg_pdev);
+
+ dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n",
+ src, r_msg->header.cmd, r_msg->param.resp);
+
+ switch (r_msg->header.type) {
+ case MSG_TYPE_C:
+ /* TYPE C is notification from M core */
+ switch (r_msg->header.cmd) {
+ case TX_PERIOD_DONE:
+ spin_lock_irqsave(&info->lock[TX], flags);
+ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->r_msg.param.buffer_tail =
+ r_msg->param.buffer_tail;
+ msg->r_msg.param.buffer_tail %= info->num_period[TX];
+ spin_unlock_irqrestore(&info->lock[TX], flags);
+ info->callback[TX](info->callback_param[TX]);
+ break;
+ case RX_PERIOD_DONE:
+ spin_lock_irqsave(&info->lock[RX], flags);
+ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->r_msg.param.buffer_tail =
+ r_msg->param.buffer_tail;
+ msg->r_msg.param.buffer_tail %= info->num_period[1];
+ spin_unlock_irqrestore(&info->lock[RX], flags);
+ info->callback[RX](info->callback_param[RX]);
+ break;
+ default:
+ dev_warn(&rpdev->dev, "unknown msg command\n");
+ break;
+ }
+ break;
+ case MSG_TYPE_B:
+ /* TYPE B is response msg */
+ memcpy(&info->r_msg, r_msg, sizeof(struct rpmsg_r_msg));
+ complete(&info->cmd_complete);
+ break;
+ default:
+ dev_warn(&rpdev->dev, "unknown msg type\n");
+ break;
+ }
+
+ return 0;
+}
+
+static int imx_audio_rpmsg_probe(struct rpmsg_device *rpdev)
+{
+ struct imx_audio_rpmsg *data;
+ int ret = 0;
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
+ rpdev->src, rpdev->dst);
+
+ data = devm_kzalloc(&rpdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ dev_set_drvdata(&rpdev->dev, data);
+
+ /* Register platform driver for rpmsg routine */
+ data->rpmsg_pdev = platform_device_register_data(&rpdev->dev,
+ IMX_PCM_DRV_NAME,
+ PLATFORM_DEVID_NONE,
+ NULL, 0);
+ if (IS_ERR(data->rpmsg_pdev)) {
+ dev_err(&rpdev->dev, "failed to register rpmsg platform.\n");
+ ret = PTR_ERR(data->rpmsg_pdev);
+ }
+
+ return ret;
+}
+
+static void imx_audio_rpmsg_remove(struct rpmsg_device *rpdev)
+{
+ struct imx_audio_rpmsg *data = dev_get_drvdata(&rpdev->dev);
+
+ if (data->rpmsg_pdev)
+ platform_device_unregister(data->rpmsg_pdev);
+
+ dev_info(&rpdev->dev, "audio rpmsg driver is removed\n");
+}
+
+static struct rpmsg_device_id imx_audio_rpmsg_id_table[] = {
+ { .name = "rpmsg-audio-channel" },
+ { },
+};
+
+static struct rpmsg_driver imx_audio_rpmsg_driver = {
+ .drv.name = "imx_audio_rpmsg",
+ .drv.owner = THIS_MODULE,
+ .id_table = imx_audio_rpmsg_id_table,
+ .probe = imx_audio_rpmsg_probe,
+ .callback = imx_audio_rpmsg_cb,
+ .remove = imx_audio_rpmsg_remove,
+};
+
+module_rpmsg_driver(imx_audio_rpmsg_driver);
+
+MODULE_DESCRIPTION("Freescale SoC Audio RPMSG interface");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_ALIAS("platform:imx_audio_rpmsg");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/imx-audmix.c b/sound/soc/fsl/imx-audmix.c
new file mode 100644
index 000000000..cbe24d5b4
--- /dev/null
+++ b/sound/soc/fsl/imx-audmix.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2017 NXP
+ *
+ * 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:
+ *
+ * https://www.opensource.org/licenses/gpl-license.html
+ * https://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/pm_runtime.h>
+#include "fsl_sai.h"
+#include "fsl_audmix.h"
+
+struct imx_audmix {
+ struct platform_device *pdev;
+ struct snd_soc_card card;
+ struct platform_device *audmix_pdev;
+ struct platform_device *out_pdev;
+ struct clk *cpu_mclk;
+ int num_dai;
+ struct snd_soc_dai_link *dai;
+ int num_dai_conf;
+ struct snd_soc_codec_conf *dai_conf;
+ int num_dapm_routes;
+ struct snd_soc_dapm_route *dapm_routes;
+};
+
+static const u32 imx_audmix_rates[] = {
+ 8000, 12000, 16000, 24000, 32000, 48000, 64000, 96000,
+};
+
+static const struct snd_pcm_hw_constraint_list imx_audmix_rate_constraints = {
+ .count = ARRAY_SIZE(imx_audmix_rates),
+ .list = imx_audmix_rates,
+};
+
+static int imx_audmix_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct imx_audmix *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct device *dev = rtd->card->dev;
+ unsigned long clk_rate = clk_get_rate(priv->cpu_mclk);
+ int ret;
+
+ if (clk_rate % 24576000 == 0) {
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &imx_audmix_rate_constraints);
+ if (ret < 0)
+ return ret;
+ } else {
+ dev_warn(dev, "mclk may be not supported %lu\n", clk_rate);
+ }
+
+ ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
+ 1, 8);
+ if (ret < 0)
+ return ret;
+
+ return snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT,
+ FSL_AUDMIX_FORMATS);
+}
+
+static int imx_audmix_fe_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct device *dev = rtd->card->dev;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF;
+ u32 channels = params_channels(params);
+ int ret, dir;
+
+ /* For playback the AUDMIX is consumer, and for record is provider */
+ fmt |= tx ? SND_SOC_DAIFMT_BP_FP : SND_SOC_DAIFMT_BC_FC;
+ dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN;
+
+ /* set DAI configuration */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(rtd, 0), FSL_SAI_CLK_MAST1, 0, dir);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Per datasheet, AUDMIX expects 8 slots and 32 bits
+ * for every slot in TDM mode.
+ */
+ ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), BIT(channels) - 1,
+ BIT(channels) - 1, 8, 32);
+ if (ret)
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+
+ return ret;
+}
+
+static int imx_audmix_be_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct device *dev = rtd->card->dev;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF;
+ int ret;
+
+ if (!tx)
+ return 0;
+
+ /* For playback the AUDMIX is consumer */
+ fmt |= SND_SOC_DAIFMT_BC_FC;
+
+ /* set AUDMIX DAI configuration */
+ ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0), fmt);
+ if (ret)
+ dev_err(dev, "failed to set AUDMIX DAI fmt: %d\n", ret);
+
+ return ret;
+}
+
+static const struct snd_soc_ops imx_audmix_fe_ops = {
+ .startup = imx_audmix_fe_startup,
+ .hw_params = imx_audmix_fe_hw_params,
+};
+
+static const struct snd_soc_ops imx_audmix_be_ops = {
+ .hw_params = imx_audmix_be_hw_params,
+};
+
+static int imx_audmix_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *audmix_np = NULL, *out_cpu_np = NULL;
+ struct platform_device *audmix_pdev = NULL;
+ struct platform_device *cpu_pdev;
+ struct of_phandle_args args;
+ struct imx_audmix *priv;
+ int i, num_dai, ret;
+ const char *fe_name_pref = "HiFi-AUDMIX-FE-";
+ char *be_name, *be_pb, *be_cp, *dai_name, *capture_dai_name;
+
+ if (pdev->dev.parent) {
+ audmix_np = pdev->dev.parent->of_node;
+ } else {
+ dev_err(&pdev->dev, "Missing parent device.\n");
+ return -EINVAL;
+ }
+
+ if (!audmix_np) {
+ dev_err(&pdev->dev, "Missing DT node for parent device.\n");
+ return -EINVAL;
+ }
+
+ audmix_pdev = of_find_device_by_node(audmix_np);
+ if (!audmix_pdev) {
+ dev_err(&pdev->dev, "Missing AUDMIX platform device for %s\n",
+ np->full_name);
+ return -EINVAL;
+ }
+ put_device(&audmix_pdev->dev);
+
+ num_dai = of_count_phandle_with_args(audmix_np, "dais", NULL);
+ if (num_dai != FSL_AUDMIX_MAX_DAIS) {
+ dev_err(&pdev->dev, "Need 2 dais to be provided for %s\n",
+ audmix_np->full_name);
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->num_dai = 2 * num_dai;
+ priv->dai = devm_kcalloc(&pdev->dev, priv->num_dai,
+ sizeof(struct snd_soc_dai_link), GFP_KERNEL);
+ if (!priv->dai)
+ return -ENOMEM;
+
+ priv->num_dai_conf = num_dai;
+ priv->dai_conf = devm_kcalloc(&pdev->dev, priv->num_dai_conf,
+ sizeof(struct snd_soc_codec_conf),
+ GFP_KERNEL);
+ if (!priv->dai_conf)
+ return -ENOMEM;
+
+ priv->num_dapm_routes = 3 * num_dai;
+ priv->dapm_routes = devm_kcalloc(&pdev->dev, priv->num_dapm_routes,
+ sizeof(struct snd_soc_dapm_route),
+ GFP_KERNEL);
+ if (!priv->dapm_routes)
+ return -ENOMEM;
+
+ for (i = 0; i < num_dai; i++) {
+ struct snd_soc_dai_link_component *dlc;
+
+ /* for CPU/Codec/Platform x 2 */
+ dlc = devm_kcalloc(&pdev->dev, 6, sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ ret = of_parse_phandle_with_args(audmix_np, "dais", NULL, i,
+ &args);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "of_parse_phandle_with_args failed\n");
+ return ret;
+ }
+
+ cpu_pdev = of_find_device_by_node(args.np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ return -EINVAL;
+ }
+ put_device(&cpu_pdev->dev);
+
+ dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%s",
+ fe_name_pref, args.np->full_name + 1);
+ if (!dai_name)
+ return -ENOMEM;
+
+ dev_info(pdev->dev.parent, "DAI FE name:%s\n", dai_name);
+
+ if (i == 0) {
+ out_cpu_np = args.np;
+ capture_dai_name =
+ devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s",
+ dai_name, "CPU-Capture");
+ if (!capture_dai_name)
+ return -ENOMEM;
+ }
+
+ priv->dai[i].cpus = &dlc[0];
+ priv->dai[i].codecs = &dlc[1];
+ priv->dai[i].platforms = &dlc[2];
+
+ priv->dai[i].num_cpus = 1;
+ priv->dai[i].num_codecs = 1;
+ priv->dai[i].num_platforms = 1;
+
+ priv->dai[i].name = dai_name;
+ priv->dai[i].stream_name = "HiFi-AUDMIX-FE";
+ priv->dai[i].codecs->dai_name = "snd-soc-dummy-dai";
+ priv->dai[i].codecs->name = "snd-soc-dummy";
+ priv->dai[i].cpus->of_node = args.np;
+ priv->dai[i].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ priv->dai[i].platforms->of_node = args.np;
+ priv->dai[i].dynamic = 1;
+ priv->dai[i].dpcm_playback = 1;
+ priv->dai[i].dpcm_capture = (i == 0 ? 1 : 0);
+ priv->dai[i].ignore_pmdown_time = 1;
+ priv->dai[i].ops = &imx_audmix_fe_ops;
+
+ /* Add AUDMIX Backend */
+ be_name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "audmix-%d", i);
+ be_pb = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "AUDMIX-Playback-%d", i);
+ be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "AUDMIX-Capture-%d", i);
+ if (!be_name || !be_pb || !be_cp)
+ return -ENOMEM;
+
+ priv->dai[num_dai + i].cpus = &dlc[3];
+ priv->dai[num_dai + i].codecs = &dlc[4];
+ priv->dai[num_dai + i].platforms = &dlc[5];
+
+ priv->dai[num_dai + i].num_cpus = 1;
+ priv->dai[num_dai + i].num_codecs = 1;
+ priv->dai[num_dai + i].num_platforms = 1;
+
+ priv->dai[num_dai + i].name = be_name;
+ priv->dai[num_dai + i].codecs->dai_name = "snd-soc-dummy-dai";
+ priv->dai[num_dai + i].codecs->name = "snd-soc-dummy";
+ priv->dai[num_dai + i].cpus->of_node = audmix_np;
+ priv->dai[num_dai + i].cpus->dai_name = be_name;
+ priv->dai[num_dai + i].platforms->name = "snd-soc-dummy";
+ priv->dai[num_dai + i].no_pcm = 1;
+ priv->dai[num_dai + i].dpcm_playback = 1;
+ priv->dai[num_dai + i].dpcm_capture = 1;
+ priv->dai[num_dai + i].ignore_pmdown_time = 1;
+ priv->dai[num_dai + i].ops = &imx_audmix_be_ops;
+
+ priv->dai_conf[i].dlc.of_node = args.np;
+ priv->dai_conf[i].name_prefix = dai_name;
+
+ priv->dapm_routes[i].source =
+ devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s",
+ dai_name, "CPU-Playback");
+ if (!priv->dapm_routes[i].source)
+ return -ENOMEM;
+
+ priv->dapm_routes[i].sink = be_pb;
+ priv->dapm_routes[num_dai + i].source = be_pb;
+ priv->dapm_routes[num_dai + i].sink = be_cp;
+ priv->dapm_routes[2 * num_dai + i].source = be_cp;
+ priv->dapm_routes[2 * num_dai + i].sink = capture_dai_name;
+ }
+
+ cpu_pdev = of_find_device_by_node(out_cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ return -EINVAL;
+ }
+ put_device(&cpu_pdev->dev);
+
+ priv->cpu_mclk = devm_clk_get(&cpu_pdev->dev, "mclk1");
+ if (IS_ERR(priv->cpu_mclk)) {
+ ret = PTR_ERR(priv->cpu_mclk);
+ dev_err(&cpu_pdev->dev, "failed to get DAI mclk1: %d\n", ret);
+ return ret;
+ }
+
+ priv->audmix_pdev = audmix_pdev;
+ priv->out_pdev = cpu_pdev;
+
+ priv->card.dai_link = priv->dai;
+ priv->card.num_links = priv->num_dai;
+ priv->card.codec_conf = priv->dai_conf;
+ priv->card.num_configs = priv->num_dai_conf;
+ priv->card.dapm_routes = priv->dapm_routes;
+ priv->card.num_dapm_routes = priv->num_dapm_routes;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ priv->card.name = "imx-audmix";
+
+ platform_set_drvdata(pdev, &priv->card);
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct platform_driver imx_audmix_driver = {
+ .probe = imx_audmix_probe,
+ .driver = {
+ .name = "imx-audmix",
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(imx_audmix_driver);
+
+MODULE_DESCRIPTION("NXP AUDMIX ASoC machine driver");
+MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>");
+MODULE_ALIAS("platform:imx-audmix");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c
new file mode 100644
index 000000000..50b71e5d4
--- /dev/null
+++ b/sound/soc/fsl/imx-audmux.c
@@ -0,0 +1,386 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// 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, https://www.phytec.de
+
+#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;
+static u32 *regcache;
+static u32 reg_max;
+
+#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;
+
+ 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));
+
+ clk_disable_unprepare(audmux_clk);
+
+ buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = scnprintf(buf, PAGE_SIZE, "PDCR: %08x\nPTCR: %08x\n",
+ pdcr, ptcr);
+
+ if (ptcr & IMX_AUDMUX_V2_PTCR_TFSDIR)
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "TxFS output from %s, ",
+ audmux_port_string((ptcr >> 27) & 0x7));
+ else
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "TxFS input, ");
+
+ if (ptcr & IMX_AUDMUX_V2_PTCR_TCLKDIR)
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "TxClk output from %s",
+ audmux_port_string((ptcr >> 22) & 0x7));
+ else
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "TxClk input");
+
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n");
+
+ if (ptcr & IMX_AUDMUX_V2_PTCR_SYN) {
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "Port is symmetric");
+ } else {
+ if (ptcr & IMX_AUDMUX_V2_PTCR_RFSDIR)
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "RxFS output from %s, ",
+ audmux_port_string((ptcr >> 17) & 0x7));
+ else
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "RxFS input, ");
+
+ if (ptcr & IMX_AUDMUX_V2_PTCR_RCLKDIR)
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "RxClk output from %s",
+ audmux_port_string((ptcr >> 12) & 0x7));
+ else
+ ret += scnprintf(buf + ret, PAGE_SIZE - ret,
+ "RxClk input");
+ }
+
+ ret += scnprintf(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);
+
+ for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) {
+ snprintf(buf, sizeof(buf), "ssi%lu", i);
+ debugfs_create_file(buf, 0444, audmux_debugfs_root,
+ (void *)i, &audmux_debugfs_fops);
+ }
+}
+
+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 of_device_id imx_audmux_dt_ids[] = {
+ { .compatible = "fsl,imx21-audmux", .data = (void *)IMX21_AUDMUX, },
+ { .compatible = "fsl,imx31-audmux", .data = (void *)IMX31_AUDMUX, },
+ { /* 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;
+
+ 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));
+
+ 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)
+{
+ audmux_base = devm_platform_ioremap_resource(pdev, 0);
+ 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;
+ }
+
+ audmux_type = (uintptr_t)of_device_get_match_data(&pdev->dev);
+
+ switch (audmux_type) {
+ case IMX31_AUDMUX:
+ audmux_debugfs_init();
+ reg_max = 14;
+ break;
+ case IMX21_AUDMUX:
+ reg_max = 6;
+ break;
+ default:
+ dev_err(&pdev->dev, "unsupported version!\n");
+ return -EINVAL;
+ }
+
+ regcache = devm_kzalloc(&pdev->dev, sizeof(u32) * reg_max, GFP_KERNEL);
+ if (!regcache)
+ return -ENOMEM;
+
+ 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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx_audmux_suspend(struct device *dev)
+{
+ int i;
+
+ clk_prepare_enable(audmux_clk);
+
+ for (i = 0; i < reg_max; i++)
+ regcache[i] = readl(audmux_base + i * 4);
+
+ clk_disable_unprepare(audmux_clk);
+
+ return 0;
+}
+
+static int imx_audmux_resume(struct device *dev)
+{
+ int i;
+
+ clk_prepare_enable(audmux_clk);
+
+ for (i = 0; i < reg_max; i++)
+ writel(regcache[i], audmux_base + i * 4);
+
+ clk_disable_unprepare(audmux_clk);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops imx_audmux_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(imx_audmux_suspend, imx_audmux_resume)
+};
+
+static struct platform_driver imx_audmux_driver = {
+ .probe = imx_audmux_probe,
+ .remove = imx_audmux_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .pm = &imx_audmux_pm,
+ .of_match_table = imx_audmux_dt_ids,
+ }
+};
+
+static int __init imx_audmux_init(void)
+{
+ return platform_driver_register(&imx_audmux_driver);
+}
+subsys_initcall(imx_audmux_init);
+
+static void __exit imx_audmux_exit(void)
+{
+ platform_driver_unregister(&imx_audmux_driver);
+}
+module_exit(imx_audmux_exit);
+
+MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/sound/soc/fsl/imx-audmux.h b/sound/soc/fsl/imx-audmux.h
new file mode 100644
index 000000000..f75b4d3ae
--- /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-card.c b/sound/soc/fsl/imx-card.c
new file mode 100644
index 000000000..64a4d7e9d
--- /dev/null
+++ b/sound/soc/fsl/imx-card.c
@@ -0,0 +1,869 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2017-2021 NXP
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm.h>
+#include <sound/soc-dapm.h>
+#include <sound/simple_card_utils.h>
+
+#include "fsl_sai.h"
+
+#define IMX_CARD_MCLK_22P5792MHZ 22579200
+#define IMX_CARD_MCLK_24P576MHZ 24576000
+
+enum codec_type {
+ CODEC_DUMMY = 0,
+ CODEC_AK5558 = 1,
+ CODEC_AK4458,
+ CODEC_AK4497,
+ CODEC_AK5552,
+};
+
+/*
+ * Mapping LRCK fs and frame width, table 3 & 4 in datasheet
+ * @rmin: min rate
+ * @rmax: max rate
+ * @wmin: min frame ratio
+ * @wmax: max frame ratio
+ */
+struct imx_akcodec_fs_mul {
+ unsigned int rmin;
+ unsigned int rmax;
+ unsigned int wmin;
+ unsigned int wmax;
+};
+
+/*
+ * Mapping TDM mode and frame width
+ */
+struct imx_akcodec_tdm_fs_mul {
+ unsigned int min;
+ unsigned int max;
+ unsigned int mul;
+};
+
+/*
+ * struct imx_card_plat_data - specific info for codecs
+ *
+ * @fs_mul: ratio of mclk/fs for normal mode
+ * @tdm_fs_mul: ratio of mclk/fs for tdm mode
+ * @support_rates: supported sample rate
+ * @support_tdm_rates: supported sample rate for tdm mode
+ * @support_channels: supported channels
+ * @support_tdm_channels: supported channels for tdm mode
+ * @num_fs_mul: ARRAY_SIZE of fs_mul
+ * @num_tdm_fs_mul: ARRAY_SIZE of tdm_fs_mul
+ * @num_rates: ARRAY_SIZE of support_rates
+ * @num_tdm_rates: ARRAY_SIZE of support_tdm_rates
+ * @num_channels: ARRAY_SIZE of support_channels
+ * @num_tdm_channels: ARRAY_SIZE of support_tdm_channels
+ * @type: codec type
+ */
+struct imx_card_plat_data {
+ struct imx_akcodec_fs_mul *fs_mul;
+ struct imx_akcodec_tdm_fs_mul *tdm_fs_mul;
+ const u32 *support_rates;
+ const u32 *support_tdm_rates;
+ const u32 *support_channels;
+ const u32 *support_tdm_channels;
+ unsigned int num_fs_mul;
+ unsigned int num_tdm_fs_mul;
+ unsigned int num_rates;
+ unsigned int num_tdm_rates;
+ unsigned int num_channels;
+ unsigned int num_tdm_channels;
+ unsigned int num_codecs;
+ enum codec_type type;
+};
+
+/*
+ * struct dai_link_data - specific info for dai link
+ *
+ * @slots: slot number
+ * @slot_width: slot width value
+ * @cpu_sysclk_id: sysclk id for cpu dai
+ * @one2one_ratio: true if mclk equal to bclk
+ */
+struct dai_link_data {
+ unsigned int slots;
+ unsigned int slot_width;
+ unsigned int cpu_sysclk_id;
+ bool one2one_ratio;
+};
+
+/*
+ * struct imx_card_data - platform device data
+ *
+ * @plat_data: pointer of imx_card_plat_data
+ * @dapm_routes: pointer of dapm_routes
+ * @link_data: private data for dai link
+ * @card: card instance
+ * @num_dapm_routes: number of dapm_routes
+ * @asrc_rate: asrc rates
+ * @asrc_format: asrc format
+ */
+struct imx_card_data {
+ struct imx_card_plat_data *plat_data;
+ struct snd_soc_dapm_route *dapm_routes;
+ struct dai_link_data *link_data;
+ struct snd_soc_card card;
+ int num_dapm_routes;
+ u32 asrc_rate;
+ snd_pcm_format_t asrc_format;
+};
+
+static struct imx_akcodec_fs_mul ak4458_fs_mul[] = {
+ /* Normal, < 32kHz */
+ { .rmin = 8000, .rmax = 24000, .wmin = 256, .wmax = 1024, },
+ /* Normal, 32kHz */
+ { .rmin = 32000, .rmax = 32000, .wmin = 256, .wmax = 1024, },
+ /* Normal */
+ { .rmin = 44100, .rmax = 48000, .wmin = 256, .wmax = 768, },
+ /* Double */
+ { .rmin = 88200, .rmax = 96000, .wmin = 256, .wmax = 512, },
+ /* Quad */
+ { .rmin = 176400, .rmax = 192000, .wmin = 128, .wmax = 256, },
+ /* Oct */
+ { .rmin = 352800, .rmax = 384000, .wmin = 32, .wmax = 128, },
+ /* Hex */
+ { .rmin = 705600, .rmax = 768000, .wmin = 16, .wmax = 64, },
+};
+
+static struct imx_akcodec_tdm_fs_mul ak4458_tdm_fs_mul[] = {
+ /*
+ * Table 13 - Audio Interface Format
+ * For TDM mode, MCLK should is set to
+ * obtained from 2 * slots * slot_width
+ */
+ { .min = 128, .max = 128, .mul = 256 }, /* TDM128 */
+ { .min = 256, .max = 256, .mul = 512 }, /* TDM256 */
+ { .min = 512, .max = 512, .mul = 1024 }, /* TDM512 */
+};
+
+static struct imx_akcodec_fs_mul ak4497_fs_mul[] = {
+ /**
+ * Table 7 - mapping multiplier and speed mode
+ * Tables 8 & 9 - mapping speed mode and LRCK fs
+ */
+ { .rmin = 8000, .rmax = 32000, .wmin = 256, .wmax = 1024, }, /* Normal, <= 32kHz */
+ { .rmin = 44100, .rmax = 48000, .wmin = 256, .wmax = 512, }, /* Normal */
+ { .rmin = 88200, .rmax = 96000, .wmin = 256, .wmax = 256, }, /* Double */
+ { .rmin = 176400, .rmax = 192000, .wmin = 128, .wmax = 128, }, /* Quad */
+ { .rmin = 352800, .rmax = 384000, .wmin = 128, .wmax = 128, }, /* Oct */
+ { .rmin = 705600, .rmax = 768000, .wmin = 64, .wmax = 64, }, /* Hex */
+};
+
+/*
+ * Auto MCLK selection based on LRCK for Normal Mode
+ * (Table 4 from datasheet)
+ */
+static struct imx_akcodec_fs_mul ak5558_fs_mul[] = {
+ { .rmin = 8000, .rmax = 32000, .wmin = 512, .wmax = 1024, },
+ { .rmin = 44100, .rmax = 48000, .wmin = 512, .wmax = 512, },
+ { .rmin = 88200, .rmax = 96000, .wmin = 256, .wmax = 256, },
+ { .rmin = 176400, .rmax = 192000, .wmin = 128, .wmax = 128, },
+ { .rmin = 352800, .rmax = 384000, .wmin = 64, .wmax = 64, },
+ { .rmin = 705600, .rmax = 768000, .wmin = 32, .wmax = 32, },
+};
+
+/*
+ * MCLK and BCLK selection based on TDM mode
+ * because of SAI we also add the restriction: MCLK >= 2 * BCLK
+ * (Table 9 from datasheet)
+ */
+static struct imx_akcodec_tdm_fs_mul ak5558_tdm_fs_mul[] = {
+ { .min = 128, .max = 128, .mul = 256 },
+ { .min = 256, .max = 256, .mul = 512 },
+ { .min = 512, .max = 512, .mul = 1024 },
+};
+
+static const u32 akcodec_rates[] = {
+ 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800, 384000, 705600, 768000,
+};
+
+static const u32 akcodec_tdm_rates[] = {
+ 8000, 16000, 32000, 48000, 96000,
+};
+
+static const u32 ak4458_channels[] = {
+ 1, 2, 4, 6, 8, 10, 12, 14, 16,
+};
+
+static const u32 ak4458_tdm_channels[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 16,
+};
+
+static const u32 ak5558_channels[] = {
+ 1, 2, 4, 6, 8,
+};
+
+static const u32 ak5558_tdm_channels[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8,
+};
+
+static bool format_is_dsd(struct snd_pcm_hw_params *params)
+{
+ snd_pcm_format_t format = params_format(params);
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_DSD_U8:
+ case SNDRV_PCM_FORMAT_DSD_U16_LE:
+ case SNDRV_PCM_FORMAT_DSD_U16_BE:
+ case SNDRV_PCM_FORMAT_DSD_U32_LE:
+ case SNDRV_PCM_FORMAT_DSD_U32_BE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool format_is_tdm(struct dai_link_data *link_data)
+{
+ if (link_data->slots > 2)
+ return true;
+ else
+ return false;
+}
+
+static bool codec_is_akcodec(unsigned int type)
+{
+ switch (type) {
+ case CODEC_AK4458:
+ case CODEC_AK4497:
+ case CODEC_AK5558:
+ case CODEC_AK5552:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static unsigned long akcodec_get_mclk_rate(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ int slots, int slot_width)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_card_data *data = snd_soc_card_get_drvdata(rtd->card);
+ const struct imx_card_plat_data *plat_data = data->plat_data;
+ struct dai_link_data *link_data = &data->link_data[rtd->num];
+ unsigned int width = slots * slot_width;
+ unsigned int rate = params_rate(params);
+ int i;
+
+ if (format_is_tdm(link_data)) {
+ for (i = 0; i < plat_data->num_tdm_fs_mul; i++) {
+ /* min = max = slots * slots_width */
+ if (width != plat_data->tdm_fs_mul[i].min)
+ continue;
+ return rate * plat_data->tdm_fs_mul[i].mul;
+ }
+ } else {
+ for (i = 0; i < plat_data->num_fs_mul; i++) {
+ if (rate >= plat_data->fs_mul[i].rmin &&
+ rate <= plat_data->fs_mul[i].rmax) {
+ width = max(width, plat_data->fs_mul[i].wmin);
+ width = min(width, plat_data->fs_mul[i].wmax);
+
+ /* Adjust SAI bclk:mclk ratio */
+ width *= link_data->one2one_ratio ? 1 : 2;
+
+ return rate * width;
+ }
+ }
+ }
+
+ /* Let DAI manage clk frequency by default */
+ return 0;
+}
+
+static int imx_aif_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 = asoc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_card *card = rtd->card;
+ struct imx_card_data *data = snd_soc_card_get_drvdata(card);
+ struct dai_link_data *link_data = &data->link_data[rtd->num];
+ struct imx_card_plat_data *plat_data = data->plat_data;
+ struct device *dev = card->dev;
+ struct snd_soc_dai *codec_dai;
+ unsigned long mclk_freq;
+ unsigned int fmt = rtd->dai_link->dai_fmt;
+ unsigned int slots, slot_width;
+ int ret, i;
+
+ slots = link_data->slots;
+ slot_width = link_data->slot_width;
+
+ if (!format_is_tdm(link_data)) {
+ if (format_is_dsd(params)) {
+ slots = 1;
+ slot_width = params_width(params);
+ fmt = (rtd->dai_link->dai_fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) |
+ SND_SOC_DAIFMT_PDM;
+ } else {
+ slots = 2;
+ slot_width = params_physical_width(params);
+ fmt = (rtd->dai_link->dai_fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) |
+ SND_SOC_DAIFMT_I2S;
+ }
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, snd_soc_daifmt_clock_provider_flipped(fmt));
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(slots) - 1,
+ BIT(slots) - 1,
+ slots, slot_width);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ for_each_rtd_codec_dais(rtd, i, codec_dai) {
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set codec dai[%d] fmt: %d\n", i, ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ BIT(slots) - 1,
+ BIT(slots) - 1,
+ slots, slot_width);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set codec dai[%d] tdm slot: %d\n", i, ret);
+ return ret;
+ }
+ }
+
+ /* Set MCLK freq */
+ if (codec_is_akcodec(plat_data->type))
+ mclk_freq = akcodec_get_mclk_rate(substream, params, slots, slot_width);
+ else
+ mclk_freq = params_rate(params) * slots * slot_width;
+
+ if (format_is_dsd(params)) {
+ /* Use the maximum freq from DSD512 (512*44100 = 22579200) */
+ if (!(params_rate(params) % 11025))
+ mclk_freq = IMX_CARD_MCLK_22P5792MHZ;
+ else
+ mclk_freq = IMX_CARD_MCLK_24P576MHZ;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, link_data->cpu_sysclk_id, mclk_freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set cpui dai mclk1 rate (%lu): %d\n", mclk_freq, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ak5558_hw_rule_rate(struct snd_pcm_hw_params *p, struct snd_pcm_hw_rule *r)
+{
+ struct dai_link_data *link_data = r->private;
+ struct snd_interval t = { .min = 8000, .max = 8000, };
+ unsigned long mclk_freq;
+ unsigned int fs;
+ int i;
+
+ fs = hw_param_interval(p, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+ fs *= link_data->slots;
+
+ /* Identify maximum supported rate */
+ for (i = 0; i < ARRAY_SIZE(akcodec_rates); i++) {
+ mclk_freq = fs * akcodec_rates[i];
+ /* Adjust SAI bclk:mclk ratio */
+ mclk_freq *= link_data->one2one_ratio ? 1 : 2;
+
+ /* Skip rates for which MCLK is beyond supported value */
+ if (mclk_freq > 36864000)
+ continue;
+
+ if (t.max < akcodec_rates[i])
+ t.max = akcodec_rates[i];
+ }
+
+ return snd_interval_refine(hw_param_interval(p, r->var), &t);
+}
+
+static int imx_aif_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_card_data *data = snd_soc_card_get_drvdata(card);
+ struct dai_link_data *link_data = &data->link_data[rtd->num];
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ static struct snd_pcm_hw_constraint_list constraint_channels;
+ int ret = 0;
+
+ if (format_is_tdm(link_data)) {
+ constraint_channels.list = data->plat_data->support_tdm_channels;
+ constraint_channels.count = data->plat_data->num_tdm_channels;
+ constraint_rates.list = data->plat_data->support_tdm_rates;
+ constraint_rates.count = data->plat_data->num_tdm_rates;
+ } else {
+ constraint_channels.list = data->plat_data->support_channels;
+ constraint_channels.count = data->plat_data->num_channels;
+ constraint_rates.list = data->plat_data->support_rates;
+ constraint_rates.count = data->plat_data->num_rates;
+ }
+
+ if (constraint_channels.count) {
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraint_channels);
+ if (ret)
+ return ret;
+ }
+
+ if (constraint_rates.count) {
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+ }
+
+ if (data->plat_data->type == CODEC_AK5558)
+ ret = snd_pcm_hw_rule_add(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ ak5558_hw_rule_rate, link_data,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+
+ return ret;
+}
+
+static const struct snd_soc_ops imx_aif_ops = {
+ .hw_params = imx_aif_hw_params,
+ .startup = imx_aif_startup,
+};
+
+static const struct snd_soc_ops imx_aif_ops_be = {
+ .hw_params = imx_aif_hw_params,
+};
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct imx_card_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = data->asrc_rate;
+ rate->min = data->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, (__force unsigned int)data->asrc_format);
+
+ return 0;
+}
+
+static int imx_card_parse_of(struct imx_card_data *data)
+{
+ struct imx_card_plat_data *plat_data = data->plat_data;
+ struct snd_soc_card *card = &data->card;
+ struct snd_soc_dai_link_component *dlc;
+ struct device_node *platform = NULL;
+ struct device_node *codec = NULL;
+ struct device_node *cpu = NULL;
+ struct device_node *np;
+ struct device *dev = card->dev;
+ struct snd_soc_dai_link *link;
+ struct dai_link_data *link_data;
+ struct of_phandle_args args;
+ int ret, num_links;
+ u32 asrc_fmt = 0;
+ u32 width;
+
+ ret = snd_soc_of_parse_card_name(card, "model");
+ if (ret) {
+ dev_err(dev, "Error parsing card name: %d\n", ret);
+ return ret;
+ }
+
+ /* DAPM routes */
+ if (of_property_read_bool(dev->of_node, "audio-routing")) {
+ ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
+ if (ret)
+ return ret;
+ }
+
+ /* Populate links */
+ num_links = of_get_child_count(dev->of_node);
+
+ /* Allocate the DAI link array */
+ card->dai_link = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL);
+ if (!card->dai_link)
+ return -ENOMEM;
+
+ data->link_data = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL);
+ if (!data->link_data)
+ return -ENOMEM;
+
+ card->num_links = num_links;
+ link = card->dai_link;
+ link_data = data->link_data;
+
+ for_each_child_of_node(dev->of_node, np) {
+ dlc = devm_kzalloc(dev, 2 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc) {
+ ret = -ENOMEM;
+ goto err_put_np;
+ }
+
+ link->cpus = &dlc[0];
+ link->platforms = &dlc[1];
+
+ link->num_cpus = 1;
+ link->num_platforms = 1;
+
+ ret = of_property_read_string(np, "link-name", &link->name);
+ if (ret) {
+ dev_err(card->dev, "error getting codec dai_link name\n");
+ goto err_put_np;
+ }
+
+ cpu = of_get_child_by_name(np, "cpu");
+ if (!cpu) {
+ dev_err(dev, "%s: Can't find cpu DT node\n", link->name);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = of_parse_phandle_with_args(cpu, "sound-dai",
+ "#sound-dai-cells", 0, &args);
+ if (ret) {
+ dev_err(card->dev, "%s: error getting cpu phandle\n", link->name);
+ goto err;
+ }
+
+ if (of_node_name_eq(args.np, "sai")) {
+ /* sai sysclk id */
+ link_data->cpu_sysclk_id = FSL_SAI_CLK_MAST1;
+
+ /* sai may support mclk/bclk = 1 */
+ if (of_property_read_bool(np, "fsl,mclk-equal-bclk")) {
+ link_data->one2one_ratio = true;
+ } else {
+ int i;
+
+ /*
+ * i.MX8MQ don't support one2one ratio, then
+ * with ak4497 only 16bit case is supported.
+ */
+ for (i = 0; i < ARRAY_SIZE(ak4497_fs_mul); i++) {
+ if (ak4497_fs_mul[i].rmin == 705600 &&
+ ak4497_fs_mul[i].rmax == 768000) {
+ ak4497_fs_mul[i].wmin = 32;
+ ak4497_fs_mul[i].wmax = 32;
+ }
+ }
+ }
+ }
+
+ link->cpus->of_node = args.np;
+ link->platforms->of_node = link->cpus->of_node;
+ link->id = args.args[0];
+
+ ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name);
+ if (ret) {
+ dev_err_probe(card->dev, ret,
+ "%s: error getting cpu dai name\n", link->name);
+ goto err;
+ }
+
+ codec = of_get_child_by_name(np, "codec");
+ if (codec) {
+ ret = snd_soc_of_get_dai_link_codecs(dev, codec, link);
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "%s: codec dai not found\n",
+ link->name);
+ goto err;
+ }
+
+ plat_data->num_codecs = link->num_codecs;
+
+ /* Check the akcodec type */
+ if (!strcmp(link->codecs->dai_name, "ak4458-aif"))
+ plat_data->type = CODEC_AK4458;
+ else if (!strcmp(link->codecs->dai_name, "ak4497-aif"))
+ plat_data->type = CODEC_AK4497;
+ else if (!strcmp(link->codecs->dai_name, "ak5558-aif"))
+ plat_data->type = CODEC_AK5558;
+ else if (!strcmp(link->codecs->dai_name, "ak5552-aif"))
+ plat_data->type = CODEC_AK5552;
+
+ } else {
+ dlc = devm_kzalloc(dev, sizeof(*dlc), GFP_KERNEL);
+ if (!dlc) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ link->codecs = dlc;
+ link->num_codecs = 1;
+
+ link->codecs->dai_name = "snd-soc-dummy-dai";
+ link->codecs->name = "snd-soc-dummy";
+ }
+
+ if (!strncmp(link->name, "HiFi-ASRC-FE", 12)) {
+ /* DPCM frontend */
+ link->dynamic = 1;
+ link->dpcm_merged_chan = 1;
+
+ ret = of_property_read_u32(args.np, "fsl,asrc-rate", &data->asrc_rate);
+ if (ret) {
+ dev_err(dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = of_property_read_u32(args.np, "fsl,asrc-format", &asrc_fmt);
+ data->asrc_format = (__force snd_pcm_format_t)asrc_fmt;
+ if (ret) {
+ /* Fallback to old binding; translate to asrc_format */
+ ret = of_property_read_u32(args.np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(dev,
+ "failed to decide output format\n");
+ goto err;
+ }
+
+ if (width == 24)
+ data->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ data->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+ } else if (!strncmp(link->name, "HiFi-ASRC-BE", 12)) {
+ /* DPCM backend */
+ link->no_pcm = 1;
+ link->platforms->of_node = NULL;
+ link->platforms->name = "snd-soc-dummy";
+
+ link->be_hw_params_fixup = be_hw_params_fixup;
+ link->ops = &imx_aif_ops_be;
+ } else {
+ link->ops = &imx_aif_ops;
+ }
+
+ if (link->no_pcm || link->dynamic)
+ snd_soc_dai_link_set_capabilities(link);
+
+ /* Get dai fmt */
+ ret = asoc_simple_parse_daifmt(dev, np, codec,
+ NULL, &link->dai_fmt);
+ if (ret)
+ link->dai_fmt = SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBC_CFC |
+ SND_SOC_DAIFMT_I2S;
+
+ /* Get tdm slot */
+ snd_soc_of_parse_tdm_slot(np, NULL, NULL,
+ &link_data->slots,
+ &link_data->slot_width);
+ /* default value */
+ if (!link_data->slots)
+ link_data->slots = 2;
+
+ if (!link_data->slot_width)
+ link_data->slot_width = 32;
+
+ link->ignore_pmdown_time = 1;
+ link->stream_name = link->name;
+ link++;
+ link_data++;
+
+ of_node_put(cpu);
+ of_node_put(codec);
+ of_node_put(platform);
+
+ cpu = NULL;
+ codec = NULL;
+ platform = NULL;
+ }
+
+ return 0;
+err:
+ of_node_put(cpu);
+ of_node_put(codec);
+ of_node_put(platform);
+err_put_np:
+ of_node_put(np);
+ return ret;
+}
+
+static int imx_card_probe(struct platform_device *pdev)
+{
+ struct snd_soc_dai_link *link_be = NULL, *link;
+ struct imx_card_plat_data *plat_data;
+ struct imx_card_data *data;
+ int ret, i;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ plat_data = devm_kzalloc(&pdev->dev, sizeof(*plat_data), GFP_KERNEL);
+ if (!plat_data)
+ return -ENOMEM;
+
+ data->plat_data = plat_data;
+ data->card.dev = &pdev->dev;
+
+ dev_set_drvdata(&pdev->dev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = imx_card_parse_of(data);
+ if (ret)
+ return ret;
+
+ data->num_dapm_routes = plat_data->num_codecs + 1;
+ data->dapm_routes = devm_kcalloc(&pdev->dev, data->num_dapm_routes,
+ sizeof(struct snd_soc_dapm_route),
+ GFP_KERNEL);
+ if (!data->dapm_routes)
+ return -ENOMEM;
+
+ /* configure the dapm routes */
+ switch (plat_data->type) {
+ case CODEC_AK4458:
+ case CODEC_AK4497:
+ if (plat_data->num_codecs == 1) {
+ data->dapm_routes[0].sink = "Playback";
+ data->dapm_routes[0].source = "CPU-Playback";
+ i = 1;
+ } else {
+ for (i = 0; i < plat_data->num_codecs; i++) {
+ data->dapm_routes[i].sink =
+ devm_kasprintf(&pdev->dev, GFP_KERNEL, "%d %s",
+ i + 1, "Playback");
+ data->dapm_routes[i].source = "CPU-Playback";
+ }
+ }
+ data->dapm_routes[i].sink = "CPU-Playback";
+ data->dapm_routes[i].source = "ASRC-Playback";
+ break;
+ case CODEC_AK5558:
+ case CODEC_AK5552:
+ if (plat_data->num_codecs == 1) {
+ data->dapm_routes[0].sink = "CPU-Capture";
+ data->dapm_routes[0].source = "Capture";
+ i = 1;
+ } else {
+ for (i = 0; i < plat_data->num_codecs; i++) {
+ data->dapm_routes[i].source =
+ devm_kasprintf(&pdev->dev, GFP_KERNEL, "%d %s",
+ i + 1, "Capture");
+ data->dapm_routes[i].sink = "CPU-Capture";
+ }
+ }
+ data->dapm_routes[i].sink = "ASRC-Capture";
+ data->dapm_routes[i].source = "CPU-Capture";
+ break;
+ default:
+ break;
+ }
+
+ /* default platform data for akcodecs */
+ if (codec_is_akcodec(plat_data->type)) {
+ plat_data->support_rates = akcodec_rates;
+ plat_data->num_rates = ARRAY_SIZE(akcodec_rates);
+ plat_data->support_tdm_rates = akcodec_tdm_rates;
+ plat_data->num_tdm_rates = ARRAY_SIZE(akcodec_tdm_rates);
+
+ switch (plat_data->type) {
+ case CODEC_AK4458:
+ plat_data->fs_mul = ak4458_fs_mul;
+ plat_data->num_fs_mul = ARRAY_SIZE(ak4458_fs_mul);
+ plat_data->tdm_fs_mul = ak4458_tdm_fs_mul;
+ plat_data->num_tdm_fs_mul = ARRAY_SIZE(ak4458_tdm_fs_mul);
+ plat_data->support_channels = ak4458_channels;
+ plat_data->num_channels = ARRAY_SIZE(ak4458_channels);
+ plat_data->support_tdm_channels = ak4458_tdm_channels;
+ plat_data->num_tdm_channels = ARRAY_SIZE(ak4458_tdm_channels);
+ break;
+ case CODEC_AK4497:
+ plat_data->fs_mul = ak4497_fs_mul;
+ plat_data->num_fs_mul = ARRAY_SIZE(ak4497_fs_mul);
+ plat_data->support_channels = ak4458_channels;
+ plat_data->num_channels = ARRAY_SIZE(ak4458_channels);
+ break;
+ case CODEC_AK5558:
+ case CODEC_AK5552:
+ plat_data->fs_mul = ak5558_fs_mul;
+ plat_data->num_fs_mul = ARRAY_SIZE(ak5558_fs_mul);
+ plat_data->tdm_fs_mul = ak5558_tdm_fs_mul;
+ plat_data->num_tdm_fs_mul = ARRAY_SIZE(ak5558_tdm_fs_mul);
+ plat_data->support_channels = ak5558_channels;
+ plat_data->num_channels = ARRAY_SIZE(ak5558_channels);
+ plat_data->support_tdm_channels = ak5558_tdm_channels;
+ plat_data->num_tdm_channels = ARRAY_SIZE(ak5558_tdm_channels);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* with asrc as front end */
+ if (data->card.num_links == 3) {
+ data->card.dapm_routes = data->dapm_routes;
+ data->card.num_dapm_routes = data->num_dapm_routes;
+ for_each_card_prelinks(&data->card, i, link) {
+ if (link->no_pcm == 1)
+ link_be = link;
+ }
+ for_each_card_prelinks(&data->card, i, link) {
+ if (link->dynamic == 1 && link_be) {
+ link->dpcm_playback = link_be->dpcm_playback;
+ link->dpcm_capture = link_be->dpcm_capture;
+ }
+ }
+ }
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n");
+
+ return 0;
+}
+
+static const struct of_device_id imx_card_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-card", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx_card_dt_ids);
+
+static struct platform_driver imx_card_driver = {
+ .driver = {
+ .name = "imx-card",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_card_dt_ids,
+ },
+ .probe = imx_card_probe,
+};
+module_platform_driver(imx_card_driver);
+
+MODULE_DESCRIPTION("Freescale i.MX ASoC Machine Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-card");
diff --git a/sound/soc/fsl/imx-es8328.c b/sound/soc/fsl/imx-es8328.c
new file mode 100644
index 000000000..b80c57362
--- /dev/null
+++ b/sound/soc/fsl/imx-es8328.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright 2012 Freescale Semiconductor, Inc.
+// Copyright 2012 Linaro Ltd.
+
+#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);
+ 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;
+ struct snd_soc_dai_link_component *comp;
+ 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);
+ ret = -EINVAL;
+ 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 put_device;
+ }
+
+ comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL);
+ if (!comp) {
+ ret = -ENOMEM;
+ goto put_device;
+ }
+
+ data->dev = dev;
+
+ data->jack_gpio = of_get_named_gpio(pdev->dev.of_node, "jack-gpio", 0);
+
+ data->dai.cpus = &comp[0];
+ data->dai.codecs = &comp[1];
+ data->dai.platforms = &comp[2];
+
+ data->dai.num_cpus = 1;
+ data->dai.num_codecs = 1;
+ data->dai.num_platforms = 1;
+
+ data->dai.name = "hifi";
+ data->dai.stream_name = "hifi";
+ data->dai.codecs->dai_name = "es8328-hifi-analog";
+ data->dai.codecs->of_node = codec_np;
+ data->dai.cpus->of_node = ssi_np;
+ data->dai.platforms->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_CBP_CFP;
+
+ 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 put_device;
+ }
+ ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
+ if (ret) {
+ dev_err(dev, "Unable to parse routing: %d\n", ret);
+ goto put_device;
+ }
+ data->card.num_links = 1;
+ data->card.owner = THIS_MODULE;
+ data->card.dai_link = &data->dai;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(dev, "Unable to register: %d\n", ret);
+ goto put_device;
+ }
+
+ platform_set_drvdata(pdev, data);
+put_device:
+ put_device(&ssi_pdev->dev);
+fail:
+ of_node_put(ssi_np);
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+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,
+};
+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-hdmi.c b/sound/soc/fsl/imx-hdmi.c
new file mode 100644
index 000000000..a780cf5a6
--- /dev/null
+++ b/sound/soc/fsl/imx-hdmi.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2017-2020 NXP
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+#include <sound/hdmi-codec.h>
+#include "fsl_sai.h"
+
+/**
+ * struct cpu_priv - CPU private data
+ * @sysclk_id: SYSCLK ids for set_sysclk()
+ * @slot_width: Slot width of each frame
+ *
+ * Note: [1] for tx and [0] for rx
+ */
+struct cpu_priv {
+ u32 sysclk_id[2];
+ u32 slot_width;
+};
+
+struct imx_hdmi_data {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+ struct snd_soc_jack hdmi_jack;
+ struct snd_soc_jack_pin hdmi_jack_pin;
+ struct cpu_priv cpu_priv;
+ u32 dai_fmt;
+};
+
+static int imx_hdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_hdmi_data *data = snd_soc_card_get_drvdata(rtd->card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ u32 slot_width = data->cpu_priv.slot_width;
+ int ret;
+
+ /* MCLK always is (256 or 192) * rate. */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, data->cpu_priv.sysclk_id[tx],
+ 8 * slot_width * params_rate(params),
+ tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, slot_width);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops imx_hdmi_ops = {
+ .hw_params = imx_hdmi_hw_params,
+};
+
+static const struct snd_soc_dapm_widget imx_hdmi_widgets[] = {
+ SND_SOC_DAPM_LINE("HDMI Jack", NULL),
+};
+
+static int imx_hdmi_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = codec_dai->component;
+ struct imx_hdmi_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ data->hdmi_jack_pin.pin = "HDMI Jack";
+ data->hdmi_jack_pin.mask = SND_JACK_LINEOUT;
+ /* enable jack detection */
+ ret = snd_soc_card_jack_new_pins(card, "HDMI Jack", SND_JACK_LINEOUT,
+ &data->hdmi_jack,
+ &data->hdmi_jack_pin, 1);
+ if (ret) {
+ dev_err(card->dev, "Can't new HDMI Jack %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_jack(component, &data->hdmi_jack, NULL);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(card->dev, "Can't set HDMI Jack %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+};
+
+static int imx_hdmi_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ bool hdmi_out = of_property_read_bool(np, "hdmi-out");
+ bool hdmi_in = of_property_read_bool(np, "hdmi-in");
+ struct snd_soc_dai_link_component *dlc;
+ struct platform_device *cpu_pdev;
+ struct device_node *cpu_np;
+ struct imx_hdmi_data *data;
+ int ret;
+
+ dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(np, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai 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 SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ put_device(&cpu_pdev->dev);
+ goto fail;
+ }
+
+ data->dai.cpus = &dlc[0];
+ data->dai.num_cpus = 1;
+ data->dai.platforms = &dlc[1];
+ data->dai.num_platforms = 1;
+ data->dai.codecs = &dlc[2];
+ data->dai.num_codecs = 1;
+
+ data->dai.name = "i.MX HDMI";
+ data->dai.stream_name = "i.MX HDMI";
+ data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platforms->of_node = cpu_np;
+ data->dai.ops = &imx_hdmi_ops;
+ data->dai.playback_only = true;
+ data->dai.capture_only = false;
+ data->dai.init = imx_hdmi_init;
+
+ put_device(&cpu_pdev->dev);
+
+ if (of_node_name_eq(cpu_np, "sai")) {
+ data->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1;
+ data->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1;
+ }
+
+ if (of_device_is_compatible(np, "fsl,imx-audio-sii902x")) {
+ data->dai_fmt = SND_SOC_DAIFMT_LEFT_J;
+ data->cpu_priv.slot_width = 24;
+ } else {
+ data->dai_fmt = SND_SOC_DAIFMT_I2S;
+ data->cpu_priv.slot_width = 32;
+ }
+
+ if ((hdmi_out && hdmi_in) || (!hdmi_out && !hdmi_in)) {
+ dev_err(&pdev->dev, "Invalid HDMI DAI link\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (hdmi_out) {
+ data->dai.playback_only = true;
+ data->dai.capture_only = false;
+ data->dai.codecs->dai_name = "i2s-hifi";
+ data->dai.codecs->name = "hdmi-audio-codec.1";
+ data->dai.dai_fmt = data->dai_fmt |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBC_CFC;
+ }
+
+ if (hdmi_in) {
+ data->dai.playback_only = false;
+ data->dai.capture_only = true;
+ data->dai.codecs->dai_name = "i2s-hifi";
+ data->dai.codecs->name = "hdmi-audio-codec.2";
+ data->dai.dai_fmt = data->dai_fmt |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBP_CFP;
+ }
+
+ data->card.dapm_widgets = imx_hdmi_widgets;
+ data->card.num_dapm_widgets = ARRAY_SIZE(imx_hdmi_widgets);
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+
+ 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;
+ }
+
+fail:
+ of_node_put(cpu_np);
+
+ return ret;
+}
+
+static const struct of_device_id imx_hdmi_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-hdmi", },
+ { .compatible = "fsl,imx-audio-sii902x", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids);
+
+static struct platform_driver imx_hdmi_driver = {
+ .driver = {
+ .name = "imx-hdmi",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_hdmi_dt_ids,
+ },
+ .probe = imx_hdmi_probe,
+};
+module_platform_driver(imx_hdmi_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX hdmi audio ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-hdmi");
diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c
new file mode 100644
index 000000000..14e942709
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-dma.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 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.
+ */
+#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)
+{
+ 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 000000000..0d1240026
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-fiq.c
@@ -0,0 +1,322 @@
+// SPDX-License-Identifier: GPL-2.0+
+// 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.
+
+#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_soc_component *component,
+ 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);
+
+ return 0;
+}
+
+static int snd_imx_pcm_prepare(struct snd_soc_component *component,
+ 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_soc_component *component,
+ 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_soc_component *component,
+ 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_soc_component *component,
+ 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_soc_component *component,
+ 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 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;
+
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC,
+ pcm->card->dev,
+ IMX_SSI_DMABUF_SIZE);
+}
+
+static int ssi_irq;
+
+static int snd_imx_pcm_new(struct snd_soc_component *component,
+ 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 snd_imx_pcm_free(struct snd_soc_component *component,
+ struct snd_pcm *pcm)
+{
+ mxc_set_irq_fiq(ssi_irq, 0);
+ release_fiq(&fh);
+}
+
+static const struct snd_soc_component_driver imx_soc_component_fiq = {
+ .open = snd_imx_open,
+ .close = snd_imx_close,
+ .hw_params = snd_imx_pcm_hw_params,
+ .prepare = snd_imx_pcm_prepare,
+ .trigger = snd_imx_pcm_trigger,
+ .pointer = snd_imx_pcm_pointer,
+ .pcm_construct = snd_imx_pcm_new,
+ .pcm_destruct = snd_imx_pcm_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-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c
new file mode 100644
index 000000000..933bac7ea
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-rpmsg.c
@@ -0,0 +1,834 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2017-2021 NXP
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/rpmsg.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+#include "imx-pcm.h"
+#include "fsl_rpmsg.h"
+#include "imx-pcm-rpmsg.h"
+
+static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE,
+ .period_bytes_min = 512,
+ .period_bytes_max = 65536,
+ .periods_min = 2,
+ .periods_max = 6000,
+ .fifo_size = 0,
+};
+
+static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg,
+ struct rpmsg_info *info)
+{
+ struct rpmsg_device *rpdev = info->rpdev;
+ int ret = 0;
+
+ mutex_lock(&info->msg_lock);
+ if (!rpdev) {
+ dev_err(info->dev, "rpmsg channel not ready\n");
+ mutex_unlock(&info->msg_lock);
+ return -EINVAL;
+ }
+
+ dev_dbg(&rpdev->dev, "send cmd %d\n", msg->s_msg.header.cmd);
+
+ if (!(msg->s_msg.header.type == MSG_TYPE_C))
+ reinit_completion(&info->cmd_complete);
+
+ ret = rpmsg_send(rpdev->ept, (void *)&msg->s_msg,
+ sizeof(struct rpmsg_s_msg));
+ if (ret) {
+ dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
+ mutex_unlock(&info->msg_lock);
+ return ret;
+ }
+
+ /* No receive msg for TYPE_C command */
+ if (msg->s_msg.header.type == MSG_TYPE_C) {
+ mutex_unlock(&info->msg_lock);
+ return 0;
+ }
+
+ /* wait response from rpmsg */
+ ret = wait_for_completion_timeout(&info->cmd_complete,
+ msecs_to_jiffies(RPMSG_TIMEOUT));
+ if (!ret) {
+ dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n",
+ msg->s_msg.header.cmd);
+ mutex_unlock(&info->msg_lock);
+ return -ETIMEDOUT;
+ }
+
+ memcpy(&msg->r_msg, &info->r_msg, sizeof(struct rpmsg_r_msg));
+ memcpy(&info->msg[msg->r_msg.header.cmd].r_msg,
+ &msg->r_msg, sizeof(struct rpmsg_r_msg));
+
+ /*
+ * Reset the buffer pointer to be zero, actully we have
+ * set the buffer pointer to be zero in imx_rpmsg_terminate_all
+ * But if there is timer task queued in queue, after it is
+ * executed the buffer pointer will be changed, so need to
+ * reset it again with TERMINATE command.
+ */
+ switch (msg->s_msg.header.cmd) {
+ case TX_TERMINATE:
+ info->msg[TX_POINTER].r_msg.param.buffer_offset = 0;
+ break;
+ case RX_TERMINATE:
+ info->msg[RX_POINTER].r_msg.param.buffer_offset = 0;
+ break;
+ default:
+ break;
+ }
+
+ dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd,
+ info->r_msg.param.resp);
+
+ mutex_unlock(&info->msg_lock);
+
+ return 0;
+}
+
+static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream,
+ struct rpmsg_msg *msg,
+ struct rpmsg_info *info)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ /*
+ * Queue the work to workqueue.
+ * If the queue is full, drop the message.
+ */
+ spin_lock_irqsave(&info->wq_lock, flags);
+ if (info->work_write_index != info->work_read_index) {
+ int index = info->work_write_index;
+
+ memcpy(&info->work_list[index].msg, msg,
+ sizeof(struct rpmsg_s_msg));
+
+ queue_work(info->rpmsg_wq, &info->work_list[index].work);
+ info->work_write_index++;
+ info->work_write_index %= WORK_MAX_NUM;
+ } else {
+ info->msg_drop_count[substream->stream]++;
+ ret = -EPIPE;
+ }
+ spin_unlock_irqrestore(&info->wq_lock, flags);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ int ret = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_HW_PARAM];
+ msg->s_msg.header.cmd = TX_HW_PARAM;
+ } else {
+ msg = &info->msg[RX_HW_PARAM];
+ msg->s_msg.header.cmd = RX_HW_PARAM;
+ }
+
+ msg->s_msg.param.rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ msg->s_msg.param.format = RPMSG_S16_LE;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ msg->s_msg.param.format = RPMSG_S24_LE;
+ break;
+ case SNDRV_PCM_FORMAT_DSD_U16_LE:
+ msg->s_msg.param.format = RPMSG_DSD_U16_LE;
+ break;
+ case SNDRV_PCM_FORMAT_DSD_U32_LE:
+ msg->s_msg.param.format = RPMSG_DSD_U32_LE;
+ break;
+ default:
+ msg->s_msg.param.format = RPMSG_S32_LE;
+ break;
+ }
+
+ switch (params_channels(params)) {
+ case 1:
+ msg->s_msg.param.channels = RPMSG_CH_LEFT;
+ break;
+ case 2:
+ msg->s_msg.param.channels = RPMSG_CH_STEREO;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ info->send_message(msg, info);
+
+ return ret;
+}
+
+static snd_pcm_uframes_t imx_rpmsg_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ unsigned int pos = 0;
+ int buffer_tail = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ else
+ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
+
+ buffer_tail = msg->r_msg.param.buffer_tail;
+ pos = buffer_tail * snd_pcm_lib_period_bytes(substream);
+
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static void imx_rpmsg_timer_callback(struct timer_list *t)
+{
+ struct stream_timer *stream_timer =
+ from_timer(stream_timer, t, timer);
+ struct snd_pcm_substream *substream = stream_timer->substream;
+ struct rpmsg_info *info = stream_timer->info;
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->s_msg.header.cmd = TX_PERIOD_DONE;
+ } else {
+ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->s_msg.header.cmd = RX_PERIOD_DONE;
+ }
+
+ imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ int ret = 0;
+ int cmd;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_OPEN];
+ msg->s_msg.header.cmd = TX_OPEN;
+
+ /* reinitialize buffer counter*/
+ cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM;
+ info->msg[cmd].s_msg.param.buffer_tail = 0;
+ info->msg[cmd].r_msg.param.buffer_tail = 0;
+ info->msg[TX_POINTER].r_msg.param.buffer_offset = 0;
+
+ } else {
+ msg = &info->msg[RX_OPEN];
+ msg->s_msg.header.cmd = RX_OPEN;
+
+ /* reinitialize buffer counter*/
+ cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM;
+ info->msg[cmd].s_msg.param.buffer_tail = 0;
+ info->msg[cmd].r_msg.param.buffer_tail = 0;
+ info->msg[RX_POINTER].r_msg.param.buffer_offset = 0;
+ }
+
+ info->send_message(msg, info);
+
+ imx_rpmsg_pcm_hardware.period_bytes_max =
+ imx_rpmsg_pcm_hardware.buffer_bytes_max / 2;
+
+ snd_soc_set_runtime_hwparams(substream, &imx_rpmsg_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ info->msg_drop_count[substream->stream] = 0;
+
+ /* Create timer*/
+ info->stream_timer[substream->stream].info = info;
+ info->stream_timer[substream->stream].substream = substream;
+ timer_setup(&info->stream_timer[substream->stream].timer,
+ imx_rpmsg_timer_callback, 0);
+ return ret;
+}
+
+static int imx_rpmsg_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ int ret = 0;
+
+ /* Flush work in workqueue to make TX_CLOSE is the last message */
+ flush_workqueue(info->rpmsg_wq);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_CLOSE];
+ msg->s_msg.header.cmd = TX_CLOSE;
+ } else {
+ msg = &info->msg[RX_CLOSE];
+ msg->s_msg.header.cmd = RX_CLOSE;
+ }
+
+ info->send_message(msg, info);
+
+ del_timer(&info->stream_timer[substream->stream].timer);
+
+ rtd->dai_link->ignore_suspend = 0;
+
+ if (info->msg_drop_count[substream->stream])
+ dev_warn(rtd->dev, "Msg is dropped!, number is %d\n",
+ info->msg_drop_count[substream->stream]);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_prepare(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
+
+ /*
+ * NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts
+ * four conditions to determine the lpa is enabled.
+ */
+ if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+ runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) &&
+ rpmsg->enable_lpa) {
+ /*
+ * Ignore suspend operation in low power mode
+ * M core will continue playback music on A core suspend.
+ */
+ rtd->dai_link->ignore_suspend = 1;
+ rpmsg->force_lpa = 1;
+ } else {
+ rpmsg->force_lpa = 0;
+ }
+
+ return 0;
+}
+
+static void imx_rpmsg_pcm_dma_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static int imx_rpmsg_prepare_and_submit(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_BUFFER];
+ msg->s_msg.header.cmd = TX_BUFFER;
+ } else {
+ msg = &info->msg[RX_BUFFER];
+ msg->s_msg.header.cmd = RX_BUFFER;
+ }
+
+ /* Send buffer address and buffer size */
+ msg->s_msg.param.buffer_addr = substream->runtime->dma_addr;
+ msg->s_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ msg->s_msg.param.period_size = snd_pcm_lib_period_bytes(substream);
+ msg->s_msg.param.buffer_tail = 0;
+
+ info->num_period[substream->stream] = msg->s_msg.param.buffer_size /
+ msg->s_msg.param.period_size;
+
+ info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete;
+ info->callback_param[substream->stream] = substream;
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_async_issue_pending(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_START];
+ msg->s_msg.header.cmd = TX_START;
+ } else {
+ msg = &info->msg[RX_START];
+ msg->s_msg.header.cmd = RX_START;
+ }
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_restart(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_RESTART];
+ msg->s_msg.header.cmd = TX_RESTART;
+ } else {
+ msg = &info->msg[RX_RESTART];
+ msg->s_msg.header.cmd = RX_RESTART;
+ }
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_pause(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_PAUSE];
+ msg->s_msg.header.cmd = TX_PAUSE;
+ } else {
+ msg = &info->msg[RX_PAUSE];
+ msg->s_msg.header.cmd = RX_PAUSE;
+ }
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_terminate_all(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ int cmd;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_TERMINATE];
+ msg->s_msg.header.cmd = TX_TERMINATE;
+ /* Clear buffer count*/
+ cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM;
+ info->msg[cmd].s_msg.param.buffer_tail = 0;
+ info->msg[cmd].r_msg.param.buffer_tail = 0;
+ info->msg[TX_POINTER].r_msg.param.buffer_offset = 0;
+ } else {
+ msg = &info->msg[RX_TERMINATE];
+ msg->s_msg.header.cmd = RX_TERMINATE;
+ /* Clear buffer count*/
+ cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM;
+ info->msg[cmd].s_msg.param.buffer_tail = 0;
+ info->msg[cmd].r_msg.param.buffer_tail = 0;
+ info->msg[RX_POINTER].r_msg.param.buffer_offset = 0;
+ }
+
+ del_timer(&info->stream_timer[substream->stream].timer);
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = imx_rpmsg_prepare_and_submit(component, substream);
+ if (ret)
+ return ret;
+ ret = imx_rpmsg_async_issue_pending(component, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (rpmsg->force_lpa)
+ break;
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = imx_rpmsg_restart(component, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (!rpmsg->force_lpa) {
+ if (runtime->info & SNDRV_PCM_INFO_PAUSE)
+ ret = imx_rpmsg_pause(component, substream);
+ else
+ ret = imx_rpmsg_terminate_all(component, substream);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = imx_rpmsg_pause(component, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = imx_rpmsg_terminate_all(component, substream);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * imx_rpmsg_pcm_ack
+ *
+ * Send the period index to M core through rpmsg, but not send
+ * all the period index to M core, reduce some unnessesary msg
+ * to reduce the pressure of rpmsg bandwidth.
+ */
+static int imx_rpmsg_pcm_ack(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ snd_pcm_uframes_t period_size = runtime->period_size;
+ snd_pcm_sframes_t avail;
+ struct timer_list *timer;
+ struct rpmsg_msg *msg;
+ unsigned long flags;
+ int buffer_tail = 0;
+ int written_num;
+
+ if (!rpmsg->force_lpa)
+ return 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->s_msg.header.cmd = TX_PERIOD_DONE;
+ } else {
+ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->s_msg.header.cmd = RX_PERIOD_DONE;
+ }
+
+ msg->s_msg.header.type = MSG_TYPE_C;
+
+ buffer_tail = (frames_to_bytes(runtime, runtime->control->appl_ptr) %
+ snd_pcm_lib_buffer_bytes(substream));
+ buffer_tail = buffer_tail / snd_pcm_lib_period_bytes(substream);
+
+ /* There is update for period index */
+ if (buffer_tail != msg->s_msg.param.buffer_tail) {
+ written_num = buffer_tail - msg->s_msg.param.buffer_tail;
+ if (written_num < 0)
+ written_num += runtime->periods;
+
+ msg->s_msg.param.buffer_tail = buffer_tail;
+
+ /* The notification message is updated to latest */
+ spin_lock_irqsave(&info->lock[substream->stream], flags);
+ memcpy(&info->notify[substream->stream], msg,
+ sizeof(struct rpmsg_s_msg));
+ info->notify_updated[substream->stream] = true;
+ spin_unlock_irqrestore(&info->lock[substream->stream], flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ avail = snd_pcm_playback_hw_avail(runtime);
+ else
+ avail = snd_pcm_capture_hw_avail(runtime);
+
+ timer = &info->stream_timer[substream->stream].timer;
+ /*
+ * If the data in the buffer is less than one period before
+ * this fill, which means the data may not enough on M
+ * core side, we need to send message immediately to let
+ * M core know the pointer is updated.
+ * if there is more than one period data in the buffer before
+ * this fill, which means the data is enough on M core side,
+ * we can delay one period (using timer) to send the message
+ * for reduce the message number in workqueue, because the
+ * pointer may be updated by ack function later, we can
+ * send latest pointer to M core side.
+ */
+ if ((avail - written_num * period_size) <= period_size) {
+ imx_rpmsg_insert_workqueue(substream, msg, info);
+ } else if (rpmsg->force_lpa && !timer_pending(timer)) {
+ int time_msec;
+
+ time_msec = (int)(runtime->period_size * 1000 / runtime->rate);
+ mod_timer(timer, jiffies + msecs_to_jiffies(time_msec));
+ }
+ }
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
+ int ret;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ imx_rpmsg_pcm_hardware.buffer_bytes_max = rpmsg->buffer_size;
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC,
+ pcm->card->dev, rpmsg->buffer_size);
+}
+
+static const struct snd_soc_component_driver imx_rpmsg_soc_component = {
+ .name = IMX_PCM_DRV_NAME,
+ .pcm_construct = imx_rpmsg_pcm_new,
+ .open = imx_rpmsg_pcm_open,
+ .close = imx_rpmsg_pcm_close,
+ .hw_params = imx_rpmsg_pcm_hw_params,
+ .trigger = imx_rpmsg_pcm_trigger,
+ .pointer = imx_rpmsg_pcm_pointer,
+ .ack = imx_rpmsg_pcm_ack,
+ .prepare = imx_rpmsg_pcm_prepare,
+};
+
+static void imx_rpmsg_pcm_work(struct work_struct *work)
+{
+ struct work_of_rpmsg *work_of_rpmsg;
+ bool is_notification = false;
+ struct rpmsg_info *info;
+ struct rpmsg_msg msg;
+ unsigned long flags;
+
+ work_of_rpmsg = container_of(work, struct work_of_rpmsg, work);
+ info = work_of_rpmsg->info;
+
+ /*
+ * Every work in the work queue, first we check if there
+ * is update for period is filled, because there may be not
+ * enough data in M core side, need to let M core know
+ * data is updated immediately.
+ */
+ spin_lock_irqsave(&info->lock[TX], flags);
+ if (info->notify_updated[TX]) {
+ memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg));
+ info->notify_updated[TX] = false;
+ spin_unlock_irqrestore(&info->lock[TX], flags);
+ info->send_message(&msg, info);
+ } else {
+ spin_unlock_irqrestore(&info->lock[TX], flags);
+ }
+
+ spin_lock_irqsave(&info->lock[RX], flags);
+ if (info->notify_updated[RX]) {
+ memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg));
+ info->notify_updated[RX] = false;
+ spin_unlock_irqrestore(&info->lock[RX], flags);
+ info->send_message(&msg, info);
+ } else {
+ spin_unlock_irqrestore(&info->lock[RX], flags);
+ }
+
+ /* Skip the notification message for it has been processed above */
+ if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C &&
+ (work_of_rpmsg->msg.s_msg.header.cmd == TX_PERIOD_DONE ||
+ work_of_rpmsg->msg.s_msg.header.cmd == RX_PERIOD_DONE))
+ is_notification = true;
+
+ if (!is_notification)
+ info->send_message(&work_of_rpmsg->msg, info);
+
+ /* update read index */
+ spin_lock_irqsave(&info->wq_lock, flags);
+ info->work_read_index++;
+ info->work_read_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&info->wq_lock, flags);
+}
+
+static int imx_rpmsg_pcm_probe(struct platform_device *pdev)
+{
+ struct snd_soc_component *component;
+ struct rpmsg_info *info;
+ int ret, i;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, info);
+
+ info->rpdev = container_of(pdev->dev.parent, struct rpmsg_device, dev);
+ info->dev = &pdev->dev;
+ /* Setup work queue */
+ info->rpmsg_wq = alloc_ordered_workqueue("rpmsg_audio",
+ WQ_HIGHPRI |
+ WQ_UNBOUND |
+ WQ_FREEZABLE);
+ if (!info->rpmsg_wq) {
+ dev_err(&pdev->dev, "workqueue create failed\n");
+ return -ENOMEM;
+ }
+
+ /* Write index initialize 1, make it differ with the read index */
+ info->work_write_index = 1;
+ info->send_message = imx_rpmsg_pcm_send_message;
+
+ for (i = 0; i < WORK_MAX_NUM; i++) {
+ INIT_WORK(&info->work_list[i].work, imx_rpmsg_pcm_work);
+ info->work_list[i].info = info;
+ }
+
+ /* Initialize msg */
+ for (i = 0; i < MSG_MAX_NUM; i++) {
+ info->msg[i].s_msg.header.cate = IMX_RPMSG_AUDIO;
+ info->msg[i].s_msg.header.major = IMX_RMPSG_MAJOR;
+ info->msg[i].s_msg.header.minor = IMX_RMPSG_MINOR;
+ info->msg[i].s_msg.header.type = MSG_TYPE_A;
+ info->msg[i].s_msg.param.audioindex = 0;
+ }
+
+ init_completion(&info->cmd_complete);
+ mutex_init(&info->msg_lock);
+ spin_lock_init(&info->lock[TX]);
+ spin_lock_init(&info->lock[RX]);
+ spin_lock_init(&info->wq_lock);
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &imx_rpmsg_soc_component,
+ NULL, 0);
+ if (ret)
+ goto fail;
+
+ component = snd_soc_lookup_component(&pdev->dev, IMX_PCM_DRV_NAME);
+ if (!component) {
+ ret = -EINVAL;
+ goto fail;
+ }
+#ifdef CONFIG_DEBUG_FS
+ component->debugfs_prefix = "rpmsg";
+#endif
+
+ return 0;
+
+fail:
+ if (info->rpmsg_wq)
+ destroy_workqueue(info->rpmsg_wq);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_remove(struct platform_device *pdev)
+{
+ struct rpmsg_info *info = platform_get_drvdata(pdev);
+
+ if (info->rpmsg_wq)
+ destroy_workqueue(info->rpmsg_wq);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int imx_rpmsg_pcm_runtime_resume(struct device *dev)
+{
+ struct rpmsg_info *info = dev_get_drvdata(dev);
+
+ cpu_latency_qos_add_request(&info->pm_qos_req, 0);
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_runtime_suspend(struct device *dev)
+{
+ struct rpmsg_info *info = dev_get_drvdata(dev);
+
+ cpu_latency_qos_remove_request(&info->pm_qos_req);
+
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int imx_rpmsg_pcm_suspend(struct device *dev)
+{
+ struct rpmsg_info *info = dev_get_drvdata(dev);
+ struct rpmsg_msg *rpmsg_tx;
+ struct rpmsg_msg *rpmsg_rx;
+
+ rpmsg_tx = &info->msg[TX_SUSPEND];
+ rpmsg_rx = &info->msg[RX_SUSPEND];
+
+ rpmsg_tx->s_msg.header.cmd = TX_SUSPEND;
+ info->send_message(rpmsg_tx, info);
+
+ rpmsg_rx->s_msg.header.cmd = RX_SUSPEND;
+ info->send_message(rpmsg_rx, info);
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_resume(struct device *dev)
+{
+ struct rpmsg_info *info = dev_get_drvdata(dev);
+ struct rpmsg_msg *rpmsg_tx;
+ struct rpmsg_msg *rpmsg_rx;
+
+ rpmsg_tx = &info->msg[TX_RESUME];
+ rpmsg_rx = &info->msg[RX_RESUME];
+
+ rpmsg_tx->s_msg.header.cmd = TX_RESUME;
+ info->send_message(rpmsg_tx, info);
+
+ rpmsg_rx->s_msg.header.cmd = RX_RESUME;
+ info->send_message(rpmsg_rx, info);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = {
+ SET_RUNTIME_PM_OPS(imx_rpmsg_pcm_runtime_suspend,
+ imx_rpmsg_pcm_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(imx_rpmsg_pcm_suspend,
+ imx_rpmsg_pcm_resume)
+};
+
+static struct platform_driver imx_pcm_rpmsg_driver = {
+ .probe = imx_rpmsg_pcm_probe,
+ .remove = imx_rpmsg_pcm_remove,
+ .driver = {
+ .name = IMX_PCM_DRV_NAME,
+ .pm = &imx_rpmsg_pcm_pm_ops,
+ },
+};
+module_platform_driver(imx_pcm_rpmsg_driver);
+
+MODULE_DESCRIPTION("Freescale SoC Audio RPMSG PCM interface");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_ALIAS("platform:" IMX_PCM_DRV_NAME);
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/imx-pcm-rpmsg.h b/sound/soc/fsl/imx-pcm-rpmsg.h
new file mode 100644
index 000000000..8286b55f0
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-rpmsg.h
@@ -0,0 +1,512 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2017-2021 NXP
+ *
+ ******************************************************************************
+ * Communication stack of audio with rpmsg
+ ******************************************************************************
+ * Packet structure:
+ * A SRTM message consists of a 10 bytes header followed by 0~N bytes of data
+ *
+ * +---------------+-------------------------------+
+ * | | Content |
+ * +---------------+-------------------------------+
+ * | Byte Offset | 7 6 5 4 3 2 1 0 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 0 | Category |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 1 ~ 2 | Version |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 3 | Type |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 4 | Command |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 5 | Reserved0 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 6 | Reserved1 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 7 | Reserved2 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 8 | Reserved3 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 9 | Reserved4 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 10 | DATA 0 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * : : : : : : : : : : : : :
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | N + 10 - 1 | DATA N-1 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ *
+ * +----------+------------+------------------------------------------------+
+ * | Field | Byte | |
+ * +----------+------------+------------------------------------------------+
+ * | Category | 0 | The destination category. |
+ * +----------+------------+------------------------------------------------+
+ * | Version | 1 ~ 2 | The category version of the sender of the |
+ * | | | packet. |
+ * | | | The first byte represent the major version of |
+ * | | | the packet.The second byte represent the minor |
+ * | | | version of the packet. |
+ * +----------+------------+------------------------------------------------+
+ * | Type | 3 | The message type of current message packet. |
+ * +----------+------------+------------------------------------------------+
+ * | Command | 4 | The command byte sent to remote processor/SoC. |
+ * +----------+------------+------------------------------------------------+
+ * | Reserved | 5 ~ 9 | Reserved field for future extension. |
+ * +----------+------------+------------------------------------------------+
+ * | Data | N | The data payload of the message packet. |
+ * +----------+------------+------------------------------------------------+
+ *
+ * Audio control:
+ * SRTM Audio Control Category Request Command Table:
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | Category | Version | Type | Command | Data | Function |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x00 | Data[0]: Audio Device Index | Open a TX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x01 | Data[0]: Audio Device Index | Start a TX Instance. |
+ * | | | | | Same as above command | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x02 | Data[0]: Audio Device Index | Pause a TX Instance. |
+ * | | | | | Same as above command | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x03 | Data[0]: Audio Device Index | Resume a TX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x04 | Data[0]: Audio Device Index | Stop a TX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x05 | Data[0]: Audio Device Index | Close a TX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x06 | Data[0]: Audio Device Index | Set Parameters for |
+ * | | | | | Data[1]: format | a TX Instance. |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x07 | Data[0]: Audio Device Index | Set TX Buffer. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x08 | Data[0]: Audio Device Index | Suspend a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x09 | Data[0]: Audio Device Index | Resume a TX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0A | Data[0]: Audio Device Index | Open a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0B | Data[0]: Audio Device Index | Start a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0C | Data[0]: Audio Device Index | Pause a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0D | Data[0]: Audio Device Index | Resume a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0E | Data[0]: Audio Device Index | Stop a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0F | Data[0]: Audio Device Index | Close a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x10 | Data[0]: Audio Device Index | Set Parameters for |
+ * | | | | | Data[1]: format | a RX Instance. |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x11 | Data[0]: Audio Device Index | Set RX Buffer. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x12 | Data[0]: Audio Device Index | Suspend a RX Instance.|
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x13 | Data[0]: Audio Device Index | Resume a RX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x14 | Data[0]: Audio Device Index | Set register value |
+ * | | | | | Data[1-6]: reserved | to codec |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-14]: value | |
+ * | | | | | Data[15-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x15 | Data[0]: Audio Device Index | Get register value |
+ * | | | | | Data[1-6]: reserved | from codec |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * Note 1: See <List of Sample Format> for available value of
+ * Sample Format;
+ * Note 2: See <List of Audio Channels> for available value of Channels;
+ * Note 3: Sample Rate of Set Parameters for an Audio TX Instance
+ * Command and Set Parameters for an Audio RX Instance Command is
+ * in little-endian format.
+ *
+ * SRTM Audio Control Category Response Command Table:
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | Category | Version | Type | Command | Data | Function |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x00 | Data[0]: Audio Device Index | Reply for Open |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x01 | Data[0]: Audio Device Index | Reply for Start |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x02 | Data[0]: Audio Device Index | Reply for Pause |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x03 | Data[0]: Audio Device Index | Reply for Resume |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x04 | Data[0]: Audio Device Index | Reply for Stop |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x05 | Data[0]: Audio Device Index | Reply for Close |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x06 | Data[0]: Audio Device Index | Reply for Set Param |
+ * | | | | | Data[1]: Return code | for a TX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x07 | Data[0]: Audio Device Index | Reply for Set |
+ * | | | | | Data[1]: Return code | TX Buffer |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x08 | Data[0]: Audio Device Index | Reply for Suspend |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x09 | Data[0]: Audio Device Index | Reply for Resume |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0A | Data[0]: Audio Device Index | Reply for Open |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0B | Data[0]: Audio Device Index | Reply for Start |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0C | Data[0]: Audio Device Index | Reply for Pause |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0D | Data[0]: Audio Device Index | Reply for Resume |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0E | Data[0]: Audio Device Index | Reply for Stop |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0F | Data[0]: Audio Device Index | Reply for Close |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x10 | Data[0]: Audio Device Index | Reply for Set Param |
+ * | | | | | Data[1]: Return code | for a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x11 | Data[0]: Audio Device Index | Reply for Set |
+ * | | | | | Data[1]: Return code | RX Buffer |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x12 | Data[0]: Audio Device Index | Reply for Suspend |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x13 | Data[0]: Audio Device Index | Reply for Resume |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x14 | Data[0]: Audio Device Index | Reply for Set codec |
+ * | | | | | Data[1]: Return code | register value |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x15 | Data[0]: Audio Device Index | Reply for Get codec |
+ * | | | | | Data[1]: Return code | register value |
+ * | | | | | Data[2-6]: reserved | |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-14]: value | |
+ * | | | | | Data[15-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ *
+ * SRTM Audio Control Category Notification Command Table:
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | Category | Version | Type | Command | Data | Function |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x02 | 0x00 | Data[0]: Audio Device Index | Notify one TX period |
+ * | | | | | | is finished |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x02 | 0x01 | Data[0]: Audio Device Index | Notify one RX period |
+ * | | | | | | is finished |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ *
+ * List of Sample Format:
+ * +------------------+-----------------------+
+ * | Sample Format | Description |
+ * +------------------+-----------------------+
+ * | 0x0 | S16_LE |
+ * +------------------+-----------------------+
+ * | 0x1 | S24_LE |
+ * +------------------+-----------------------+
+ *
+ * List of Audio Channels
+ * +------------------+-----------------------+
+ * | Audio Channel | Description |
+ * +------------------+-----------------------+
+ * | 0x0 | Left Channel |
+ * +------------------+-----------------------+
+ * | 0x1 | Right Channel |
+ * +------------------+---------------- ------+
+ * | 0x2 | Left & Right Channel |
+ * +------------------+-----------------------+
+ *
+ */
+
+#ifndef _IMX_PCM_RPMSG_H
+#define _IMX_PCM_RPMSG_H
+
+#include <linux/pm_qos.h>
+#include <linux/interrupt.h>
+#include <sound/dmaengine_pcm.h>
+
+#define RPMSG_TIMEOUT 1000
+
+/* RPMSG Command (TYPE A)*/
+#define TX_OPEN 0x0
+#define TX_START 0x1
+#define TX_PAUSE 0x2
+#define TX_RESTART 0x3
+#define TX_TERMINATE 0x4
+#define TX_CLOSE 0x5
+#define TX_HW_PARAM 0x6
+#define TX_BUFFER 0x7
+#define TX_SUSPEND 0x8
+#define TX_RESUME 0x9
+
+#define RX_OPEN 0xA
+#define RX_START 0xB
+#define RX_PAUSE 0xC
+#define RX_RESTART 0xD
+#define RX_TERMINATE 0xE
+#define RX_CLOSE 0xF
+#define RX_HW_PARAM 0x10
+#define RX_BUFFER 0x11
+#define RX_SUSPEND 0x12
+#define RX_RESUME 0x13
+#define SET_CODEC_VALUE 0x14
+#define GET_CODEC_VALUE 0x15
+#define TX_POINTER 0x16
+#define RX_POINTER 0x17
+/* Total msg numver for type A */
+#define MSG_TYPE_A_NUM 0x18
+
+/* RPMSG Command (TYPE C)*/
+#define TX_PERIOD_DONE 0x0
+#define RX_PERIOD_DONE 0x1
+/* Total msg numver for type C */
+#define MSG_TYPE_C_NUM 0x2
+
+#define MSG_MAX_NUM (MSG_TYPE_A_NUM + MSG_TYPE_C_NUM)
+
+#define MSG_TYPE_A 0x0
+#define MSG_TYPE_B 0x1
+#define MSG_TYPE_C 0x2
+
+#define RESP_NONE 0x0
+#define RESP_NOT_ALLOWED 0x1
+#define RESP_SUCCESS 0x2
+#define RESP_FAILED 0x3
+
+#define RPMSG_S16_LE 0x0
+#define RPMSG_S24_LE 0x1
+#define RPMSG_S32_LE 0x2
+#define RPMSG_DSD_U16_LE 49 /* SNDRV_PCM_FORMAT_DSD_U16_LE */
+#define RPMSG_DSD_U24_LE 0x4
+#define RPMSG_DSD_U32_LE 50 /* SNDRV_PCM_FORMAT_DSD_U32_LE */
+
+#define RPMSG_CH_LEFT 0x0
+#define RPMSG_CH_RIGHT 0x1
+#define RPMSG_CH_STEREO 0x2
+
+#define WORK_MAX_NUM 0x30
+
+/* Category define */
+#define IMX_RMPSG_LIFECYCLE 1
+#define IMX_RPMSG_PMIC 2
+#define IMX_RPMSG_AUDIO 3
+#define IMX_RPMSG_KEY 4
+#define IMX_RPMSG_GPIO 5
+#define IMX_RPMSG_RTC 6
+#define IMX_RPMSG_SENSOR 7
+
+/* rpmsg version */
+#define IMX_RMPSG_MAJOR 1
+#define IMX_RMPSG_MINOR 0
+
+#define TX SNDRV_PCM_STREAM_PLAYBACK
+#define RX SNDRV_PCM_STREAM_CAPTURE
+
+/**
+ * struct rpmsg_head: rpmsg header structure
+ *
+ * @cate: category
+ * @major: major version
+ * @minor: minor version
+ * @type: message type (A/B/C)
+ * @cmd: message command
+ * @reserved: reserved space
+ */
+struct rpmsg_head {
+ u8 cate;
+ u8 major;
+ u8 minor;
+ u8 type;
+ u8 cmd;
+ u8 reserved[5];
+} __packed;
+
+/**
+ * struct param_s: sent rpmsg parameter
+ *
+ * @audioindex: audio instance index
+ * @format: audio format
+ * @channels: audio channel number
+ * @rate: sample rate
+ * @buffer_addr: dma buffer physical address or register for SET_CODEC_VALUE
+ * @buffer_size: dma buffer size or register value for SET_CODEC_VALUE
+ * @period_size: period size
+ * @buffer_tail: current period index
+ */
+struct param_s {
+ unsigned char audioindex;
+ unsigned char format;
+ unsigned char channels;
+ unsigned int rate;
+ unsigned int buffer_addr;
+ unsigned int buffer_size;
+ unsigned int period_size;
+ unsigned int buffer_tail;
+} __packed;
+
+/**
+ * struct param_s: send rpmsg parameter
+ *
+ * @audioindex: audio instance index
+ * @resp: response value
+ * @reserved1: reserved space
+ * @buffer_offset: the consumed offset of buffer
+ * @reg_addr: register addr of codec
+ * @reg_data: register value of codec
+ * @reserved2: reserved space
+ * @buffer_tail: current period index
+ */
+struct param_r {
+ unsigned char audioindex;
+ unsigned char resp;
+ unsigned char reserved1[1];
+ unsigned int buffer_offset;
+ unsigned int reg_addr;
+ unsigned int reg_data;
+ unsigned char reserved2[4];
+ unsigned int buffer_tail;
+} __packed;
+
+/* Struct of sent message */
+struct rpmsg_s_msg {
+ struct rpmsg_head header;
+ struct param_s param;
+};
+
+/* Struct of received message */
+struct rpmsg_r_msg {
+ struct rpmsg_head header;
+ struct param_r param;
+};
+
+/* Struct of rpmsg */
+struct rpmsg_msg {
+ struct rpmsg_s_msg s_msg;
+ struct rpmsg_r_msg r_msg;
+};
+
+/* Struct of rpmsg for workqueue */
+struct work_of_rpmsg {
+ struct rpmsg_info *info;
+ /* Sent msg for each work */
+ struct rpmsg_msg msg;
+ struct work_struct work;
+};
+
+/* Struct of timer */
+struct stream_timer {
+ struct timer_list timer;
+ struct rpmsg_info *info;
+ struct snd_pcm_substream *substream;
+};
+
+typedef void (*dma_callback)(void *arg);
+
+/**
+ * struct rpmsg_info: rpmsg audio information
+ *
+ * @rpdev: pointer of rpmsg_device
+ * @dev: pointer for imx_pcm_rpmsg device
+ * @cmd_complete: command is finished
+ * @pm_qos_req: request of pm qos
+ * @r_msg: received rpmsg
+ * @msg: array of rpmsg
+ * @notify: notification msg (type C) for TX & RX
+ * @notify_updated: notification flag for TX & RX
+ * @rpmsg_wq: rpmsg workqueue
+ * @work_list: array of work list for workqueue
+ * @work_write_index: write index of work list
+ * @work_read_index: read index of work list
+ * @msg_drop_count: counter of dropped msg for TX & RX
+ * @num_period: period number for TX & RX
+ * @callback_param: parameter for period elapse callback for TX & RX
+ * @callback: period elapse callback for TX & RX
+ * @send_message: function pointer for send message
+ * @lock: spin lock for TX & RX
+ * @wq_lock: lock for work queue
+ * @msg_lock: lock for send message
+ * @stream_timer: timer for tigger workqueue
+ */
+struct rpmsg_info {
+ struct rpmsg_device *rpdev;
+ struct device *dev;
+ struct completion cmd_complete;
+ struct pm_qos_request pm_qos_req;
+
+ /* Received msg (global) */
+ struct rpmsg_r_msg r_msg;
+ struct rpmsg_msg msg[MSG_MAX_NUM];
+ /* period done */
+ struct rpmsg_msg notify[2];
+ bool notify_updated[2];
+
+ struct workqueue_struct *rpmsg_wq;
+ struct work_of_rpmsg work_list[WORK_MAX_NUM];
+ int work_write_index;
+ int work_read_index;
+ int msg_drop_count[2];
+ int num_period[2];
+ void *callback_param[2];
+ dma_callback callback[2];
+ int (*send_message)(struct rpmsg_msg *msg, struct rpmsg_info *info);
+ spinlock_t lock[2]; /* spin lock for resource protection */
+ spinlock_t wq_lock; /* spin lock for resource protection */
+ struct mutex msg_lock; /* mutex for resource protection */
+ struct stream_timer stream_timer[2];
+};
+
+#define IMX_PCM_DRV_NAME "imx_pcm_rpmsg"
+
+#endif /* IMX_PCM_RPMSG_H */
diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h
new file mode 100644
index 000000000..ac5f57c3c
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ */
+
+#ifndef _IMX_PCM_H
+#define _IMX_PCM_H
+
+#include <linux/dma/imx-dma.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)
+
+struct imx_pcm_fiq_params {
+ int irq;
+ void __iomem *base;
+
+ /* Pointer to original ssi driver to setup tx rx sizes */
+ struct snd_dmaengine_dai_dma_data *dma_params_rx;
+ struct snd_dmaengine_dai_dma_data *dma_params_tx;
+};
+
+#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA)
+int imx_pcm_dma_init(struct platform_device *pdev);
+#else
+static inline int imx_pcm_dma_init(struct platform_device *pdev)
+{
+ return -ENODEV;
+}
+#endif
+
+#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ)
+int imx_pcm_fiq_init(struct platform_device *pdev,
+ struct imx_pcm_fiq_params *params);
+void imx_pcm_fiq_exit(struct platform_device *pdev);
+#else
+static inline int imx_pcm_fiq_init(struct platform_device *pdev,
+ struct imx_pcm_fiq_params *params)
+{
+ return -ENODEV;
+}
+
+static inline void imx_pcm_fiq_exit(struct platform_device *pdev)
+{
+}
+#endif
+
+#endif /* _IMX_PCM_H */
diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c
new file mode 100644
index 000000000..76c6febf2
--- /dev/null
+++ b/sound/soc/fsl/imx-rpmsg.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2017-2020 NXP
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include "imx-pcm-rpmsg.h"
+
+struct imx_rpmsg {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+ unsigned long sysclk;
+};
+
+static const struct snd_soc_dapm_widget imx_rpmsg_dapm_widgets[] = {
+ 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("Main MIC", NULL),
+};
+
+static int imx_rpmsg_late_probe(struct snd_soc_card *card)
+{
+ struct imx_rpmsg *data = 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 = asoc_rtd_to_codec(rtd, 0);
+ struct device *dev = card->dev;
+ int ret;
+
+ if (!data->sysclk)
+ return 0;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, data->sysclk, 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 imx_rpmsg_probe(struct platform_device *pdev)
+{
+ struct snd_soc_dai_link_component *dlc;
+ struct device *dev = pdev->dev.parent;
+ /* rpmsg_pdev is the platform device for the rpmsg node that probed us */
+ struct platform_device *rpmsg_pdev = to_platform_device(dev);
+ struct device_node *np = rpmsg_pdev->dev.of_node;
+ struct of_phandle_args args;
+ struct imx_rpmsg *data;
+ int ret = 0;
+
+ dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = of_reserved_mem_device_init_by_idx(&pdev->dev, np, 0);
+ if (ret)
+ dev_warn(&pdev->dev, "no reserved DMA memory\n");
+
+ data->dai.cpus = &dlc[0];
+ data->dai.num_cpus = 1;
+ data->dai.platforms = &dlc[1];
+ data->dai.num_platforms = 1;
+ data->dai.codecs = &dlc[2];
+ data->dai.num_codecs = 1;
+
+ data->dai.name = "rpmsg hifi";
+ data->dai.stream_name = "rpmsg hifi";
+ data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBC_CFC;
+
+ /*
+ * i.MX rpmsg sound cards work on codec slave mode. MCLK will be
+ * disabled by CPU DAI driver in hw_free(). Some codec requires MCLK
+ * present at power up/down sequence. So need to set ignore_pmdown_time
+ * to power down codec immediately before MCLK is turned off.
+ */
+ data->dai.ignore_pmdown_time = 1;
+
+ /* Optional codec node */
+ ret = of_parse_phandle_with_fixed_args(np, "audio-codec", 0, 0, &args);
+ if (ret) {
+ data->dai.codecs->dai_name = "snd-soc-dummy-dai";
+ data->dai.codecs->name = "snd-soc-dummy";
+ } else {
+ struct clk *clk;
+
+ data->dai.codecs->of_node = args.np;
+ ret = snd_soc_get_dai_name(&args, &data->dai.codecs->dai_name);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to get codec_dai_name\n");
+ goto fail;
+ }
+
+ clk = devm_get_clk_from_child(&pdev->dev, args.np, NULL);
+ if (!IS_ERR(clk))
+ data->sysclk = clk_get_rate(clk);
+ }
+
+ data->dai.cpus->dai_name = dev_name(&rpmsg_pdev->dev);
+ data->dai.platforms->name = IMX_PCM_DRV_NAME;
+ data->dai.playback_only = true;
+ data->dai.capture_only = true;
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+
+ if (of_property_read_bool(np, "fsl,rpmsg-out"))
+ data->dai.capture_only = false;
+
+ if (of_property_read_bool(np, "fsl,rpmsg-in"))
+ data->dai.playback_only = false;
+
+ if (data->dai.playback_only && data->dai.capture_only) {
+ dev_err(&pdev->dev, "no enabled rpmsg DAI link\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ data->card.dapm_widgets = imx_rpmsg_dapm_widgets;
+ data->card.num_dapm_widgets = ARRAY_SIZE(imx_rpmsg_dapm_widgets);
+ data->card.late_probe = imx_rpmsg_late_probe;
+ /*
+ * Inoder to use common api to get card name and audio routing.
+ * Use parent of_node for this device, revert it after finishing using
+ */
+ data->card.dev->of_node = np;
+
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+
+ if (of_property_read_bool(np, "audio-routing")) {
+ ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
+ if (ret) {
+ dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret);
+ goto fail;
+ }
+ }
+
+ 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_probe(&pdev->dev, ret, "snd_soc_register_card failed\n");
+ goto fail;
+ }
+
+fail:
+ pdev->dev.of_node = NULL;
+ return ret;
+}
+
+static struct platform_driver imx_rpmsg_driver = {
+ .driver = {
+ .name = "imx-audio-rpmsg",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = imx_rpmsg_probe,
+};
+module_platform_driver(imx_rpmsg_driver);
+
+MODULE_DESCRIPTION("Freescale SoC Audio RPMSG Machine Driver");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_ALIAS("platform:imx-audio-rpmsg");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c
new file mode 100644
index 000000000..580a0d963
--- /dev/null
+++ b/sound/soc/fsl/imx-sgtl5000.c
@@ -0,0 +1,226 @@
+// 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(asoc_rtd_to_codec(rtd, 0), 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;
+ struct snd_soc_dai_link_component *comp;
+ 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_dbg(&pdev->dev, "failed to find SSI platform device\n");
+ ret = -EPROBE_DEFER;
+ goto fail;
+ }
+ put_device(&ssi_pdev->dev);
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev) {
+ dev_dbg(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EPROBE_DEFER;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto put_device;
+ }
+
+ comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL);
+ if (!comp) {
+ ret = -ENOMEM;
+ goto put_device;
+ }
+
+ data->codec_clk = clk_get(&codec_dev->dev, NULL);
+ if (IS_ERR(data->codec_clk)) {
+ ret = PTR_ERR(data->codec_clk);
+ goto put_device;
+ }
+
+ data->clk_frequency = clk_get_rate(data->codec_clk);
+
+ data->dai.cpus = &comp[0];
+ data->dai.codecs = &comp[1];
+ data->dai.platforms = &comp[2];
+
+ data->dai.num_cpus = 1;
+ data->dai.num_codecs = 1;
+ data->dai.num_platforms = 1;
+
+ data->dai.name = "HiFi";
+ data->dai.stream_name = "HiFi";
+ data->dai.codecs->dai_name = "sgtl5000";
+ data->dai.codecs->of_node = codec_np;
+ data->dai.cpus->of_node = ssi_np;
+ data->dai.platforms->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_CBP_CFP;
+
+ data->card.dev = &pdev->dev;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto put_device;
+ ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
+ if (ret)
+ goto put_device;
+ 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_probe(&pdev->dev, ret, "snd_soc_register_card failed\n");
+ goto put_device;
+ }
+
+ of_node_put(ssi_np);
+ of_node_put(codec_np);
+
+ return 0;
+
+put_device:
+ put_device(&codec_dev->dev);
+fail:
+ if (data && !IS_ERR(data->codec_clk))
+ clk_put(data->codec_clk);
+ of_node_put(ssi_np);
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static int imx_sgtl5000_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card);
+
+ clk_put(data->codec_clk);
+
+ return 0;
+}
+
+static const struct of_device_id imx_sgtl5000_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-sgtl5000", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_sgtl5000_dt_ids);
+
+static struct platform_driver imx_sgtl5000_driver = {
+ .driver = {
+ .name = "imx-sgtl5000",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_sgtl5000_dt_ids,
+ },
+ .probe = imx_sgtl5000_probe,
+ .remove = imx_sgtl5000_remove,
+};
+module_platform_driver(imx_sgtl5000_driver);
+
+MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
+MODULE_DESCRIPTION("Freescale i.MX SGTL5000 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-sgtl5000");
diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c
new file mode 100644
index 000000000..4446fba75
--- /dev/null
+++ b/sound/soc/fsl/imx-spdif.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright (C) 2013 Freescale Semiconductor, Inc.
+
+#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;
+ struct snd_soc_dai_link_component *comp;
+ 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);
+ comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL);
+ if (!data || !comp) {
+ ret = -ENOMEM;
+ goto end;
+ }
+
+ data->dai.cpus = &comp[0];
+ data->dai.codecs = &comp[1];
+ data->dai.platforms = &comp[2];
+
+ data->dai.num_cpus = 1;
+ data->dai.num_codecs = 1;
+ data->dai.num_platforms = 1;
+
+ data->dai.name = "S/PDIF PCM";
+ data->dai.stream_name = "S/PDIF PCM";
+ data->dai.codecs->dai_name = "snd-soc-dummy-dai";
+ data->dai.codecs->name = "snd-soc-dummy";
+ data->dai.cpus->of_node = spdif_np;
+ data->dai.platforms->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_probe(&pdev->dev, ret, "snd_soc_register_card failed\n");
+
+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.h b/sound/soc/fsl/imx-ssi.h
new file mode 100644
index 000000000..2d30d8224
--- /dev/null
+++ b/sound/soc/fsl/imx-ssi.h
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#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/dma/imx-dma.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 000000000..3f7ccae3f
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_dma.c
@@ -0,0 +1,457 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// 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;
+}
+
+/**
+ * psc_dma_trigger: start and stop the DMA transfer.
+ * @component: triggered component
+ * @substream: triggered substream
+ * @cmd: triggered command
+ *
+ * This function is called by ALSA to start, stop, pause, and resume the DMA
+ * transfer of data.
+ */
+static int psc_dma_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_soc_dai *dai = asoc_rtd_to_cpu(rtd, 0);
+ 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;
+
+ return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, card->dev,
+ size);
+}
+
+static const struct snd_soc_component_driver mpc5200_audio_dma_component = {
+ .name = DRV_NAME,
+ .open = psc_dma_open,
+ .close = psc_dma_close,
+ .pointer = psc_dma_pointer,
+ .trigger = psc_dma_trigger,
+ .pcm_construct = psc_dma_new,
+};
+
+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%d", 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 000000000..d7ee33b5b
--- /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 000000000..a082ae636
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_psc_ac97.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip.
+//
+// Copyright (C) 2009 Jon Smirl, Digispeaker
+// Author: Jon Smirl <jonsmirl@gmail.com>
+
+#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",
+ .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",
+ .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 000000000..73f3e61f2
--- /dev/null
+++ b/sound/soc/fsl/mpc5200_psc_i2s.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// 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 = asoc_substream_to_rtd(substream);
+ struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ 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",
+ .legacy_dai_naming = 1,
+};
+
+/* ---------------------------------------------------------------------
+ * 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 000000000..e71a992fb
--- /dev/null
+++ b/sound/soc/fsl/mpc8610_hpcd.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale MPC8610HPCD ALSA SoC Machine driver
+//
+// Author: Timur Tabi <timur@freescale.com>
+//
+// Copyright 2007-2010 Freescale Semiconductor, Inc.
+
+#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 = asoc_substream_to_rtd(substream);
+ 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(asoc_rtd_to_codec(rtd, 0), 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(asoc_rtd_to_codec(rtd, 0), 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;
+ struct snd_soc_dai_link_component *comp;
+ int ret;
+ 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;
+ }
+
+ comp = devm_kzalloc(&pdev->dev, 6 * sizeof(*comp), GFP_KERNEL);
+ if (!comp) {
+ ret = -ENOMEM;
+ goto error_alloc;
+ }
+
+ machine_data->dai[0].cpus = &comp[0];
+ machine_data->dai[0].codecs = &comp[1];
+ machine_data->dai[0].platforms = &comp[2];
+
+ machine_data->dai[0].num_cpus = 1;
+ machine_data->dai[0].num_codecs = 1;
+ machine_data->dai[0].num_platforms = 1;
+
+ machine_data->dai[1].cpus = &comp[3];
+ machine_data->dai[1].codecs = &comp[4];
+ machine_data->dai[1].platforms = &comp[5];
+
+ machine_data->dai[1].num_cpus = 1;
+ machine_data->dai[1].num_codecs = 1;
+ machine_data->dai[1].num_platforms = 1;
+
+ machine_data->dai[0].cpus->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].codecs->of_node = codec_np;
+
+ /* The DAI name from the codec (snd_soc_dai_driver.name) */
+ machine_data->dai[0].codecs->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_CBP_CFP;
+ 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_CBC_CFC;
+ 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_CBP_CFP;
+ 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_CBC_CFC;
+ 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_CBP_CFP;
+ 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_CBC_CFC;
+ 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_CBP_CFP;
+ 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_CBC_CFC;
+ 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].platforms->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].platforms->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");
+ of_node_put(guts_np);
+ return -EINVAL;
+ }
+ guts_phys = res.start;
+ of_node_put(guts_np);
+
+ 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/p1022_ds.c b/sound/soc/fsl/p1022_ds.c
new file mode 100644
index 000000000..b45742931
--- /dev/null
+++ b/sound/soc/fsl/p1022_ds.c
@@ -0,0 +1,461 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale P1022DS ALSA SoC Machine driver
+//
+// Author: Timur Tabi <timur@freescale.com>
+//
+// Copyright 2010 Freescale Semiconductor, Inc.
+
+#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 = asoc_substream_to_rtd(substream);
+ 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(asoc_rtd_to_codec(rtd, 0), 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(asoc_rtd_to_codec(rtd, 0), 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;
+ struct snd_soc_dai_link_component *comp;
+ int ret;
+ 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;
+ }
+
+ comp = devm_kzalloc(&pdev->dev, 6 * sizeof(*comp), GFP_KERNEL);
+ if (!comp) {
+ ret = -ENOMEM;
+ goto error_put;
+ }
+
+ mdata->dai[0].cpus = &comp[0];
+ mdata->dai[0].codecs = &comp[1];
+ mdata->dai[0].platforms = &comp[2];
+
+ mdata->dai[0].num_cpus = 1;
+ mdata->dai[0].num_codecs = 1;
+ mdata->dai[0].num_platforms = 1;
+
+ mdata->dai[1].cpus = &comp[3];
+ mdata->dai[1].codecs = &comp[4];
+ mdata->dai[1].platforms = &comp[5];
+
+ mdata->dai[1].num_cpus = 1;
+ mdata->dai[1].num_codecs = 1;
+ mdata->dai[1].num_platforms = 1;
+
+
+ mdata->dai[0].cpus->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].codecs->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].codecs->dai_name = "wm8776-hifi-playback";
+ mdata->dai[1].codecs->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_CBP_CFP;
+ 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_CBC_CFC;
+ 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_CBP_CFP;
+ 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_CBC_CFC;
+ 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_CBP_CFP;
+ 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_CBC_CFC;
+ 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_CBP_CFP;
+ 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_CBC_CFC;
+ 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].platforms->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].platforms->name = mdata->platform_name[1];
+ ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1],
+ &mdata->dma_channel_id[1],
+ &mdata->dma_id[1]);
+ if (ret) {
+ dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
+ goto error;
+ }
+
+ /* Initialize our DAI data structure. */
+ mdata->dai[0].stream_name = "playback";
+ mdata->dai[1].stream_name = "capture";
+ mdata->dai[0].name = mdata->dai[0].stream_name;
+ mdata->dai[1].name = mdata->dai[1].stream_name;
+
+ mdata->card.probe = p1022_ds_machine_probe;
+ mdata->card.remove = p1022_ds_machine_remove;
+ mdata->card.name = pdev->name; /* The platform driver name */
+ mdata->card.owner = THIS_MODULE;
+ mdata->card.dev = &pdev->dev;
+ mdata->card.num_links = 2;
+ mdata->card.dai_link = mdata->dai;
+
+ /* Register with ASoC */
+ ret = snd_soc_register_card(&mdata->card);
+ if (ret) {
+ dev_err(&pdev->dev, "could not register card\n");
+ goto error;
+ }
+
+ of_node_put(codec_np);
+
+ return 0;
+
+error:
+ kfree(mdata);
+error_put:
+ of_node_put(codec_np);
+ return ret;
+}
+
+/**
+ * p1022_ds_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int p1022_ds_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct machine_data *mdata =
+ container_of(card, struct machine_data, card);
+
+ snd_soc_unregister_card(card);
+ kfree(mdata);
+
+ return 0;
+}
+
+static struct platform_driver p1022_ds_driver = {
+ .probe = p1022_ds_probe,
+ .remove = p1022_ds_remove,
+ .driver = {
+ /*
+ * The name must match 'compatible' property in the device tree,
+ * in lowercase letters.
+ */
+ .name = "snd-soc-p1022ds",
+ },
+};
+
+/**
+ * p1022_ds_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init p1022_ds_init(void)
+{
+ struct device_node *guts_np;
+ struct resource res;
+
+ /* Get the physical address of the global utilities registers */
+ guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
+ if (of_address_to_resource(guts_np, 0, &res)) {
+ pr_err("snd-soc-p1022ds: missing/invalid global utils node\n");
+ of_node_put(guts_np);
+ return -EINVAL;
+ }
+ guts_phys = res.start;
+ of_node_put(guts_np);
+
+ return platform_driver_register(&p1022_ds_driver);
+}
+
+/**
+ * p1022_ds_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit p1022_ds_exit(void)
+{
+ platform_driver_unregister(&p1022_ds_driver);
+}
+
+module_init(p1022_ds_init);
+module_exit(p1022_ds_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/p1022_rdk.c b/sound/soc/fsl/p1022_rdk.c
new file mode 100644
index 000000000..b395adabe
--- /dev/null
+++ b/sound/soc/fsl/p1022_rdk.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale P1022RDK ALSA SoC Machine driver
+//
+// Author: Timur Tabi <timur@freescale.com>
+//
+// Copyright 2012 Freescale Semiconductor, Inc.
+//
+// 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 = asoc_substream_to_rtd(substream);
+ 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(asoc_rtd_to_codec(rtd, 0), 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(asoc_rtd_to_codec(rtd, 0), 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;
+ struct snd_soc_dai_link_component *comp;
+ 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;
+ }
+
+ comp = devm_kzalloc(&pdev->dev, 6 * sizeof(*comp), GFP_KERNEL);
+ if (!comp) {
+ ret = -ENOMEM;
+ goto error_put;
+ }
+
+ mdata->dai[0].cpus = &comp[0];
+ mdata->dai[0].codecs = &comp[1];
+ mdata->dai[0].platforms = &comp[2];
+
+ mdata->dai[0].num_cpus = 1;
+ mdata->dai[0].num_codecs = 1;
+ mdata->dai[0].num_platforms = 1;
+
+ mdata->dai[1].cpus = &comp[3];
+ mdata->dai[1].codecs = &comp[4];
+ mdata->dai[1].platforms = &comp[5];
+
+ mdata->dai[1].num_cpus = 1;
+ mdata->dai[1].num_codecs = 1;
+ mdata->dai[1].num_platforms = 1;
+
+ mdata->dai[0].cpus->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].codecs->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].codecs->dai_name = "wm8960-hifi";
+ mdata->dai[1].codecs->dai_name = mdata->dai[0].codecs->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_CBP_CFP;
+ 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].platforms->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].platforms->name = mdata->platform_name[1];
+ ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1],
+ &mdata->dma_channel_id[1],
+ &mdata->dma_id[1]);
+ if (ret) {
+ dev_err(&pdev->dev, "missing/invalid capture DMA phandle (ret=%i)\n",
+ ret);
+ goto error;
+ }
+
+ /* Initialize our DAI data structure. */
+ mdata->dai[0].stream_name = "playback";
+ mdata->dai[1].stream_name = "capture";
+ mdata->dai[0].name = mdata->dai[0].stream_name;
+ mdata->dai[1].name = mdata->dai[1].stream_name;
+
+ mdata->card.probe = p1022_rdk_machine_probe;
+ mdata->card.remove = p1022_rdk_machine_remove;
+ mdata->card.name = pdev->name; /* The platform driver name */
+ mdata->card.owner = THIS_MODULE;
+ mdata->card.dev = &pdev->dev;
+ mdata->card.num_links = 2;
+ mdata->card.dai_link = mdata->dai;
+
+ /* Register with ASoC */
+ ret = snd_soc_register_card(&mdata->card);
+ if (ret) {
+ dev_err(&pdev->dev, "could not register card (ret=%i)\n", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ kfree(mdata);
+error_put:
+ of_node_put(codec_np);
+ return ret;
+}
+
+/**
+ * p1022_rdk_remove: remove the platform device
+ *
+ * This function is called when the platform device is removed.
+ */
+static int p1022_rdk_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct machine_data *mdata =
+ container_of(card, struct machine_data, card);
+
+ snd_soc_unregister_card(card);
+ kfree(mdata);
+
+ return 0;
+}
+
+static struct platform_driver p1022_rdk_driver = {
+ .probe = p1022_rdk_probe,
+ .remove = p1022_rdk_remove,
+ .driver = {
+ /*
+ * The name must match 'compatible' property in the device tree,
+ * in lowercase letters.
+ */
+ .name = "snd-soc-p1022rdk",
+ },
+};
+
+/**
+ * p1022_rdk_init: machine driver initialization.
+ *
+ * This function is called when this module is loaded.
+ */
+static int __init p1022_rdk_init(void)
+{
+ struct device_node *guts_np;
+ struct resource res;
+
+ /* Get the physical address of the global utilities registers */
+ guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
+ if (of_address_to_resource(guts_np, 0, &res)) {
+ pr_err("snd-soc-p1022rdk: missing/invalid global utils node\n");
+ of_node_put(guts_np);
+ return -EINVAL;
+ }
+ guts_phys = res.start;
+ of_node_put(guts_np);
+
+ return platform_driver_register(&p1022_rdk_driver);
+}
+
+/**
+ * p1022_rdk_exit: machine driver exit
+ *
+ * This function is called when this driver is unloaded.
+ */
+static void __exit p1022_rdk_exit(void)
+{
+ platform_driver_unregister(&p1022_rdk_driver);
+}
+
+late_initcall(p1022_rdk_init);
+module_exit(p1022_rdk_exit);
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale / iVeia P1022 RDK ALSA SoC machine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c
new file mode 100644
index 000000000..997c3e66c
--- /dev/null
+++ b/sound/soc/fsl/pcm030-audio-fabric.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// 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>
+
+#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;
+};
+
+SND_SOC_DAILINK_DEFS(analog,
+ DAILINK_COMP_ARRAY(COMP_CPU("mpc5200-psc-ac97.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-hifi")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(iec958,
+ DAILINK_COMP_ARRAY(COMP_CPU("mpc5200-psc-ac97.1")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-aux")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link pcm030_fabric_dai[] = {
+{
+ .name = "AC97.0",
+ .stream_name = "AC97 Analog",
+ SND_SOC_DAILINK_REG(analog),
+},
+{
+ .name = "AC97.1",
+ .stream_name = "AC97 IEC958",
+ SND_SOC_DAILINK_REG(iec958),
+},
+};
+
+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;
+ struct snd_soc_dai_link *dai_link;
+ 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_each_card_prelinks(card, i, dai_link)
+ dai_link->platforms->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);
+ platform_device_put(pdata->codec_device);
+ }
+
+ ret = snd_soc_register_card(card);
+ if (ret) {
+ dev_err(&op->dev, "snd_soc_register_card() failed: %d\n", ret);
+ platform_device_unregister(pdata->codec_device);
+ }
+
+ 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);
+
+ snd_soc_unregister_card(pdata->card);
+ platform_device_unregister(pdata->codec_device);
+
+ return 0;
+}
+
+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");
+