summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/sound/fsl,asrc.txt3
-rw-r--r--Documentation/devicetree/bindings/sound/fsl,dsp.txt16
-rw-r--r--Documentation/devicetree/bindings/sound/fsl,easrc.txt53
-rw-r--r--Documentation/devicetree/bindings/sound/fsl,mqs.txt23
-rw-r--r--Documentation/devicetree/bindings/sound/fsl,rpmsg-i2s.txt22
-rw-r--r--Documentation/devicetree/bindings/sound/fsl,spdif.txt4
-rw-r--r--Documentation/devicetree/bindings/sound/fsl-asoc-card.txt1
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-ak4458.txt30
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-ak4497.txt27
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-ak5558.txt30
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-cdnhdmi.txt16
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-cs42888.txt25
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-mqs.txt18
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-rpmsg.txt13
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-si476x.txt24
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-wm8524.txt29
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt61
-rw-r--r--Documentation/devicetree/bindings/sound/wm8962.txt8
-rw-r--r--drivers/media/radio/radio-si476x.c18
-rw-r--r--drivers/mfd/si476x-i2c.c24
-rw-r--r--include/linux/busfreq-imx.h77
-rw-r--r--include/linux/mfd/si476x-core.h2
-rw-r--r--include/sound/dmaengine_pcm.h1
-rw-r--r--include/sound/sof.h7
-rw-r--r--include/sound/sof/dai-imx.h55
-rw-r--r--include/sound/sof/dai.h3
-rw-r--r--include/uapi/linux/mxc_asrc.h165
-rw-r--r--include/uapi/linux/mxc_dsp.h152
-rw-r--r--include/uapi/sound/sof/abi.h2
-rw-r--r--include/uapi/sound/sof/tokens.h6
-rw-r--r--sound/soc/atmel/atmel-classd.c7
-rw-r--r--sound/soc/atmel/atmel-pdmic.c7
-rw-r--r--sound/soc/bcm/cygnus-ssp.c7
-rw-r--r--sound/soc/codecs/Kconfig14
-rw-r--r--sound/soc/codecs/Makefile9
-rw-r--r--sound/soc/codecs/ak4458.c161
-rw-r--r--sound/soc/codecs/ak4458.h7
-rw-r--r--sound/soc/codecs/ak5558.c31
-rw-r--r--sound/soc/codecs/cs42xx8.c90
-rw-r--r--sound/soc/codecs/fsl_mqs.c357
-rw-r--r--sound/soc/codecs/hdmi-codec.c41
-rw-r--r--sound/soc/codecs/msm8916-wcd-analog.c12
-rw-r--r--sound/soc/codecs/rpmsg_ak4497.c1110
-rw-r--r--sound/soc/codecs/rpmsg_ak4497.h90
-rw-r--r--sound/soc/codecs/rpmsg_cs42xx8.c752
-rw-r--r--sound/soc/codecs/rpmsg_cs42xx8.h232
-rw-r--r--sound/soc/codecs/rpmsg_wm8960.c1542
-rw-r--r--sound/soc/codecs/si476x.c19
-rw-r--r--sound/soc/codecs/twl6040.c4
-rw-r--r--sound/soc/codecs/wm8524.c11
-rw-r--r--sound/soc/codecs/wm8960.c44
-rw-r--r--sound/soc/codecs/wm8962.c27
-rw-r--r--sound/soc/fsl/Kconfig255
-rw-r--r--sound/soc/fsl/Makefile54
-rw-r--r--sound/soc/fsl/fsl_asrc.c417
-rw-r--r--sound/soc/fsl/fsl_asrc.h116
-rw-r--r--sound/soc/fsl/fsl_asrc_dma.c137
-rw-r--r--sound/soc/fsl/fsl_asrc_m2m.c1046
-rw-r--r--sound/soc/fsl/fsl_audmix.c5
-rw-r--r--sound/soc/fsl/fsl_audmix.h1
-rw-r--r--sound/soc/fsl/fsl_dai.c276
-rw-r--r--sound/soc/fsl/fsl_dsd.h58
-rw-r--r--sound/soc/fsl/fsl_dsp.c1176
-rw-r--r--sound/soc/fsl/fsl_dsp.h143
-rw-r--r--sound/soc/fsl/fsl_dsp_cpu.c97
-rw-r--r--sound/soc/fsl/fsl_dsp_cpu.h16
-rw-r--r--sound/soc/fsl/fsl_dsp_library_load.c642
-rw-r--r--sound/soc/fsl/fsl_dsp_library_load.h92
-rw-r--r--sound/soc/fsl/fsl_dsp_platform.h21
-rw-r--r--sound/soc/fsl/fsl_dsp_platform_compress.c480
-rw-r--r--sound/soc/fsl/fsl_dsp_pool.c150
-rw-r--r--sound/soc/fsl/fsl_dsp_pool.h113
-rw-r--r--sound/soc/fsl/fsl_dsp_proxy.c858
-rw-r--r--sound/soc/fsl/fsl_dsp_proxy.h520
-rw-r--r--sound/soc/fsl/fsl_dsp_xaf_api.c490
-rw-r--r--sound/soc/fsl/fsl_dsp_xaf_api.h160
-rw-r--r--sound/soc/fsl/fsl_easrc.c2533
-rw-r--r--sound/soc/fsl/fsl_easrc.h698
-rw-r--r--sound/soc/fsl/fsl_easrc_dma.c493
-rw-r--r--sound/soc/fsl/fsl_easrc_m2m.c966
-rw-r--r--sound/soc/fsl/fsl_esai.c219
-rw-r--r--sound/soc/fsl/fsl_hdmi.c750
-rw-r--r--sound/soc/fsl/fsl_micfil.c1682
-rw-r--r--sound/soc/fsl/fsl_micfil.h53
-rw-r--r--sound/soc/fsl/fsl_rpmsg_i2s.c425
-rw-r--r--sound/soc/fsl/fsl_rpmsg_i2s.h454
-rw-r--r--sound/soc/fsl/fsl_sai.c939
-rw-r--r--sound/soc/fsl/fsl_sai.h222
-rw-r--r--sound/soc/fsl/fsl_spdif.c686
-rw-r--r--sound/soc/fsl/fsl_spdif.h1
-rw-r--r--sound/soc/fsl/fsl_ssi.c4
-rw-r--r--sound/soc/fsl/hdmi_pcm.S246
-rw-r--r--sound/soc/fsl/imx-ak4458.c417
-rw-r--r--sound/soc/fsl/imx-ak4497.c269
-rw-r--r--sound/soc/fsl/imx-ak5558.c485
-rw-r--r--sound/soc/fsl/imx-cdnhdmi.c561
-rw-r--r--sound/soc/fsl/imx-cs42888.c502
-rw-r--r--sound/soc/fsl/imx-dsp.c227
-rw-r--r--sound/soc/fsl/imx-hdmi-dma.c1193
-rw-r--r--sound/soc/fsl/imx-hdmi.c117
-rw-r--r--sound/soc/fsl/imx-hdmi.h106
-rw-r--r--sound/soc/fsl/imx-micfil.c184
-rw-r--r--sound/soc/fsl/imx-mqs.c291
-rw-r--r--sound/soc/fsl/imx-pcm-dma-v2.c268
-rw-r--r--sound/soc/fsl/imx-pcm-rpmsg.c893
-rw-r--r--sound/soc/fsl/imx-pcm.h14
-rw-r--r--sound/soc/fsl/imx-pdm.c207
-rw-r--r--sound/soc/fsl/imx-rpmsg.c175
-rw-r--r--sound/soc/fsl/imx-si476x.c194
-rw-r--r--sound/soc/fsl/imx-sii902x.c221
-rw-r--r--sound/soc/fsl/imx-spdif.c31
-rw-r--r--sound/soc/fsl/imx-ssi.c4
-rw-r--r--sound/soc/fsl/imx-wm8524.c315
-rw-r--r--sound/soc/fsl/imx-wm8958.c587
-rw-r--r--sound/soc/fsl/imx-wm8960.c700
-rw-r--r--sound/soc/fsl/imx-wm8962.c860
-rw-r--r--sound/soc/kirkwood/kirkwood-i2s.c4
-rw-r--r--sound/soc/mediatek/common/mtk-btcvsd.c4
-rw-r--r--sound/soc/mediatek/mt2701/mt2701-afe-pcm.c4
-rw-r--r--sound/soc/mediatek/mt8173/mt8173-afe-pcm.c4
-rw-r--r--sound/soc/mxs/mxs-saif.c8
-rw-r--r--sound/soc/qcom/lpass-platform.c5
-rw-r--r--sound/soc/soc-core.c2
-rw-r--r--sound/soc/soc-pcm.c26
-rw-r--r--sound/soc/sof/core.c132
-rw-r--r--sound/soc/sof/intel/apl.c5
-rw-r--r--sound/soc/sof/intel/bdw.c10
-rw-r--r--sound/soc/sof/intel/byt.c20
-rw-r--r--sound/soc/sof/intel/cnl.c5
-rw-r--r--sound/soc/sof/nocodec.c12
-rw-r--r--sound/soc/sof/ops.h26
-rw-r--r--sound/soc/sof/pcm.c29
-rw-r--r--sound/soc/sof/sof-acpi-dev.c11
-rw-r--r--sound/soc/sof/sof-of-dev.c20
-rw-r--r--sound/soc/sof/sof-pci-dev.c22
-rw-r--r--sound/soc/sof/sof-priv.h15
-rw-r--r--sound/soc/sof/topology.c152
-rw-r--r--sound/soc/sof/trace.c4
-rw-r--r--sound/soc/sof/utils.c60
-rw-r--r--sound/soc/sprd/sprd-mcdt.c4
-rw-r--r--sound/soc/sti/sti_uniperif.c4
-rw-r--r--sound/soc/stm/stm32_i2s.c5
-rw-r--r--sound/soc/stm/stm32_sai.c4
-rw-r--r--sound/soc/stm/stm32_spdifrx.c4
-rw-r--r--sound/soc/sunxi/sun4i-i2s.c4
-rw-r--r--sound/soc/uniphier/aio-dma.c4
-rw-r--r--sound/soc/xilinx/xlnx_formatter_pcm.c2
-rw-r--r--sound/soc/xtensa/xtfpga-i2s.c1
148 files changed, 31565 insertions, 1051 deletions
diff --git a/Documentation/devicetree/bindings/sound/fsl,asrc.txt b/Documentation/devicetree/bindings/sound/fsl,asrc.txt
index 1d4d9f938689..cd2bd3daa7e1 100644
--- a/Documentation/devicetree/bindings/sound/fsl,asrc.txt
+++ b/Documentation/devicetree/bindings/sound/fsl,asrc.txt
@@ -8,7 +8,8 @@ three substreams within totally 10 channels.
Required properties:
- - compatible : Contains "fsl,imx35-asrc" or "fsl,imx53-asrc".
+ - compatible : Contains "fsl,imx35-asrc", "fsl,imx53-asrc",
+ "fsl,imx8qm-asrc0" or "fsl,imx8qm-asrc1".
- reg : Offset and length of the register set for the device.
diff --git a/Documentation/devicetree/bindings/sound/fsl,dsp.txt b/Documentation/devicetree/bindings/sound/fsl,dsp.txt
new file mode 100644
index 000000000000..84bc228b4e32
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/fsl,dsp.txt
@@ -0,0 +1,16 @@
+NXP DSP
+
+The IP is from Cadence.
+
+Required properties:
+
+ - compatible : Contains "fsl,imx8qxp-dsp".
+ - reg : Offset and length of the register set for the device.
+
+Example:
+
+dsp: dsp@596e8000 {
+ compatible = "fsl,imx8qxp-dsp";
+ reg = <0x0 0x596e8000 0x0 0x88000>;
+ status = "okay";
+};
diff --git a/Documentation/devicetree/bindings/sound/fsl,easrc.txt b/Documentation/devicetree/bindings/sound/fsl,easrc.txt
new file mode 100644
index 000000000000..569ff3f50317
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/fsl,easrc.txt
@@ -0,0 +1,53 @@
+Freescale Asynchronous Sample Rate Converter (ASRC) Controller
+
+The Asynchronous Sample Rate Converter (ASRC) converts the sampling rate of a
+signal associated with an input clock into a signal associated with a different
+output clock. The driver currently works as a Front End of DPCM with other Back
+Ends Audio controller such as ESAI, SSI and SAI. It has four context to support
+four substreams within totally 32 channels.
+
+Required properties:
+
+ - compatible : Contains "fsl,imx8mn-easrc".
+
+ - reg : Offset and length of the register set for the device.
+
+ - interrupts : Contains the asrc interrupt.
+
+ - dmas : Generic dma devicetree binding as described in
+ Documentation/devicetree/bindings/dma/dma.txt.
+
+ - dma-names : Contains "ctx0_rx", "ctx0_tx", "ctx1_rx", "ctx1_tx",
+ "ctx2_rx", "ctx2_tx", "ctx3_rx", "ctx3_tx".
+
+ - clocks : Contains an entry for each entry in clock-names.
+
+ - clock-names : Contains the following entries
+ "mem" Peripheral clock to driver module.
+
+ - fsl,easrc-ram-script-name : The coefficient table for the filters
+ - fsl,asrc-rate : Defines a mutual sample rate used by DPCM Back Ends.
+
+ - fsl,asrc-width : Defines a mutual sample width used by DPCM Back Ends.
+
+Example:
+
+easrc: easrc@300C0000 {
+ compatible = "fsl,imx8mn-easrc";
+ reg = <0x0 0x300C0000 0x0 0x10000>;
+ interrupts = <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk IMX8MN_CLK_ASRC_ROOT>;
+ clock-names = "mem";
+ dmas = <&sdma2 16 23 0> , <&sdma2 17 23 0>,
+ <&sdma2 18 23 0> , <&sdma2 19 23 0>,
+ <&sdma2 20 23 0> , <&sdma2 21 23 0>,
+ <&sdma2 22 23 0> , <&sdma2 23 23 0>;
+ dma-names = "ctx0_rx", "ctx0_tx",
+ "ctx1_rx", "ctx1_tx",
+ "ctx2_rx", "ctx2_tx",
+ "ctx3_rx", "ctx3_tx";
+ fsl,easrc-ram-script-name = "imx/easrc/easrc-imx8mn.bin";
+ fsl,asrc-rate = <8000>;
+ fsl,asrc-width = <16>;
+ status = "disabled";
+};
diff --git a/Documentation/devicetree/bindings/sound/fsl,mqs.txt b/Documentation/devicetree/bindings/sound/fsl,mqs.txt
new file mode 100644
index 000000000000..092083d3a335
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/fsl,mqs.txt
@@ -0,0 +1,23 @@
+fsl,mqs audio CODEC
+
+Required properties:
+
+ - compatible : must contain one of "fsl,imx6sx-mqs", "fsl,codec-mqs"
+ "fsl,imx8qm-mqs".
+
+ - clocks : a list of phandles + clock-specifiers, one for each entry in
+ clock-names
+
+ - clock-names : must contain "mclk"
+
+ - gpr : the gpr node.
+
+Example:
+
+mqs: mqs {
+ compatible = "fsl,imx6sx-mqs";
+ gpr = <&gpr>;
+ clocks = <&clks IMX6SX_CLK_SAI1>;
+ clock-names = "mclk";
+ status = "disabled";
+};
diff --git a/Documentation/devicetree/bindings/sound/fsl,rpmsg-i2s.txt b/Documentation/devicetree/bindings/sound/fsl,rpmsg-i2s.txt
new file mode 100644
index 000000000000..27de48eb2519
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/fsl,rpmsg-i2s.txt
@@ -0,0 +1,22 @@
+Freescale rpmsg i2s interface.
+
+The rpmsg i2s is based on RPMSG that used communicating with M4 core,
+which provides a synchronous audio interface that supports fullduplex
+serial interfaces with frame synchronization such as I2S.
+
+Required properties:
+
+ - compatible : Compatible list, contains "fsl,imx7ulp-rpmsg-i2s".
+ "fsl,imx8mq-rpmsg-i2s", "fsl,imx8qxp-rpmsg-i2s"
+ "fsl,imx8qm-rpmsg-i2s"
+
+ - fsl,audioindex : This is an index indicating the audio device index in
+ the M4 side.
+
+Example:
+rpmsg_i2s: rpmsg-i2s {
+ compatible = "fsl,imx7ulp-rpmsg-i2s";
+ /* the audio device index in m4 domain */
+ fsl,audioindex = <0> ;
+ status = "okay";
+};
diff --git a/Documentation/devicetree/bindings/sound/fsl,spdif.txt b/Documentation/devicetree/bindings/sound/fsl,spdif.txt
index 8b324f82a782..cf3c3556c801 100644
--- a/Documentation/devicetree/bindings/sound/fsl,spdif.txt
+++ b/Documentation/devicetree/bindings/sound/fsl,spdif.txt
@@ -6,7 +6,9 @@ a fibre cable.
Required properties:
- - compatible : Compatible list, must contain "fsl,imx35-spdif".
+ - compatible : Compatible list, must contain "fsl,imx35-spdif",
+ "fsl,vf610-spdif", "fsl,imx8qm-spdif",
+ "fsl,imx8mm-spdif"
- reg : Offset and length of the register set for the device.
diff --git a/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt b/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt
index c60a5732d29c..f749e2744824 100644
--- a/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt
+++ b/Documentation/devicetree/bindings/sound/fsl-asoc-card.txt
@@ -28,6 +28,7 @@ The compatible list for this generic sound card currently:
(compatible with CS4271 and CS4272)
"fsl,imx-audio-wm8962"
+ (compatible with Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt)
"fsl,imx-audio-sgtl5000"
(compatible with Documentation/devicetree/bindings/sound/imx-audio-sgtl5000.txt)
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-ak4458.txt b/Documentation/devicetree/bindings/sound/imx-audio-ak4458.txt
new file mode 100644
index 000000000000..a442d3edd62d
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-ak4458.txt
@@ -0,0 +1,30 @@
+Freescale i.MX audio complex with AK4458 DAC
+
+Required properties:
+
+- compatible : "fsl,imx-audio-ak4458", "fsl,imx-audio-ak4458-mq"
+- model : The user-visible name of this sound complex
+- audio-cpu : The phandle of CPU DAI
+- audio-codec : The phandle of the AK4458 audio DAC
+- audio-routing : A list of the connections between audio components. Each entry
+ is a pair of strings, the first being the connection's sink, the second being
+ the connection's source. Valid names could be power supplies, AK4458 pins,
+ and the jacks on the board.
+
+Example:
+
+sound {
+ compatible = "fsl,imx-audio-ak4458";
+ model = "ak4458-audio";
+ audio-cpu = <&sai1>;
+ audio-codec = <&codec>;
+ audio-routing =
+ "AOUTL1", "Playback",
+ "AOUTR1", "Playback",
+ "AOUTL2", "Playback",
+ "AOUTR2", "Playback",
+ "AOUTL3", "Playback",
+ "AOUTR3", "Playback",
+ "AOUTL4", "Playback",
+ "AOUTR4", "Playback";
+};
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-ak4497.txt b/Documentation/devicetree/bindings/sound/imx-audio-ak4497.txt
new file mode 100644
index 000000000000..7eeeeeda74f5
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-ak4497.txt
@@ -0,0 +1,27 @@
+Freescale i.MX audio complex with AK4497 DAC
+
+Required properties:
+
+- compatible : "fsl,imx-audio-ak4497", "fsl,imx-audio-ak4497-mq"
+- model : The user-visible name of this sound complex
+- audio-cpu : The phandle of CPU DAI
+- audio-codec : The phandle of the ak4497 audio DAC
+- audio-routing : A list of the connections between audio components. Each entry
+ is a pair of strings, the first being the connection's sink, the second being
+ the connection's source. Valid names could be power supplies, ak4497 pins,
+ and the jacks on the board.
+
+Example:
+
+sound {
+ compatible = "fsl,imx-audio-ak4497";
+ model = "ak4497-audio";
+ audio-cpu = <&sai3>;
+ audio-codec = <&codec>;
+ audio-routing =
+ "AOUTLN", "Playback",
+ "AOUTLP", "Playback",
+ "AOUTRN", "Playback",
+ "AOUTRP", "Playback",
+};
+
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-ak5558.txt b/Documentation/devicetree/bindings/sound/imx-audio-ak5558.txt
new file mode 100644
index 000000000000..7b62fbb14f8d
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-ak5558.txt
@@ -0,0 +1,30 @@
+Freescale i.MX audio complex with AK5558 ADC
+
+Required properties:
+
+- compatible : "fsl,imx-audio-ak5558", "fsl,imx-audio-ak5558-mq"
+- model : The user-visible name of this sound complex
+- audio-cpu : The phandle of CPU DAI
+- audio-codec : The phandle of the AK5558 audio ADC
+- audio-routing : A list of the connections between audio components. Each entry
+ is a pair of strings, the first being the connection's sink, the second being
+ the connection's source. Valid names could be power supplies, AK5558 pins,
+ and the jacks on the board.
+
+Example:
+
+sound {
+ compatible = "fsl,imx-audio-ak5558";
+ model = "ak5558-audio";
+ audio-cpu = <&sai1>;
+ audio-codec = <&codec>;
+ audio-routing =
+ "AIN1", "Capture",
+ "AIN2", "Capture",
+ "AIN3", "Capture",
+ "AIN4", "Capture",
+ "AIN5", "Capture",
+ "AIN6", "Capture",
+ "AIN7", "Capture",
+ "AIN8", "Capture";
+};
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-cdnhdmi.txt b/Documentation/devicetree/bindings/sound/imx-audio-cdnhdmi.txt
new file mode 100644
index 000000000000..6d41217dd7be
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-cdnhdmi.txt
@@ -0,0 +1,16 @@
+Freescale i.MX audio complex with Cadence HDMI
+
+Required properties:
+- compatible : "fsl,imx-audio-cdnhdmi", "fsl,imx8mq-evk-cdnhdmi"
+- model : The user-visible name of this sound complex
+- audio-cpu : The phandle of the i.MX SAI controller
+- protocol : 0 is hdmi, 1 is dp.
+
+Example:
+
+sound-hdmi {
+ compatible = "fsl,imx-audio-cdnhdmi";
+ model = "imx-audio-hdmi";
+ audio-cpu = <&sai4>;
+ protocol = <0>;
+};
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-cs42888.txt b/Documentation/devicetree/bindings/sound/imx-audio-cs42888.txt
new file mode 100644
index 000000000000..af746c4c81df
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-cs42888.txt
@@ -0,0 +1,25 @@
+Freescale i.MX audio complex with CS42888 codec
+
+Required properties:
+- compatible : "fsl,imx-audio-cs42888"
+- model : The user-visible name of this sound complex
+- esai-controller : The phandle of the i.MX SSI controller
+- audio-codec : The phandle of the CS42888 audio codec
+
+Optional properties:
+- asrc-controller : The phandle of the i.MX ASRC controller
+- audio-routing : A list of the connections between audio components.
+ Each entry is a pair of strings, the first being the connection's sink,
+ the second being the connection's source. Valid names could be power
+ supplies, CS42888 pins, and the jacks on the board:
+
+Example:
+
+sound {
+ compatible = "fsl,imx6q-sabresd-wm8962",
+ "fsl,imx-audio-wm8962";
+ model = "cs42888-audio";
+ esai-controller = <&esai>;
+ asrc-controller = <&asrc_p2p>;
+ audio-codec = <&codec>;
+};
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-mqs.txt b/Documentation/devicetree/bindings/sound/imx-audio-mqs.txt
new file mode 100644
index 000000000000..c0195c202894
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-mqs.txt
@@ -0,0 +1,18 @@
+Freescale i.MX audio complex with mqs codec
+
+Required properties:
+- compatible : "fsl,imx-audio-mqs", "fsl,imx8qm-lpddr4-arm2-mqs".
+- model : The user-visible name of this sound complex
+- cpu-dai : The phandle of the i.MX sai controller
+- audio-codec : The phandle of the mqs audio codec
+
+Example:
+
+sound-mqs {
+ compatible = "fsl,imx6sx-sdb-mqs",
+ "fsl,imx-audio-mqs";
+ model = "mqs-audio";
+ cpu-dai = <&sai1>;
+ audio-codec = <&mqs>;
+};
+
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-rpmsg.txt b/Documentation/devicetree/bindings/sound/imx-audio-rpmsg.txt
new file mode 100644
index 000000000000..3f015974ffeb
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-rpmsg.txt
@@ -0,0 +1,13 @@
+Freescale i.MX audio complex with rpmsg devices
+
+Required properties:
+- compatible : "fsl,imx-audio-rpmsg"
+- model : The user-visible name of this sound complex
+- cpu-dai : The phandle of the i.MX rpmsg i2s device.
+
+Example:
+sound-rpmsg {
+ compatible = "fsl,imx-audio-rpmsg";
+ model = "rpmsg-audio";
+ cpu-dai = <&rpmsg_i2s>;
+};
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-si476x.txt b/Documentation/devicetree/bindings/sound/imx-audio-si476x.txt
new file mode 100644
index 000000000000..53cd34afe6b8
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-si476x.txt
@@ -0,0 +1,24 @@
+Freescale i.MX audio complex with si476x codec
+
+Required properties:
+- compatible : "fsl,imx-audio-si476x"
+- model : The user-visible name of this sound complex
+- ssi-controller : The phandle of the i.MX SSI controller
+
+- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX)
+- mux-ext-port : The external port of the i.MX audio muxer
+
+Note: The AUDMUX port numbering should start at 1, which is consistent with
+hardware manual.
+
+Example:
+
+sound {
+ compatible = "fsl,imx-audio-si476x",
+ "fsl,imx-tuner-si476x";
+ model = "imx-radio-si476x";
+
+ ssi-controller = <&ssi1>;
+ mux-int-port = <2>;
+ mux-ext-port = <5>;
+};
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-wm8524.txt b/Documentation/devicetree/bindings/sound/imx-audio-wm8524.txt
new file mode 100644
index 000000000000..b3e3c01464bd
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-wm8524.txt
@@ -0,0 +1,29 @@
+Freescale i.MX audio complex with WM8524 codec
+
+Required properties:
+
+ - compatible : "fsl,imx-audio-wm8524"
+
+ - model : The user-visible name of this sound complex
+
+ - audio-cpu : The phandle of CPU DAI
+
+ - audio-codec : The phandle of the WM8962 audio codec
+
+ - audio-routing : A list of the connections between audio components.
+ Each entry is a pair of strings, the first being the
+ connection's sink, the second being the connection's
+ source. Valid names could be power supplies, WM8524
+ pins, and the jacks on the board:
+
+Example:
+
+sound {
+ compatible = "fsl,imx-audio-wm8524";
+ model = "wm8524-audio";
+ audio-cpu = <&sai2>;
+ audio-codec = <&codec>;
+ audio-routing =
+ "Line Out Jack", "LINEVOUTL",
+ "Line Out Jack", "LINEVOUTR";
+};
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt b/Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt
new file mode 100644
index 000000000000..06bc12d4cc76
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt
@@ -0,0 +1,61 @@
+Freescale i.MX audio complex with WM8962 codec
+
+Required properties:
+
+ - compatible : "fsl,imx-audio-wm8962"
+
+ - model : The user-visible name of this sound complex
+
+ - cpu-dai : The phandle of CPU DAI
+
+ - audio-codec : The phandle of the WM8962 audio codec
+
+ - audio-routing : A list of the connections between audio components.
+ Each entry is a pair of strings, the first being the
+ connection's sink, the second being the connection's
+ source. Valid names could be power supplies, WM8962
+ pins, and the jacks on the board:
+
+ Power supplies:
+ * Mic Bias
+
+ Board connectors:
+ * Mic Jack
+ * Headphone Jack
+ * Ext Spk
+
+ - mux-int-port : The internal port of the i.MX audio muxer (AUDMUX)
+
+ - mux-ext-port : The external port of the i.MX audio muxer
+
+Note: The AUDMUX port numbering should start at 1, which is consistent with
+hardware manual.
+
+Optional properties:
+- hp-det-gpios : The gpio pin to detect plug in/out event that happens to
+ Headphone jack.
+- mic-det-gpios: The gpio pin to detect plug in/out event that happens to
+ Microphone jack.
+
+Example:
+
+sound {
+ compatible = "fsl,imx6q-sabresd-wm8962",
+ "fsl,imx-audio-wm8962";
+ model = "wm8962-audio";
+ cpu-dai = <&ssi2>;
+ audio-codec = <&codec>;
+ audio-routing =
+ "Headphone Jack", "HPOUTL",
+ "Headphone Jack", "HPOUTR",
+ "Ext Spk", "SPKOUTL",
+ "Ext Spk", "SPKOUTR",
+ "MICBIAS", "AMIC",
+ "IN3R", "MICBIAS",
+ "DMIC", "MICBIAS",
+ "DMICDAT", "DMIC";
+ mux-int-port = <2>;
+ mux-ext-port = <3>;
+ hp-det-gpios = <&gpio7 8 1>;
+ mic-det-gpios = <&gpio1 9 1>;
+};
diff --git a/Documentation/devicetree/bindings/sound/wm8962.txt b/Documentation/devicetree/bindings/sound/wm8962.txt
index dcfa9a3369fd..c624cebc5372 100644
--- a/Documentation/devicetree/bindings/sound/wm8962.txt
+++ b/Documentation/devicetree/bindings/sound/wm8962.txt
@@ -13,6 +13,14 @@ Optional properties:
of R51 (Class D Control 2) gets set, indicating that the speaker is
in mono mode.
+ - amic-mono: This is a boolean property. If present, indicating that the
+ analog micphone is hardware mono input, the driver would enable monomix
+ for it.
+
+ - dmic-mono: This is a boolean property. If present, indicating that the
+ digital micphone is hardware mono input, the driver would enable monomix
+ for it.
+
- mic-cfg : Default register value for R48 (Additional Control 4).
If absent, the default should be the register default.
diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c
index b203296de977..c2490267f254 100644
--- a/drivers/media/radio/radio-si476x.c
+++ b/drivers/media/radio/radio-si476x.c
@@ -988,6 +988,14 @@ static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl)
}
break;
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->val)
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_AUDIO_MUTE, 3);
+ else
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_AUDIO_MUTE, 0);
+ break;
default:
retval = -EINVAL;
break;
@@ -1515,6 +1523,16 @@ static int si476x_radio_probe(struct platform_device *pdev)
goto exit;
}
+ ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops,
+ V4L2_CID_AUDIO_MUTE,
+ 0, 1, 1, 0);
+ rval = radio->ctrl_handler.error;
+ if (ctrl == NULL && rval) {
+ dev_err(&pdev->dev, "Could not initialize V4L2_CID_AUDIO_MUTE control %d\n",
+ rval);
+ goto exit;
+ }
+
if (si476x_core_has_diversity(radio->core)) {
si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def =
si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode);
diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c
index c8d28b844def..accdcdc7f23f 100644
--- a/drivers/mfd/si476x-i2c.c
+++ b/drivers/mfd/si476x-i2c.c
@@ -97,7 +97,7 @@ static int si476x_core_config_pinmux(struct si476x_core *core)
static inline void si476x_core_schedule_polling_work(struct si476x_core *core)
{
- schedule_delayed_work(&core->status_monitor,
+ queue_delayed_work(system_freezable_wq, &core->status_monitor,
usecs_to_jiffies(SI476X_STATUS_POLL_US));
}
@@ -294,7 +294,7 @@ int si476x_core_set_power_state(struct si476x_core *core,
*/
udelay(100);
- err = si476x_core_start(core, false);
+ err = si476x_core_start(core, true);
if (err < 0)
goto disable_regulators;
@@ -303,7 +303,7 @@ int si476x_core_set_power_state(struct si476x_core *core,
case SI476X_POWER_DOWN:
core->power_state = next_state;
- err = si476x_core_stop(core, false);
+ err = si476x_core_stop(core, true);
if (err < 0)
core->power_state = SI476X_POWER_INCONSISTENT;
disable_regulators:
@@ -729,8 +729,15 @@ static int si476x_core_probe(struct i2c_client *client,
memcpy(&core->pinmux, &pdata->pinmux,
sizeof(struct si476x_pinmux));
} else {
- dev_err(&client->dev, "No platform data provided\n");
- return -EINVAL;
+ dev_warn(&client->dev, "Using default platform data.\n");
+ core->power_up_parameters.xcload = 0x28;
+ core->power_up_parameters.func = SI476X_FUNC_FM_RECEIVER;
+ core->power_up_parameters.freq = SI476X_FREQ_37P209375_MHZ;
+ core->diversity_mode = SI476X_PHDIV_DISABLED;
+ core->pinmux.dclk = SI476X_DCLK_DAUDIO;
+ core->pinmux.dfs = SI476X_DFS_DAUDIO;
+ core->pinmux.dout = SI476X_DOUT_I2S_OUTPUT;
+ core->pinmux.xout = SI476X_XOUT_TRISTATE;
}
core->supplies[0].supply = "vd";
@@ -789,12 +796,18 @@ static int si476x_core_probe(struct i2c_client *client,
core->chip_id = id->driver_data;
+ /* Power down si476x first */
+ si476x_core_stop(core, true);
+
rval = si476x_core_get_revision_info(core);
if (rval < 0) {
rval = -ENODEV;
goto free_kfifo;
}
+ if (of_property_read_bool(client->dev.of_node, "revision-a10"))
+ core->revision = SI476X_REVISION_A10;
+
cell_num = 0;
cell = &core->cells[SI476X_RADIO_CELL];
@@ -810,6 +823,7 @@ static int si476x_core_probe(struct i2c_client *client,
core->pinmux.xout == SI476X_XOUT_TRISTATE) {
cell = &core->cells[SI476X_CODEC_CELL];
cell->name = "si476x-codec";
+ cell->of_compatible = "si476x-codec";
cell_num++;
}
#endif
diff --git a/include/linux/busfreq-imx.h b/include/linux/busfreq-imx.h
new file mode 100644
index 000000000000..39c71a9f55eb
--- /dev/null
+++ b/include/linux/busfreq-imx.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012-2016 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __ASM_ARCH_MXC_BUSFREQ_H__
+#define __ASM_ARCH_MXC_BUSFREQ_H__
+
+#include <linux/notifier.h>
+#include <linux/regulator/consumer.h>
+
+/*
+ * This enumerates busfreq low power mode entry and exit.
+ */
+enum busfreq_event {
+ LOW_BUSFREQ_ENTER,
+ LOW_BUSFREQ_EXIT,
+};
+
+/*
+ * This enumerates the system bus and ddr frequencies in various modes.
+ * BUS_FREQ_HIGH - DDR @ 528MHz, AHB @ 132MHz.
+ * BUS_FREQ_MED - DDR @ 400MHz, AHB @ 132MHz
+ * BUS_FREQ_AUDIO - DDR @ 50MHz/100MHz, AHB @ 24MHz.
+ * BUS_FREQ_LOW - DDR @ 24MHz, AHB @ 24MHz.
+ * BUS_FREQ_ULTRA_LOW - DDR @ 1MHz, AHB - 3MHz.
+ *
+ * Drivers need to request/release the bus/ddr frequencies based on
+ * their performance requirements. Drivers cannot request/release
+ * BUS_FREQ_ULTRA_LOW mode as this mode is automatically entered from
+ * either BUS_FREQ_AUDIO or BUS_FREQ_LOW
+ * modes.
+ */
+enum bus_freq_mode {
+ BUS_FREQ_HIGH,
+ BUS_FREQ_MED,
+ BUS_FREQ_AUDIO,
+ BUS_FREQ_LOW,
+ BUS_FREQ_ULTRA_LOW,
+};
+
+#if defined(CONFIG_HAVE_IMX_BUSFREQ) && !defined(CONFIG_ARM64)
+extern struct regulator *arm_reg;
+extern struct regulator *soc_reg;
+void request_bus_freq(enum bus_freq_mode mode);
+void release_bus_freq(enum bus_freq_mode mode);
+int register_busfreq_notifier(struct notifier_block *nb);
+int unregister_busfreq_notifier(struct notifier_block *nb);
+int get_bus_freq_mode(void);
+#elif defined(CONFIG_HAVE_IMX_BUSFREQ)
+void request_bus_freq(enum bus_freq_mode mode);
+void release_bus_freq(enum bus_freq_mode mode);
+int get_bus_freq_mode(void);
+#else
+static inline void request_bus_freq(enum bus_freq_mode mode)
+{
+}
+static inline void release_bus_freq(enum bus_freq_mode mode)
+{
+}
+static inline int register_busfreq_notifier(struct notifier_block *nb)
+{
+ return 0;
+}
+static inline int unregister_busfreq_notifier(struct notifier_block *nb)
+{
+ return 0;
+}
+static inline int get_bus_freq_mode(void)
+{
+ return BUS_FREQ_HIGH;
+}
+#endif
+#endif
diff --git a/include/linux/mfd/si476x-core.h b/include/linux/mfd/si476x-core.h
index 4708c2b8512a..e591c050dce1 100644
--- a/include/linux/mfd/si476x-core.h
+++ b/include/linux/mfd/si476x-core.h
@@ -484,6 +484,8 @@ enum si476x_common_receiver_properties {
SI476X_PROP_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202,
SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT = 0x0203,
+ SI476X_PROP_AUDIO_MUTE = 0x0301,
+
SI476X_PROP_SEEK_BAND_BOTTOM = 0x1100,
SI476X_PROP_SEEK_BAND_TOP = 0x1101,
SI476X_PROP_SEEK_FREQUENCY_SPACING = 0x1102,
diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h
index c679f6116580..649704b6f349 100644
--- a/include/sound/dmaengine_pcm.h
+++ b/include/sound/dmaengine_pcm.h
@@ -76,6 +76,7 @@ struct snd_dmaengine_dai_dma_data {
const char *chan_name;
unsigned int fifo_size;
unsigned int flags;
+ unsigned int fifo_num;
};
void snd_dmaengine_pcm_set_config_from_dai_data(
diff --git a/include/sound/sof.h b/include/sound/sof.h
index 4640566b54fe..81889a859379 100644
--- a/include/sound/sof.h
+++ b/include/sound/sof.h
@@ -22,7 +22,6 @@ struct snd_sof_dsp_ops;
*/
struct snd_sof_pdata {
const struct firmware *fw;
- const char *drv_name;
const char *name;
const char *platform;
@@ -88,13 +87,13 @@ struct sof_dev_desc {
const char *default_fw_path;
const char *default_tplg_path;
+ /* default firmware name */
+ const char *default_fw_filename;
+
const struct snd_sof_dsp_ops *ops;
const struct sof_arch_ops *arch_ops;
};
int sof_nocodec_setup(struct device *dev,
- struct snd_sof_pdata *sof_pdata,
- struct snd_soc_acpi_mach *mach,
- const struct sof_dev_desc *desc,
const struct snd_sof_dsp_ops *ops);
#endif
diff --git a/include/sound/sof/dai-imx.h b/include/sound/sof/dai-imx.h
new file mode 100644
index 000000000000..8b286054b223
--- /dev/null
+++ b/include/sound/sof/dai-imx.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * Copyright 2019 NXP
+ *
+ * Author: Daniel Baluta <daniel.baluta@nxp.com>
+ */
+
+#ifndef __INCLUDE_SOUND_SOF_DAI_IMX_H__
+#define __INCLUDE_SOUND_SOF_DAI_IMX_H__
+
+#include <sound/sof/header.h>
+
+/* ESAI Configuration Request - SOF_IPC_DAI_ESAI_CONFIG */
+struct sof_ipc_dai_esai_params {
+ struct sof_ipc_hdr hdr;
+
+ /* MCLK */
+ uint16_t reserved1;
+ uint16_t mclk_id;
+ uint32_t mclk_direction;
+
+ uint32_t mclk_rate; /* MCLK frequency in Hz */
+ uint32_t fsync_rate; /* FSYNC frequency in Hz */
+ uint32_t bclk_rate; /* BCLK frequency in Hz */
+
+ /* TDM */
+ uint32_t tdm_slots;
+ uint32_t rx_slots;
+ uint32_t tx_slots;
+ uint16_t tdm_slot_width;
+ uint16_t reserved2; /* alignment */
+} __packed;
+
+/* SAI Configuration Request - SOF_IPC_DAI_SAI_CONFIG */
+struct sof_ipc_dai_sai_params {
+ struct sof_ipc_hdr hdr;
+
+ /* MCLK */
+ uint16_t reserved1;
+ uint16_t mclk_id;
+ uint32_t mclk_direction;
+
+ uint32_t mclk_rate; /* MCLK frequency in Hz */
+ uint32_t fsync_rate; /* FSYNC frequency in Hz */
+ uint32_t bclk_rate; /* BCLK frequency in Hz */
+
+ /* TDM */
+ uint32_t tdm_slots;
+ uint32_t rx_slots;
+ uint32_t tx_slots;
+ uint16_t tdm_slot_width;
+ uint16_t reserved2; /* alignment */
+} __packed;
+#endif
+
diff --git a/include/sound/sof/dai.h b/include/sound/sof/dai.h
index 0f1235022146..2565edd336f1 100644
--- a/include/sound/sof/dai.h
+++ b/include/sound/sof/dai.h
@@ -11,6 +11,7 @@
#include <sound/sof/header.h>
#include <sound/sof/dai-intel.h>
+#include <sound/sof/dai-imx.h>
/*
* DAI Configuration.
@@ -73,6 +74,8 @@ struct sof_ipc_dai_config {
struct sof_ipc_dai_dmic_params dmic;
struct sof_ipc_dai_hda_params hda;
struct sof_ipc_dai_alh_params alh;
+ struct sof_ipc_dai_esai_params esai;
+ struct sof_ipc_dai_sai_params sai;
};
} __packed;
diff --git a/include/uapi/linux/mxc_asrc.h b/include/uapi/linux/mxc_asrc.h
new file mode 100644
index 000000000000..79059c3be2c4
--- /dev/null
+++ b/include/uapi/linux/mxc_asrc.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2008-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file mxc_asrc.h
+ *
+ * @brief i.MX Asynchronous Sample Rate Converter
+ *
+ * @ingroup Audio
+ */
+
+#ifndef __MXC_ASRC_UAPI_H__
+#define __MXC_ASRC_UAPI_H__
+
+#define ASRC_IOC_MAGIC 'C'
+
+#define ASRC_REQ_PAIR _IOWR(ASRC_IOC_MAGIC, 0, struct asrc_req)
+#define ASRC_CONFIG_PAIR _IOWR(ASRC_IOC_MAGIC, 1, struct asrc_config)
+#define ASRC_RELEASE_PAIR _IOW(ASRC_IOC_MAGIC, 2, enum asrc_pair_index)
+#define ASRC_CONVERT _IOW(ASRC_IOC_MAGIC, 3, struct asrc_convert_buffer)
+#define ASRC_START_CONV _IOW(ASRC_IOC_MAGIC, 4, enum asrc_pair_index)
+#define ASRC_STOP_CONV _IOW(ASRC_IOC_MAGIC, 5, enum asrc_pair_index)
+#define ASRC_STATUS _IOW(ASRC_IOC_MAGIC, 6, struct asrc_status_flags)
+#define ASRC_FLUSH _IOW(ASRC_IOC_MAGIC, 7, enum asrc_pair_index)
+
+enum asrc_pair_index {
+ ASRC_INVALID_PAIR = -1,
+ ASRC_PAIR_A = 0,
+ ASRC_PAIR_B = 1,
+ ASRC_PAIR_C = 2,
+ ASRC_PAIR_D = 3,
+};
+
+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,
+/* 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,
+
+/* 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,
+};
+
+struct asrc_config {
+ enum asrc_pair_index pair;
+ unsigned int channel_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;
+ uint64_t supported_in_format;
+ uint64_t supported_out_format;
+};
+
+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,
+};
+#endif/* __MXC_ASRC_UAPI_H__ */
diff --git a/include/uapi/linux/mxc_dsp.h b/include/uapi/linux/mxc_dsp.h
new file mode 100644
index 000000000000..040681cc39c8
--- /dev/null
+++ b/include/uapi/linux/mxc_dsp.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MXC_DSP_UAPI_H__
+#define __MXC_DSP_UAPI_H__
+
+#define DSP_IOC_MAGIC 'H'
+#define DSP_CLIENT_REGISTER _IOW(DSP_IOC_MAGIC, 0, unsigned int)
+#define DSP_CLIENT_UNREGISTER _IOW(DSP_IOC_MAGIC, 1, unsigned int)
+#define DSP_IPC_MSG_SEND _IOW(DSP_IOC_MAGIC, 2, unsigned int)
+#define DSP_IPC_MSG_RECV _IOW(DSP_IOC_MAGIC, 3, unsigned int)
+#define DSP_GET_SHMEM_INFO _IOW(DSP_IOC_MAGIC, 4, unsigned int)
+#define DSP_LOAD_LIB _IOW(DSP_IOC_MAGIC, 5, unsigned int)
+#define DSP_UNLOAD_LIB _IOW(DSP_IOC_MAGIC, 6, unsigned int)
+
+
+#define CODEC_MP3_DEC 1
+#define CODEC_AAC_DEC 2
+#define CODEC_DAB_DEC 3
+#define CODEC_MP2_DEC 4
+#define CODEC_BSAC_DEC 5
+#define CODEC_DRM_DEC 6
+#define CODEC_SBC_DEC 7
+#define CODEC_SBC_ENC 8
+#define CODEC_DEMO_DEC 9
+
+#define RENDER_ESAI 0x10
+#define RENDER_SAI 0x11
+
+enum DSP_ERROR_TYPE {
+ XA_SUCCESS = 0,
+
+ XA_ERROR_STREAM,
+ XA_PARA_ERROR,
+ XA_INSUFFICIENT_MEM,
+ XA_ERR_UNKNOWN,
+ XA_PROFILE_NOT_SUPPORT,
+ XA_INIT_ERR,
+ XA_NO_OUTPUT,
+
+ XA_NOT_ENOUGH_DATA = 0x100,
+ XA_CAPIBILITY_CHANGE = 0x200,
+ XA_END_OF_STREAM = 0x300, /* no output */
+};
+
+/* Parameter type to Set /Get */
+enum DSP_ParaType {
+/* Set parmameters */
+/* common */
+ XA_SAMPLERATE = 0,
+ XA_CHANNEL,
+ XA_FRAMED, /* one whole frame input */
+ XA_DEPTH,
+ XA_CODEC_DATA,
+ XA_BITRATE,
+ XA_DOWNMIX_STEREO,
+ XA_STREAM_TYPE,
+ XA_CHAN_MAP_TABLE,
+ //UNIA_CHANNEL_MASK,
+ XA_TO_STEREO,
+
+/* dedicate for mp3 dec */
+ XA_MP3_DEC_CRC_CHECK = 0x120,
+ XA_MP3_DEC_MCH_ENABLE,
+ XA_MP3_DEC_NONSTD_STRM_SUPPORT,
+
+/* dedicate for bsac dec */
+ XA_BSAC_DEC_DECODELAYERS = 0x130,
+
+/* dedicate for aacplus dec */
+ XA_AACPLUS_DEC_BDOWNSAMPLE = 0x140,
+ XA_AACPLUS_DEC_BBITSTREAMDOWNMIX,
+ XA_AACPLUS_DEC_CHANROUTING,
+
+/* dedicate for dabplus dec */
+ XA_DABPLUS_DEC_BDOWNSAMPLE = 0x150,
+ XA_DABPLUS_DEC_BBITSTREAMDOWNMIX,
+ XA_DABPLUS_DEC_CHANROUTING,
+
+/* dedicate for sbc enc */
+ XA_SBC_ENC_SUBBANDS = 0x160,
+ XA_SBC_ENC_BLOCKS,
+ XA_SBC_ENC_SNR,
+ XA_SBC_ENC_BITPOOL,
+ XA_SBC_ENC_CHMODE,
+
+/* Get parmameters */
+ XA_CODEC_DESCRIPTION = 0x200,
+ XA_OUTPUT_PCM_FORMAT,
+ XA_CONSUMED_LENGTH,
+ XA_OUTBUF_ALLOC_SIZE,
+ XA_CONSUMED_CYCLES,
+
+};
+
+#define XA_STREAM_DABPLUS_BASE 0x30
+enum DSP_StreamType {
+ /* AAC/AACPLUS file format */
+ XA_STREAM_UNKNOWN = 0,
+ XA_STREAM_ADTS,
+ XA_STREAM_ADIF,
+ XA_STREAM_RAW,
+
+ XA_STREAM_LATM,
+ XA_STREAM_LATM_OUTOFBAND_CONFIG,
+ XA_STREAM_LOAS,
+
+ /* DABPLUS file format */
+ XA_STREAM_DABPLUS_RAW_SIDEINFO = XA_STREAM_DABPLUS_BASE,
+ XA_STREAM_DABPLUS,
+
+ /* BSAC file raw format */
+ XA_STREAM_BSAC_RAW,
+
+};
+
+/* sbc_enc-specific channel modes */
+enum DSP_SbcEncChmode {
+ XA_CHMODE_MONO = 0,
+ XA_CHMODE_DUAL = 1,
+ XA_CHMODE_STEREO = 2,
+ XA_CHMODE_JOINT = 3,
+};
+
+struct shmem_info {
+ unsigned int phys_addr;
+ unsigned int size;
+};
+
+#endif/* __MXC_DSP_UAPI_H__ */
diff --git a/include/uapi/sound/sof/abi.h b/include/uapi/sound/sof/abi.h
index a0fe0d4c4b66..ebfdc20ca081 100644
--- a/include/uapi/sound/sof/abi.h
+++ b/include/uapi/sound/sof/abi.h
@@ -26,7 +26,7 @@
/* SOF ABI version major, minor and patch numbers */
#define SOF_ABI_MAJOR 3
-#define SOF_ABI_MINOR 10
+#define SOF_ABI_MINOR 11
#define SOF_ABI_PATCH 0
/* SOF ABI version number. Format within 32bit word is MMmmmppp */
diff --git a/include/uapi/sound/sof/tokens.h b/include/uapi/sound/sof/tokens.h
index 8f996857fb24..c66cfc0b451e 100644
--- a/include/uapi/sound/sof/tokens.h
+++ b/include/uapi/sound/sof/tokens.h
@@ -107,11 +107,9 @@
#define SOF_TKN_EFFECT_TYPE SOF_TKN_PROCESS_TYPE
/* SAI */
-#define SOF_TKN_IMX_SAI_FIRST_TOKEN 1000
-/* TODO: Add SAI tokens */
+#define SOF_TKN_IMX_SAI_MCLK_ID 1000
/* ESAI */
-#define SOF_TKN_IMX_ESAI_FIRST_TOKEN 1100
-/* TODO: Add ESAI tokens */
+#define SOF_TKN_IMX_ESAI_MCLK_ID 1100
#endif
diff --git a/sound/soc/atmel/atmel-classd.c b/sound/soc/atmel/atmel-classd.c
index e98601eccfa3..0f2c574f27f1 100644
--- a/sound/soc/atmel/atmel-classd.c
+++ b/sound/soc/atmel/atmel-classd.c
@@ -571,8 +571,11 @@ static int atmel_classd_probe(struct platform_device *pdev)
dd->pdata = pdata;
dd->irq = platform_get_irq(pdev, 0);
- if (dd->irq < 0)
- return dd->irq;
+ if (dd->irq < 0) {
+ ret = dd->irq;
+ dev_err(dev, "failed to could not get irq: %d\n", ret);
+ return ret;
+ }
dd->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dd->pclk)) {
diff --git a/sound/soc/atmel/atmel-pdmic.c b/sound/soc/atmel/atmel-pdmic.c
index 04ec6f0af179..e09c28349e0d 100644
--- a/sound/soc/atmel/atmel-pdmic.c
+++ b/sound/soc/atmel/atmel-pdmic.c
@@ -612,8 +612,11 @@ static int atmel_pdmic_probe(struct platform_device *pdev)
dd->dev = dev;
dd->irq = platform_get_irq(pdev, 0);
- if (dd->irq < 0)
- return dd->irq;
+ if (dd->irq < 0) {
+ ret = dd->irq;
+ dev_err(dev, "failed to get irq: %d\n", ret);
+ return ret;
+ }
dd->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dd->pclk)) {
diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c
index 2f9357d7da96..b7c358b48d8d 100644
--- a/sound/soc/bcm/cygnus-ssp.c
+++ b/sound/soc/bcm/cygnus-ssp.c
@@ -1342,8 +1342,11 @@ static int cygnus_ssp_probe(struct platform_device *pdev)
}
cygaud->irq_num = platform_get_irq(pdev, 0);
- if (cygaud->irq_num <= 0)
- return cygaud->irq_num;
+ if (cygaud->irq_num <= 0) {
+ dev_err(dev, "platform_get_irq failed\n");
+ err = cygaud->irq_num;
+ return err;
+ }
err = audio_clk_init(pdev, cygaud);
if (err) {
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 229cc89f8c5a..f128ffabf5e5 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -257,6 +257,9 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM9705 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)
select SND_SOC_WM9712 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)
select SND_SOC_WM9713 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)
+ select SND_SOC_RPMSG_WM8960
+ select SND_SOC_RPMSG_CS42XX8
+ select SND_SOC_RPMSG_AK4497
help
Normally ASoC codec drivers are only built if a machine driver which
uses them is also built since they are only usable with a machine
@@ -696,6 +699,9 @@ config SND_SOC_ES8328_SPI
depends on SPI_MASTER
select SND_SOC_ES8328
+config SND_SOC_FSL_MQS
+ tristate
+
config SND_SOC_GTM601
tristate 'GTM601 UMTS modem audio codec'
@@ -1436,6 +1442,14 @@ config SND_SOC_ZX_AUD96P22
depends on I2C
select REGMAP_I2C
+config SND_SOC_RPMSG_WM8960
+ tristate
+
+config SND_SOC_RPMSG_CS42XX8
+ tristate
+
+config SND_SOC_RPMSG_AK4497
+ tristate
# Amp
config SND_SOC_LM4857
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index c498373dcc5f..58c2b3cec86f 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -86,6 +86,7 @@ snd-soc-es8316-objs := es8316.o
snd-soc-es8328-objs := es8328.o
snd-soc-es8328-i2c-objs := es8328-i2c.o
snd-soc-es8328-spi-objs := es8328-spi.o
+snd-soc-fsl-mqs-objs := fsl_mqs.o
snd-soc-gtm601-objs := gtm601.o
snd-soc-hdac-hdmi-objs := hdac_hdmi.o
snd-soc-hdac-hda-objs := hdac_hda.o
@@ -274,6 +275,10 @@ snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
snd-soc-zx-aud96p22-objs := zx_aud96p22.o
+snd-soc-rpmsg-wm8960-objs := rpmsg_wm8960.o
+snd-soc-rpmsg-cs42xx8-objs := rpmsg_cs42xx8.o
+snd-soc-rpmsg-ak4497-objs := rpmsg_ak4497.o
+
# Amp
snd-soc-max9877-objs := max9877.o
snd-soc-max98504-objs := max98504.o
@@ -370,6 +375,7 @@ obj-$(CONFIG_SND_SOC_ES8316) += snd-soc-es8316.o
obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o
obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o
obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
+obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o
obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o
obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o
obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o
@@ -557,6 +563,9 @@ obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
obj-$(CONFIG_SND_SOC_ZX_AUD96P22) += snd-soc-zx-aud96p22.o
+obj-$(CONFIG_SND_SOC_RPMSG_WM8960) += snd-soc-rpmsg-wm8960.o
+obj-$(CONFIG_SND_SOC_RPMSG_CS42XX8) += snd-soc-rpmsg-cs42xx8.o
+obj-$(CONFIG_SND_SOC_RPMSG_AK4497) += snd-soc-rpmsg-ak4497.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c
index 71562154c0b1..48eff8ca722a 100644
--- a/sound/soc/codecs/ak4458.c
+++ b/sound/soc/codecs/ak4458.c
@@ -18,12 +18,20 @@
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
+#include <linux/regulator/consumer.h>
#include "ak4458.h"
+#define AK4458_NUM_SUPPLIES 2
+static const char *ak4458_supply_names[AK4458_NUM_SUPPLIES] = {
+ "DVDD",
+ "AVDD",
+};
+
struct ak4458_drvdata {
struct snd_soc_dai_driver *dai_drv;
const struct snd_soc_component_driver *comp_drv;
+ bool dsd512; /* DSD512 is supported or not */
};
/* AK4458 Codec Private Data */
@@ -32,11 +40,13 @@ struct ak4458_priv {
struct regmap *regmap;
struct gpio_desc *reset_gpiod;
struct gpio_desc *mute_gpiod;
+ const struct ak4458_drvdata *drvdata;
int digfil; /* SSLOW, SD, SLOW bits */
int fs; /* sampling rate */
int fmt;
int slots;
int slot_width;
+ struct regulator_bulk_data supplies[AK4458_NUM_SUPPLIES];
};
static const struct reg_default ak4458_reg_defaults[] = {
@@ -128,6 +138,9 @@ static const char * const ak4458_ats_select_texts[] = {
/* DIF2 bit Audio Interface Format Setting(BICK fs) */
static const char * const ak4458_dif_select_texts[] = {"32fs,48fs", "64fs",};
+static const char * const ak4497_dsd_input_path_select[] = {
+ "16_17_19pin", "3_4_5pin"};
+
static const struct soc_enum ak4458_dac1_dem_enum =
SOC_ENUM_SINGLE(AK4458_01_CONTROL2, 1,
ARRAY_SIZE(ak4458_dem_select_texts),
@@ -167,6 +180,10 @@ static const struct soc_enum ak4458_dif_enum =
SOC_ENUM_SINGLE(AK4458_00_CONTROL1, 3,
ARRAY_SIZE(ak4458_dif_select_texts),
ak4458_dif_select_texts);
+static const struct soc_enum ak4497_dsdp_enum =
+ SOC_ENUM_SINGLE(AK4458_09_DSD2, 2,
+ ARRAY_SIZE(ak4497_dsd_input_path_select),
+ ak4497_dsd_input_path_select);
static int get_digfil(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
@@ -274,6 +291,7 @@ static const struct snd_kcontrol_new ak4497_snd_controls[] = {
SOC_ENUM("AK4497 Sound Mode", ak4458_sm_enum),
SOC_ENUM("AK4497 Attenuation transition Time Setting",
ak4458_ats_enum),
+ SOC_ENUM("AK4497 DSD Data Input Pin", ak4497_dsdp_enum),
};
/* ak4497 dapm widgets */
@@ -290,6 +308,20 @@ static const struct snd_soc_dapm_route ak4497_intercon[] = {
};
+static int ak4458_get_tdm_mode(struct ak4458_priv *ak4458)
+{
+ switch (ak4458->slots * ak4458->slot_width) {
+ case 128:
+ return 1;
+ case 256:
+ return 2;
+ case 512:
+ return 3;
+ default:
+ return 0;
+ }
+}
+
static int ak4458_rstn_control(struct snd_soc_component *component, int bit)
{
int ret;
@@ -318,11 +350,60 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream,
struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component);
int pcm_width = max(params_physical_width(params), ak4458->slot_width);
int nfs1;
- u8 format;
+ u8 format, dsdsel0, dsdsel1, dchn;
+ int ret, dsd_bclk, channels, channels_max;
+ bool is_dsd = false;
+
+ channels = params_channels(params);
+ channels_max = dai->driver->playback.channels_max;
+
+ switch (params_format(params)) {
+ 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:
+ is_dsd = true;
+ dsd_bclk = params_rate(params) * params_physical_width(params);
+ break;
+ }
nfs1 = params_rate(params);
ak4458->fs = nfs1;
+ if (is_dsd) {
+ switch (dsd_bclk) {
+ case 2822400:
+ dsdsel0 = 0;
+ dsdsel1 = 0;
+ break;
+ case 5644800:
+ dsdsel0 = 1;
+ dsdsel1 = 0;
+ break;
+ case 11289600:
+ dsdsel0 = 0;
+ dsdsel1 = 1;
+ break;
+ case 22579200:
+ if (ak4458->drvdata->dsd512) {
+ dsdsel0 = 1;
+ dsdsel1 = 1;
+ } else {
+ dev_err(dai->dev, "DSD512 not supported.\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_component_update_bits(component, AK4458_06_DSD1,
+ AK4458_DSDSEL_MASK, dsdsel0);
+ snd_soc_component_update_bits(component, AK4458_09_DSD2,
+ AK4458_DSDSEL_MASK, dsdsel1);
+ }
+
/* Master Clock Frequency Auto Setting Mode Enable */
snd_soc_component_update_bits(component, AK4458_00_CONTROL1, 0x80, 0x80);
@@ -347,6 +428,9 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream,
case SND_SOC_DAIFMT_DSP_B:
format = AK4458_DIF_32BIT_MSB;
break;
+ case SND_SOC_DAIFMT_PDM:
+ format = AK4458_DIF_32BIT_MSB;
+ break;
default:
return -EINVAL;
}
@@ -358,8 +442,24 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream,
snd_soc_component_update_bits(component, AK4458_00_CONTROL1,
AK4458_DIF_MASK, format);
- ak4458_rstn_control(component, 0);
- ak4458_rstn_control(component, 1);
+ /*
+ * Enable/disable Daisy Chain if in TDM mode and the number of played
+ * channels is bigger than the maximum supported number of channels
+ */
+ dchn = ak4458_get_tdm_mode(ak4458) &&
+ (ak4458->fmt == SND_SOC_DAIFMT_DSP_B) &&
+ (channels > channels_max) ? AK4458_DCHAIN_MASK : 0;
+
+ snd_soc_component_update_bits(component, AK4458_0B_CONTROL7,
+ AK4458_DCHAIN_MASK, dchn);
+
+ ret = ak4458_rstn_control(component, 0);
+ if (ret)
+ return ret;
+
+ ret = ak4458_rstn_control(component, 1);
+ if (ret)
+ return ret;
return 0;
}
@@ -368,6 +468,7 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_component *component = dai->component;
struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component);
+ u8 dp;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS: /* Slave Mode */
@@ -385,6 +486,7 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_RIGHT_J:
case SND_SOC_DAIFMT_DSP_B:
+ case SND_SOC_DAIFMT_PDM:
ak4458->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
break;
default:
@@ -393,6 +495,11 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
return -EINVAL;
}
+ /* DSD mode */
+ dp = ak4458->fmt == SND_SOC_DAIFMT_PDM ? AK4458_DP_MASK : 0;
+ snd_soc_component_update_bits(component, AK4458_02_CONTROL3,
+ AK4458_DP_MASK, dp);
+
ak4458_rstn_control(component, 0);
ak4458_rstn_control(component, 1);
@@ -440,31 +547,20 @@ static int ak4458_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
ak4458->slots = slots;
ak4458->slot_width = slot_width;
- switch (slots * slot_width) {
- case 128:
- mode = AK4458_MODE_TDM128;
- break;
- case 256:
- mode = AK4458_MODE_TDM256;
- break;
- case 512:
- mode = AK4458_MODE_TDM512;
- break;
- default:
- mode = AK4458_MODE_NORMAL;
- break;
- }
+ mode = ak4458_get_tdm_mode(ak4458) << AK4458_MODE_SHIFT;
snd_soc_component_update_bits(component, AK4458_0A_CONTROL6,
AK4458_MODE_MASK,
mode);
-
return 0;
}
#define AK4458_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S24_LE |\
- SNDRV_PCM_FMTBIT_S32_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 unsigned int ak4458_rates[] = {
8000, 11025, 16000, 22050,
@@ -649,11 +745,13 @@ static const struct regmap_config ak4458_regmap = {
static const struct ak4458_drvdata ak4458_drvdata = {
.dai_drv = &ak4458_dai,
.comp_drv = &soc_codec_dev_ak4458,
+ .dsd512 = false,
};
static const struct ak4458_drvdata ak4497_drvdata = {
.dai_drv = &ak4497_dai,
.comp_drv = &soc_codec_dev_ak4497,
+ .dsd512 = true,
};
static const struct dev_pm_ops ak4458_pm = {
@@ -665,8 +763,8 @@ static const struct dev_pm_ops ak4458_pm = {
static int ak4458_i2c_probe(struct i2c_client *i2c)
{
struct ak4458_priv *ak4458;
- const struct ak4458_drvdata *drvdata;
int ret;
+ int i;
ak4458 = devm_kzalloc(&i2c->dev, sizeof(*ak4458), GFP_KERNEL);
if (!ak4458)
@@ -679,7 +777,7 @@ static int ak4458_i2c_probe(struct i2c_client *i2c)
i2c_set_clientdata(i2c, ak4458);
ak4458->dev = &i2c->dev;
- drvdata = of_device_get_match_data(&i2c->dev);
+ ak4458->drvdata = of_device_get_match_data(&i2c->dev);
ak4458->reset_gpiod = devm_gpiod_get_optional(ak4458->dev, "reset",
GPIOD_OUT_LOW);
@@ -691,8 +789,25 @@ static int ak4458_i2c_probe(struct i2c_client *i2c)
if (IS_ERR(ak4458->mute_gpiod))
return PTR_ERR(ak4458->mute_gpiod);
- ret = devm_snd_soc_register_component(ak4458->dev, drvdata->comp_drv,
- drvdata->dai_drv, 1);
+ for (i = 0; i < ARRAY_SIZE(ak4458->supplies); i++)
+ ak4458->supplies[i].supply = ak4458_supply_names[i];
+
+ ret = devm_regulator_bulk_get(ak4458->dev, ARRAY_SIZE(ak4458->supplies),
+ ak4458->supplies);
+ if (ret != 0) {
+ dev_err(ak4458->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak4458->supplies),
+ ak4458->supplies);
+ if (ret != 0) {
+ dev_err(ak4458->dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(ak4458->dev, ak4458->drvdata->comp_drv,
+ ak4458->drvdata->dai_drv, 1);
if (ret < 0) {
dev_err(ak4458->dev, "Failed to register CODEC: %d\n", ret);
return ret;
diff --git a/sound/soc/codecs/ak4458.h b/sound/soc/codecs/ak4458.h
index f906215f7e4e..f4cf00720dd7 100644
--- a/sound/soc/codecs/ak4458.h
+++ b/sound/soc/codecs/ak4458.h
@@ -83,4 +83,9 @@
#define AK4458_ATS_SHIFT 6
#define AK4458_ATS_MASK GENMASK(7, 6)
-#endif /* _AK4458_H */
+#define AK4458_DSDSEL_MASK (0x1 << 0)
+#define AK4458_DP_MASK (0x1 << 7)
+
+#define AK4458_DCHAIN_MASK (0x1 << 1)
+
+#endif
diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c
index 8179512129d3..3afac1838541 100644
--- a/sound/soc/codecs/ak5558.c
+++ b/sound/soc/codecs/ak5558.c
@@ -19,9 +19,21 @@
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
+#include <linux/regulator/consumer.h>
#include "ak5558.h"
+#define AK5558_SLAVE_CKS_AUTO
+
+/* enable debug */
+/* #define AK5558_DEBUG */
+
+#define AK5558_NUM_SUPPLIES 2
+static const char *ak5558_supply_names[AK5558_NUM_SUPPLIES] = {
+ "DVDD",
+ "AVDD",
+};
+
/* AK5558 Codec Private Data */
struct ak5558_priv {
struct snd_soc_component component;
@@ -30,6 +42,7 @@ struct ak5558_priv {
struct gpio_desc *reset_gpiod; /* Reset & Power down GPIO */
int slots;
int slot_width;
+ struct regulator_bulk_data supplies[AK5558_NUM_SUPPLIES];
};
/* ak5558 register cache & default register settings */
@@ -350,6 +363,7 @@ static int ak5558_i2c_probe(struct i2c_client *i2c)
{
struct ak5558_priv *ak5558;
int ret = 0;
+ int i;
ak5558 = devm_kzalloc(&i2c->dev, sizeof(*ak5558), GFP_KERNEL);
if (!ak5558)
@@ -367,6 +381,23 @@ static int ak5558_i2c_probe(struct i2c_client *i2c)
if (IS_ERR(ak5558->reset_gpiod))
return PTR_ERR(ak5558->reset_gpiod);
+ for (i = 0; i < ARRAY_SIZE(ak5558->supplies); i++)
+ ak5558->supplies[i].supply = ak5558_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(ak5558->supplies),
+ ak5558->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak5558->supplies),
+ ak5558->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
ret = devm_snd_soc_register_component(&i2c->dev,
&soc_codec_dev_ak5558,
&ak5558_dai, 1);
diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c
index 94b1adb088fd..36e893a15c59 100644
--- a/sound/soc/codecs/cs42xx8.c
+++ b/sound/soc/codecs/cs42xx8.c
@@ -17,6 +17,7 @@
#include <linux/gpio/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
+#include <linux/pm_domain.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
@@ -43,7 +44,8 @@ struct cs42xx8_priv {
struct regmap *regmap;
struct clk *clk;
- bool slave_mode;
+ bool slave_mode[2];
+ bool is_tdm;
unsigned long sysclk;
u32 tx_channels;
struct gpio_desc *gpiod_reset;
@@ -131,7 +133,6 @@ static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("AIN2L"),
SND_SOC_DAPM_INPUT("AIN2R"),
- SND_SOC_DAPM_SUPPLY("PWR", CS42XX8_PWRCTL, 0, 1, NULL, 0),
};
static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = {
@@ -145,35 +146,28 @@ static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = {
/* Playback */
{ "AOUT1L", NULL, "DAC1" },
{ "AOUT1R", NULL, "DAC1" },
- { "DAC1", NULL, "PWR" },
{ "AOUT2L", NULL, "DAC2" },
{ "AOUT2R", NULL, "DAC2" },
- { "DAC2", NULL, "PWR" },
{ "AOUT3L", NULL, "DAC3" },
{ "AOUT3R", NULL, "DAC3" },
- { "DAC3", NULL, "PWR" },
{ "AOUT4L", NULL, "DAC4" },
{ "AOUT4R", NULL, "DAC4" },
- { "DAC4", NULL, "PWR" },
/* Capture */
{ "ADC1", NULL, "AIN1L" },
{ "ADC1", NULL, "AIN1R" },
- { "ADC1", NULL, "PWR" },
{ "ADC2", NULL, "AIN2L" },
{ "ADC2", NULL, "AIN2R" },
- { "ADC2", NULL, "PWR" },
};
static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = {
/* Capture */
{ "ADC3", NULL, "AIN3L" },
{ "ADC3", NULL, "AIN3R" },
- { "ADC3", NULL, "PWR" },
};
struct cs42xx8_ratios {
@@ -218,6 +212,8 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai,
struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
u32 val;
+ cs42xx8->is_tdm = false;
+
/* Set DAI format */
switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_LEFT_J:
@@ -231,6 +227,7 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai,
break;
case SND_SOC_DAIFMT_DSP_A:
val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM;
+ cs42xx8->is_tdm = true;
break;
default:
dev_err(component->dev, "unsupported dai format\n");
@@ -241,17 +238,21 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai,
CS42XX8_INTF_DAC_DIF_MASK |
CS42XX8_INTF_ADC_DIF_MASK, val);
- /* Set master/slave audio interface */
- switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBS_CFS:
- cs42xx8->slave_mode = true;
- break;
- case SND_SOC_DAIFMT_CBM_CFM:
- cs42xx8->slave_mode = false;
- break;
- default:
- dev_err(component->dev, "unsupported master/slave mode\n");
- return -EINVAL;
+ if (cs42xx8->slave_mode[0] == cs42xx8->slave_mode[1]) {
+ /* Set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs42xx8->slave_mode[0] = true;
+ cs42xx8->slave_mode[1] = true;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs42xx8->slave_mode[0] = false;
+ cs42xx8->slave_mode[1] = false;
+ break;
+ default:
+ dev_err(component->dev, "unsupported master/slave mode\n");
+ return -EINVAL;
+ }
}
return 0;
@@ -281,7 +282,7 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream,
/* Get functional mode for tx and rx according to rate */
for (i = 0; i < 2; i++) {
- if (cs42xx8->slave_mode) {
+ if (cs42xx8->slave_mode[i]) {
fm[i] = CS42XX8_FM_AUTO;
} else {
if (rate[i] < 50000) {
@@ -336,6 +337,16 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream,
cs42xx8->rate[tx] = params_rate(params);
+ if (cs42xx8->is_tdm && !cs42xx8->slave_mode[tx]) {
+ dev_err(component->dev, "TDM mode is unsupported in master mode\n");
+ return -EINVAL;
+ }
+
+ if (cs42xx8->is_tdm && (cs42xx8->sysclk < 256 * cs42xx8->rate[tx])) {
+ dev_err(component->dev, "unsupported sysclk for TDM mode\n");
+ return -EINVAL;
+ }
+
mask = CS42XX8_FUNCMOD_MFREQ_MASK;
val = cs42xx8_ratios[i].mfreq;
@@ -459,6 +470,8 @@ const struct regmap_config cs42xx8_regmap_config = {
.volatile_reg = cs42xx8_volatile_register,
.writeable_reg = cs42xx8_writeable_register,
.cache_type = REGCACHE_RBTREE,
+ .use_single_read = true,
+ .use_single_write = true,
};
EXPORT_SYMBOL_GPL(cs42xx8_regmap_config);
@@ -482,7 +495,8 @@ static int cs42xx8_component_probe(struct snd_soc_component *component)
/* Mute all DAC channels */
regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL);
-
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 0);
return 0;
}
@@ -521,9 +535,11 @@ EXPORT_SYMBOL_GPL(cs42xx8_of_match);
int cs42xx8_probe(struct device *dev, struct regmap *regmap)
{
+ struct device_node *np = dev->of_node;
const struct of_device_id *of_id;
struct cs42xx8_priv *cs42xx8;
int ret, val, i;
+ int num_domains = 0;
if (IS_ERR(regmap)) {
ret = PTR_ERR(regmap);
@@ -563,6 +579,36 @@ int cs42xx8_probe(struct device *dev, struct regmap *regmap)
cs42xx8->sysclk = clk_get_rate(cs42xx8->clk);
+ num_domains = of_count_phandle_with_args(np, "power-domains",
+ "#power-domain-cells");
+ for (i = 0; i < num_domains; i++) {
+ struct device *pd_dev;
+ struct device_link *link;
+
+ pd_dev = dev_pm_domain_attach_by_id(dev, i);
+ if (IS_ERR(pd_dev))
+ return PTR_ERR(pd_dev);
+
+ link = device_link_add(dev, pd_dev,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME |
+ DL_FLAG_RPM_ACTIVE);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ }
+
+ if (of_property_read_bool(np, "fsl,txm-rxs")) {
+ /* 0 -- rx, 1 -- tx */
+ cs42xx8->slave_mode[0] = true;
+ cs42xx8->slave_mode[1] = false;
+ }
+
+ if (of_property_read_bool(np, "fsl,txs-rxm")) {
+ /* 0 -- rx, 1 -- tx */
+ cs42xx8->slave_mode[0] = false;
+ cs42xx8->slave_mode[1] = true;
+ }
+
for (i = 0; i < ARRAY_SIZE(cs42xx8->supplies); i++)
cs42xx8->supplies[i].supply = cs42xx8_supply_names[i];
diff --git a/sound/soc/codecs/fsl_mqs.c b/sound/soc/codecs/fsl_mqs.c
new file mode 100644
index 000000000000..c82d889bfabb
--- /dev/null
+++ b/sound/soc/codecs/fsl_mqs.c
@@ -0,0 +1,357 @@
+/*
+ * ALSA SoC IMX MQS driver
+ *
+ * Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#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/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)
+
+
+/* codec private data */
+struct fsl_mqs {
+ struct platform_device *pdev;
+ struct regmap *gpr;
+ unsigned int reg_iomuxc_gpr2;
+
+ struct regmap *regmap;
+ unsigned int reg_mqs_ctrl;
+
+ struct clk *mclk;
+ struct clk *ipg;
+
+ unsigned long mclk_rate;
+
+ int sysclk_rate;
+ int bclk;
+ int lrclk;
+ bool use_gpr;
+ char name[32];
+};
+
+#define FSL_MQS_RATES SNDRV_PCM_RATE_8000_192000
+#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);
+ int div, res;
+
+ mqs_priv->mclk_rate = clk_get_rate(mqs_priv->mclk);
+
+ mqs_priv->bclk = snd_soc_params_to_bclk(params);
+ mqs_priv->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 = mqs_priv->mclk_rate / (32 * 2 * mqs_priv->lrclk * 8);
+ res = mqs_priv->mclk_rate % (32 * 2 * mqs_priv->lrclk * 8);
+
+ if (res == 0 && div > 0 && div <= 256) {
+ if (mqs_priv->use_gpr) {
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2,
+ IMX6SX_GPR2_MQS_CLK_DIV_MASK,
+ (div-1) << IMX6SX_GPR2_MQS_CLK_DIV_SHIFT);
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2,
+ IMX6SX_GPR2_MQS_OVERSAMPLE_MASK,
+ 0 << IMX6SX_GPR2_MQS_OVERSAMPLE_SHIFT);
+ } else {
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_CLK_DIV_MASK,
+ (div-1) << MQS_CLK_DIV_SHIFT);
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_OVERSAMPLE_MASK,
+ 0 << MQS_OVERSAMPLE_SHIFT);
+ }
+ } else
+ dev_err(&mqs_priv->pdev->dev, "can't get proper divider\n");
+
+ return 0;
+}
+
+static int fsl_mqs_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ 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_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_mqs_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_component *component = dai->component;
+ struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component);
+
+ mqs_priv->sysclk_rate = freq;
+
+ 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);
+
+ if (mqs_priv->use_gpr)
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, IMX6SX_GPR2_MQS_EN_MASK,
+ 1 << IMX6SX_GPR2_MQS_EN_SHIFT);
+ else
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_EN_MASK,
+ 1 << MQS_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);
+
+ if (mqs_priv->use_gpr)
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2,
+ IMX6SX_GPR2_MQS_EN_MASK, 0);
+ else
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_EN_MASK, 0);
+}
+
+static struct snd_soc_component_driver soc_codec_fsl_mqs = {
+ .idle_bias_on = 1,
+ .non_legacy_dai_naming = 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,
+ .set_sysclk = fsl_mqs_set_dai_sysclk,
+};
+
+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 = 0;
+ struct fsl_mqs *mqs_priv;
+ struct resource *res;
+ void __iomem *regs;
+ int ret = 0;
+
+ mqs_priv = devm_kzalloc(&pdev->dev, sizeof(*mqs_priv), GFP_KERNEL);
+ if (!mqs_priv)
+ return -ENOMEM;
+
+ mqs_priv->pdev = pdev;
+ strncpy(mqs_priv->name, np->name, sizeof(mqs_priv->name) - 1);
+
+ if (of_device_is_compatible(np, "fsl,imx8qm-mqs"))
+ mqs_priv->use_gpr = false;
+ else
+ mqs_priv->use_gpr = true;
+
+ if (mqs_priv->use_gpr) {
+ gpr_np = of_parse_phandle(np, "gpr", 0);
+ if (IS_ERR(gpr_np)) {
+ dev_err(&pdev->dev, "failed to get gpr node by phandle\n");
+ ret = PTR_ERR(gpr_np);
+ goto out;
+ }
+
+ mqs_priv->gpr = syscon_node_to_regmap(gpr_np);
+ if (IS_ERR(mqs_priv->gpr)) {
+ dev_err(&pdev->dev, "failed to get gpr regmap\n");
+ ret = PTR_ERR(mqs_priv->gpr);
+ goto out;
+ }
+ } else {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ 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));
+ goto out;
+ }
+ }
+
+ 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));
+ goto out;
+ }
+
+ dev_set_drvdata(&pdev->dev, mqs_priv);
+ pm_runtime_enable(&pdev->dev);
+
+ return devm_snd_soc_register_component(&pdev->dev, &soc_codec_fsl_mqs,
+ &fsl_mqs_dai, 1);
+out:
+ if (!IS_ERR(gpr_np))
+ of_node_put(gpr_np);
+
+ return ret;
+}
+
+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);
+
+ if (mqs_priv->ipg)
+ clk_prepare_enable(mqs_priv->ipg);
+
+ if (mqs_priv->mclk)
+ clk_prepare_enable(mqs_priv->mclk);
+
+ if (mqs_priv->use_gpr)
+ regmap_write(mqs_priv->gpr, IOMUXC_GPR2,
+ mqs_priv->reg_iomuxc_gpr2);
+ else
+ regmap_write(mqs_priv->regmap, REG_MQS_CTRL,
+ 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);
+
+ if (mqs_priv->use_gpr)
+ regmap_read(mqs_priv->gpr, IOMUXC_GPR2,
+ &mqs_priv->reg_iomuxc_gpr2);
+ else
+ regmap_read(mqs_priv->regmap, REG_MQS_CTRL,
+ &mqs_priv->reg_mqs_ctrl);
+
+ if (mqs_priv->mclk)
+ clk_disable_unprepare(mqs_priv->mclk);
+
+ if (mqs_priv->ipg)
+ 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 of_device_id fsl_mqs_dt_ids[] = {
+ { .compatible = "fsl,imx8qm-mqs", },
+ { .compatible = "fsl,imx6sx-mqs", },
+ {}
+};
+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@freescale.com>");
+MODULE_DESCRIPTION("MQS dummy codec driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform: fsl-mqs");
diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c
index f8b5b960e597..54d640065eac 100644
--- a/sound/soc/codecs/hdmi-codec.c
+++ b/sound/soc/codecs/hdmi-codec.c
@@ -281,6 +281,7 @@ struct hdmi_codec_priv {
static const struct snd_soc_dapm_widget hdmi_widgets[] = {
SND_SOC_DAPM_OUTPUT("TX"),
+ SND_SOC_DAPM_OUTPUT("RX"),
};
enum {
@@ -388,6 +389,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int ret = 0;
ret = test_and_set_bit(0, &hcp->busy);
@@ -402,7 +404,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream,
goto err;
}
- if (hcp->hcd.ops->get_eld) {
+ if (tx && hcp->hcd.ops->get_eld) {
ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data,
hcp->eld, sizeof(hcp->eld));
@@ -471,13 +473,11 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
/* Select a channel allocation that matches with ELD and pcm channels */
idx = hdmi_codec_get_ch_alloc_table_idx(hcp, hp.cea.channels);
if (idx < 0) {
- dev_err(dai->dev, "Not able to map channels to speakers (%d)\n",
- idx);
hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN;
- return idx;
+ } else {
+ hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id;
+ hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id;
}
- hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id;
- hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id;
hp.sample_width = params_width(params);
hp.sample_rate = params_rate(params);
@@ -647,14 +647,20 @@ static int hdmi_dai_probe(struct snd_soc_dai *dai)
{
struct snd_soc_dapm_context *dapm;
struct hdmi_codec_daifmt *daifmt;
- struct snd_soc_dapm_route route = {
- .sink = "TX",
- .source = dai->driver->playback.stream_name,
+ struct snd_soc_dapm_route route[] = {
+ {
+ .sink = "TX",
+ .source = dai->driver->playback.stream_name,
+ },
+ {
+ .sink = dai->driver->playback.stream_name,
+ .source = "RX",
+ },
};
int ret;
dapm = snd_soc_component_get_dapm(dai->component);
- ret = snd_soc_dapm_add_routes(dapm, &route, 1);
+ ret = snd_soc_dapm_add_routes(dapm, route, 2);
if (ret)
return ret;
@@ -744,6 +750,14 @@ static const struct snd_soc_dai_driver hdmi_i2s_dai = {
.sig_bits = 24,
},
.ops = &hdmi_codec_i2s_dai_ops,
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = HDMI_RATES,
+ .formats = I2S_FORMATS,
+ .sig_bits = 24,
+ },
.pcm_new = hdmi_codec_pcm_new,
};
@@ -760,6 +774,13 @@ static const struct snd_soc_dai_driver hdmi_spdif_dai = {
.formats = SPDIF_FORMATS,
},
.ops = &hdmi_codec_spdif_dai_ops,
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = HDMI_RATES,
+ .formats = SPDIF_FORMATS,
+ },
.pcm_new = hdmi_codec_pcm_new,
};
diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c
index e3d311fb510e..aa9a8ac987dc 100644
--- a/sound/soc/codecs/msm8916-wcd-analog.c
+++ b/sound/soc/codecs/msm8916-wcd-analog.c
@@ -1185,8 +1185,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev)
}
irq = platform_get_irq_byname(pdev, "mbhc_switch_int");
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(dev, "failed to get mbhc switch irq\n");
return irq;
+ }
ret = devm_request_threaded_irq(dev, irq, NULL,
pm8916_mbhc_switch_irq_handler,
@@ -1198,8 +1200,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev)
if (priv->mbhc_btn_enabled) {
irq = platform_get_irq_byname(pdev, "mbhc_but_press_det");
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(dev, "failed to get button press irq\n");
return irq;
+ }
ret = devm_request_threaded_irq(dev, irq, NULL,
mbhc_btn_press_irq_handler,
@@ -1210,8 +1214,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev)
dev_err(dev, "cannot request mbhc button press irq\n");
irq = platform_get_irq_byname(pdev, "mbhc_but_rel_det");
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(dev, "failed to get button release irq\n");
return irq;
+ }
ret = devm_request_threaded_irq(dev, irq, NULL,
mbhc_btn_release_irq_handler,
diff --git a/sound/soc/codecs/rpmsg_ak4497.c b/sound/soc/codecs/rpmsg_ak4497.c
new file mode 100644
index 000000000000..8f5657c57fbc
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_ak4497.c
@@ -0,0 +1,1110 @@
+/*
+ * ak4497.c -- audio driver for AK4497
+ *
+ * Copyright (C) 2016 Asahi Kasei Microdevices Corporation
+ * Copyright (C) 2017, NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/pcm_params.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include "../fsl/fsl_rpmsg_i2s.h"
+
+#include "rpmsg_ak4497.h"
+
+//#define AK4497_DEBUG //used at debug mode
+#define AK4497_NUM_SUPPLIES 2
+static const char *ak4497_supply_names[AK4497_NUM_SUPPLIES] = {
+ "DVDD",
+ "AVDD",
+};
+
+/* AK4497 Codec Private Data */
+struct rpmsg_ak4497_priv {
+ struct regmap *regmap;
+ int fs1; /* Sampling Frequency */
+ int nBickFreq; /* 0: 48fs for 24bit, 1: 64fs or more for 32bit */
+ int nTdmSds;
+ int pdn_gpio;
+ int mute_gpio;
+ int fmt;
+ struct regulator_bulk_data supplies[AK4497_NUM_SUPPLIES];
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+ int audioindex;
+ struct platform_device *pdev;
+};
+
+/* ak4497 register cache & default register settings */
+static const struct reg_default ak4497_reg[] = {
+ { AK4497_00_CONTROL1, 0x0C},
+ { AK4497_01_CONTROL2, 0x22},
+ { AK4497_02_CONTROL3, 0x00},
+ { AK4497_03_LCHATT, 0xFF},
+ { AK4497_04_RCHATT, 0xFF},
+ { AK4497_05_CONTROL4, 0x00},
+ { AK4497_06_DSD1, 0x00},
+ { AK4497_07_CONTROL5, 0x00},
+ { AK4497_08_SOUNDCONTROL, 0x00},
+ { AK4497_09_DSD2, 0x00},
+ { AK4497_0A_CONTROL7, 0x04},
+ { AK4497_0B_CONTROL8, 0x00},
+ { AK4497_0C_RESERVED, 0x00},
+ { AK4497_0D_RESERVED, 0x00},
+ { AK4497_0E_RESERVED, 0x00},
+ { AK4497_0F_RESERVED, 0x00},
+ { AK4497_10_RESERVED, 0x00},
+ { AK4497_11_RESERVED, 0x00},
+ { AK4497_12_RESERVED, 0x00},
+ { AK4497_13_RESERVED, 0x00},
+ { AK4497_14_RESERVED, 0x00},
+ { AK4497_15_DFSREAD, 0x00},
+};
+
+/* Volume control:
+ * from -127 to 0 dB in 0.5 dB steps (mute instead of -127.5 dB)
+ */
+static DECLARE_TLV_DB_SCALE(latt_tlv, -12750, 50, 0);
+static DECLARE_TLV_DB_SCALE(ratt_tlv, -12750, 50, 0);
+
+static const char * const ak4497_ecs_select_texts[] = {"768kHz", "384kHz"};
+
+static const char * const ak4497_dem_select_texts[] = {
+ "44.1kHz", "OFF", "48kHz", "32kHz"};
+static const char * const ak4497_dzfm_select_texts[] = {
+ "Separated", "ANDed"};
+
+static const char * const ak4497_sellr_select_texts[] = {
+ "Rch", "Lch"};
+static const char * const ak4497_dckb_select_texts[] = {
+ "Falling", "Rising"};
+static const char * const ak4497_dcks_select_texts[] = {
+ "512fs", "768fs"};
+
+static const char * const ak4497_dsdd_select_texts[] = {
+ "Normal", "Volume Bypass"};
+
+static const char * const ak4497_sc_select_texts[] = {
+ "Setting 1", "Setting 2", "Setting 3"};
+static const char * const ak4497_dsdf_select_texts[] = {
+ "50kHz", "150kHz"};
+static const char * const ak4497_dsd_input_path_select[] = {
+ "16_17_19pin", "3_4_5pin"};
+static const char * const ak4497_ats_select_texts[] = {
+ "4080/fs", "2040/fs", "510/fs", "255/fs"};
+
+static const struct soc_enum ak4497_dac_enum[] = {
+ SOC_ENUM_SINGLE(AK4497_00_CONTROL1, 5,
+ ARRAY_SIZE(ak4497_ecs_select_texts),
+ ak4497_ecs_select_texts),
+ SOC_ENUM_SINGLE(AK4497_01_CONTROL2, 1,
+ ARRAY_SIZE(ak4497_dem_select_texts),
+ ak4497_dem_select_texts),
+ SOC_ENUM_SINGLE(AK4497_01_CONTROL2, 6,
+ ARRAY_SIZE(ak4497_dzfm_select_texts),
+ ak4497_dzfm_select_texts),
+ SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 1,
+ ARRAY_SIZE(ak4497_sellr_select_texts),
+ ak4497_sellr_select_texts),
+ SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 4,
+ ARRAY_SIZE(ak4497_dckb_select_texts),
+ ak4497_dckb_select_texts),
+ SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 5,
+ ARRAY_SIZE(ak4497_dcks_select_texts),
+ ak4497_dcks_select_texts),
+ SOC_ENUM_SINGLE(AK4497_06_DSD1, 1,
+ ARRAY_SIZE(ak4497_dsdd_select_texts),
+ ak4497_dsdd_select_texts),
+ SOC_ENUM_SINGLE(AK4497_08_SOUNDCONTROL, 0,
+ ARRAY_SIZE(ak4497_sc_select_texts),
+ ak4497_sc_select_texts),
+ SOC_ENUM_SINGLE(AK4497_09_DSD2, 1,
+ ARRAY_SIZE(ak4497_dsdf_select_texts),
+ ak4497_dsdf_select_texts),
+ SOC_ENUM_SINGLE(AK4497_09_DSD2, 2,
+ ARRAY_SIZE(ak4497_dsd_input_path_select),
+ ak4497_dsd_input_path_select),
+ SOC_ENUM_SINGLE(AK4497_0B_CONTROL8, 6,
+ ARRAY_SIZE(ak4497_ats_select_texts),
+ ak4497_ats_select_texts),
+};
+
+static const char * const ak4497_dsdsel_select_texts[] = {
+ "64fs", "128fs", "256fs", "512fs"};
+static const char * const ak4497_bickfreq_select[] = {"48fs", "64fs"};
+
+static const char * const ak4497_tdm_sds_select[] = {
+ "L1R1", "TDM128_L1R1", "TDM128_L2R2",
+ "TDM256_L1R1", "TDM256_L2R2", "TDM256_L3R3", "TDM256_L4R4",
+ "TDM512_L1R1", "TDM512_L2R2", "TDM512_L3R3", "TDM512_L4R4",
+ "TDM512_L5R5", "TDM512_L6R6", "TDM512_L7R7", "TDM512_L8R8",
+};
+
+static const char * const ak4497_adfs_select[] = {
+ "Normal Speed Mode", "Double Speed Mode", "Quad Speed Mode",
+ "Quad Speed Mode", "Oct Speed Mode", "Hex Speed Mode", "Oct Speed Mode",
+ "Hex Speed Mode"
+};
+
+static const struct soc_enum ak4497_dac_enum2[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_dsdsel_select_texts),
+ ak4497_dsdsel_select_texts),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_bickfreq_select),
+ ak4497_bickfreq_select),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_tdm_sds_select),
+ ak4497_tdm_sds_select),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_adfs_select),
+ ak4497_adfs_select)
+};
+
+static int ak4497_read(struct snd_soc_component *component, unsigned int reg,
+ unsigned int *val)
+{
+ int ret;
+
+ ret = snd_soc_component_read(component, reg, val);
+ if (ret < 0)
+ dev_err(component->dev, "Register %u read failed, ret=%d.\n", reg, ret);
+
+ return ret;
+}
+
+static int ak4497_get_dsdsel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ unsigned int dsdsel0, dsdsel1;
+
+ ak4497_read(component, AK4497_06_DSD1, &dsdsel0);
+ dsdsel0 &= AK4497_DSDSEL0;
+
+ ak4497_read(component, AK4497_09_DSD2, &dsdsel1);
+ dsdsel1 &= AK4497_DSDSEL1;
+
+ ucontrol->value.enumerated.item[0] = ((dsdsel1 << 1) | dsdsel0);
+
+ return 0;
+}
+
+static int ak4497_set_dsdsel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ unsigned int dsdsel = ucontrol->value.enumerated.item[0];
+
+ switch (dsdsel) {
+ case 0: /* 2.8224MHz */
+ snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x00);
+ snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x00);
+ break;
+ case 1: /* 5.6448MHz */
+ snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x01);
+ snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x00);
+ break;
+ case 2: /* 11.2896MHz */
+ snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x00);
+ snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x01);
+ break;
+ case 3: /* 22.5792MHz */
+ snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x01);
+ snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x01);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ak4497_get_bickfs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.enumerated.item[0] = ak4497->nBickFreq;
+
+ return 0;
+}
+
+static int ak4497_set_bickfs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+
+ ak4497->nBickFreq = ucontrol->value.enumerated.item[0];
+
+ return 0;
+}
+
+static int ak4497_get_tdmsds(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.enumerated.item[0] = ak4497->nTdmSds;
+
+ return 0;
+}
+
+static int ak4497_set_tdmsds(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ int regA, regB;
+
+ ak4497->nTdmSds = ucontrol->value.enumerated.item[0];
+
+ if (ak4497->nTdmSds == 0)
+ regB = 0; /* SDS0 bit = 0 */
+ else
+ regB = (1 & (ak4497->nTdmSds - 1)); /* SDS0 bit = 1 */
+
+ switch (ak4497->nTdmSds) {
+ case 0:
+ regA = 0; /* Normal */
+ break;
+ case 1:
+ case 2:
+ regA = 4; /* TDM128 TDM1-0bits = 1 */
+ break;
+ case 3:
+ case 4:
+ regA = 8; /* TDM128 TDM1-0bits = 2 */
+ break;
+ case 5:
+ case 6:
+ regA = 9; /* TDM128 TDM1-0bits = 2 */
+ break;
+ case 7:
+ case 8:
+ regA = 0xC; /* TDM128 TDM1-0bits = 3 */
+ break;
+ case 9:
+ case 10:
+ regA = 0xD; /* TDM128 TDM1-0bits = 3 */
+ break;
+ case 11:
+ case 12:
+ regA = 0xE; /* TDM128 TDM1-0bits = 3 */
+ break;
+ case 13:
+ case 14:
+ regA = 0xF; /* TDM128 TDM1-0bits = 3 */
+ break;
+ default:
+ regA = 0;
+ regB = 0;
+ break;
+ }
+
+ regA <<= 4;
+ regB <<= 4;
+
+ snd_soc_component_update_bits(component, AK4497_0A_CONTROL7, 0xF0, regA);
+ snd_soc_component_update_bits(component, AK4497_0B_CONTROL8, 0x10, regB);
+
+ return 0;
+}
+
+static int ak4497_get_adfs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ unsigned int nADFSbit;
+
+ ak4497_read(component, AK4497_15_DFSREAD, &nADFSbit);
+ nADFSbit &= 0x7;
+
+ ucontrol->value.enumerated.item[0] = nADFSbit;
+
+ return 0;
+}
+
+static int ak4497_set_adfs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ pr_debug("AK4497 : ADFS is read only\n");
+
+ return 0;
+}
+
+static const char * const gain_control_texts[] = {
+ "2.8_2.8Vpp", "2.8_2.5Vpp", "2.5_2.5Vpp", "3.75_3.75Vpp", "3.75_2.5Vpp"
+};
+
+static const unsigned int gain_control_values[] = {
+ 0, 1, 2, 4, 5
+};
+
+static const struct soc_enum ak4497_gain_control_enum =
+ SOC_VALUE_ENUM_SINGLE(AK4497_07_CONTROL5, 1, 7,
+ ARRAY_SIZE(gain_control_texts),
+ gain_control_texts,
+ gain_control_values);
+
+#ifdef AK4497_DEBUG
+
+static const char * const test_reg_select[] = {
+ "read AK4497 Reg 00:0B",
+ "read AK4497 Reg 15"
+};
+
+static const struct soc_enum ak4497_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(test_reg_select), test_reg_select),
+};
+
+static int nTestRegNo;
+
+static int get_test_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ /* Get the current output routing */
+ ucontrol->value.enumerated.item[0] = nTestRegNo;
+
+ return 0;
+}
+
+static int set_test_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ u32 currMode = ucontrol->value.enumerated.item[0];
+ int i, regs, rege;
+ unsigned int value;
+
+ nTestRegNo = currMode;
+
+ if (nTestRegNo == 0) {
+ regs = 0x00;
+ rege = 0x0B;
+ } else {
+ regs = 0x15;
+ rege = 0x15;
+ }
+
+ for (i = regs; i <= rege; i++) {
+ ak4497_read(component, i, &value);
+ pr_debug("***AK4497 Addr,Reg=(%x, %x)\n", i, value);
+ }
+
+ return 0;
+}
+#endif
+
+static const struct snd_kcontrol_new ak4497_snd_controls[] = {
+ SOC_SINGLE_TLV("AK4497 Lch Digital Volume",
+ AK4497_03_LCHATT, 0, 0xFF, 0, latt_tlv),
+ SOC_SINGLE_TLV("AK4497 Rch Digital Volume",
+ AK4497_04_RCHATT, 0, 0xFF, 0, ratt_tlv),
+
+ SOC_ENUM("AK4497 EX DF I/F clock", ak4497_dac_enum[0]),
+ SOC_ENUM("AK4497 De-emphasis Response", ak4497_dac_enum[1]),
+ SOC_ENUM("AK4497 Data Zero Detect Mode", ak4497_dac_enum[2]),
+ SOC_ENUM("AK4497 Data Selection at Mono Mode", ak4497_dac_enum[3]),
+
+ SOC_ENUM("AK4497 Polarity of DCLK", ak4497_dac_enum[4]),
+ SOC_ENUM("AK4497 DCKL Frequency", ak4497_dac_enum[5]),
+
+ SOC_ENUM("AK4497 DDSD Play Back Path", ak4497_dac_enum[6]),
+ SOC_ENUM("AK4497 Sound control", ak4497_dac_enum[7]),
+ SOC_ENUM("AK4497 Cut Off of DSD Filter", ak4497_dac_enum[8]),
+
+ SOC_ENUM_EXT("AK4497 DSD Data Stream", ak4497_dac_enum2[0],
+ ak4497_get_dsdsel, ak4497_set_dsdsel),
+ SOC_ENUM_EXT("AK4497 BICK Frequency Select", ak4497_dac_enum2[1],
+ ak4497_get_bickfs, ak4497_set_bickfs),
+ SOC_ENUM_EXT("AK4497 TDM Data Select", ak4497_dac_enum2[2],
+ ak4497_get_tdmsds, ak4497_set_tdmsds),
+
+ SOC_SINGLE("AK4497 External Digital Filter", AK4497_00_CONTROL1,
+ 6, 1, 0),
+ SOC_SINGLE("AK4497 MCLK Frequency Auto Setting", AK4497_00_CONTROL1,
+ 7, 1, 0),
+ SOC_SINGLE("AK4497 MCLK FS Auto Detect", AK4497_00_CONTROL1, 4, 1, 0),
+
+ SOC_SINGLE("AK4497 Soft Mute Control", AK4497_01_CONTROL2, 0, 1, 0),
+ SOC_SINGLE("AK4497 Short delay filter", AK4497_01_CONTROL2, 5, 1, 0),
+ SOC_SINGLE("AK4497 Data Zero Detect Enable", AK4497_01_CONTROL2,
+ 7, 1, 0),
+ SOC_SINGLE("AK4497 Slow Roll-off Filter", AK4497_02_CONTROL3, 0, 1, 0),
+ SOC_SINGLE("AK4497 Invering Enable of DZF", AK4497_02_CONTROL3,
+ 4, 1, 0),
+ SOC_SINGLE("AK4497 Mono Mode", AK4497_02_CONTROL3, 3, 1, 0),
+ SOC_SINGLE("AK4497 Super Slow Roll-off Filter", AK4497_05_CONTROL4,
+ 0, 1, 0),
+ SOC_SINGLE("AK4497 AOUTR Phase Inverting", AK4497_05_CONTROL4,
+ 6, 1, 0),
+ SOC_SINGLE("AK4497 AOUTL Phase Inverting", AK4497_05_CONTROL4,
+ 7, 1, 0),
+ SOC_SINGLE("AK4497 DSD Mute Release", AK4497_06_DSD1, 3, 1, 0),
+ SOC_SINGLE("AK4497 DSD Mute Control Hold", AK4497_06_DSD1, 4, 1, 0),
+ SOC_SINGLE("AK4497 DSDR is detected", AK4497_06_DSD1, 5, 1, 0),
+ SOC_SINGLE("AK4497 DSDL is detected", AK4497_06_DSD1, 6, 1, 0),
+ SOC_SINGLE("AK4497 DSD Data Mute", AK4497_06_DSD1, 7, 1, 0),
+ SOC_SINGLE("AK4497 Synchronization Control", AK4497_07_CONTROL5,
+ 0, 1, 0),
+
+ SOC_ENUM("AK4497 Output Level", ak4497_gain_control_enum),
+ SOC_SINGLE("AK4497 High Sonud Quality Mode", AK4497_08_SOUNDCONTROL,
+ 2, 1, 0),
+ SOC_SINGLE("AK4497 Heavy Load Mode", AK4497_08_SOUNDCONTROL, 3, 1, 0),
+ SOC_ENUM("AK4497 DSD Data Input Pin", ak4497_dac_enum[9]),
+ SOC_SINGLE("AK4497 Daisy Chain", AK4497_0B_CONTROL8, 1, 1, 0),
+ SOC_ENUM("AK4497 ATT Transit Time", ak4497_dac_enum[10]),
+
+ SOC_ENUM_EXT("AK4497 Read FS Auto Detect Mode", ak4497_dac_enum2[3],
+ ak4497_get_adfs, ak4497_set_adfs),
+
+#ifdef AK4497_DEBUG
+ SOC_ENUM_EXT("Reg Read", ak4497_enum[0], get_test_reg, set_test_reg),
+#endif
+
+};
+
+static const char * const ak4497_dac_enable_texts[] = {"Off", "On"};
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak4497_dac_enable_enum,
+ ak4497_dac_enable_texts);
+
+static const struct snd_kcontrol_new ak4497_dac_enable_control =
+ SOC_DAPM_ENUM("DAC Switch", ak4497_dac_enable_enum);
+
+/* ak4497 dapm widgets */
+static const struct snd_soc_dapm_widget ak4497_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("AK4497 SDTI", "Playback", 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_DAC("AK4497 DAC", NULL, AK4497_0A_CONTROL7, 2, 0),
+
+ SND_SOC_DAPM_MUX("AK4497 DAC Enable", SND_SOC_NOPM,
+ 0, 0, &ak4497_dac_enable_control),
+
+ SND_SOC_DAPM_OUTPUT("AK4497 AOUT"),
+
+};
+
+static const struct snd_soc_dapm_route ak4497_intercon[] = {
+ {"AK4497 DAC", NULL, "AK4497 SDTI"},
+ {"AK4497 DAC Enable", "On", "AK4497 DAC"},
+ {"AK4497 AOUT", NULL, "AK4497 DAC Enable"},
+};
+
+static int ak4497_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 rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ snd_pcm_format_t pcm_format = params_format(params);
+
+ unsigned int dfs, dfs2, dsdsel0, dsdsel1, format;
+ int nfs1;
+ bool is_dsd = false;
+ int dsd_bclk;
+
+ if (pcm_format == SNDRV_PCM_FORMAT_DSD_U8 ||
+ pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE ||
+ pcm_format == SNDRV_PCM_FORMAT_DSD_U16_BE ||
+ pcm_format == SNDRV_PCM_FORMAT_DSD_U32_LE ||
+ pcm_format == SNDRV_PCM_FORMAT_DSD_U32_BE)
+ is_dsd = true;
+
+ nfs1 = params_rate(params);
+ ak4497->fs1 = nfs1;
+
+ ak4497_read(component, AK4497_01_CONTROL2, &dfs);
+ dfs &= ~AK4497_DFS;
+
+ ak4497_read(component, AK4497_05_CONTROL4, &dfs2);
+ dfs2 &= ~AK4497_DFS2;
+
+ ak4497_read(component, AK4497_06_DSD1, &dsdsel0);
+ dsdsel0 &= ~AK4497_DSDSEL0;
+
+ ak4497_read(component, AK4497_09_DSD2, &dsdsel1);
+ dsdsel1 &= ~AK4497_DSDSEL1;
+
+ if (!is_dsd) {
+ switch (nfs1) {
+ case 8000:
+ case 11025:
+ case 16000:
+ case 22050:
+ case 32000:
+ case 44100:
+ case 48000:
+ dfs |= AK4497_DFS_48KHZ;
+ dfs2 |= AK4497_DFS2_48KHZ;
+ break;
+ case 88200:
+ case 96000:
+ dfs |= AK4497_DFS_96KHZ;
+ dfs2 |= AK4497_DFS2_48KHZ;
+ break;
+ case 176400:
+ case 192000:
+ dfs |= AK4497_DFS_192KHZ;
+ dfs2 |= AK4497_DFS2_48KHZ;
+ break;
+ case 352800:
+ case 384000:
+ dfs |= AK4497_DFS_384KHZ;
+ dfs2 |= AK4497_DFS2_384KHZ;
+ break;
+ case 705600:
+ case 768000:
+ dfs |= AK4497_DFS_768KHZ;
+ dfs2 |= AK4497_DFS2_384KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ dsd_bclk = params_rate(params) *
+ params_physical_width(params);
+
+ switch (dsd_bclk) {
+ case 2822400:
+ dsdsel0 |= AK4497_DSDSEL0_2MHZ;
+ dsdsel1 |= AK4497_DSDSEL1_2MHZ;
+ break;
+ case 5644800:
+ dsdsel0 |= AK4497_DSDSEL0_5MHZ;
+ dsdsel1 |= AK4497_DSDSEL1_5MHZ;
+ break;
+ case 11289600:
+ dsdsel0 |= AK4497_DSDSEL0_11MHZ;
+ dsdsel1 |= AK4497_DSDSEL1_11MHZ;
+ break;
+ case 22579200:
+ dsdsel0 |= AK4497_DSDSEL0_22MHZ;
+ dsdsel1 |= AK4497_DSDSEL1_22MHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_component_write(component, AK4497_06_DSD1, dsdsel0);
+ snd_soc_component_write(component, AK4497_09_DSD2, dsdsel1);
+ }
+
+ snd_soc_component_write(component, AK4497_01_CONTROL2, dfs);
+ snd_soc_component_write(component, AK4497_05_CONTROL4, dfs2);
+
+ ak4497_read(component, AK4497_00_CONTROL1, &format);
+ format &= ~AK4497_DIF;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (ak4497->fmt == SND_SOC_DAIFMT_I2S)
+ format |= AK4497_DIF_24BIT_I2S;
+ else
+ format |= AK4497_DIF_16BIT_LSB;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (ak4497->fmt == SND_SOC_DAIFMT_I2S)
+ format |= AK4497_DIF_32BIT_I2S;
+ else if (ak4497->fmt == SND_SOC_DAIFMT_LEFT_J)
+ format |= AK4497_DIF_32BIT_MSB;
+ else if (ak4497->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ format |= AK4497_DIF_32BIT_LSB;
+ else
+ return -EINVAL;
+ break;
+ case SNDRV_PCM_FORMAT_DSD_U8:
+ case SNDRV_PCM_FORMAT_DSD_U16_LE:
+ case SNDRV_PCM_FORMAT_DSD_U32_LE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_component_write(component, AK4497_00_CONTROL1, format);
+
+ return 0;
+}
+
+static int ak4497_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ return 0;
+}
+
+static int ak4497_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ unsigned int format, format2;
+
+ /* set master/slave audio interface */
+ ak4497_read(component, AK4497_00_CONTROL1, &format);
+ format &= ~AK4497_DIF;
+
+ ak4497_read(component, AK4497_02_CONTROL3, &format2);
+ format2 &= ~AK4497_DIF_DSD;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(component->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ak4497->fmt = SND_SOC_DAIFMT_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ak4497->fmt = SND_SOC_DAIFMT_LEFT_J;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ ak4497->fmt = SND_SOC_DAIFMT_RIGHT_J;
+ break;
+ case SND_SOC_DAIFMT_PDM:
+ format2 |= AK4497_DIF_DSD_MODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set format */
+ snd_soc_component_write(component, AK4497_00_CONTROL1, format);
+ snd_soc_component_write(component, AK4497_02_CONTROL3, format2);
+
+ return 0;
+}
+
+static bool ak4497_volatile(struct device *dev, unsigned int reg)
+{
+ int ret;
+
+#ifdef AK4497_DEBUG
+ ret = 1;
+#else
+ switch (reg) {
+ case AK4497_15_DFSREAD:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+#endif
+ return ret;
+}
+
+static int ak4497_set_bias_level(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ /* RSTN bit = 1 */
+ snd_soc_component_update_bits(component, AK4497_00_CONTROL1, 0x01, 0x01);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* RSTN bit = 0 */
+ snd_soc_component_update_bits(component, AK4497_00_CONTROL1, 0x01, 0x00);
+ break;
+ }
+
+ return 0;
+}
+
+#define AK4497_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\
+ SNDRV_PCM_RATE_192000)
+
+#define AK4497_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 unsigned int ak4497_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000, 1411200,
+ 2822400,
+};
+
+static const struct snd_pcm_hw_constraint_list ak4497_rate_constraints = {
+ .count = ARRAY_SIZE(ak4497_rates),
+ .list = ak4497_rates,
+};
+
+static int ak4497_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &ak4497_rate_constraints);
+
+ return ret;
+}
+
+static struct snd_soc_dai_ops ak4497_dai_ops = {
+ .startup = ak4497_startup,
+ .hw_params = ak4497_hw_params,
+ .set_sysclk = ak4497_set_dai_sysclk,
+ .set_fmt = ak4497_set_dai_fmt,
+};
+
+struct snd_soc_dai_driver rpmsg_ak4497_dai[] = {
+ {
+ .name = "rpmsg-ak4497-aif",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = AK4497_FORMATS,
+ },
+ .ops = &ak4497_dai_ops,
+ },
+};
+
+static int ak4497_init_reg(struct snd_soc_component *component)
+{
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ int ret = 0;
+
+ /* External Mute ON */
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_set_value_cansleep(ak4497->mute_gpio, 1);
+
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ /* ak4497_set_bias_level(codec, SND_SOC_BIAS_STANDBY); */
+
+ /* SYNCE bit = 1 */
+ ret = snd_soc_component_update_bits(component, AK4497_07_CONTROL5, 0x01, 0x01);
+ if (ret)
+ return ret;
+
+ /* HLOAD bit = 1, SC2 bit = 1 */
+ ret = snd_soc_component_update_bits(component, AK4497_08_SOUNDCONTROL, 0x0F, 0x0C);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int ak4497_parse_dt(struct rpmsg_ak4497_priv *ak4497)
+{
+ struct device *dev;
+ struct device_node *np;
+
+ dev = &(ak4497->pdev->dev);
+ np = dev->of_node;
+
+ ak4497->pdn_gpio = -1;
+ ak4497->mute_gpio = -1;
+
+ if (!np)
+ return 0;
+
+ ak4497->pdn_gpio = of_get_named_gpio(np, "ak4497,pdn-gpio", 0);
+ if (ak4497->pdn_gpio < 0)
+ ak4497->pdn_gpio = -1;
+
+ if (!gpio_is_valid(ak4497->pdn_gpio)) {
+ dev_err(dev, "ak4497 pdn pin(%u) is invalid\n",
+ ak4497->pdn_gpio);
+ ak4497->pdn_gpio = -1;
+ }
+
+ ak4497->mute_gpio = of_get_named_gpio(np, "ak4497,mute-gpio", 0);
+ if (ak4497->mute_gpio < 0)
+ ak4497->mute_gpio = -1;
+
+ if (!gpio_is_valid(ak4497->mute_gpio)) {
+ dev_err(dev, "ak4497 mute_gpio(%u) is invalid\n",
+ ak4497->mute_gpio);
+ ak4497->mute_gpio = -1;
+ }
+
+ return 0;
+}
+
+static int ak4497_probe(struct snd_soc_component *component)
+{
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ int ret = 0;
+
+ ret = ak4497_parse_dt(ak4497);
+ if (ret)
+ return ret;
+
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ ret = gpio_request(ak4497->pdn_gpio, "ak4497 pdn");
+ if (ret)
+ return ret;
+ gpio_direction_output(ak4497->pdn_gpio, 0);
+ }
+ if (gpio_is_valid(ak4497->mute_gpio)) {
+ ret = gpio_request(ak4497->mute_gpio, "ak4497 mute");
+ if (ret)
+ return ret;
+ gpio_direction_output(ak4497->mute_gpio, 0);
+ }
+
+ snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "AK4497 AOUT");
+ snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "Playback");
+
+ ret = ak4497_init_reg(component);
+ if (ret)
+ return ret;
+
+ ak4497->fs1 = 48000;
+ ak4497->nBickFreq = 1;
+ ak4497->nTdmSds = 0;
+
+ return ret;
+}
+
+static void ak4497_remove(struct snd_soc_component *component)
+{
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+
+ ak4497_set_bias_level(component, SND_SOC_BIAS_OFF);
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 0);
+ gpio_free(ak4497->pdn_gpio);
+ }
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_free(ak4497->mute_gpio);
+
+}
+
+#ifdef CONFIG_PM
+static int ak4497_runtime_suspend(struct device *dev)
+{
+ struct rpmsg_ak4497_priv *ak4497 = dev_get_drvdata(dev);
+
+ regcache_cache_only(ak4497->regmap, true);
+
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ }
+
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_free(ak4497->mute_gpio);
+
+ return 0;
+}
+
+static int ak4497_runtime_resume(struct device *dev)
+{
+ struct rpmsg_ak4497_priv *ak4497 = dev_get_drvdata(dev);
+
+ /* External Mute ON */
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_set_value_cansleep(ak4497->mute_gpio, 1);
+
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ regcache_cache_only(ak4497->regmap, false);
+ regcache_mark_dirty(ak4497->regmap);
+
+ return regcache_sync(ak4497->regmap);
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops ak4497_pm = {
+ SET_RUNTIME_PM_OPS(ak4497_runtime_suspend, ak4497_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+};
+
+struct snd_soc_component_driver rpmsg_codec_dev_ak4497 = {
+ .probe = ak4497_probe,
+ .remove = ak4497_remove,
+
+ .set_bias_level = ak4497_set_bias_level,
+ .controls = ak4497_snd_controls,
+ .num_controls = ARRAY_SIZE(ak4497_snd_controls),
+ .dapm_widgets = ak4497_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ak4497_dapm_widgets),
+ .dapm_routes = ak4497_intercon,
+ .num_dapm_routes = ARRAY_SIZE(ak4497_intercon),
+ .suspend_bias_off = 1,
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+ .non_legacy_dai_naming = 1,
+};
+
+static int rpmsg_ak4497_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct rpmsg_ak4497_priv *ak4497 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = ak4497->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg;
+ int err, reg_val;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = ak4497->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->header.cmd = GET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info);
+ reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data;
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ *val = reg_val;
+ return 0;
+}
+
+static int rpmsg_ak4497_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct rpmsg_ak4497_priv *ak4497 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = ak4497->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg;
+ int err;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = ak4497->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->param.buffer_size = val;
+ rpmsg->header.cmd = SET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info);
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ return 0;
+}
+
+static const struct regmap_config rpmsg_ak4497_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = AK4497_MAX_REGISTERS,
+ .volatile_reg = ak4497_volatile,
+
+ .reg_defaults = ak4497_reg,
+ .num_reg_defaults = ARRAY_SIZE(ak4497_reg),
+ .cache_type = REGCACHE_RBTREE,
+
+ .reg_read = rpmsg_ak4497_read,
+ .reg_write = rpmsg_ak4497_write,
+};
+
+static int rpmsg_ak4497_codec_probe(struct platform_device *pdev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent);
+ struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data;
+ struct rpmsg_ak4497_priv *ak4497;
+ int ret = 0;
+ int i;
+
+ ak4497 = devm_kzalloc(&pdev->dev,
+ sizeof(struct rpmsg_ak4497_priv), GFP_KERNEL);
+ if (ak4497 == NULL)
+ return -ENOMEM;
+
+ ak4497->rpmsg_i2s = rpmsg_i2s;
+ ak4497->pdev = pdev;
+
+ ak4497->regmap = devm_regmap_init(&pdev->dev, NULL, ak4497, &rpmsg_ak4497_regmap);
+ if (IS_ERR(ak4497->regmap))
+ return PTR_ERR(ak4497->regmap);
+
+ if (pdata)
+ ak4497->audioindex = pdata->audioindex;
+
+ dev_set_drvdata(&pdev->dev, ak4497);
+
+ for (i = 0; i < ARRAY_SIZE(ak4497->supplies); i++)
+ ak4497->supplies[i].supply = ak4497_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&pdev->dev, ARRAY_SIZE(ak4497->supplies),
+ ak4497->supplies);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak4497->supplies),
+ ak4497->supplies);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &rpmsg_codec_dev_ak4497,
+ &rpmsg_ak4497_dai[0], ARRAY_SIZE(rpmsg_ak4497_dai));
+ if (ret < 0)
+ return ret;
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static int rpmsg_ak4497_codec_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver rpmsg_ak4497_codec_driver = {
+ .driver = {
+ .name = RPMSG_CODEC_DRV_NAME_AK4497,
+ .pm = &ak4497_pm,
+ },
+ .probe = rpmsg_ak4497_codec_probe,
+ .remove = rpmsg_ak4497_codec_remove,
+};
+
+module_platform_driver(rpmsg_ak4497_codec_driver);
+
+MODULE_AUTHOR("Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>");
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
+MODULE_DESCRIPTION("ASoC ak4497 codec driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/rpmsg_ak4497.h b/sound/soc/codecs/rpmsg_ak4497.h
new file mode 100644
index 000000000000..33644088cc94
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_ak4497.h
@@ -0,0 +1,90 @@
+/*
+ * ak4497.h -- audio driver for ak4497
+ *
+ * Copyright (C) 2016 Asahi Kasei Microdevices Corporation
+ * Copyright (C) 2017, NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+#ifndef _RPMSG_AK4497_H
+#define _RPMSG_AK4497_H
+
+#define AK4497_00_CONTROL1 0x00
+#define AK4497_01_CONTROL2 0x01
+#define AK4497_02_CONTROL3 0x02
+#define AK4497_03_LCHATT 0x03
+#define AK4497_04_RCHATT 0x04
+#define AK4497_05_CONTROL4 0x05
+#define AK4497_06_DSD1 0x06
+#define AK4497_07_CONTROL5 0x07
+#define AK4497_08_SOUNDCONTROL 0x08
+#define AK4497_09_DSD2 0x09
+#define AK4497_0A_CONTROL7 0x0A
+#define AK4497_0B_CONTROL8 0x0B
+#define AK4497_0C_RESERVED 0x0C
+#define AK4497_0D_RESERVED 0x0D
+#define AK4497_0E_RESERVED 0x0E
+#define AK4497_0F_RESERVED 0x0F
+#define AK4497_10_RESERVED 0x10
+#define AK4497_11_RESERVED 0x11
+#define AK4497_12_RESERVED 0x12
+#define AK4497_13_RESERVED 0x13
+#define AK4497_14_RESERVED 0x14
+#define AK4497_15_DFSREAD 0x15
+
+
+#define AK4497_MAX_REGISTERS (AK4497_15_DFSREAD)
+
+/* Bitfield Definitions */
+
+/* AK4497_00_CONTROL1 (0x00) Fields */
+#define AK4497_DIF 0x0E
+#define AK4497_DIF_MSB_MODE (2 << 1)
+#define AK4497_DIF_I2S_MODE (3 << 1)
+#define AK4497_DIF_32BIT_MODE (4 << 1)
+
+#define AK4497_DIF_16BIT_LSB (0 << 1)
+#define AK4497_DIF_20BIT_LSB (1 << 1)
+#define AK4497_DIF_24BIT_MSB (2 << 1)
+#define AK4497_DIF_24BIT_I2S (3 << 1)
+#define AK4497_DIF_24BIT_LSB (4 << 1)
+#define AK4497_DIF_32BIT_LSB (5 << 1)
+#define AK4497_DIF_32BIT_MSB (6 << 1)
+#define AK4497_DIF_32BIT_I2S (7 << 1)
+
+/* AK4497_02_CONTROL3 (0x02) Fields */
+#define AK4497_DIF_DSD 0x80
+#define AK4497_DIF_DSD_MODE (1 << 7)
+
+
+/* AK4497_01_CONTROL2 (0x01) Fields */
+/* AK4497_05_CONTROL4 (0x05) Fields */
+#define AK4497_DFS 0x18
+#define AK4497_DFS_48KHZ (0x0 << 3) // 30kHz to 54kHz
+#define AK4497_DFS_96KHZ (0x1 << 3) // 54kHz to 108kHz
+#define AK4497_DFS_192KHZ (0x2 << 3) // 120kHz to 216kHz
+#define AK4497_DFS_384KHZ (0x0 << 3)
+#define AK4497_DFS_768KHZ (0x1 << 3)
+
+#define AK4497_DFS2 0x2
+#define AK4497_DFS2_48KHZ (0x0 << 1) // 30kHz to 216kHz
+#define AK4497_DFS2_384KHZ (0x1 << 1) // 384kHz, 768kHz to 108kHz
+
+
+#define AK4497_DSDSEL0 0x1
+#define AK4497_DSDSEL0_2MHZ 0x0
+#define AK4497_DSDSEL0_5MHZ 0x1
+#define AK4497_DSDSEL0_11MHZ 0x0
+#define AK4497_DSDSEL0_22MHZ 0x1
+
+#define AK4497_DSDSEL1 0x1
+#define AK4497_DSDSEL1_2MHZ 0x0
+#define AK4497_DSDSEL1_5MHZ 0x0
+#define AK4497_DSDSEL1_11MHZ 0x1
+#define AK4497_DSDSEL1_22MHZ 0x1
+
+#endif
diff --git a/sound/soc/codecs/rpmsg_cs42xx8.c b/sound/soc/codecs/rpmsg_cs42xx8.c
new file mode 100644
index 000000000000..02bdfb0cfd49
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_cs42xx8.c
@@ -0,0 +1,752 @@
+/*
+ * Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver
+ *
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <Guangyu.Chen@freescale.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "rpmsg_cs42xx8.h"
+#include "../fsl/fsl_rpmsg_i2s.h"
+
+#define CS42XX8_NUM_SUPPLIES 4
+static const char *const cs42xx8_supply_names[CS42XX8_NUM_SUPPLIES] = {
+ "VA",
+ "VD",
+ "VLS",
+ "VLC",
+};
+
+#define CS42XX8_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+/* codec private data */
+struct rpmsg_cs42xx8_priv {
+ struct regulator_bulk_data supplies[CS42XX8_NUM_SUPPLIES];
+ struct cs42xx8_driver_data *drvdata;
+ struct regmap *regmap;
+ struct clk *clk;
+
+ bool slave_mode[2];
+ unsigned long sysclk;
+ u32 tx_channels;
+ int rate[2];
+ int reset_gpio;
+ int audioindex;
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+};
+
+/* -127.5dB to 0dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+/* -64dB to 24dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 0);
+
+static const char *const cs42xx8_adc_single[] = { "Differential", "Single-Ended" };
+static const char *const cs42xx8_szc[] = { "Immediate Change", "Zero Cross",
+ "Soft Ramp", "Soft Ramp on Zero Cross" };
+
+static const struct soc_enum adc1_single_enum =
+ SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 4, 2, cs42xx8_adc_single);
+static const struct soc_enum adc2_single_enum =
+ SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 3, 2, cs42xx8_adc_single);
+static const struct soc_enum adc3_single_enum =
+ SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 2, 2, cs42xx8_adc_single);
+static const struct soc_enum dac_szc_enum =
+ SOC_ENUM_SINGLE(CS42XX8_TXCTL, 5, 4, cs42xx8_szc);
+static const struct soc_enum adc_szc_enum =
+ SOC_ENUM_SINGLE(CS42XX8_TXCTL, 0, 4, cs42xx8_szc);
+
+static const struct snd_kcontrol_new cs42xx8_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("DAC1 Playback Volume", CS42XX8_VOLAOUT1,
+ CS42XX8_VOLAOUT2, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_TLV("DAC2 Playback Volume", CS42XX8_VOLAOUT3,
+ CS42XX8_VOLAOUT4, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_TLV("DAC3 Playback Volume", CS42XX8_VOLAOUT5,
+ CS42XX8_VOLAOUT6, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_TLV("DAC4 Playback Volume", CS42XX8_VOLAOUT7,
+ CS42XX8_VOLAOUT8, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_S_TLV("ADC1 Capture Volume", CS42XX8_VOLAIN1,
+ CS42XX8_VOLAIN2, 0, -0x80, 0x30, 7, 0, adc_tlv),
+ SOC_DOUBLE_R_S_TLV("ADC2 Capture Volume", CS42XX8_VOLAIN3,
+ CS42XX8_VOLAIN4, 0, -0x80, 0x30, 7, 0, adc_tlv),
+ SOC_DOUBLE("DAC1 Invert Switch", CS42XX8_DACINV, 0, 1, 1, 0),
+ SOC_DOUBLE("DAC2 Invert Switch", CS42XX8_DACINV, 2, 3, 1, 0),
+ SOC_DOUBLE("DAC3 Invert Switch", CS42XX8_DACINV, 4, 5, 1, 0),
+ SOC_DOUBLE("DAC4 Invert Switch", CS42XX8_DACINV, 6, 7, 1, 0),
+ SOC_DOUBLE("ADC1 Invert Switch", CS42XX8_ADCINV, 0, 1, 1, 0),
+ SOC_DOUBLE("ADC2 Invert Switch", CS42XX8_ADCINV, 2, 3, 1, 0),
+ SOC_SINGLE("ADC High-Pass Filter Switch", CS42XX8_ADCCTL, 7, 1, 1),
+ SOC_SINGLE("DAC De-emphasis Switch", CS42XX8_ADCCTL, 5, 1, 0),
+ SOC_ENUM("ADC1 Single Ended Mode Switch", adc1_single_enum),
+ SOC_ENUM("ADC2 Single Ended Mode Switch", adc2_single_enum),
+ SOC_SINGLE("DAC Single Volume Control Switch", CS42XX8_TXCTL, 7, 1, 0),
+ SOC_ENUM("DAC Soft Ramp & Zero Cross Control Switch", dac_szc_enum),
+ SOC_SINGLE("DAC Auto Mute Switch", CS42XX8_TXCTL, 4, 1, 0),
+ SOC_SINGLE("Mute ADC Serial Port Switch", CS42XX8_TXCTL, 3, 1, 0),
+ SOC_SINGLE("ADC Single Volume Control Switch", CS42XX8_TXCTL, 2, 1, 0),
+ SOC_ENUM("ADC Soft Ramp & Zero Cross Control Switch", adc_szc_enum),
+};
+
+static const struct snd_kcontrol_new cs42xx8_adc3_snd_controls[] = {
+ SOC_DOUBLE_R_S_TLV("ADC3 Capture Volume", CS42XX8_VOLAIN5,
+ CS42XX8_VOLAIN6, 0, -0x80, 0x30, 7, 0, adc_tlv),
+ SOC_DOUBLE("ADC3 Invert Switch", CS42XX8_ADCINV, 4, 5, 1, 0),
+ SOC_ENUM("ADC3 Single Ended Mode Switch", adc3_single_enum),
+};
+
+static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC1", "Playback", CS42XX8_PWRCTL, 1, 1),
+ SND_SOC_DAPM_DAC("DAC2", "Playback", CS42XX8_PWRCTL, 2, 1),
+ SND_SOC_DAPM_DAC("DAC3", "Playback", CS42XX8_PWRCTL, 3, 1),
+ SND_SOC_DAPM_DAC("DAC4", "Playback", CS42XX8_PWRCTL, 4, 1),
+
+ SND_SOC_DAPM_OUTPUT("AOUT1L"),
+ SND_SOC_DAPM_OUTPUT("AOUT1R"),
+ SND_SOC_DAPM_OUTPUT("AOUT2L"),
+ SND_SOC_DAPM_OUTPUT("AOUT2R"),
+ SND_SOC_DAPM_OUTPUT("AOUT3L"),
+ SND_SOC_DAPM_OUTPUT("AOUT3R"),
+ SND_SOC_DAPM_OUTPUT("AOUT4L"),
+ SND_SOC_DAPM_OUTPUT("AOUT4R"),
+
+ SND_SOC_DAPM_ADC("ADC1", "Capture", CS42XX8_PWRCTL, 5, 1),
+ SND_SOC_DAPM_ADC("ADC2", "Capture", CS42XX8_PWRCTL, 6, 1),
+
+ SND_SOC_DAPM_INPUT("AIN1L"),
+ SND_SOC_DAPM_INPUT("AIN1R"),
+ SND_SOC_DAPM_INPUT("AIN2L"),
+ SND_SOC_DAPM_INPUT("AIN2R"),
+
+};
+
+static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = {
+ SND_SOC_DAPM_ADC("ADC3", "Capture", CS42XX8_PWRCTL, 7, 1),
+
+ SND_SOC_DAPM_INPUT("AIN3L"),
+ SND_SOC_DAPM_INPUT("AIN3R"),
+};
+
+static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = {
+ /* Playback */
+ { "AOUT1L", NULL, "DAC1" },
+ { "AOUT1R", NULL, "DAC1" },
+
+ { "AOUT2L", NULL, "DAC2" },
+ { "AOUT2R", NULL, "DAC2" },
+
+ { "AOUT3L", NULL, "DAC3" },
+ { "AOUT3R", NULL, "DAC3" },
+
+ { "AOUT4L", NULL, "DAC4" },
+ { "AOUT4R", NULL, "DAC4" },
+
+ /* Capture */
+ { "ADC1", NULL, "AIN1L" },
+ { "ADC1", NULL, "AIN1R" },
+
+ { "ADC2", NULL, "AIN2L" },
+ { "ADC2", NULL, "AIN2R" },
+};
+
+static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = {
+ /* Capture */
+ { "ADC3", NULL, "AIN3L" },
+ { "ADC3", NULL, "AIN3R" },
+};
+
+struct cs42xx8_ratios {
+ unsigned int mfreq;
+ unsigned int min_mclk;
+ unsigned int max_mclk;
+ unsigned int ratio[3];
+};
+
+static const struct cs42xx8_ratios cs42xx8_ratios[] = {
+ { 0, 1029000, 12800000, {256, 128, 64} },
+ { 2, 1536000, 19200000, {384, 192, 96} },
+ { 4, 2048000, 25600000, {512, 256, 128} },
+ { 6, 3072000, 38400000, {768, 384, 192} },
+ { 8, 4096000, 51200000, {1024, 512, 256} },
+};
+
+static int cs42xx8_set_dai_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+
+ cs42xx8->sysclk = freq;
+
+ return 0;
+}
+
+static int cs42xx8_set_dai_fmt(struct snd_soc_dai *dai,
+ unsigned int format)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ u32 val;
+
+ /* Set DAI format */
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ val = CS42XX8_INTF_DAC_DIF_LEFTJ | CS42XX8_INTF_ADC_DIF_LEFTJ;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val = CS42XX8_INTF_DAC_DIF_I2S | CS42XX8_INTF_ADC_DIF_I2S;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val = CS42XX8_INTF_DAC_DIF_RIGHTJ | CS42XX8_INTF_ADC_DIF_RIGHTJ;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM;
+ break;
+ default:
+ dev_err(component->dev, "unsupported dai format\n");
+ return -EINVAL;
+ }
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_INTF,
+ CS42XX8_INTF_DAC_DIF_MASK |
+ CS42XX8_INTF_ADC_DIF_MASK, val);
+
+ if (cs42xx8->slave_mode[0] == cs42xx8->slave_mode[1]) {
+ /* Set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs42xx8->slave_mode[0] = true;
+ cs42xx8->slave_mode[1] = true;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs42xx8->slave_mode[0] = false;
+ cs42xx8->slave_mode[1] = false;
+ break;
+ default:
+ dev_err(component->dev, "unsupported master/slave mode\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int cs42xx8_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 rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 ratio[2];
+ u32 rate[2];
+ u32 fm[2];
+ u32 i, val, mask;
+ bool condition1, condition2;
+
+ if (tx)
+ cs42xx8->tx_channels = params_channels(params);
+
+ rate[tx] = params_rate(params);
+ rate[!tx] = cs42xx8->rate[!tx];
+
+ ratio[tx] = rate[tx] > 0 ? cs42xx8->sysclk / rate[tx] : 0;
+ ratio[!tx] = rate[!tx] > 0 ? cs42xx8->sysclk / rate[!tx] : 0;
+
+ for (i = 0; i < 2; i++) {
+ if (cs42xx8->slave_mode[i]) {
+ fm[i] = CS42XX8_FM_AUTO;
+ } else {
+ if (rate[i] < 50000)
+ fm[i] = CS42XX8_FM_SINGLE;
+ else if (rate[i] > 50000 && rate[i] < 100000)
+ fm[i] = CS42XX8_FM_DOUBLE;
+ else if (rate[i] > 100000 && rate[i] < 200000)
+ fm[i] = CS42XX8_FM_QUAD;
+ else {
+ dev_err(component->dev,
+ "unsupported sample rate or rate combine\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) {
+ condition1 = ((fm[tx] == CS42XX8_FM_AUTO) ?
+ (cs42xx8_ratios[i].ratio[0] == ratio[tx] ||
+ cs42xx8_ratios[i].ratio[1] == ratio[tx] ||
+ cs42xx8_ratios[i].ratio[2] == ratio[tx]) :
+ (cs42xx8_ratios[i].ratio[fm[tx]] == ratio[tx])) &&
+ cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk &&
+ cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk;
+
+ if (ratio[tx] <= 0)
+ condition1 = true;
+
+ condition2 = ((fm[!tx] == CS42XX8_FM_AUTO) ?
+ (cs42xx8_ratios[i].ratio[0] == ratio[!tx] ||
+ cs42xx8_ratios[i].ratio[1] == ratio[!tx] ||
+ cs42xx8_ratios[i].ratio[2] == ratio[!tx]) :
+ (cs42xx8_ratios[i].ratio[fm[!tx]] == ratio[!tx]));
+
+ if (ratio[!tx] <= 0)
+ condition2 = true;
+
+ if (condition1 && condition2)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(cs42xx8_ratios)) {
+ dev_err(component->dev, "unsupported sysclk ratio\n");
+ return -EINVAL;
+ }
+
+ cs42xx8->rate[tx] = params_rate(params);
+
+ mask = CS42XX8_FUNCMOD_MFREQ_MASK;
+ val = cs42xx8_ratios[i].mfreq;
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
+ CS42XX8_FUNCMOD_xC_FM_MASK(tx) | mask,
+ CS42XX8_FUNCMOD_xC_FM(tx, fm[tx]) | val);
+
+ return 0;
+}
+
+static int cs42xx8_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ cs42xx8->rate[tx] = 0;
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
+ CS42XX8_FUNCMOD_xC_FM_MASK(tx),
+ CS42XX8_FUNCMOD_xC_FM(tx, CS42XX8_FM_AUTO));
+ return 0;
+}
+
+static int cs42xx8_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ u8 dac_unmute = cs42xx8->tx_channels ?
+ ~((0x1 << cs42xx8->tx_channels) - 1) : 0;
+
+ regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE,
+ mute ? CS42XX8_DACMUTE_ALL : dac_unmute);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops cs42xx8_dai_ops = {
+ .set_fmt = cs42xx8_set_dai_fmt,
+ .set_sysclk = cs42xx8_set_dai_sysclk,
+ .hw_params = cs42xx8_hw_params,
+ .hw_free = cs42xx8_hw_free,
+ .digital_mute = cs42xx8_digital_mute,
+};
+
+static struct snd_soc_dai_driver cs42xx8_dai = {
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = CS42XX8_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = CS42XX8_FORMATS,
+ },
+ .ops = &cs42xx8_dai_ops,
+};
+
+static const struct reg_default cs42xx8_reg[] = {
+ { 0x02, 0x00 }, /* Power Control */
+ { 0x03, 0xF0 }, /* Functional Mode */
+ { 0x04, 0x46 }, /* Interface Formats */
+ { 0x05, 0x00 }, /* ADC Control & DAC De-Emphasis */
+ { 0x06, 0x10 }, /* Transition Control */
+ { 0x07, 0x00 }, /* DAC Channel Mute */
+ { 0x08, 0x00 }, /* Volume Control AOUT1 */
+ { 0x09, 0x00 }, /* Volume Control AOUT2 */
+ { 0x0a, 0x00 }, /* Volume Control AOUT3 */
+ { 0x0b, 0x00 }, /* Volume Control AOUT4 */
+ { 0x0c, 0x00 }, /* Volume Control AOUT5 */
+ { 0x0d, 0x00 }, /* Volume Control AOUT6 */
+ { 0x0e, 0x00 }, /* Volume Control AOUT7 */
+ { 0x0f, 0x00 }, /* Volume Control AOUT8 */
+ { 0x10, 0x00 }, /* DAC Channel Invert */
+ { 0x11, 0x00 }, /* Volume Control AIN1 */
+ { 0x12, 0x00 }, /* Volume Control AIN2 */
+ { 0x13, 0x00 }, /* Volume Control AIN3 */
+ { 0x14, 0x00 }, /* Volume Control AIN4 */
+ { 0x15, 0x00 }, /* Volume Control AIN5 */
+ { 0x16, 0x00 }, /* Volume Control AIN6 */
+ { 0x17, 0x00 }, /* ADC Channel Invert */
+ { 0x18, 0x00 }, /* Status Control */
+ { 0x1a, 0x00 }, /* Status Mask */
+ { 0x1b, 0x00 }, /* MUTEC Pin Control */
+};
+
+static bool cs42xx8_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS42XX8_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cs42xx8_writeable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS42XX8_CHIPID:
+ case CS42XX8_STATUS:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static int rpmsg_cs42xx8_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg;
+ int err, reg_val;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = cs42xx8->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->header.cmd = GET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info);
+ reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data;
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ *val = reg_val;
+ return 0;
+}
+
+static int rpmsg_cs42xx8_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg;
+ int err;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = cs42xx8->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->param.buffer_size = val;
+ rpmsg->header.cmd = SET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info);
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ return 0;
+}
+
+static struct regmap_config rpmsg_cs42xx8_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = CS42XX8_LASTREG,
+ .reg_defaults = cs42xx8_reg,
+ .num_reg_defaults = ARRAY_SIZE(cs42xx8_reg),
+ .volatile_reg = cs42xx8_volatile_register,
+ .writeable_reg = cs42xx8_writeable_register,
+ .cache_type = REGCACHE_RBTREE,
+
+ .reg_read = rpmsg_cs42xx8_read,
+ .reg_write = rpmsg_cs42xx8_write,
+};
+
+static int cs42xx8_codec_probe(struct snd_soc_component *component)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+
+ switch (cs42xx8->drvdata->num_adcs) {
+ case 3:
+ snd_soc_add_component_controls(component, cs42xx8_adc3_snd_controls,
+ ARRAY_SIZE(cs42xx8_adc3_snd_controls));
+ snd_soc_dapm_new_controls(dapm, cs42xx8_adc3_dapm_widgets,
+ ARRAY_SIZE(cs42xx8_adc3_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, cs42xx8_adc3_dapm_routes,
+ ARRAY_SIZE(cs42xx8_adc3_dapm_routes));
+ break;
+ default:
+ break;
+ }
+
+ /* Mute all DAC channels */
+ regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL);
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 0);
+ return 0;
+}
+
+static const struct snd_soc_component_driver cs42xx8_driver = {
+ .probe = cs42xx8_codec_probe,
+ .controls = cs42xx8_snd_controls,
+ .num_controls = ARRAY_SIZE(cs42xx8_snd_controls),
+ .dapm_widgets = cs42xx8_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cs42xx8_dapm_widgets),
+ .dapm_routes = cs42xx8_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(cs42xx8_dapm_routes),
+ .suspend_bias_off = 1,
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+ .non_legacy_dai_naming = 1,
+};
+
+static int rpmsg_cs42xx8_codec_probe(struct platform_device *pdev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent);
+ struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data;
+ struct rpmsg_cs42xx8_priv *cs42xx8;
+ struct device *dev = &pdev->dev;
+ int ret, val, i;
+
+ cs42xx8 = devm_kzalloc(&pdev->dev, sizeof(*cs42xx8), GFP_KERNEL);
+ if (cs42xx8 == NULL)
+ return -ENOMEM;
+
+ cs42xx8->regmap = devm_regmap_init(&pdev->dev, NULL,
+ cs42xx8, &rpmsg_cs42xx8_regmap_config);
+ if (IS_ERR(cs42xx8->regmap))
+ return PTR_ERR(cs42xx8->regmap);
+
+ dev_set_drvdata(&pdev->dev, cs42xx8);
+
+ cs42xx8->drvdata = devm_kzalloc(&pdev->dev,
+ sizeof(struct cs42xx8_driver_data), GFP_KERNEL);
+ if (!cs42xx8->drvdata)
+ return -ENOMEM;
+
+ cs42xx8->rpmsg_i2s = rpmsg_i2s;
+
+ memcpy(cs42xx8->drvdata->name, pdata->name, 32);
+ cs42xx8->drvdata->num_adcs = pdata->num_adcs;
+ cs42xx8->audioindex = pdata->audioindex;
+ cs42xx8->reset_gpio = of_get_named_gpio(pdev->dev.parent->of_node,
+ "reset-gpio", 0);
+ if (gpio_is_valid(cs42xx8->reset_gpio)) {
+ ret = devm_gpio_request_one(dev, cs42xx8->reset_gpio,
+ GPIOF_OUT_INIT_LOW, "cs42xx8 reset");
+ if (ret) {
+ dev_err(dev, "unable to get reset gpio\n");
+ return ret;
+ }
+ gpio_set_value_cansleep(cs42xx8->reset_gpio, 1);
+ }
+
+ cs42xx8->clk = devm_clk_get(pdev->dev.parent, "mclk");
+ if (IS_ERR(cs42xx8->clk)) {
+ dev_err(dev, "failed to get the clock: %ld\n",
+ PTR_ERR(cs42xx8->clk));
+ return -EINVAL;
+ }
+
+ cs42xx8->sysclk = clk_get_rate(cs42xx8->clk);
+
+ if (of_property_read_bool(pdev->dev.parent->of_node, "fsl,txm-rxs")) {
+ /* 0 -- rx, 1 -- tx */
+ cs42xx8->slave_mode[0] = true;
+ cs42xx8->slave_mode[1] = false;
+ }
+
+ if (of_property_read_bool(pdev->dev.parent->of_node, "fsl,txs-rxm")) {
+ /* 0 -- rx, 1 -- tx */
+ cs42xx8->slave_mode[0] = false;
+ cs42xx8->slave_mode[1] = true;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cs42xx8->supplies); i++)
+ cs42xx8->supplies[i].supply = cs42xx8_supply_names[i];
+
+ ret = devm_regulator_bulk_get(pdev->dev.parent,
+ ARRAY_SIZE(cs42xx8->supplies), cs42xx8->supplies);
+ if (ret) {
+ dev_err(dev, "failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+ if (ret) {
+ dev_err(dev, "failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ /* Make sure hardware reset done */
+ usleep_range(5000, 10000);
+
+ /*
+ * We haven't marked the chip revision as volatile due to
+ * sharing a register with the right input volume; explicitly
+ * bypass the cache to read it.
+ */
+ regcache_cache_bypass(cs42xx8->regmap, true);
+
+ /* Validate the chip ID */
+ ret = regmap_read(cs42xx8->regmap, CS42XX8_CHIPID, &val);
+ if (ret < 0) {
+ dev_err(dev, "failed to get device ID, ret = %d", ret);
+ goto err_enable;
+ }
+
+ /* The top four bits of the chip ID should be 0000 */
+ if (((val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4) != 0x00) {
+ dev_err(dev, "unmatched chip ID: %d\n",
+ (val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ dev_info(dev, "found device, revision %X\n",
+ val & CS42XX8_CHIPID_REV_ID_MASK);
+
+ regcache_cache_bypass(cs42xx8->regmap, false);
+
+ cs42xx8_dai.name = cs42xx8->drvdata->name;
+
+ /* Each adc supports stereo input */
+ cs42xx8_dai.capture.channels_max = cs42xx8->drvdata->num_adcs * 2;
+
+ pm_runtime_enable(dev);
+ pm_request_idle(dev);
+
+ ret = devm_snd_soc_register_component(dev, &cs42xx8_driver, &cs42xx8_dai, 1);
+ if (ret) {
+ dev_err(dev, "failed to register codec:%d\n", ret);
+ goto err_enable;
+ }
+
+ regcache_cache_only(cs42xx8->regmap, true);
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int cs42xx8_runtime_resume(struct device *dev)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(cs42xx8->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable mclk: %d\n", ret);
+ return ret;
+ }
+
+ if (gpio_is_valid(cs42xx8->reset_gpio)) {
+ gpio_set_value_cansleep(cs42xx8->reset_gpio, 0);
+ gpio_set_value_cansleep(cs42xx8->reset_gpio, 1);
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+ if (ret) {
+ dev_err(dev, "failed to enable supplies: %d\n", ret);
+ goto err_clk;
+ }
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 1);
+ /* Make sure hardware reset done */
+ usleep_range(5000, 10000);
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 0);
+
+ regcache_cache_only(cs42xx8->regmap, false);
+
+ ret = regcache_sync(cs42xx8->regmap);
+ if (ret) {
+ dev_err(dev, "failed to sync regmap: %d\n", ret);
+ goto err_bulk;
+ }
+
+ return 0;
+
+err_bulk:
+ regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+err_clk:
+ clk_disable_unprepare(cs42xx8->clk);
+
+ return ret;
+}
+
+static int cs42xx8_runtime_suspend(struct device *dev)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev);
+
+ regcache_cache_only(cs42xx8->regmap, true);
+
+ regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+
+ clk_disable_unprepare(cs42xx8->clk);
+
+ return 0;
+}
+#endif
+
+const struct dev_pm_ops rpmsg_cs42xx8_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(cs42xx8_runtime_suspend, cs42xx8_runtime_resume, NULL)
+};
+
+static int rpmsg_cs42xx8_codec_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver rpmsg_cs42xx8_codec_driver = {
+ .driver = {
+ .name = RPMSG_CODEC_DRV_NAME_CS42888,
+ .pm = &rpmsg_cs42xx8_pm,
+ },
+ .probe = rpmsg_cs42xx8_codec_probe,
+ .remove = rpmsg_cs42xx8_codec_remove,
+};
+
+module_platform_driver(rpmsg_cs42xx8_codec_driver);
+
+MODULE_DESCRIPTION("Cirrus Logic CS42448/CS42888 ALSA SoC Codec Driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/rpmsg_cs42xx8.h b/sound/soc/codecs/rpmsg_cs42xx8.h
new file mode 100644
index 000000000000..682295272f49
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_cs42xx8.h
@@ -0,0 +1,232 @@
+/*
+ * cs42xx8.h - Cirrus Logic CS42448/CS42888 Audio CODEC driver header file
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <Guangyu.Chen@freescale.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef _RPMSG_CS42XX8_H
+#define _RPMSG_CS42XX8_H
+
+struct cs42xx8_driver_data {
+ char name[32];
+ int num_adcs;
+};
+
+/* CS42888 register map */
+#define CS42XX8_CHIPID 0x01 /* Chip ID */
+#define CS42XX8_PWRCTL 0x02 /* Power Control */
+#define CS42XX8_FUNCMOD 0x03 /* Functional Mode */
+#define CS42XX8_INTF 0x04 /* Interface Formats */
+#define CS42XX8_ADCCTL 0x05 /* ADC Control */
+#define CS42XX8_TXCTL 0x06 /* Transition Control */
+#define CS42XX8_DACMUTE 0x07 /* DAC Mute Control */
+#define CS42XX8_VOLAOUT1 0x08 /* Volume Control AOUT1 */
+#define CS42XX8_VOLAOUT2 0x09 /* Volume Control AOUT2 */
+#define CS42XX8_VOLAOUT3 0x0A /* Volume Control AOUT3 */
+#define CS42XX8_VOLAOUT4 0x0B /* Volume Control AOUT4 */
+#define CS42XX8_VOLAOUT5 0x0C /* Volume Control AOUT5 */
+#define CS42XX8_VOLAOUT6 0x0D /* Volume Control AOUT6 */
+#define CS42XX8_VOLAOUT7 0x0E /* Volume Control AOUT7 */
+#define CS42XX8_VOLAOUT8 0x0F /* Volume Control AOUT8 */
+#define CS42XX8_DACINV 0x10 /* DAC Channel Invert */
+#define CS42XX8_VOLAIN1 0x11 /* Volume Control AIN1 */
+#define CS42XX8_VOLAIN2 0x12 /* Volume Control AIN2 */
+#define CS42XX8_VOLAIN3 0x13 /* Volume Control AIN3 */
+#define CS42XX8_VOLAIN4 0x14 /* Volume Control AIN4 */
+#define CS42XX8_VOLAIN5 0x15 /* Volume Control AIN5 */
+#define CS42XX8_VOLAIN6 0x16 /* Volume Control AIN6 */
+#define CS42XX8_ADCINV 0x17 /* ADC Channel Invert */
+#define CS42XX8_STATUSCTL 0x18 /* Status Control */
+#define CS42XX8_STATUS 0x19 /* Status */
+#define CS42XX8_STATUSM 0x1A /* Status Mask */
+#define CS42XX8_MUTEC 0x1B /* MUTEC Pin Control */
+
+#define CS42XX8_FIRSTREG CS42XX8_CHIPID
+#define CS42XX8_LASTREG CS42XX8_MUTEC
+#define CS42XX8_NUMREGS (CS42XX8_LASTREG - CS42XX8_FIRSTREG + 1)
+#define CS42XX8_I2C_INCR 0x80
+
+/* Chip I.D. and Revision Register (Address 01h) */
+#define CS42XX8_CHIPID_CHIP_ID_MASK 0xF0
+#define CS42XX8_CHIPID_REV_ID_MASK 0x0F
+
+/* Power Control (Address 02h) */
+#define CS42XX8_PWRCTL_PDN_ADC3_SHIFT 7
+#define CS42XX8_PWRCTL_PDN_ADC3_MASK (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC3 (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC2_SHIFT 6
+#define CS42XX8_PWRCTL_PDN_ADC2_MASK (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC2 (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC1_SHIFT 5
+#define CS42XX8_PWRCTL_PDN_ADC1_MASK (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC1 (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC4_SHIFT 4
+#define CS42XX8_PWRCTL_PDN_DAC4_MASK (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC4 (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC3_SHIFT 3
+#define CS42XX8_PWRCTL_PDN_DAC3_MASK (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC3 (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC2_SHIFT 2
+#define CS42XX8_PWRCTL_PDN_DAC2_MASK (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC2 (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC1_SHIFT 1
+#define CS42XX8_PWRCTL_PDN_DAC1_MASK (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC1 (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT)
+#define CS42XX8_PWRCTL_PDN_SHIFT 0
+#define CS42XX8_PWRCTL_PDN_MASK (1 << CS42XX8_PWRCTL_PDN_SHIFT)
+#define CS42XX8_PWRCTL_PDN (1 << CS42XX8_PWRCTL_PDN_SHIFT)
+
+/* Functional Mode (Address 03h) */
+#define CS42XX8_FUNCMOD_DAC_FM_SHIFT 6
+#define CS42XX8_FUNCMOD_DAC_FM_WIDTH 2
+#define CS42XX8_FUNCMOD_DAC_FM_MASK (((1 << CS42XX8_FUNCMOD_DAC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_DAC_FM_SHIFT)
+#define CS42XX8_FUNCMOD_DAC_FM(v) ((v) << CS42XX8_FUNCMOD_DAC_FM_SHIFT)
+#define CS42XX8_FUNCMOD_ADC_FM_SHIFT 4
+#define CS42XX8_FUNCMOD_ADC_FM_WIDTH 2
+#define CS42XX8_FUNCMOD_ADC_FM_MASK (((1 << CS42XX8_FUNCMOD_ADC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_ADC_FM_SHIFT)
+#define CS42XX8_FUNCMOD_ADC_FM(v) ((v) << CS42XX8_FUNCMOD_ADC_FM_SHIFT)
+#define CS42XX8_FUNCMOD_xC_FM_MASK(x) ((x) ? CS42XX8_FUNCMOD_DAC_FM_MASK : CS42XX8_FUNCMOD_ADC_FM_MASK)
+#define CS42XX8_FUNCMOD_xC_FM(x, v) ((x) ? CS42XX8_FUNCMOD_DAC_FM(v) : CS42XX8_FUNCMOD_ADC_FM(v))
+#define CS42XX8_FUNCMOD_MFREQ_SHIFT 1
+#define CS42XX8_FUNCMOD_MFREQ_WIDTH 3
+#define CS42XX8_FUNCMOD_MFREQ_MASK (((1 << CS42XX8_FUNCMOD_MFREQ_WIDTH) - 1) << CS42XX8_FUNCMOD_MFREQ_SHIFT)
+#define CS42XX8_FUNCMOD_MFREQ_256(s) ((0 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42XX8_FUNCMOD_MFREQ_384(s) ((1 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42XX8_FUNCMOD_MFREQ_512(s) ((2 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42XX8_FUNCMOD_MFREQ_768(s) ((3 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42XX8_FUNCMOD_MFREQ_1024(s) ((4 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+
+#define CS42XX8_FM_SINGLE 0
+#define CS42XX8_FM_DOUBLE 1
+#define CS42XX8_FM_QUAD 2
+#define CS42XX8_FM_AUTO 3
+
+/* Interface Formats (Address 04h) */
+#define CS42XX8_INTF_FREEZE_SHIFT 7
+#define CS42XX8_INTF_FREEZE_MASK (1 << CS42XX8_INTF_FREEZE_SHIFT)
+#define CS42XX8_INTF_FREEZE (1 << CS42XX8_INTF_FREEZE_SHIFT)
+#define CS42XX8_INTF_AUX_DIF_SHIFT 6
+#define CS42XX8_INTF_AUX_DIF_MASK (1 << CS42XX8_INTF_AUX_DIF_SHIFT)
+#define CS42XX8_INTF_AUX_DIF (1 << CS42XX8_INTF_AUX_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_SHIFT 3
+#define CS42XX8_INTF_DAC_DIF_WIDTH 3
+#define CS42XX8_INTF_DAC_DIF_MASK (((1 << CS42XX8_INTF_DAC_DIF_WIDTH) - 1) << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_LEFTJ (0 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_I2S (1 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_RIGHTJ (2 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_ONELINE_20 (4 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_ONELINE_24 (5 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_TDM (6 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_SHIFT 0
+#define CS42XX8_INTF_ADC_DIF_WIDTH 3
+#define CS42XX8_INTF_ADC_DIF_MASK (((1 << CS42XX8_INTF_ADC_DIF_WIDTH) - 1) << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_LEFTJ (0 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_I2S (1 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_RIGHTJ (2 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_ONELINE_20 (4 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_ONELINE_24 (5 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_TDM (6 << CS42XX8_INTF_ADC_DIF_SHIFT)
+
+/* ADC Control & DAC De-Emphasis (Address 05h) */
+#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT 7
+#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_MASK (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT)
+#define CS42XX8_ADCCTL_ADC_HPF_FREEZE (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT)
+#define CS42XX8_ADCCTL_DAC_DEM_SHIFT 5
+#define CS42XX8_ADCCTL_DAC_DEM_MASK (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT)
+#define CS42XX8_ADCCTL_DAC_DEM (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT)
+#define CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT 4
+#define CS42XX8_ADCCTL_ADC1_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC1_SINGLE (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT 3
+#define CS42XX8_ADCCTL_ADC2_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC2_SINGLE (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT 2
+#define CS42XX8_ADCCTL_ADC3_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC3_SINGLE (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_AIN5_MUX_SHIFT 1
+#define CS42XX8_ADCCTL_AIN5_MUX_MASK (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT)
+#define CS42XX8_ADCCTL_AIN5_MUX (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT)
+#define CS42XX8_ADCCTL_AIN6_MUX_SHIFT 0
+#define CS42XX8_ADCCTL_AIN6_MUX_MASK (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT)
+#define CS42XX8_ADCCTL_AIN6_MUX (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT)
+
+/* Transition Control (Address 06h) */
+#define CS42XX8_TXCTL_DAC_SNGVOL_SHIFT 7
+#define CS42XX8_TXCTL_DAC_SNGVOL_MASK (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT)
+#define CS42XX8_TXCTL_DAC_SNGVOL (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_SHIFT 5
+#define CS42XX8_TXCTL_DAC_SZC_WIDTH 2
+#define CS42XX8_TXCTL_DAC_SZC_MASK (((1 << CS42XX8_TXCTL_DAC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_IC (0 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_ZC (1 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_SR (2 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_SRZC (3 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_AMUTE_SHIFT 4
+#define CS42XX8_TXCTL_AMUTE_MASK (1 << CS42XX8_TXCTL_AMUTE_SHIFT)
+#define CS42XX8_TXCTL_AMUTE (1 << CS42XX8_TXCTL_AMUTE_SHIFT)
+#define CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT 3
+#define CS42XX8_TXCTL_MUTE_ADC_SP_MASK (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT)
+#define CS42XX8_TXCTL_MUTE_ADC_SP (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT)
+#define CS42XX8_TXCTL_ADC_SNGVOL_SHIFT 2
+#define CS42XX8_TXCTL_ADC_SNGVOL_MASK (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT)
+#define CS42XX8_TXCTL_ADC_SNGVOL (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_SHIFT 0
+#define CS42XX8_TXCTL_ADC_SZC_MASK (((1 << CS42XX8_TXCTL_ADC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_IC (0 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_ZC (1 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_SR (2 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_SRZC (3 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+
+/* DAC Channel Mute (Address 07h) */
+#define CS42XX8_DACMUTE_AOUT(n) (0x1 << n)
+#define CS42XX8_DACMUTE_ALL 0xff
+
+/* Status Control (Address 18h)*/
+#define CS42XX8_STATUSCTL_INI_SHIFT 2
+#define CS42XX8_STATUSCTL_INI_WIDTH 2
+#define CS42XX8_STATUSCTL_INI_MASK (((1 << CS42XX8_STATUSCTL_INI_WIDTH) - 1) << CS42XX8_STATUSCTL_INI_SHIFT)
+#define CS42XX8_STATUSCTL_INT_ACTIVE_HIGH (0 << CS42XX8_STATUSCTL_INI_SHIFT)
+#define CS42XX8_STATUSCTL_INT_ACTIVE_LOW (1 << CS42XX8_STATUSCTL_INI_SHIFT)
+#define CS42XX8_STATUSCTL_INT_OPEN_DRAIN (2 << CS42XX8_STATUSCTL_INI_SHIFT)
+
+/* Status (Address 19h)*/
+#define CS42XX8_STATUS_DAC_CLK_ERR_SHIFT 4
+#define CS42XX8_STATUS_DAC_CLK_ERR_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_SHIFT)
+#define CS42XX8_STATUS_ADC_CLK_ERR_SHIFT 3
+#define CS42XX8_STATUS_ADC_CLK_ERR_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_SHIFT)
+#define CS42XX8_STATUS_ADC3_OVFL_SHIFT 2
+#define CS42XX8_STATUS_ADC3_OVFL_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_SHIFT)
+#define CS42XX8_STATUS_ADC2_OVFL_SHIFT 1
+#define CS42XX8_STATUS_ADC2_OVFL_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_SHIFT)
+#define CS42XX8_STATUS_ADC1_OVFL_SHIFT 0
+#define CS42XX8_STATUS_ADC1_OVFL_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_SHIFT)
+
+/* Status Mask (Address 1Ah) */
+#define CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT 4
+#define CS42XX8_STATUS_DAC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT)
+#define CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT 3
+#define CS42XX8_STATUS_ADC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT)
+#define CS42XX8_STATUS_ADC3_OVFL_M_SHIFT 2
+#define CS42XX8_STATUS_ADC3_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_M_SHIFT)
+#define CS42XX8_STATUS_ADC2_OVFL_M_SHIFT 1
+#define CS42XX8_STATUS_ADC2_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_M_SHIFT)
+#define CS42XX8_STATUS_ADC1_OVFL_M_SHIFT 0
+#define CS42XX8_STATUS_ADC1_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_M_SHIFT)
+
+/* MUTEC Pin Control (Address 1Bh) */
+#define CS42XX8_MUTEC_MCPOLARITY_SHIFT 1
+#define CS42XX8_MUTEC_MCPOLARITY_MASK (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT)
+#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_LOW (0 << CS42XX8_MUTEC_MCPOLARITY_SHIFT)
+#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_HIGH (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT)
+#define CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT 0
+#define CS42XX8_MUTEC_MUTEC_ACTIVE_MASK (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT)
+#define CS42XX8_MUTEC_MUTEC_ACTIVE (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT)
+#endif /* _CS42XX8_H */
diff --git a/sound/soc/codecs/rpmsg_wm8960.c b/sound/soc/codecs/rpmsg_wm8960.c
new file mode 100644
index 000000000000..d70e5a9b0606
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_wm8960.c
@@ -0,0 +1,1542 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * Copyright 2007-11 Wolfson Microelectronics, plc
+ *
+ * Author: Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/wm8960.h>
+#include "../fsl/fsl_rpmsg_i2s.h"
+#include "wm8960.h"
+
+/* R25 - Power 1 */
+#define WM8960_VMID_MASK 0x180
+#define WM8960_VREF 0x40
+
+/* R26 - Power 2 */
+#define WM8960_PWR2_LOUT1 0x40
+#define WM8960_PWR2_ROUT1 0x20
+#define WM8960_PWR2_OUT3 0x02
+
+/* R28 - Anti-pop 1 */
+#define WM8960_POBCTRL 0x80
+#define WM8960_BUFDCOPEN 0x10
+#define WM8960_BUFIOEN 0x08
+#define WM8960_SOFT_ST 0x04
+#define WM8960_HPSTBY 0x01
+
+/* R29 - Anti-pop 2 */
+#define WM8960_DISOP 0x40
+#define WM8960_DRES_MASK 0x30
+
+static bool is_pll_freq_available(unsigned int source, unsigned int target);
+static int wm8960_set_pll(struct snd_soc_component *component,
+ unsigned int freq_in, unsigned int freq_out);
+/*
+ * wm8960 register cache
+ * We can't read the WM8960 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ */
+static const struct reg_default wm8960_reg_defaults[] = {
+ { 0x0, 0x00a7 },
+ { 0x1, 0x00a7 },
+ { 0x2, 0x0000 },
+ { 0x3, 0x0000 },
+ { 0x4, 0x0000 },
+ { 0x5, 0x0008 },
+ { 0x6, 0x0000 },
+ { 0x7, 0x000a },
+ { 0x8, 0x01c0 },
+ { 0x9, 0x0000 },
+ { 0xa, 0x00ff },
+ { 0xb, 0x00ff },
+
+ { 0x10, 0x0000 },
+ { 0x11, 0x007b },
+ { 0x12, 0x0100 },
+ { 0x13, 0x0032 },
+ { 0x14, 0x0000 },
+ { 0x15, 0x00c3 },
+ { 0x16, 0x00c3 },
+ { 0x17, 0x01c0 },
+ { 0x18, 0x0000 },
+ { 0x19, 0x0000 },
+ { 0x1a, 0x0000 },
+ { 0x1b, 0x0000 },
+ { 0x1c, 0x0000 },
+ { 0x1d, 0x0000 },
+
+ { 0x20, 0x0100 },
+ { 0x21, 0x0100 },
+ { 0x22, 0x0050 },
+
+ { 0x25, 0x0050 },
+ { 0x26, 0x0000 },
+ { 0x27, 0x0000 },
+ { 0x28, 0x0000 },
+ { 0x29, 0x0000 },
+ { 0x2a, 0x0040 },
+ { 0x2b, 0x0000 },
+ { 0x2c, 0x0000 },
+ { 0x2d, 0x0050 },
+ { 0x2e, 0x0050 },
+ { 0x2f, 0x0000 },
+ { 0x30, 0x0002 },
+ { 0x31, 0x0037 },
+
+ { 0x33, 0x0080 },
+ { 0x34, 0x0008 },
+ { 0x35, 0x0031 },
+ { 0x36, 0x0026 },
+ { 0x37, 0x00e9 },
+};
+
+struct rpmsg_wm8960_priv {
+ struct clk *mclk;
+ struct regmap *regmap;
+ int (*set_bias_level)(struct snd_soc_component *,
+ enum snd_soc_bias_level level);
+ struct snd_soc_dapm_widget *lout1;
+ struct snd_soc_dapm_widget *rout1;
+ struct snd_soc_dapm_widget *out3;
+ bool deemph;
+ int lrclk;
+ int bclk;
+ int sysclk;
+ int clk_id;
+ int freq_in;
+ bool is_stream_in_use[2];
+ struct wm8960_data pdata;
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+ int audioindex;
+};
+
+static bool wm8960_volatile(struct device *dev, unsigned int reg)
+{
+ struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev);
+
+ if (!wm8960->mclk)
+ return true;
+
+ switch (reg) {
+ case WM8960_RESET:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0)
+
+/* enumerated controls */
+static const char * const wm8960_polarity[] = {"No Inversion", "Left Inverted",
+ "Right Inverted", "Stereo Inversion"};
+static const char * const wm8960_3d_upper_cutoff[] = {"High", "Low"};
+static const char * const wm8960_3d_lower_cutoff[] = {"Low", "High"};
+static const char * const wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"};
+static const char * const wm8960_alcmode[] = {"ALC", "Limiter"};
+static const char * const wm8960_adc_data_output_sel[] = {
+ "Left Data = Left ADC; Right Data = Right ADC",
+ "Left Data = Left ADC; Right Data = Left ADC",
+ "Left Data = Right ADC; Right Data = Right ADC",
+ "Left Data = Right ADC; Right Data = Left ADC",
+};
+static const char * const wm8960_dmonomix[] = {"Stereo", "Mono"};
+
+static const struct soc_enum wm8960_enum[] = {
+ SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity),
+ SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity),
+ SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff),
+ SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff),
+ SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc),
+ SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),
+ SOC_ENUM_SINGLE(WM8960_ADDCTL1, 2, 4, wm8960_adc_data_output_sel),
+ SOC_ENUM_SINGLE(WM8960_ADDCTL1, 4, 2, wm8960_dmonomix),
+};
+
+static const int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8960_set_deemph(struct snd_soc_component *component)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ int val, i, best;
+
+ /* If we're using deemphasis select the nearest available sample
+ * rate.
+ */
+ if (wm8960->deemph) {
+ best = 1;
+ for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+ if (abs(deemph_settings[i] - wm8960->lrclk) <
+ abs(deemph_settings[best] - wm8960->lrclk))
+ best = i;
+ }
+
+ val = best << 1;
+ } else {
+ val = 0;
+ }
+
+ dev_dbg(component->dev, "Set deemphasis %d\n", val);
+
+ return snd_soc_component_update_bits(component, WM8960_DACCTL1,
+ 0x6, val);
+}
+
+static int wm8960_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = wm8960->deemph;
+ return 0;
+}
+
+static int wm8960_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ unsigned int deemph = ucontrol->value.integer.value[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ wm8960->deemph = deemph;
+
+ return wm8960_set_deemph(component);
+}
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(lineinboost_tlv, -1500, 300, 1);
+static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(micboost_tlv,
+ 0, 1, TLV_DB_SCALE_ITEM(0, 1300, 0),
+ 2, 3, TLV_DB_SCALE_ITEM(2000, 900, 0),
+);
+
+static const struct snd_kcontrol_new wm8960_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL,
+ 0, 63, 0, inpga_tlv),
+SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,
+ 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL,
+ 7, 1, 1),
+
+SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume",
+ WM8960_INBMIX1, 4, 7, 0, lineinboost_tlv),
+SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT2 Volume",
+ WM8960_INBMIX1, 1, 7, 0, lineinboost_tlv),
+SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume",
+ WM8960_INBMIX2, 4, 7, 0, lineinboost_tlv),
+SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume",
+ WM8960_INBMIX2, 1, 7, 0, lineinboost_tlv),
+SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT1 Volume",
+ WM8960_RINPATH, 4, 3, 0, micboost_tlv),
+SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume",
+ WM8960_LINPATH, 4, 3, 0, micboost_tlv),
+
+SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC,
+ 0, 255, 0, dac_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1,
+ 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2,
+ 7, 1, 0),
+SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
+SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),
+
+SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
+SOC_ENUM("ADC Polarity", wm8960_enum[0]),
+SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
+
+SOC_ENUM("DAC Polarity", wm8960_enum[1]),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+ wm8960_get_deemph, wm8960_put_deemph),
+
+SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]),
+SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]),
+SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),
+SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),
+
+SOC_ENUM("ALC Function", wm8960_enum[4]),
+SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),
+SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),
+SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),
+SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),
+SOC_ENUM("ALC Mode", wm8960_enum[5]),
+SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),
+
+SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0),
+SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("ADC PCM Capture Volume", WM8960_LADC, WM8960_RADC,
+ 0, 255, 0, adc_tlv),
+
+SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume",
+ WM8960_BYPASS1, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume",
+ WM8960_LOUTMIX, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume",
+ WM8960_BYPASS2, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume",
+ WM8960_ROUTMIX, 4, 7, 1, bypass_tlv),
+
+SOC_ENUM("ADC Data Output Select", wm8960_enum[6]),
+SOC_ENUM("DAC Mono Mix", wm8960_enum[7]),
+};
+
+static const struct snd_kcontrol_new wm8960_lin_boost[] = {
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_lin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin_boost[] = {
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_routput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_mono_out[] = {
+SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+
+SND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0,
+ wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),
+SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0,
+ wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),
+
+SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,
+ wm8960_lin, ARRAY_SIZE(wm8960_lin)),
+SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0,
+ wm8960_rin, ARRAY_SIZE(wm8960_rin)),
+
+SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0),
+
+SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,
+ &wm8960_loutput_mixer[0],
+ ARRAY_SIZE(wm8960_loutput_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,
+ &wm8960_routput_mixer[0],
+ ARRAY_SIZE(wm8960_routput_mixer)),
+
+SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("SPK_LP"),
+SND_SOC_DAPM_OUTPUT("SPK_LN"),
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+SND_SOC_DAPM_OUTPUT("SPK_RP"),
+SND_SOC_DAPM_OUTPUT("SPK_RN"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+};
+
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = {
+SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
+ &wm8960_mono_out[0],
+ ARRAY_SIZE(wm8960_mono_out)),
+};
+
+/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = {
+SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
+ { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
+ { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" },
+
+ { "Left Input Mixer", "Boost Switch", "Left Boost Mixer" },
+ { "Left Input Mixer", "Boost Switch", "LINPUT1" }, /* Really Boost Switch */
+ { "Left Input Mixer", NULL, "LINPUT2" },
+ { "Left Input Mixer", NULL, "LINPUT3" },
+
+ { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" },
+ { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" },
+ { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" },
+
+ { "Right Input Mixer", "Boost Switch", "Right Boost Mixer" },
+ { "Right Input Mixer", "Boost Switch", "RINPUT1" }, /* Really Boost Switch */
+ { "Right Input Mixer", NULL, "RINPUT2" },
+ { "Right Input Mixer", NULL, "RINPUT3" },
+
+ { "Left ADC", NULL, "Left Input Mixer" },
+ { "Right ADC", NULL, "Right Input Mixer" },
+
+ { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },
+ { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer" },
+ { "Left Output Mixer", "PCM Playback Switch", "Left DAC" },
+
+ { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" },
+ { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" },
+ { "Right Output Mixer", "PCM Playback Switch", "Right DAC" },
+
+ { "LOUT1 PGA", NULL, "Left Output Mixer" },
+ { "ROUT1 PGA", NULL, "Right Output Mixer" },
+
+ { "HP_L", NULL, "LOUT1 PGA" },
+ { "HP_R", NULL, "ROUT1 PGA" },
+
+ { "Left Speaker PGA", NULL, "Left Output Mixer" },
+ { "Right Speaker PGA", NULL, "Right Output Mixer" },
+
+ { "Left Speaker Output", NULL, "Left Speaker PGA" },
+ { "Right Speaker Output", NULL, "Right Speaker PGA" },
+
+ { "SPK_LN", NULL, "Left Speaker Output" },
+ { "SPK_LP", NULL, "Left Speaker Output" },
+ { "SPK_RN", NULL, "Right Speaker Output" },
+ { "SPK_RP", NULL, "Right Speaker Output" },
+};
+
+static const struct snd_soc_dapm_route audio_paths_out3[] = {
+ { "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
+ { "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
+
+ { "OUT3", NULL, "Mono Output Mixer", }
+};
+
+static const struct snd_soc_dapm_route audio_paths_capless[] = {
+ { "HP_L", NULL, "OUT3 VMID" },
+ { "HP_R", NULL, "OUT3 VMID" },
+
+ { "OUT3 VMID", NULL, "Left Output Mixer" },
+ { "OUT3 VMID", NULL, "Right Output Mixer" },
+};
+
+static int wm8960_add_widgets(struct snd_soc_component *component)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ struct wm8960_data *pdata = &wm8960->pdata;
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+ struct snd_soc_dapm_widget *w;
+
+ snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets,
+ ARRAY_SIZE(wm8960_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
+
+ /* In capless mode OUT3 is used to provide VMID for the
+ * headphone outputs, otherwise it is used as a mono mixer.
+ */
+ if (pdata && pdata->capless) {
+ snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless,
+ ARRAY_SIZE(wm8960_dapm_widgets_capless));
+
+ snd_soc_dapm_add_routes(dapm, audio_paths_capless,
+ ARRAY_SIZE(audio_paths_capless));
+ } else {
+ snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3,
+ ARRAY_SIZE(wm8960_dapm_widgets_out3));
+
+ snd_soc_dapm_add_routes(dapm, audio_paths_out3,
+ ARRAY_SIZE(audio_paths_out3));
+ }
+
+ /* We need to power up the headphone output stage out of
+ * sequence for capless mode. To save scanning the widget
+ * list each time to find the desired power state do so now
+ * and save the result.
+ */
+ list_for_each_entry(w, &component->card->widgets, list) {
+ if (w->dapm != dapm)
+ continue;
+ if (strcmp(w->name, "LOUT1 PGA") == 0)
+ wm8960->lout1 = w;
+ if (strcmp(w->name, "ROUT1 PGA") == 0)
+ wm8960->rout1 = w;
+ if (strcmp(w->name, "OUT3 VMID") == 0)
+ wm8960->out3 = w;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_component *component = codec_dai->component;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ snd_soc_component_write(component, WM8960_IFACE1, iface);
+ return 0;
+}
+
+static struct {
+ int rate;
+ unsigned int val;
+} alc_rates[] = {
+ { 48000, 0 },
+ { 44100, 0 },
+ { 32000, 1 },
+ { 22050, 2 },
+ { 24000, 2 },
+ { 16000, 3 },
+ { 11025, 4 },
+ { 12000, 4 },
+ { 8000, 5 },
+};
+
+/* -1 for reserved value */
+static const int sysclk_divs[] = { 1, -1, 2, -1 };
+
+/* Multiply 256 for internal 256 div */
+static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 };
+
+/* Multiply 10 to eliminate decimials */
+static const int bclk_divs[] = {
+ 10, 15, 20, 30, 40, 55, 60, 80, 110,
+ 120, 160, 220, 240, 320, 320, 320
+};
+
+/**
+ * wm8960_configure_sysclk - checks if there is a sysclk frequency available
+ * The sysclk must be chosen such that:
+ * - sysclk = MCLK / sysclk_divs
+ * - lrclk = sysclk / dac_divs
+ * - 10 * bclk = sysclk / bclk_divs
+ *
+ * @wm8960_priv: wm8960 codec private data
+ * @mclk: MCLK used to derive sysclk
+ * @sysclk_idx: sysclk_divs index for found sysclk
+ * @dac_idx: dac_divs index for found lrclk
+ * @bclk_idx: bclk_divs index for found bclk
+ *
+ * Returns:
+ * -1, in case no sysclk frequency available found
+ * >=0, in case we could derive bclk and lrclk from sysclk using
+ * (@sysclk_idx, @dac_idx, @bclk_idx) dividers
+ */
+static
+int wm8960_configure_sysclk(struct rpmsg_wm8960_priv *wm8960, int mclk,
+ int *sysclk_idx, int *dac_idx, int *bclk_idx)
+{
+ int sysclk, bclk, lrclk;
+ int i, j, k;
+ int diff;
+
+ /* marker for no match */
+ *bclk_idx = -1;
+
+ bclk = wm8960->bclk;
+ lrclk = wm8960->lrclk;
+
+ /* check if the sysclk frequency is available. */
+ for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+ if (sysclk_divs[i] == -1)
+ continue;
+ sysclk = mclk / sysclk_divs[i];
+ for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+ if (sysclk != dac_divs[j] * lrclk)
+ continue;
+ for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
+ diff = sysclk - bclk * bclk_divs[k] / 10;
+ if (diff == 0) {
+ *sysclk_idx = i;
+ *dac_idx = j;
+ *bclk_idx = k;
+ break;
+ }
+ }
+ if (k != ARRAY_SIZE(bclk_divs))
+ break;
+ }
+ if (j != ARRAY_SIZE(dac_divs))
+ break;
+ }
+ return *bclk_idx;
+}
+
+/**
+ * wm8960_configure_pll - checks if there is a PLL out frequency available
+ * The PLL out frequency must be chosen such that:
+ * - sysclk = lrclk * dac_divs
+ * - freq_out = sysclk * sysclk_divs
+ * - 10 * sysclk = bclk * bclk_divs
+ *
+ * @component: component structure
+ * @freq_in: input frequency used to derive freq out via PLL
+ * @sysclk_idx: sysclk_divs index for found sysclk
+ * @dac_idx: dac_divs index for found lrclk
+ * @bclk_idx: bclk_divs index for found bclk
+ *
+ * Returns:
+ * -1, in case no PLL frequency out available was found
+ * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using
+ * (@sysclk_idx, @dac_idx, @bclk_idx) dividers
+ */
+static
+int wm8960_configure_pll(struct snd_soc_component *component, int freq_in,
+ int *sysclk_idx, int *dac_idx, int *bclk_idx)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ int sysclk, bclk, lrclk, freq_out;
+ int diff, best_freq_out = 0;
+ int i, j, k;
+
+ bclk = wm8960->bclk;
+ lrclk = wm8960->lrclk;
+
+ *bclk_idx = *dac_idx = *sysclk_idx = -1;
+
+ for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+ if (sysclk_divs[i] == -1)
+ continue;
+ for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+ sysclk = lrclk * dac_divs[j];
+ freq_out = sysclk * sysclk_divs[i];
+
+ for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
+ if (!is_pll_freq_available(freq_in, freq_out))
+ continue;
+
+ diff = sysclk - bclk * bclk_divs[k] / 10;
+ if (diff == 0) {
+ *sysclk_idx = i;
+ *dac_idx = j;
+ *bclk_idx = k;
+ best_freq_out = freq_out;
+ break;
+ }
+ }
+ if (k != ARRAY_SIZE(bclk_divs))
+ break;
+ }
+ if (j != ARRAY_SIZE(dac_divs))
+ break;
+ }
+
+ if (*bclk_idx != -1)
+ wm8960_set_pll(component, freq_in, best_freq_out);
+
+ return *bclk_idx;
+}
+static int wm8960_configure_clocking(struct snd_soc_component *component)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ int freq_out, freq_in;
+ u16 iface1 = snd_soc_component_read32(component, WM8960_IFACE1);
+ int i, j, k;
+ int ret;
+
+ if (!(iface1 & (1<<6))) {
+ dev_dbg(component->dev,
+ "Codec is slave mode, no need to configure clock\n");
+ return 0;
+ }
+
+ if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) {
+ dev_err(component->dev, "No MCLK configured\n");
+ return -EINVAL;
+ }
+
+ freq_in = wm8960->freq_in;
+ /*
+ * If it's sysclk auto mode, check if the MCLK can provide sysclk or
+ * not. If MCLK can provide sysclk, using MCLK to provide sysclk
+ * directly. Otherwise, auto select a available pll out frequency
+ * and set PLL.
+ */
+ if (wm8960->clk_id == WM8960_SYSCLK_AUTO) {
+ /* disable the PLL and using MCLK to provide sysclk */
+ wm8960_set_pll(component, 0, 0);
+ freq_out = freq_in;
+ } else if (wm8960->sysclk) {
+ freq_out = wm8960->sysclk;
+ } else {
+ dev_err(component->dev, "No SYSCLK configured\n");
+ return -EINVAL;
+ }
+
+ if (wm8960->clk_id != WM8960_SYSCLK_PLL) {
+ ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k);
+ if (ret >= 0) {
+ goto configure_clock;
+ } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) {
+ dev_err(component->dev, "failed to configure clock\n");
+ return -EINVAL;
+ }
+ }
+
+ ret = wm8960_configure_pll(component, freq_in, &i, &j, &k);
+ if (ret < 0) {
+ dev_err(component->dev, "failed to configure clock via PLL\n");
+ return -EINVAL;
+ }
+
+configure_clock:
+ /* configure sysclk clock */
+ snd_soc_component_update_bits(component, WM8960_CLOCK1, 3 << 1, i << 1);
+
+ /* configure frame clock */
+ snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 3, j << 3);
+ snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 6, j << 6);
+
+ /* configure bit clock */
+ snd_soc_component_update_bits(component, WM8960_CLOCK2, 0xf, k);
+
+ return 0;
+}
+
+static int wm8960_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 rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ u16 iface = snd_soc_component_read32(component, WM8960_IFACE1) & 0xfff3;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int i;
+
+ wm8960->bclk = snd_soc_params_to_bclk(params);
+ if (params_channels(params) == 1)
+ wm8960->bclk *= 2;
+
+ /* bit size */
+ switch (params_width(params)) {
+ case 16:
+ break;
+ case 20:
+ iface |= 0x0004;
+ break;
+ case 24:
+ iface |= 0x0008;
+ break;
+ case 32:
+ /* right justify mode does not support 32 word length */
+ if ((iface & 0x3) != 0) {
+ iface |= 0x000c;
+ break;
+ }
+ /* fall through */
+ default:
+ dev_err(component->dev, "unsupported width %d\n",
+ params_width(params));
+ return -EINVAL;
+ }
+
+ wm8960->lrclk = params_rate(params);
+ /* Update filters for the new rate */
+ if (tx) {
+ wm8960_set_deemph(component);
+ } else {
+ for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
+ if (alc_rates[i].rate == params_rate(params))
+ snd_soc_component_update_bits(component,
+ WM8960_ADDCTL3, 0x7,
+ alc_rates[i].val);
+ }
+
+ /* set iface */
+ snd_soc_component_write(component, WM8960_IFACE1, iface);
+
+ wm8960->is_stream_in_use[tx] = true;
+
+ if (!wm8960->is_stream_in_use[!tx])
+ return wm8960_configure_clocking(component);
+
+ return 0;
+}
+
+static int wm8960_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ wm8960->is_stream_in_use[tx] = false;
+
+ return 0;
+}
+
+static int wm8960_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_component *component = dai->component;
+
+ if (mute)
+ snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0x8);
+ else
+ snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0);
+ return 0;
+}
+
+static int wm8960_set_bias_level_out3(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2);
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ switch (snd_soc_component_get_bias_level(component)) {
+ case SND_SOC_BIAS_STANDBY:
+ if (!IS_ERR(wm8960->mclk)) {
+ ret = clk_prepare_enable(wm8960->mclk);
+ if (ret) {
+ dev_err(component->dev,
+ "Failed to enable MCLK: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ ret = wm8960_configure_clocking(component);
+ if (ret)
+ return ret;
+
+ /* Set VMID to 2x50k */
+ snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x80);
+ break;
+
+ case SND_SOC_BIAS_ON:
+ /*
+ * If it's sysclk auto mode, and the pll is enabled,
+ * disable the pll
+ */
+ if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1))
+ wm8960_set_pll(component, 0, 0);
+
+ if (!IS_ERR(wm8960->mclk))
+ clk_disable_unprepare(wm8960->mclk);
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) {
+ regcache_sync(wm8960->regmap);
+
+ /* Enable anti-pop features */
+ snd_soc_component_write(component, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+ /* Enable & ramp VMID at 2x50k */
+ snd_soc_component_update_bits(component, WM8960_POWER1, 0x80, 0x80);
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_component_update_bits(component, WM8960_POWER1, WM8960_VREF,
+ WM8960_VREF);
+
+ /* Disable anti-pop features */
+ snd_soc_component_write(component, WM8960_APOP1, WM8960_BUFIOEN);
+ }
+
+ /* Set VMID to 2x250k */
+ snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x100);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable anti-pop features */
+ snd_soc_component_write(component, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+ /* Disable VMID and VREF, let them discharge */
+ snd_soc_component_write(component, WM8960_POWER1, 0);
+ msleep(600);
+ break;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_bias_level_capless(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2);
+ int reg, ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ switch (snd_soc_component_get_bias_level(component)) {
+ case SND_SOC_BIAS_STANDBY:
+ /* Enable anti pop mode */
+ snd_soc_component_update_bits(component, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+
+ /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */
+ reg = 0;
+ if (wm8960->lout1 && wm8960->lout1->power)
+ reg |= WM8960_PWR2_LOUT1;
+ if (wm8960->rout1 && wm8960->rout1->power)
+ reg |= WM8960_PWR2_ROUT1;
+ if (wm8960->out3 && wm8960->out3->power)
+ reg |= WM8960_PWR2_OUT3;
+ snd_soc_component_update_bits(component, WM8960_POWER2,
+ WM8960_PWR2_LOUT1 |
+ WM8960_PWR2_ROUT1 |
+ WM8960_PWR2_OUT3, reg);
+
+ /* Enable VMID at 2*50k */
+ snd_soc_component_update_bits(component, WM8960_POWER1,
+ WM8960_VMID_MASK, 0x80);
+
+ /* Ramp */
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_component_update_bits(component, WM8960_POWER1,
+ WM8960_VREF, WM8960_VREF);
+
+ msleep(100);
+
+ if (!IS_ERR(wm8960->mclk)) {
+ ret = clk_prepare_enable(wm8960->mclk);
+ if (ret) {
+ dev_err(component->dev,
+ "Failed to enable MCLK: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ ret = wm8960_configure_clocking(component);
+ if (ret)
+ return ret;
+
+ break;
+
+ case SND_SOC_BIAS_ON:
+ /*
+ * If it's sysclk auto mode, and the pll is enabled,
+ * disable the pll
+ */
+ if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1))
+ wm8960_set_pll(component, 0, 0);
+
+ if (!IS_ERR(wm8960->mclk))
+ clk_disable_unprepare(wm8960->mclk);
+
+ /* Enable anti-pop mode */
+ snd_soc_component_update_bits(component, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+
+ /* Disable VMID and VREF */
+ snd_soc_component_update_bits(component, WM8960_POWER1,
+ WM8960_VREF | WM8960_VMID_MASK, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ regcache_sync(wm8960->regmap);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ switch (snd_soc_component_get_bias_level(component)) {
+ case SND_SOC_BIAS_PREPARE:
+ /* Disable HP discharge */
+ snd_soc_component_update_bits(component, WM8960_APOP2,
+ WM8960_DISOP | WM8960_DRES_MASK,
+ 0);
+
+ /* Disable anti-pop features */
+ snd_soc_component_update_bits(component, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 pre_div:1;
+ u32 n:4;
+ u32 k:24;
+};
+
+static bool is_pll_freq_available(unsigned int source, unsigned int target)
+{
+ unsigned int Ndiv;
+
+ if (source == 0 || target == 0)
+ return false;
+
+ /* Scale up target to PLL operating frequency */
+ target *= 4;
+ Ndiv = target / source;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ return false;
+
+ return true;
+}
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later
+ */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static int pll_factors(unsigned int source, unsigned int target,
+ struct _pll_div *pll_div)
+{
+ unsigned long long Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target);
+
+ /* Scale up target to PLL operating frequency */
+ target *= 4;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->pre_div = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->pre_div = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12)) {
+ pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv);
+ return -EINVAL;
+ }
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+
+ pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n",
+ pll_div->n, pll_div->k, pll_div->pre_div);
+
+ return 0;
+}
+
+static int wm8960_set_pll(struct snd_soc_component *component,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg;
+ static struct _pll_div pll_div;
+ int ret;
+
+ if (freq_in && freq_out) {
+ ret = pll_factors(freq_in, freq_out, &pll_div);
+ if (ret != 0)
+ return ret;
+ }
+
+ /* Disable the PLL: even if we are changing the frequency the
+ * PLL needs to be disabled while we do so.
+ */
+ snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x1, 0);
+ snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ reg = snd_soc_component_read32(component, WM8960_PLL1) & ~0x3f;
+ reg |= pll_div.pre_div << 4;
+ reg |= pll_div.n;
+
+ if (pll_div.k) {
+ reg |= 0x20;
+
+ snd_soc_component_write(component, WM8960_PLL2, (pll_div.k >> 16) & 0xff);
+ snd_soc_component_write(component, WM8960_PLL3, (pll_div.k >> 8) & 0xff);
+ snd_soc_component_write(component, WM8960_PLL4, pll_div.k & 0xff);
+ }
+ snd_soc_component_write(component, WM8960_PLL1, reg);
+
+ /* Turn it on */
+ snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0x1);
+ msleep(250);
+ snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x1, 0x1);
+
+ return 0;
+}
+
+static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_component *component = codec_dai->component;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+
+ wm8960->freq_in = freq_in;
+
+ if (pll_id == WM8960_SYSCLK_AUTO)
+ return 0;
+
+ if (is_pll_freq_available(freq_in, freq_out))
+ return -EINVAL;
+
+ return wm8960_set_pll(component, freq_in, freq_out);
+}
+
+static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_component *component = codec_dai->component;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8960_SYSCLKDIV:
+ reg = snd_soc_component_read32(component, WM8960_CLOCK1) & 0x1f9;
+ snd_soc_component_write(component, WM8960_CLOCK1, reg | div);
+ break;
+ case WM8960_DACDIV:
+ reg = snd_soc_component_read32(component, WM8960_CLOCK1) & 0x1c7;
+ snd_soc_component_write(component, WM8960_CLOCK1, reg | div);
+ break;
+ case WM8960_OPCLKDIV:
+ reg = snd_soc_component_read32(component, WM8960_PLL1) & 0x03f;
+ snd_soc_component_write(component, WM8960_PLL1, reg | div);
+ break;
+ case WM8960_DCLKDIV:
+ reg = snd_soc_component_read32(component, WM8960_CLOCK2) & 0x03f;
+ snd_soc_component_write(component, WM8960_CLOCK2, reg | div);
+ break;
+ case WM8960_TOCLKSEL:
+ reg = snd_soc_component_read32(component, WM8960_ADDCTL1) & 0x1fd;
+ snd_soc_component_write(component, WM8960_ADDCTL1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_bias_level(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+
+ return wm8960->set_bias_level(component, level);
+}
+
+static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+
+ switch (clk_id) {
+ case WM8960_SYSCLK_MCLK:
+ snd_soc_component_update_bits(component, WM8960_CLOCK1,
+ 0x1, WM8960_SYSCLK_MCLK);
+ break;
+ case WM8960_SYSCLK_PLL:
+ snd_soc_component_update_bits(component, WM8960_CLOCK1,
+ 0x1, WM8960_SYSCLK_PLL);
+ break;
+ case WM8960_SYSCLK_AUTO:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8960->sysclk = freq;
+ wm8960->clk_id = clk_id;
+
+ return 0;
+}
+
+#define RPMSG_RATES SNDRV_PCM_RATE_8000_48000
+
+#define RPMSG_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static const struct snd_soc_dai_ops rpmsg_wm8960_dai_ops = {
+ .hw_params = wm8960_hw_params,
+ .hw_free = wm8960_hw_free,
+ .digital_mute = wm8960_mute,
+ .set_fmt = wm8960_set_dai_fmt,
+ .set_clkdiv = wm8960_set_dai_clkdiv,
+ .set_pll = wm8960_set_dai_pll,
+ .set_sysclk = wm8960_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver rpmsg_wm8960_codec_dai = {
+ .name = "rpmsg-wm8960-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = RPMSG_RATES,
+ .formats = RPMSG_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = RPMSG_RATES,
+ .formats = RPMSG_FORMATS,
+ },
+ .ops = &rpmsg_wm8960_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int rpmsg_wm8960_probe(struct snd_soc_component *component)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ struct wm8960_data *pdata = &wm8960->pdata;
+
+ if (pdata->capless)
+ wm8960->set_bias_level = wm8960_set_bias_level_capless;
+ else
+ wm8960->set_bias_level = wm8960_set_bias_level_out3;
+
+ snd_soc_add_component_controls(component, wm8960_snd_controls,
+ ARRAY_SIZE(wm8960_snd_controls));
+ wm8960_add_widgets(component);
+
+ return 0;
+}
+
+static int rpmsg_wm8960_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct rpmsg_wm8960_priv *wm8960 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = wm8960->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg;
+ int err, reg_val;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = wm8960->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->header.cmd = GET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info);
+ reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data;
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ *val = reg_val;
+ return 0;
+}
+
+static int rpmsg_wm8960_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct rpmsg_wm8960_priv *wm8960 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = wm8960->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg;
+ int err;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = wm8960->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->param.buffer_size = val;
+ rpmsg->header.cmd = SET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info);
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver rpmsg_wm8960_component = {
+ .probe = rpmsg_wm8960_probe,
+ .set_bias_level = wm8960_set_bias_level,
+ .suspend_bias_off = 1,
+};
+
+static const struct regmap_config rpmsg_wm8960_regmap = {
+ .reg_bits = 7,
+ .val_bits = 9,
+ .max_register = WM8960_PLL4,
+
+ .reg_defaults = wm8960_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(wm8960_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+
+ .volatile_reg = wm8960_volatile,
+ .reg_read = rpmsg_wm8960_read,
+ .reg_write = rpmsg_wm8960_write,
+};
+
+#ifdef CONFIG_PM
+static int wm8960_runtime_resume(struct device *dev)
+{
+ struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(wm8960->mclk);
+ if (ret) {
+ dev_err(dev, "Failed to enable MCLK: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int wm8960_runtime_suspend(struct device *dev)
+{
+ struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(wm8960->mclk);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops wm8960_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(wm8960_runtime_suspend, wm8960_runtime_resume, NULL)
+};
+
+static int rpmsg_wm8960_codec_probe(struct platform_device *pdev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent);
+ struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data;
+ struct rpmsg_wm8960_priv *wm8960;
+ int ret;
+ int repeat_reset = 10;
+
+ wm8960 = devm_kzalloc(&pdev->dev, sizeof(struct rpmsg_wm8960_priv),
+ GFP_KERNEL);
+ if (wm8960 == NULL)
+ return -ENOMEM;
+
+ wm8960->rpmsg_i2s = rpmsg_i2s;
+
+ wm8960->mclk = devm_clk_get(pdev->dev.parent, "mclk");
+ if (IS_ERR(wm8960->mclk)) {
+ if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ wm8960->mclk = NULL;
+ }
+
+ dev_set_drvdata(&pdev->dev, wm8960);
+
+ wm8960->regmap = devm_regmap_init(&pdev->dev, NULL, wm8960, &rpmsg_wm8960_regmap);
+ if (IS_ERR(wm8960->regmap))
+ return PTR_ERR(wm8960->regmap);
+
+ if (pdata) {
+ wm8960->pdata.shared_lrclk = pdata->shared_lrclk;
+ wm8960->pdata.capless = pdata->capless;
+ wm8960->audioindex = pdata->audioindex;
+ }
+
+ if (wm8960->mclk) {
+ do {
+ ret = wm8960_reset(wm8960->regmap);
+ repeat_reset--;
+ } while (repeat_reset > 0 && ret != 0);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ if (wm8960->pdata.shared_lrclk) {
+ ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2,
+ 0x4, 0x4);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to enable LRCM: %d\n",
+ ret);
+ return ret;
+ }
+ }
+ }
+
+ /* Latch the update bits */
+ regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_LADC, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_RADC, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_LDAC, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_RDAC, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_LOUT1, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100);
+
+ pm_runtime_enable(&pdev->dev);
+
+ if (!wm8960->mclk)
+ rpmsg_wm8960_codec_dai.ops = NULL;
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &rpmsg_wm8960_component, &rpmsg_wm8960_codec_dai, 1);
+
+ return ret;
+}
+
+static int rpmsg_wm8960_codec_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver rpmsg_wm8960_codec_driver = {
+ .driver = {
+ .name = RPMSG_CODEC_DRV_NAME_WM8960,
+ .pm = &wm8960_pm,
+ },
+ .probe = rpmsg_wm8960_codec_probe,
+ .remove = rpmsg_wm8960_codec_remove,
+};
+
+module_platform_driver(rpmsg_wm8960_codec_driver);
+
+MODULE_DESCRIPTION("rpmsg wm8960 Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c
index 8d88db9c11a6..8924aeb6bed4 100644
--- a/sound/soc/codecs/si476x.c
+++ b/sound/soc/codecs/si476x.c
@@ -199,9 +199,28 @@ out:
return err;
}
+static int si476x_codec_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai) {
+ struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
+
+ if (!si476x_core_is_powered_up(core))
+ si476x_core_set_power_state(core, SI476X_POWER_UP_FULL);
+ return 0;
+}
+
+static void si476x_codec_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai) {
+ struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
+
+ if (si476x_core_is_powered_up(core))
+ si476x_core_set_power_state(core, SI476X_POWER_DOWN);
+}
+
static const struct snd_soc_dai_ops si476x_dai_ops = {
.hw_params = si476x_codec_hw_params,
.set_fmt = si476x_codec_set_dai_fmt,
+ .startup = si476x_codec_startup,
+ .shutdown = si476x_codec_shutdown,
};
static struct snd_soc_dai_driver si476x_dai = {
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c
index f34637afee51..472c2fff34a8 100644
--- a/sound/soc/codecs/twl6040.c
+++ b/sound/soc/codecs/twl6040.c
@@ -1108,8 +1108,10 @@ static int twl6040_probe(struct snd_soc_component *component)
priv->component = component;
priv->plug_irq = platform_get_irq(pdev, 0);
- if (priv->plug_irq < 0)
+ if (priv->plug_irq < 0) {
+ dev_err(component->dev, "invalid irq: %d\n", priv->plug_irq);
return priv->plug_irq;
+ }
INIT_DELAYED_WORK(&priv->hs_jack.work, twl6040_accessory_work);
diff --git a/sound/soc/codecs/wm8524.c b/sound/soc/codecs/wm8524.c
index 91e3d1570c45..60ef4c042e1e 100644
--- a/sound/soc/codecs/wm8524.c
+++ b/sound/soc/codecs/wm8524.c
@@ -61,6 +61,7 @@ static int wm8524_startup(struct snd_pcm_substream *substream,
{
struct snd_soc_component *component = dai->component;
struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
/* The set of sample rates that can be supported depends on the
* MCLK supplied to the CODEC - enforce this.
@@ -71,9 +72,10 @@ static int wm8524_startup(struct snd_pcm_substream *substream,
return -EINVAL;
}
- snd_pcm_hw_constraint_list(substream->runtime, 0,
- SNDRV_PCM_HW_PARAM_RATE,
- &wm8524->rate_constraint);
+ if (!rtd->dai_link->be_hw_params_fixup)
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &wm8524->rate_constraint);
gpiod_set_value_cansleep(wm8524->mute, 1);
@@ -159,7 +161,8 @@ static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
#define WM8524_RATES SNDRV_PCM_RATE_8000_192000
-#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops wm8524_dai_ops = {
.startup = wm8524_startup,
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c
index 55112c1bba5e..adb82301ff20 100644
--- a/sound/soc/codecs/wm8960.c
+++ b/sound/soc/codecs/wm8960.c
@@ -608,10 +608,6 @@ static const int bclk_divs[] = {
* - lrclk = sysclk / dac_divs
* - 10 * bclk = sysclk / bclk_divs
*
- * If we cannot find an exact match for (sysclk, lrclk, bclk)
- * triplet, we relax the bclk such that bclk is chosen as the
- * closest available frequency greater than expected bclk.
- *
* @wm8960_priv: wm8960 codec private data
* @mclk: MCLK used to derive sysclk
* @sysclk_idx: sysclk_divs index for found sysclk
@@ -629,7 +625,7 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk,
{
int sysclk, bclk, lrclk;
int i, j, k;
- int diff, closest = mclk;
+ int diff;
/* marker for no match */
*bclk_idx = -1;
@@ -653,12 +649,6 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk,
*bclk_idx = k;
break;
}
- if (diff > 0 && closest > diff) {
- *sysclk_idx = i;
- *dac_idx = j;
- *bclk_idx = k;
- closest = diff;
- }
}
if (k != ARRAY_SIZE(bclk_divs))
break;
@@ -676,10 +666,6 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk,
* - freq_out = sysclk * sysclk_divs
* - 10 * sysclk = bclk * bclk_divs
*
- * If we cannot find an exact match for (sysclk, lrclk, bclk)
- * triplet, we relax the bclk such that bclk is chosen as the
- * closest available frequency greater than expected bclk.
- *
* @component: component structure
* @freq_in: input frequency used to derive freq out via PLL
* @sysclk_idx: sysclk_divs index for found sysclk
@@ -697,12 +683,11 @@ int wm8960_configure_pll(struct snd_soc_component *component, int freq_in,
{
struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
int sysclk, bclk, lrclk, freq_out;
- int diff, closest, best_freq_out;
+ int diff, best_freq_out;
int i, j, k;
bclk = wm8960->bclk;
lrclk = wm8960->lrclk;
- closest = freq_in;
best_freq_out = -EINVAL;
*sysclk_idx = *dac_idx = *bclk_idx = -1;
@@ -725,13 +710,6 @@ int wm8960_configure_pll(struct snd_soc_component *component, int freq_in,
*bclk_idx = k;
return freq_out;
}
- if (diff > 0 && closest > diff) {
- *sysclk_idx = i;
- *dac_idx = j;
- *bclk_idx = k;
- closest = diff;
- best_freq_out = freq_out;
- }
}
}
}
@@ -860,8 +838,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
wm8960->is_stream_in_use[tx] = true;
- if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON &&
- !wm8960->is_stream_in_use[!tx])
+ if (!wm8960->is_stream_in_use[!tx])
return wm8960_configure_clocking(component);
return 0;
@@ -1120,11 +1097,6 @@ static bool is_pll_freq_available(unsigned int source, unsigned int target)
target *= 4;
Ndiv = target / source;
- if (Ndiv < 6) {
- source >>= 1;
- Ndiv = target / source;
- }
-
if ((Ndiv < 6) || (Ndiv > 12))
return false;
@@ -1235,6 +1207,9 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
if (pll_id == WM8960_SYSCLK_AUTO)
return 0;
+ if (is_pll_freq_available(freq_in, freq_out))
+ return -EINVAL;
+
return wm8960_set_pll(component, freq_in, freq_out);
}
@@ -1398,6 +1373,7 @@ static int wm8960_i2c_probe(struct i2c_client *i2c,
struct wm8960_data *pdata = dev_get_platdata(&i2c->dev);
struct wm8960_priv *wm8960;
int ret;
+ int repeat_reset = 10;
wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv),
GFP_KERNEL);
@@ -1419,7 +1395,11 @@ static int wm8960_i2c_probe(struct i2c_client *i2c,
else if (i2c->dev.of_node)
wm8960_set_pdata_from_of(i2c, &wm8960->pdata);
- ret = wm8960_reset(wm8960->regmap);
+ do {
+ ret = wm8960_reset(wm8960->regmap);
+ repeat_reset--;
+ } while (repeat_reset > 0 && ret != 0);
+
if (ret != 0) {
dev_err(&i2c->dev, "Failed to issue reset\n");
return ret;
diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c
index 3e5c69fbc33a..0b2f84f5cec9 100644
--- a/sound/soc/codecs/wm8962.c
+++ b/sound/soc/codecs/wm8962.c
@@ -3,6 +3,7 @@
* wm8962.c -- WM8962 ALSA SoC Audio driver
*
* Copyright 2010-2 Wolfson Microelectronics plc
+ * Copyright 2017 NXP
*
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
*/
@@ -82,6 +83,7 @@ struct wm8962_priv {
#endif
int irq;
+ u32 cache_clocking2_reg;
};
/* We can't use the same notifier block for more than one supply and
@@ -1777,8 +1779,11 @@ SND_SOC_BYTES("HD Bass Coefficients", WM8962_HDBASS_AI_1, 30),
SOC_DOUBLE("ALC Switch", WM8962_ALC1, WM8962_ALCL_ENA_SHIFT,
WM8962_ALCR_ENA_SHIFT, 1, 0),
-SND_SOC_BYTES_MASK("ALC Coefficients", WM8962_ALC1, 4,
+SND_SOC_BYTES_MASK("ALC1", WM8962_ALC1, 1,
WM8962_ALCL_ENA_MASK | WM8962_ALCR_ENA_MASK),
+SND_SOC_BYTES("ALC2", WM8962_ALC2, 1),
+SND_SOC_BYTES("ALC3", WM8962_ALC3, 1),
+SND_SOC_BYTES("Noise Gate", WM8962_NOISE_GATE, 1),
};
static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = {
@@ -2554,11 +2559,17 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream,
{
struct snd_soc_component *component = dai->component;
struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component);
+ snd_pcm_format_t sample_format = params_format(params);
int i;
int aif0 = 0;
int adctl3 = 0;
- wm8962->bclk = snd_soc_params_to_bclk(params);
+ if (sample_format == SNDRV_PCM_FORMAT_S20_3LE)
+ wm8962->bclk = params_rate(params) *
+ params_channels(params) *
+ params_physical_width(params);
+ else
+ wm8962->bclk = snd_soc_params_to_bclk(params);
if (params_channels(params) == 1)
wm8962->bclk *= 2;
@@ -2788,7 +2799,7 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
if (target % Fref == 0) {
fll_div->theta = 0;
- fll_div->lambda = 0;
+ fll_div->lambda = 1;
} else {
gcd_fll = gcd(target, fratio * Fref);
@@ -2858,7 +2869,7 @@ static int wm8962_set_fll(struct snd_soc_component *component, int fll_id, int s
return -EINVAL;
}
- if (fll_div.theta || fll_div.lambda)
+ if (fll_div.theta)
fll1 |= WM8962_FLL_FRAC;
/* Stop the FLL while we reconfigure */
@@ -3813,6 +3824,10 @@ static int wm8962_runtime_resume(struct device *dev)
regcache_sync(wm8962->regmap);
+ regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2,
+ WM8962_SYSCLK_SRC_MASK,
+ wm8962->cache_clocking2_reg);
+
regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP,
WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA,
WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA);
@@ -3842,6 +3857,9 @@ static int wm8962_runtime_suspend(struct device *dev)
WM8962_STARTUP_BIAS_ENA |
WM8962_VMID_BUF_ENA, 0);
+ regmap_read(wm8962->regmap, WM8962_CLOCKING2,
+ &wm8962->cache_clocking2_reg);
+
regcache_cache_only(wm8962->regmap, true);
regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies),
@@ -3854,6 +3872,7 @@ static int wm8962_runtime_suspend(struct device *dev)
#endif
static const struct dev_pm_ops wm8962_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(wm8962_runtime_suspend, wm8962_runtime_resume, NULL)
};
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index aa99c008a925..e3f94e95e746 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -65,6 +65,14 @@ config SND_SOC_FSL_ESAI
This option is only useful for out-of-tree drivers since
in-tree drivers select it automatically.
+config SND_SOC_FSL_DAI
+ tristate "Generic FSL DAI support for Sound Open Firmware"
+ help
+ Say Y if you want to enable generic FSL DAI support to be used
+ with Sound Open Firmware. This module takes care of enabling
+ clocks, power domain, pinctrl for FSL DAIs. The rest of DAI
+ control is taken care of by SOF firmware.
+
config SND_SOC_FSL_MICFIL
tristate "Pulse Density Modulation Microphone Interface (MICFIL) module support"
select REGMAP_MMIO
@@ -74,13 +82,47 @@ config SND_SOC_FSL_MICFIL
Say Y if you want to add Pulse Density Modulation microphone
interface (MICFIL) support for NXP.
+config SND_SOC_FSL_EASRC
+ tristate "Enhanced ASRC module support"
+ 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_DSP
+ tristate "dsp module support"
+ select SND_SOC_COMPRESS
+ help
+ Say Y if you want to add hifi 4 support for the Freescale CPUs.
+ which is a DSP core for audio processing.
+
+config SND_SOC_FSL_RPMSG_I2S
+ tristate "I2S base on the RPMSG support"
+ depends on HAVE_IMX_RPMSG
+ help
+ Say Y if you want to add rpmsg i2s support for the Freescale CPUs.
+ which is depends on the rpmsg.
+ This option is only useful for out-of-tree drivers since
+ in-tree drivers select it automatically.
+
config SND_SOC_FSL_UTILS
tristate
+config SND_SOC_FSL_HDMI
+ tristate
+
config SND_SOC_IMX_PCM_DMA
tristate
select SND_SOC_GENERIC_DMAENGINE_PCM
+config SND_SOC_IMX_PCM_RPMSG
+ tristate
+ depends on HAVE_IMX_RPMSG
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+
config SND_SOC_IMX_AUDMUX
tristate "Digital Audio Mux module support"
help
@@ -98,7 +140,7 @@ config SND_POWERPC_SOC
config SND_IMX_SOC
tristate "SoC Audio for Freescale i.MX CPUs"
- depends on ARCH_MXC || COMPILE_TEST
+ depends on ARCH_MXC || ARCH_MXC_ARM64 || COMPILE_TEST
help
Say Y or M if you want to add support for codecs attached to
the i.MX CPUs.
@@ -201,6 +243,11 @@ config SND_SOC_IMX_SSI
tristate
select SND_SOC_FSL_UTILS
+config SND_SOC_IMX_HDMI_DMA
+ bool
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select SND_SOC_IMX_PCM_DMA
+
comment "SoC Audio support for Freescale i.MX boards:"
config SND_MXC_SOC_WM1133_EV1
@@ -249,6 +296,149 @@ config SND_SOC_EUKREA_TLV320
Enable I2S based access to the TLV320AIC23B codec attached
to the SSI interface
+config SND_SOC_IMX_AK4458
+ tristate "SoC Audio support for i.MX boards with AK4458"
+ depends on OF && I2C
+ select SND_SOC_AK4458_I2C
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with AK4458
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ an AK4458 DAC.
+
+config SND_SOC_IMX_AK5558
+ tristate "SoC Audio support for i.MX boards with AK5558"
+ depends on OF && I2C
+ select SND_SOC_AK5558
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with AK5558
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ an AK5558 ADC.
+
+config SND_SOC_IMX_AK4497
+ tristate "SoC Audio support for i.MX boards with AK4497"
+ depends on OF && I2C
+ select SND_SOC_AK4458
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with AK4497
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ an AK4497 DAC.
+
+config SND_SOC_IMX_WM8960
+ tristate "SoC Audio support for i.MX boards with wm8960"
+ depends on OF && I2C
+ select SND_SOC_WM8960
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ select SND_KCTL_JACK
+ help
+ SoC Audio support for i.MX boards with WM8960
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a wm8960 codec.
+
+config SND_SOC_IMX_WM8524
+ tristate "SoC Audio support for i.MX boards with wm8524"
+ depends on OF && I2C
+ select SND_SOC_WM8524
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ select SND_KCTL_JACK
+ help
+ SoC Audio support for i.MX boards with WM8524
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a wm8524 codec.
+
+config SND_SOC_IMX_SII902X
+ tristate "SoC Audio support for i.MX boards with sii902x"
+ depends on OF && I2C
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with SII902X
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a sii902x.
+
+config SND_SOC_IMX_WM8958
+ tristate "SoC Audio support for i.MX boards with wm8958"
+ depends on OF && I2C
+ select MFD_WM8994
+ select SND_SOC_WM8994
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ select SND_KCTL_JACK
+ help
+ SoC Audio support for i.MX boards with WM8958
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a wm8958 codec.
+
+config SND_SOC_IMX_CS42888
+ tristate "SoC Audio support for i.MX boards with cs42888"
+ depends on OF && I2C
+ select SND_SOC_CS42XX8_I2C
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_ESAI
+ select SND_SOC_FSL_ASRC
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with cs42888
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a cs42888 codec.
+
+config SND_SOC_IMX_WM8962
+ tristate "SoC Audio support for i.MX boards with wm8962"
+ depends on OF && I2C && INPUT
+ select SND_SOC_WM8962
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_IMX_AUDMUX
+ select SND_SOC_FSL_SSI
+ select SND_KCTL_JACK
+ help
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a wm8962 codec.
+
+config SND_SOC_IMX_WM8962_ANDROID
+ tristate "SoC Audio support for i.MX boards with wm8962 in android"
+ depends on SND_SOC_IMX_WM8962=y
+ help
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a wm8962 codec in android.
+
+config SND_SOC_IMX_MICFIL
+ tristate "SoC Audio support for i.MX boards with micfil"
+ depends on OF && I2C
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_MICFIL
+ help
+ Soc Audio support for i.MX boards with micfil
+ Say Y if you want to add support for SoC audio on
+ an i.MX board with micfil.
+
+config SND_SOC_IMX_RPMSG
+ tristate "SoC Audio support for i.MX boards with rpmsg"
+ depends on HAVE_IMX_RPMSG
+ select SND_SOC_IMX_PCM_RPMSG
+ select SND_SOC_FSL_RPMSG_I2S
+ select SND_SOC_RPMSG_WM8960
+ select SND_SOC_RPMSG_AK4497
+ select SND_SOC_RPMSG_CS42XX8
+ help
+ SoC Audio support for i.MX boards with rpmsg.
+ There should be rpmsg devices defined in other 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_ES8328
tristate "SoC Audio support for i.MX boards with the ES8328 codec"
depends on OF && (I2C || SPI)
@@ -272,6 +462,14 @@ config SND_SOC_IMX_SGTL5000
Say Y if you want to add support for SoC audio on an i.MX board with
a sgtl5000 codec.
+config SND_SOC_IMX_MQS
+ tristate "SoC Audio support for i.MX boards with MQS"
+ depends on OF
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_MQS
+ select SND_SOC_FSL_UTILS
+
config SND_SOC_IMX_SPDIF
tristate "SoC Audio support for i.MX boards with S/PDIF"
select SND_SOC_IMX_PCM_DMA
@@ -302,7 +500,7 @@ config SND_SOC_FSL_ASOC_CARD
help
ALSA SoC Audio support with ASRC feature for Freescale SoCs that have
ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888,
- CS4271, CS4272 and SGTL5000.
+ CS4271, CS4272, and SGTL5000.
Say Y if you want to add support for Freescale Generic ASoC Sound Card.
config SND_SOC_IMX_AUDMIX
@@ -314,6 +512,59 @@ config SND_SOC_IMX_AUDMIX
Say Y if you want to add support for SoC audio on an i.MX board with
an Audio Mixer.
+config SND_SOC_IMX_PDM_MIC
+ tristate "SoC Audio support for i.MX boards with PDM mic on SAI"
+ depends on OF
+ select SND_SOC_IMX_PDM_DMA
+ select SND_SOC_FSL_SAI
+ help
+ SoC Audio support for i.MX boards with PDM microphones on SAI
+ Say Y if you want to add support for SoC Audio support for i.MX boards
+ with PDM microphones on SAI.
+
+config SND_SOC_IMX_DSP
+ tristate "SoC Audio support for i.MX boards with DSP port"
+ select SND_SOC_FSL_DSP
+ select SND_SOC_COMPRESS
+ help
+ SoC Audio support for i.MX boards with DSP audio
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ IMX DSP.
+
+config SND_SOC_IMX_SI476X
+ tristate "SoC Audio support for i.MX boards with si476x"
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_IMX_AUDMUX
+ select SND_SOC_FSL_SSI
+ select SND_SOC_FSL_UTILS
+ select SND_SOC_SI476X
+ help
+ SoC Audio support for i.MX boards with SI476x
+ Say Y if you want to add support for Soc audio for the AMFM Tuner chip
+ SI476x module.
+
+config SND_SOC_IMX_HDMI
+ tristate "SoC Audio support for i.MX boards with HDMI port"
+ depends on MFD_MXC_HDMI
+ select SND_SOC_IMX_HDMI_DMA
+ select SND_SOC_FSL_HDMI
+ select SND_SOC_HDMI_CODEC
+ help
+ SoC Audio support for i.MX boards with HDMI audio
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ IMX HDMI.
+
+config SND_SOC_IMX_CDNHDMI
+ tristate "SoC Audio support for i.MX boards with CDN HDMI port"
+ depends on DRM_IMX_CDNS_MHDP
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_HDMI_CODEC
+ help
+ SoC Audio support for i.MX boards with CDN HDMI audio
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ IMX CDN HDMI.
+
endif # SND_IMX_SOC
endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index c0dd04422fe9..630a39ae8eb0 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -13,27 +13,40 @@ 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-dsp-objs := fsl_dsp.o fsl_dsp_proxy.o fsl_dsp_pool.o \
+ fsl_dsp_library_load.o fsl_dsp_xaf_api.o fsl_dsp_cpu.o \
+ fsl_dsp_platform_compress.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-dai-objs := fsl_dai.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-easrc-objs := fsl_easrc.o fsl_easrc_dma.o
obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o
+snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
+snd-soc-fsl-rpmsg-i2s-objs := fsl_rpmsg_i2s.o
obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
+snd-soc-fsl-hdmi-objs := fsl_hdmi.o
obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o
+obj-$(CONFIG_SND_SOC_FSL_DSP) += snd-soc-fsl-dsp.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_DAI) += snd-soc-fsl-dai.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_HDMI) += snd-soc-fsl-hdmi.o
obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
+obj-$(CONFIG_SND_SOC_FSL_EASRC) += snd-soc-fsl-easrc.o
+obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
+obj-$(CONFIG_SND_SOC_FSL_RPMSG_I2S) += snd-soc-fsl-rpmsg-i2s.o
# MPC5200 Platform Support
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
@@ -51,7 +64,10 @@ obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o
obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o
obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o
+obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o imx-pcm-dma-v2.o
obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o
+obj-$(CONFIG_SND_SOC_IMX_HDMI_DMA) += imx-hdmi-dma.o hdmi_pcm.o
+obj-$(CONFIG_SND_SOC_IMX_PCM_RPMSG) += imx-pcm-rpmsg.o
# i.MX Machine Support
snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
@@ -59,17 +75,53 @@ snd-soc-phycore-ac97-objs := phycore-ac97.o
snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o
snd-soc-wm1133-ev1-objs := wm1133-ev1.o
snd-soc-imx-es8328-objs := imx-es8328.o
+snd-soc-imx-cs42888-objs := imx-cs42888.o
snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
+snd-soc-imx-wm8958-objs := imx-wm8958.o
+snd-soc-imx-wm8960-objs := imx-wm8960.o
+snd-soc-imx-wm8524-objs := imx-wm8524.o
+snd-soc-imx-wm8962-objs := imx-wm8962.o
+snd-soc-imx-sii902x-objs := imx-sii902x.o
snd-soc-imx-spdif-objs := imx-spdif.o
snd-soc-imx-mc13783-objs := imx-mc13783.o
snd-soc-imx-audmix-objs := imx-audmix.o
+snd-soc-imx-mqs-objs := imx-mqs.o
+snd-soc-imx-pdm-objs := imx-pdm.o
+snd-soc-imx-ak4458-objs := imx-ak4458.o
+snd-soc-imx-ak5558-objs := imx-ak5558.o
+snd-soc-imx-ak4497-objs := imx-ak4497.o
+snd-soc-imx-micfil-objs := imx-micfil.o
+snd-soc-imx-dsp-objs := imx-dsp.o
+snd-soc-imx-si476x-objs := imx-si476x.o
+snd-soc-imx-hdmi-objs := imx-hdmi.o
+snd-soc-imx-cdnhdmi-objs := imx-cdnhdmi.o
+snd-soc-imx-rpmsg-objs := imx-rpmsg.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o
+obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o
obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
+obj-${CONFIG_SND_SOC_IMX_WM8958} += snd-soc-imx-wm8958.o
+obj-$(CONFIG_SND_SOC_IMX_WM8960) += snd-soc-imx-wm8960.o
+obj-$(CONFIG_SND_SOC_IMX_WM8524) += snd-soc-imx-wm8524.o
+obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o
+obj-$(CONFIG_SND_SOC_IMX_SII902X) += snd-soc-imx-sii902x.o
+obj-$(CONFIG_SND_SOC_IMX_RPMSG) += snd-soc-imx-rpmsg.o
obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o
obj-$(CONFIG_SND_SOC_IMX_AUDMIX) += snd-soc-imx-audmix.o
+obj-$(CONFIG_SND_SOC_IMX_MQS) += snd-soc-imx-mqs.o
+obj-$(CONFIG_SND_SOC_IMX_PDM_MIC) += snd-soc-imx-pdm.o
+obj-$(CONFIG_SND_SOC_IMX_AK4458) += snd-soc-imx-ak4458.o
+obj-$(CONFIG_SND_SOC_IMX_AK5558) += snd-soc-imx-ak5558.o
+obj-$(CONFIG_SND_SOC_IMX_AK4497) += snd-soc-imx-ak4497.o
+obj-$(CONFIG_SND_SOC_IMX_MICFIL) += snd-soc-imx-micfil.o
+obj-$(CONFIG_SND_SOC_IMX_DSP) += snd-soc-imx-dsp.o
+obj-$(CONFIG_SND_SOC_IMX_SI476X) += snd-soc-imx-si476x.o
+obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o
+obj-$(CONFIG_SND_SOC_IMX_CDNHDMI) += snd-soc-imx-cdnhdmi.o
+
+AFLAGS_hdmi_pcm.o := -march=armv7-a -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp
diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c
index cfa40ef6b1ca..0b6f7c09804f 100644
--- a/sound/soc/fsl/fsl_asrc.c
+++ b/sound/soc/fsl/fsl_asrc.c
@@ -2,7 +2,8 @@
//
// Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver
//
-// Copyright (C) 2014 Freescale Semiconductor, Inc.
+// Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
+// Copyright 2017 NXP
//
// Author: Nicolin Chen <nicoleotsuka@gmail.com>
@@ -13,6 +14,9 @@
#include <linux/of_platform.h>
#include <linux/platform_data/dma-imx.h>
#include <linux/pm_runtime.h>
+#include <linux/miscdevice.h>
+#include <linux/sched/signal.h>
+#include <linux/pm_domain.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
@@ -23,6 +27,9 @@
#define pair_err(fmt, ...) \
dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+#define pair_warn(fmt, ...) \
+ dev_warn(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+
#define pair_dbg(fmt, ...) \
dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
@@ -41,26 +48,58 @@ static struct snd_pcm_hw_constraint_list fsl_asrc_rate_constraints = {
* The following tables map the relationship between asrc_inclk/asrc_outclk in
* fsl_asrc.h and the registers of ASRCSR
*/
+#define CLK_MAP_NUM 48
static unsigned char input_clk_map_imx35[] = {
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[] = {
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[] = {
/* 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[] = {
/* 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,
};
-static unsigned char *clk_map[2];
+/* i.MX8 uses the same map for input and output */
+static unsigned char input_clk_map_imx8_0[] = {
+ 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,
+};
+
+static unsigned char output_clk_map_imx8_0[] = {
+ 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,
+};
+
+static unsigned char input_clk_map_imx8_1[] = {
+ 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 output_clk_map_imx8_1[] = {
+ 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,
+};
/**
* Select the pre-processing and post-processing options
@@ -115,7 +154,7 @@ static void fsl_asrc_sel_proc(int inrate, int outrate,
* 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)
+int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
{
enum asrc_pair_index index = ASRC_INVALID_PAIR;
struct fsl_asrc *asrc_priv = pair->asrc_priv;
@@ -158,7 +197,7 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
*
* It clears the resource from asrc_priv and releases the occupied channels.
*/
-static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair)
+void fsl_asrc_release_pair(struct fsl_asrc_pair *pair)
{
struct fsl_asrc *asrc_priv = pair->asrc_priv;
enum asrc_pair_index index = pair->index;
@@ -260,17 +299,20 @@ static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair,
* of struct asrc_config which includes in/output sample rate, width, channel
* and clock settings.
*/
-static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
+static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool p2p_in, bool p2p_out)
{
struct asrc_config *config = pair->config;
struct fsl_asrc *asrc_priv = pair->asrc_priv;
enum asrc_pair_index index = pair->index;
u32 inrate, outrate, indiv, outdiv;
- u32 clk_index[2], div[2];
+ u32 clk_index[2], div[2], rem[2];
+ u64 clk_rate;
int in, out, channels;
int pre_proc, post_proc;
struct clk *clk;
bool ideal;
+ enum asrc_word_width input_word_width;
+ enum asrc_word_width output_word_width;
if (!config) {
pair_err("invalid pair config\n");
@@ -283,9 +325,32 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
return -EINVAL;
}
- /* Validate output width */
- if (config->output_word_width == ASRC_WIDTH_8_BIT) {
- pair_err("does not support 8bit width output\n");
+ 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;
}
@@ -320,13 +385,14 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
}
/* Validate input and output clock sources */
- clk_index[IN] = clk_map[IN][config->inclk];
- clk_index[OUT] = clk_map[OUT][config->outclk];
+ 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]];
-
- div[IN] = clk_get_rate(clk) / inrate;
+ clk_rate = clk_get_rate(clk);
+ rem[IN] = do_div(clk_rate, inrate);
+ div[IN] = (u32)clk_rate;
if (div[IN] == 0) {
pair_err("failed to support input sample rate %dHz by asrck_%x\n",
inrate, clk_index[ideal ? OUT : IN]);
@@ -335,11 +401,20 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
clk = asrc_priv->asrck_clk[clk_index[OUT]];
- /* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */
- if (ideal)
- div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE;
- else
- div[OUT] = clk_get_rate(clk) / outrate;
+ /*
+ * When P2P mode, output rate should align with the out samplerate.
+ * if set too high output rate, there will be lots of Overload.
+ * When M2M mode, output rate should also need to align with the out
+ * samplerate, but M2M must use less time to achieve good performance.
+ */
+ clk_rate = clk_get_rate(clk);
+ if (p2p_out || p2p_in || (!ideal)) {
+ rem[OUT] = do_div(clk_rate, outrate);
+ div[OUT] = clk_rate;
+ } else {
+ rem[OUT] = do_div(clk_rate, IDEAL_RATIO_RATE);
+ div[OUT] = clk_rate;
+ }
if (div[OUT] == 0) {
pair_err("failed to support output sample rate %dHz by asrck_%x\n",
@@ -347,6 +422,23 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
return -EINVAL;
}
+ if (!ideal && (div[IN] > 1024 || div[OUT] > 1024 ||
+ rem[IN] != 0 || rem[OUT] != 0)) {
+ pair_err("The divider can't be used for non ideal mode\n");
+ return -EINVAL;
+ }
+
+ if (ideal && div[IN] > 1024 && div[OUT] > 1024) {
+ pair_warn("both divider (%d, %d) are larger than threshold\n",
+ div[IN], div[OUT]);
+ }
+
+ if (div[IN] > 1024)
+ div[IN] = 1024;
+
+ if (div[OUT] > 1024)
+ div[OUT] = 1024;
+
/* Set the channel number */
channels = config->channel_num;
@@ -361,8 +453,11 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
/* Default setting: Automatic selection for processing mode */
regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index));
+
+ /* Default setting: use internal measured ratio */
regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
- ASRCTR_USRi_MASK(index), 0);
+ ASRCTR_USRi_MASK(index) | ASRCTR_IDRi_MASK(index),
+ ASRCTR_USR(index));
/* Set the input and output clock sources */
regmap_update_bits(asrc_priv->regmap, REG_ASRCSR,
@@ -383,8 +478,8 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
/* Implement word_width configurations */
regmap_update_bits(asrc_priv->regmap, REG_ASRMCR1(index),
ASRMCR1i_OW16_MASK | ASRMCR1i_IWD_MASK,
- ASRMCR1i_OW16(config->output_word_width) |
- ASRMCR1i_IWD(config->input_word_width));
+ ASRMCR1i_OW16(output_word_width) |
+ ASRMCR1i_IWD(input_word_width));
/* Enable BUFFER STALL */
regmap_update_bits(asrc_priv->regmap, REG_ASRMCR(index),
@@ -427,7 +522,7 @@ static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair)
{
struct fsl_asrc *asrc_priv = pair->asrc_priv;
enum asrc_pair_index index = pair->index;
- int reg, retry = 10, i;
+ int reg, retry = 50, i;
/* Enable the current pair */
regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
@@ -440,6 +535,9 @@ static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair)
reg &= ASRCFG_INIRQi_MASK(index);
} while (!reg && --retry);
+ if (retry == 0)
+ pair_warn("initialization is not finished\n");
+
/* Make the input fifo to ASRC STALL level */
regmap_read(asrc_priv->regmap, REG_ASRCNCR, &reg);
for (i = 0; i < pair->channels * 4; i++)
@@ -492,18 +590,73 @@ static int fsl_asrc_dai_startup(struct snd_pcm_substream *substream,
SNDRV_PCM_HW_PARAM_RATE, &fsl_asrc_rate_constraints);
}
+static int fsl_asrc_select_clk(struct fsl_asrc *asrc_priv,
+ struct fsl_asrc_pair *pair,
+ int in_rate,
+ int out_rate)
+{
+ struct asrc_config *config = pair->config;
+ int clk_rate;
+ int clk_index;
+ int i = 0, j = 0;
+ int rate[2];
+ int select_clk[2];
+ bool clk_sel[2];
+
+ rate[0] = in_rate;
+ rate[1] = out_rate;
+
+ /*select proper clock for asrc p2p mode*/
+ for (j = 0; j < 2; j++) {
+ for (i = 0; i < CLK_MAP_NUM; i++) {
+ clk_index = asrc_priv->clk_map[j][i];
+ clk_rate = clk_get_rate(asrc_priv->asrck_clk[clk_index]);
+ if (clk_rate != 0 && (clk_rate / rate[j]) <= 1024 &&
+ (clk_rate % rate[j]) == 0)
+ break;
+ }
+
+ if (i == CLK_MAP_NUM) {
+ select_clk[j] = OUTCLK_ASRCK1_CLK;
+ clk_sel[j] = false;
+ } else {
+ select_clk[j] = i;
+ clk_sel[j] = true;
+ }
+ }
+
+ if (clk_sel[0] != true || clk_sel[1] != true)
+ select_clk[IN] = INCLK_NONE;
+
+ config->inclk = select_clk[IN];
+ config->outclk = select_clk[OUT];
+
+ /*
+ * FIXME: workaroud for 176400/192000 with 8 channel input case
+ * the output sample rate is 48kHz.
+ * with ideal ratio mode, the asrc seems has performance issue
+ * that the output sound is not correct. so switch to non-ideal
+ * ratio mode
+ */
+ if (config->channel_num >= 8 && config->input_sample_rate >= 176400
+ && config->inclk == INCLK_NONE)
+ config->inclk = INCLK_ASRCK1_CLK;
+
+ return 0;
+}
+
static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(dai);
- int width = params_width(params);
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_asrc_pair *pair = runtime->private_data;
unsigned int channels = params_channels(params);
unsigned int rate = params_rate(params);
struct asrc_config config;
- int word_width, ret;
+ snd_pcm_format_t format;
+ int ret;
ret = fsl_asrc_request_pair(channels, pair);
if (ret) {
@@ -511,39 +664,56 @@ static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream,
return ret;
}
+ pair->pair_streams |= BIT(substream->stream);
pair->config = &config;
- if (width == 16)
- width = ASRC_WIDTH_16_BIT;
- else
- width = ASRC_WIDTH_24_BIT;
-
if (asrc_priv->asrc_width == 16)
- word_width = ASRC_WIDTH_16_BIT;
+ format = SNDRV_PCM_FORMAT_S16_LE;
else
- word_width = ASRC_WIDTH_24_BIT;
+ format = SNDRV_PCM_FORMAT_S24_LE;
config.pair = pair->index;
config.channel_num = channels;
- config.inclk = INCLK_NONE;
- config.outclk = OUTCLK_ASRCK1_CLK;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- config.input_word_width = width;
- config.output_word_width = word_width;
+ config.input_format = params_format(params);
+ config.output_format = format;
config.input_sample_rate = rate;
config.output_sample_rate = asrc_priv->asrc_rate;
+
+ ret = fsl_asrc_select_clk(asrc_priv, pair,
+ config.input_sample_rate,
+ config.output_sample_rate);
+ if (ret) {
+ dev_err(dai->dev, "fail to select clock\n");
+ return ret;
+ }
+
+ ret = fsl_asrc_config_pair(pair, false, true);
+ if (ret) {
+ dev_err(dai->dev, "fail to config asrc pair\n");
+ return ret;
+ }
+
} else {
- config.input_word_width = word_width;
- config.output_word_width = width;
+ config.input_format = format;
+ config.output_format = params_format(params);
config.input_sample_rate = asrc_priv->asrc_rate;
config.output_sample_rate = rate;
- }
- ret = fsl_asrc_config_pair(pair);
- if (ret) {
- dev_err(dai->dev, "fail to config asrc pair\n");
- return ret;
+ ret = fsl_asrc_select_clk(asrc_priv, pair,
+ config.input_sample_rate,
+ config.output_sample_rate);
+ if (ret) {
+ dev_err(dai->dev, "fail to select clock\n");
+ return ret;
+ }
+
+ ret = fsl_asrc_config_pair(pair, true, false);
+ if (ret) {
+ dev_err(dai->dev, "fail to config asrc pair\n");
+ return ret;
+ }
}
return 0;
@@ -555,8 +725,10 @@ static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_asrc_pair *pair = runtime->private_data;
- if (pair)
+ if (pair && (pair->pair_streams & BIT(substream->stream))) {
fsl_asrc_release_pair(pair);
+ pair->pair_streams &= ~BIT(substream->stream);
+ }
return 0;
}
@@ -572,6 +744,8 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
fsl_asrc_start_pair(pair);
+ /* Output enough data to content the DMA burstsize of BE */
+ mdelay(1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
@@ -602,9 +776,13 @@ static int fsl_asrc_dai_probe(struct snd_soc_dai *dai)
return 0;
}
-#define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \
+#define FSL_ASRC_FORMATS_RX (SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_3LE)
+#define FSL_ASRC_FORMATS_TX (SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S16_LE | \
- SNDRV_PCM_FMTBIT_S20_3LE)
+ SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S24_3LE)
static struct snd_soc_dai_driver fsl_asrc_dai = {
.probe = fsl_asrc_dai_probe,
@@ -615,7 +793,7 @@ static struct snd_soc_dai_driver fsl_asrc_dai = {
.rate_min = 5512,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_KNOT,
- .formats = FSL_ASRC_FORMATS,
+ .formats = FSL_ASRC_FORMATS_TX,
},
.capture = {
.stream_name = "ASRC-Capture",
@@ -624,7 +802,7 @@ static struct snd_soc_dai_driver fsl_asrc_dai = {
.rate_min = 5512,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_KNOT,
- .formats = FSL_ASRC_FORMATS,
+ .formats = FSL_ASRC_FORMATS_RX,
},
.ops = &fsl_asrc_dai_ops,
};
@@ -772,11 +950,15 @@ static const struct regmap_config fsl_asrc_regmap_config = {
.cache_type = REGCACHE_FLAT,
};
+#include "fsl_asrc_m2m.c"
+
/**
* Initialize ASRC registers with a default configurations
*/
static int fsl_asrc_init(struct fsl_asrc *asrc_priv)
{
+ unsigned long ipg_rate;
+
/* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */
regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN);
@@ -794,11 +976,12 @@ static int fsl_asrc_init(struct fsl_asrc *asrc_priv)
regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1,
ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc));
- /* Set the processing clock for 76KHz to 133M */
- regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6);
-
- /* Set the processing clock for 56KHz to 133M */
- return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947);
+ ipg_rate = clk_get_rate(asrc_priv->ipg_clk);
+ /* Set the period of the 76KHz and 56KHz sampling clocks based on
+ * the ASRC processing clock.
+ */
+ regmap_write(asrc_priv->regmap, REG_ASR76K, ipg_rate / 76000);
+ return regmap_write(asrc_priv->regmap, REG_ASR56K, ipg_rate / 56000);
}
/**
@@ -862,6 +1045,7 @@ static int fsl_asrc_probe(struct platform_device *pdev)
void __iomem *regs;
int irq, ret, i;
char tmp[16];
+ int num_domains = 0;
asrc_priv = devm_kzalloc(&pdev->dev, sizeof(*asrc_priv), GFP_KERNEL);
if (!asrc_priv)
@@ -885,8 +1069,10 @@ static int fsl_asrc_probe(struct platform_device *pdev)
}
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
+ }
ret = devm_request_irq(&pdev->dev, irq, fsl_asrc_isr, 0,
dev_name(&pdev->dev), asrc_priv);
@@ -920,14 +1106,52 @@ static int fsl_asrc_probe(struct platform_device *pdev)
}
}
+ num_domains = of_count_phandle_with_args(np, "power-domains",
+ "#power-domain-cells");
+ for (i = 0; i < num_domains; i++) {
+ struct device *pd_dev;
+ struct device_link *link;
+
+ pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, i);
+ if (IS_ERR(pd_dev))
+ return PTR_ERR(pd_dev);
+
+ link = device_link_add(&pdev->dev, pd_dev,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME |
+ DL_FLAG_RPM_ACTIVE);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ }
+
if (of_device_is_compatible(np, "fsl,imx35-asrc")) {
asrc_priv->channel_bits = 3;
- clk_map[IN] = input_clk_map_imx35;
- clk_map[OUT] = output_clk_map_imx35;
- } else {
+ strncpy(asrc_priv->name, "mxc_asrc",
+ sizeof(asrc_priv->name) - 1);
+ asrc_priv->clk_map[IN] = input_clk_map_imx35;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx35;
+ asrc_priv->dma_type = DMA_SDMA;
+ } else if (of_device_is_compatible(np, "fsl,imx53-asrc")) {
+ asrc_priv->channel_bits = 4;
+ strncpy(asrc_priv->name, "mxc_asrc",
+ sizeof(asrc_priv->name) - 1);
+ asrc_priv->clk_map[IN] = input_clk_map_imx53;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx53;
+ asrc_priv->dma_type = DMA_SDMA;
+ } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc0")) {
asrc_priv->channel_bits = 4;
- clk_map[IN] = input_clk_map_imx53;
- clk_map[OUT] = output_clk_map_imx53;
+ strncpy(asrc_priv->name, "mxc_asrc",
+ sizeof(asrc_priv->name) - 1);
+ asrc_priv->clk_map[IN] = input_clk_map_imx8_0;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx8_0;
+ asrc_priv->dma_type = DMA_EDMA;
+ } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc1")) {
+ asrc_priv->channel_bits = 4;
+ strncpy(asrc_priv->name, "mxc_asrc1",
+ sizeof(asrc_priv->name) - 1);
+ asrc_priv->clk_map[IN] = input_clk_map_imx8_1;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx8_1;
+ asrc_priv->dma_type = DMA_EDMA;
}
ret = fsl_asrc_init(asrc_priv);
@@ -961,6 +1185,8 @@ static int fsl_asrc_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
spin_lock_init(&asrc_priv->lock);
+ regcache_cache_only(asrc_priv->regmap, true);
+
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component,
&fsl_asrc_dai, 1);
if (ret) {
@@ -968,6 +1194,12 @@ static int fsl_asrc_probe(struct platform_device *pdev)
return ret;
}
+ ret = fsl_asrc_m2m_init(asrc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init m2m device %d\n", ret);
+ return ret;
+ }
+
return 0;
}
@@ -976,6 +1208,9 @@ static int fsl_asrc_runtime_resume(struct device *dev)
{
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
int i, ret;
+ u32 asrctr;
+ u32 reg;
+ int retry = 50;
ret = clk_prepare_enable(asrc_priv->mem_clk);
if (ret)
@@ -994,6 +1229,34 @@ static int fsl_asrc_runtime_resume(struct device *dev)
goto disable_asrck_clk;
}
+ /* Stop all pairs provisionally */
+ regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr);
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_ALL_MASK, 0);
+
+ /* Restore all registers */
+ regcache_cache_only(asrc_priv->regmap, false);
+ regcache_mark_dirty(asrc_priv->regmap);
+ regcache_sync(asrc_priv->regmap);
+
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCFG,
+ ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK |
+ ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg);
+
+ /* Restart enabled pairs */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_ALL_MASK, asrctr);
+
+ /* Wait for status of initialization */
+ do {
+ udelay(5);
+ regmap_read(asrc_priv->regmap, REG_ASRCFG, &reg);
+ reg = (reg >> ASRCFG_INIRQi_SHIFT(0)) & 0x7;
+ } while (!(reg == ((asrctr & 0xE) >> 1)) && --retry);
+
+ if (retry == 0)
+ dev_warn(dev, "initialization is not finished\n");
+
return 0;
disable_asrck_clk:
@@ -1013,6 +1276,11 @@ static int fsl_asrc_runtime_suspend(struct device *dev)
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
int i;
+ regmap_read(asrc_priv->regmap, REG_ASRCFG,
+ &asrc_priv->regcache_cfg);
+
+ regcache_cache_only(asrc_priv->regmap, true);
+
for (i = 0; i < ASRC_CLK_MAX_NUM; i++)
clk_disable_unprepare(asrc_priv->asrck_clk[i]);
if (!IS_ERR(asrc_priv->spba_clk))
@@ -1028,39 +1296,25 @@ static int fsl_asrc_runtime_suspend(struct device *dev)
static int fsl_asrc_suspend(struct device *dev)
{
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+ int ret;
- regmap_read(asrc_priv->regmap, REG_ASRCFG,
- &asrc_priv->regcache_cfg);
+ fsl_asrc_m2m_suspend(asrc_priv);
- regcache_cache_only(asrc_priv->regmap, true);
- regcache_mark_dirty(asrc_priv->regmap);
+ ret = pm_runtime_force_suspend(dev);
- return 0;
+ return ret;
}
static int fsl_asrc_resume(struct device *dev)
{
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
- u32 asrctr;
+ int ret;
- /* Stop all pairs provisionally */
- regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr);
- regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
- ASRCTR_ASRCEi_ALL_MASK, 0);
-
- /* Restore all registers */
- regcache_cache_only(asrc_priv->regmap, false);
- regcache_sync(asrc_priv->regmap);
-
- regmap_update_bits(asrc_priv->regmap, REG_ASRCFG,
- ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK |
- ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg);
+ ret = pm_runtime_force_resume(dev);
- /* Restart enabled pairs */
- regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
- ASRCTR_ASRCEi_ALL_MASK, asrctr);
+ fsl_asrc_m2m_resume(asrc_priv);
- return 0;
+ return ret;
}
#endif /* CONFIG_PM_SLEEP */
@@ -1072,12 +1326,15 @@ static const struct dev_pm_ops fsl_asrc_pm = {
static const struct of_device_id fsl_asrc_ids[] = {
{ .compatible = "fsl,imx35-asrc", },
{ .compatible = "fsl,imx53-asrc", },
+ { .compatible = "fsl,imx8qm-asrc0", },
+ { .compatible = "fsl,imx8qm-asrc1", },
{}
};
MODULE_DEVICE_TABLE(of, fsl_asrc_ids);
static struct platform_driver fsl_asrc_driver = {
.probe = fsl_asrc_probe,
+ .remove = fsl_asrc_m2m_remove,
.driver = {
.name = "fsl-asrc",
.of_match_table = fsl_asrc_ids,
diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h
index c60075112570..c3c17769aa7e 100644
--- a/sound/soc/fsl/fsl_asrc.h
+++ b/sound/soc/fsl/fsl_asrc.h
@@ -2,7 +2,7 @@
/*
* fsl_asrc.h - Freescale ASRC ALSA SoC header file
*
- * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
*
* Author: Nicolin Chen <nicoleotsuka@gmail.com>
*/
@@ -10,6 +10,12 @@
#ifndef _FSL_ASRC_H
#define _FSL_ASRC_H
+#include <sound/asound.h>
+#include <uapi/linux/mxc_asrc.h>
+#include <linux/miscdevice.h>
+
+#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1)
+
#define IN 0
#define OUT 1
@@ -20,7 +26,8 @@
#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 ASRC_OUTPUT_LAST_SAMPLE_MAX 32
+#define ASRC_OUTPUT_LAST_SAMPLE 4
#define IDEAL_RATIO_RATE 1000000
@@ -283,106 +290,15 @@
#define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT)
#define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT)
-
-enum asrc_pair_index {
- ASRC_INVALID_PAIR = -1,
- ASRC_PAIR_A = 0,
- ASRC_PAIR_B = 1,
- ASRC_PAIR_C = 2,
-};
-
-#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1)
-
-enum asrc_inclk {
- INCLK_NONE = 0x03,
- INCLK_ESAI_RX = 0x00,
- INCLK_SSI1_RX = 0x01,
- INCLK_SSI2_RX = 0x02,
- INCLK_SSI3_RX = 0x07,
- INCLK_SPDIF_RX = 0x04,
- INCLK_MLB_CLK = 0x05,
- INCLK_PAD = 0x06,
- INCLK_ESAI_TX = 0x08,
- INCLK_SSI1_TX = 0x09,
- INCLK_SSI2_TX = 0x0a,
- INCLK_SSI3_TX = 0x0b,
- INCLK_SPDIF_TX = 0x0c,
- INCLK_ASRCK1_CLK = 0x0f,
-};
-
-enum asrc_outclk {
- OUTCLK_NONE = 0x03,
- OUTCLK_ESAI_TX = 0x00,
- OUTCLK_SSI1_TX = 0x01,
- OUTCLK_SSI2_TX = 0x02,
- OUTCLK_SSI3_TX = 0x07,
- OUTCLK_SPDIF_TX = 0x04,
- OUTCLK_MLB_CLK = 0x05,
- OUTCLK_PAD = 0x06,
- OUTCLK_ESAI_RX = 0x08,
- OUTCLK_SSI1_RX = 0x09,
- OUTCLK_SSI2_RX = 0x0a,
- OUTCLK_SSI3_RX = 0x0b,
- OUTCLK_SPDIF_RX = 0x0c,
- OUTCLK_ASRCK1_CLK = 0x0f,
-};
-
#define ASRC_CLK_MAX_NUM 16
enum asrc_word_width {
ASRC_WIDTH_24_BIT = 0,
ASRC_WIDTH_16_BIT = 1,
- ASRC_WIDTH_8_BIT = 2,
-};
-
-struct asrc_config {
- enum asrc_pair_index pair;
- unsigned int channel_num;
- unsigned int buffer_num;
- unsigned int dma_buffer_size;
- unsigned int input_sample_rate;
- unsigned int output_sample_rate;
- enum asrc_word_width input_word_width;
- enum asrc_word_width output_word_width;
- enum asrc_inclk inclk;
- enum asrc_outclk outclk;
-};
-
-struct asrc_req {
- unsigned int chn_num;
- enum asrc_pair_index index;
-};
-
-struct asrc_querybuf {
- unsigned int buffer_index;
- unsigned int input_length;
- unsigned int output_length;
- unsigned long input_offset;
- unsigned long output_offset;
-};
-
-struct asrc_convert_buffer {
- void *input_buffer_vaddr;
- void *output_buffer_vaddr;
- unsigned int input_buffer_length;
- unsigned int output_buffer_length;
-};
-
-struct asrc_status_flags {
- enum asrc_pair_index index;
- unsigned int overload_error;
-};
-
-enum asrc_error_status {
- ASRC_TASK_Q_OVERLOAD = 0x01,
- ASRC_OUTPUT_TASK_OVERLOAD = 0x02,
- ASRC_INPUT_TASK_OVERLOAD = 0x04,
- ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08,
- ASRC_INPUT_BUFFER_UNDERRUN = 0x10,
+ ASRC_WIDTH_8_BIT = 2,
};
struct dma_block {
- dma_addr_t dma_paddr;
void *dma_vaddr;
unsigned int length;
};
@@ -413,6 +329,7 @@ struct fsl_asrc_pair {
struct dma_chan *dma_chan[2];
struct imx_dma_data dma_data;
unsigned int pos;
+ unsigned int pair_streams;
void *private;
};
@@ -433,6 +350,7 @@ struct fsl_asrc_pair {
* @pair: pair pointers
* @channel_bits: width of ASRCNCR register for each pair
* @channel_avail: non-occupied channel numbers
+ * @pair_streams:indicat which substream is running
* @asrc_rate: default sample rate for ASoC Back-Ends
* @asrc_width: default sample width for ASoC Back-Ends
* @regcache_cfg: store register value of REG_ASRCFG
@@ -447,19 +365,29 @@ struct fsl_asrc {
struct clk *ipg_clk;
struct clk *spba_clk;
struct clk *asrck_clk[ASRC_CLK_MAX_NUM];
+ unsigned char *clk_map[2];
spinlock_t lock;
struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM];
+ struct miscdevice asrc_miscdev;
unsigned int channel_bits;
unsigned int channel_avail;
int asrc_rate;
int asrc_width;
+ int dma_type; /* 0 is sdma, 1 is edma */
u32 regcache_cfg;
+ char name[20];
};
+#define DMA_SDMA 0
+#define DMA_EDMA 1
+
#define DRV_NAME "fsl-asrc-dai"
extern struct snd_soc_component_driver fsl_asrc_component;
struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir);
+int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair);
+void fsl_asrc_release_pair(struct fsl_asrc_pair *pair);
+
#endif /* _FSL_ASRC_H */
diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c
index 01052a0808b0..9ec527e03fb6 100644
--- a/sound/soc/fsl/fsl_asrc_dma.c
+++ b/sound/soc/fsl/fsl_asrc_dma.c
@@ -16,16 +16,14 @@
#define FSL_ASRC_DMABUF_SIZE (256 * 1024)
-static const struct snd_pcm_hardware snd_imx_hardware = {
+static struct snd_pcm_hardware snd_imx_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_PAUSE |
- SNDRV_PCM_INFO_RESUME,
+ SNDRV_PCM_INFO_MMAP_VALID,
.buffer_bytes_max = FSL_ASRC_DMABUF_SIZE,
.period_bytes_min = 128,
- .period_bytes_max = 65535, /* Limited by SDMA engine */
+ .period_bytes_max = 65532, /* Limited by SDMA engine */
.periods_min = 2,
.periods_max = 255,
.fifo_size = 0,
@@ -148,6 +146,7 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream,
struct device *dev_be;
u8 dir = tx ? OUT : IN;
dma_cap_mask_t mask;
+ enum sdma_peripheral_type be_peripheral_type;
int ret;
/* Fetch the Back-End dma_data from DPCM */
@@ -201,19 +200,43 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream,
/* Get DMA request of Back-End */
tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx");
- tmp_data = tmp_chan->private;
- pair->dma_data.dma_request = tmp_data->dma_request;
- dma_release_channel(tmp_chan);
+ if (tmp_chan) {
+ tmp_data = tmp_chan->private;
+ if (tmp_data) {
+ pair->dma_data.dma_request = tmp_data->dma_request;
+ be_peripheral_type = tmp_data->peripheral_type;
+ if (tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL)
+ pair->dma_data.dst_dualfifo = true;
+ if (!tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL)
+ pair->dma_data.src_dualfifo = true;
+ }
+ dma_release_channel(tmp_chan);
+ }
/* Get DMA request of Front-End */
tmp_chan = fsl_asrc_get_dma_channel(pair, dir);
- tmp_data = tmp_chan->private;
- pair->dma_data.dma_request2 = tmp_data->dma_request;
- pair->dma_data.peripheral_type = tmp_data->peripheral_type;
- pair->dma_data.priority = tmp_data->priority;
- dma_release_channel(tmp_chan);
+ if (tmp_chan) {
+ tmp_data = tmp_chan->private;
+ if (tmp_data) {
+ 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);
+ }
+
+ /* For sdma DEV_TO_DEV, there is two dma request
+ * But for emda DEV_TO_DEV, there is only one dma request, which is
+ * from the BE.
+ */
+ if (pair->dma_data.dma_request2 != pair->dma_data.dma_request)
+ pair->dma_chan[dir] =
+ dma_request_channel(mask, filter, &pair->dma_data);
+ else
+ pair->dma_chan[dir] =
+ fsl_asrc_get_dma_channel(pair, dir);
- pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data);
if (!pair->dma_chan[dir]) {
dev_err(dev, "failed to request DMA channel for Back-End\n");
return -EINVAL;
@@ -224,6 +247,8 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream,
else
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ memset(&config_be, 0, sizeof(config_be));
+
config_be.direction = DMA_DEV_TO_DEV;
config_be.src_addr_width = buswidth;
config_be.src_maxburst = dma_params_be->maxburst;
@@ -276,6 +301,16 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream)
struct device *dev = component->dev;
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
struct fsl_asrc_pair *pair;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u8 dir = tx ? OUT : IN;
+ struct dma_slave_caps dma_caps;
+ struct dma_chan *tmp_chan;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+ int ret;
+ int i;
pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL);
if (!pair)
@@ -285,8 +320,78 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream)
runtime->private_data = pair;
- snd_pcm_hw_constraint_integer(substream->runtime,
- SNDRV_PCM_HW_PARAM_PERIODS);
+ 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;
+ }
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ fsl_asrc_request_pair(1, pair);
+
+ tmp_chan = fsl_asrc_get_dma_channel(pair, dir);
+ if (!tmp_chan) {
+ dev_err(dev, "can't get dma channel\n");
+ return -EINVAL;
+ }
+
+ ret = dma_get_slave_caps(tmp_chan, &dma_caps);
+ if (ret == 0) {
+ if (dma_caps.cmd_pause)
+ snd_imx_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME;
+ if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT)
+ snd_imx_hardware.info |= SNDRV_PCM_INFO_BATCH;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ addr_widths = dma_caps.dst_addr_widths;
+ else
+ addr_widths = dma_caps.src_addr_widths;
+ }
+
+ /*
+ * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep
+ * hw.formats set to 0, meaning no restrictions are in place.
+ * In this case it's the responsibility of the DAI driver to
+ * provide the supported format information.
+ */
+ if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK))
+ /*
+ * Prepare formats mask for valid/allowed sample types. If the
+ * dma does not have support for the given physical word size,
+ * it needs to be masked out so user space can not use the
+ * format which produces corrupted audio.
+ * In case the dma driver does not implement the slave_caps the
+ * default assumption is that it supports 1, 2 and 4 bytes
+ * widths.
+ */
+ for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) {
+ int bits = snd_pcm_format_physical_width(i);
+
+ /*
+ * Enable only samples with DMA supported physical
+ * widths
+ */
+ switch (bits) {
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ case 64:
+ if (addr_widths & (1 << (bits / 8)))
+ snd_imx_hardware.formats |= (1LL << i);
+ break;
+ default:
+ /* Unsupported types */
+ break;
+ }
+ }
+
+ if (tmp_chan)
+ dma_release_channel(tmp_chan);
+ fsl_asrc_release_pair(pair);
+
snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
return 0;
diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c
new file mode 100644
index 000000000000..ea4f863caee1
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc_m2m.c
@@ -0,0 +1,1046 @@
+/*
+ * Freescale ASRC Memory to Memory (M2M) driver
+ *
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#define FSL_ASRC_INPUTFIFO_WML 0x4
+#define FSL_ASRC_OUTPUTFIFO_WML 0x2
+
+#define DIR_STR(dir) dir == IN ? "in" : "out"
+
+struct fsl_asrc_m2m {
+ struct fsl_asrc_pair *pair;
+ struct completion complete[2];
+ struct dma_block dma_block[2];
+ unsigned int pair_hold;
+ unsigned int asrc_active;
+ unsigned int sg_nodes[2];
+ struct scatterlist sg[2][4];
+
+ snd_pcm_format_t word_format[2];
+ unsigned int rate[2];
+ unsigned int last_period_size;
+ u32 watermark[2];
+ spinlock_t lock;
+};
+
+static void fsl_asrc_get_status(struct fsl_asrc_pair *pair,
+ struct asrc_status_flags *flags)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ unsigned long lock_flags;
+
+ spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+
+ flags->overload_error = pair->error;
+
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+}
+
+#define ASRC_xPUT_DMA_CALLBACK(dir) \
+ ((dir == IN) ? fsl_asrc_input_dma_callback : fsl_asrc_output_dma_callback)
+
+static void fsl_asrc_input_dma_callback(void *data)
+{
+ struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data;
+ struct fsl_asrc_m2m *m2m = pair->private;
+
+ complete(&m2m->complete[IN]);
+}
+
+static void fsl_asrc_output_dma_callback(void *data)
+{
+ struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data;
+ struct fsl_asrc_m2m *m2m = pair->private;
+
+ complete(&m2m->complete[OUT]);
+}
+
+static unsigned int fsl_asrc_get_output_FIFO_size(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ u32 val;
+
+ regmap_read(asrc_priv->regmap, REG_ASRFST(index), &val);
+
+ val &= ASRFSTi_OUTPUT_FIFO_MASK;
+
+ return val >> ASRFSTi_OUTPUT_FIFO_SHIFT;
+}
+
+static void fsl_asrc_read_last_FIFO(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ struct dma_block *output = &m2m->dma_block[OUT];
+ u32 i, reg, size, t_size = 0, width;
+ u32 *reg32 = NULL;
+ u16 *reg16 = NULL;
+ u8 *reg24 = NULL;
+
+ width = snd_pcm_format_physical_width(m2m->word_format[OUT]);
+
+ if (width == 32)
+ reg32 = output->dma_vaddr + output->length;
+ else if (width == 16)
+ reg16 = output->dma_vaddr + output->length;
+ else
+ reg24 = output->dma_vaddr + output->length;
+
+retry:
+ size = fsl_asrc_get_output_FIFO_size(pair);
+
+ for (i = 0; i < size * pair->channels; i++) {
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ if (reg32) {
+ *(reg32) = reg;
+ reg32++;
+ } else if (reg16) {
+ *(reg16) = (u16)reg;
+ reg16++;
+ } else {
+ *reg24++ = (u8)reg;
+ *reg24++ = (u8)(reg >> 8);
+ *reg24++ = (u8)(reg >> 16);
+ }
+ }
+ t_size += size;
+
+ if (size)
+ goto retry;
+
+ if (t_size > m2m->last_period_size)
+ t_size = m2m->last_period_size;
+
+ if (reg32)
+ output->length += t_size * pair->channels * 4;
+ else if (reg16)
+ output->length += t_size * pair->channels * 2;
+ else
+ output->length += t_size * pair->channels * 3;
+}
+
+static int fsl_allocate_dma_buf(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct dma_block *input = &m2m->dma_block[IN];
+ struct dma_block *output = &m2m->dma_block[OUT];
+ enum asrc_pair_index index = pair->index;
+
+ input->dma_vaddr = kzalloc(input->length, GFP_KERNEL);
+ if (!input->dma_vaddr) {
+ pair_err("failed to allocate input DMA buffer\n");
+ return -ENOMEM;
+ }
+
+ output->dma_vaddr = kzalloc(output->length, GFP_KERNEL);
+ if (!output->dma_vaddr) {
+ pair_err("failed to allocate output DMA buffer\n");
+ goto exit;
+ }
+
+ return 0;
+
+exit:
+ kfree(input->dma_vaddr);
+
+ return -ENOMEM;
+}
+
+static int fsl_asrc_dmaconfig(struct fsl_asrc_pair *pair, struct dma_chan *chan,
+ u32 dma_addr, void *buf_addr, u32 buf_len,
+ bool dir, snd_pcm_format_t word_format)
+{
+ struct dma_async_tx_descriptor *desc = pair->desc[dir];
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ unsigned int sg_nent = m2m->sg_nodes[dir];
+ enum asrc_pair_index index = pair->index;
+ struct scatterlist *sg = m2m->sg[dir];
+ struct dma_slave_config slave_config;
+ enum dma_slave_buswidth buswidth;
+ int ret, i;
+
+ switch (snd_pcm_format_physical_width(word_format)) {
+ case 8:
+ buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ break;
+ case 16:
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case 24:
+ buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES;
+ break;
+ case 32:
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ default:
+ pair_err("invalid word width\n");
+ return -EINVAL;
+ }
+
+ if (dir == IN) {
+ slave_config.direction = DMA_MEM_TO_DEV;
+ slave_config.dst_addr = dma_addr;
+ slave_config.dst_addr_width = buswidth;
+ if (asrc_priv->dma_type == DMA_SDMA)
+ slave_config.dst_maxburst =
+ m2m->watermark[IN] * pair->channels;
+ else
+ slave_config.dst_maxburst = 1;
+ } else {
+ slave_config.direction = DMA_DEV_TO_MEM;
+ slave_config.src_addr = dma_addr;
+ slave_config.src_addr_width = buswidth;
+ if (asrc_priv->dma_type == DMA_SDMA)
+ slave_config.src_maxburst =
+ m2m->watermark[OUT] * pair->channels;
+ else
+ slave_config.src_maxburst = 1;
+ }
+
+ ret = dmaengine_slave_config(chan, &slave_config);
+ if (ret) {
+ pair_err("failed to config dmaengine for %sput task: %d\n",
+ DIR_STR(dir), ret);
+ return -EINVAL;
+ }
+
+ sg_init_table(sg, sg_nent);
+ switch (sg_nent) {
+ case 1:
+ sg_init_one(sg, buf_addr, buf_len);
+ break;
+ case 2:
+ case 3:
+ case 4:
+ for (i = 0; i < (sg_nent - 1); i++)
+ sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE,
+ ASRC_MAX_BUFFER_SIZE);
+
+ sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE,
+ buf_len - ASRC_MAX_BUFFER_SIZE * i);
+ break;
+ default:
+ pair_err("invalid input DMA nodes number: %d\n", sg_nent);
+ return -EINVAL;
+ }
+
+ ret = dma_map_sg(&asrc_priv->pdev->dev, sg, sg_nent, slave_config.direction);
+ if (ret != sg_nent) {
+ pair_err("failed to map DMA sg for %sput task\n", DIR_STR(dir));
+ return -EINVAL;
+ }
+
+ desc = dmaengine_prep_slave_sg(chan, sg, sg_nent,
+ slave_config.direction, DMA_PREP_INTERRUPT);
+ if (!desc) {
+ pair_err("failed to prepare dmaengine for %sput task\n",
+ DIR_STR(dir));
+ return -EINVAL;
+ }
+
+ pair->desc[dir] = desc;
+ pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir);
+
+ desc->callback = ASRC_xPUT_DMA_CALLBACK(dir);
+ desc->callback_param = pair;
+
+ return 0;
+}
+
+static int fsl_asrc_prepare_io_buffer(struct fsl_asrc_pair *pair,
+ struct asrc_convert_buffer *pbuf, bool dir)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ unsigned int *dma_len = &m2m->dma_block[dir].length;
+ void *dma_vaddr = m2m->dma_block[dir].dma_vaddr;
+ struct dma_chan *dma_chan = pair->dma_chan[dir];
+ unsigned int buf_len, wm = m2m->watermark[dir];
+ unsigned int *sg_nodes = &m2m->sg_nodes[dir];
+ unsigned int last_period_size = m2m->last_period_size;
+ enum asrc_pair_index index = pair->index;
+ u32 word_size, fifo_addr;
+ void __user *buf_vaddr;
+
+ /* Clean the DMA buffer */
+ memset(dma_vaddr, 0, ASRC_DMA_BUFFER_SIZE);
+
+ if (dir == IN) {
+ buf_vaddr = (void __user *)pbuf->input_buffer_vaddr;
+ buf_len = pbuf->input_buffer_length;
+ } else {
+ buf_vaddr = (void __user *)pbuf->output_buffer_vaddr;
+ buf_len = pbuf->output_buffer_length;
+ }
+
+ word_size = snd_pcm_format_physical_width(m2m->word_format[dir]) / 8;
+
+ if (buf_len < word_size * pair->channels * wm ||
+ buf_len > ASRC_DMA_BUFFER_SIZE ||
+ (dir == OUT && buf_len < word_size * pair->channels * last_period_size)) {
+ pair_err("%sput buffer size is error: [%d]\n",
+ DIR_STR(dir), buf_len);
+ return -EINVAL;
+ }
+
+ /* Copy origin data into input buffer */
+ if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len))
+ return -EFAULT;
+
+ *dma_len = buf_len;
+ if (dir == OUT) {
+ *dma_len -= last_period_size * word_size * pair->channels;
+ *dma_len = *dma_len / (word_size * pair->channels) *
+ (word_size * pair->channels);
+ if (asrc_priv->dma_type == DMA_EDMA)
+ *dma_len = *dma_len / (word_size * pair->channels * m2m->watermark[OUT])
+ * (word_size * pair->channels * m2m->watermark[OUT]);
+ }
+
+ *sg_nodes = *dma_len / ASRC_MAX_BUFFER_SIZE + 1;
+
+ fifo_addr = asrc_priv->paddr + REG_ASRDx(dir, index);
+
+ return fsl_asrc_dmaconfig(pair, dma_chan, fifo_addr, dma_vaddr,
+ *dma_len, dir, m2m->word_format[dir]);
+}
+
+static int fsl_asrc_prepare_buffer(struct fsl_asrc_pair *pair,
+ struct asrc_convert_buffer *pbuf)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ int ret;
+
+ ret = fsl_asrc_prepare_io_buffer(pair, pbuf, IN);
+ if (ret) {
+ pair_err("failed to prepare input buffer: %d\n", ret);
+ return ret;
+ }
+
+ ret = fsl_asrc_prepare_io_buffer(pair, pbuf, OUT);
+ if (ret) {
+ pair_err("failed to prepare output buffer: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int fsl_asrc_process_buffer_pre(struct completion *complete,
+ enum asrc_pair_index index, bool dir)
+{
+ if (!wait_for_completion_interruptible_timeout(complete, 10 * HZ)) {
+ pr_err("%sput DMA task timeout\n", DIR_STR(dir));
+ return -ETIME;
+ } else if (signal_pending(current)) {
+ pr_err("%sput task forcibly aborted\n", DIR_STR(dir));
+ return -ERESTARTSYS;
+ }
+
+ return 0;
+}
+
+#define mxc_asrc_dma_umap(dev, m2m) \
+ do { \
+ dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \
+ DMA_MEM_TO_DEV); \
+ dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \
+ DMA_DEV_TO_MEM); \
+ } while (0)
+
+int fsl_asrc_process_buffer(struct fsl_asrc_pair *pair,
+ struct asrc_convert_buffer *pbuf)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index = pair->index;
+ unsigned long lock_flags;
+ int ret;
+
+ /* Check input task first */
+ ret = fsl_asrc_process_buffer_pre(&m2m->complete[IN], index, IN);
+ if (ret) {
+ mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m);
+ return ret;
+ }
+
+ /* ...then output task*/
+ ret = fsl_asrc_process_buffer_pre(&m2m->complete[OUT], index, OUT);
+ if (ret) {
+ mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m);
+ return ret;
+ }
+
+ mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m);
+
+ /* Fetch the remaining data */
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ if (!m2m->pair_hold) {
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+ return -EFAULT;
+ }
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ fsl_asrc_read_last_FIFO(pair);
+
+ /* Update final lengths after getting last FIFO */
+ pbuf->input_buffer_length = m2m->dma_block[IN].length;
+ pbuf->output_buffer_length = m2m->dma_block[OUT].length;
+
+ if (copy_to_user((void __user *)pbuf->output_buffer_vaddr,
+ m2m->dma_block[OUT].dma_vaddr,
+ m2m->dma_block[OUT].length))
+ return -EFAULT;
+
+ return 0;
+}
+
+#ifdef ASRC_POLLING_WITHOUT_DMA
+/* THIS FUNCTION ONLY EXISTS FOR DEBUGGING AND ONLY SUPPORTS TWO CHANNELS */
+static void fsl_asrc_polling_debug(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index = pair->index;
+ u32 *in24 = m2m->dma_block[IN].dma_vaddr;
+ u32 dma_len = m2m->dma_block[IN].length / (pair->channels * 4);
+ u32 *reg24 = m2m->dma_block[OUT].dma_vaddr;
+ u32 size, i, j, t_size, reg;
+
+ t_size = 0;
+
+ for (i = 0; i < dma_len; ) {
+ for (j = 0; j < 2; j++) {
+ regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24);
+ in24++;
+ regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24);
+ in24++;
+ i++;
+ }
+ udelay(50);
+ udelay(50 * m2m->rate[OUT] / m2m->rate[IN]);
+
+ size = fsl_asrc_get_output_FIFO_size(index);
+ for (j = 0; j < size; j++) {
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ *(reg24) = reg;
+ reg24++;
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ *(reg24) = reg;
+ reg24++;
+ }
+ t_size += size;
+ }
+
+ mdelay(1);
+ size = fsl_asrc_get_output_FIFO_size(index);
+ for (j = 0; j < size; j++) {
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ *(reg24) = reg;
+ reg24++;
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ *(reg24) = reg;
+ reg24++;
+ }
+ t_size += size;
+
+ m2m->dma_block[OUT].length = t_size * pair->channels * 4;
+
+ complete(&m2m->complete[OUT]);
+ complete(&m2m->complete[IN]);
+}
+#else
+static void fsl_asrc_submit_dma(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index = pair->index;
+ u32 size = fsl_asrc_get_output_FIFO_size(pair);
+ int i;
+
+ /* Read all data in OUTPUT FIFO */
+ while (size) {
+ u32 val;
+ for (i = 0; i < size * pair->channels; i++)
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &val);
+ /* Fetch the data every 100us */
+ udelay(100);
+
+ size = fsl_asrc_get_output_FIFO_size(pair);
+ }
+
+ /* Submit DMA request */
+ dmaengine_submit(pair->desc[IN]);
+ dma_async_issue_pending(pair->desc[IN]->chan);
+
+ dmaengine_submit(pair->desc[OUT]);
+ dma_async_issue_pending(pair->desc[OUT]->chan);
+
+ /*
+ * Clear DMA request during the stall state of ASRC:
+ * During STALL state, the remaining in input fifo would never be
+ * smaller than the input threshold while the output fifo would not
+ * be bigger than output one. Thus the DMA request would be cleared.
+ */
+ fsl_asrc_set_watermarks(pair, ASRC_FIFO_THRESHOLD_MIN,
+ ASRC_FIFO_THRESHOLD_MAX);
+
+ /* Update the real input threshold to raise DMA request */
+ fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]);
+}
+#endif /* ASRC_POLLING_WITHOUT_DMA */
+
+static long fsl_asrc_ioctl_req_pair(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct device *dev = &asrc_priv->pdev->dev;
+ struct asrc_req req;
+ unsigned long lock_flags;
+ long ret;
+
+ ret = copy_from_user(&req, user, sizeof(req));
+ if (ret) {
+ dev_err(dev, "failed to get req from user space: %ld\n", ret);
+ return ret;
+ }
+
+ ret = fsl_asrc_request_pair(req.chn_num, pair);
+ if (ret) {
+ dev_err(dev, "failed to request pair: %ld\n", ret);
+ return ret;
+ }
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ m2m->pair_hold = 1;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+ pair->channels = req.chn_num;
+
+ req.index = pair->index;
+ req.supported_in_format = FSL_ASRC_FORMATS_TX;
+ req.supported_out_format = FSL_ASRC_FORMATS_RX;
+ if (asrc_priv->dma_type == DMA_EDMA) {
+ req.supported_in_format &= ~SNDRV_PCM_FMTBIT_S24_3LE;
+ req.supported_out_format &= ~SNDRV_PCM_FMTBIT_S24_3LE;
+ }
+
+ ret = copy_to_user(user, &req, sizeof(req));
+ if (ret) {
+ dev_err(dev, "failed to send req to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_config_pair(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct device *dev = &asrc_priv->pdev->dev;
+ struct asrc_config config;
+ enum asrc_pair_index index;
+ long ret;
+
+ ret = copy_from_user(&config, user, sizeof(config));
+ if (ret) {
+ dev_err(dev, "failed to get config from user space: %ld\n", ret);
+ return ret;
+ }
+
+ index = config.pair;
+
+ pair->config = &config;
+ ret = fsl_asrc_config_pair(pair, false, false);
+ if (ret) {
+ pair_err("failed to config pair: %ld\n", ret);
+ return ret;
+ }
+
+ m2m->watermark[IN] = FSL_ASRC_INPUTFIFO_WML;
+ m2m->watermark[OUT] = FSL_ASRC_OUTPUTFIFO_WML;
+
+ fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]);
+
+ m2m->dma_block[IN].length = ASRC_DMA_BUFFER_SIZE;
+ m2m->dma_block[OUT].length = ASRC_DMA_BUFFER_SIZE;
+
+ m2m->word_format[IN] = config.input_format;
+ m2m->word_format[OUT] = config.output_format;
+
+ m2m->rate[IN] = config.input_sample_rate;
+ m2m->rate[OUT] = config.output_sample_rate;
+
+ m2m->last_period_size = ASRC_OUTPUT_LAST_SAMPLE;
+
+ ret = fsl_allocate_dma_buf(pair);
+ if (ret) {
+ pair_err("failed to allocate DMA buffer: %ld\n", ret);
+ return ret;
+ }
+
+ /* Request DMA channel for both input and output */
+ pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN);
+ if (pair->dma_chan[IN] == NULL) {
+ pair_err("failed to request input task DMA channel\n");
+ return -EBUSY;
+ }
+
+ pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT);
+ if (pair->dma_chan[OUT] == NULL) {
+ pair_err("failed to request output task DMA channel\n");
+ return -EBUSY;
+ }
+
+ ret = copy_to_user(user, &config, sizeof(config));
+ if (ret) {
+ pair_err("failed to send config to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_release_pair(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index;
+ unsigned long lock_flags;
+ long ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ pair_err("failed to get index from user space: %ld\n", ret);
+ return ret;
+ }
+
+ /* index might be not valid due to some application failure. */
+ if (index < 0)
+ return -EINVAL;
+
+ m2m->asrc_active = 0;
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ m2m->pair_hold = 0;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ if (pair->dma_chan[IN])
+ dma_release_channel(pair->dma_chan[IN]);
+ if (pair->dma_chan[OUT])
+ dma_release_channel(pair->dma_chan[OUT]);
+ kfree(m2m->dma_block[IN].dma_vaddr);
+ kfree(m2m->dma_block[OUT].dma_vaddr);
+ fsl_asrc_release_pair(pair);
+
+ return 0;
+}
+
+static long fsl_asrc_calc_last_period_size(struct fsl_asrc_pair *pair,
+ struct asrc_convert_buffer *pbuf)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ unsigned int out_length;
+ unsigned int in_width, out_width;
+ unsigned int channels = pair->channels;
+ unsigned int in_samples, out_samples;
+ unsigned int last_period_size;
+ unsigned int remain;
+
+ in_width = snd_pcm_format_physical_width(m2m->word_format[IN]) / 8;
+ out_width = snd_pcm_format_physical_width(m2m->word_format[OUT]) / 8;
+
+ in_samples = pbuf->input_buffer_length / (in_width * channels);
+
+ out_samples = (m2m->rate[OUT] * in_samples / m2m->rate[IN]);
+
+ out_length = out_samples * out_width * channels;
+
+ last_period_size = pbuf->output_buffer_length / (out_width * channels)
+ - out_samples;
+
+ m2m->last_period_size = last_period_size + 1 + ASRC_OUTPUT_LAST_SAMPLE;
+
+ if (asrc_priv->dma_type == DMA_EDMA) {
+ remain = pbuf->output_buffer_length % (out_width * channels * m2m->watermark[OUT]);
+ if (remain)
+ m2m->last_period_size += remain / (out_width * channels);
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_convert(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ struct asrc_convert_buffer buf;
+ long ret;
+
+ ret = copy_from_user(&buf, user, sizeof(buf));
+ if (ret) {
+ pair_err("failed to get buf from user space: %ld\n", ret);
+ return ret;
+ }
+
+ fsl_asrc_calc_last_period_size(pair, &buf);
+
+ ret = fsl_asrc_prepare_buffer(pair, &buf);
+ if (ret) {
+ pair_err("failed to prepare buffer: %ld\n", ret);
+ return ret;
+ }
+
+ init_completion(&m2m->complete[IN]);
+ init_completion(&m2m->complete[OUT]);
+
+#ifdef ASRC_POLLING_WITHOUT_DMA
+ fsl_asrc_polling_debug(pair);
+#else
+ fsl_asrc_submit_dma(pair);
+#endif
+
+ ret = fsl_asrc_process_buffer(pair, &buf);
+ if (ret) {
+ if (ret != -ERESTARTSYS)
+ pair_err("failed to process buffer: %ld\n", ret);
+ return ret;
+ }
+
+ ret = copy_to_user(user, &buf, sizeof(buf));
+ if (ret) {
+ pair_err("failed to send buf to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_start_conv(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index;
+ long ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ pair_err("failed to get index from user space: %ld\n", ret);
+ return ret;
+ }
+
+ m2m->asrc_active = 1;
+ fsl_asrc_start_pair(pair);
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_stop_conv(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index;
+ long ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ pair_err("failed to get index from user space: %ld\n", ret);
+ return ret;
+ }
+
+ dmaengine_terminate_all(pair->dma_chan[IN]);
+ dmaengine_terminate_all(pair->dma_chan[OUT]);
+
+ fsl_asrc_stop_pair(pair);
+ m2m->asrc_active = 0;
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_status(struct fsl_asrc_pair *pair, void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ struct asrc_status_flags flags;
+ long ret;
+
+ ret = copy_from_user(&flags, user, sizeof(flags));
+ if (ret) {
+ pair_err("failed to get flags from user space: %ld\n", ret);
+ return ret;
+ }
+
+ fsl_asrc_get_status(pair, &flags);
+
+ ret = copy_to_user(user, &flags, sizeof(flags));
+ if (ret) {
+ pair_err("failed to send flags to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_flush(struct fsl_asrc_pair *pair, void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+
+ /* Release DMA and request again */
+ dma_release_channel(pair->dma_chan[IN]);
+ dma_release_channel(pair->dma_chan[OUT]);
+
+ pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN);
+ if (pair->dma_chan[IN] == NULL) {
+ pair_err("failed to request input task DMA channel\n");
+ return -EBUSY;
+ }
+
+ pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT);
+ if (pair->dma_chan[OUT] == NULL) {
+ pair_err("failed to request output task DMA channel\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct fsl_asrc_pair *pair = file->private_data;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ void __user *user = (void __user *)arg;
+ long ret = 0;
+
+ switch (cmd) {
+ case ASRC_REQ_PAIR:
+ ret = fsl_asrc_ioctl_req_pair(pair, user);
+ break;
+ case ASRC_CONFIG_PAIR:
+ ret = fsl_asrc_ioctl_config_pair(pair, user);
+ break;
+ case ASRC_RELEASE_PAIR:
+ ret = fsl_asrc_ioctl_release_pair(pair, user);
+ break;
+ case ASRC_CONVERT:
+ ret = fsl_asrc_ioctl_convert(pair, user);
+ break;
+ case ASRC_START_CONV:
+ ret = fsl_asrc_ioctl_start_conv(pair, user);
+ break;
+ case ASRC_STOP_CONV:
+ ret = fsl_asrc_ioctl_stop_conv(pair, user);
+ break;
+ case ASRC_STATUS:
+ ret = fsl_asrc_ioctl_status(pair, user);
+ break;
+ case ASRC_FLUSH:
+ ret = fsl_asrc_ioctl_flush(pair, user);
+ break;
+ default:
+ dev_err(&asrc_priv->pdev->dev, "invalid ioctl cmd!\n");
+ break;
+ }
+
+ return ret;
+}
+
+static int fsl_asrc_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *asrc_miscdev = file->private_data;
+ struct fsl_asrc *asrc_priv = dev_get_drvdata(asrc_miscdev->parent);
+ struct device *dev = &asrc_priv->pdev->dev;
+ struct fsl_asrc_pair *pair;
+ struct fsl_asrc_m2m *m2m;
+ int ret;
+
+ ret = signal_pending(current);
+ if (ret) {
+ dev_err(dev, "current process has a signal pending\n");
+ return ret;
+ }
+
+ pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL);
+ if (!pair) {
+ dev_err(dev, "failed to allocate pair\n");
+ return -ENOMEM;
+ }
+
+ m2m = kzalloc(sizeof(struct fsl_asrc_m2m), GFP_KERNEL);
+ if (!m2m) {
+ dev_err(dev, "failed to allocate m2m resource\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ pair->private = m2m;
+ pair->asrc_priv = asrc_priv;
+
+ spin_lock_init(&m2m->lock);
+
+ file->private_data = pair;
+
+ pm_runtime_get_sync(dev);
+
+ return 0;
+out:
+ kfree(pair);
+
+ return ret;
+}
+
+static int fsl_asrc_close(struct inode *inode, struct file *file)
+{
+ struct fsl_asrc_pair *pair = file->private_data;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct device *dev = &asrc_priv->pdev->dev;
+ unsigned long lock_flags;
+
+ if (m2m->asrc_active) {
+ m2m->asrc_active = 0;
+
+ dmaengine_terminate_all(pair->dma_chan[IN]);
+ dmaengine_terminate_all(pair->dma_chan[OUT]);
+
+ fsl_asrc_stop_pair(pair);
+ fsl_asrc_input_dma_callback((void *)pair);
+ fsl_asrc_output_dma_callback((void *)pair);
+ }
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ if (m2m->pair_hold) {
+ m2m->pair_hold = 0;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ if (pair->dma_chan[IN])
+ dma_release_channel(pair->dma_chan[IN]);
+ if (pair->dma_chan[OUT])
+ dma_release_channel(pair->dma_chan[OUT]);
+
+ kfree(m2m->dma_block[IN].dma_vaddr);
+ kfree(m2m->dma_block[OUT].dma_vaddr);
+
+ fsl_asrc_release_pair(pair);
+ } else
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+ kfree(m2m);
+ kfree(pair);
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+ file->private_data = NULL;
+
+ pm_runtime_put_sync(dev);
+
+ return 0;
+}
+
+static const struct file_operations asrc_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fsl_asrc_ioctl,
+ .open = fsl_asrc_open,
+ .release = fsl_asrc_close,
+};
+
+static int fsl_asrc_m2m_init(struct fsl_asrc *asrc_priv)
+{
+ struct device *dev = &asrc_priv->pdev->dev;
+ int ret;
+
+ asrc_priv->asrc_miscdev.fops = &asrc_fops;
+ asrc_priv->asrc_miscdev.parent = dev;
+ asrc_priv->asrc_miscdev.name = asrc_priv->name;
+ asrc_priv->asrc_miscdev.minor = MISC_DYNAMIC_MINOR;
+ ret = misc_register(&asrc_priv->asrc_miscdev);
+ if (ret) {
+ dev_err(dev, "failed to register char device %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_asrc_m2m_remove(struct platform_device *pdev)
+{
+ struct fsl_asrc *asrc_priv = dev_get_drvdata(&pdev->dev);
+
+ misc_deregister(&asrc_priv->asrc_miscdev);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void fsl_asrc_m2m_suspend(struct fsl_asrc *asrc_priv)
+{
+ struct fsl_asrc_pair *pair;
+ struct fsl_asrc_m2m *m2m;
+ unsigned long lock_flags;
+ int i;
+
+ for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) {
+ spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+ pair = asrc_priv->pair[i];
+ if (!pair || !pair->private) {
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+ continue;
+ }
+ m2m = pair->private;
+
+ if (!completion_done(&m2m->complete[IN])) {
+ if (pair->dma_chan[IN])
+ dmaengine_terminate_all(pair->dma_chan[IN]);
+ fsl_asrc_input_dma_callback((void *)pair);
+ }
+ if (!completion_done(&m2m->complete[OUT])) {
+ if (pair->dma_chan[OUT])
+ dmaengine_terminate_all(pair->dma_chan[OUT]);
+ fsl_asrc_output_dma_callback((void *)pair);
+ }
+
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+ }
+}
+
+static void fsl_asrc_m2m_resume(struct fsl_asrc *asrc_priv)
+{
+ struct fsl_asrc_pair *pair;
+ struct fsl_asrc_m2m *m2m;
+ unsigned long lock_flags;
+ enum asrc_pair_index index;
+ int i, j;
+
+ for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) {
+ spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+ pair = asrc_priv->pair[i];
+ if (!pair || !pair->private) {
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+ continue;
+ }
+ m2m = pair->private;
+ index = pair->index;
+
+ for (j = 0; j < pair->channels * 4; j++)
+ regmap_write(asrc_priv->regmap, REG_ASRDI(index), 0);
+
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+ }
+}
+#endif
diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c
index c7e4e9757dce..1525235a51d0 100644
--- a/sound/soc/fsl/fsl_audmix.c
+++ b/sound/soc/fsl/fsl_audmix.c
@@ -286,6 +286,7 @@ 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)
@@ -295,12 +296,16 @@ static int fsl_audmix_dai_trigger(struct snd_pcm_substream *substream, int 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;
diff --git a/sound/soc/fsl/fsl_audmix.h b/sound/soc/fsl/fsl_audmix.h
index 7812ffec45c5..479f05695d53 100644
--- a/sound/soc/fsl/fsl_audmix.h
+++ b/sound/soc/fsl/fsl_audmix.h
@@ -96,6 +96,7 @@ struct fsl_audmix {
struct platform_device *pdev;
struct regmap *regmap;
struct clk *ipg_clk;
+ spinlock_t lock; /* Protect tdms */
u8 tdms;
};
diff --git a/sound/soc/fsl/fsl_dai.c b/sound/soc/fsl/fsl_dai.c
new file mode 100644
index 000000000000..f70889939801
--- /dev/null
+++ b/sound/soc/fsl/fsl_dai.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Freescale Generic DAI driver for DSP
+//
+// Copyright 2019 NXP
+// Author: Daniel Baluta <daniel.baluta@nxp.com>
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+enum fsl_dai_type {
+ FSL_DAI_TYPE_NONE,
+ FSL_DAI_TYPE_SAI,
+ FSL_DAI_TYPE_ESAI,
+};
+
+#define FSL_DAI_ESAI_CLK_NUM 4
+static const char *esai_clks[FSL_DAI_ESAI_CLK_NUM] = {
+ "core",
+ "extal",
+ "fsys",
+ "spba",
+};
+
+#define FSL_DAI_SAI_CLK_NUM 5
+static const char *sai_clks[FSL_DAI_SAI_CLK_NUM] = {
+ "bus",
+ "mclk0",
+ "mclk1",
+ "mclk2",
+ "mclk3",
+};
+
+struct fsl_dai {
+ struct platform_device *pdev;
+
+ /* DAI clocks */
+ struct clk **clks;
+ const char **clk_names;
+ int num_clks;
+
+ /* Power Domain handling */
+ int num_domains;
+ struct device **pd_dev;
+ struct device_link **link;
+
+ /* DAIS */
+ struct snd_soc_dai_driver *dai_drv;
+ int num_drv;
+};
+
+static struct snd_soc_dai_driver fsl_esai_dai = {
+ .name = "esai0",
+};
+
+static struct snd_soc_dai_driver fsl_sai_dai = {
+ .name = "sai1",
+};
+
+static const struct snd_soc_component_driver fsl_dai_component = {
+ .name = "fsl-dai",
+};
+
+static int fsl_dai_init_clocks(struct fsl_dai *dai_priv)
+{
+ struct device *dev = &dai_priv->pdev->dev;
+ int i;
+
+ dai_priv->clks = devm_kcalloc(dev, dai_priv->num_clks,
+ sizeof(*dai_priv->clks), GFP_KERNEL);
+ if (!dai_priv->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < dai_priv->num_clks; i++) {
+ dai_priv->clks[i] = devm_clk_get(dev, dai_priv->clk_names[i]);
+ if (IS_ERR(dai_priv->clks[i])) {
+ dev_dbg(dev, "Failed to get clk %s\n",
+ dai_priv->clk_names[i]);
+ dai_priv->clks[i] = NULL;
+ }
+ }
+
+ return 0;
+}
+
+int fsl_get_dai_type(struct fsl_dai *dai_priv)
+{
+ struct device_node *np = dai_priv->pdev->dev.of_node;
+
+ if (of_device_is_compatible(np, "fsl,esai-dai"))
+ return FSL_DAI_TYPE_ESAI;
+
+ if (of_device_is_compatible(np, "fsl,sai-dai"))
+ return FSL_DAI_TYPE_SAI;
+
+ return FSL_DAI_TYPE_NONE;
+}
+
+static int fsl_dai_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_dai *priv;
+ int dai_type;
+ int ret;
+ int i;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+
+ dev_set_drvdata(&pdev->dev, priv);
+
+ dai_type = fsl_get_dai_type(priv);
+ switch (dai_type) {
+ case FSL_DAI_TYPE_ESAI:
+ priv->clk_names = esai_clks;
+ priv->num_clks = FSL_DAI_ESAI_CLK_NUM;
+ priv->dai_drv = &fsl_esai_dai;
+ priv->num_drv = 1;
+ break;
+ case FSL_DAI_TYPE_SAI:
+ priv->clk_names = sai_clks;
+ priv->num_clks = FSL_DAI_SAI_CLK_NUM;
+ priv->dai_drv = &fsl_sai_dai;
+ priv->num_drv = 1;
+ break;
+ default:
+ dev_err(&pdev->dev, "Invalid DAI type %d\n", dai_type);
+ return -EINVAL;
+ }
+
+ ret = fsl_dai_init_clocks(priv);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Error at init clocks\n");
+ return ret;
+ }
+
+ priv->num_domains = of_count_phandle_with_args(np, "power-domains",
+ "#power-domain-cells");
+ if (priv->num_domains < 0) {
+ dev_err(&pdev->dev, "no power-domains property in %pOF\n", np);
+ return priv->num_domains;
+ }
+
+ priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains,
+ sizeof(*priv->pd_dev), GFP_KERNEL);
+ if (!priv->pd_dev)
+ return -ENOMEM;
+
+ priv->link = devm_kmalloc_array(&pdev->dev, priv->num_domains,
+ sizeof(*priv->link), GFP_KERNEL);
+ if (!priv->link)
+ return -ENOMEM;
+
+ for (i = 0; i < priv->num_domains; i++) {
+ priv->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i);
+ if (IS_ERR(priv->pd_dev[i])) {
+ ret = PTR_ERR(priv->pd_dev[i]);
+ goto unroll_pm;
+ }
+
+ priv->link[i] = device_link_add(&pdev->dev, priv->pd_dev[i],
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME |
+ DL_FLAG_RPM_ACTIVE);
+ if (!priv->link[i]) {
+ ret = -EINVAL;
+ dev_pm_domain_detach(priv->pd_dev[i], false);
+ goto unroll_pm;
+ }
+ }
+
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_dai_component,
+ priv->dai_drv, priv->num_drv);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register DAI ret = %d\n", ret);
+ return ret;
+ }
+ return 0;
+
+unroll_pm:
+ while (--i >= 0) {
+ device_link_del(priv->link[i]);
+ dev_pm_domain_detach(priv->pd_dev[i], false);
+ }
+ return ret;
+}
+
+static int fsl_dai_remove(struct platform_device *pdev)
+{
+ struct fsl_dai *priv = platform_get_drvdata(pdev);
+ int i;
+
+ pm_runtime_disable(&priv->pdev->dev);
+
+ for (i = 0; i < priv->num_domains; i++) {
+ device_link_del(priv->link[i]);
+ dev_pm_domain_detach(priv->pd_dev[i], false);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id fsl_dai_dt_ids[] = {
+ { .compatible = "fsl,esai-dai", },
+ { .compatible = "fsl,sai-dai", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_dai_dt_ids);
+
+#ifdef CONFIG_PM
+static int fsl_dai_runtime_resume(struct device *dev)
+{
+ struct fsl_dai *priv = dev_get_drvdata(dev);
+ int i, ret;
+
+ for (i = 0; i < priv->num_clks; i++) {
+ ret = clk_prepare_enable(priv->clks[i]);
+ if (ret < 0) {
+ dev_err(dev, "Failed to enable clk %s\n",
+ priv->clk_names[i]);
+ goto out;
+ }
+ }
+ return 0;
+out:
+ while (--i >= 0)
+ clk_disable_unprepare(priv->clks[i]);
+
+ return ret;
+}
+
+static int fsl_dai_runtime_suspend(struct device *dev)
+{
+ struct fsl_dai *priv = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < priv->num_clks; i++)
+ clk_disable_unprepare(priv->clks[i]);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops fsl_dai_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_dai_runtime_suspend,
+ fsl_dai_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_dai_driver = {
+ .probe = fsl_dai_probe,
+ .remove = fsl_dai_remove,
+ .driver = {
+ .name = "fsl-dai",
+ .pm = &fsl_dai_pm_ops,
+ .of_match_table = fsl_dai_dt_ids,
+ },
+};
+
+module_platform_driver(fsl_dai_driver);
+
+MODULE_ALIAS("platform:fsl-dai");
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
+MODULE_DESCRIPTION("FSL Generic DAI driver for DSP");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_dsd.h b/sound/soc/fsl/fsl_dsd.h
new file mode 100644
index 000000000000..a45f31422f84
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsd.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __FSL_DSD_H
+#define __FSL_DSD_H
+
+#include <linux/pinctrl/consumer.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+static bool fsl_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 inline struct pinctrl_state *fsl_get_pins_state(struct pinctrl *pinctrl,
+ struct snd_pcm_hw_params *params, u32 bclk)
+{
+ struct pinctrl_state *state = 0;
+
+ if (fsl_is_dsd(params)) {
+ /* DSD512@44.1kHz, DSD512@48kHz */
+ if (bclk >= 22579200)
+ state = pinctrl_lookup_state(pinctrl, "dsd512");
+
+ /* Get default DSD state */
+ if (IS_ERR_OR_NULL(state))
+ state = pinctrl_lookup_state(pinctrl, "dsd");
+ } else {
+ /* 706k32b2c, 768k32b2c, etc */
+ if (bclk >= 45158400)
+ state = pinctrl_lookup_state(pinctrl, "pcm_b2m");
+ }
+
+ /* Get default state */
+ if (IS_ERR_OR_NULL(state))
+ state = pinctrl_lookup_state(pinctrl, "default");
+
+ return state;
+}
+
+#endif /* __FSL_DSD_H */
diff --git a/sound/soc/fsl/fsl_dsp.c b/sound/soc/fsl/fsl_dsp.c
new file mode 100644
index 000000000000..b3bcf21730d2
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp.c
@@ -0,0 +1,1176 @@
+/*
+ * Freescale DSP driver
+ *
+ * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED.
+ * Copyright 2018 NXP
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Copyright (c) 2001 William L. Pitts
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are freely
+ * permitted provided that the above copyright notice and this
+ * paragraph and the following disclaimer are duplicated in all
+ * such forms.
+ *
+ * This software is provided "AS IS" and without any express or
+ * implied warranties, including, without limitation, the implied
+ * warranties of merchantability and fitness for a particular
+ * purpose.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/file.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+#include <linux/platform_data/dma-imx.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <linux/mx8_mu.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+#ifdef CONFIG_COMPAT
+#include <linux/compat.h>
+#endif
+#include <uapi/linux/mxc_dsp.h>
+#include <linux/firmware/imx/svc/misc.h>
+#include <dt-bindings/firmware/imx/rsrc.h>
+
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include "fsl_dsp.h"
+#include "fsl_dsp_pool.h"
+#include "fsl_dsp_xaf_api.h"
+
+/* ...allocate new client */
+struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv)
+{
+ struct xf_client *client;
+ u32 id;
+
+ id = dsp_priv->xf_client_map[0].next;
+
+ /* ...try to allocate a client handle */
+ if (id != 0) {
+ /* ...allocate client memory */
+ client = kmalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return ERR_PTR(-ENOMEM);
+
+ /* ...advance the head of free clients */
+ dsp_priv->xf_client_map[0].next =
+ dsp_priv->xf_client_map[id].next;
+
+ /* ...put associate client id with given object */
+ dsp_priv->xf_client_map[id].client = client;
+
+ /* ...mark client is not yet bound to proxy */
+ client->proxy = NULL;
+
+ /* ...save global proxy client identifier */
+ client->id = id;
+
+ return client;
+ }
+
+ /* ...number of clients exceeded */
+ return ERR_PTR(-EBUSY);
+}
+
+/* ...recycle client object */
+static inline void xf_client_free(struct xf_client *client)
+{
+ int id = client->id;
+ struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global;
+
+ /* ...put proxy client id into free clients list */
+ dsp_priv->xf_client_map[id].next = dsp_priv->xf_client_map[0].next;
+ dsp_priv->xf_client_map[0].next = id;
+
+ /* ...destroy client data */
+ kfree(client);
+}
+
+/* ...lookup client basing on id */
+struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id)
+{
+ if ((id >= XF_CFG_MAX_IPC_CLIENTS) ||
+ (dsp_priv->xf_client_map[id].next < XF_CFG_MAX_IPC_CLIENTS)
+ )
+ return NULL;
+ else
+ return dsp_priv->xf_client_map[id].client;
+}
+
+/* ...helper function for retrieving the client handle */
+static inline struct xf_client *xf_get_client(struct file *file)
+{
+ struct xf_client *client;
+ u32 id;
+
+ client = (struct xf_client *)file->private_data;
+ if (!client)
+ return ERR_PTR(-EINVAL);
+
+ id = client->id;
+ if (id >= XF_CFG_MAX_IPC_CLIENTS)
+ return ERR_PTR(-EINVAL);
+
+ return client;
+}
+
+static int fsl_dsp_client_register(struct xf_client *client)
+{
+ struct fsl_dsp *dsp_priv;
+ struct device *dev;
+
+ dsp_priv = (struct fsl_dsp *)client->global;
+ dev = dsp_priv->dev;
+
+ /* ...make sure client is not registered yet */
+ if (client->proxy != NULL) {
+ pr_err("client-%x already registered", client->id);
+ return -EBUSY;
+ }
+
+ /* ...complete association (no communication with remote proxy here) */
+ client->proxy = &dsp_priv->proxy;
+
+ pr_debug("client-%x registered within proxy", client->id);
+
+ return 0;
+}
+
+/* ...unregister client from shared memory interface */
+static int fsl_dsp_client_unregister(struct xf_client *client)
+{
+ struct xf_proxy *proxy = client->proxy;
+
+ /* ...make sure client is registered */
+ if (proxy == NULL) {
+ pr_err("client-%x is not registered", client->id);
+ return -EBUSY;
+ }
+
+ /* ...just clean proxy reference */
+ client->proxy = NULL;
+
+ pr_debug("client-%x registered within proxy", client->id);
+
+ return 0;
+}
+
+static int fsl_dsp_ipc_msg_to_dsp(struct xf_client *client,
+ void __user *user)
+{
+ struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global;
+ struct device *dev = dsp_priv->dev;
+ struct xf_proxy_message msg;
+ void *buffer;
+ unsigned long ret = 0;
+
+ ret = copy_from_user(&msg, user, sizeof(struct xf_proxy_message));
+ if (ret) {
+ dev_err(dev, "failed to get message from user space\n");
+ return -EFAULT;
+ }
+
+ /* ...make sure message pointer is sane */
+ buffer = xf_proxy_a2b(&dsp_priv->proxy, msg.address);
+ if (buffer == (void *)-1)
+ return -EFAULT;
+
+ /* ...put current proxy client into message session id */
+ msg.session_id = XF_MSG_AP_FROM_USER(msg.session_id, client->id);
+
+ xf_cmd_send(&dsp_priv->proxy,
+ msg.session_id,
+ msg.opcode,
+ buffer,
+ msg.length);
+
+ return 0;
+}
+
+static int fsl_dsp_ipc_msg_from_dsp(struct xf_client *client,
+ void __user *user)
+{
+ struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global;
+ struct device *dev = dsp_priv->dev;
+ struct xf_message *m;
+ struct xf_proxy_message msg;
+ unsigned long ret = 0;
+
+ m = xf_cmd_recv(&dsp_priv->proxy, &client->wait, &client->queue, 0);
+ if (IS_ERR(m)) {
+ xf_unlock(&dsp_priv->proxy.lock);
+ dev_err(dev, "receiving failed: %d", (int)PTR_ERR(m));
+ return PTR_ERR(m);
+ }
+
+ /* ...check if there is a response available */
+ if (m == NULL)
+ return -EAGAIN;
+
+ /* ...prepare message parameters (lock is taken) */
+ msg.session_id = XF_MSG_AP_TO_USER(m->id);
+ msg.opcode = m->opcode;
+ msg.length = m->length;
+ msg.address = xf_proxy_b2a(&dsp_priv->proxy, m->buffer);
+ msg.ret = m->ret;
+
+ /* ...return the message back to a pool and release lock */
+ xf_msg_free(&dsp_priv->proxy, m);
+ xf_unlock(&dsp_priv->proxy.lock);
+
+ ret = copy_to_user(user, &msg, sizeof(struct xf_proxy_message));
+ if (ret) {
+ dev_err(dev, "failed to response message to user space\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int fsl_dsp_get_shmem_info(struct xf_client *client,
+ void __user *user)
+{
+ struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global;
+ struct device *dev = dsp_priv->dev;
+ struct shmem_info mem_info;
+ unsigned long ret = 0;
+
+ mem_info.phys_addr = dsp_priv->scratch_buf_phys;
+ mem_info.size = dsp_priv->scratch_buf_size;
+
+ ret = copy_to_user(user, &mem_info, sizeof(struct shmem_info));
+ if (ret) {
+ dev_err(dev, "failed to response message to user space\n");
+ return -EFAULT;
+ }
+
+ return ret;
+}
+
+static struct miscdevice dsp_miscdev = {
+ .name = "mxc_hifi4",
+ .minor = MISC_DYNAMIC_MINOR,
+};
+
+static long fsl_dsp_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct xf_client *client;
+ struct fsl_dsp *dsp_priv;
+ struct xf_proxy *proxy;
+ struct device *dev;
+ void __user *user;
+ long ret = 0;
+
+ /* ...basic sanity checks */
+ client = xf_get_client(file);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ dsp_priv = (struct fsl_dsp *)client->global;
+ proxy = &dsp_priv->proxy;
+ dev = dsp_priv->dev;
+ user = (void __user *)arg;
+
+ mutex_lock(&dsp_priv->dsp_mutex);
+
+ if (!proxy->is_ready) {
+ mutex_unlock(&dsp_priv->dsp_mutex);
+ dev_err(dev, "dsp firmware is not ready\n");
+ return -EFAULT;
+ }
+
+ switch (cmd) {
+ case DSP_CLIENT_REGISTER:
+ ret = fsl_dsp_client_register(client);
+ break;
+ case DSP_CLIENT_UNREGISTER:
+ ret = fsl_dsp_client_unregister(client);
+ break;
+ case DSP_IPC_MSG_SEND:
+ ret = fsl_dsp_ipc_msg_to_dsp(client, user);
+ break;
+ case DSP_IPC_MSG_RECV:
+ ret = fsl_dsp_ipc_msg_from_dsp(client, user);
+ break;
+ case DSP_GET_SHMEM_INFO:
+ ret = fsl_dsp_get_shmem_info(client, user);
+ break;
+ default:
+ break;
+ }
+
+ mutex_unlock(&dsp_priv->dsp_mutex);
+
+ return ret;
+}
+
+void resource_release(struct fsl_dsp *dsp_priv)
+{
+ int i;
+
+ /* ...initialize client association map */
+ for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++)
+ dsp_priv->xf_client_map[i].next = i + 1;
+ /* ...set list terminator */
+ dsp_priv->xf_client_map[i].next = 0;
+
+ /* ...set pointer to shared memory */
+ xf_proxy_init(&dsp_priv->proxy);
+}
+
+int fsl_dsp_open_func(struct fsl_dsp *dsp_priv, struct xf_client *client)
+{
+ struct device *dev = dsp_priv->dev;
+ int ret = 0;
+
+ /* ...initialize waiting queue */
+ init_waitqueue_head(&client->wait);
+
+ /* ...initialize client pending message queue */
+ xf_msg_queue_init(&client->queue);
+
+ /* ...mark user data is not mapped */
+ client->vm_start = 0;
+
+ /* ...reset mappings counter */
+ atomic_set(&client->vm_use, 0);
+
+ client->global = (void *)dsp_priv;
+ dsp_priv->proxy.is_active = 1;
+
+ pm_runtime_get_sync(dev);
+
+ mutex_lock(&dsp_priv->dsp_mutex);
+ /* increase reference counter when opening device */
+ atomic_long_inc(&dsp_priv->refcnt);
+ mutex_unlock(&dsp_priv->dsp_mutex);
+
+ return ret;
+}
+
+static int fsl_dsp_open(struct inode *inode, struct file *file)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dsp_miscdev.parent);
+ struct xf_client *client;
+ int ret = 0;
+
+ /* ...basic sanity checks */
+ if (!inode || !file)
+ return -EINVAL;
+
+ /* ...allocate new proxy client object */
+ client = xf_client_alloc(dsp_priv);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ fsl_dsp_open_func(dsp_priv, client);
+
+ file->private_data = (void *)client;
+
+ return ret;
+}
+
+int fsl_dsp_close_func(struct xf_client *client)
+{
+ struct fsl_dsp *dsp_priv;
+ struct device *dev;
+ struct xf_proxy *proxy;
+
+ /* ...basic sanity checks */
+ proxy = client->proxy;
+
+ /* release all pending messages */
+ if (proxy)
+ xf_msg_free_all(proxy, &client->queue);
+
+ dsp_priv = (struct fsl_dsp *)client->global;
+ dev = dsp_priv->dev;
+ pm_runtime_put_sync(dev);
+
+ /* ...recycle client id and release memory */
+ xf_client_free(client);
+
+ mutex_lock(&dsp_priv->dsp_mutex);
+ /* decrease reference counter when closing device */
+ atomic_long_dec(&dsp_priv->refcnt);
+ /* If device is free, reinitialize the resource of
+ * dsp driver and framework
+ */
+ if (atomic_long_read(&dsp_priv->refcnt) <= 0) {
+ /* we are closing up, wait for proxy processing
+ * function to finish */
+ cancel_work_sync(&dsp_priv->proxy.work);
+ resource_release(dsp_priv);
+ }
+
+ mutex_unlock(&dsp_priv->dsp_mutex);
+
+ return 0;
+}
+
+static int fsl_dsp_close(struct inode *inode, struct file *file)
+{
+ struct xf_client *client;
+
+ /* ...basic sanity checks */
+ client = xf_get_client(file);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ fsl_dsp_close_func(client);
+
+ return 0;
+}
+
+/* ...wait until data is available in the response queue */
+static unsigned int fsl_dsp_poll(struct file *file, poll_table *wait)
+{
+ struct xf_proxy *proxy;
+ struct xf_client *client;
+ int mask;
+
+ /* ...basic sanity checks */
+ client = xf_get_client(file);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ /* ...get proxy interface */
+ proxy = client->proxy;
+ if (!proxy)
+ return -EPERM;
+
+ /* ...register client waiting queue */
+ poll_wait(file, &client->wait, wait);
+
+ /* ...return current queue state */
+ mask = (xf_msg_queue_head(&client->queue) ? POLLIN | POLLRDNORM : 0);
+
+ return mask;
+}
+
+/*******************************************************************************
+ * Low-level mmap interface
+ ******************************************************************************/
+
+/* ...add reference to shared buffer */
+static void dsp_mmap_open(struct vm_area_struct *vma)
+{
+ struct xf_client *client = vma->vm_private_data;
+
+ /* ...probably just increase counter of open references? - tbd */
+ atomic_inc(&client->vm_use);
+
+ pr_debug("xf_mmap_open: vma = %p, client = %p", vma, client);
+}
+
+/* ...close reference to shared buffer */
+static void dsp_mmap_close(struct vm_area_struct *vma)
+{
+ struct xf_client *client = vma->vm_private_data;
+
+ pr_debug("xf_mmap_close: vma = %p, b = %p", vma, client);
+
+ /* ...decrement number of mapping */
+ atomic_dec(&client->vm_use);
+}
+
+/* ...memory map operations */
+static const struct vm_operations_struct dsp_mmap_ops = {
+ .open = dsp_mmap_open,
+ .close = dsp_mmap_close,
+};
+
+/* ...shared memory mapping */
+static int fsl_dsp_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct xf_proxy *proxy;
+ struct xf_client *client;
+ unsigned long size;
+ unsigned long pfn;
+ int r;
+ struct fsl_dsp *dsp_priv;
+
+ /* ...basic sanity checks */
+ client = xf_get_client(file);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ /* ...get proxy interface */
+ proxy = client->proxy;
+ if (!proxy)
+ return -EPERM;
+
+ /* ...check it was not mapped already */
+ if (client->vm_start != 0)
+ return -EBUSY;
+
+ /* ...check mapping flags (tbd) */
+ if ((vma->vm_flags & (VM_READ | VM_WRITE | VM_SHARED))
+ != (VM_READ | VM_WRITE | VM_SHARED))
+ return -EPERM;
+
+ /* ...set memory map operations */
+ vma->vm_ops = &dsp_mmap_ops;
+
+ /* ...assign private data */
+ client->vm_start = vma->vm_start;
+
+ /* ...set private memory data */
+ vma->vm_private_data = client;
+
+ /* ...set page number of shared memory */
+ dsp_priv = (struct fsl_dsp *)client->global;
+ pfn = dsp_priv->scratch_buf_phys >> PAGE_SHIFT;
+ size = dsp_priv->scratch_buf_size;
+
+ /* ...remap shared memory to user-space */
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
+ if (r != 0) {
+ pr_err("mapping failed: %d", r);
+ return r;
+ }
+
+ /* ...system-specific hook for registering shared memory mapping */
+ return 0;
+}
+
+void *memset_dsp(void *dest, int c, size_t count)
+{
+ uint *dl = (uint *)dest;
+ void *dl_1, *dl_2;
+ size_t align = 4;
+ size_t n, n1, n2;
+
+ /* while all data is aligned (common case), copy a word at a time */
+ if ((((ulong)dest) & (sizeof(*dl) - 1)) != 0) {
+ dl = (unsigned int *)(((ulong)dest + align - 1) &
+ (~(align - 1)));
+ dl_1 = dest;
+ dl_2 = (void *)(((ulong)dest + count) & (~(align - 1)));
+ n1 = (ulong)dl - (ulong)dl_1;
+ n2 = (ulong)dest + count - (ulong)dl_2;
+ n = (count - n1 - n2) / align;
+
+ while (n--) {
+ writel_relaxed(0, dl);
+ dl++;
+ }
+ while (n1--) {
+ writeb_relaxed(0, dl_1);
+ dl_1++;
+ }
+ while (n2--) {
+ writeb_relaxed(0, dl_2);
+ dl_2++;
+ }
+ } else {
+ n = count / align;
+ n1 = count - n * align;
+ dl_1 = dest + n * align;
+ while (n--) {
+ writel_relaxed(0, dl);
+ dl++;
+ }
+ while (n1--) {
+ writeb_relaxed(0, dl_1);
+ dl_1++;
+ }
+ }
+
+ return dest;
+}
+
+void *memcpy_dsp(void *dest, const void *src, size_t count)
+{
+ unsigned int *dl = (unsigned int *)dest, *sl = (unsigned int *)src;
+ size_t n = round_up(count, 4) / 4;
+
+ if (src == dest)
+ return dest;
+
+ /* while all data is aligned (common case), copy a word at a time */
+ if ((((ulong)dest | (ulong)src) & (sizeof(*dl) - 1)) != 0)
+ pr_info("dest %p src %p not 4 bytes aligned\n", dest, src);
+
+ while (n--) {
+ writel_relaxed(*sl, dl);
+ dl++;
+ sl++;
+ }
+
+ return dest;
+}
+
+static void dsp_load_firmware(const struct firmware *fw, void *context)
+{
+ struct fsl_dsp *dsp_priv = context;
+ struct device *dev = dsp_priv->dev;
+ Elf32_Ehdr *ehdr; /* Elf header structure pointer */
+ Elf32_Shdr *shdr; /* Section header structure pointer */
+ Elf32_Addr sh_addr;
+ unsigned char *strtab = 0; /* String table pointer */
+ unsigned char *image; /* Binary image pointer */
+ int i; /* Loop counter */
+ unsigned long addr;
+
+ if (!fw) {
+ dev_info(dev, "external firmware not found\n");
+ return;
+ }
+
+ addr = (unsigned long)fw->data;
+ ehdr = (Elf32_Ehdr *)addr;
+
+ /* Find the section header string table for output info */
+ shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff +
+ (ehdr->e_shstrndx * sizeof(Elf32_Shdr)));
+
+ strtab = (unsigned char *)(addr + shdr->sh_offset);
+
+ /* Load each appropriate section */
+ for (i = 0; i < ehdr->e_shnum; ++i) {
+ shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff +
+ (i * sizeof(Elf32_Shdr)));
+
+ if (!(shdr->sh_flags & SHF_ALLOC) ||
+ shdr->sh_addr == 0 || shdr->sh_size == 0)
+ continue;
+
+ dev_dbg(dev, "%sing %s @ 0x%08lx (%ld bytes)\n",
+ (shdr->sh_type == SHT_NOBITS) ? "Clear" : "Load",
+ &strtab[shdr->sh_name], (unsigned long)shdr->sh_addr,
+ (long)shdr->sh_size);
+
+ sh_addr = shdr->sh_addr;
+
+ if (shdr->sh_type == SHT_NOBITS) {
+ memset_dsp((void *)(dsp_priv->sdram_vir_addr +
+ (sh_addr - dsp_priv->sdram_phys_addr)),
+ 0,
+ shdr->sh_size);
+ } else {
+ image = (unsigned char *)addr + shdr->sh_offset;
+ if ((!strcmp(&strtab[shdr->sh_name], ".rodata")) ||
+ (!strcmp(&strtab[shdr->sh_name], ".text")) ||
+ (!strcmp(&strtab[shdr->sh_name], ".data")) ||
+ (!strcmp(&strtab[shdr->sh_name], ".bss"))
+ ) {
+ memcpy_dsp((void *)(dsp_priv->sdram_vir_addr
+ + (sh_addr - dsp_priv->sdram_phys_addr)),
+ (const void *)image,
+ shdr->sh_size);
+ } else {
+ /* sh_addr is from DSP view, we need to
+ * fixup addr because we load the firmware from
+ * the ARM core side
+ */
+ sh_addr -= dsp_priv->fixup_offset;
+
+ memcpy_dsp((void *)(dsp_priv->regs +
+ (sh_addr - dsp_priv->paddr)),
+ (const void *)image,
+ shdr->sh_size);
+ }
+ }
+ }
+
+ /* start the core */
+ imx_sc_pm_cpu_start(dsp_priv->dsp_ipcHandle,
+ IMX_SC_R_DSP, true, dsp_priv->iram);
+}
+
+/* Initialization of the MU code. */
+int dsp_mu_init(struct fsl_dsp *dsp_priv)
+{
+ struct device *dev = dsp_priv->dev;
+ struct device_node *np;
+ unsigned int dsp_mu_id;
+ u32 irq;
+ int ret = 0;
+
+ /*
+ * Get the address of MU to be used for communication with the dsp
+ */
+ np = of_find_compatible_node(NULL, NULL, "fsl,imx8-mu-dsp");
+ if (!np) {
+ dev_err(dev, "Cannot find MU entry in device tree\n");
+ return -EINVAL;
+ }
+ dsp_priv->mu_base_virtaddr = of_iomap(np, 0);
+ WARN_ON(!dsp_priv->mu_base_virtaddr);
+
+ ret = of_property_read_u32_index(np,
+ "fsl,dsp_ap_mu_id", 0, &dsp_mu_id);
+ if (ret) {
+ dev_err(dev, "Cannot get mu_id %d\n", ret);
+ return -EINVAL;
+ }
+
+ dsp_priv->dsp_mu_id = dsp_mu_id;
+
+ irq = of_irq_get(np, 0);
+
+ ret = devm_request_irq(dsp_priv->dev, irq, fsl_dsp_mu_isr,
+ IRQF_EARLY_RESUME, "dsp_mu_isr", &dsp_priv->proxy);
+ if (ret) {
+ dev_err(dev, "request_irq failed %d, err = %d\n", irq, ret);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct file_operations dsp_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fsl_dsp_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = fsl_dsp_ioctl,
+#endif
+ .open = fsl_dsp_open,
+ .poll = fsl_dsp_poll,
+ .mmap = fsl_dsp_mmap,
+ .release = fsl_dsp_close,
+};
+
+extern struct snd_compr_ops dsp_platform_compr_ops;
+
+static const struct snd_soc_component_driver dsp_soc_platform_drv = {
+ .name = FSL_DSP_COMP_NAME,
+ .compr_ops = &dsp_platform_compr_ops,
+};
+
+static int fsl_dsp_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *reserved_node;
+ struct resource reserved_res;
+ struct fsl_dsp *dsp_priv;
+ const char *fw_name;
+ struct resource *res;
+ void __iomem *regs;
+ void *buf_virt;
+ dma_addr_t buf_phys;
+ int size, offset, i;
+ int ret;
+ int num_domains = 0;
+ char tmp[16];
+
+ dsp_priv = devm_kzalloc(&pdev->dev, sizeof(*dsp_priv), GFP_KERNEL);
+ if (!dsp_priv)
+ return -ENOMEM;
+
+ if (of_device_is_compatible(np, "fsl,imx8qxp-dsp-v1"))
+ dsp_priv->dsp_board_type = DSP_IMX8QXP_TYPE;
+ else
+ dsp_priv->dsp_board_type = DSP_IMX8QM_TYPE;
+
+ dsp_priv->dev = &pdev->dev;
+
+ /* Get the addresses and IRQ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ dsp_priv->paddr = res->start;
+ dsp_priv->regs = regs;
+
+ dsp_priv->dram0 = dsp_priv->paddr + DRAM0_OFFSET;
+ dsp_priv->dram1 = dsp_priv->paddr + DRAM1_OFFSET;
+ dsp_priv->iram = dsp_priv->paddr + IRAM_OFFSET;
+ dsp_priv->sram = dsp_priv->paddr + SYSRAM_OFFSET;
+
+ ret = imx_scu_get_handle(&dsp_priv->dsp_ipcHandle);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot get scu handle %d\n", ret);
+ return ret;
+ };
+
+ num_domains = of_count_phandle_with_args(np, "power-domains",
+ "#power-domain-cells");
+ for (i = 0; i < num_domains; i++) {
+ struct device *pd_dev;
+ struct device_link *link;
+
+ pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, i);
+ if (IS_ERR(pd_dev))
+ return PTR_ERR(pd_dev);
+
+ link = device_link_add(&pdev->dev, pd_dev,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME |
+ DL_FLAG_RPM_ACTIVE);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ }
+
+ if (dsp_priv->dsp_board_type == DSP_IMX8QXP_TYPE) {
+ ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP,
+ IMX_SC_C_OFS_SEL, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Error system address offset source select\n");
+ return -EIO;
+ }
+
+ ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP,
+ IMX_SC_C_OFS_PERIPH, 0x5A);
+ if (ret) {
+ dev_err(&pdev->dev, "Error system address offset of PERIPH %d\n",
+ ret);
+ return -EIO;
+ }
+
+ ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP,
+ IMX_SC_C_OFS_IRQ, 0x51);
+ if (ret) {
+ dev_err(&pdev->dev, "Error system address offset of IRQ\n");
+ return -EIO;
+ }
+
+ ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP,
+ IMX_SC_C_OFS_AUDIO, 0x80);
+ if (ret) {
+ dev_err(&pdev->dev, "Error system address offset of AUDIO\n");
+ return -EIO;
+ }
+ } else {
+ ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP,
+ IMX_SC_C_OFS_SEL, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "Error system address offset source select\n");
+ return -EIO;
+ }
+ }
+
+ ret = dsp_mu_init(dsp_priv);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_string(np, "fsl,dsp-firmware", &fw_name);
+ dsp_priv->fw_name = fw_name;
+
+ ret = of_property_read_u32(np, "fixup-offset", &dsp_priv->fixup_offset);
+
+ platform_set_drvdata(pdev, dsp_priv);
+ pm_runtime_enable(&pdev->dev);
+
+ dsp_miscdev.fops = &dsp_fops,
+ dsp_miscdev.parent = &pdev->dev,
+ ret = misc_register(&dsp_miscdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register misc device %d\n", ret);
+ return ret;
+ }
+
+ reserved_node = of_parse_phandle(np, "reserved-region", 0);
+ if (!reserved_node) {
+ dev_err(&pdev->dev, "failed to get reserved region node\n");
+ return -ENODEV;
+ }
+
+ if (of_address_to_resource(reserved_node, 0, &reserved_res)) {
+ dev_err(&pdev->dev, "failed to get reserved region address\n");
+ return -EINVAL;
+ }
+
+ dsp_priv->sdram_phys_addr = reserved_res.start;
+ dsp_priv->sdram_reserved_size = (reserved_res.end - reserved_res.start)
+ + 1;
+ if (dsp_priv->sdram_reserved_size <= 0) {
+ dev_err(&pdev->dev, "invalid value of reserved region size\n");
+ return -EINVAL;
+ }
+
+ dsp_priv->sdram_vir_addr = ioremap_wc(dsp_priv->sdram_phys_addr,
+ dsp_priv->sdram_reserved_size);
+ if (!dsp_priv->sdram_vir_addr) {
+ dev_err(&pdev->dev, "failed to remap sdram space for dsp firmware\n");
+ return -ENXIO;
+ }
+ memset_io(dsp_priv->sdram_vir_addr, 0, dsp_priv->sdram_reserved_size);
+
+ size = MSG_BUF_SIZE + DSP_CONFIG_SIZE;
+
+ buf_virt = dma_alloc_coherent(&pdev->dev, size, &buf_phys, GFP_KERNEL);
+ if (!buf_virt) {
+ dev_err(&pdev->dev, "failed alloc memory.\n");
+ return -ENOMEM;
+ }
+
+ /* msg ring buffer memory */
+ dsp_priv->msg_buf_virt = buf_virt;
+ dsp_priv->msg_buf_phys = buf_phys;
+ dsp_priv->msg_buf_size = MSG_BUF_SIZE;
+ offset = MSG_BUF_SIZE;
+
+ /* keep dsp framework's global data when suspend/resume */
+ dsp_priv->dsp_config_virt = buf_virt + offset;
+ dsp_priv->dsp_config_phys = buf_phys + offset;
+ dsp_priv->dsp_config_size = DSP_CONFIG_SIZE;
+
+ /* scratch memory for dsp framework. The sdram reserved memory
+ * is split into two equal parts currently. The front part is
+ * used to keep the dsp firmware, the other part is considered
+ * as scratch memory for dsp framework.
+ */
+ dsp_priv->scratch_buf_virt = dsp_priv->sdram_vir_addr +
+ dsp_priv->sdram_reserved_size / 2;
+ dsp_priv->scratch_buf_phys = dsp_priv->sdram_phys_addr +
+ dsp_priv->sdram_reserved_size / 2;
+ dsp_priv->scratch_buf_size = dsp_priv->sdram_reserved_size / 2;
+
+ /* initialize the reference counter for dsp_priv
+ * structure
+ */
+ atomic_long_set(&dsp_priv->refcnt, 0);
+
+ /* ...initialize client association map */
+ for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++)
+ dsp_priv->xf_client_map[i].next = i + 1;
+ /* ...set list terminator */
+ dsp_priv->xf_client_map[i].next = 0;
+
+ /* ...set pointer to shared memory */
+ xf_proxy_init(&dsp_priv->proxy);
+
+ /* ...initialize mutex */
+ mutex_init(&dsp_priv->dsp_mutex);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &dsp_soc_platform_drv, NULL, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "registering soc platform failed\n");
+ return ret;
+ }
+
+ dsp_priv->esai_ipg_clk = devm_clk_get(&pdev->dev, "esai_ipg");
+ if (IS_ERR(dsp_priv->esai_ipg_clk))
+ dsp_priv->esai_ipg_clk = NULL;
+
+ dsp_priv->esai_mclk = devm_clk_get(&pdev->dev, "esai_mclk");
+ if (IS_ERR(dsp_priv->esai_mclk))
+ dsp_priv->esai_mclk = NULL;
+
+ dsp_priv->asrc_mem_clk = devm_clk_get(&pdev->dev, "asrc_mem");
+ if (IS_ERR(dsp_priv->asrc_mem_clk))
+ dsp_priv->asrc_mem_clk = NULL;
+
+ dsp_priv->asrc_ipg_clk = devm_clk_get(&pdev->dev, "asrc_ipg");
+ if (IS_ERR(dsp_priv->asrc_ipg_clk))
+ dsp_priv->asrc_ipg_clk = NULL;
+
+ for (i = 0; i < 4; i++) {
+ sprintf(tmp, "asrck_%x", i);
+ dsp_priv->asrck_clk[i] = devm_clk_get(&pdev->dev, tmp);
+ if (IS_ERR(dsp_priv->asrck_clk[i]))
+ dsp_priv->asrck_clk[i] = NULL;
+ }
+
+ return 0;
+}
+
+static int fsl_dsp_remove(struct platform_device *pdev)
+{
+ struct fsl_dsp *dsp_priv = platform_get_drvdata(pdev);
+ int size;
+
+ misc_deregister(&dsp_miscdev);
+
+ size = MSG_BUF_SIZE + DSP_CONFIG_SIZE;
+ dma_free_coherent(&pdev->dev, size, dsp_priv->msg_buf_virt,
+ dsp_priv->msg_buf_phys);
+ if (dsp_priv->sdram_vir_addr)
+ iounmap(dsp_priv->sdram_vir_addr);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_dsp_runtime_resume(struct device *dev)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dev);
+ struct xf_proxy *proxy = &dsp_priv->proxy;
+ int ret;
+ int i;
+
+ ret = clk_prepare_enable(dsp_priv->esai_ipg_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable esai ipg clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dsp_priv->esai_mclk);
+ if (ret) {
+ dev_err(dev, "failed to enable esai mclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dsp_priv->asrc_mem_clk);
+ if (ret < 0)
+ dev_err(dev, "Failed to enable asrc_mem_clk ret = %d\n", ret);
+
+ ret = clk_prepare_enable(dsp_priv->asrc_ipg_clk);
+ if (ret < 0)
+ dev_err(dev, "Failed to enable asrc_ipg_clk ret = %d\n", ret);
+
+ for (i = 0; i < 4; i++) {
+ ret = clk_prepare_enable(dsp_priv->asrck_clk[i]);
+ if (ret < 0)
+ dev_err(dev, "failed to prepare arc clk %d\n", i);
+ }
+
+ if (!dsp_priv->dsp_mu_init) {
+ MU_Init(dsp_priv->mu_base_virtaddr);
+ MU_EnableRxFullInt(dsp_priv->mu_base_virtaddr, 0);
+ dsp_priv->dsp_mu_init = 1;
+ }
+
+ if (!proxy->is_ready) {
+ init_completion(&proxy->cmd_complete);
+
+ ret = request_firmware_nowait(THIS_MODULE,
+ FW_ACTION_HOTPLUG, dsp_priv->fw_name,
+ dev,
+ GFP_KERNEL, dsp_priv, dsp_load_firmware);
+
+ if (ret) {
+ dev_err(dev, "failed to load firmware\n");
+ return ret;
+ }
+
+ ret = icm_ack_wait(proxy, 0);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "dsp driver registered\n");
+ }
+
+ return 0;
+}
+
+static int fsl_dsp_runtime_suspend(struct device *dev)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dev);
+ struct xf_proxy *proxy = &dsp_priv->proxy;
+ int i;
+
+ dsp_priv->dsp_mu_init = 0;
+ proxy->is_ready = 0;
+
+ for (i = 0; i < 4; i++)
+ clk_disable_unprepare(dsp_priv->asrck_clk[i]);
+
+ clk_disable_unprepare(dsp_priv->asrc_ipg_clk);
+ clk_disable_unprepare(dsp_priv->asrc_mem_clk);
+
+ clk_disable_unprepare(dsp_priv->esai_mclk);
+ clk_disable_unprepare(dsp_priv->esai_ipg_clk);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_dsp_suspend(struct device *dev)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dev);
+ struct xf_proxy *proxy = &dsp_priv->proxy;
+ int ret = 0;
+
+ if (proxy->is_ready) {
+ ret = xf_cmd_send_suspend(proxy);
+ if (ret) {
+ dev_err(dev, "dsp suspend fail\n");
+ return ret;
+ }
+ }
+
+ ret = pm_runtime_force_suspend(dev);
+
+ return ret;
+}
+
+static int fsl_dsp_resume(struct device *dev)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dev);
+ struct xf_proxy *proxy = &dsp_priv->proxy;
+ int ret = 0;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ if (proxy->is_ready) {
+ ret = xf_cmd_send_resume(proxy);
+ if (ret) {
+ dev_err(dev, "dsp resume fail\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_dsp_pm = {
+ SET_RUNTIME_PM_OPS(fsl_dsp_runtime_suspend,
+ fsl_dsp_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_dsp_suspend, fsl_dsp_resume)
+};
+
+static const struct of_device_id fsl_dsp_ids[] = {
+ { .compatible = "fsl,imx8qxp-dsp-v1", },
+ { .compatible = "fsl,imx8qm-dsp-v1", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_dsp_ids);
+
+static struct platform_driver fsl_dsp_driver = {
+ .probe = fsl_dsp_probe,
+ .remove = fsl_dsp_remove,
+ .driver = {
+ .name = "fsl-dsp",
+ .of_match_table = fsl_dsp_ids,
+ .pm = &fsl_dsp_pm,
+ },
+};
+module_platform_driver(fsl_dsp_driver);
+
+MODULE_DESCRIPTION("Freescale DSP driver");
+MODULE_ALIAS("platform:fsl-dsp");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/fsl/fsl_dsp.h b/sound/soc/fsl/fsl_dsp.h
new file mode 100644
index 000000000000..a66b083124ae
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/
+/*
+ * Copyright (C) 2017 Cadence Design Systems, Inc.
+ * Copyright 2018 NXP
+ *
+ */
+
+#ifndef FSL_DSP_H
+#define FSL_DSP_H
+#include <uapi/linux/mxc_dsp.h>
+#include <linux/firmware/imx/ipc.h>
+#include "fsl_dsp_proxy.h"
+#include "fsl_dsp_platform.h"
+
+
+#define FSL_DSP_COMP_NAME "fsl-dsp-component"
+
+typedef void (*memcpy_func) (void *dest, const void *src, size_t n);
+typedef void (*memset_func) (void *s, int c, size_t n);
+
+/* ...maximal number of IPC clients per proxy */
+#define XF_CFG_MAX_IPC_CLIENTS (1 << 4)
+
+enum {
+ DSP_IMX8QXP_TYPE = 0,
+ DSP_IMX8QM_TYPE,
+};
+
+/* ...proxy client data */
+struct xf_client {
+ /* ...pointer to proxy interface */
+ struct xf_proxy *proxy;
+
+ /* ...allocated proxy client id */
+ u32 id;
+
+ /* ...pending response queue */
+ struct xf_msg_queue queue;
+ /* ...response waiting queue */
+ wait_queue_head_t wait;
+
+ /* ...virtual memory mapping */
+ unsigned long vm_start;
+ /* ...counter of memory mappings (no real use of it yet - tbd) */
+ atomic_t vm_use;
+
+ /* ...global structure pointer */
+ void *global;
+ struct xf_message m;
+
+ struct snd_compr_stream *cstream;
+
+ struct work_struct work;
+ struct completion compr_complete;
+
+ int input_bytes;
+ int consume_bytes;
+};
+
+union xf_client_link {
+ /* ...index of next client in free list */
+ u32 next;
+
+ /* ...reference to proxy data for allocated client */
+ struct xf_client *client;
+};
+
+struct fsl_dsp {
+ struct device *dev;
+ const char *fw_name;
+ void __iomem *regs;
+ void __iomem *mu_base_virtaddr;
+ struct imx_sc_ipc *dsp_ipcHandle;
+ unsigned int dsp_mu_id;
+ int dsp_mu_init;
+ atomic_long_t refcnt;
+ unsigned long paddr;
+ unsigned long dram0;
+ unsigned long dram1;
+ unsigned long iram;
+ unsigned long sram;
+ void *sdram_vir_addr;
+ unsigned long sdram_phys_addr;
+ int sdram_reserved_size;
+ void *msg_buf_virt;
+ dma_addr_t msg_buf_phys;
+ int msg_buf_size;
+ void *scratch_buf_virt;
+ dma_addr_t scratch_buf_phys;
+ int scratch_buf_size;
+ void *dsp_config_virt;
+ dma_addr_t dsp_config_phys;
+ int dsp_config_size;
+ int dsp_board_type;
+ unsigned int fixup_offset;
+
+ /* ...proxy data structures */
+ struct xf_proxy proxy;
+
+ /* ...mutex lock */
+ struct mutex dsp_mutex;
+
+ struct dsp_data dsp_data;
+
+ /* ...global clients pool (item[0] serves as list terminator) */
+ union xf_client_link xf_client_map[XF_CFG_MAX_IPC_CLIENTS];
+
+ struct clk *esai_ipg_clk;
+ struct clk *esai_mclk;
+ struct clk *asrc_mem_clk;
+ struct clk *asrc_ipg_clk;
+ struct clk *asrck_clk[4];
+};
+
+#define IRAM_OFFSET 0x10000
+#define IRAM_SIZE 2048
+
+#define DRAM0_OFFSET 0x0
+#define DRAM0_SIZE 0x8000
+
+#define DRAM1_OFFSET 0x8000
+#define DRAM1_SIZE 0x8000
+
+#define SYSRAM_OFFSET 0x18000
+#define SYSRAM_SIZE 0x40000
+
+#define SYSROM_OFFSET 0x58000
+#define SYSROM_SIZE 0x30000
+
+#define MSG_BUF_SIZE 8192
+#define INPUT_BUF_SIZE 4096
+#define OUTPUT_BUF_SIZE 16384
+#define DSP_CONFIG_SIZE 4096
+
+void *memcpy_dsp(void *dest, const void *src, size_t count);
+void *memset_dsp(void *dest, int c, size_t count);
+struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id);
+struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv);
+
+int fsl_dsp_open_func(struct fsl_dsp *dsp_priv, struct xf_client *client);
+int fsl_dsp_close_func(struct xf_client *client);
+
+#endif
diff --git a/sound/soc/fsl/fsl_dsp_cpu.c b/sound/soc/fsl/fsl_dsp_cpu.c
new file mode 100644
index 000000000000..e97d09ae0c45
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_cpu.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// DSP Audio platform driver
+//
+// Copyright 2018 NXP
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/core.h>
+#include <sound/compress_driver.h>
+
+#include "fsl_dsp_cpu.h"
+
+static int dsp_audio_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai) {
+ return 0;
+}
+
+
+static void dsp_audio_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai) {
+}
+
+static const struct snd_soc_dai_ops dsp_audio_dai_ops = {
+ .startup = dsp_audio_startup,
+ .shutdown = dsp_audio_shutdown,
+};
+
+static struct snd_soc_dai_driver dsp_audio_dai = {
+ .name = "dsp-audio-cpu-dai",
+ .compress_new = snd_soc_new_compress,
+ .ops = &dsp_audio_dai_ops,
+ .playback = {
+ .stream_name = "Compress Playback",
+ .channels_min = 1,
+ },
+};
+
+static const struct snd_soc_component_driver audio_dsp_component = {
+ .name = "audio-dsp",
+};
+
+static int dsp_audio_probe(struct platform_device *pdev)
+{
+ struct fsl_dsp_audio *dsp_audio;
+ int ret;
+
+ dsp_audio = devm_kzalloc(&pdev->dev, sizeof(*dsp_audio), GFP_KERNEL);
+ if (dsp_audio == NULL)
+ return -ENOMEM;
+
+ dev_dbg(&pdev->dev, "probing DSP device....\n");
+
+ /* intialise sof device */
+ dev_set_drvdata(&pdev->dev, dsp_audio);
+
+ pm_runtime_enable(&pdev->dev);
+
+ /* now register audio DSP platform driver */
+ ret = snd_soc_register_component(&pdev->dev, &audio_dsp_component,
+ &dsp_audio_dai, 1);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "error: failed to register DSP DAI driver %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ return ret;
+}
+
+static int dsp_audio_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_component(&pdev->dev);
+ return 0;
+}
+
+
+static const struct of_device_id dsp_audio_ids[] = {
+ { .compatible = "fsl,dsp-audio"},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dsp_audio_ids);
+
+static struct platform_driver dsp_audio_driver = {
+ .driver = {
+ .name = "dsp-audio",
+ .of_match_table = dsp_audio_ids,
+ },
+ .probe = dsp_audio_probe,
+ .remove = dsp_audio_remove,
+};
+module_platform_driver(dsp_audio_driver);
diff --git a/sound/soc/fsl/fsl_dsp_cpu.h b/sound/soc/fsl/fsl_dsp_cpu.h
new file mode 100644
index 000000000000..dac10b4ab9d0
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_cpu.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * DSP Audio DAI header
+ *
+ * Copyright 2018 NXP
+ */
+
+#ifndef __FSL_DSP_CPU_H
+#define __FSL_DSP_CPU_H
+
+struct fsl_dsp_audio {
+ struct platform_device *pdev;
+};
+
+#endif /*__FSL_DSP_CPU_H*/
+
diff --git a/sound/soc/fsl/fsl_dsp_library_load.c b/sound/soc/fsl/fsl_dsp_library_load.c
new file mode 100644
index 000000000000..d273f7296e29
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_library_load.c
@@ -0,0 +1,642 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2018 NXP
+// Copyright (c) 2012-2013 by Tensilica Inc.
+
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/elf.h>
+
+#include "fsl_dsp.h"
+#include "fsl_dsp_library_load.h"
+
+static Elf32_Half xtlib_host_half(Elf32_Half v, int byteswap)
+{
+ return (byteswap) ? (v >> 8) | (v << 8) : v;
+}
+
+static Elf32_Word xtlib_host_word(Elf32_Word v, int byteswap)
+{
+ if (byteswap) {
+ v = ((v & 0x00FF00FF) << 8) | ((v & 0xFF00FF00) >> 8);
+ v = (v >> 16) | (v << 16);
+ }
+ return v;
+}
+
+static int xtlib_verify_magic(Elf32_Ehdr *header,
+ struct lib_info *lib_info)
+{
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+ Elf32_Byte magic_no;
+
+ magic_no = header->e_ident[EI_MAG0];
+ if (magic_no != 0x7f)
+ return -1;
+
+ magic_no = header->e_ident[EI_MAG1];
+ if (magic_no != 'E')
+ return -1;
+
+ magic_no = header->e_ident[EI_MAG2];
+ if (magic_no != 'L')
+ return -1;
+
+ magic_no = header->e_ident[EI_MAG3];
+ if (magic_no != 'F')
+ return -1;
+
+ if (header->e_ident[EI_CLASS] != ELFCLASS32)
+ return -1;
+
+ {
+ /* determine byte order */
+ union {
+ short s;
+ char c[sizeof(short)];
+ } u;
+
+ u.s = 1;
+
+ if (header->e_ident[EI_DATA] == ELFDATA2LSB)
+ xtlib_globals->byteswap = u.c[sizeof(short) - 1] == 1;
+ else if (header->e_ident[EI_DATA] == ELFDATA2MSB)
+ xtlib_globals->byteswap = u.c[0] == 1;
+ else
+ return -1;
+ }
+
+ return 0;
+}
+
+static void xtlib_load_seg(Elf32_Phdr *pheader, void *src_addr, xt_ptr dst_addr,
+ struct lib_info *lib_info)
+{
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+ Elf32_Word bytes_to_copy = xtlib_host_word(pheader->p_filesz,
+ xtlib_globals->byteswap);
+ Elf32_Word bytes_to_zero = xtlib_host_word(pheader->p_memsz,
+ xtlib_globals->byteswap)
+ - bytes_to_copy;
+ unsigned int i;
+ char *src_back, *dst_back;
+
+ void *zero_addr = (void *)dst_addr + bytes_to_copy;
+
+ if (bytes_to_copy > 0) {
+ // memcpy((void *)(dst_addr), src_addr, bytes_to_copy);
+ src_back = (char *)src_addr;
+ dst_back = (char *)dst_addr;
+ for (i = 0; i < bytes_to_copy; i++)
+ *dst_back++ = *src_back++;
+ }
+
+ if (bytes_to_zero > 0) {
+ // memset(zero_addr, 0, bytes_to_zero);
+ dst_back = (char *)zero_addr;
+ for (i = 0; i < bytes_to_zero; i++)
+ *dst_back++ = 0;
+ }
+}
+
+#define xtlib_xt_half xtlib_host_half
+#define xtlib_xt_word xtlib_host_word
+
+static xt_ptr align_ptr(xt_ptr ptr, xt_uint align)
+{
+ return (xt_ptr)(((xt_uint)ptr + align - 1) & ~(align - 1));
+}
+
+static xt_ptr xt_ptr_offs(xt_ptr base, Elf32_Word offs,
+ struct lib_info *lib_info)
+{
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+
+ return (xt_ptr)xtlib_xt_word((xt_uint)base +
+ xtlib_host_word(offs, xtlib_globals->byteswap),
+ xtlib_globals->byteswap);
+}
+
+static Elf32_Dyn *find_dynamic_info(Elf32_Ehdr *eheader,
+ struct lib_info *lib_info)
+{
+ char *base_addr = (char *)eheader;
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+ Elf32_Phdr *pheader = (Elf32_Phdr *)(base_addr +
+ xtlib_host_word(eheader->e_phoff,
+ xtlib_globals->byteswap));
+
+ int seg = 0;
+ int num = xtlib_host_half(eheader->e_phnum, xtlib_globals->byteswap);
+
+ while (seg < num) {
+ if (xtlib_host_word(pheader[seg].p_type,
+ xtlib_globals->byteswap) == PT_DYNAMIC) {
+ return (Elf32_Dyn *)(base_addr +
+ xtlib_host_word(pheader[seg].p_offset,
+ xtlib_globals->byteswap));
+ }
+ seg++;
+ }
+ return 0;
+}
+
+static int find_align(Elf32_Ehdr *header,
+ struct lib_info *lib_info)
+{
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+ Elf32_Shdr *sheader = (Elf32_Shdr *) (((char *)header) +
+ xtlib_host_word(header->e_shoff, xtlib_globals->byteswap));
+
+ int sec = 0;
+ int num = xtlib_host_half(header->e_shnum, xtlib_globals->byteswap);
+
+ int align = 0;
+
+ while (sec < num) {
+ if (sheader[sec].sh_type != SHT_NULL &&
+ xtlib_host_word(sheader[sec].sh_size,
+ xtlib_globals->byteswap) > 0) {
+ int sec_align =
+ xtlib_host_word(sheader[sec].sh_addralign,
+ xtlib_globals->byteswap);
+ if (sec_align > align)
+ align = sec_align;
+ }
+ sec++;
+ }
+
+ return align;
+}
+
+static int validate_dynamic(Elf32_Ehdr *header,
+ struct lib_info *lib_info)
+{
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+
+ if (xtlib_verify_magic(header, lib_info) != 0)
+ return XTLIB_NOT_ELF;
+
+ if (xtlib_host_half(header->e_type,
+ xtlib_globals->byteswap) != ET_DYN)
+ return XTLIB_NOT_DYNAMIC;
+
+ return XTLIB_NO_ERR;
+}
+
+static int validate_dynamic_splitload(Elf32_Ehdr *header,
+ struct lib_info *lib_info)
+{
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+ Elf32_Phdr *pheader;
+ int err = validate_dynamic(header, lib_info);
+
+ if (err != XTLIB_NO_ERR)
+ return err;
+
+ /* make sure it's split load pi library, expecting three headers,
+ * code, data and dynamic, for example:
+ *
+ *LOAD off 0x00000094 vaddr 0x00000000 paddr 0x00000000 align 2**0
+ * filesz 0x00000081 memsz 0x00000081 flags r-x
+ *LOAD off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**0
+ * filesz 0x000001ab memsz 0x000011bc flags rwx
+ *DYNAMIC off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**2
+ * filesz 0x000000a0 memsz 0x000000a0 flags rw-
+ */
+
+ if (xtlib_host_half(header->e_phnum, xtlib_globals->byteswap) != 3)
+ return XTLIB_NOT_SPLITLOAD;
+
+ pheader = (Elf32_Phdr *)((char *)header +
+ xtlib_host_word(header->e_phoff, xtlib_globals->byteswap));
+
+ /* LOAD R-X */
+ if (xtlib_host_word(pheader[0].p_type,
+ xtlib_globals->byteswap) != PT_LOAD ||
+ (xtlib_host_word(pheader[0].p_flags,
+ xtlib_globals->byteswap)
+ & (PF_R | PF_W | PF_X)) != (PF_R | PF_X))
+ return XTLIB_NOT_SPLITLOAD;
+
+ /* LOAD RWX */
+ if (xtlib_host_word(pheader[1].p_type,
+ xtlib_globals->byteswap) != PT_LOAD ||
+ (xtlib_host_word(pheader[1].p_flags,
+ xtlib_globals->byteswap)
+ & (PF_R | PF_W | PF_X)) != (PF_R | PF_W | PF_X))
+ return XTLIB_NOT_SPLITLOAD;
+
+ /* DYNAMIC RW- */
+ if (xtlib_host_word(pheader[2].p_type,
+ xtlib_globals->byteswap) != PT_DYNAMIC ||
+ (xtlib_host_word(pheader[2].p_flags,
+ xtlib_globals->byteswap)
+ & (PF_R | PF_W | PF_X)) != (PF_R | PF_W))
+ return XTLIB_NOT_SPLITLOAD;
+
+ return XTLIB_NO_ERR;
+}
+
+static unsigned int
+xtlib_split_pi_library_size(struct xtlib_packaged_library *library,
+ unsigned int *code_size,
+ unsigned int *data_size,
+ struct lib_info *lib_info)
+{
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+ Elf32_Phdr *pheader;
+ Elf32_Ehdr *header = (Elf32_Ehdr *)library;
+ int align;
+ int err = validate_dynamic_splitload(header, lib_info);
+
+ if (err != XTLIB_NO_ERR) {
+ xtlib_globals->err = err;
+ return err;
+ }
+
+ align = find_align(header, lib_info);
+
+ pheader = (Elf32_Phdr *)((char *)library +
+ xtlib_host_word(header->e_phoff, xtlib_globals->byteswap));
+
+ *code_size = xtlib_host_word(pheader[0].p_memsz,
+ xtlib_globals->byteswap) + align;
+ *data_size = xtlib_host_word(pheader[1].p_memsz,
+ xtlib_globals->byteswap) + align;
+
+ return XTLIB_NO_ERR;
+}
+
+static int get_dyn_info(Elf32_Ehdr *eheader,
+ xt_ptr dst_addr, xt_uint src_offs,
+ xt_ptr dst_data_addr, xt_uint src_data_offs,
+ struct xtlib_pil_info *info,
+ struct lib_info *lib_info)
+{
+ unsigned int jmprel = 0;
+ unsigned int pltrelsz = 0;
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+ Elf32_Dyn *dyn_entry = find_dynamic_info(eheader, lib_info);
+
+ if (dyn_entry == 0)
+ return XTLIB_NO_DYNAMIC_SEGMENT;
+
+ info->dst_addr = (xt_uint)xtlib_xt_word((Elf32_Word)dst_addr,
+ xtlib_globals->byteswap);
+ info->src_offs = xtlib_xt_word(src_offs, xtlib_globals->byteswap);
+ info->dst_data_addr = (xt_uint)xtlib_xt_word(
+ (Elf32_Word)dst_data_addr + src_data_offs,
+ xtlib_globals->byteswap);
+ info->src_data_offs = xtlib_xt_word(src_data_offs,
+ xtlib_globals->byteswap);
+
+ dst_addr -= src_offs;
+ dst_data_addr = dst_data_addr + src_data_offs - src_data_offs;
+
+ info->start_sym = xt_ptr_offs(dst_addr, eheader->e_entry, lib_info);
+
+ info->align = xtlib_xt_word(find_align(eheader, lib_info),
+ xtlib_globals->byteswap);
+
+ info->text_addr = 0;
+
+ while (dyn_entry->d_tag != DT_NULL) {
+ switch ((Elf32_Sword) xtlib_host_word(
+ (Elf32_Word)dyn_entry->d_tag,
+ xtlib_globals->byteswap)) {
+ case DT_RELA:
+ info->rel = xt_ptr_offs(dst_data_addr,
+ dyn_entry->d_un.d_ptr, lib_info);
+ break;
+ case DT_RELASZ:
+ info->rela_count = xtlib_xt_word(
+ xtlib_host_word(dyn_entry->d_un.d_val,
+ xtlib_globals->byteswap) /
+ sizeof(Elf32_Rela),
+ xtlib_globals->byteswap);
+ break;
+ case DT_INIT:
+ info->init = xt_ptr_offs(dst_addr,
+ dyn_entry->d_un.d_ptr, lib_info);
+ break;
+ case DT_FINI:
+ info->fini = xt_ptr_offs(dst_addr,
+ dyn_entry->d_un.d_ptr, lib_info);
+ break;
+ case DT_HASH:
+ info->hash = xt_ptr_offs(dst_data_addr,
+ dyn_entry->d_un.d_ptr, lib_info);
+ break;
+ case DT_SYMTAB:
+ info->symtab = xt_ptr_offs(dst_data_addr,
+ dyn_entry->d_un.d_ptr, lib_info);
+ break;
+ case DT_STRTAB:
+ info->strtab = xt_ptr_offs(dst_data_addr,
+ dyn_entry->d_un.d_ptr, lib_info);
+ break;
+ case DT_JMPREL:
+ jmprel = dyn_entry->d_un.d_val;
+ break;
+ case DT_PLTRELSZ:
+ pltrelsz = dyn_entry->d_un.d_val;
+ break;
+ case DT_LOPROC + 2:
+ info->text_addr = xt_ptr_offs(dst_addr,
+ dyn_entry->d_un.d_ptr, lib_info);
+ break;
+
+ default:
+ /* do nothing */
+ break;
+ }
+ dyn_entry++;
+ }
+
+ return XTLIB_NO_ERR;
+}
+
+static xt_ptr
+xtlib_load_split_pi_library_common(struct xtlib_packaged_library *library,
+ xt_ptr destination_code_address,
+ xt_ptr destination_data_address,
+ struct xtlib_pil_info *info,
+ struct lib_info *lib_info)
+{
+ struct xtlib_loader_globals *xtlib_globals =
+ &lib_info->xtlib_globals;
+ Elf32_Ehdr *header = (Elf32_Ehdr *)library;
+ Elf32_Phdr *pheader;
+ unsigned int align;
+ int err = validate_dynamic_splitload(header, lib_info);
+ xt_ptr destination_code_address_back;
+ xt_ptr destination_data_address_back;
+
+ if (err != XTLIB_NO_ERR) {
+ xtlib_globals->err = err;
+ return 0;
+ }
+
+ align = find_align(header, lib_info);
+
+ destination_code_address_back = destination_code_address;
+ destination_data_address_back = destination_data_address;
+
+ destination_code_address = align_ptr(destination_code_address, align);
+ destination_data_address = align_ptr(destination_data_address, align);
+ lib_info->code_buf_virt += (destination_code_address -
+ destination_code_address_back);
+ lib_info->data_buf_virt += (destination_data_address -
+ destination_data_address_back);
+
+ pheader = (Elf32_Phdr *)((char *)library +
+ xtlib_host_word(header->e_phoff,
+ xtlib_globals->byteswap));
+
+ err = get_dyn_info(header,
+ destination_code_address,
+ xtlib_host_word(pheader[0].p_paddr,
+ xtlib_globals->byteswap),
+ destination_data_address,
+ xtlib_host_word(pheader[1].p_paddr,
+ xtlib_globals->byteswap),
+ info,
+ lib_info);
+
+ if (err != XTLIB_NO_ERR) {
+ xtlib_globals->err = err;
+ return 0;
+ }
+
+ /* loading code */
+ xtlib_load_seg(&pheader[0],
+ (char *)library + xtlib_host_word(pheader[0].p_offset,
+ xtlib_globals->byteswap),
+ (xt_ptr)lib_info->code_buf_virt,
+ lib_info);
+
+ if (info->text_addr == 0)
+ info->text_addr =
+ (xt_ptr)xtlib_xt_word((Elf32_Word)destination_code_address,
+ xtlib_globals->byteswap);
+
+ /* loading data */
+ xtlib_load_seg(&pheader[1],
+ (char *)library + xtlib_host_word(pheader[1].p_offset,
+ xtlib_globals->byteswap),
+ (xt_ptr)lib_info->data_buf_virt +
+ xtlib_host_word(pheader[1].p_paddr,
+ xtlib_globals->byteswap),
+ lib_info);
+
+ return (xt_ptr)xtlib_host_word((Elf32_Word)info->start_sym,
+ xtlib_globals->byteswap);
+}
+
+static xt_ptr
+xtlib_host_load_split_pi_library(struct xtlib_packaged_library *library,
+ xt_ptr destination_code_address,
+ xt_ptr destination_data_address,
+ struct xtlib_pil_info *info,
+ struct lib_info *lib_info)
+{
+ return xtlib_load_split_pi_library_common(library,
+ destination_code_address,
+ destination_data_address,
+ info,
+ lib_info);
+}
+
+static long
+load_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy,
+ struct lib_info *lib_info)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy);
+ unsigned char *srambuf;
+ struct lib_dnld_info_t dpulib;
+ struct file *file;
+ struct xf_buffer *buf;
+ Elf32_Phdr *pheader;
+ Elf32_Ehdr *header;
+ loff_t pos = 0;
+ unsigned int align;
+ int filesize = 0;
+ long ret_val = 0;
+
+ file = filp_open(lib_info->filename, O_RDONLY, 0);
+ if (IS_ERR(file))
+ return PTR_ERR(file);
+
+ vfs_llseek(file, 0, SEEK_END);
+ filesize = (int)file->f_pos;
+
+ srambuf = kmalloc(filesize, GFP_KERNEL);
+ if (!srambuf)
+ return -ENOMEM;
+
+ vfs_llseek(file, 0, SEEK_SET);
+ ret_val = kernel_read(file, srambuf, filesize, &pos);
+ if (ret_val < 0)
+ return ret_val;
+ filp_close(file, NULL);
+
+ ret_val = xtlib_split_pi_library_size(
+ (struct xtlib_packaged_library *)(srambuf),
+ (unsigned int *)&dpulib.size_code,
+ (unsigned int *)&dpulib.size_data,
+ lib_info);
+ if (ret_val != XTLIB_NO_ERR)
+ return -EINVAL;
+
+ lib_info->code_buf_size = dpulib.size_code;
+ lib_info->data_buf_size = dpulib.size_data;
+
+ header = (Elf32_Ehdr *)srambuf;
+ pheader = (Elf32_Phdr *)((char *)srambuf +
+ xtlib_host_word(header->e_phoff,
+ lib_info->xtlib_globals.byteswap));
+
+ align = find_align(header, lib_info);
+ ret_val = xf_pool_alloc(client, proxy, 1, dpulib.size_code + align,
+ XF_POOL_AUX, &lib_info->code_section_pool);
+ if (ret_val) {
+ kfree(srambuf);
+ pr_err("Allocation failure for loading code section\n");
+ return -ENOMEM;
+ }
+
+ ret_val = xf_pool_alloc(client, proxy, 1,
+ dpulib.size_data + pheader[1].p_paddr + align,
+ XF_POOL_AUX, &lib_info->data_section_pool);
+ if (ret_val) {
+ kfree(srambuf);
+ pr_err("Allocation failure for loading data section\n");
+ return -ENOMEM;
+ }
+
+ buf = xf_buffer_get(lib_info->code_section_pool);
+ lib_info->code_buf_virt = xf_buffer_data(buf);
+ lib_info->code_buf_phys = ((u64)xf_buffer_data(buf) -
+ (u64)dsp_priv->scratch_buf_virt) +
+ dsp_priv->scratch_buf_phys;
+ lib_info->code_buf_size = dpulib.size_code + align;
+ xf_buffer_put(buf);
+
+ buf = xf_buffer_get(lib_info->data_section_pool);
+ lib_info->data_buf_virt = xf_buffer_data(buf);
+ lib_info->data_buf_phys = ((u64)xf_buffer_data(buf) -
+ (u64)dsp_priv->scratch_buf_virt) +
+ dsp_priv->scratch_buf_phys;
+ lib_info->data_buf_size = dpulib.size_data + align + pheader[1].p_paddr;
+ xf_buffer_put(buf);
+
+ dpulib.pbuf_code = (unsigned long)lib_info->code_buf_phys;
+ dpulib.pbuf_data = (unsigned long)lib_info->data_buf_phys;
+
+ dpulib.ppil_inf = &lib_info->pil_info;
+ xtlib_host_load_split_pi_library((struct xtlib_packaged_library *)srambuf,
+ (xt_ptr)(dpulib.pbuf_code),
+ (xt_ptr)(dpulib.pbuf_data),
+ (struct xtlib_pil_info *)dpulib.ppil_inf,
+ (void *)lib_info);
+ kfree(srambuf);
+
+ return ret_val;
+}
+
+static long
+unload_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy,
+ struct lib_info *lib_info)
+{
+ xf_pool_free(client, lib_info->code_section_pool);
+ xf_pool_free(client, lib_info->data_section_pool);
+
+ return 0;
+}
+
+long xf_load_lib(struct xf_client *client,
+ struct xf_handle *handle, struct lib_info *lib_info)
+{
+ void *b = xf_handle_aux(handle);
+ struct icm_xtlib_pil_info icm_info;
+ struct xf_proxy *proxy = handle->proxy;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+ long ret_val;
+
+ ret_val = load_dpu_with_library(client, proxy, lib_info);
+ if (ret_val)
+ return ret_val;
+
+ memcpy((void *)(&icm_info.pil_info), (void *)(&lib_info->pil_info),
+ sizeof(struct xtlib_pil_info));
+
+ icm_info.lib_type = lib_info->lib_type;
+
+ /* ...set message parameters */
+ msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), __XF_PORT_SPEC2(handle->id, 0));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_LOAD_LIB;
+ msg.buffer = b;
+ msg.length = sizeof(struct icm_xtlib_pil_info);
+ msg.ret = 0;
+
+ /* ...copy lib info */
+ memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux));
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+ if (IS_ERR(rmsg))
+ return PTR_ERR(rmsg);
+
+// xf_msg_free(proxy, rmsg);
+// xf_unlock(&proxy->lock);
+
+ return 0;
+}
+
+long xf_unload_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info)
+{
+ void *b = xf_handle_aux(handle);
+ struct xf_proxy *proxy = handle->proxy;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+ struct icm_xtlib_pil_info icm_info;
+
+ memset((void *)&icm_info, 0, sizeof(struct icm_xtlib_pil_info));
+ icm_info.lib_type = lib_info->lib_type;
+
+ /* ...set message parameters */
+ msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0),__XF_PORT_SPEC2(handle->id, 0));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_UNLOAD_LIB;
+ msg.buffer = b;
+ msg.length = sizeof(struct icm_xtlib_pil_info);
+ msg.ret = 0;
+
+ /* ...copy lib info */
+ memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux));
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+ if (IS_ERR(rmsg))
+ return PTR_ERR(rmsg);
+
+// xf_msg_free(proxy, rmsg);
+// xf_unlock(&proxy->lock);
+
+ return unload_dpu_with_library(client, proxy, lib_info);
+}
diff --git a/sound/soc/fsl/fsl_dsp_library_load.h b/sound/soc/fsl/fsl_dsp_library_load.h
new file mode 100644
index 000000000000..8c14dda20b27
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_library_load.h
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2018 NXP
+// Copyright (c) 2012-2013 by Tensilica Inc.
+
+#ifndef FSL_DSP_LIBRARY_LOAD_H
+#define FSL_DSP_LIBRARY_LOAD_H
+
+#include "fsl_dsp_pool.h"
+
+#define Elf32_Byte unsigned char
+#define xt_ptr unsigned long
+#define xt_int int
+#define xt_uint unsigned int
+#define xt_ulong unsigned long
+
+struct xtlib_packaged_library;
+
+enum {
+ XTLIB_NO_ERR = 0,
+ XTLIB_NOT_ELF = 1,
+ XTLIB_NOT_DYNAMIC = 2,
+ XTLIB_NOT_STATIC = 3,
+ XTLIB_NO_DYNAMIC_SEGMENT = 4,
+ XTLIB_UNKNOWN_SYMBOL = 5,
+ XTLIB_NOT_ALIGNED = 6,
+ XTLIB_NOT_SPLITLOAD = 7,
+ XTLIB_RELOCATION_ERR = 8
+};
+
+enum lib_type {
+ DSP_CODEC_LIB = 1,
+ DSP_CODEC_WRAP_LIB
+};
+
+struct xtlib_loader_globals {
+ int err;
+ int byteswap;
+};
+
+struct xtlib_pil_info {
+ xt_uint dst_addr;
+ xt_uint src_offs;
+ xt_uint dst_data_addr;
+ xt_uint src_data_offs;
+ xt_uint start_sym;
+ xt_uint text_addr;
+ xt_uint init;
+ xt_uint fini;
+ xt_uint rel;
+ xt_int rela_count;
+ xt_uint hash;
+ xt_uint symtab;
+ xt_uint strtab;
+ xt_int align;
+};
+
+struct icm_xtlib_pil_info {
+ struct xtlib_pil_info pil_info;
+ unsigned int lib_type;
+};
+
+struct lib_dnld_info_t {
+ unsigned long pbuf_code;
+ unsigned long pbuf_data;
+ unsigned int size_code;
+ unsigned int size_data;
+ struct xtlib_pil_info *ppil_inf;
+ unsigned int lib_on_dpu; /* 0: not loaded, 1: loaded. */
+};
+
+struct lib_info {
+ struct xtlib_pil_info pil_info;
+ struct xtlib_loader_globals xtlib_globals;
+
+ struct xf_pool *code_section_pool;
+ struct xf_pool *data_section_pool;
+
+ void *code_buf_virt;
+ unsigned int code_buf_phys;
+ unsigned int code_buf_size;
+ void *data_buf_virt;
+ unsigned int data_buf_phys;
+ unsigned int data_buf_size;
+
+ const char *filename;
+ unsigned int lib_type;
+};
+
+long xf_load_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info);
+long xf_unload_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info);
+
+#endif
diff --git a/sound/soc/fsl/fsl_dsp_platform.h b/sound/soc/fsl/fsl_dsp_platform.h
new file mode 100644
index 000000000000..15d085c9f23d
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_platform.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED.
+ * Copyright 2018 NXP
+ */
+
+#ifndef _FSL_DSP_PLATFORM_H
+#define _FSL_DSP_PLATFORM_H
+
+#include "fsl_dsp_xaf_api.h"
+
+struct dsp_data {
+ struct xf_client *client;
+ struct xaf_pipeline *p_pipe;
+ struct xaf_pipeline pipeline;
+ struct xaf_comp component[2];
+ int codec_type;
+ int status;
+};
+
+#endif /*_FSL_DSP_PLATFORM_H*/
diff --git a/sound/soc/fsl/fsl_dsp_platform_compress.c b/sound/soc/fsl/fsl_dsp_platform_compress.c
new file mode 100644
index 000000000000..43b07af3cbfe
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_platform_compress.c
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// DSP driver compress implementation
+//
+// Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED.
+// Copyright 2018 NXP
+
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/core.h>
+#include <sound/compress_driver.h>
+
+#include "fsl_dsp.h"
+#include "fsl_dsp_platform.h"
+#include "fsl_dsp_xaf_api.h"
+
+#define NUM_CODEC 2
+#define MIN_FRAGMENT 1
+#define MAX_FRAGMENT 1
+#define MIN_FRAGMENT_SIZE (4 * 1024)
+#define MAX_FRAGMENT_SIZE (4 * 1024)
+
+void dsp_platform_process(struct work_struct *w)
+{
+ struct xf_client *client = container_of(w, struct xf_client, work);
+ struct xf_proxy *proxy = client->proxy;
+ struct xf_message *rmsg;
+
+ while (1) {
+ rmsg = xf_cmd_recv(proxy, &client->wait, &client->queue, 1);
+
+ if (!proxy->is_active || IS_ERR(rmsg))
+ return;
+ if (rmsg->opcode == XF_EMPTY_THIS_BUFFER) {
+ client->consume_bytes += rmsg->length;
+ snd_compr_fragment_elapsed(client->cstream);
+
+ if (rmsg->buffer == NULL && rmsg->length == 0)
+ snd_compr_drain_notify(client->cstream);
+
+ } else {
+ memcpy(&client->m, rmsg, sizeof(struct xf_message));
+ complete(&client->compr_complete);
+ }
+
+ xf_msg_free(proxy, rmsg);
+ xf_unlock(&proxy->lock);
+ }
+}
+
+static int dsp_platform_compr_open(struct snd_compr_stream *cstream)
+{
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME);
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component);
+ struct dsp_data *drv = &dsp_priv->dsp_data;
+
+ drv->client = xf_client_alloc(dsp_priv);
+ if (IS_ERR(drv->client))
+ return PTR_ERR(drv->client);
+
+ fsl_dsp_open_func(dsp_priv, drv->client);
+
+ drv->client->proxy = &dsp_priv->proxy;
+
+ cpu_dai->driver->ops->startup(NULL, cpu_dai);
+
+ drv->client->cstream = cstream;
+
+ INIT_WORK(&drv->client->work, dsp_platform_process);
+
+ return 0;
+}
+
+static int dsp_platform_compr_free(struct snd_compr_stream *cstream)
+{
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME);
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component);
+ struct dsp_data *drv = &dsp_priv->dsp_data;
+ int ret;
+
+ if (cstream->runtime->state != SNDRV_PCM_STATE_PAUSED &&
+ cstream->runtime->state != SNDRV_PCM_STATE_RUNNING &&
+ cstream->runtime->state != SNDRV_PCM_STATE_DRAINING) {
+
+ ret = xaf_comp_delete(drv->client, &drv->component[1]);
+ if (ret) {
+ dev_err(component->dev, "Fail to delete component, err = %d\n", ret);
+ return ret;
+ }
+
+ ret = xaf_comp_delete(drv->client, &drv->component[0]);
+ if (ret) {
+ dev_err(component->dev, "Fail to delete component, err = %d\n", ret);
+ return ret;
+ }
+ }
+
+ cpu_dai->driver->ops->shutdown(NULL, cpu_dai);
+
+ drv->client->proxy->is_active = 0;
+ wake_up(&drv->client->wait);
+ cancel_work_sync(&drv->client->work);
+
+ fsl_dsp_close_func(drv->client);
+
+ return 0;
+}
+
+static int dsp_platform_compr_set_params(struct snd_compr_stream *cstream,
+ struct snd_compr_params *params)
+{
+ /* accroding to the params, load the library and create component*/
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME);
+ struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component);
+ struct dsp_data *drv = &dsp_priv->dsp_data;
+ struct xf_proxy *p_proxy = &dsp_priv->proxy;
+ struct xf_set_param_msg s_param;
+ int ret;
+
+ switch (params->codec.id) {
+ case SND_AUDIOCODEC_MP3:
+ drv->codec_type = CODEC_MP3_DEC;
+ break;
+ case SND_AUDIOCODEC_AAC:
+ drv->codec_type = CODEC_AAC_DEC;
+ break;
+ default:
+ dev_err(component->dev, "codec not supported, id =%d\n", params->codec.id);
+ return -EINVAL;
+ }
+
+ /* ...create auxiliary buffers pool for control commands */
+ ret = xf_pool_alloc(drv->client,
+ p_proxy,
+ XA_AUX_POOL_SIZE,
+ XA_AUX_POOL_MSG_LENGTH,
+ XF_POOL_AUX,
+ &p_proxy->aux);
+ if (ret) {
+ dev_err(component->dev, "xf_pool_alloc failed");
+ return ret;
+ }
+
+ /* ...create pipeline */
+ ret = xaf_pipeline_create(&drv->pipeline);
+ if (ret) {
+ dev_err(component->dev, "create pipeline error\n");
+ goto err_pool_alloc;
+ }
+
+ /* ...create component */
+ ret = xaf_comp_create(drv->client, p_proxy, &drv->component[0],
+ drv->codec_type);
+ if (ret) {
+ dev_err(component->dev,
+ "create component failed type = %d, err = %d\n",
+ drv->codec_type, ret);
+ goto err_pool_alloc;
+ }
+
+ ret = xaf_comp_create(drv->client, p_proxy, &drv->component[1],
+ RENDER_ESAI);
+ if (ret) {
+ dev_err(component->dev,
+ "create component failed, type = %d, err = %d\n",
+ RENDER_ESAI, ret);
+ goto err_comp0_create;
+ }
+
+ /* ...add component into pipeline */
+ ret = xaf_comp_add(&drv->pipeline, &drv->component[0]);
+ if (ret) {
+ dev_err(component->dev,
+ "add component failed, type = %d, err = %d\n",
+ drv->codec_type, ret);
+ goto err_comp1_create;
+ }
+
+ ret = xaf_comp_add(&drv->pipeline, &drv->component[1]);
+ if (ret) {
+ dev_err(component->dev,
+ "add component failed, type = %d, err = %d\n",
+ drv->codec_type, ret);
+ goto err_comp1_create;
+ }
+
+ drv->client->input_bytes = 0;
+ drv->client->consume_bytes = 0;
+
+ s_param.id = XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE;
+ s_param.mixData.value = params->codec.sample_rate;
+ ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param);
+ if (ret) {
+ dev_err(component->dev,
+ "set param[cmd:0x%x|val:0x%x] error, err = %d\n",
+ s_param.id, s_param.mixData.value, ret);
+ goto err_comp1_create;
+ }
+
+ s_param.id = XA_RENDERER_CONFIG_PARAM_CHANNELS;
+ s_param.mixData.value = params->codec.ch_out;
+ ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param);
+ if (ret) {
+ dev_err(component->dev,
+ "set param[cmd:0x%x|val:0x%x] error, err = %d\n",
+ s_param.id, s_param.mixData.value, ret);
+ goto err_comp1_create;
+ }
+
+ s_param.id = XA_RENDERER_CONFIG_PARAM_PCM_WIDTH;
+ s_param.mixData.value = 16;
+ ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param);
+ if (ret) {
+ dev_err(component->dev,
+ "set param[cmd:0x%x|val:0x%x] error, err = %d\n",
+ s_param.id, s_param.mixData.value, ret);
+ goto err_comp1_create;
+ }
+ return 0;
+
+err_comp1_create:
+ xaf_comp_delete(drv->client, &drv->component[1]);
+err_comp0_create:
+ xaf_comp_delete(drv->client, &drv->component[0]);
+err_pool_alloc:
+ xf_pool_free(drv->client, p_proxy->aux);
+
+ return ret;
+}
+
+static int dsp_platform_compr_trigger_start(struct snd_compr_stream *cstream)
+{
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME);
+ struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component);
+ struct dsp_data *drv = &dsp_priv->dsp_data;
+ struct xaf_comp *p_comp = &drv->component[0];
+ int ret;
+
+ ret = xaf_comp_process(drv->client,
+ p_comp,
+ p_comp->inptr,
+ drv->client->input_bytes,
+ XF_EMPTY_THIS_BUFFER);
+
+ ret = xaf_connect(drv->client,
+ &drv->component[0],
+ &drv->component[1],
+ 1,
+ OUTBUF_SIZE);
+ if (ret) {
+ dev_err(component->dev, "Failed to connect component, err = %d\n", ret);
+ return ret;
+ }
+
+ schedule_work(&drv->client->work);
+
+ return 0;
+}
+
+static int dsp_platform_compr_trigger_stop(struct snd_compr_stream *cstream)
+{
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME);
+ struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component);
+ struct dsp_data *drv = &dsp_priv->dsp_data;
+ int ret;
+
+ ret = xaf_comp_flush(drv->client, &drv->component[0]);
+ if (ret) {
+ dev_err(component->dev, "Fail to flush component, err = %d\n", ret);
+ return ret;
+ }
+
+ ret = xaf_comp_flush(drv->client, &drv->component[1]);
+ if (ret) {
+ dev_err(component->dev, "Fail to flush component, err = %d\n", ret);
+ return ret;
+ }
+
+ ret = xaf_comp_delete(drv->client, &drv->component[0]);
+ if (ret) {
+ dev_err(component->dev, "Fail to delete component, err = %d\n", ret);
+ return ret;
+ }
+
+ ret = xaf_comp_delete(drv->client, &drv->component[1]);
+ if (ret) {
+ dev_err(component->dev, "Fail to delete component, err = %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dsp_platform_compr_trigger_drain(struct snd_compr_stream *cstream)
+{
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME);
+ struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component);
+ struct dsp_data *drv = &dsp_priv->dsp_data;
+ struct xaf_comp *p_comp = &drv->component[0];
+ int ret;
+
+ ret = xaf_comp_process(drv->client, p_comp, NULL, 0,
+ XF_EMPTY_THIS_BUFFER);
+
+ schedule_work(&drv->client->work);
+ return 0;
+}
+
+static int dsp_platform_compr_trigger(struct snd_compr_stream *cstream, int cmd)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = dsp_platform_compr_trigger_start(cstream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = dsp_platform_compr_trigger_stop(cstream);
+ break;
+ case SND_COMPR_TRIGGER_DRAIN:
+ ret = dsp_platform_compr_trigger_drain(cstream);
+ break;
+ case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ break;
+ }
+
+ /*send command*/
+ return ret;
+}
+
+static int dsp_platform_compr_pointer(struct snd_compr_stream *cstream,
+ struct snd_compr_tstamp *tstamp)
+{
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME);
+ struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component);
+ struct dsp_data *drv = &dsp_priv->dsp_data;
+
+ tstamp->copied_total = drv->client->input_bytes;
+ tstamp->byte_offset = drv->client->input_bytes;
+ tstamp->pcm_frames = 0x900;
+ tstamp->pcm_io_frames = 0,
+ tstamp->sampling_rate = 48000;
+
+ return 0;
+}
+
+static int dsp_platform_compr_copy(struct snd_compr_stream *cstream,
+ char __user *buf,
+ size_t count)
+{
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME);
+ struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component);
+ struct dsp_data *drv = &dsp_priv->dsp_data;
+ struct xaf_comp *p_comp = &drv->component[0];
+ int copied = 0;
+ int ret;
+
+ if (drv->client->input_bytes == drv->client->consume_bytes) {
+ if (count > INBUF_SIZE){
+ ret = copy_from_user(p_comp->inptr, buf, INBUF_SIZE);
+ if (ret) {
+ dev_err(component->dev, "failed to get message from user space\n");
+ return -EFAULT;
+ }
+ copied = INBUF_SIZE;
+ } else {
+ ret = copy_from_user(p_comp->inptr, buf, count);
+ if (ret) {
+ dev_err(component->dev, "failed to get message from user space\n");
+ return -EFAULT;
+ }
+ copied = count;
+ }
+ drv->client->input_bytes += copied;
+
+ if (cstream->runtime->state == SNDRV_PCM_STATE_RUNNING) {
+ ret = xaf_comp_process(drv->client, p_comp,
+ p_comp->inptr, copied,
+ XF_EMPTY_THIS_BUFFER);
+ schedule_work(&drv->client->work);
+ }
+ }
+
+ return copied;
+}
+
+static int dsp_platform_compr_get_caps(struct snd_compr_stream *cstream,
+ struct snd_compr_caps *caps)
+{
+ caps->num_codecs = NUM_CODEC;
+ caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */
+ caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */
+ caps->min_fragments = MIN_FRAGMENT;
+ caps->max_fragments = MAX_FRAGMENT;
+ caps->codecs[0] = SND_AUDIOCODEC_MP3;
+ caps->codecs[1] = SND_AUDIOCODEC_AAC;
+
+ return 0;
+}
+
+static struct snd_compr_codec_caps caps_mp3 = {
+ .num_descriptors = 1,
+ .descriptor[0].max_ch = 2,
+ .descriptor[0].sample_rates[0] = 48000,
+ .descriptor[0].sample_rates[1] = 44100,
+ .descriptor[0].sample_rates[2] = 32000,
+ .descriptor[0].sample_rates[3] = 16000,
+ .descriptor[0].sample_rates[4] = 8000,
+ .descriptor[0].num_sample_rates = 5,
+ .descriptor[0].bit_rate[0] = 320,
+ .descriptor[0].bit_rate[1] = 192,
+ .descriptor[0].num_bitrates = 2,
+ .descriptor[0].profiles = 0,
+ .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO,
+ .descriptor[0].formats = 0,
+};
+
+static struct snd_compr_codec_caps caps_aac = {
+ .num_descriptors = 2,
+ .descriptor[1].max_ch = 2,
+ .descriptor[0].sample_rates[0] = 48000,
+ .descriptor[0].sample_rates[1] = 44100,
+ .descriptor[0].sample_rates[2] = 32000,
+ .descriptor[0].sample_rates[3] = 16000,
+ .descriptor[0].sample_rates[4] = 8000,
+ .descriptor[0].num_sample_rates = 5,
+ .descriptor[1].bit_rate[0] = 320,
+ .descriptor[1].bit_rate[1] = 192,
+ .descriptor[1].num_bitrates = 2,
+ .descriptor[1].profiles = 0,
+ .descriptor[1].modes = 0,
+ .descriptor[1].formats =
+ (SND_AUDIOSTREAMFORMAT_MP4ADTS |
+ SND_AUDIOSTREAMFORMAT_RAW),
+};
+
+static int dsp_platform_compr_get_codec_caps(struct snd_compr_stream *cstream,
+ struct snd_compr_codec_caps *codec)
+{
+ if (codec->codec == SND_AUDIOCODEC_MP3)
+ *codec = caps_mp3;
+ else if (codec->codec == SND_AUDIOCODEC_AAC)
+ *codec = caps_aac;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int dsp_platform_compr_set_metadata(struct snd_compr_stream *cstream,
+ struct snd_compr_metadata *metadata)
+{
+ return 0;
+}
+
+const struct snd_compr_ops dsp_platform_compr_ops = {
+ .open = dsp_platform_compr_open,
+ .free = dsp_platform_compr_free,
+ .set_params = dsp_platform_compr_set_params,
+ .set_metadata = dsp_platform_compr_set_metadata,
+ .trigger = dsp_platform_compr_trigger,
+ .pointer = dsp_platform_compr_pointer,
+ .copy = dsp_platform_compr_copy,
+ .get_caps = dsp_platform_compr_get_caps,
+ .get_codec_caps = dsp_platform_compr_get_codec_caps,
+};
diff --git a/sound/soc/fsl/fsl_dsp_pool.c b/sound/soc/fsl/fsl_dsp_pool.c
new file mode 100644
index 000000000000..637454d97231
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_pool.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Xtensa buffer pool API
+//
+// Copyright 2018 NXP
+// Copyright (c) 2012-2013 by Tensilica Inc.
+
+#include <linux/slab.h>
+
+#include "fsl_dsp_pool.h"
+#include "fsl_dsp.h"
+
+/* ...allocate buffer pool */
+int xf_pool_alloc(struct xf_client *client, struct xf_proxy *proxy,
+ u32 number, u32 length, xf_pool_type_t type,
+ struct xf_pool **pool)
+{
+ struct xf_pool *p;
+ struct xf_buffer *b;
+ void *data;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+
+ /* ...basic sanity checks; number of buffers is positive */
+ if (number <=0)
+ return -EINVAL;
+
+ /* ...get properly aligned buffer length */
+ length = ALIGN(length, XF_PROXY_ALIGNMENT);
+
+ p = kzalloc(offsetof(struct xf_pool, buffer) +
+ number * sizeof(struct xf_buffer), GFP_KERNEL);
+ if(!p)
+ return -ENOMEM;
+
+ /* ...prepare command parameters */
+ msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_ALLOC;
+ msg.length = length * number;
+ msg.buffer = NULL;
+ msg.ret = 0;
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+ if (IS_ERR(rmsg)) {
+ kfree(p);
+ return PTR_ERR(rmsg);
+ }
+
+ p->p = rmsg->buffer;
+ /* TODO: review cleanup */
+ /* xf_msg_free(proxy, rmsg);
+ * xf_unlock(&proxy->lock); */
+
+ /* ...if operation is failed, do cleanup */
+ /* ...set pool parameters */
+ p->number = number, p->length = length;
+ p->proxy = proxy;
+
+ /* ...create individual buffers and link them into free list */
+ for (p->free = b = &p->buffer[0], data = p->p; number > 0;
+ number--, b++) {
+ /* ...set address of the buffer (no length there) */
+ b->address = data;
+
+ /* ...file buffer into the free list */
+ b->link.next = b + 1;
+
+ /* ...advance data pointer in contiguous buffer */
+ data += length;
+ }
+
+ /* ...terminate list of buffers (not too good - tbd) */
+ b[-1].link.next = NULL;
+
+ /* ...return buffer pointer */
+ *pool = p;
+
+ return 0;
+}
+/* ...buffer pool destruction */
+int xf_pool_free(struct xf_client *client, struct xf_pool *pool)
+{
+ struct xf_proxy *proxy;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+
+ /* ...basic sanity checks; pool is positive */
+ if (pool == NULL)
+ return -EINVAL;
+
+ /* ...get proxy pointer */
+ if ((proxy = pool->proxy) == NULL)
+ return -EINVAL;
+
+ /* ...prepare command parameters */
+ msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_FREE;
+ msg.length = pool->length * pool->number;
+ msg.buffer = pool->p;
+ msg.ret = 0;
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+ kfree(pool);
+ if (IS_ERR(rmsg))
+ return PTR_ERR(rmsg);
+
+ /* TODO: review cleanup */
+ /* xf_msg_free(proxy, rmsg);
+ * xf_unlock(&proxy->lock); */
+
+ return 0;
+}
+
+/* ...get new buffer from a pool */
+struct xf_buffer *xf_buffer_get(struct xf_pool *pool)
+{
+ struct xf_buffer *b;
+
+ xf_lock(&pool->proxy->lock);
+ /* ...take buffer from a head of the free list */
+ b = pool->free;
+ if (b) {
+ /* ...advance free list head */
+ pool->free = b->link.next, b->link.pool = pool;
+ }
+
+ xf_unlock(&pool->proxy->lock);
+ return b;
+}
+
+/* ...return buffer back to pool */
+void xf_buffer_put(struct xf_buffer *buffer)
+{
+ struct xf_pool *pool = buffer->link.pool;
+
+ xf_lock(&pool->proxy->lock);
+ /* ...use global proxy lock for pool operations protection */
+ /* ...put buffer back to a pool */
+ buffer->link.next = pool->free, pool->free = buffer;
+
+ xf_unlock(&pool->proxy->lock);
+}
diff --git a/sound/soc/fsl/fsl_dsp_pool.h b/sound/soc/fsl/fsl_dsp_pool.h
new file mode 100644
index 000000000000..4a56262faf7f
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_pool.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Xtensa buffer pool API header
+ *
+ * Copyright 2018 NXP
+ * Copyright (c) 2012-2013 by Tensilica Inc
+ */
+#ifndef FSL_DSP_POOL_H
+#define FSL_DSP_POOL_H
+
+#include <linux/types.h>
+#include "fsl_dsp_proxy.h"
+
+/* ...buffer pool type */
+typedef u32 xf_pool_type_t;
+
+/* ...previous declaration of struct */
+struct xf_buffer;
+struct xf_pool;
+struct xf_handle;
+struct xf_message;
+struct xf_client;
+
+/* ...response callback */
+typedef void (*xf_response_cb)(struct xf_handle *h, struct xf_message *msg);
+
+/* ...buffer pool type */
+enum xf_pool_type {
+ XF_POOL_AUX = 0,
+ XF_POOL_INPUT = 1,
+ XF_POOL_OUTPUT = 2
+};
+
+/* ...buffer link pointer */
+union xf_buffer_link {
+ /* ...pointer to next free buffer in a pool (for free buffer) */
+ struct xf_buffer *next;
+ /* ...reference to a buffer pool (for allocated buffer) */
+ struct xf_pool *pool;
+};
+
+/* ...buffer descriptor */
+struct xf_buffer {
+ /* ...virtual address of contiguous buffer */
+ void *address;
+ /* ...link pointer */
+ union xf_buffer_link link;
+};
+
+/* ...buffer pool */
+struct xf_pool {
+ /* ...reference to proxy data */
+ struct xf_proxy *proxy;
+ /* ...length of individual buffer in a pool */
+ u32 length;
+ /* ...number of buffers in a pool */
+ u32 number;
+ /* ...pointer to pool memory */
+ void *p;
+ /* ...pointer to first free buffer in a pool */
+ struct xf_buffer *free;
+ /* ...individual buffers */
+ struct xf_buffer buffer[0];
+};
+
+/* component handle */
+struct xf_handle {
+ /* ...reference to proxy data */
+ struct xf_proxy *proxy;
+ /* ...auxiliary control buffer for control transactions */
+ struct xf_buffer *aux;
+ /* ...global client-id of the component */
+ u32 id;
+ /* ...local client number (think about merging into "id" field - tbd) */
+ u32 client;
+ /* ...response processing hook */
+ xf_response_cb response;
+};
+
+/* ...accessor to buffer data */
+static inline void *xf_buffer_data(struct xf_buffer *buffer)
+{
+ return buffer->address;
+}
+
+/* ...length of buffer data */
+static inline size_t xf_buffer_length(struct xf_buffer *buffer)
+{
+ struct xf_pool *pool = buffer->link.pool;
+
+ return (size_t)pool->length;
+}
+
+/* ...component client-id (global scope) */
+static inline u32 xf_handle_id(struct xf_handle *handle)
+{
+ return handle->id;
+}
+
+/* ...pointer to auxiliary buffer */
+static inline void *xf_handle_aux(struct xf_handle *handle)
+{
+ return xf_buffer_data(handle->aux);
+}
+
+int xf_pool_alloc(struct xf_client *client, struct xf_proxy *proxy, u32 number,
+ u32 length, xf_pool_type_t type, struct xf_pool **pool);
+int xf_pool_free(struct xf_client *client, struct xf_pool *pool);
+
+struct xf_buffer *xf_buffer_get(struct xf_pool *pool);
+void xf_buffer_put(struct xf_buffer *buffer);
+
+#endif /* FSL_DSP_POOL_H */
diff --git a/sound/soc/fsl/fsl_dsp_proxy.c b/sound/soc/fsl/fsl_dsp_proxy.c
new file mode 100644
index 000000000000..266a50deab91
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_proxy.c
@@ -0,0 +1,858 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+//
+// DSP proxy driver transfers messages between DSP driver and DSP framework
+//
+// Copyright 2018 NXP
+// Copyright (C) 2017 Cadence Design Systems, Inc.
+
+#include "fsl_dsp_proxy.h"
+#include "fsl_dsp.h"
+
+/* ...initialize message queue */
+void xf_msg_queue_init(struct xf_msg_queue *queue)
+{
+ queue->head = queue->tail = NULL;
+}
+
+/* ...get message queue head */
+struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue)
+{
+ return queue->head;
+}
+
+/* ...allocate new message from the pool */
+struct xf_message *xf_msg_alloc(struct xf_proxy *proxy)
+{
+ struct xf_message *m = proxy->free;
+
+ /* ...make sure we have a free message item */
+ if (m != NULL) {
+ /* ...get message from the pool */
+ proxy->free = m->next, m->next = NULL;
+ }
+
+ return m;
+}
+
+/* ...return message to the pool of free items */
+void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m)
+{
+ /* ...put message into the head of free items list */
+ m->next = proxy->free, proxy->free = m;
+
+ /* ...notify potential client waiting for message */
+ wake_up(&proxy->busy);
+}
+
+/* ...return all messages from the queue to the pool of free items */
+void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue)
+{
+ struct xf_message *m = queue->head;
+
+ /* ...check if there is anything in the queue */
+ if (m != NULL) {
+ queue->tail->next = proxy->free;
+ proxy->free = queue->head;
+ queue->head = queue->tail = NULL;
+
+ /* ...notify potential client waiting for message */
+ wake_up(&proxy->busy);
+ }
+}
+
+/* ...submit message to a queue */
+int xf_msg_enqueue(struct xf_msg_queue *queue, struct xf_message *m)
+{
+ int first = (queue->head == NULL);
+
+ /* ...set pointer to next item */
+ m->next = NULL;
+
+ /* ...advance head/tail pointer as required */
+ if (first)
+ queue->head = m;
+ else
+ queue->tail->next = m;
+
+ /* ...new tail points to this message */
+ queue->tail = m;
+
+ return first;
+}
+
+/* ...retrieve next message from the per-task queue */
+struct xf_message *xf_msg_dequeue(struct xf_msg_queue *queue)
+{
+ struct xf_message *m = queue->head;
+
+ /* ...check if there is anything in the queue */
+ if (m != NULL) {
+ /* ...pop message from the head of the list */
+ queue->head = m->next;
+ if (queue->head == NULL)
+ queue->tail = NULL;
+ }
+
+ return m;
+}
+
+/* ...helper function for requesting execution message from a pool */
+struct xf_message *xf_msg_available(struct xf_proxy *proxy)
+{
+ struct xf_message *m;
+
+ /* ...acquire global lock */
+ xf_lock(&proxy->lock);
+
+ /* ...try to allocate the message */
+ m = xf_msg_alloc(proxy);
+ if (m == NULL) {
+ /* ...failed to allocate message; release lock */
+ xf_unlock(&proxy->lock);
+ }
+
+ /* ...if successfully allocated */
+ return m;
+}
+
+/* ...helper function for receiving a message from per-client queue */
+struct xf_message *xf_msg_received(struct xf_proxy *proxy,
+ struct xf_msg_queue *queue)
+{
+ struct xf_message *m;
+
+ /* ...acquire global lock */
+ xf_lock(&proxy->lock);
+
+ /* ...try to peek message from the queue */
+ m = xf_msg_dequeue(queue);
+ if (m == NULL) {
+ /* ...queue is empty; release lock */
+ xf_unlock(&proxy->lock);
+ }
+
+ /* ...if message is non-null, lock is held */
+ return m;
+}
+
+/*
+ * MU related functions
+ */
+u32 icm_intr_send(struct xf_proxy *proxy, u32 msg)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+
+ MU_SendMessage(dsp_priv->mu_base_virtaddr, 0, msg);
+ return 0;
+}
+
+int icm_intr_extended_send(struct xf_proxy *proxy,
+ u32 msg,
+ struct dsp_ext_msg *ext_msg)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ struct device *dev = dsp_priv->dev;
+ union icm_header_t msghdr;
+
+ msghdr.allbits = msg;
+ if (msghdr.size != 8)
+ dev_err(dev, "too much ext msg\n");
+
+ MU_SendMessage(dsp_priv->mu_base_virtaddr, 1, ext_msg->phys);
+ MU_SendMessage(dsp_priv->mu_base_virtaddr, 2, ext_msg->size);
+ MU_SendMessage(dsp_priv->mu_base_virtaddr, 0, msg);
+
+ return 0;
+}
+
+int send_dpu_ext_msg_addr(struct xf_proxy *proxy)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ union icm_header_t msghdr;
+ struct dsp_ext_msg ext_msg;
+ struct dsp_mem_msg *dpu_ext_msg =
+ (struct dsp_mem_msg *)((unsigned char *)dsp_priv->msg_buf_virt
+ + (MSG_BUF_SIZE / 2));
+ int ret_val = 0;
+
+ msghdr.allbits = 0; /* clear all bits; */
+ msghdr.ack = 0;
+ msghdr.intr = 1;
+ msghdr.msg = ICM_CORE_INIT;
+ msghdr.size = 8;
+ ext_msg.phys = dsp_priv->msg_buf_phys + (MSG_BUF_SIZE / 2);
+ ext_msg.size = sizeof(struct dsp_mem_msg);
+
+ dpu_ext_msg->ext_msg_phys = dsp_priv->msg_buf_phys;
+ dpu_ext_msg->ext_msg_size = MSG_BUF_SIZE;
+ dpu_ext_msg->scratch_phys = dsp_priv->scratch_buf_phys;
+ dpu_ext_msg->scratch_size = dsp_priv->scratch_buf_size;
+ dpu_ext_msg->dsp_config_phys = dsp_priv->dsp_config_phys;
+ dpu_ext_msg->dsp_config_size = dsp_priv->dsp_config_size;
+ dpu_ext_msg->dsp_board_type = dsp_priv->dsp_board_type;
+
+ icm_intr_extended_send(proxy, msghdr.allbits, &ext_msg);
+
+ return ret_val;
+}
+
+long icm_ack_wait(struct xf_proxy *proxy, u32 msg)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ struct device *dev = dsp_priv->dev;
+ union icm_header_t msghdr;
+ int err;
+
+ msghdr.allbits = msg;
+ /* wait response from mu */
+ err = wait_for_completion_timeout(&proxy->cmd_complete,
+ msecs_to_jiffies(1000));
+ if (!err) {
+ dev_err(dev, "icm ack timeout! %x\n", msg);
+ return -ETIMEDOUT;
+ }
+
+ dev_dbg(dev, "Ack recd for message 0x%08x\n", msghdr.allbits);
+
+ return 0;
+}
+
+irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id)
+{
+ struct xf_proxy *proxy = dev_id;
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ struct device *dev = dsp_priv->dev;
+ union icm_header_t msghdr;
+ u32 reg;
+
+ MU_ReceiveMsg(dsp_priv->mu_base_virtaddr, 0, &reg);
+ msghdr = (union icm_header_t)reg;
+
+ if (msghdr.intr == 1) {
+ dev_dbg(dev, "INTR: Received ICM intr, msg 0x%08x\n",
+ msghdr.allbits);
+ switch (msghdr.msg) {
+ case ICM_CORE_EXIT:
+ break;
+ case ICM_CORE_READY:
+ send_dpu_ext_msg_addr(proxy);
+ proxy->is_ready = 1;
+ complete(&proxy->cmd_complete);
+ break;
+ case XF_SUSPEND:
+ case XF_RESUME:
+ complete(&proxy->cmd_complete);
+ break;
+ default:
+ schedule_work(&proxy->work);
+ break;
+ }
+ } else if (msghdr.ack == 1) {
+ dev_dbg(dev, "INTR: Received ICM ack 0x%08x\n", msghdr.size);
+ msghdr.ack = 0;
+ } else {
+ dev_dbg(dev, "Received false ICM intr 0x%08x\n",
+ msghdr.allbits);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Proxy related functions
+ */
+/* ...NULL-address specification */
+#define XF_PROXY_NULL (~0U)
+
+#define XF_PROXY_BADADDR (dsp_priv->scratch_buf_size)
+
+/* ...shared memory translation - kernel virtual address to shared address */
+u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+
+ if (b == NULL)
+ return XF_PROXY_NULL;
+ else if ((u32)(b - dsp_priv->scratch_buf_virt) <
+ dsp_priv->scratch_buf_size)
+ return (u32)(b - dsp_priv->scratch_buf_virt);
+ else
+ return XF_PROXY_BADADDR;
+}
+
+/* ...shared memory translation - shared address to kernel virtual address */
+void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+
+ if (address < dsp_priv->scratch_buf_size)
+ return dsp_priv->scratch_buf_virt + address;
+ else if (address == XF_PROXY_NULL)
+ return NULL;
+ else
+ return (void *) -1;
+}
+
+/* ...process association between response received and intended client */
+static void xf_cmap(struct xf_proxy *proxy, struct xf_message *m)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ u32 id = XF_AP_IPC_CLIENT(m->id);
+ struct xf_client *client;
+
+ /* ...process messages addressed to proxy itself */
+ if (id == 0) {
+ /* ...place message into local response queue */
+ xf_msg_enqueue(&proxy->response, m);
+ wake_up(&proxy->wait);
+ return;
+ }
+
+ /* ...make sure the client ID is sane */
+ client = xf_client_lookup(dsp_priv, id);
+ if (!client) {
+ pr_err("rsp[id:%08x]: client lookup failed", m->id);
+ xf_msg_free(proxy, m);
+ return;
+ }
+
+ /* ...make sure client is bound to this proxy interface */
+ if (client->proxy != proxy) {
+ pr_err("rsp[id:%08x]: wrong proxy interface", m->id);
+ xf_msg_free(proxy, m);
+ return;
+ }
+
+ /* ...place message into local response queue */
+ if (xf_msg_enqueue(&client->queue, m))
+ wake_up(&client->wait);
+}
+
+/* ...retrieve pending responses from shared memory ring-buffer */
+static u32 xf_shmem_process_responses(struct xf_proxy *proxy)
+{
+ struct xf_message *m;
+ u32 read_idx, write_idx;
+ int status;
+
+ status = 0;
+
+ /* ...get current values of read/write pointers in response queue */
+ read_idx = XF_PROXY_READ(proxy, rsp_read_idx);
+ write_idx = XF_PROXY_READ(proxy, rsp_write_idx);
+
+ /* ...process all committed responses */
+ while (!XF_QUEUE_EMPTY(read_idx, write_idx)) {
+ struct xf_proxy_message *response;
+
+ /* ...allocate execution message */
+ m = xf_msg_alloc(proxy);
+ if (m == NULL)
+ break;
+
+ /* ...mark the interface status has changed */
+ status |= (XF_QUEUE_FULL(read_idx, write_idx) ? 0x3 : 0x1);
+
+ /* ...get oldest not yet processed response */
+ response = XF_PROXY_RESPONSE(proxy, XF_QUEUE_IDX(read_idx));
+
+ /* ...fill message parameters */
+ m->id = response->session_id;
+ m->opcode = response->opcode;
+ m->length = response->length;
+ m->buffer = xf_proxy_a2b(proxy, response->address);
+ m->ret = response->ret;
+
+ /* ...advance local reading index copy */
+ read_idx = XF_QUEUE_ADVANCE_IDX(read_idx);
+
+ /* ...update shadow copy of reading index */
+ XF_PROXY_WRITE(proxy, rsp_read_idx, read_idx);
+
+ /* ...submit message to proper client */
+ xf_cmap(proxy, m);
+ }
+
+ return status;
+}
+
+/* ...put pending commands into shared memory ring-buffer */
+static u32 xf_shmem_process_commands(struct xf_proxy *proxy)
+{
+ struct xf_message *m;
+ u32 read_idx, write_idx;
+ int status = 0;
+
+ /* ...get current value of peer read pointer */
+ write_idx = XF_PROXY_READ(proxy, cmd_write_idx);
+ read_idx = XF_PROXY_READ(proxy, cmd_read_idx);
+
+ /* ...submit any pending commands */
+ while (!XF_QUEUE_FULL(read_idx, write_idx)) {
+ struct xf_proxy_message *command;
+
+ /* ...check if we have a pending command */
+ m = xf_msg_dequeue(&proxy->command);
+ if (m == NULL)
+ break;
+
+ /* ...always mark the interface status has changed */
+ status |= 0x3;
+
+ /* ...select the place for the command */
+ command = XF_PROXY_COMMAND(proxy, XF_QUEUE_IDX(write_idx));
+
+ /* ...put the response message fields */
+ command->session_id = m->id;
+ command->opcode = m->opcode;
+ command->length = m->length;
+ command->address = xf_proxy_b2a(proxy, m->buffer);
+ command->ret = m->ret;
+
+ /* ...return message back to the pool */
+ xf_msg_free(proxy, m);
+
+ /* ...advance local writing index copy */
+ write_idx = XF_QUEUE_ADVANCE_IDX(write_idx);
+
+ /* ...update shared copy of queue write pointer */
+ XF_PROXY_WRITE(proxy, cmd_write_idx, write_idx);
+ }
+
+ if (status)
+ icm_intr_send(proxy, 0);
+
+ return status;
+}
+
+/* ...shared memory interface maintenance routine */
+void xf_proxy_process(struct work_struct *w)
+{
+ struct xf_proxy *proxy = container_of(w, struct xf_proxy, work);
+ int status = 0;
+
+ /* ...get exclusive access to internal data */
+ xf_lock(&proxy->lock);
+
+ do {
+ /* ...process outgoing commands first */
+ status = xf_shmem_process_commands(proxy);
+
+ /* ...process all pending responses */
+ status |= xf_shmem_process_responses(proxy);
+
+ } while (status);
+
+ /* ...unlock internal proxy data */
+ xf_unlock(&proxy->lock);
+}
+
+/* ...initialize shared memory interface */
+int xf_proxy_init(struct xf_proxy *proxy)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ struct xf_message *m;
+ int i;
+
+ /* ...create a list of all messages in a pool; set head pointer */
+ proxy->free = &proxy->pool[0];
+
+ /* ...put all messages into a single-linked list */
+ for (i = 0, m = proxy->free; i < XF_CFG_MESSAGE_POOL_SIZE - 1; i++, m++)
+ m->next = m + 1;
+
+ /* ...set list tail pointer */
+ m->next = NULL;
+
+ /* ...initialize proxy lock */
+ xf_lock_init(&proxy->lock);
+
+ /* ...initialize proxy thread message queues */
+ xf_msg_queue_init(&proxy->command);
+ xf_msg_queue_init(&proxy->response);
+
+ /* ...initialize global busy queue */
+ init_waitqueue_head(&proxy->busy);
+ init_waitqueue_head(&proxy->wait);
+
+ /* ...create work structure */
+ INIT_WORK(&proxy->work, xf_proxy_process);
+
+ /* ...set pointer to shared memory */
+ proxy->ipc.shmem = (struct xf_shmem_data *)dsp_priv->msg_buf_virt;
+
+ /* ...initialize shared memory interface */
+ XF_PROXY_WRITE(proxy, cmd_read_idx, 0);
+ XF_PROXY_WRITE(proxy, cmd_write_idx, 0);
+ XF_PROXY_WRITE(proxy, cmd_invalid, 0);
+ XF_PROXY_WRITE(proxy, rsp_read_idx, 0);
+ XF_PROXY_WRITE(proxy, rsp_write_idx, 0);
+ XF_PROXY_WRITE(proxy, rsp_invalid, 0);
+
+ return 0;
+}
+
+/* ...trigger shared memory interface processing */
+void xf_proxy_notify(struct xf_proxy *proxy)
+{
+ schedule_work(&proxy->work);
+}
+
+/* ...submit a command to proxy pending queue (lock released upon return) */
+void xf_proxy_command(struct xf_proxy *proxy, struct xf_message *m)
+{
+ int first;
+
+ /* ...submit message to proxy thread */
+ first = xf_msg_enqueue(&proxy->command, m);
+
+ /* ...release the lock */
+ xf_unlock(&proxy->lock);
+
+ /* ...notify thread about command reception */
+ (first ? xf_proxy_notify(proxy), 1 : 0);
+}
+
+/*
+ * Proxy cmd send and receive functions
+ */
+int xf_cmd_send(struct xf_proxy *proxy,
+ u32 id,
+ u32 opcode,
+ void *buffer,
+ u32 length)
+{
+ struct xf_message *m;
+ int ret;
+
+ /* ...retrieve message handle (take the lock on success) */
+ ret = wait_event_interruptible(proxy->busy,
+ (m = xf_msg_available(proxy)) != NULL);
+ if (ret)
+ return -EINTR;
+
+ /* ...fill-in message parameters (lock is taken) */
+ m->id = id;
+ m->opcode = opcode;
+ m->length = length;
+ m->buffer = buffer;
+ m->ret = 0;
+
+ /* ...submit command to the proxy */
+ xf_proxy_command(proxy, m);
+
+ return 0;
+}
+
+struct xf_message *xf_cmd_recv(struct xf_proxy *proxy,
+ wait_queue_head_t *wq,
+ struct xf_msg_queue *queue,
+ int wait)
+{
+ struct xf_message *m = NULL;
+ int ret;
+
+ /* ...wait for message reception (take lock on success) */
+ ret = wait_event_interruptible(*wq,
+ (m = xf_msg_received(proxy, queue)) != NULL || !wait
+ || !proxy->is_active);
+ if (ret)
+ return ERR_PTR(-EINTR);
+
+ /* ...return message with a lock taken */
+ return m;
+}
+
+struct xf_message *xf_cmd_recv_timeout(struct xf_proxy *proxy,
+ wait_queue_head_t *wq,
+ struct xf_msg_queue *queue, int wait)
+{
+ struct xf_message *m;
+ int ret;
+
+ /* ...wait for message reception (take lock on success) */
+ ret = wait_event_interruptible_timeout(*wq,
+ (m = xf_msg_received(proxy, queue)) != NULL || !wait,
+ msecs_to_jiffies(1000));
+ if (ret < 0)
+ return ERR_PTR(-EINTR);
+
+ if (ret == 0)
+ return ERR_PTR(-ETIMEDOUT);
+
+ /* ...return message with a lock taken */
+ return m;
+}
+
+/* ...helper function for synchronous command execution */
+struct xf_message *xf_cmd_send_recv(struct xf_proxy *proxy,
+ u32 id, u32 opcode,
+ void *buffer,
+ u32 length)
+{
+ int ret;
+
+ /* ...send command to remote proxy */
+ ret = xf_cmd_send(proxy, id, opcode, buffer, length);
+ if (ret)
+ return ERR_PTR(ret);
+
+ /* ...wait for message delivery */
+ return xf_cmd_recv(proxy, &proxy->wait, &proxy->response, 1);
+}
+
+struct xf_message *xf_cmd_send_recv_wq(struct xf_proxy *proxy, u32 id,
+ u32 opcode, void *buffer, u32 length,
+ wait_queue_head_t *wq,
+ struct xf_msg_queue *queue)
+{
+ int ret;
+
+ /* ...send command to remote proxy */
+ ret = xf_cmd_send(proxy, id, opcode, buffer, length);
+ if (ret)
+ return ERR_PTR(ret);
+
+ /* ...wait for message delivery */
+ return xf_cmd_recv(proxy, wq, queue, 1);
+}
+
+struct xf_message *xf_cmd_send_recv_complete(struct xf_client *client,
+ struct xf_proxy *proxy,
+ u32 id, u32 opcode, void *buffer,
+ u32 length,
+ struct work_struct *work,
+ struct completion *completion)
+{
+ struct xf_message *m;
+ int ret;
+
+ /* ...retrieve message handle (take the lock on success) */
+ m = xf_msg_available(proxy);
+ if (!m)
+ return ERR_PTR(-EBUSY);
+
+ /* ...fill-in message parameters (lock is taken) */
+ m->id = id;
+ m->opcode = opcode;
+ m->length = length;
+ m->buffer = buffer;
+ m->ret = 0;
+
+ init_completion(completion);
+
+ /* ...submit command to the proxy */
+ xf_proxy_command(proxy, m);
+
+ schedule_work(work);
+
+ /* ...wait for message reception (take lock on success) */
+ ret = wait_for_completion_timeout(completion,
+ msecs_to_jiffies(1000));
+ if (!ret)
+ return ERR_PTR(-ETIMEDOUT);
+
+ m = &client->m;
+
+ /* ...return message with a lock taken */
+ return m;
+}
+/*
+ * Proxy allocate and free memory functions
+ */
+/* ...allocate memory buffer for kernel use */
+int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length)
+{
+ struct xf_message *m;
+ u32 id = 0;
+ int ret;
+
+ /* ...send command to remote proxy */
+ m = xf_cmd_send_recv(proxy, id, XF_ALLOC, NULL, length);
+ if (IS_ERR(m)) {
+ xf_unlock(&proxy->lock);
+ ret = PTR_ERR(m);
+ return ret;
+ }
+
+ /* ...check if response is expected */
+ if (m->opcode == XF_ALLOC && m->buffer != NULL) {
+ *buffer = m->buffer;
+ ret = 0;
+ } else {
+ ret = -ENOMEM;
+ }
+
+ /* ...free message and release proxy lock */
+ xf_msg_free(proxy, m);
+ xf_unlock(&proxy->lock);
+
+ return ret;
+}
+
+/* ...free memory buffer */
+int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length)
+{
+ struct xf_message *m;
+ u32 id = 0;
+ int ret;
+
+ /* ...synchronously execute freeing command */
+ m = xf_cmd_send_recv(proxy, id, XF_FREE, buffer, length);
+ if (IS_ERR(m)) {
+ xf_unlock(&proxy->lock);
+ ret = PTR_ERR(m);
+ return ret;
+ }
+
+ /* ...check if response is expected */
+ if (m->opcode == XF_FREE)
+ ret = 0;
+ else
+ ret = -EINVAL;
+
+ /* ...free message and release proxy lock */
+ xf_msg_free(proxy, m);
+ xf_unlock(&proxy->lock);
+
+ return ret;
+}
+
+/*
+ * suspend & resume functions
+ */
+int xf_cmd_send_suspend(struct xf_proxy *proxy)
+{
+ union icm_header_t msghdr;
+ int ret = 0;
+
+ init_completion(&proxy->cmd_complete);
+
+ msghdr.allbits = 0; /* clear all bits; */
+ msghdr.ack = 0;
+ msghdr.intr = 1;
+ msghdr.msg = XF_SUSPEND;
+ msghdr.size = 0;
+ icm_intr_send(proxy, msghdr.allbits);
+
+ /* wait for response here */
+ ret = icm_ack_wait(proxy, msghdr.allbits);
+
+ return ret;
+}
+
+int xf_cmd_send_resume(struct xf_proxy *proxy)
+{
+ union icm_header_t msghdr;
+ int ret = 0;
+
+ init_completion(&proxy->cmd_complete);
+
+ msghdr.allbits = 0; /* clear all bits; */
+ msghdr.ack = 0;
+ msghdr.intr = 1;
+ msghdr.msg = XF_RESUME;
+ msghdr.size = 0;
+ icm_intr_send(proxy, msghdr.allbits);
+
+ /* wait for response here */
+ ret = icm_ack_wait(proxy, msghdr.allbits);
+
+ return ret;
+}
+
+/* ...open component handle */
+int xf_open(struct xf_client *client, struct xf_proxy *proxy,
+ struct xf_handle *handle, const char *id, u32 core,
+ xf_response_cb response)
+{
+ void *b;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+
+ /* ...retrieve auxiliary control buffer from proxy - need I */
+ handle->aux = xf_buffer_get(proxy->aux);
+
+ b = xf_handle_aux(handle);
+
+ msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_REGISTER;
+ msg.buffer = b;
+ msg.length = strlen(id) + 1;
+ msg.ret = 0;
+
+ /* ...copy component identifier */
+ memcpy(b, (void *)id, xf_buffer_length(handle->aux));
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+
+ if (IS_ERR(rmsg)) {
+ xf_buffer_put(handle->aux), handle->aux = NULL;
+ return PTR_ERR(rmsg);
+ }
+ /* ...save received component global client-id */
+ handle->id = XF_MSG_SRC(rmsg->id);
+ /* TODO: review cleanup */
+ /* xf_msg_free(proxy, rmsg);
+ * xf_unlock(&proxy->lock); */
+
+ /* ...if failed, release buffer handle */
+ /* ...operation completed successfully; assign handle data */
+ handle->response = response;
+ handle->proxy = proxy;
+
+ return 0;
+}
+
+/* ...close component handle */
+int xf_close(struct xf_client *client, struct xf_handle *handle)
+{
+ struct xf_proxy *proxy = handle->proxy;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+
+ /* ...do I need to take component lock here? guess no - tbd */
+
+ /* ...buffers and stuff? - tbd */
+
+ /* ...acquire global proxy lock */
+ /* ...unregister component from remote DSP proxy (ignore result code) */
+
+ msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), handle->id);
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_UNREGISTER;
+ msg.buffer = NULL;
+ msg.length = 0;
+ msg.ret = 0;
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+
+ if (IS_ERR(rmsg)) {
+ xf_buffer_put(handle->aux), handle->aux = NULL;
+ return PTR_ERR(rmsg);
+ }
+ /* TODO: review cleanup */
+ /* xf_msg_free(proxy, rmsg);
+ * xf_unlock(&proxy->lock); */
+
+ /* ...wipe out proxy pointer */
+ handle->proxy = NULL;
+
+ return 0;
+}
diff --git a/sound/soc/fsl/fsl_dsp_proxy.h b/sound/soc/fsl/fsl_dsp_proxy.h
new file mode 100644
index 000000000000..bc9ccf37bc8f
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_proxy.h
@@ -0,0 +1,520 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
+/*
+ * DSP proxy header - commands/responses from DSP driver to DSP ramework
+ *
+ * Copyright 2018 NXP
+ * Copyright (c) 2017 Cadence Design Systems, Inc.
+ */
+
+#ifndef __FSL_DSP_PROXY_H
+#define __FSL_DSP_PROXY_H
+
+#include <linux/wait.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/compiler.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_data/dma-imx.h>
+#include <linux/mx8_mu.h>
+#include <linux/interrupt.h>
+
+#include "fsl_dsp_pool.h"
+#define XF_CFG_MESSAGE_POOL_SIZE 256
+
+struct xf_client;
+
+/*******************************************************************************
+ * Local proxy data
+ ******************************************************************************/
+
+struct xf_message;
+struct xf_handle;
+typedef void (*xf_response_cb)(struct xf_handle *h, struct xf_message *msg);
+
+/* ...execution message */
+struct xf_message {
+ /* ...pointer to next message in a list */
+ struct xf_message *next;
+
+ /* ...session-id */
+ u32 id;
+
+ /* ...operation code */
+ u32 opcode;
+
+ /* ...length of data buffer */
+ u32 length;
+
+ /* ...translated data pointer */
+ void *buffer;
+
+ /* ...return message status */
+ u32 ret;
+};
+
+/* ...message queue */
+struct xf_msg_queue {
+ /* ...pointer to list head */
+ struct xf_message *head;
+
+ /* ...pointer to list tail */
+ struct xf_message *tail;
+};
+
+struct xf_proxy_message {
+ /* ...session ID */
+ u32 session_id;
+
+ /* ...proxy API command/response code */
+ u32 opcode;
+
+ /* ...length of attached buffer */
+ u32 length;
+
+ /* ...physical address of message buffer */
+ u32 address;
+
+ /* ...return message status */
+ u32 ret;
+};
+/**********************************************************************/
+
+enum icm_action_t {
+ ICM_CORE_READY = 1,
+ ICM_CORE_INIT,
+ ICM_CORE_EXIT,
+};
+
+/* ...adjust IPC client of message going from user-space */
+#define XF_MSG_AP_FROM_USER(id, client) (((id) & ~(0xF << 2)) | (client << 2))
+
+
+#define __XF_PORT_SPEC(core, id, port) ((core) | ((id) << 2) | ((port) << 8))
+#define __XF_PORT_SPEC2(id, port) ((id) | ((port) << 8))
+
+
+/* ...wipe out IPC client from message going to user-space */
+#define XF_MSG_AP_TO_USER(id) ((id) & ~(0xF << 18))
+#define __XF_AP_PROXY(core) ((core) | 0x8000)
+#define __XF_DSP_PROXY(core) ((core) | 0x8000)
+
+/* ...message id contains source and destination ports specification */
+#define __XF_MSG_ID(src, dst) (((src) & 0xFFFF) | (((dst) & 0xFFFF) << 16))
+#define XF_MSG_SRC(id) (((id) >> 0) & 0xFFFF)
+#define XF_MSG_SRC_CORE(id) (((id) >> 0) & 0x3)
+#define XF_MSG_SRC_CLIENT(id) (((id) >> 2) & 0x3F)
+#define XF_MSG_DST_CLIENT(id) (((id) >> 18) & 0x3F)
+
+/* ...special treatment of AP-proxy destination field */
+#define XF_AP_IPC_CLIENT(id) (((id) >> 18) & 0xF)
+#define XF_AP_CLIENT(id) (((id) >> 22) & 0x1FF)
+#define __XF_AP_PROXY(core) ((core) | 0x8000)
+#define __XF_DSP_PROXY(core) ((core) | 0x8000)
+#define __XF_AP_CLIENT(core, client) ((core) | ((client) << 6) | 0x8000)
+
+/* ...opcode composition with command/response data tags */
+#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F))
+
+/* ...shared buffer allocation */
+#define XF_ALLOC __XF_OPCODE(0, 0, 4)
+
+/* ...shared buffer freeing */
+#define XF_FREE __XF_OPCODE(0, 0, 5)
+
+/* ...resume component operation */
+#define XF_RESUME __XF_OPCODE(0, 0, 14)
+
+/* ...resume component operation */
+#define XF_SUSPEND __XF_OPCODE(0, 0, 15)
+
+
+/*******************************************************************************
+ * Ring buffer support
+ ******************************************************************************/
+/* ...cache-line size on DSP */
+#define XF_PROXY_ALIGNMENT 64
+
+/* ...total length of shared memory queue (for commands and responses) */
+#define XF_PROXY_MESSAGE_QUEUE_LENGTH (1 << 6)
+
+/* ...index mask */
+#define XF_PROXY_MESSAGE_QUEUE_MASK 0x3F
+
+/* ...ring-buffer index */
+#define __XF_QUEUE_IDX(idx, counter) \
+ (((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) | ((counter) << 16))
+
+/* ...retrieve ring-buffer index */
+#define XF_QUEUE_IDX(idx) \
+ ((idx) & XF_PROXY_MESSAGE_QUEUE_MASK)
+
+/* ...increment ring-buffer index */
+#define XF_QUEUE_ADVANCE_IDX(idx) \
+ (((idx) + 0x10001) & (0xFFFF0000 | XF_PROXY_MESSAGE_QUEUE_MASK))
+
+/* ...test if ring buffer is empty */
+#define XF_QUEUE_EMPTY(read, write) \
+ ((read) == (write))
+
+/* ...test if ring buffer is full */
+#define XF_QUEUE_FULL(read, write) \
+ ((write) == (read) + (XF_PROXY_MESSAGE_QUEUE_LENGTH << 16))
+
+/* ...basic cache operations */
+#define XF_PROXY_INVALIDATE(addr, len) { }
+
+#define XF_PROXY_FLUSH(addr, len) { }
+
+/* ...data managed by host CPU (remote) - in case of shunt it is a IPC layer */
+struct xf_proxy_host_data {
+ /* ...command queue */
+ struct xf_proxy_message command[XF_PROXY_MESSAGE_QUEUE_LENGTH];
+
+ /* ...writing index into command queue */
+ u32 cmd_write_idx;
+
+ /* ...reading index for response queue */
+ u32 rsp_read_idx;
+
+ /* ...indicate command queue is valid or not */
+ u32 cmd_invalid;
+};
+
+/* ...data managed by DSP (local) */
+struct xf_proxy_dsp_data {
+ /* ...response queue */
+ struct xf_proxy_message response[XF_PROXY_MESSAGE_QUEUE_LENGTH];
+
+ /* ...writing index into response queue */
+ u32 rsp_write_idx;
+
+ /* ...reading index for command queue */
+ u32 cmd_read_idx;
+
+ /* ...indicate response queue is valid or not */
+ u32 rsp_invalid;
+};
+
+/* ...shared memory data */
+struct xf_shmem_data {
+ /* ...ingoing data (maintained by DSP (local side)) */
+ struct xf_proxy_host_data local;
+
+ /* ...outgoing data (maintained by host CPU (remote side)) */
+ struct xf_proxy_dsp_data remote;
+
+};
+
+/* ...shared memory data accessor */
+#define XF_SHMEM_DATA(proxy) \
+ ((proxy)->ipc.shmem)
+
+/* ...atomic reading */
+#define __XF_PROXY_READ_ATOMIC(var) \
+ ({ XF_PROXY_INVALIDATE(&(var), sizeof(var)); \
+ *(u32 *)&(var); })
+
+/* ...atomic writing */
+#define __XF_PROXY_WRITE_ATOMIC(var, value) \
+ ({*(u32 *)&(var) = (value); \
+ XF_PROXY_FLUSH(&(var), sizeof(var)); \
+ (value); })
+
+/* ...accessors */
+#define XF_PROXY_READ(proxy, field) \
+ __XF_PROXY_READ_##field(XF_SHMEM_DATA(proxy))
+
+#define XF_PROXY_WRITE(proxy, field, v) \
+ __XF_PROXY_WRITE_##field(XF_SHMEM_DATA(proxy), (v))
+
+/* ...individual fields reading */
+#define __XF_PROXY_READ_cmd_write_idx(shmem) \
+ __XF_PROXY_READ_ATOMIC(shmem->local.cmd_write_idx)
+
+#define __XF_PROXY_READ_cmd_read_idx(shmem) \
+ shmem->remote.cmd_read_idx
+
+#define __XF_PROXY_READ_cmd_invalid(shmem) \
+ __XF_PROXY_READ_ATOMIC(shmem->local.cmd_invalid)
+
+#define __XF_PROXY_READ_rsp_write_idx(shmem) \
+ __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_write_idx)
+
+#define __XF_PROXY_READ_rsp_read_idx(shmem) \
+ shmem->local.rsp_read_idx
+
+#define __XF_PROXY_READ_rsp_invalid(shmem) \
+ __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_invalid)
+
+/* ...individual fields writings */
+#define __XF_PROXY_WRITE_cmd_write_idx(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_write_idx, v)
+
+#define __XF_PROXY_WRITE_cmd_read_idx(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->remote.cmd_read_idx, v)
+
+#define __XF_PROXY_WRITE_cmd_invalid(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_invalid, v)
+
+#define __XF_PROXY_WRITE_rsp_read_idx(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->local.rsp_read_idx, v)
+
+#define __XF_PROXY_WRITE_rsp_write_idx(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_write_idx, v)
+
+#define __XF_PROXY_WRITE_rsp_invalid(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_invalid, v)
+
+/* ...command buffer accessor */
+#define XF_PROXY_COMMAND(proxy, idx) \
+ (&XF_SHMEM_DATA(proxy)->local.command[(idx)])
+
+/* ...response buffer accessor */
+#define XF_PROXY_RESPONSE(proxy, idx) \
+ (&XF_SHMEM_DATA(proxy)->remote.response[(idx)])
+
+/*******************************************************************************
+ * Local proxy data
+ ******************************************************************************/
+
+struct xf_proxy_ipc_data {
+ /* ...shared memory data pointer */
+ struct xf_shmem_data __iomem *shmem;
+
+ /* ...core identifier */
+ u32 core;
+
+ /* ...IPC registers memory */
+ void __iomem *regs;
+};
+
+/* ...proxy data */
+struct xf_proxy {
+ /* ...IPC layer data */
+ struct xf_proxy_ipc_data ipc;
+
+ /* ...shared memory status change processing item */
+ struct work_struct work;
+
+ struct completion cmd_complete;
+ int is_ready;
+ int is_active;
+
+ /* ...internal lock */
+ spinlock_t lock;
+
+ /* ...busy queue (for clients waiting ON NOTIFIcation) */
+ wait_queue_head_t busy;
+
+ /* ...waiting queue for synchronous proxy operations */
+ wait_queue_head_t wait;
+
+ /* ...submitted commands queue */
+ struct xf_msg_queue command;
+
+ /* ...pending responses queue */
+ struct xf_msg_queue response;
+
+ /* ...global message pool */
+ struct xf_message pool[XF_CFG_MESSAGE_POOL_SIZE];
+
+ /* ...pointer to first free message in the pool */
+ struct xf_message *free;
+
+ /* ...auxiliary buffer pool for clients */
+ struct xf_pool *aux;
+};
+
+union icm_header_t {
+ struct {
+ u32 msg:6;
+ u32 sub_msg:6; // sub_msg will have ICM_MSG
+ u32 rsvd:3; /* reserved */
+ u32 intr:1; /* intr = 1 when sending msg. */
+ u32 size:15; /* =size in bytes (excluding header) */
+ u32 ack:1; /* response message when ack=1 */
+ };
+ u32 allbits;
+};
+
+struct dsp_ext_msg {
+ u32 phys;
+ u32 size;
+};
+
+struct dsp_mem_msg {
+ u32 ext_msg_phys;
+ u32 ext_msg_size;
+ u32 scratch_phys;
+ u32 scratch_size;
+ u32 dsp_config_phys;
+ u32 dsp_config_size;
+ u32 dsp_board_type;
+};
+
+static inline void xf_lock_init(spinlock_t *lock)
+{
+ spin_lock_init(lock);
+}
+
+static inline void xf_lock(spinlock_t *lock)
+{
+ spin_lock(lock);
+}
+
+static inline void xf_unlock(spinlock_t *lock)
+{
+ spin_unlock(lock);
+}
+
+/* ...init proxy */
+int xf_proxy_init(struct xf_proxy *proxy);
+
+/* ...send message to proxy */
+int xf_cmd_send(struct xf_proxy *proxy,
+ u32 id,
+ u32 opcode,
+ void *buffer,
+ u32 length);
+
+/* ...get message from proxy */
+struct xf_message *xf_cmd_recv(struct xf_proxy *proxy,
+ wait_queue_head_t *wq,
+ struct xf_msg_queue *queue,
+ int wait);
+
+struct xf_message*
+xf_cmd_recv_timeout(struct xf_proxy *proxy, wait_queue_head_t *wq,
+ struct xf_msg_queue *queue, int wait);
+
+struct xf_message*
+xf_cmd_send_recv(struct xf_proxy *proxy, u32 id, u32 opcode,
+ void *buffer, u32 length);
+
+struct xf_message*
+xf_cmd_send_recv_wq(struct xf_proxy *proxy, u32 id, u32 opcode, void *buffer,
+ u32 length, wait_queue_head_t *wq,
+ struct xf_msg_queue *queue);
+
+struct xf_message*
+xf_cmd_send_recv_complete(struct xf_client *client, struct xf_proxy *proxy,
+ u32 id, u32 opcode, void *buffer, u32 length,
+ struct work_struct *work,
+ struct completion *completion);
+
+/* ...mu interrupt handle */
+irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id);
+
+/* ...initialize client pending message queue */
+void xf_msg_queue_init(struct xf_msg_queue *queue);
+
+/* ...return current queue state */
+struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue);
+
+/* ...return the message back to a pool */
+void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m);
+
+/* ...release all pending messages */
+void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue);
+
+/* ...wait mu interrupt */
+long icm_ack_wait(struct xf_proxy *proxy, u32 msg);
+
+/* ...shared memory translation - kernel virtual address to shared address */
+u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b);
+
+/* ...shared memory translation - shared address to kernel virtual address */
+void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address);
+
+int xf_cmd_send_suspend(struct xf_proxy *proxy);
+int xf_cmd_send_resume(struct xf_proxy *proxy);
+
+int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length);
+int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length);
+
+int xf_open(struct xf_client *client, struct xf_proxy *proxy,
+ struct xf_handle *handle, const char *id, u32 core,
+ xf_response_cb response);
+
+int xf_close(struct xf_client *client, struct xf_handle *handle);
+
+
+
+/*******************************************************************************
+ * Opcode composition
+ ******************************************************************************/
+
+/* ...opcode composition with command/response data tags */
+#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F))
+
+/* ...accessors */
+#define XF_OPCODE_CDATA(opcode) ((opcode) & (1 << 31))
+#define XF_OPCODE_RDATA(opcode) ((opcode) & (1 << 30))
+#define XF_OPCODE_TYPE(opcode) ((opcode) & (0x3F))
+
+/*******************************************************************************
+ * Opcode types
+ ******************************************************************************/
+
+/* ...unregister client */
+#define XF_UNREGISTER __XF_OPCODE(0, 0, 0)
+
+/* ...register client at proxy */
+#define XF_REGISTER __XF_OPCODE(1, 0, 1)
+
+/* ...port routing command */
+#define XF_ROUTE __XF_OPCODE(1, 0, 2)
+
+/* ...port unrouting command */
+#define XF_UNROUTE __XF_OPCODE(1, 0, 3)
+
+/* ...shared buffer allocation */
+#define XF_ALLOC __XF_OPCODE(0, 0, 4)
+
+/* ...shared buffer freeing */
+#define XF_FREE __XF_OPCODE(0, 0, 5)
+
+/* ...set component parameters */
+#define XF_SET_PARAM __XF_OPCODE(1, 0, 6)
+
+/* ...get component parameters */
+#define XF_GET_PARAM __XF_OPCODE(1, 1, 7)
+
+/* ...input buffer reception */
+#define XF_EMPTY_THIS_BUFFER __XF_OPCODE(1, 0, 8)
+
+/* ...output buffer reception */
+#define XF_FILL_THIS_BUFFER __XF_OPCODE(0, 1, 9)
+
+/* ...flush specific port */
+#define XF_FLUSH __XF_OPCODE(0, 0, 10)
+
+/* ...start component operation */
+#define XF_START __XF_OPCODE(0, 0, 11)
+
+/* ...stop component operation */
+#define XF_STOP __XF_OPCODE(0, 0, 12)
+
+/* ...pause component operation */
+#define XF_PAUSE __XF_OPCODE(0, 0, 13)
+
+/* ...resume component operation */
+#define XF_RESUME __XF_OPCODE(0, 0, 14)
+
+/* ...resume component operation */
+#define XF_SUSPEND __XF_OPCODE(0, 0, 15)
+
+/* ...load lib for component operation */
+#define XF_LOAD_LIB __XF_OPCODE(0, 0, 16)
+
+/* ...unload lib for component operation */
+#define XF_UNLOAD_LIB __XF_OPCODE(0, 0, 17)
+
+/* ...component output eos operation */
+#define XF_OUTPUT_EOS __XF_OPCODE(0, 0, 18)
+
+/* ...total amount of supported decoder commands */
+#define __XF_OP_NUM 19
+
+#endif
diff --git a/sound/soc/fsl/fsl_dsp_xaf_api.c b/sound/soc/fsl/fsl_dsp_xaf_api.c
new file mode 100644
index 000000000000..4312a6b29ef1
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_xaf_api.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+//
+// Xtensa Audio Framework API for communication with DSP
+//
+// Copyright (C) 2017 Cadence Design Systems, Inc.
+// Copyright 2018 NXP
+
+#include "fsl_dsp.h"
+#include "fsl_dsp_xaf_api.h"
+
+/* ...send a command message to component */
+int xf_command(struct xf_client *client, struct xf_handle *handle,
+ u32 port, u32 opcode, void *buffer, u32 length)
+{
+ struct xf_proxy *proxy = handle->proxy;
+ struct xf_message msg;
+
+ /* ...fill-in message parameters */
+ msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0),
+ __XF_PORT_SPEC2(handle->id, port));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = opcode;
+ msg.length = length;
+ msg.buffer = buffer;
+ msg.ret = 0;
+
+ /* ...execute command synchronously */
+ return xf_cmd_send(proxy, msg.id, msg.opcode, msg.buffer, msg.length);
+}
+
+int xaf_comp_set_config(struct xf_client *client, struct xaf_comp *p_comp,
+ u32 num_param, void *p_param)
+{
+ struct xf_handle *p_handle;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+ struct xf_set_param_msg *smsg;
+ struct xf_set_param_msg *param = (struct xf_set_param_msg *)p_param;
+ struct xf_proxy *proxy;
+ u32 i;
+
+ p_handle = &p_comp->handle;
+ proxy = p_handle->proxy;
+
+ /* ...set persistent stream characteristics */
+ smsg = xf_buffer_data(p_handle->aux);
+
+ for (i = 0; i < num_param; i++) {
+ smsg[i].id = param[i].id;
+ smsg[i].mixData.value = param[i].mixData.value;
+ }
+
+ /* ...set command parameters */
+ msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0),
+ __XF_PORT_SPEC2(p_handle->id, 0));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_SET_PARAM;
+ msg.length = sizeof(*smsg) * num_param;
+ msg.buffer = smsg;
+ msg.ret = 0;
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+
+ if(IS_ERR(rmsg))
+ return PTR_ERR(rmsg);
+ /* ...save received component global client-id */
+ /* TODO: review cleanup */
+ /* xf_msg_free(proxy, rmsg);
+ * xf_unlock(&proxy->lock);
+ */
+
+ /* ...make sure response is expected */
+ if ((rmsg->opcode != XF_SET_PARAM) || (rmsg->buffer != smsg)) {
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+int xaf_comp_get_config(struct xf_client *client, struct xaf_comp *p_comp,
+ u32 num_param, void *p_param)
+{
+
+ struct xf_handle *p_handle;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+ struct xf_get_param_msg *smsg;
+ struct xf_get_param_msg *param = (struct xf_get_param_msg *)p_param;
+ struct xf_proxy *proxy;
+ u32 i;
+
+ p_handle = &p_comp->handle;
+ proxy = p_handle->proxy;
+
+ /* ...set persistent stream characteristics */
+ smsg = xf_buffer_data(p_handle->aux);
+
+ for (i = 0; i < num_param; i++)
+ smsg[i].id = param[i].id;
+
+
+ msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0),
+ __XF_PORT_SPEC2(p_handle->id, 0));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_GET_PARAM;
+ msg.length = sizeof(*smsg) * num_param;
+ msg.buffer = smsg;
+ msg.ret = 0;
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+
+ /* ...save received component global client-id */
+ if(IS_ERR(rmsg))
+ return PTR_ERR(rmsg);
+
+ /* TODO: review cleanup */
+ /* xf_msg_free(proxy, rmsg);
+ * xf_unlock(&proxy->lock); */
+
+ /* ...make sure response is expected */
+ if ((rmsg->opcode != (u32)XF_GET_PARAM) || (rmsg->buffer != smsg)) {
+ return -EPIPE;
+ }
+
+ for (i = 0; i < num_param; i++)
+ param[i].mixData.value = smsg[i].mixData.value;
+
+ return 0;
+}
+
+int xaf_comp_flush(struct xf_client *client, struct xaf_comp *p_comp)
+{
+
+ struct xf_handle *p_handle;
+ struct xf_proxy *proxy;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+
+ p_handle = &p_comp->handle;
+ proxy = p_handle->proxy;
+
+ msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0),
+ __XF_PORT_SPEC2(p_handle->id, 0));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_FLUSH;
+ msg.length = 0;
+ msg.buffer = NULL;
+ msg.ret = 0;
+
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+
+ if(IS_ERR(rmsg))
+ return PTR_ERR(rmsg);
+
+ /* ...make sure response is expected */
+ if ((rmsg->opcode != (u32)XF_FLUSH) || rmsg->buffer) {
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+int xaf_comp_create(struct xf_client *client, struct xf_proxy *proxy,
+ struct xaf_comp *p_comp, int comp_type)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy);
+ char lib_path[200];
+ char lib_wrap_path[200];
+ struct xf_handle *p_handle;
+ struct xf_buffer *buf;
+ int ret = 0;
+ bool loadlib = true;
+
+ memset((void *)p_comp, 0, sizeof(struct xaf_comp));
+
+ strcpy(lib_path, "/usr/lib/imx-mm/audio-codec/dsp/");
+ strcpy(lib_wrap_path, "/usr/lib/imx-mm/audio-codec/dsp/");
+
+ p_handle = &p_comp->handle;
+
+ p_comp->comp_type = comp_type;
+
+ if (comp_type == RENDER_ESAI)
+ loadlib = false;
+
+ if (loadlib) {
+ p_comp->codec_lib.filename = lib_path;
+ p_comp->codec_wrap_lib.filename = lib_wrap_path;
+ p_comp->codec_lib.lib_type = DSP_CODEC_LIB;
+ }
+
+ switch (comp_type) {
+ case CODEC_MP3_DEC:
+ p_comp->dec_id = "audio-decoder/mp3";
+ strcat(lib_path, "lib_dsp_mp3_dec.so");
+ break;
+ case CODEC_AAC_DEC:
+ p_comp->dec_id = "audio-decoder/aac";
+ strcat(lib_path, "lib_dsp_aac_dec.so");
+ break;
+ case RENDER_ESAI:
+ p_comp->dec_id = "renderer/esai";
+ break;
+
+ default:
+ return -EINVAL;
+ break;
+ }
+
+ /* ...create decoder component instance (select core-0) */
+ ret = xf_open(client, proxy, p_handle, p_comp->dec_id, 0, NULL);
+ if (ret) {
+ dev_err(dsp_priv->dev, "create (%s) component error: %d\n",
+ p_comp->dec_id, ret);
+ return ret;
+ }
+
+ if (loadlib) {
+ strcat(lib_wrap_path, "lib_dsp_codec_wrap.so");
+ p_comp->codec_wrap_lib.lib_type = DSP_CODEC_WRAP_LIB;
+
+ /* ...load codec wrapper lib */
+ ret = xf_load_lib(client, p_handle, &p_comp->codec_wrap_lib);
+ if (ret) {
+ dev_err(dsp_priv->dev, "load codec wrap lib error\n");
+ goto err_wrap_load;
+ }
+
+ /* ...load codec lib */
+ ret = xf_load_lib(client, p_handle, &p_comp->codec_lib);
+ if (ret) {
+ dev_err(dsp_priv->dev, "load codec lib error\n");
+ goto err_codec_load;
+ }
+
+ /* ...allocate input buffer */
+ ret = xf_pool_alloc(client, proxy, 1, INBUF_SIZE,
+ XF_POOL_INPUT, &p_comp->inpool);
+ if (ret) {
+ dev_err(dsp_priv->dev, "alloc input buf error\n");
+ goto err_pool_alloc;
+ }
+
+ /* ...initialize input buffer pointer */
+ buf = xf_buffer_get(p_comp->inpool);
+ p_comp->inptr = xf_buffer_data(buf);
+ }
+
+ p_comp->active = true;
+
+ return ret;
+
+err_pool_alloc:
+ xf_unload_lib(client, p_handle, &p_comp->codec_lib);
+err_codec_load:
+ xf_unload_lib(client, p_handle, &p_comp->codec_wrap_lib);
+err_wrap_load:
+ xf_close(client, p_handle);
+
+ return ret;
+}
+
+int xaf_comp_delete(struct xf_client *client, struct xaf_comp *p_comp)
+{
+
+ struct xf_handle *p_handle;
+ bool loadlib = true;
+ u32 ret = 0;
+
+ if (!p_comp->active)
+ return ret;
+
+ /* mark component as unusable from this point */
+ p_comp->active = false;
+
+ if (p_comp->comp_type == RENDER_ESAI)
+ loadlib = false;
+
+ p_handle = &p_comp->handle;
+
+ if (loadlib) {
+ /* ...unload codec wrapper library */
+ xf_unload_lib(client, p_handle, &p_comp->codec_wrap_lib);
+
+ /* ...unload codec library */
+ xf_unload_lib(client, p_handle, &p_comp->codec_lib);
+
+ xf_pool_free(client, p_comp->inpool);
+ }
+
+ /* ...delete component */
+ xf_close(client, p_handle);
+
+ return ret;
+}
+
+int xaf_comp_process(struct xf_client *client, struct xaf_comp *p_comp, void *p_buf, u32 length, u32 flag)
+{
+ struct xf_handle *p_handle;
+ u32 ret = 0;
+
+ p_handle = &p_comp->handle;
+
+ switch (flag) {
+ case XF_FILL_THIS_BUFFER:
+ /* ...send message to component output port (port-id=1) */
+ ret = xf_command(client, p_handle, 1, XF_FILL_THIS_BUFFER,
+ p_buf, length);
+ break;
+ case XF_EMPTY_THIS_BUFFER:
+ /* ...send message to component input port (port-id=0) */
+ ret = xf_command(client, p_handle, 0, XF_EMPTY_THIS_BUFFER,
+ p_buf, length);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/* ...port binding function */
+int xf_route(struct xf_client *client, struct xf_handle *src, u32 src_port,
+ struct xf_handle *dst, u32 dst_port, u32 num, u32 size, u32 align)
+{
+ struct xf_proxy *proxy = src->proxy;
+ struct xf_buffer *b;
+ struct xf_route_port_msg *m;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+
+ /* ...sanity checks - proxy pointers are same */
+ if (proxy != dst->proxy)
+ return -EINVAL;
+
+ /* ...buffer data is sane */
+ if (!(num && size && xf_is_power_of_two(align)))
+ return -EINVAL;
+
+ /* ...get control buffer */
+ if ((b = xf_buffer_get(proxy->aux)) == NULL)
+ return -EBUSY;
+
+ /* ...get message buffer */
+ m = xf_buffer_data(b);
+
+ /* ...fill-in message parameters */
+ m->src = __XF_PORT_SPEC2(src->id, src_port);
+ m->dst = __XF_PORT_SPEC2(dst->id, dst_port);
+ m->alloc_number = num;
+ m->alloc_size = size;
+ m->alloc_align = align;
+
+ /* ...set command parameters */
+ msg.id = __XF_MSG_ID(__XF_AP_PROXY(0),
+ __XF_PORT_SPEC2(src->id, src_port));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_ROUTE;
+ msg.length = sizeof(*m);
+ msg.buffer = m;
+ msg.ret = 0;
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+ if(IS_ERR(rmsg))
+ return PTR_ERR(rmsg);
+
+ /* ...save received component global client-id */
+ /* TODO: review cleanup */
+ /* xf_msg_free(proxy, rmsg);
+ * xf_unlock(&proxy->lock); */
+
+
+ /* ...synchronously execute command on remote DSP */
+ /* XF_CHK_API(xf_proxy_cmd_exec(proxy, &msg)); */
+
+ /* ...return buffer to proxy */
+ xf_buffer_put(b);
+
+ /* ...check result is successful */
+ /* XF_CHK_ERR(msg.opcode == XF_ROUTE, -ENOMEM); */
+
+ return 0;
+}
+
+/* ...port unbinding function */
+int xf_unroute(struct xf_client *client, struct xf_handle *src, u32 src_port)
+{
+ struct xf_proxy *proxy = src->proxy;
+ struct xf_buffer *b;
+ struct xf_unroute_port_msg *m;
+ struct xf_message msg;
+ struct xf_message *rmsg;
+ int r = 0;
+
+ /* ...get control buffer */
+ if((b = xf_buffer_get(proxy->aux)) == NULL)
+ return -EBUSY;
+
+ /* ...get message buffer */
+ m = xf_buffer_data(b);
+
+ /* ...fill-in message parameters */
+ m->src = __XF_PORT_SPEC2(src->id, src_port);
+
+ /* ...set command parameters */
+ msg.id = __XF_MSG_ID(__XF_AP_PROXY(0),
+ __XF_PORT_SPEC2(src->id, src_port));
+ msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id);
+ msg.opcode = XF_UNROUTE;
+ msg.length = sizeof(*m);
+ msg.buffer = m;
+ msg.ret = 0;
+
+ /* ...execute command synchronously */
+ rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode,
+ msg.buffer, msg.length, &client->work,
+ &client->compr_complete);
+ if (IS_ERR(rmsg))
+ return PTR_ERR(rmsg);
+ /* ...save received component global client-id */
+
+ /*TODO: review cleanup */
+ /* xf_msg_free(proxy, rmsg); */
+ /* xf_unlock(&proxy->lock); */
+
+ /* ...return buffer to proxy */
+ xf_buffer_put(b);
+
+ return r;
+}
+
+int xaf_connect(struct xf_client *client,
+ struct xaf_comp *p_src,
+ struct xaf_comp *p_dest,
+ u32 num_buf,
+ u32 buf_length)
+{
+ /* ...connect p_src output port with p_dest input port */
+ return xf_route(client, &p_src->handle, 0, &p_dest->handle, 0,
+ num_buf, buf_length, 8);
+}
+
+int xaf_disconnect(struct xf_client *client, struct xaf_comp *p_comp)
+{
+ /* ...disconnect p_src output port with p_dest input port */
+ return xf_unroute(client, &p_comp->handle, 0);
+
+}
+
+int xaf_comp_add(struct xaf_pipeline *p_pipe, struct xaf_comp *p_comp)
+{
+ int ret = 0;
+
+ p_comp->next = p_pipe->comp_chain;
+ p_comp->pipeline = p_pipe;
+ p_pipe->comp_chain = p_comp;
+
+ return ret;
+}
+
+int xaf_pipeline_create(struct xaf_pipeline *p_pipe)
+{
+ int ret = 0;
+
+ memset(p_pipe, 0, sizeof(struct xaf_pipeline));
+
+ return ret;
+}
+
+int xaf_pipeline_delete(struct xaf_pipeline *p_pipe)
+{
+ int ret = 0;
+
+ memset(p_pipe, 0, sizeof(struct xaf_pipeline));
+
+ return ret;
+}
diff --git a/sound/soc/fsl/fsl_dsp_xaf_api.h b/sound/soc/fsl/fsl_dsp_xaf_api.h
new file mode 100644
index 000000000000..2367b6c9c34d
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_xaf_api.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/
+/*
+ * Xtensa Audio Framework API for communication with DSP
+ *
+ * Copyright (C) 2017 Cadence Design Systems, Inc.
+ * Copyright 2018 NXP
+ */
+#ifndef FSL_DSP_XAF_API_H
+#define FSL_DSP_XAF_API_H
+
+#include "fsl_dsp_library_load.h"
+
+/* ...size of auxiliary pool for communication with DSP */
+#define XA_AUX_POOL_SIZE 32
+
+/* ...length of auxiliary pool messages */
+#define XA_AUX_POOL_MSG_LENGTH 128
+
+/* ...number of max input buffers */
+#define INBUF_SIZE 4096
+#define OUTBUF_SIZE 16384
+
+struct xaf_pipeline;
+
+struct xaf_info_s {
+ u32 opcode;
+ void *buf;
+ u32 length;
+ u32 ret;
+};
+
+struct xaf_comp {
+ struct xaf_comp *next;
+
+ struct xaf_pipeline *pipeline;
+ struct xf_handle handle;
+
+ const char *dec_id;
+ int comp_type;
+
+ struct xf_pool *inpool;
+ struct xf_pool *outpool;
+ void *inptr;
+ void *outptr;
+
+ struct lib_info codec_lib;
+ struct lib_info codec_wrap_lib;
+
+ int active; /* component fully initialized */
+};
+
+struct xaf_pipeline {
+ struct xaf_comp *comp_chain;
+
+ u32 input_eos;
+ u32 output_eos;
+};
+
+int xaf_comp_create(struct xf_client *client, struct xf_proxy *p_proxy,
+ struct xaf_comp *p_comp, int comp_type);
+int xaf_comp_delete(struct xf_client *client, struct xaf_comp *p_comp);
+int xaf_comp_flush(struct xf_client *client, struct xaf_comp *p_comp);
+
+int xaf_comp_set_config(struct xf_client *client,struct xaf_comp *p_comp,
+ u32 num_param, void *p_param);
+int xaf_comp_get_config(struct xf_client *client,struct xaf_comp *p_comp,
+ u32 num_param, void *p_param);
+
+int xaf_comp_add(struct xaf_pipeline *p_pipe, struct xaf_comp *p_comp);
+int xaf_comp_process(struct xf_client *client, struct xaf_comp *p_comp,
+ void *p_buf, u32 length, u32 flag);
+int xaf_comp_get_status(struct xaf_comp *p_comp, struct xaf_info_s *p_info);
+int xaf_comp_get_msg_count(struct xaf_comp *p_comp);
+
+int xaf_connect(struct xf_client *client,struct xaf_comp *p_src,
+ struct xaf_comp *p_dest, u32 num_buf, u32 buf_length);
+int xaf_disconnect(struct xf_client *client,struct xaf_comp *p_comp);
+
+int xaf_pipeline_create(struct xaf_pipeline *p_pipe);
+int xaf_pipeline_delete(struct xaf_pipeline *p_pipe);
+
+int xaf_pipeline_send_eos(struct xaf_pipeline *p_pipe);
+
+/* ...port routing command */
+struct __attribute__((__packed__)) xf_route_port_msg {
+ /* ...source port specification */
+ u32 src;
+ /* ...destination port specification */
+ u32 dst;
+ /* ...number of buffers to allocate */
+ u32 alloc_number;
+ /* ...length of buffer to allocate */
+ u32 alloc_size;
+ /* ...alignment restriction for a buffer */
+ u32 alloc_align;
+};
+
+/* ...port unrouting command */
+struct __attribute__((__packed__)) xf_unroute_port_msg {
+ /* ...source port specification */
+ u32 src;
+ /* ...destination port specification */
+ u32 dst;
+};
+
+/* ...check if non-zero value is a power-of-two */
+#define xf_is_power_of_two(v) (((v) & ((v) - 1)) == 0)
+
+
+/*******************************************************************************
+ * bascial message
+ ******************************************************************************/
+typedef union DATA {
+ u32 value;
+
+ struct {
+ u32 size;
+ u32 channel_table[10];
+ } chan_map_tab;
+
+ struct {
+ u32 samplerate;
+ u32 width;
+ u32 depth;
+ u32 channels;
+ u32 endian;
+ u32 interleave;
+ u32 layout[12];
+ u32 chan_pos_set; // indicate if channel position is set outside or use codec default
+ } outputFormat;
+} data_t;
+
+/* ...component initialization parameter */
+struct __attribute__((__packed__)) xf_set_param_msg {
+ /* ...index of parameter passed to SET_CONFIG_PARAM call */
+ u32 id;
+ /* ...value of parameter */
+ data_t mixData;
+};
+
+/* ...message body (command/response) */
+struct __attribute__((__packed__)) xf_get_param_msg {
+ /* ...array of parameters requested */
+ u32 id;
+ /* ...array of parameters values */
+ data_t mixData;
+};
+
+/* ...renderer-specific configuration parameters */
+enum xa_config_param_renderer {
+ XA_RENDERER_CONFIG_PARAM_CB = 0,
+ XA_RENDERER_CONFIG_PARAM_STATE = 1,
+ XA_RENDERER_CONFIG_PARAM_PCM_WIDTH = 2,
+ XA_RENDERER_CONFIG_PARAM_CHANNELS = 3,
+ XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE = 4,
+ XA_RENDERER_CONFIG_PARAM_FRAME_SIZE = 5,
+ XA_RENDERER_CONFIG_PARAM_NUM = 6,
+};
+
+#endif /* FSL_DSP_XAF_API_H */
diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c
new file mode 100644
index 000000000000..c7917ccf8c82
--- /dev/null
+++ b/sound/soc/fsl/fsl_easrc.c
@@ -0,0 +1,2533 @@
+// 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"
+
+extern struct snd_soc_component_driver fsl_easrc_dma_component;
+
+#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_easrc *easrc = snd_soc_component_get_drvdata(comp);
+ struct soc_mreg_control *mc =
+ (struct soc_mreg_control *)kcontrol->private_value;
+ unsigned int regval = ucontrol->value.integer.value[0];
+
+ easrc->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_easrc *easrc = snd_soc_component_get_drvdata(comp);
+ struct soc_mreg_control *mc =
+ (struct soc_mreg_control *)kcontrol->private_value;
+
+ ucontrol->value.enumerated.item[0] = easrc->bps_iec958[mc->regbase];
+
+ return 0;
+}
+
+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;
+ int ret;
+
+ ret = snd_soc_component_read(component, mc->regbase, &regval);
+ if (ret < 0)
+ return ret;
+
+ ucontrol->value.integer.value[0] = regval;
+
+ return 0;
+}
+
+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)),
+};
+
+/* set_rs_ratio
+ *
+ * According to the resample taps, calculate the resample ratio
+ */
+static int set_rs_ratio(struct fsl_easrc_context *ctx)
+{
+ struct fsl_easrc *easrc = ctx->easrc;
+ unsigned int in_rate = ctx->in_params.norm_rate;
+ unsigned int out_rate = ctx->out_params.norm_rate;
+ unsigned int int_bits;
+ unsigned int frac_bits;
+ u64 val;
+ u32 *r;
+ int ret;
+
+ switch (easrc->rs_num_taps) {
+ case EASRC_RS_32_TAPS:
+ int_bits = 5;
+ frac_bits = 39;
+ break;
+ case EASRC_RS_64_TAPS:
+ int_bits = 6;
+ frac_bits = 38;
+ break;
+ case EASRC_RS_128_TAPS:
+ int_bits = 7;
+ frac_bits = 37;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val = ((uint64_t)in_rate << frac_bits) / out_rate;
+ r = (uint32_t *)&val;
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_RRL(ctx->index),
+ EASRC_RRL_RS_RL(r[0]));
+ ret |= regmap_write(easrc->regmap,
+ REG_EASRC_RRH(ctx->index),
+ EASRC_RRH_RS_RH(r[1]));
+
+ return ret;
+}
+
+/* normalize input and output sample rates */
+static void fsl_easrc_normalize_rates(struct fsl_easrc_context *ctx)
+{
+ int a, b;
+
+ if (!ctx)
+ return;
+
+ a = ctx->in_params.sample_rate;
+ b = ctx->out_params.sample_rate;
+
+ a = gcd(a, b);
+
+ /* divide by gcd to normalize the rate */
+ ctx->in_params.norm_rate = ctx->in_params.sample_rate / a;
+ ctx->out_params.norm_rate = ctx->out_params.sample_rate / a;
+}
+
+/* resets the pointer of the coeff memory pointers */
+static int fsl_coeff_mem_ptr_reset(struct fsl_easrc *easrc,
+ unsigned int ctx_id, int mem_type)
+{
+ struct device *dev;
+ int ret = 0;
+ 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.
+ */
+ ret |= regmap_update_bits(easrc->regmap, reg, mask, 0);
+ ret |= regmap_update_bits(easrc->regmap, reg, mask, val);
+ ret |= regmap_update_bits(easrc->regmap, reg, mask, 0);
+
+ return ret;
+}
+
+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_easrc *easrc)
+{
+ struct device *dev = &easrc->pdev->dev;
+ struct asrc_firmware_hdr *hdr = easrc->firmware_hdr;
+ struct interp_params *interp = easrc->interp;
+ struct interp_params *selected_interp = NULL;
+ unsigned int num_coeff;
+ unsigned int i;
+ u64 *arr;
+ u32 r0, r1;
+ 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->rs_num_taps)) {
+ arr = 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;
+ r0 = r[0] & EASRC_32b_MASK;
+ r1 = r[1] & EASRC_32b_MASK;
+
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_RCTCL,
+ EASRC_RCTCL_RS_CL(r0));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_RCTCH,
+ EASRC_RCTCH_RS_CH(r1));
+ if (ret)
+ return ret;
+
+ /* Write Number of Resampling Coefficient Taps
+ * 00b - 32-Tap Resampling Filter
+ * 01b - 64-Tap Resampling Filter
+ * 10b - 128-Tap Resampling Filter
+ * 11b - N/A
+ */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CRCC,
+ EASRC_CRCC_RS_TAPS_MASK,
+ EASRC_CRCC_RS_TAPS(easrc->rs_num_taps));
+ if (ret)
+ return ret;
+
+ /* Reset prefilter coefficient pointer back to 0 */
+ ret = fsl_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 *)&arr[i];
+ r0 = r[0] & EASRC_32b_MASK;
+ r1 = r[1] & EASRC_32b_MASK;
+
+ ret |= regmap_write(easrc->regmap,
+ REG_EASRC_CRCM,
+ EASRC_CRCM_RS_CWD(r0));
+
+ ret |= regmap_write(easrc->regmap,
+ REG_EASRC_CRCM,
+ EASRC_CRCM_RS_CWD(r1));
+
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ * 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_easrc
+ * filterIn : Pointer to non-scaled input filter
+ * shift: The multiply factor
+ * output:
+ * filterOut: scaled filter
+ *****************************************************************************/
+static int NormalizedFilterForFloat32InIntOut(struct fsl_easrc *easrc,
+ uint64_t *filterIn,
+ uint64_t *filterOut,
+ int shift)
+{
+ struct device *dev = &easrc->pdev->dev;
+ uint64_t coef = *filterIn;
+ int64_t exp = (coef & 0x7ff0000000000000ll) >> 52;
+ uint64_t coefOut;
+
+ /*
+ * If exponent is zero (value == 0), or 7ff (value == NaNs)
+ * dont touch the content
+ */
+ if (((coef & 0x7ff0000000000000ll) == 0) ||
+ ((coef & 0x7ff0000000000000ll) == ((uint64_t)0x7ff << 52))) {
+ *filterOut = coef;
+ } else {
+ if ((shift > 0 && (shift + exp) >= 2047) ||
+ (shift < 0 && (exp + shift) <= 0)) {
+ dev_err(dev, "coef error\n");
+ return -EINVAL;
+ }
+
+ /* coefficient * 2^shift ==> coefficient_exp + shift */
+ exp += shift;
+ coefOut = (uint64_t)(coef & 0x800FFFFFFFFFFFFFll) +
+ ((uint64_t)exp << 52);
+ *filterOut = coefOut;
+ }
+
+ return 0;
+}
+
+static int write_pf_coeff_mem(struct fsl_easrc *easrc, int ctx_id,
+ u64 *arr, int n_taps, int shift)
+{
+ struct device *dev = &easrc->pdev->dev;
+ int ret = 0;
+ int i;
+ u32 *r;
+ u32 r0, r1;
+ u64 tmp;
+
+ /* If STx_NUM_TAPS is set to 0x0 then return */
+ if (!n_taps)
+ return 0;
+
+ if (!arr) {
+ dev_err(dev, "NULL buffer\n");
+ return -EINVAL;
+ }
+
+ /* When switching between stages, the address pointer
+ * should be reset back to 0x0 before performing a write
+ */
+ ret = fsl_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 = NormalizedFilterForFloat32InIntOut(easrc,
+ &arr[i],
+ &tmp,
+ shift);
+ if (ret)
+ return ret;
+
+ r = (uint32_t *)&tmp;
+
+ r0 = r[0] & EASRC_32b_MASK;
+ r1 = r[1] & EASRC_32b_MASK;
+
+ ret |= regmap_write(easrc->regmap,
+ REG_EASRC_PCF(ctx_id),
+ EASRC_PCF_CD(r0));
+
+ ret |= regmap_write(easrc->regmap,
+ REG_EASRC_PCF(ctx_id),
+ EASRC_PCF_CD(r1));
+
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_easrc_prefilter_config(struct fsl_easrc *easrc,
+ unsigned int ctx_id)
+{
+ struct fsl_easrc_context *ctx;
+ struct asrc_firmware_hdr *hdr;
+ struct prefil_params *prefil, *selected_prefil = NULL;
+ struct device *dev;
+ u32 inrate, outrate, offset = 0;
+ int ret, i;
+
+ /* to modify prefilter coeficients, the user must perform
+ * a write in ASRC_PRE_COEFF_FIFO[COEFF_DATA] while the
+ * RUN_EN for that context is set to 0
+ */
+ 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->ctx[ctx_id];
+
+ ctx->in_filled_sample = bits_taps_to_val(easrc->rs_num_taps) / 2;
+ ctx->out_missed_sample = ctx->in_filled_sample *
+ ctx->out_params.sample_rate /
+ ctx->in_params.sample_rate;
+
+ ctx->st1_num_taps = 0;
+ ctx->st2_num_taps = 0;
+
+ ret = regmap_write(easrc->regmap, REG_EASRC_CCE1(ctx_id), 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(easrc->regmap, REG_EASRC_CCE2(ctx_id), 0);
+ if (ret)
+ return ret;
+
+ /* prefilter is enabled only when doing downsampling.
+ * When out_rate >= in_rate, pf will be in bypass mode
+ */
+ if (ctx->out_params.sample_rate >= ctx->in_params.sample_rate) {
+ if (ctx->out_params.sample_rate == ctx->in_params.sample_rate) {
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_RS_BYPASS_MASK,
+ EASRC_CCE1_RS_BYPASS);
+ if (ret)
+ return ret;
+ }
+
+ if (ctx->in_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE &&
+ ctx->out_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE) {
+ ctx->st1_num_taps = 1;
+ ctx->st1_coeff = &easrc->const_coeff;
+ ctx->st1_num_exp = 1;
+ ctx->st2_num_taps = 0;
+ ctx->st1_addexp = 31;
+ } else if (ctx->in_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE &&
+ ctx->out_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE) {
+ ctx->st1_num_taps = 1;
+ ctx->st1_coeff = &easrc->const_coeff;
+ ctx->st1_num_exp = 1;
+ ctx->st2_num_taps = 0;
+ ctx->st1_addexp -= ctx->in_params.fmt.addexp;
+ } else {
+ ctx->st1_num_taps = 1;
+ ctx->st1_coeff = &easrc->const_coeff;
+ ctx->st1_num_exp = 1;
+ ctx->st2_num_taps = 0;
+ }
+ } else {
+ inrate = ctx->in_params.norm_rate;
+ outrate = ctx->out_params.norm_rate;
+
+ hdr = easrc->firmware_hdr;
+ prefil = easrc->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",
+ ctx->in_params.sample_rate,
+ inrate,
+ ctx->out_params.sample_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->st1_num_taps = selected_prefil->st1_taps;
+ ctx->st1_coeff = selected_prefil->coeff;
+ ctx->st1_num_exp = selected_prefil->st1_exp;
+
+ offset = ((selected_prefil->st1_taps + 1) / 2) *
+ sizeof(selected_prefil->coeff[0]);
+ ctx->st2_num_taps = selected_prefil->st2_taps;
+ ctx->st2_coeff = (uint64_t *)((uint64_t)selected_prefil->coeff + offset);
+
+ if (ctx->in_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE &&
+ ctx->out_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE) {
+ /* only change stage2 coefficient for 2 stage case */
+ if (ctx->st2_num_taps > 0)
+ ctx->st2_addexp = 31;
+ else
+ ctx->st1_addexp = 31;
+ } else if (ctx->in_params.sample_format != SNDRV_PCM_FORMAT_FLOAT_LE &&
+ ctx->out_params.sample_format == SNDRV_PCM_FORMAT_FLOAT_LE) {
+ if (ctx->st2_num_taps > 0)
+ ctx->st2_addexp -= ctx->in_params.fmt.addexp;
+ else
+ ctx->st1_addexp -= ctx->in_params.fmt.addexp;
+ }
+ }
+
+ ctx->in_filled_sample += (ctx->st1_num_taps / 2) * ctx->st1_num_exp +
+ ctx->st2_num_taps / 2;
+ ctx->out_missed_sample = ctx->in_filled_sample *
+ ctx->out_params.sample_rate /
+ ctx->in_params.sample_rate;
+
+ if (ctx->in_filled_sample * ctx->out_params.sample_rate %
+ ctx->in_params.sample_rate != 0)
+ ctx->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
+ */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx_id),
+ EASRC_CC_EN_MASK, 0);
+ if (ret)
+ goto ctx_error;
+
+ if (ctx->st1_num_taps > EASRC_MAX_PF_TAPS) {
+ dev_err(dev, "ST1 taps [%d] mus be lower than %d\n",
+ ctx->st1_num_taps, EASRC_MAX_PF_TAPS);
+ ret = -EINVAL;
+ goto ctx_error;
+ }
+
+ /* Update ctx ST1_NUM_TAPS in Context Control Extended 2 register */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CCE2(ctx_id),
+ EASRC_CCE2_ST1_TAPS_MASK,
+ EASRC_CCE2_ST1_TAPS(ctx->st1_num_taps - 1));
+ if (ret)
+ goto ctx_error;
+
+ /* Prefilter Coefficient Write Select to write in ST1 coeff */
+ ret = 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));
+ if (ret)
+ goto ctx_error;
+
+ ret = write_pf_coeff_mem(easrc, ctx_id,
+ ctx->st1_coeff,
+ ctx->st1_num_taps,
+ ctx->st1_addexp);
+ if (ret)
+ goto ctx_error;
+
+ if (ctx->st2_num_taps > 0) {
+ if (ctx->st2_num_taps + ctx->st1_num_taps > EASRC_MAX_PF_TAPS) {
+ dev_err(dev, "ST2 taps [%d] mus be lower than %d\n",
+ ctx->st2_num_taps, EASRC_MAX_PF_TAPS);
+ ret = -EINVAL;
+ goto ctx_error;
+ }
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_PF_TSEN_MASK,
+ EASRC_CCE1_PF_TSEN);
+ if (ret)
+ goto ctx_error;
+ /*
+ * Enable prefilter stage1 writeback floating point
+ * which is used for FLOAT_LE case
+ */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_PF_ST1_WBFP_MASK,
+ EASRC_CCE1_PF_ST1_WBFP);
+ if (ret)
+ goto ctx_error;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_PF_EXP_MASK,
+ EASRC_CCE1_PF_EXP(ctx->st1_num_exp - 1));
+ if (ret)
+ goto ctx_error;
+
+ /* Update ctx ST2_NUM_TAPS in Context Control Extended 2 reg */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CCE2(ctx_id),
+ EASRC_CCE2_ST2_TAPS_MASK,
+ EASRC_CCE2_ST2_TAPS(ctx->st2_num_taps - 1));
+ if (ret)
+ goto ctx_error;
+
+ /* Prefilter Coefficient Write Select to write in ST2 coeff */
+ ret = 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);
+ if (ret)
+ goto ctx_error;
+
+ ret = write_pf_coeff_mem(easrc, ctx_id,
+ ctx->st2_coeff,
+ ctx->st2_num_taps,
+ ctx->st2_addexp);
+ if (ret)
+ goto ctx_error;
+ }
+
+ return 0;
+
+ctx_error:
+ return ret;
+}
+
+static int fsl_easrc_max_ch_for_slot(struct fsl_easrc_context *ctx,
+ struct fsl_easrc_slot *slot)
+{
+ 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->st1_num_taps > 0) {
+ if (ctx->st2_num_taps > 0)
+ st1_mem_alloc =
+ (ctx->st1_num_taps - 1) * ctx->st1_num_exp + 1;
+ else
+ st1_mem_alloc = ctx->st1_num_taps;
+ }
+
+ if (ctx->st2_num_taps > 0)
+ st2_mem_alloc = ctx->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_easrc_context *ctx,
+ struct fsl_easrc_slot *slot,
+ unsigned int slot_idx,
+ unsigned int reg0,
+ unsigned int reg1,
+ unsigned int reg2,
+ unsigned int reg3,
+ unsigned int *req_channels,
+ unsigned int *start_channel,
+ unsigned int *avail_channel)
+{
+ struct fsl_easrc *easrc = ctx->easrc;
+ int st1_chanxexp, st1_mem_alloc = 0, st2_mem_alloc = 0;
+ unsigned int addr;
+ int ret;
+
+ if (*req_channels <= *avail_channel) {
+ slot->num_channel = *req_channels;
+ slot->min_channel = *start_channel;
+ slot->max_channel = *start_channel + *req_channels - 1;
+ slot->ctx_index = ctx->index;
+ slot->busy = true;
+ *start_channel += *req_channels;
+ *req_channels = 0;
+ } else {
+ slot->num_channel = *avail_channel;
+ slot->min_channel = *start_channel;
+ slot->max_channel = *start_channel + *avail_channel - 1;
+ slot->ctx_index = ctx->index;
+ slot->busy = true;
+ *start_channel += *avail_channel;
+ *req_channels -= *avail_channel;
+ }
+
+ ret = regmap_update_bits(easrc->regmap,
+ reg0,
+ EASRC_DPCS0R0_MAXCH_MASK,
+ EASRC_DPCS0R0_MAXCH(slot->max_channel));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ reg0,
+ EASRC_DPCS0R0_MINCH_MASK,
+ EASRC_DPCS0R0_MINCH(slot->min_channel));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ reg0,
+ EASRC_DPCS0R0_NUMCH_MASK,
+ EASRC_DPCS0R0_NUMCH(slot->num_channel - 1));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ reg0,
+ EASRC_DPCS0R0_CTXNUM_MASK,
+ EASRC_DPCS0R0_CTXNUM(slot->ctx_index));
+ if (ret)
+ return ret;
+
+ if (ctx->st1_num_taps > 0) {
+ if (ctx->st2_num_taps > 0)
+ st1_mem_alloc =
+ (ctx->st1_num_taps - 1) * slot->num_channel *
+ ctx->st1_num_exp + slot->num_channel;
+ else
+ st1_mem_alloc = ctx->st1_num_taps * slot->num_channel;
+
+ slot->pf_mem_used = st1_mem_alloc;
+ ret = regmap_update_bits(easrc->regmap,
+ reg2,
+ EASRC_DPCS0R2_ST1_MA_MASK,
+ EASRC_DPCS0R2_ST1_MA(st1_mem_alloc));
+ if (ret)
+ return ret;
+
+ if (slot_idx == 1)
+ addr = 0x1800 - st1_mem_alloc;
+ else
+ addr = 0;
+
+ ret = regmap_update_bits(easrc->regmap,
+ reg2,
+ EASRC_DPCS0R2_ST1_SA_MASK,
+ EASRC_DPCS0R2_ST1_SA(addr));
+ if (ret)
+ return ret;
+ }
+
+ if (ctx->st2_num_taps > 0) {
+ st1_chanxexp = slot->num_channel * (ctx->st1_num_exp - 1);
+
+ ret = regmap_update_bits(easrc->regmap,
+ reg1,
+ EASRC_DPCS0R1_ST1_EXP_MASK,
+ EASRC_DPCS0R1_ST1_EXP(st1_chanxexp));
+ if (ret)
+ return ret;
+
+ st2_mem_alloc = slot->num_channel * ctx->st2_num_taps;
+ slot->pf_mem_used += st2_mem_alloc;
+ ret = regmap_update_bits(easrc->regmap,
+ reg3,
+ EASRC_DPCS0R3_ST2_MA_MASK,
+ EASRC_DPCS0R3_ST2_MA(st2_mem_alloc));
+ if (ret)
+ return ret;
+
+ if (slot_idx == 1)
+ addr = 0x1800 - st1_mem_alloc - st2_mem_alloc;
+ else
+ addr = st1_mem_alloc;
+
+ ret = regmap_update_bits(easrc->regmap,
+ reg3,
+ EASRC_DPCS0R3_ST2_SA_MASK,
+ EASRC_DPCS0R3_ST2_SA(addr));
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits(easrc->regmap,
+ reg0,
+ EASRC_DPCS0R0_EN_MASK,
+ EASRC_DPCS0R0_EN);
+ if (ret)
+ return ret;
+
+ 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_easrc *easrc, unsigned int ctx_id)
+{
+ struct fsl_easrc_context *ctx = easrc->ctx[ctx_id];
+ int req_channels = ctx->channels;
+ int start_channel = 0, avail_channel;
+ struct fsl_easrc_slot *slot0, *slot1;
+ int i, ret;
+
+ if (req_channels <= 0)
+ return -EINVAL;
+
+ for (i = 0; i < EASRC_CTX_MAX_NUM; i++) {
+ slot0 = &easrc->slot[i][0];
+ slot1 = &easrc->slot[i][1];
+
+ if (slot0->busy && slot1->busy)
+ continue;
+
+ if (!slot0->busy) {
+ if (slot1->busy && slot1->ctx_index == ctx->index)
+ continue;
+
+ avail_channel = fsl_easrc_max_ch_for_slot(ctx, slot1);
+ if (avail_channel <= 0)
+ continue;
+
+ ret = fsl_easrc_config_one_slot(ctx,
+ slot0, 0,
+ REG_EASRC_DPCS0R0(i),
+ REG_EASRC_DPCS0R1(i),
+ REG_EASRC_DPCS0R2(i),
+ REG_EASRC_DPCS0R3(i),
+ &req_channels,
+ &start_channel,
+ &avail_channel);
+ if (ret)
+ return ret;
+
+ if (req_channels > 0)
+ continue;
+ else
+ break;
+ }
+
+ if (slot0->busy && !slot1->busy) {
+ if (slot0->ctx_index == ctx->index)
+ continue;
+
+ avail_channel = fsl_easrc_max_ch_for_slot(ctx, slot0);
+ if (avail_channel <= 0)
+ continue;
+
+ ret = fsl_easrc_config_one_slot(ctx,
+ slot1, 1,
+ REG_EASRC_DPCS1R0(i),
+ REG_EASRC_DPCS1R1(i),
+ REG_EASRC_DPCS1R2(i),
+ REG_EASRC_DPCS1R3(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_easrc *easrc, unsigned int ctx_id)
+{
+ struct fsl_easrc_context *ctx = easrc->ctx[ctx_id];
+ int i, ret;
+
+ for (i = 0; i < EASRC_CTX_MAX_NUM; i++) {
+ if (easrc->slot[i][0].busy &&
+ easrc->slot[i][0].ctx_index == ctx->index) {
+ easrc->slot[i][0].busy = false;
+ easrc->slot[i][0].num_channel = 0;
+ easrc->slot[i][0].pf_mem_used = 0;
+ /* set registers */
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_DPCS0R0(i), 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_DPCS0R1(i), 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_DPCS0R2(i), 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_DPCS0R3(i), 0);
+ if (ret)
+ return ret;
+ }
+
+ if (easrc->slot[i][1].busy &&
+ easrc->slot[i][1].ctx_index == ctx->index) {
+ easrc->slot[i][1].busy = false;
+ easrc->slot[i][1].num_channel = 0;
+ easrc->slot[i][1].pf_mem_used = 0;
+ /* set registers */
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_DPCS1R0(i), 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_DPCS1R1(i), 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_DPCS1R2(i), 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(easrc->regmap,
+ REG_EASRC_DPCS1R3(i), 0);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/* fsl_easrc_config_context
+ *
+ * configure the register relate with context.
+ */
+int fsl_easrc_config_context(struct fsl_easrc *easrc, unsigned int ctx_id)
+{
+ struct fsl_easrc_context *ctx;
+ struct device *dev;
+ unsigned long lock_flags;
+ int ret;
+
+ /* to modify prefilter coeficients, the user must perform
+ * a write in ASRC_PRE_COEFF_FIFO[COEFF_DATA] while the
+ * RUN_EN for that context is set to 0
+ */
+ 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->ctx[ctx_id];
+
+ fsl_easrc_normalize_rates(ctx);
+
+ ret = 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
+ */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_RS_INIT_MASK,
+ EASRC_CCE1_RS_INIT(ctx->rs_init_mode));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CCE1(ctx_id),
+ EASRC_CCE1_PF_INIT_MASK,
+ EASRC_CCE1_PF_INIT(ctx->pf_init_mode));
+ if (ret)
+ return ret;
+
+ /* Context Input FIFO Watermark */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx_id),
+ EASRC_CC_FIFO_WTMK_MASK,
+ EASRC_CC_FIFO_WTMK(ctx->in_params.fifo_wtmk));
+ if (ret)
+ return ret;
+
+ /* Context Output FIFO Watermark */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COC(ctx_id),
+ EASRC_COC_FIFO_WTMK_MASK,
+ EASRC_COC_FIFO_WTMK(ctx->out_params.fifo_wtmk - 1));
+ if (ret)
+ return ret;
+
+ /* number of channels */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx_id),
+ EASRC_CC_CHEN_MASK,
+ EASRC_CC_CHEN(ctx->channels - 1));
+ return ret;
+}
+
+static int fsl_easrc_process_format(struct fsl_easrc_context *ctx,
+ struct fsl_easrc_data_fmt *fmt,
+ snd_pcm_format_t raw_fmt)
+{
+ struct fsl_easrc *easrc = ctx->easrc;
+ 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->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;
+}
+
+int fsl_easrc_set_ctx_format(struct fsl_easrc_context *ctx,
+ snd_pcm_format_t *in_raw_format,
+ snd_pcm_format_t *out_raw_format)
+{
+ struct fsl_easrc *easrc = ctx->easrc;
+ struct fsl_easrc_data_fmt *in_fmt = &ctx->in_params.fmt;
+ struct fsl_easrc_data_fmt *out_fmt = &ctx->out_params.fmt;
+ int ret;
+
+ /* 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;
+ }
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_BPS_MASK,
+ EASRC_CC_BPS(in_fmt->width));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_ENDIANNESS_MASK,
+ in_fmt->endianness << EASRC_CC_ENDIANNESS_SHIFT);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_FMT_MASK,
+ in_fmt->floating_point << EASRC_CC_FMT_SHIFT);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_INSIGN_MASK,
+ in_fmt->unsign << EASRC_CC_INSIGN_SHIFT);
+
+ /* In Sample Position */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_SAMPLE_POS_MASK,
+ EASRC_CC_SAMPLE_POS(in_fmt->sample_pos));
+ if (ret)
+ return ret;
+
+ /* 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;
+ }
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COC(ctx->index),
+ EASRC_COC_BPS_MASK,
+ EASRC_COC_BPS(out_fmt->width));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COC(ctx->index),
+ EASRC_COC_ENDIANNESS_MASK,
+ out_fmt->endianness << EASRC_COC_ENDIANNESS_SHIFT);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COC(ctx->index),
+ EASRC_COC_FMT_MASK,
+ out_fmt->floating_point << EASRC_COC_FMT_SHIFT);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COC(ctx->index),
+ EASRC_COC_OUTSIGN_MASK,
+ out_fmt->unsign << EASRC_COC_OUTSIGN_SHIFT);
+ if (ret)
+ return ret;
+
+ /* Out Sample Position */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COC(ctx->index),
+ EASRC_COC_SAMPLE_POS_MASK,
+ EASRC_COC_SAMPLE_POS(out_fmt->sample_pos));
+ if (ret)
+ return ret;
+
+ ret = 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
+ */
+int fsl_easrc_set_ctx_organziation(struct fsl_easrc_context *ctx)
+{
+ struct device *dev;
+ struct fsl_easrc *easrc;
+ int ret;
+
+ if (!ctx)
+ return -ENODEV;
+
+ easrc = ctx->easrc;
+ dev = &easrc->pdev->dev;
+
+ /* input interleaving parameters */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CIA(ctx->index),
+ EASRC_CIA_ITER_MASK,
+ EASRC_CIA_ITER(ctx->in_params.iterations));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CIA(ctx->index),
+ EASRC_CIA_GRLEN_MASK,
+ EASRC_CIA_GRLEN(ctx->in_params.group_len));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CIA(ctx->index),
+ EASRC_CIA_ACCLEN_MASK,
+ EASRC_CIA_ACCLEN(ctx->in_params.access_len));
+ if (ret)
+ return ret;
+
+ /* output interleaving parameters */
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COA(ctx->index),
+ EASRC_COA_ITER_MASK,
+ EASRC_COA_ITER(ctx->out_params.iterations));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COA(ctx->index),
+ EASRC_COA_GRLEN_MASK,
+ EASRC_COA_GRLEN(ctx->out_params.group_len));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COA(ctx->index),
+ EASRC_COA_ACCLEN_MASK,
+ EASRC_COA_ACCLEN(ctx->out_params.access_len));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/* Request one of the available contexts
+ *
+ * Returns a negative number on error and >=0 as context id
+ * on success
+ */
+int fsl_easrc_request_context(struct fsl_easrc_context *ctx,
+ unsigned int channels)
+{
+ enum asrc_pair_index index = ASRC_INVALID_PAIR;
+ struct fsl_easrc *easrc = ctx->easrc;
+ 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->ctx[i])
+ continue;
+
+ index = i;
+ break;
+ }
+
+ if (index == ASRC_INVALID_PAIR) {
+ dev_err(dev, "all contexts are busy\n");
+ ret = -EBUSY;
+ } else if (channels > easrc->chn_avail) {
+ dev_err(dev, "can't give the required channels: %d\n",
+ channels);
+ ret = -EINVAL;
+ } else {
+ ctx->index = index;
+ ctx->channels = channels;
+ easrc->ctx[index] = ctx;
+ easrc->chn_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
+ */
+int fsl_easrc_release_context(struct fsl_easrc_context *ctx)
+{
+ unsigned long lock_flags;
+ struct fsl_easrc *easrc;
+ struct device *dev;
+ int ret;
+
+ if (!ctx)
+ return 0;
+
+ easrc = ctx->easrc;
+ dev = &easrc->pdev->dev;
+
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+
+ ret = fsl_easrc_release_slot(easrc, ctx->index);
+
+ easrc->chn_avail += ctx->channels;
+ easrc->ctx[ctx->index] = NULL;
+
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+
+ return ret;
+}
+
+/* Start the context
+ *
+ * Enable the DMA request and context
+ */
+int fsl_easrc_start_context(struct fsl_easrc_context *ctx)
+{
+ struct fsl_easrc *easrc = ctx->easrc;
+ int ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_FWMDE_MASK,
+ EASRC_CC_FWMDE);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COC(ctx->index),
+ EASRC_COC_FWMDE_MASK,
+ EASRC_COC_FWMDE);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_EN_MASK,
+ EASRC_CC_EN);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/* Stop the context
+ *
+ * Disable the DMA request and context
+ */
+int fsl_easrc_stop_context(struct fsl_easrc_context *ctx)
+{
+ struct fsl_easrc *easrc = ctx->easrc;
+ int ret, val, i;
+ int size = 0;
+ int retry = 200;
+
+ regmap_read(easrc->regmap, REG_EASRC_CC(ctx->index), &val);
+
+ if (val & EASRC_CC_EN_MASK) {
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_STOP_MASK, EASRC_CC_STOP);
+ if (ret)
+ return ret;
+
+ 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_err(&easrc->pdev->dev, "RUN STOP fail\n");
+ }
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_EN_MASK | EASRC_CC_STOP_MASK, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_CC(ctx->index),
+ EASRC_CC_FWMDE_MASK, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(easrc->regmap,
+ REG_EASRC_COC(ctx->index),
+ EASRC_COC_FWMDE_MASK, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+struct dma_chan *fsl_easrc_get_dma_channel(struct fsl_easrc_context *ctx,
+ bool dir)
+{
+ struct fsl_easrc *easrc = ctx->easrc;
+ 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);
+};
+EXPORT_SYMBOL_GPL(fsl_easrc_get_dma_channel);
+
+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_easrc_context *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_easrc *easrc = snd_soc_dai_get_drvdata(dai);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_context *ctx = runtime->private_data;
+ 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(ctx, channels);
+ if (ret) {
+ dev_err(dev, "failed to request context\n");
+ return ret;
+ }
+
+ ctx->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->in_params.sample_rate = rate;
+ ctx->in_params.sample_format = format;
+ ctx->out_params.sample_rate = easrc->easrc_rate;
+ ctx->out_params.sample_format = easrc->easrc_format;
+ } else {
+ ctx->out_params.sample_rate = rate;
+ ctx->out_params.sample_format = format;
+ ctx->in_params.sample_rate = easrc->easrc_rate;
+ ctx->in_params.sample_format = easrc->easrc_format;
+ }
+
+ ctx->channels = channels;
+ ctx->in_params.fifo_wtmk = 0x20;
+ ctx->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->in_params.sample_format,
+ &ctx->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->in_params.iterations = 1;
+ ctx->in_params.group_len = ctx->channels;
+ ctx->in_params.access_len = ctx->channels;
+ ctx->out_params.iterations = 1;
+ ctx->out_params.group_len = ctx->channels;
+ ctx->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_easrc_context *ctx = runtime->private_data;
+ int ret;
+
+ if (ctx && (ctx->ctx_streams & BIT(substream->stream))) {
+ ctx->ctx_streams &= ~BIT(substream->stream);
+ ret = fsl_easrc_release_context(ctx);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static 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_easrc *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),
+};
+
+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 bool fsl_easrc_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_EASRC_RDFIFO(0):
+ case REG_EASRC_RDFIFO(1):
+ case REG_EASRC_RDFIFO(2):
+ case REG_EASRC_RDFIFO(3):
+ case REG_EASRC_CC(0):
+ case REG_EASRC_CC(1):
+ case REG_EASRC_CC(2):
+ case REG_EASRC_CC(3):
+ case REG_EASRC_CCE1(0):
+ case REG_EASRC_CCE1(1):
+ case REG_EASRC_CCE1(2):
+ case REG_EASRC_CCE1(3):
+ case REG_EASRC_CCE2(0):
+ case REG_EASRC_CCE2(1):
+ case REG_EASRC_CCE2(2):
+ case REG_EASRC_CCE2(3):
+ case REG_EASRC_CIA(0):
+ case REG_EASRC_CIA(1):
+ case REG_EASRC_CIA(2):
+ case REG_EASRC_CIA(3):
+ case REG_EASRC_DPCS0R0(0):
+ case REG_EASRC_DPCS0R0(1):
+ case REG_EASRC_DPCS0R0(2):
+ case REG_EASRC_DPCS0R0(3):
+ case REG_EASRC_DPCS0R1(0):
+ case REG_EASRC_DPCS0R1(1):
+ case REG_EASRC_DPCS0R1(2):
+ case REG_EASRC_DPCS0R1(3):
+ case REG_EASRC_DPCS0R2(0):
+ case REG_EASRC_DPCS0R2(1):
+ case REG_EASRC_DPCS0R2(2):
+ case REG_EASRC_DPCS0R2(3):
+ case REG_EASRC_DPCS0R3(0):
+ case REG_EASRC_DPCS0R3(1):
+ case REG_EASRC_DPCS0R3(2):
+ case REG_EASRC_DPCS0R3(3):
+ case REG_EASRC_DPCS1R0(0):
+ case REG_EASRC_DPCS1R0(1):
+ case REG_EASRC_DPCS1R0(2):
+ case REG_EASRC_DPCS1R0(3):
+ case REG_EASRC_DPCS1R1(0):
+ case REG_EASRC_DPCS1R1(1):
+ case REG_EASRC_DPCS1R1(2):
+ case REG_EASRC_DPCS1R1(3):
+ case REG_EASRC_DPCS1R2(0):
+ case REG_EASRC_DPCS1R2(1):
+ case REG_EASRC_DPCS1R2(2):
+ case REG_EASRC_DPCS1R2(3):
+ case REG_EASRC_DPCS1R3(0):
+ case REG_EASRC_DPCS1R3(1):
+ case REG_EASRC_DPCS1R3(2):
+ case REG_EASRC_DPCS1R3(3):
+ case REG_EASRC_COC(0):
+ case REG_EASRC_COC(1):
+ case REG_EASRC_COC(2):
+ case REG_EASRC_COC(3):
+ case REG_EASRC_COA(0):
+ case REG_EASRC_COA(1):
+ case REG_EASRC_COA(2):
+ case REG_EASRC_COA(3):
+ case REG_EASRC_SFS(0):
+ case REG_EASRC_SFS(1):
+ case REG_EASRC_SFS(2):
+ case REG_EASRC_SFS(3):
+ case REG_EASRC_RRL(0):
+ case REG_EASRC_RRL(1):
+ case REG_EASRC_RRL(2):
+ case REG_EASRC_RRL(3):
+ case REG_EASRC_RRH(0):
+ case REG_EASRC_RRH(1):
+ case REG_EASRC_RRH(2):
+ case REG_EASRC_RRH(3):
+ case REG_EASRC_RUC(0):
+ case REG_EASRC_RUC(1):
+ case REG_EASRC_RUC(2):
+ case REG_EASRC_RUC(3):
+ case REG_EASRC_RUR(0):
+ case REG_EASRC_RUR(1):
+ case REG_EASRC_RUR(2):
+ case REG_EASRC_RUR(3): /* fallthrough */
+ case REG_EASRC_RCTCL:
+ case REG_EASRC_RCTCH:
+ case REG_EASRC_PCF(0):
+ case REG_EASRC_PCF(1):
+ case REG_EASRC_PCF(2):
+ case REG_EASRC_PCF(3): /* fallthrough */
+ case REG_EASRC_CRCC:
+ case REG_EASRC_IRQC:
+ case REG_EASRC_IRQF:
+ case REG_EASRC_CS0(0):
+ case REG_EASRC_CS0(1):
+ case REG_EASRC_CS0(2):
+ case REG_EASRC_CS0(3):
+ case REG_EASRC_CS1(0):
+ case REG_EASRC_CS1(1):
+ case REG_EASRC_CS1(2):
+ case REG_EASRC_CS1(3):
+ case REG_EASRC_CS2(0):
+ case REG_EASRC_CS2(1):
+ case REG_EASRC_CS2(2):
+ case REG_EASRC_CS2(3):
+ case REG_EASRC_CS3(0):
+ case REG_EASRC_CS3(1):
+ case REG_EASRC_CS3(2):
+ case REG_EASRC_CS3(3):
+ case REG_EASRC_CS4(0):
+ case REG_EASRC_CS4(1):
+ case REG_EASRC_CS4(2):
+ case REG_EASRC_CS4(3):
+ case REG_EASRC_CS5(0):
+ case REG_EASRC_CS5(1):
+ case REG_EASRC_CS5(2):
+ case REG_EASRC_CS5(3): /* fallthrough */
+ case REG_EASRC_DBGC:
+ case REG_EASRC_DBGS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_easrc_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_EASRC_WRFIFO(0):
+ case REG_EASRC_WRFIFO(1):
+ case REG_EASRC_WRFIFO(2):
+ case REG_EASRC_WRFIFO(3):
+ case REG_EASRC_CC(0):
+ case REG_EASRC_CC(1):
+ case REG_EASRC_CC(2):
+ case REG_EASRC_CC(3):
+ case REG_EASRC_CCE1(0):
+ case REG_EASRC_CCE1(1):
+ case REG_EASRC_CCE1(2):
+ case REG_EASRC_CCE1(3):
+ case REG_EASRC_CCE2(0):
+ case REG_EASRC_CCE2(1):
+ case REG_EASRC_CCE2(2):
+ case REG_EASRC_CCE2(3):
+ case REG_EASRC_CIA(0):
+ case REG_EASRC_CIA(1):
+ case REG_EASRC_CIA(2):
+ case REG_EASRC_CIA(3):
+ case REG_EASRC_DPCS0R0(0):
+ case REG_EASRC_DPCS0R0(1):
+ case REG_EASRC_DPCS0R0(2):
+ case REG_EASRC_DPCS0R0(3):
+ case REG_EASRC_DPCS0R1(0):
+ case REG_EASRC_DPCS0R1(1):
+ case REG_EASRC_DPCS0R1(2):
+ case REG_EASRC_DPCS0R1(3):
+ case REG_EASRC_DPCS0R2(0):
+ case REG_EASRC_DPCS0R2(1):
+ case REG_EASRC_DPCS0R2(2):
+ case REG_EASRC_DPCS0R2(3):
+ case REG_EASRC_DPCS0R3(0):
+ case REG_EASRC_DPCS0R3(1):
+ case REG_EASRC_DPCS0R3(2):
+ case REG_EASRC_DPCS0R3(3):
+ case REG_EASRC_DPCS1R0(0):
+ case REG_EASRC_DPCS1R0(1):
+ case REG_EASRC_DPCS1R0(2):
+ case REG_EASRC_DPCS1R0(3):
+ case REG_EASRC_DPCS1R1(0):
+ case REG_EASRC_DPCS1R1(1):
+ case REG_EASRC_DPCS1R1(2):
+ case REG_EASRC_DPCS1R1(3):
+ case REG_EASRC_DPCS1R2(0):
+ case REG_EASRC_DPCS1R2(1):
+ case REG_EASRC_DPCS1R2(2):
+ case REG_EASRC_DPCS1R2(3):
+ case REG_EASRC_DPCS1R3(0):
+ case REG_EASRC_DPCS1R3(1):
+ case REG_EASRC_DPCS1R3(2):
+ case REG_EASRC_DPCS1R3(3):
+ case REG_EASRC_COC(0):
+ case REG_EASRC_COC(1):
+ case REG_EASRC_COC(2):
+ case REG_EASRC_COC(3):
+ case REG_EASRC_COA(0):
+ case REG_EASRC_COA(1):
+ case REG_EASRC_COA(2):
+ case REG_EASRC_COA(3):
+ case REG_EASRC_RRL(0):
+ case REG_EASRC_RRL(1):
+ case REG_EASRC_RRL(2):
+ case REG_EASRC_RRL(3):
+ case REG_EASRC_RRH(0):
+ case REG_EASRC_RRH(1):
+ case REG_EASRC_RRH(2):
+ case REG_EASRC_RRH(3):
+ case REG_EASRC_RUC(0):
+ case REG_EASRC_RUC(1):
+ case REG_EASRC_RUC(2):
+ case REG_EASRC_RUC(3):
+ case REG_EASRC_RUR(0):
+ case REG_EASRC_RUR(1):
+ case REG_EASRC_RUR(2):
+ case REG_EASRC_RUR(3): /* fallthrough */
+ case REG_EASRC_RCTCL:
+ case REG_EASRC_RCTCH:
+ case REG_EASRC_PCF(0):
+ case REG_EASRC_PCF(1):
+ case REG_EASRC_PCF(2):
+ case REG_EASRC_PCF(3): /* fallthrough */
+ case REG_EASRC_CRCM:
+ case REG_EASRC_CRCC:
+ case REG_EASRC_IRQC:
+ case REG_EASRC_IRQF:
+ case REG_EASRC_CS0(0):
+ case REG_EASRC_CS0(1):
+ case REG_EASRC_CS0(2):
+ case REG_EASRC_CS0(3):
+ case REG_EASRC_CS1(0):
+ case REG_EASRC_CS1(1):
+ case REG_EASRC_CS1(2):
+ case REG_EASRC_CS1(3):
+ case REG_EASRC_CS2(0):
+ case REG_EASRC_CS2(1):
+ case REG_EASRC_CS2(2):
+ case REG_EASRC_CS2(3):
+ case REG_EASRC_CS3(0):
+ case REG_EASRC_CS3(1):
+ case REG_EASRC_CS3(2):
+ case REG_EASRC_CS3(3):
+ case REG_EASRC_CS4(0):
+ case REG_EASRC_CS4(1):
+ case REG_EASRC_CS4(2):
+ case REG_EASRC_CS4(3):
+ case REG_EASRC_CS5(0):
+ case REG_EASRC_CS5(1):
+ case REG_EASRC_CS5(2):
+ case REG_EASRC_CS5(3): /* fallthrough */
+ case REG_EASRC_DBGC:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_easrc_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_EASRC_RDFIFO(0):
+ case REG_EASRC_RDFIFO(1):
+ case REG_EASRC_RDFIFO(2):
+ case REG_EASRC_RDFIFO(3):
+ case REG_EASRC_SFS(0):
+ case REG_EASRC_SFS(1):
+ case REG_EASRC_SFS(2):
+ case REG_EASRC_SFS(3):
+ case REG_EASRC_IRQF:
+ case REG_EASRC_DBGS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+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),
+ .readable_reg = fsl_easrc_readable_reg,
+ .volatile_reg = fsl_easrc_volatile_reg,
+ .writeable_reg = fsl_easrc_writeable_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+#include "fsl_easrc_m2m.c"
+
+void easrc_dump_firmware(struct fsl_easrc *easrc)
+{
+ struct device *dev = &easrc->pdev->dev;
+ struct asrc_firmware_hdr *firm = easrc->firmware_hdr;
+ struct interp_params *interp = easrc->interp;
+ struct prefil_params *prefil = easrc->prefil;
+ 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);
+ pr_debug("Num prefitler scenarios: %u\n", firm->prefil_scen);
+ pr_debug("Num interpolation scenarios: %u\n", firm->interp_scen);
+ pr_debug("\nInterpolation scenarios:\n");
+
+ for (i = 0; i < firm->interp_scen; i++) {
+ if (interp[i].magic != FIRMWARE_MAGIC) {
+ pr_debug("%d. wrong interp magic: %x\n",
+ i, interp[i].magic);
+ continue;
+ }
+ pr_debug("%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) {
+ pr_debug("%d. wrong prefil magic: %x\n",
+ i, prefil[i].magic);
+ continue;
+ }
+ pr_debug("%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");
+}
+
+int easrc_get_firmware(struct fsl_easrc *easrc)
+{
+ u32 pnum, inum, offset;
+ int ret;
+
+ if (!easrc)
+ return -EINVAL;
+
+ ret = request_firmware(&easrc->fw, easrc->fw_name,
+ &easrc->pdev->dev);
+ if (ret)
+ return ret;
+
+ easrc->firmware_hdr = (struct asrc_firmware_hdr *)easrc->fw->data;
+ pnum = easrc->firmware_hdr->prefil_scen;
+ inum = easrc->firmware_hdr->interp_scen;
+
+ if (inum) {
+ offset = sizeof(struct asrc_firmware_hdr);
+ easrc->interp =
+ (struct interp_params *)(easrc->fw->data + offset);
+ }
+
+ if (pnum) {
+ offset = sizeof(struct asrc_firmware_hdr) +
+ inum * sizeof(struct interp_params);
+ easrc->prefil =
+ (struct prefil_params *)(easrc->fw->data + offset);
+ }
+
+ return 0;
+}
+
+static irqreturn_t fsl_easrc_isr(int irq, void *dev_id)
+{
+ struct fsl_easrc *easrc = (struct fsl_easrc *)dev_id;
+ struct device *dev = &easrc->pdev->dev;
+
+ /* TODO treat the interrupt */
+ dev_err(dev, "interrupt\n");
+
+ return IRQ_HANDLED;
+}
+
+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 *easrc;
+ struct resource *res;
+ struct device_node *np;
+ void __iomem *regs;
+ int ret, irq;
+ u32 width;
+
+ easrc = devm_kzalloc(&pdev->dev, sizeof(*easrc), GFP_KERNEL);
+ if (!easrc)
+ return -ENOMEM;
+
+ strncpy(easrc->name, "mxc_asrc", sizeof(easrc->name) - 1);
+
+ easrc->pdev = pdev;
+ np = pdev->dev.of_node;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs)) {
+ dev_err(&pdev->dev, "failed ioremap\n");
+ return PTR_ERR(regs);
+ }
+
+ easrc->paddr = res->start;
+
+ easrc->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs,
+ &fsl_easrc_regmap_config);
+ if (IS_ERR(easrc->regmap)) {
+ dev_err(&pdev->dev, "failed to init regmap");
+ return PTR_ERR(easrc->regmap);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n",
+ dev_name(&pdev->dev));
+ return irq;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, fsl_easrc_isr, 0,
+ dev_name(&pdev->dev), easrc);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret);
+ return ret;
+ }
+
+ easrc->mem_clk = devm_clk_get(&pdev->dev, "mem");
+ if (IS_ERR(easrc->mem_clk)) {
+ dev_err(&pdev->dev, "failed to get mem clock\n");
+ return PTR_ERR(easrc->mem_clk);
+ }
+
+ /*Set default value*/
+ easrc->chn_avail = 32;
+ easrc->rs_num_taps = EASRC_RS_128_TAPS;
+ easrc->const_coeff = 0x3FF0000000000000;
+
+ ret = of_property_read_u32(np, "fsl,asrc-rate",
+ &easrc->easrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to asrc rate\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "fsl,asrc-width",
+ &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to asrc width\n");
+ return ret;
+ }
+
+ if (width != 16 && width != 24 && width != 32 && width != 20) {
+ dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n");
+ width = 24;
+ }
+
+ if (width == 24)
+ easrc->easrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else if (width == 16)
+ easrc->easrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ else
+ easrc->easrc_format = SNDRV_PCM_FORMAT_S32_LE;
+
+ platform_set_drvdata(pdev, easrc);
+ pm_runtime_enable(&pdev->dev);
+
+ spin_lock_init(&easrc->lock);
+
+ regcache_cache_only(easrc->regmap, true);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_easrc_component,
+ &fsl_easrc_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ASoC DAI\n");
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &fsl_easrc_dma_component,
+ NULL, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ASoC platform\n");
+ return ret;
+ }
+
+ ret = fsl_easrc_m2m_init(easrc);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init m2m device %d\n", ret);
+ return ret;
+ }
+
+ ret = of_property_read_string(np,
+ "fsl,easrc-ram-script-name",
+ &easrc->fw_name);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get firmware name\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_easrc_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_easrc_runtime_suspend(struct device *dev)
+{
+ struct fsl_easrc *easrc = dev_get_drvdata(dev);
+ unsigned long lock_flags;
+
+ regcache_cache_only(easrc->regmap, true);
+
+ clk_disable_unprepare(easrc->mem_clk);
+
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+ easrc->firmware_loaded = 0;
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+
+ return 0;
+}
+
+static int fsl_easrc_runtime_resume(struct device *dev)
+{
+ struct fsl_easrc *easrc = dev_get_drvdata(dev);
+ struct fsl_easrc_context *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->firmware_loaded) {
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+ goto skip_load;
+ }
+ easrc->firmware_loaded = 1;
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+
+ ret = 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->ctx[i];
+ if (ctx) {
+ set_rs_ratio(ctx);
+ ctx->out_missed_sample = ctx->in_filled_sample *
+ ctx->out_params.sample_rate /
+ ctx->in_params.sample_rate;
+ if (ctx->in_filled_sample * ctx->out_params.sample_rate
+ % ctx->in_params.sample_rate != 0)
+ ctx->out_missed_sample += 1;
+
+ ret = write_pf_coeff_mem(easrc, i,
+ ctx->st1_coeff,
+ ctx->st1_num_taps,
+ ctx->st1_addexp);
+ if (ret)
+ goto disable_mem_clk;
+
+ ret = write_pf_coeff_mem(easrc, i,
+ ctx->st2_coeff,
+ ctx->st2_num_taps,
+ ctx->st2_addexp);
+ if (ret)
+ goto disable_mem_clk;
+ }
+ }
+
+skip_load:
+ return 0;
+
+disable_mem_clk:
+ clk_disable_unprepare(easrc->mem_clk);
+ return ret;
+}
+#endif /*CONFIG_PM*/
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_easrc_suspend(struct device *dev)
+{
+ struct fsl_easrc *easrc = dev_get_drvdata(dev);
+ int ret;
+
+ fsl_easrc_m2m_suspend(easrc);
+
+ ret = pm_runtime_force_suspend(dev);
+
+ return ret;
+}
+
+static int fsl_easrc_resume(struct device *dev)
+{
+ struct fsl_easrc *easrc = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+
+ fsl_easrc_m2m_resume(easrc);
+
+ return ret;
+}
+#endif /*CONFIG_PM_SLEEP*/
+
+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(fsl_easrc_suspend,
+ fsl_easrc_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 000000000000..aeeb8bcdc12a
--- /dev/null
+++ b/sound/soc/fsl/fsl_easrc.h
@@ -0,0 +1,698 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 NXP
+ */
+
+#ifndef _FSL_EASRC_H
+#define _FSL_EASRC_H
+
+#include <sound/asound.h>
+#include <uapi/linux/mxc_asrc.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_data/dma-imx.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_MASK ((BIT(EASRC_RRL_RS_RL_WIDTH) - 1) \
+ << EASRC_RRL_RS_RL_SHIFT)
+#define EASRC_RRL_RS_RL(v) (((v) << EASRC_RRL_RS_RL_SHIFT) \
+ & EASRC_RRL_RS_RL_MASK)
+
+/* 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_MASK ((BIT(EASRC_RSUC_RS_RM_WIDTH) - 1) \
+ << EASRC_RSUC_RS_RM_SHIFT)
+#define EASRC_RSUC_RS_RM(v) (((v) << EASRC_RSUC_RS_RM_SHIFT) \
+ & EASRC_RSUC_RS_RM_MASK)
+
+/* 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_MASK ((BIT(EASRC_RCTCL_RS_CL_WIDTH) - 1) \
+ << EASRC_RCTCL_RS_CL_SHIFT)
+#define EASRC_RCTCL_RS_CL(v) (((v) << EASRC_RCTCL_RS_CL_SHIFT) \
+ & EASRC_RCTCL_RS_CL_MASK)
+
+/* 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_MASK ((BIT(EASRC_RCTCH_RS_CH_WIDTH) - 1) \
+ << EASRC_RCTCH_RS_CH_SHIFT)
+#define EASRC_RCTCH_RS_CH(v) (((v) << EASRC_RCTCH_RS_CH_SHIFT) \
+ & EASRC_RCTCH_RS_CH_MASK)
+
+/* ASRC Prefilter Coefficient FIFO (PCF) */
+#define EASRC_PCF_CD_SHIFT 0
+#define EASRC_PCF_CD_WIDTH 32
+#define EASRC_PCF_CD_MASK ((BIT(EASRC_PCF_CD_WIDTH) - 1) \
+ << EASRC_PCF_CD_SHIFT)
+#define EASRC_PCF_CD(v) (((v) << EASRC_PCF_CD_SHIFT) \
+ & EASRC_PCF_CD_MASK)
+
+/* 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_MASK ((BIT(EASRC_CRCM_RS_CWD_WIDTH) - 1) \
+ << EASRC_CRCM_RS_CWD_SHIFT)
+#define EASRC_CRCM_RS_CWD(v) (((v) << EASRC_CRCM_RS_CWD_SHIFT) \
+ & EASRC_CRCM_RS_CWD_MASK)
+
+/* 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_MASK ((BIT(EASRC_CSx_CSx_WIDTH) - 1) \
+ << EASRC_CSx_CSx_SHIFT)
+#define EASRC_CSx_CSx(v) (((v) << EASRC_CSx_CSx_SHIFT) \
+ & EASRC_CSx_CSx_MASK)
+
+/* 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_MASK ((BIT(EASRC_DBGS_DS_WIDTH) - 1) \
+ << EASRC_DBGS_DS_SHIFT)
+#define EASRC_DBGS_DS(v) (((v) << EASRC_DBGS_DS_SHIFT) \
+ & EASRC_DBGS_DS_MASK)
+
+/* General Constants */
+#define EASRC_CTX_MAX_NUM 4
+#define EASRC_32b_MASK (BIT(32) - 1)
+#define EASRC_64b_MASK (BIT(64) - 1)
+#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
+
+/* directions */
+#define IN 0
+#define OUT 1
+
+/* 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
+
+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;
+ unsigned int sample_format;
+ unsigned int norm_rate;
+};
+
+struct fsl_easrc_slot {
+ bool busy;
+ int ctx_index;
+ int num_channel; /*maximum is 8*/
+ int min_channel;
+ int max_channel;
+ int pf_mem_used;
+};
+
+struct fsl_easrc_context {
+ enum asrc_pair_index index;
+ struct fsl_easrc *easrc;
+ struct dma_chan *dma_chan[2];
+ struct dma_async_tx_descriptor *desc[2];
+ struct fsl_easrc_io_params in_params;
+ struct fsl_easrc_io_params out_params;
+ struct imx_dma_data dma_data;
+ unsigned int channels;
+ 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;
+ unsigned int pos;
+ u64 rs_ratio;
+ u64 *st1_coeff;
+ u64 *st2_coeff;
+ int in_filled_sample;
+ int out_missed_sample;
+ int st1_addexp;
+ int st2_addexp;
+ void *private_data;
+};
+
+/**
+ * fsl_easrc: EASRC private data
+ *
+ * name : name of EASRC device
+ * @pdev: platform device pointer
+ * @regmap: regmap handler
+ * @dma_params_rx: DMA parameters for receive channel
+ * @dma_params_tx: DMA parameters for transmit channel
+ * @ctx: context pointer
+ * @slot: slot setting
+ * @mem_clk: clock source to access register
+ * @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
+ * @paddr: physical address to the base address of registers
+ * @rs_num_taps: resample filter taps, 32, 64, or 128
+ * @bps_i2c958: bits per sample of iec958
+ * @chn_avail: available channels, maximum 32
+ * @lock: spin lock for resource protection
+ * @easrc_rate: default sample rate for ASoC Back-Ends
+ * @easrc_format: default sample format for ASoC Back-Ends
+ */
+
+struct fsl_easrc {
+ char name[32];
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct miscdevice easrc_miscdev;
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct snd_dmaengine_dai_dma_data dma_params_tx;
+ struct fsl_easrc_context *ctx[EASRC_CTX_MAX_NUM];
+ struct fsl_easrc_slot slot[EASRC_CTX_MAX_NUM][2];
+ struct clk *mem_clk;
+ struct asrc_firmware_hdr *firmware_hdr;
+ struct interp_params *interp;
+ struct prefil_params *prefil;
+ const struct firmware *fw;
+ const char *fw_name;
+ unsigned long paddr;
+ unsigned int rs_num_taps;
+ unsigned int bps_iec958[EASRC_CTX_MAX_NUM];
+ unsigned int chn_avail;
+ u64 *rs_coeff;
+ u64 const_coeff;
+ int firmware_loaded;
+ spinlock_t lock; /* spin lock for resource protection */
+ int easrc_rate;
+ int easrc_format;
+};
+
+struct dma_chan *fsl_easrc_get_dma_channel(
+ struct fsl_easrc_context *ctx, bool dir);
+int fsl_easrc_request_context(
+ struct fsl_easrc_context *ctx, unsigned int channels);
+int fsl_easrc_release_context(struct fsl_easrc_context *ctx);
+
+#define DRV_NAME "fsl-easrc-dma"
+#endif /* _FSL_EASRC_H */
diff --git a/sound/soc/fsl/fsl_easrc_dma.c b/sound/soc/fsl/fsl_easrc_dma.c
new file mode 100644
index 000000000000..d1871b48b3c1
--- /dev/null
+++ b/sound/soc/fsl/fsl_easrc_dma.c
@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2019 NXP
+
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/platform_data/dma-imx.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_easrc.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 = 65532, /* 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_easrc_dma_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_easrc_context *ctx = runtime->private_data;
+
+ ctx->pos += snd_pcm_lib_period_bytes(substream);
+ if (ctx->pos >= snd_pcm_lib_buffer_bytes(substream))
+ ctx->pos = 0;
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static int fsl_easrc_dma_prepare_and_submit(struct snd_pcm_substream *substream)
+{
+ u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_easrc_context *ctx = runtime->private_data;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct device *dev = component->dev;
+ unsigned long flags = DMA_CTRL_ACK;
+
+ /* Prepare and submit Front-End DMA channel */
+ if (!substream->runtime->no_period_wakeup)
+ flags |= DMA_PREP_INTERRUPT;
+
+ ctx->pos = 0;
+ ctx->desc[!dir] = dmaengine_prep_dma_cyclic(
+ ctx->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 (!ctx->desc[!dir]) {
+ dev_err(dev, "failed to prepare slave DMA for Front-End\n");
+ return -ENOMEM;
+ }
+
+ ctx->desc[!dir]->callback = fsl_easrc_dma_complete;
+ ctx->desc[!dir]->callback_param = substream;
+
+ dmaengine_submit(ctx->desc[!dir]);
+
+ /* Prepare and submit Back-End DMA channel */
+ ctx->desc[dir] = dmaengine_prep_dma_cyclic(
+ ctx->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0);
+ if (!ctx->desc[dir]) {
+ dev_err(dev, "failed to prepare slave DMA for Back-End\n");
+ return -ENOMEM;
+ }
+
+ dmaengine_submit(ctx->desc[dir]);
+
+ return 0;
+}
+
+static int fsl_easrc_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_easrc_context *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_dma_prepare_and_submit(substream);
+ if (ret)
+ return ret;
+ dma_async_issue_pending(ctx->dma_chan[IN]);
+ dma_async_issue_pending(ctx->dma_chan[OUT]);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dmaengine_terminate_all(ctx->dma_chan[OUT]);
+ dmaengine_terminate_all(ctx->dma_chan[IN]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_easrc_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL;
+ struct snd_dmaengine_dai_dma_data *dma_params_be = NULL;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_easrc_context *ctx = runtime->private_data;
+ struct fsl_easrc *easrc = ctx->easrc;
+ struct dma_slave_config config_fe, config_be;
+ enum asrc_pair_index index = ctx->index;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct device *dev = component->dev;
+ int stream = substream->stream;
+ struct imx_dma_data *tmp_data;
+ struct snd_soc_dpcm *dpcm;
+ struct dma_chan *tmp_chan;
+ struct device *dev_be;
+ u8 dir = tx ? OUT : IN;
+ dma_cap_mask_t mask;
+ enum sdma_peripheral_type be_peripheral_type;
+ int ret;
+
+ /* Fetch the Back-End dma_data from DPCM */
+ list_for_each_entry(dpcm, &rtd->dpcm[stream].be_clients, list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_pcm_substream *substream_be;
+ struct snd_soc_dai *dai = be->cpu_dai;
+
+ if (dpcm->fe != rtd)
+ continue;
+
+ substream_be = snd_soc_dpcm_get_substream(be, stream);
+ dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be);
+ dev_be = dai->dev;
+ break;
+ }
+
+ if (!dma_params_be) {
+ dev_err(dev, "failed to get the substream of Back-End\n");
+ return -EINVAL;
+ }
+
+ /* Override dma_data of the Front-End and config its dmaengine */
+ dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ dma_params_fe->addr = easrc->paddr + REG_EASRC_FIFO(!dir, index);
+ dma_params_fe->maxburst = dma_params_be->maxburst;
+
+ ctx->dma_chan[!dir] = fsl_easrc_get_dma_channel(ctx, !dir);
+ if (!ctx->dma_chan[!dir]) {
+ dev_err(dev, "failed to request DMA channel\n");
+ return -EINVAL;
+ }
+
+ memset(&config_fe, 0, sizeof(config_fe));
+ ret = snd_dmaengine_pcm_prepare_slave_config(substream,
+ params, &config_fe);
+ if (ret) {
+ dev_err(dev, "failed to prepare DMA config for Front-End\n");
+ return ret;
+ }
+
+ ret = dmaengine_slave_config(ctx->dma_chan[!dir], &config_fe);
+ if (ret) {
+ dev_err(dev, "failed to config DMA channel for Front-End\n");
+ return ret;
+ }
+
+ /* Request and config DMA channel for Back-End */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_CYCLIC, mask);
+
+ /* Get DMA request of Back-End */
+ tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx");
+ if (tmp_chan) {
+ tmp_data = tmp_chan->private;
+ if (tmp_data) {
+ ctx->dma_data.dma_request = tmp_data->dma_request;
+ be_peripheral_type = tmp_data->peripheral_type;
+ if (tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL)
+ ctx->dma_data.dst_dualfifo = true;
+ if (!tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL)
+ ctx->dma_data.src_dualfifo = true;
+ }
+ dma_release_channel(tmp_chan);
+ }
+
+ /* Get DMA request of Front-End */
+ tmp_chan = fsl_easrc_get_dma_channel(ctx, dir);
+ if (tmp_chan) {
+ tmp_data = tmp_chan->private;
+ if (tmp_data) {
+ ctx->dma_data.dma_request2 = tmp_data->dma_request;
+ ctx->dma_data.peripheral_type =
+ tmp_data->peripheral_type;
+ ctx->dma_data.priority = tmp_data->priority;
+ }
+ dma_release_channel(tmp_chan);
+ }
+
+ /* For sdma DEV_TO_DEV, there is two dma request
+ * But for emda DEV_TO_DEV, there is only one dma request, which is
+ * from the BE.
+ */
+ if (ctx->dma_data.dma_request2 != ctx->dma_data.dma_request)
+ ctx->dma_chan[dir] =
+ dma_request_channel(mask, filter, &ctx->dma_data);
+ else
+ ctx->dma_chan[dir] =
+ dma_request_slave_channel(dev_be, tx ? "tx" : "rx");
+
+ if (!ctx->dma_chan[dir]) {
+ dev_err(dev, "failed to request DMA channel for Back-End\n");
+ return -EINVAL;
+ }
+
+ if (easrc->easrc_format == SNDRV_PCM_FORMAT_S16_LE)
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ else
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ memset(&config_be, 0, sizeof(config_be));
+
+ config_be.direction = DMA_DEV_TO_DEV;
+ config_be.src_addr_width = buswidth;
+ config_be.src_maxburst = dma_params_be->maxburst;
+ config_be.dst_addr_width = buswidth;
+ config_be.dst_maxburst = dma_params_be->maxburst;
+
+ if (tx) {
+ config_be.src_addr = easrc->paddr + REG_EASRC_RDFIFO(index);
+ config_be.dst_addr = dma_params_be->addr;
+ } else {
+ config_be.dst_addr = easrc->paddr + REG_EASRC_WRFIFO(index);
+ config_be.src_addr = dma_params_be->addr;
+ }
+
+ ret = dmaengine_slave_config(ctx->dma_chan[dir], &config_be);
+ if (ret) {
+ dev_err(dev, "failed to config DMA channel for Back-End\n");
+ return ret;
+ }
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static int fsl_easrc_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_easrc_context *ctx = runtime->private_data;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ if (ctx->dma_chan[IN])
+ dma_release_channel(ctx->dma_chan[IN]);
+
+ if (ctx->dma_chan[OUT])
+ dma_release_channel(ctx->dma_chan[OUT]);
+
+ ctx->dma_chan[IN] = NULL;
+ ctx->dma_chan[OUT] = NULL;
+
+ return 0;
+}
+
+static int fsl_easrc_dma_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct device *dev = component->dev;
+ struct fsl_easrc *easrc = dev_get_drvdata(dev);
+ struct fsl_easrc_context *ctx;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u8 dir = tx ? OUT : IN;
+ struct dma_slave_caps dma_caps;
+ struct dma_chan *tmp_chan;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+ int ret;
+ int i;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->easrc = easrc;
+
+ runtime->private_data = ctx;
+
+ 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;
+ }
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ fsl_easrc_request_context(ctx, 1);
+
+ tmp_chan = fsl_easrc_get_dma_channel(ctx, dir);
+ if (!tmp_chan) {
+ dev_err(dev, "can't get dma channel\n");
+ return -EINVAL;
+ }
+
+ ret = dma_get_slave_caps(tmp_chan, &dma_caps);
+ if (ret == 0) {
+ if (dma_caps.cmd_pause)
+ snd_imx_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME;
+ if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT)
+ snd_imx_hardware.info |= SNDRV_PCM_INFO_BATCH;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ addr_widths = dma_caps.dst_addr_widths;
+ else
+ addr_widths = dma_caps.src_addr_widths;
+ }
+
+ /*
+ * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep
+ * hw.formats set to 0, meaning no restrictions are in place.
+ * In this case it's the responsibility of the DAI driver to
+ * provide the supported format information.
+ */
+ if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK))
+ /*
+ * Prepare formats mask for valid/allowed sample types. If the
+ * dma does not have support for the given physical word size,
+ * it needs to be masked out so user space can not use the
+ * format which produces corrupted audio.
+ * In case the dma driver does not implement the slave_caps the
+ * default assumption is that it supports 1, 2 and 4 bytes
+ * widths.
+ */
+ for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) {
+ int bits = snd_pcm_format_physical_width(i);
+
+ /*
+ * Enable only samples with DMA supported physical
+ * widths
+ */
+ switch (bits) {
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ case 64:
+ if (addr_widths & (1 << (bits / 8)))
+ snd_imx_hardware.formats |= (1LL << i);
+ break;
+ default:
+ /* Unsupported types */
+ break;
+ }
+ }
+
+ if (tmp_chan)
+ dma_release_channel(tmp_chan);
+
+ fsl_easrc_release_context(ctx);
+
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+ return 0;
+}
+
+static int fsl_easrc_dma_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_easrc_context *ctx = runtime->private_data;
+ struct fsl_easrc *easrc;
+
+ if (!ctx)
+ return 0;
+
+ easrc = ctx->easrc;
+
+ if (easrc->ctx[ctx->index] == ctx)
+ easrc->ctx[ctx->index] = NULL;
+
+ kfree(ctx);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t fsl_easrc_dma_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct fsl_easrc_context *ctx = runtime->private_data;
+
+ return bytes_to_frames(substream->runtime, ctx->pos);
+}
+
+static const struct snd_pcm_ops fsl_easrc_dma_pcm_ops = {
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = fsl_easrc_dma_hw_params,
+ .hw_free = fsl_easrc_dma_hw_free,
+ .trigger = fsl_easrc_dma_trigger,
+ .open = fsl_easrc_dma_startup,
+ .close = fsl_easrc_dma_shutdown,
+ .pointer = fsl_easrc_dma_pcm_pointer,
+};
+
+static int fsl_easrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm *pcm = rtd->pcm;
+ int ret, i;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(card->dev, "failed to set DMA mask\n");
+ return ret;
+ }
+
+ for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) {
+ substream = pcm->streams[i].substream;
+ if (!substream)
+ continue;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+ FSL_ASRC_DMABUF_SIZE,
+ &substream->dma_buffer);
+ if (ret) {
+ dev_err(card->dev, "failed to allocate DMA buffer\n");
+ goto err;
+ }
+ }
+
+ return 0;
+
+err:
+ if (--i == 0 && pcm->streams[i].substream)
+ snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer);
+
+ return ret;
+}
+
+static void fsl_easrc_dma_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ int i;
+
+ for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) {
+ substream = pcm->streams[i].substream;
+ if (!substream)
+ continue;
+
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+ substream->dma_buffer.addr = 0;
+ }
+}
+
+struct snd_soc_component_driver fsl_easrc_dma_component = {
+ .name = DRV_NAME,
+ .ops = &fsl_easrc_dma_pcm_ops,
+ .pcm_new = fsl_easrc_dma_pcm_new,
+ .pcm_free = fsl_easrc_dma_pcm_free,
+};
+EXPORT_SYMBOL_GPL(fsl_easrc_dma_component);
diff --git a/sound/soc/fsl/fsl_easrc_m2m.c b/sound/soc/fsl/fsl_easrc_m2m.c
new file mode 100644
index 000000000000..3a9429a41a8b
--- /dev/null
+++ b/sound/soc/fsl/fsl_easrc_m2m.c
@@ -0,0 +1,966 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2019 NXP
+
+struct fsl_easrc_m2m {
+ struct fsl_easrc *easrc;
+ struct fsl_easrc_context *ctx;
+ struct completion complete[2];
+ struct dma_block dma_block[2];
+ unsigned int ctx_hold;
+ unsigned int easrc_active;
+ unsigned int first_convert;
+ unsigned int sg_nodes[2];
+ struct scatterlist sg[2][9];
+ struct dma_async_tx_descriptor *desc[2];
+ spinlock_t lock; /* protect mem resource */
+};
+
+void fsl_easrc_get_status(struct fsl_easrc_context *ctx,
+ struct asrc_status_flags *flags)
+{
+ flags->overload_error = 0;
+}
+
+#define mxc_easrc_dma_umap_in(dev, m2m) \
+ dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \
+ DMA_MEM_TO_DEV) \
+
+#define mxc_easrc_dma_umap_out(dev, m2m) \
+ dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \
+ DMA_DEV_TO_MEM) \
+
+#define EASRC_xPUT_DMA_CALLBACK(dir) \
+ ((dir == IN) ? fsl_easrc_input_dma_callback \
+ : fsl_easrc_output_dma_callback)
+
+#define DIR_STR(dir) dir == IN ? "in" : "out"
+
+static void fsl_easrc_input_dma_callback(void *data)
+{
+ struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data;
+
+ complete(&m2m->complete[IN]);
+}
+
+static void fsl_easrc_output_dma_callback(void *data)
+{
+ struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data;
+
+ complete(&m2m->complete[OUT]);
+}
+
+static int fsl_allocate_dma_buf(struct fsl_easrc_m2m *m2m)
+{
+ struct dma_block *input = &m2m->dma_block[IN];
+ struct dma_block *output = &m2m->dma_block[OUT];
+
+ input->dma_vaddr = kzalloc(input->length, GFP_KERNEL);
+ if (!input->dma_vaddr)
+ return -ENOMEM;
+
+ output->dma_vaddr = kzalloc(output->length, GFP_KERNEL);
+ if (!output->dma_vaddr)
+ goto alloc_fail;
+
+ return 0;
+
+alloc_fail:
+ kfree(input->dma_vaddr);
+
+ return -ENOMEM;
+}
+
+static int fsl_easrc_dmaconfig(struct fsl_easrc_m2m *m2m,
+ struct dma_chan *chan,
+ u32 dma_addr, void *buf_addr, u32 buf_len,
+ bool dir, int bits)
+{
+ struct dma_async_tx_descriptor *desc = m2m->desc[dir];
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct device *dev = &easrc->pdev->dev;
+ unsigned int sg_nent = m2m->sg_nodes[dir];
+ struct scatterlist *sg = m2m->sg[dir];
+ struct dma_slave_config slave_config;
+ enum dma_slave_buswidth buswidth;
+ int ret, i;
+
+ switch (bits) {
+ case 16:
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case 24:
+ buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES;
+ break;
+ default:
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ }
+
+ if (dir == IN) {
+ slave_config.direction = DMA_MEM_TO_DEV;
+ slave_config.dst_addr = dma_addr;
+ slave_config.dst_addr_width = buswidth;
+ slave_config.dst_maxburst =
+ ctx->in_params.fifo_wtmk * ctx->channels;
+ } else {
+ slave_config.direction = DMA_DEV_TO_MEM;
+ slave_config.src_addr = dma_addr;
+ slave_config.src_addr_width = buswidth;
+ slave_config.src_maxburst =
+ ctx->out_params.fifo_wtmk * ctx->channels;
+ }
+
+ ret = dmaengine_slave_config(chan, &slave_config);
+ if (ret) {
+ dev_err(dev, "failed to config dmaengine for %sput task: %d\n",
+ DIR_STR(dir), ret);
+ return -EINVAL;
+ }
+
+ sg_init_table(sg, sg_nent);
+ switch (sg_nent) {
+ case 1:
+ sg_init_one(sg, buf_addr, buf_len);
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ for (i = 0; i < (sg_nent - 1); i++)
+ sg_set_buf(&sg[i],
+ buf_addr + i * m2m->dma_block[dir].max_buf_size,
+ m2m->dma_block[dir].max_buf_size);
+
+ sg_set_buf(&sg[i],
+ buf_addr + i * m2m->dma_block[dir].max_buf_size,
+ buf_len - i * m2m->dma_block[dir].max_buf_size);
+ break;
+ default:
+ dev_err(dev, "invalid input DMA nodes number: %d\n", sg_nent);
+ return -EINVAL;
+ }
+
+ ret = dma_map_sg(dev, sg, sg_nent, slave_config.direction);
+ if (ret != sg_nent) {
+ dev_err(dev, "failed to map DMA sg for %sput task\n",
+ DIR_STR(dir));
+ return -EINVAL;
+ }
+
+ desc = dmaengine_prep_slave_sg(chan, sg, sg_nent,
+ slave_config.direction,
+ DMA_PREP_INTERRUPT);
+ if (!desc) {
+ dev_err(dev, "failed to prepare dmaengine for %sput task\n",
+ DIR_STR(dir));
+ return -EINVAL;
+ }
+
+ m2m->desc[dir] = desc;
+ m2m->desc[dir]->callback = EASRC_xPUT_DMA_CALLBACK(dir);
+
+ desc->callback = EASRC_xPUT_DMA_CALLBACK(dir);
+ desc->callback_param = m2m;
+
+ return 0;
+}
+
+static long fsl_easrc_calc_outbuf_len(struct fsl_easrc_m2m *m2m,
+ struct asrc_convert_buffer *pbuf)
+{
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ unsigned int out_length;
+ unsigned int in_width, out_width;
+ unsigned int channels = ctx->channels;
+ unsigned int in_samples, out_samples;
+
+ in_width = snd_pcm_format_physical_width(ctx->in_params.sample_format) / 8;
+ out_width = snd_pcm_format_physical_width(ctx->out_params.sample_format) / 8;
+
+ in_samples = pbuf->input_buffer_length / (in_width * channels);
+ out_samples = ctx->out_params.sample_rate * in_samples /
+ ctx->in_params.sample_rate;
+ out_length = out_samples * out_width * channels;
+
+ if (out_samples <= ctx->out_missed_sample) {
+ out_length = 0;
+ ctx->out_missed_sample -= out_samples;
+ } else {
+ out_length -= ctx->out_missed_sample * out_width * channels;
+ ctx->out_missed_sample = 0;
+ }
+
+ return out_length;
+}
+
+static long fsl_easrc_prepare_io_buffer(struct fsl_easrc_m2m *m2m,
+ struct asrc_convert_buffer *buf,
+ bool dir)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct dma_chan *dma_chan = ctx->dma_chan[dir];
+ unsigned int *dma_len = &m2m->dma_block[dir].length;
+ unsigned int *sg_nodes = &m2m->sg_nodes[dir];
+ void *dma_vaddr = m2m->dma_block[dir].dma_vaddr;
+ enum asrc_pair_index index = m2m->ctx->index;
+ unsigned int buf_len, bits;
+ u32 fifo_addr;
+ void __user *buf_vaddr;
+
+ if (dir == IN) {
+ buf_vaddr = (void __user *)buf->input_buffer_vaddr;
+ buf_len = buf->input_buffer_length;
+ bits = snd_pcm_format_physical_width(ctx->in_params.sample_format);
+ fifo_addr = easrc->paddr + REG_EASRC_WRFIFO(index);
+ } else {
+ buf_vaddr = (void __user *)buf->output_buffer_vaddr;
+ buf_len = buf->output_buffer_length;
+ bits = snd_pcm_format_physical_width(ctx->out_params.sample_format);
+ fifo_addr = easrc->paddr + REG_EASRC_RDFIFO(index);
+ }
+
+ if (buf_len > EASRC_DMA_BUFFER_SIZE ||
+ (dir == IN && (buf_len % (bits / 8)))) {
+ dev_err(dev, "%sput buffer size is error: [%d]\n",
+ DIR_STR(dir), buf_len);
+ return -EINVAL;
+ }
+
+ if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len))
+ return -EFAULT;
+
+ *dma_len = buf_len;
+
+ if (dir == OUT)
+ *dma_len = fsl_easrc_calc_outbuf_len(m2m, buf);
+
+ if (*dma_len <= 0)
+ return 0;
+
+ *sg_nodes = *dma_len / m2m->dma_block[dir].max_buf_size + 1;
+
+ return fsl_easrc_dmaconfig(m2m, dma_chan, fifo_addr, dma_vaddr,
+ *dma_len, dir, bits);
+}
+
+static long fsl_easrc_prepare_buffer(struct fsl_easrc_m2m *m2m,
+ struct asrc_convert_buffer *buf)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ int ret;
+
+ ret = fsl_easrc_prepare_io_buffer(m2m, buf, IN);
+ if (ret) {
+ dev_err(dev, "failed to prepare input buffer %d\n", ret);
+ return ret;
+ }
+
+ ret = fsl_easrc_prepare_io_buffer(m2m, buf, OUT);
+ if (ret) {
+ dev_err(dev, "failed to prepare output buffer %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int fsl_easrc_process_buffer_pre(struct fsl_easrc_m2m *m2m, bool dir)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+
+ if (!wait_for_completion_interruptible_timeout(&m2m->complete[dir],
+ 10 * HZ)) {
+ dev_err(dev, "%sput DMA task timeout\n", DIR_STR(dir));
+ return -ETIME;
+ } else if (signal_pending(current)) {
+ dev_err(dev, "%sput task forcibly aborted\n", DIR_STR(dir));
+ return -ERESTARTSYS;
+ }
+
+ return 0;
+}
+
+static unsigned int fsl_easrc_get_output_FIFO_size(struct fsl_easrc_m2m *m2m)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ enum asrc_pair_index index = m2m->ctx->index;
+ u32 val;
+
+ regmap_read(easrc->regmap, REG_EASRC_SFS(index), &val);
+
+ val &= EASRC_SFS_NSGO_MASK;
+
+ return val >> EASRC_SFS_NSGO_SHIFT;
+}
+
+static void fsl_easrc_read_last_FIFO(struct fsl_easrc_m2m *m2m)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct dma_block *output = &m2m->dma_block[OUT];
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ enum asrc_pair_index index = m2m->ctx->index;
+ u32 i, reg, size, t_size = 0, width;
+ u32 *reg32 = NULL;
+ u16 *reg16 = NULL;
+ u8 *reg24 = NULL;
+
+ width = snd_pcm_format_physical_width(ctx->out_params.sample_format);
+
+ if (width == 32)
+ reg32 = output->dma_vaddr + output->length;
+ else if (width == 16)
+ reg16 = output->dma_vaddr + output->length;
+ else
+ reg24 = output->dma_vaddr + output->length;
+retry:
+ size = fsl_easrc_get_output_FIFO_size(m2m);
+ for (i = 0; i < size * ctx->channels; i++) {
+ regmap_read(easrc->regmap, REG_EASRC_RDFIFO(index), &reg);
+
+ if (reg32) {
+ *(reg32) = reg;
+ reg32++;
+ } else if (reg16) {
+ *(reg16) = (u16)reg;
+ reg16++;
+ } else {
+ *reg24++ = (u8)reg;
+ *reg24++ = (u8)(reg >> 8);
+ *reg24++ = (u8)(reg >> 16);
+ }
+ }
+ t_size += size;
+
+ if (size)
+ goto retry;
+
+ if (reg32)
+ output->length += t_size * ctx->channels * 4;
+ else if (reg16)
+ output->length += t_size * ctx->channels * 2;
+ else
+ output->length += t_size * ctx->channels * 3;
+}
+
+static long fsl_easrc_process_buffer(struct fsl_easrc_m2m *m2m,
+ struct asrc_convert_buffer *buf)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ unsigned long lock_flags;
+ int ret;
+
+ /* Check input task first */
+ ret = fsl_easrc_process_buffer_pre(m2m, IN);
+ if (ret) {
+ mxc_easrc_dma_umap_in(dev, m2m);
+ if (m2m->dma_block[OUT].length > 0)
+ mxc_easrc_dma_umap_out(dev, m2m);
+ return ret;
+ }
+
+ /* ...then output task*/
+ if (m2m->dma_block[OUT].length > 0) {
+ ret = fsl_easrc_process_buffer_pre(m2m, OUT);
+ if (ret) {
+ mxc_easrc_dma_umap_in(dev, m2m);
+ mxc_easrc_dma_umap_out(dev, m2m);
+ return ret;
+ }
+ }
+
+ mxc_easrc_dma_umap_in(dev, m2m);
+ if (m2m->dma_block[OUT].length > 0)
+ mxc_easrc_dma_umap_out(dev, m2m);
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ if (!m2m->ctx_hold) {
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+ return -EFAULT;
+ }
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ /* Fetch the remaining data */
+ fsl_easrc_read_last_FIFO(m2m);
+
+ /* Update final lengths after getting last FIFO */
+ buf->input_buffer_length = m2m->dma_block[IN].length;
+ buf->output_buffer_length = m2m->dma_block[OUT].length;
+
+ if (copy_to_user((void __user *)buf->output_buffer_vaddr,
+ m2m->dma_block[OUT].dma_vaddr,
+ m2m->dma_block[OUT].length))
+ return -EFAULT;
+
+ return 0;
+}
+
+void fsl_easrc_submit_dma(struct fsl_easrc_m2m *m2m)
+{
+ /* Submit DMA request */
+ dmaengine_submit(m2m->desc[IN]);
+ dma_async_issue_pending(m2m->desc[IN]->chan);
+
+ if (m2m->dma_block[OUT].length > 0) {
+ dmaengine_submit(m2m->desc[OUT]);
+ dma_async_issue_pending(m2m->desc[OUT]->chan);
+ }
+}
+
+static long fsl_easrc_ioctl_req_context(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct asrc_req req;
+ unsigned long lock_flags;
+ long ret;
+
+ ret = copy_from_user(&req, user, sizeof(req));
+ if (ret) {
+ dev_err(dev, "failed to get req from user space:%ld\n", ret);
+ return ret;
+ }
+
+ ret = fsl_easrc_request_context(m2m->ctx, req.chn_num);
+ if (ret < 0) {
+ dev_err(dev, "failed to request context:%ld\n", ret);
+ return ret;
+ }
+
+ /* request context returns the context id in case of success */
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ m2m->ctx_hold = 1;
+ req.index = m2m->ctx->index;
+ req.supported_in_format = FSL_EASRC_FORMATS;
+ req.supported_out_format = FSL_EASRC_FORMATS |
+ SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ ret = copy_to_user(user, &req, sizeof(req));
+ if (ret) {
+ dev_err(dev, "failed to send req to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_config_context(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ enum asrc_pair_index index = m2m->ctx->index;
+ struct device *dev = &easrc->pdev->dev;
+ struct asrc_config config;
+ int ret;
+ int in_word_size, out_word_size;
+
+ ret = copy_from_user(&config, user, sizeof(config));
+ if (ret) {
+ dev_err(dev, "failed to get config from user space: %d\n", ret);
+ return ret;
+ }
+
+ /* set context configuration parameters received from userspace */
+ ctx->in_params.sample_rate = config.input_sample_rate;
+ ctx->out_params.sample_rate = config.output_sample_rate;
+
+ ctx->in_params.fifo_wtmk = FSL_EASRC_INPUTFIFO_WML;
+ ctx->out_params.fifo_wtmk = FSL_EASRC_OUTPUTFIFO_WML;
+
+ ctx->in_params.sample_format = config.input_format;
+ ctx->out_params.sample_format = config.output_format;
+
+ ctx->channels = config.channel_num;
+ ctx->rs_init_mode = 0x2;
+ ctx->pf_init_mode = 0x2;
+
+ ret = fsl_easrc_set_ctx_format(ctx,
+ &ctx->in_params.sample_format,
+ &ctx->out_params.sample_format);
+ if (ret)
+ return ret;
+
+ ret = fsl_easrc_config_context(easrc, index);
+ if (ret) {
+ dev_err(dev, "failed to config context %d\n", ret);
+ return ret;
+ }
+
+ ctx->in_params.iterations = 1;
+ ctx->in_params.group_len = ctx->channels;
+ ctx->in_params.access_len = ctx->channels;
+ ctx->out_params.iterations = 1;
+ ctx->out_params.group_len = ctx->channels;
+ ctx->out_params.access_len = ctx->channels;
+
+ /* You can also call fsl_easrc_set_ctx_organziation for
+ * sample interleaving support
+ */
+ ret = fsl_easrc_set_ctx_organziation(ctx);
+ if (ret) {
+ dev_err(dev, "failed to set fifo organization\n");
+ return ret;
+ }
+
+ in_word_size = snd_pcm_format_physical_width(config.input_format) / 8;
+ out_word_size = snd_pcm_format_physical_width(config.output_format) / 8;
+
+ /* allocate dma buffers */
+ m2m->dma_block[IN].length = EASRC_DMA_BUFFER_SIZE;
+ m2m->dma_block[IN].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE,
+ in_word_size * ctx->channels);
+ m2m->dma_block[OUT].length = EASRC_DMA_BUFFER_SIZE;
+ m2m->dma_block[OUT].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE,
+ out_word_size * ctx->channels);
+
+ ret = fsl_allocate_dma_buf(m2m);
+ if (ret) {
+ dev_err(dev, "failed to allocate DMA buffers: %d\n", ret);
+ return ret;
+ }
+
+ ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN);
+ if (!ctx->dma_chan[IN]) {
+ dev_err(dev, "[ctx%d] failed to get input DMA channel\n",
+ m2m->ctx->index);
+ return -EBUSY;
+ }
+ ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT);
+ if (!ctx->dma_chan[OUT]) {
+ dev_err(dev, "[ctx%d] failed to get output DMA channel\n",
+ m2m->ctx->index);
+ return -EBUSY;
+ }
+
+ ret = copy_to_user(user, &config, sizeof(config));
+ if (ret) {
+ dev_err(dev, "failed to send config to user: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_release_context(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct device *dev = &easrc->pdev->dev;
+ enum asrc_pair_index index;
+ unsigned long lock_flags;
+ int ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ dev_err(dev,
+ "[ctx%d] failed to get index from user space %d\n",
+ m2m->ctx->index, ret);
+ return ret;
+ }
+
+ if (index != m2m->ctx->index) {
+ dev_err(dev,
+ "[ctx%d] releasing wrong context - %d\n",
+ m2m->ctx->index, index);
+ return -EINVAL;
+ }
+
+ if (m2m->easrc_active) {
+ m2m->easrc_active = 0;
+ fsl_easrc_stop_context(ctx);
+ }
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ m2m->ctx_hold = 0;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ if (ctx->dma_chan[IN])
+ dma_release_channel(ctx->dma_chan[IN]);
+ if (ctx->dma_chan[OUT])
+ dma_release_channel(ctx->dma_chan[OUT]);
+
+ ctx->dma_chan[IN] = NULL;
+ ctx->dma_chan[OUT] = NULL;
+
+ /* free buffers allocated in config context*/
+ kfree(m2m->dma_block[IN].dma_vaddr);
+ kfree(m2m->dma_block[OUT].dma_vaddr);
+
+ fsl_easrc_release_context(ctx);
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_convert(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct asrc_convert_buffer buf;
+ int ret;
+
+ ret = copy_from_user(&buf, user, sizeof(buf));
+ if (ret) {
+ dev_err(dev, "failed to get buf from user space: %d\n", ret);
+ return ret;
+ }
+
+ /* fsl_easrc_calc_last_period_size(ctx, &buf); */
+ ret = fsl_easrc_prepare_buffer(m2m, &buf);
+ if (ret) {
+ dev_err(dev, "failed to prepare buffer\n");
+ return ret;
+ }
+
+ init_completion(&m2m->complete[IN]);
+ init_completion(&m2m->complete[OUT]);
+
+ fsl_easrc_submit_dma(m2m);
+
+ if (m2m->first_convert) {
+ fsl_easrc_start_context(ctx);
+ m2m->first_convert = 0;
+ }
+
+ ret = fsl_easrc_process_buffer(m2m, &buf);
+ if (ret) {
+ dev_err(dev, "failed to process buffer %d\n", ret);
+ return ret;
+ }
+
+ ret = copy_to_user(user, &buf, sizeof(buf));
+ if (ret) {
+ dev_err(dev, "failed to send buffer to user: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_start_conv(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ enum asrc_pair_index index;
+ int ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ dev_err(dev, "failed to get index from user space: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (index != m2m->ctx->index) {
+ dev_err(dev, "[ctx%d] attempting to start wrong context%d\n",
+ m2m->ctx->index, index);
+ return -EINVAL;
+ }
+
+ m2m->easrc_active = 1;
+ m2m->first_convert = 1;
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_stop_conv(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct device *dev = &easrc->pdev->dev;
+ enum asrc_pair_index index;
+ int ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ dev_err(dev, "failed to get index from user space: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (index != m2m->ctx->index) {
+ dev_err(dev, "[ctx%d] attempting to start wrong context%d\n",
+ m2m->ctx->index, index);
+ return -EINVAL;
+ }
+
+ dmaengine_terminate_all(ctx->dma_chan[IN]);
+ dmaengine_terminate_all(ctx->dma_chan[OUT]);
+
+ fsl_easrc_stop_context(ctx);
+ m2m->easrc_active = 0;
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_status(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct asrc_status_flags flags;
+ int ret;
+
+ ret = copy_from_user(&flags, user, sizeof(flags));
+ if (ret) {
+ dev_err(dev,
+ "[ctx%d] failed to get flags from user space: %d\n",
+ m2m->ctx->index, ret);
+ return ret;
+ }
+
+ if (m2m->ctx->index != flags.index) {
+ dev_err(dev, "[ctx%d] getting status for other context: %d\n",
+ m2m->ctx->index, flags.index);
+ return -EINVAL;
+ }
+
+ fsl_easrc_get_status(ctx, &flags);
+
+ ret = copy_to_user(user, &flags, sizeof(flags));
+ if (ret)
+ dev_err(dev, "[ctx%d] failed to send flags to user space\n",
+ m2m->ctx->index);
+
+ return ret;
+}
+
+static long fsl_easrc_ioctl_flush(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+
+ /* Release DMA and request again */
+ dma_release_channel(ctx->dma_chan[IN]);
+ dma_release_channel(ctx->dma_chan[OUT]);
+
+ ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN);
+ if (!ctx->dma_chan[IN]) {
+ dev_err(dev, "failed to request input task DMA channel\n");
+ return -EBUSY;
+ }
+
+ ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT);
+ if (!ctx->dma_chan[OUT]) {
+ dev_err(dev, "failed to request output task DMA channel\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int fsl_easrc_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *easrc_miscdev = file->private_data;
+ struct fsl_easrc *easrc = dev_get_drvdata(easrc_miscdev->parent);
+ struct fsl_easrc_m2m *m2m;
+ struct fsl_easrc_context *ctx;
+ struct device *dev = &easrc->pdev->dev;
+ int ret;
+
+ ret = signal_pending(current);
+ if (ret) {
+ dev_err(dev, "current process has a signal pending\n");
+ return ret;
+ }
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ /* set the pointer to easrc private data */
+ m2m = kzalloc(sizeof(*m2m), GFP_KERNEL);
+ if (!m2m) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ /* just save the pointer to easrc private data */
+ m2m->easrc = easrc;
+ m2m->ctx = ctx;
+ ctx->easrc = easrc;
+ ctx->private_data = m2m;
+
+ spin_lock_init(&m2m->lock);
+
+ /* context structs are already allocated in fsl_easrc->ctx[i] */
+ file->private_data = m2m;
+
+ pm_runtime_get_sync(dev);
+
+ return 0;
+out:
+ kfree(ctx);
+ return ret;
+}
+
+static int fsl_easrc_close(struct inode *inode, struct file *file)
+{
+ struct fsl_easrc_m2m *m2m = file->private_data;
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct device *dev = &easrc->pdev->dev;
+ unsigned long lock_flags;
+
+ if (m2m->easrc_active) {
+ m2m->easrc_active = 0;
+ dmaengine_terminate_all(ctx->dma_chan[IN]);
+ dmaengine_terminate_all(ctx->dma_chan[OUT]);
+
+ fsl_easrc_stop_context(ctx);
+ fsl_easrc_input_dma_callback((void *)m2m);
+ fsl_easrc_output_dma_callback((void *)m2m);
+ }
+
+ if (!ctx)
+ goto null_ctx;
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ if (m2m->ctx_hold) {
+ m2m->ctx_hold = 0;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ if (ctx->dma_chan[IN])
+ dma_release_channel(ctx->dma_chan[IN]);
+ if (ctx->dma_chan[OUT])
+ dma_release_channel(ctx->dma_chan[OUT]);
+
+ kfree(m2m->dma_block[IN].dma_vaddr);
+ kfree(m2m->dma_block[OUT].dma_vaddr);
+
+ fsl_easrc_release_context(ctx);
+ } else {
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+ }
+
+null_ctx:
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+ kfree(m2m);
+ kfree(ctx);
+ file->private_data = NULL;
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+
+ pm_runtime_put_sync(dev);
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct fsl_easrc_m2m *m2m = file->private_data;
+ struct fsl_easrc *easrc = m2m->easrc;
+ void __user *user = (void __user *)arg;
+ long ret = 0;
+
+ switch (cmd) {
+ case ASRC_REQ_PAIR:
+ ret = fsl_easrc_ioctl_req_context(m2m, user);
+ break;
+ case ASRC_CONFIG_PAIR:
+ ret = fsl_easrc_ioctl_config_context(m2m, user);
+ break;
+ case ASRC_RELEASE_PAIR:
+ ret = fsl_easrc_ioctl_release_context(m2m, user);
+ break;
+ case ASRC_CONVERT:
+ ret = fsl_easrc_ioctl_convert(m2m, user);
+ break;
+ case ASRC_START_CONV:
+ ret = fsl_easrc_ioctl_start_conv(m2m, user);
+ break;
+ case ASRC_STOP_CONV:
+ ret = fsl_easrc_ioctl_stop_conv(m2m, user);
+ break;
+ case ASRC_STATUS:
+ ret = fsl_easrc_ioctl_status(m2m, user);
+ break;
+ case ASRC_FLUSH:
+ ret = fsl_easrc_ioctl_flush(m2m, user);
+ break;
+ default:
+ dev_err(&easrc->pdev->dev, "invalid ioctl command\n");
+ }
+
+ return ret;
+}
+
+static const struct file_operations easrc_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fsl_easrc_ioctl,
+ .open = fsl_easrc_open,
+ .release = fsl_easrc_close,
+};
+
+static int fsl_easrc_m2m_init(struct fsl_easrc *easrc)
+{
+ struct device *dev = &easrc->pdev->dev;
+ int ret;
+
+ easrc->easrc_miscdev.fops = &easrc_fops;
+ easrc->easrc_miscdev.parent = dev;
+ easrc->easrc_miscdev.name = easrc->name;
+ easrc->easrc_miscdev.minor = MISC_DYNAMIC_MINOR;
+ ret = misc_register(&easrc->easrc_miscdev);
+ if (ret)
+ dev_err(dev, "failed to register char device %d\n", ret);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static void fsl_easrc_m2m_suspend(struct fsl_easrc *easrc)
+{
+ struct fsl_easrc_context *ctx;
+ struct fsl_easrc_m2m *m2m;
+ unsigned long lock_flags;
+ int i;
+
+ for (i = 0; i < EASRC_CTX_MAX_NUM; i++) {
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+ ctx = easrc->ctx[i];
+ if (!ctx || !ctx->private_data) {
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+ continue;
+ }
+ m2m = ctx->private_data;
+
+ if (!completion_done(&m2m->complete[IN])) {
+ if (ctx->dma_chan[IN])
+ dmaengine_terminate_all(ctx->dma_chan[IN]);
+ fsl_easrc_input_dma_callback((void *)m2m);
+ }
+ if (!completion_done(&m2m->complete[OUT])) {
+ if (ctx->dma_chan[OUT])
+ dmaengine_terminate_all(ctx->dma_chan[OUT]);
+ fsl_easrc_output_dma_callback((void *)m2m);
+ }
+
+ m2m->first_convert = 1;
+ fsl_easrc_stop_context(ctx);
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+ }
+}
+
+static void fsl_easrc_m2m_resume(struct fsl_easrc *easrc)
+{
+ /* null */
+}
+#endif
diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c
index a78e4ab478df..3426d324acf9 100644
--- a/sound/soc/fsl/fsl_esai.c
+++ b/sound/soc/fsl/fsl_esai.c
@@ -10,6 +10,7 @@
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
@@ -22,6 +23,19 @@
SNDRV_PCM_FMTBIT_S24_LE)
/**
+ * fsl_esai_soc_data: soc specific data
+ *
+ * @imx: for imx platform
+ * @reset_at_xrun: flags for enable reset operaton
+ * @use_edma: edma is used.
+ */
+struct fsl_esai_soc_data {
+ bool imx;
+ bool reset_at_xrun;
+ bool use_edma;
+};
+
+/**
* fsl_esai: ESAI private data
*
* @dma_params_rx: DMA parameters for receive channel
@@ -32,7 +46,8 @@
* @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)
- * @task: tasklet 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
@@ -43,7 +58,6 @@
* @sck_div: if using PSR/PM dividers for SCKx clock
* @slave_mode: if fully using DAI slave mode
* @synchronous: if using tx/rx synchronous mode
- * @reset_at_xrun: flags for enable reset operaton
* @name: driver name
*/
struct fsl_esai {
@@ -55,7 +69,8 @@ struct fsl_esai {
struct clk *extalclk;
struct clk *fsysclk;
struct clk *spbaclk;
- struct tasklet_struct task;
+ const struct fsl_esai_soc_data *soc;
+ spinlock_t lock; /* Protect hw_reset and trigger */
u32 fifo_depth;
u32 slot_width;
u32 slots;
@@ -66,12 +81,37 @@ struct fsl_esai {
u32 sck_rate[2];
bool hck_dir[2];
bool sck_div[2];
- bool slave_mode;
+ bool slave_mode[2];
bool synchronous;
- bool reset_at_xrun;
char name[32];
};
+static struct fsl_esai_soc_data fsl_esai_vf610 = {
+ .imx = false,
+ .reset_at_xrun = true,
+ .use_edma = false,
+};
+
+static struct fsl_esai_soc_data fsl_esai_imx35 = {
+ .imx = true,
+ .reset_at_xrun = true,
+ .use_edma = false,
+};
+
+static struct fsl_esai_soc_data fsl_esai_imx6ull = {
+ .imx = true,
+ .reset_at_xrun = false,
+ .use_edma = false,
+};
+
+static struct fsl_esai_soc_data fsl_esai_imx8qm = {
+ .imx = true,
+ .reset_at_xrun = false,
+ .use_edma = true,
+};
+
+static void fsl_esai_hw_reset(struct fsl_esai *esai_priv);
+
static irqreturn_t esai_isr(int irq, void *devid)
{
struct fsl_esai *esai_priv = (struct fsl_esai *)devid;
@@ -83,19 +123,19 @@ static irqreturn_t esai_isr(int irq, void *devid)
regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr);
if ((saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE)) &&
- esai_priv->reset_at_xrun) {
+ esai_priv->soc->reset_at_xrun) {
dev_dbg(&pdev->dev, "reset module for xrun\n");
- tasklet_schedule(&esai_priv->task);
+ fsl_esai_hw_reset(esai_priv);
}
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");
+ dev_dbg(&pdev->dev, "isr: Receiving overrun\n");
if (esr & ESAI_ESR_TFE_MASK)
- dev_warn(&pdev->dev, "isr: Transmission underrun\n");
+ dev_dbg(&pdev->dev, "isr: Transmission underrun\n");
if (esr & ESAI_ESR_TLS_MASK)
dev_dbg(&pdev->dev, "isr: Just transmitted the last slot\n");
@@ -336,7 +376,7 @@ static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
int ret;
/* Don't apply for fully slave mode or unchanged bclk */
- if (esai_priv->slave_mode || esai_priv->sck_rate[tx] == freq)
+ if (esai_priv->slave_mode[tx] || esai_priv->sck_rate[tx] == freq)
return 0;
if (ratio * freq > hck_rate)
@@ -445,35 +485,62 @@ static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
return -EINVAL;
}
- esai_priv->slave_mode = false;
+ if (esai_priv->slave_mode[0] == esai_priv->slave_mode[1]) {
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ esai_priv->slave_mode[0] = true;
+ esai_priv->slave_mode[1] = true;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ xccr |= ESAI_xCCR_xCKD;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ xccr |= ESAI_xCCR_xFSD;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD;
+ esai_priv->slave_mode[0] = false;
+ esai_priv->slave_mode[1] = false;
+ break;
+ default:
+ return -EINVAL;
+ }
- /* DAI clock master masks */
- switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBM_CFM:
- esai_priv->slave_mode = true;
- break;
- case SND_SOC_DAIFMT_CBS_CFM:
- xccr |= ESAI_xCCR_xCKD;
- break;
- case SND_SOC_DAIFMT_CBM_CFS:
- xccr |= ESAI_xCCR_xFSD;
- break;
- case SND_SOC_DAIFMT_CBS_CFS:
- xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD;
- break;
- default:
- return -EINVAL;
+ mask = ESAI_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);
+
+ } else {
+
+ mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP |
+ ESAI_xCCR_xFSD | ESAI_xCCR_xCKD;
+ if (esai_priv->slave_mode[0])
+ regmap_update_bits(esai_priv->regmap,
+ REG_ESAI_RCCR, mask, xccr);
+ else
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR,
+ mask,
+ xccr | ESAI_xCCR_xFSD |
+ ESAI_xCCR_xCKD);
+
+ if (esai_priv->slave_mode[1])
+ regmap_update_bits(esai_priv->regmap,
+ REG_ESAI_TCCR, mask, xccr);
+ else
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR,
+ mask,
+ xccr | ESAI_xCCR_xFSD |
+ ESAI_xCCR_xCKD);
}
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;
}
@@ -481,6 +548,7 @@ 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);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
if (!dai->active) {
/* Set synchronous mode */
@@ -495,6 +563,12 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream,
ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2));
}
+ if (esai_priv->soc->use_edma)
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ tx ? esai_priv->dma_params_tx.maxburst :
+ esai_priv->dma_params_rx.maxburst);
+
return 0;
}
@@ -672,12 +746,13 @@ static void fsl_esai_trigger_stop(struct fsl_esai *esai_priv, bool tx)
ESAI_xFCR_xFR, 0);
}
-static void fsl_esai_hw_reset(unsigned long arg)
+static void fsl_esai_hw_reset(struct fsl_esai *esai_priv)
{
- struct fsl_esai *esai_priv = (struct fsl_esai *)arg;
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);
@@ -715,6 +790,8 @@ static void fsl_esai_hw_reset(unsigned long arg)
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,
@@ -722,6 +799,7 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
{
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;
@@ -729,12 +807,16 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int 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;
@@ -913,6 +995,8 @@ static int fsl_esai_probe(struct platform_device *pdev)
const __be32 *iprop;
void __iomem *regs;
int irq, ret;
+ unsigned long irqflag = 0;
+ int i, num_domains = 0;
esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL);
if (!esai_priv)
@@ -921,9 +1005,11 @@ static int fsl_esai_probe(struct platform_device *pdev)
esai_priv->pdev = pdev;
snprintf(esai_priv->name, sizeof(esai_priv->name), "%pOFn", np);
- if (of_device_is_compatible(np, "fsl,vf610-esai") ||
- of_device_is_compatible(np, "fsl,imx35-esai"))
- esai_priv->reset_at_xrun = true;
+ esai_priv->soc = of_device_get_match_data(&pdev->dev);
+ if (!esai_priv->soc) {
+ dev_err(&pdev->dev, "failed to get soc data\n");
+ return -ENODEV;
+ }
/* Get the addresses and IRQ */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -962,10 +1048,34 @@ static int fsl_esai_probe(struct platform_device *pdev)
PTR_ERR(esai_priv->spbaclk));
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
+ }
- ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0,
+ num_domains = of_count_phandle_with_args(np, "power-domains",
+ "#power-domain-cells");
+ for (i = 0; i < num_domains; i++) {
+ struct device *pd_dev;
+ struct device_link *link;
+
+ pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, i);
+ if (IS_ERR(pd_dev))
+ return PTR_ERR(pd_dev);
+
+ link = device_link_add(&pdev->dev, pd_dev,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME |
+ DL_FLAG_RPM_ACTIVE);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ }
+
+ /* ESAI shared interrupt */
+ if (of_property_read_bool(np, "shared-interrupt"))
+ irqflag = IRQF_SHARED;
+
+ ret = devm_request_irq(&pdev->dev, irq, esai_isr, irqflag,
esai_priv->name, esai_priv);
if (ret) {
dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
@@ -975,9 +1085,6 @@ static int fsl_esai_probe(struct platform_device *pdev)
/* Set a default slot number */
esai_priv->slots = 2;
- /* Set a default master/slave state */
- esai_priv->slave_mode = true;
-
/* Determine the FIFO depth */
iprop = of_get_property(np, "fsl,fifo-depth", NULL);
if (iprop)
@@ -993,6 +1100,20 @@ static int fsl_esai_probe(struct platform_device *pdev)
esai_priv->synchronous =
of_property_read_bool(np, "fsl,esai-synchronous");
+ if (!esai_priv->synchronous) {
+ if (of_property_read_bool(pdev->dev.of_node, "fsl,txm-rxs")) {
+ /* 0 -- rx, 1 -- tx */
+ esai_priv->slave_mode[0] = true;
+ esai_priv->slave_mode[1] = false;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "fsl,txs-rxm")) {
+ /* 0 -- rx, 1 -- tx */
+ esai_priv->slave_mode[0] = false;
+ esai_priv->slave_mode[1] = true;
+ }
+ }
+
/* Implement full symmetry for synchronous mode */
if (esai_priv->synchronous) {
fsl_esai_dai.symmetric_rates = 1;
@@ -1002,6 +1123,7 @@ static int fsl_esai_probe(struct platform_device *pdev)
dev_set_drvdata(&pdev->dev, esai_priv);
+ spin_lock_init(&esai_priv->lock);
ret = fsl_esai_hw_init(esai_priv);
if (ret)
return ret;
@@ -1022,9 +1144,6 @@ static int fsl_esai_probe(struct platform_device *pdev)
return ret;
}
- tasklet_init(&esai_priv->task, fsl_esai_hw_reset,
- (unsigned long)esai_priv);
-
pm_runtime_enable(&pdev->dev);
regcache_cache_only(esai_priv->regmap, true);
@@ -1038,18 +1157,16 @@ static int fsl_esai_probe(struct platform_device *pdev)
static int fsl_esai_remove(struct platform_device *pdev)
{
- struct fsl_esai *esai_priv = platform_get_drvdata(pdev);
-
pm_runtime_disable(&pdev->dev);
- tasklet_kill(&esai_priv->task);
return 0;
}
static const struct of_device_id fsl_esai_dt_ids[] = {
- { .compatible = "fsl,imx35-esai", },
- { .compatible = "fsl,vf610-esai", },
- { .compatible = "fsl,imx6ull-esai", },
+ { .compatible = "fsl,imx35-esai", .data = &fsl_esai_imx35 },
+ { .compatible = "fsl,vf610-esai", .data = &fsl_esai_vf610 },
+ { .compatible = "fsl,imx6ull-esai", .data = &fsl_esai_imx6ull },
+ { .compatible = "fsl,imx8qm-esai", .data = &fsl_esai_imx8qm },
{}
};
MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids);
diff --git a/sound/soc/fsl/fsl_hdmi.c b/sound/soc/fsl/fsl_hdmi.c
new file mode 100644
index 000000000000..05e9a7c1c1d9
--- /dev/null
+++ b/sound/soc/fsl/fsl_hdmi.c
@@ -0,0 +1,750 @@
+/*
+ * ALSA SoC HDMI Audio Layer for Freescale i.MX
+ *
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc.
+ *
+ * Some code from patch_hdmi.c
+ * Copyright (c) 2008-2010 Intel Corporation. All rights reserved.
+ * Copyright (c) 2006 ATI Technologies Inc.
+ * Copyright (c) 2008 NVIDIA Corp. All rights reserved.
+ * Copyright (c) 2008 Wei Ni <wni@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/asoundef.h>
+#include <sound/hdmi-codec.h>
+
+#include <video/mxc_hdmi.h>
+
+#include "imx-hdmi.h"
+
+
+static struct mxc_edid_cfg edid_cfg;
+
+static u32 playback_rates[HDMI_MAX_RATES];
+static u32 playback_sample_size[HDMI_MAX_SAMPLE_SIZE];
+static u32 playback_channels[HDMI_MAX_CHANNEL_CONSTRAINTS];
+
+static struct snd_pcm_hw_constraint_list playback_constraint_rates;
+static struct snd_pcm_hw_constraint_list playback_constraint_bits;
+static struct snd_pcm_hw_constraint_list playback_constraint_channels;
+
+#ifdef DEBUG
+static void dumpregs(struct snd_soc_dai *dai)
+{
+ u32 n, cts;
+
+ cts = (hdmi_readb(HDMI_AUD_CTS3) << 16) |
+ (hdmi_readb(HDMI_AUD_CTS2) << 8) |
+ hdmi_readb(HDMI_AUD_CTS1);
+
+ n = (hdmi_readb(HDMI_AUD_N3) << 16) |
+ (hdmi_readb(HDMI_AUD_N2) << 8) |
+ hdmi_readb(HDMI_AUD_N1);
+
+ dev_dbg(dai->dev, "HDMI_PHY_CONF0 0x%02x\n",
+ hdmi_readb(HDMI_PHY_CONF0));
+ dev_dbg(dai->dev, "HDMI_MC_CLKDIS 0x%02x\n",
+ hdmi_readb(HDMI_MC_CLKDIS));
+ dev_dbg(dai->dev, "HDMI_AUD_N[1-3] 0x%06x (%d)\n",
+ n, n);
+ dev_dbg(dai->dev, "HDMI_AUD_CTS[1-3] 0x%06x (%d)\n",
+ cts, cts);
+ dev_dbg(dai->dev, "HDMI_FC_AUDSCONF 0x%02x\n",
+ hdmi_readb(HDMI_FC_AUDSCONF));
+}
+#else
+static void dumpregs(struct snd_soc_dai *dai) {}
+#endif
+
+enum cea_speaker_placement {
+ FL = (1 << 0), /* Front Left */
+ FC = (1 << 1), /* Front Center */
+ FR = (1 << 2), /* Front Right */
+ FLC = (1 << 3), /* Front Left Center */
+ FRC = (1 << 4), /* Front Right Center */
+ RL = (1 << 5), /* Rear Left */
+ RC = (1 << 6), /* Rear Center */
+ RR = (1 << 7), /* Rear Right */
+ RLC = (1 << 8), /* Rear Left Center */
+ RRC = (1 << 9), /* Rear Right Center */
+ LFE = (1 << 10), /* Low Frequency Effect */
+ FLW = (1 << 11), /* Front Left Wide */
+ FRW = (1 << 12), /* Front Right Wide */
+ FLH = (1 << 13), /* Front Left High */
+ FCH = (1 << 14), /* Front Center High */
+ FRH = (1 << 15), /* Front Right High */
+ TC = (1 << 16), /* Top Center */
+};
+
+/*
+ * EDID SA bits in the CEA Speaker Allocation data block
+ */
+static int edid_speaker_allocation_bits[] = {
+ [0] = FL | FR,
+ [1] = LFE,
+ [2] = FC,
+ [3] = RL | RR,
+ [4] = RC,
+ [5] = FLC | FRC,
+ [6] = RLC | RRC,
+ [7] = FLW | FRW,
+ [8] = FLH | FRH,
+ [9] = TC,
+ [10] = FCH,
+};
+
+struct cea_channel_speaker_allocation {
+ int ca_index;
+ int speakers[8];
+
+ /* Derived values, just for convenience */
+ int channels;
+ int spk_mask;
+};
+
+/*
+ * This is an ordered list!
+ *
+ * The preceding ones have better chances to be selected by
+ * hdmi_channel_allocation().
+ */
+static struct cea_channel_speaker_allocation channel_allocations[] = {
+ /* channel: 7 6 5 4 3 2 1 0 */
+ { .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL },},
+ /* 2.1 */
+ { .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL },},
+ /* Dolby Surround */
+ { .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL },},
+ { .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL },},
+ { .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL },},
+ { .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL },},
+ { .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL },},
+ { .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL },},
+ { .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL },},
+ /* surround51 */
+ { .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL },},
+ /* 6.1 */
+ { .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL },},
+ /* surround71 */
+ { .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL },},
+ { .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL },},
+ { .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL },},
+ { .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL },},
+ { .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL },},
+ { .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL },},
+ { .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL },},
+ { .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL },},
+ { .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL },},
+};
+
+/* Compute derived values in channel_allocations[] */
+static void init_channel_allocations(void)
+{
+ struct cea_channel_speaker_allocation *p;
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ p = channel_allocations + i;
+ p->channels = 0;
+ p->spk_mask = 0;
+ for (j = 0; j < ARRAY_SIZE(p->speakers); j++)
+ if (p->speakers[j]) {
+ p->channels++;
+ p->spk_mask |= p->speakers[j];
+ }
+ }
+}
+
+/*
+ * The transformation takes two steps:
+ *
+ * speaker_alloc => (edid_speaker_allocation_bits[]) => spk_mask
+ * spk_mask => (channel_allocations[]) => CA
+ *
+ * TODO: it could select the wrong CA from multiple candidates.
+*/
+static int hdmi_channel_allocation(int channels)
+{
+ int spk_mask = 0, ca = 0, i, tmpchn, tmpspk;
+
+ /* CA defaults to 0 for basic stereo audio */
+ if (channels <= 2)
+ return 0;
+
+ /*
+ * Expand EDID's speaker allocation mask
+ *
+ * EDID tells the speaker mask in a compact(paired) form,
+ * expand EDID's notions to match the ones used by Audio InfoFrame.
+ */
+ for (i = 0; i < ARRAY_SIZE(edid_speaker_allocation_bits); i++) {
+ if (edid_cfg.speaker_alloc & (1 << i))
+ spk_mask |= edid_speaker_allocation_bits[i];
+ }
+
+ /* Search for the first working match in the CA table */
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ tmpchn = channel_allocations[i].channels;
+ tmpspk = channel_allocations[i].spk_mask;
+
+ if (channels == tmpchn && (spk_mask & tmpspk) == tmpspk) {
+ ca = channel_allocations[i].ca_index;
+ break;
+ }
+ }
+
+ return ca;
+}
+
+static void hdmi_set_audio_infoframe(unsigned int channels)
+{
+ u8 audiconf0, audiconf2;
+
+ /*
+ * From CEA-861-D spec:
+ * HDMI requires the CT, SS and SF fields to be set to 0 ("Refer
+ * to Stream Header") as these items are carried in the audio stream.
+ *
+ * So we only set the CC and CA fields.
+ */
+ audiconf0 = ((channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET) &
+ HDMI_FC_AUDICONF0_CC_MASK;
+
+ audiconf2 = hdmi_channel_allocation(channels);
+
+ hdmi_writeb(audiconf0, HDMI_FC_AUDICONF0);
+ hdmi_writeb(0, HDMI_FC_AUDICONF1);
+ hdmi_writeb(audiconf2, HDMI_FC_AUDICONF2);
+ hdmi_writeb(0, HDMI_FC_AUDICONF3);
+}
+
+static int cea_audio_rates[HDMI_MAX_RATES] = {
+ 32000, 44100, 48000, 88200, 96000, 176400, 192000,
+};
+
+static void fsl_hdmi_get_playback_rates(void)
+{
+ int i, count = 0;
+ u8 rates;
+
+ /* Always assume basic audio support */
+ rates = edid_cfg.sample_rates | 0x7;
+
+ for (i = 0 ; i < HDMI_MAX_RATES ; i++)
+ if ((rates & (1 << i)) != 0)
+ playback_rates[count++] = cea_audio_rates[i];
+
+ playback_constraint_rates.list = playback_rates;
+ playback_constraint_rates.count = count;
+
+ for (i = 0 ; i < playback_constraint_rates.count ; i++)
+ pr_debug("%s: constraint = %d Hz\n", __func__, playback_rates[i]);
+}
+
+static void fsl_hdmi_get_playback_sample_size(void)
+{
+ int i = 0;
+
+ /* Always assume basic audio support */
+ playback_sample_size[i++] = 16;
+
+ if (edid_cfg.sample_sizes & 0x4)
+ playback_sample_size[i++] = 24;
+
+ playback_constraint_bits.list = playback_sample_size;
+ playback_constraint_bits.count = i;
+
+ for (i = 0 ; i < playback_constraint_bits.count ; i++)
+ pr_debug("%s: constraint = %d bits\n", __func__, playback_sample_size[i]);
+}
+
+static void fsl_hdmi_get_playback_channels(void)
+{
+ int channels = 2, i = 0;
+
+ /* Always assume basic audio support */
+ playback_channels[i++] = channels;
+ channels += 2;
+
+ while ((i < HDMI_MAX_CHANNEL_CONSTRAINTS) &&
+ (channels <= edid_cfg.max_channels)) {
+ playback_channels[i++] = channels;
+ channels += 2;
+ }
+
+ playback_constraint_channels.list = playback_channels;
+ playback_constraint_channels.count = i;
+
+ for (i = 0 ; i < playback_constraint_channels.count ; i++)
+ pr_debug("%s: constraint = %d channels\n", __func__, playback_channels[i]);
+}
+
+static int fsl_hdmi_update_constraints(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ hdmi_get_edid_cfg(&edid_cfg);
+
+ fsl_hdmi_get_playback_rates();
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &playback_constraint_rates);
+ if (ret)
+ return ret;
+
+ fsl_hdmi_get_playback_sample_size();
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ &playback_constraint_bits);
+ if (ret)
+ return ret;
+
+ fsl_hdmi_get_playback_channels();
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &playback_constraint_channels);
+ if (ret)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int fsl_hdmi_soc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ clk_prepare_enable(hdmi_data->mipi_core_clk);
+ clk_prepare_enable(hdmi_data->isfr_clk);
+ clk_prepare_enable(hdmi_data->iahb_clk);
+
+ dev_dbg(dai->dev, "%s hdmi clks: mipi_core: %d isfr:%d iahb:%d\n", __func__,
+ (int)clk_get_rate(hdmi_data->mipi_core_clk),
+ (int)clk_get_rate(hdmi_data->isfr_clk),
+ (int)clk_get_rate(hdmi_data->iahb_clk));
+
+ ret = fsl_hdmi_update_constraints(substream);
+ if (ret < 0)
+ return ret;
+
+ /* Indicates the subpacket represents a flatline sample */
+ hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_SAMPFIT, 0x0);
+
+ return 0;
+}
+
+static void fsl_hdmi_soc_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai);
+
+ clk_disable_unprepare(hdmi_data->iahb_clk);
+ clk_disable_unprepare(hdmi_data->isfr_clk);
+ clk_disable_unprepare(hdmi_data->mipi_core_clk);
+}
+
+static int fsl_hdmi_soc_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ hdmi_set_audio_infoframe(runtime->channels);
+ hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_LAYOUT,
+ (runtime->channels > 2) ? 0x1 : 0x0);
+ hdmi_set_sample_rate(runtime->rate);
+ dumpregs(dai);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops fsl_hdmi_soc_dai_ops = {
+ .startup = fsl_hdmi_soc_startup,
+ .shutdown = fsl_hdmi_soc_shutdown,
+ .prepare = fsl_hdmi_soc_prepare,
+};
+
+/* IEC60958 status functions */
+static int fsl_hdmi_iec_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_hdmi_iec_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+
+ for (i = 0 ; i < 4 ; i++)
+ uvalue->value.iec958.status[i] = iec_header.status[i];
+
+ return 0;
+}
+
+static int fsl_hdmi_iec_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+
+ /* Do not allow professional mode */
+ if (uvalue->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL)
+ return -EPERM;
+
+ for (i = 0 ; i < 4 ; i++) {
+ iec_header.status[i] = uvalue->value.iec958.status[i];
+ pr_debug("%s status[%d]=0x%02x\n", __func__, i, iec_header.status[i]);
+ }
+
+ return 0;
+}
+
+static int fsl_hdmi_channels_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_channels();
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = playback_constraint_channels.count;
+
+ return 0;
+}
+
+
+static int fsl_hdmi_channels_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_channels();
+
+ for (i = 0 ; i < playback_constraint_channels.count ; i++)
+ uvalue->value.integer.value[i] = playback_channels[i];
+
+ return 0;
+}
+
+static int fsl_hdmi_rates_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_rates();
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = playback_constraint_rates.count;
+
+ return 0;
+}
+
+static int fsl_hdmi_rates_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_rates();
+
+ for (i = 0 ; i < playback_constraint_rates.count ; i++)
+ uvalue->value.integer.value[i] = playback_rates[i];
+
+ return 0;
+}
+
+static int fsl_hdmi_formats_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_sample_size();
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = playback_constraint_bits.count;
+
+ return 0;
+}
+
+static int fsl_hdmi_formats_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_sample_size();
+
+ for (i = 0 ; i < playback_constraint_bits.count ; i++)
+ uvalue->value.integer.value[i] = playback_sample_size[i];
+
+ return 0;
+}
+
+static struct snd_kcontrol_new fsl_hdmi_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_hdmi_iec_info,
+ .get = fsl_hdmi_iec_get,
+ .put = fsl_hdmi_iec_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Channels",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = fsl_hdmi_channels_info,
+ .get = fsl_hdmi_channels_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Rates",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = fsl_hdmi_rates_info,
+ .get = fsl_hdmi_rates_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Formats",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = fsl_hdmi_formats_info,
+ .get = fsl_hdmi_formats_get,
+ },
+};
+
+static int fsl_hdmi_soc_dai_probe(struct snd_soc_dai *dai)
+{
+ int ret;
+
+ init_channel_allocations();
+
+ ret = snd_soc_add_dai_controls(dai, fsl_hdmi_ctrls,
+ ARRAY_SIZE(fsl_hdmi_ctrls));
+ if (ret)
+ dev_warn(dai->dev, "failed to add dai controls\n");
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_hdmi_dai = {
+ .probe = &fsl_hdmi_soc_dai_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = MXC_HDMI_RATES_PLAYBACK,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ },
+ .ops = &fsl_hdmi_soc_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_hdmi_component = {
+ .name = "fsl-hdmi",
+};
+
+/* HDMI audio codec callbacks */
+static int fsl_hdmi_audio_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *fmt,
+ struct hdmi_codec_params *hparms)
+{
+ dev_dbg(dev, "[%s]: %u Hz, %d bit, %d channels\n", __func__,
+ hparms->sample_rate, hparms->sample_width, hparms->cea.channels);
+
+ return 0;
+}
+
+static void fsl_hdmi_audio_shutdown(struct device *dev, void *data)
+{
+ dev_dbg(dev, "[%s]\n", __func__);
+}
+
+static const struct hdmi_codec_ops fsl_hdmi_audio_codec_ops = {
+ .hw_params = fsl_hdmi_audio_hw_params,
+ .audio_shutdown = fsl_hdmi_audio_shutdown,
+};
+
+static struct hdmi_codec_pdata codec_data = {
+ .ops = &fsl_hdmi_audio_codec_ops,
+ .i2s = 1,
+ .max_i2s_channels = 8,
+};
+
+static int fsl_hdmi_dai_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct imx_hdmi *hdmi_data;
+ int ret = 0;
+
+ if (!np)
+ return -ENODEV;
+
+ if (!hdmi_get_registered()) {
+ dev_err(&pdev->dev, "failed to probe. Load HDMI-video first.\n");
+ return -ENOMEM;
+ }
+
+ hdmi_data = devm_kzalloc(&pdev->dev, sizeof(*hdmi_data), GFP_KERNEL);
+ if (!hdmi_data) {
+ dev_err(&pdev->dev, "failed to alloc hdmi_data\n");
+ return -ENOMEM;
+ }
+
+ hdmi_data->pdev = pdev;
+
+ memcpy(&hdmi_data->cpu_dai_drv, &fsl_hdmi_dai, sizeof(fsl_hdmi_dai));
+ hdmi_data->cpu_dai_drv.name = np->name;
+
+ hdmi_data->mipi_core_clk = devm_clk_get(&pdev->dev, "mipi_core");
+ if (IS_ERR(hdmi_data->mipi_core_clk)) {
+ ret = PTR_ERR(hdmi_data->mipi_core_clk);
+ dev_err(&pdev->dev, "failed to get mipi core clk: %d\n", ret);
+ return -EINVAL;
+ }
+
+ hdmi_data->isfr_clk = devm_clk_get(&pdev->dev, "hdmi_isfr");
+ if (IS_ERR(hdmi_data->isfr_clk)) {
+ ret = PTR_ERR(hdmi_data->isfr_clk);
+ dev_err(&pdev->dev, "failed to get HDMI isfr clk: %d\n", ret);
+ return -EINVAL;
+ }
+
+ hdmi_data->iahb_clk = devm_clk_get(&pdev->dev, "hdmi_iahb");
+ if (IS_ERR(hdmi_data->iahb_clk)) {
+ ret = PTR_ERR(hdmi_data->iahb_clk);
+ dev_err(&pdev->dev, "failed to get HDMI ahb clk: %d\n", ret);
+ return -EINVAL;
+ }
+
+ dev_set_drvdata(&pdev->dev, hdmi_data);
+ ret = snd_soc_register_component(&pdev->dev, &fsl_hdmi_component,
+ &hdmi_data->cpu_dai_drv, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "register DAI failed\n");
+ return ret;
+ }
+
+ hdmi_data->codec_dev = platform_device_register_data(&pdev->dev,
+ HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_NONE,
+ &codec_data, sizeof(codec_data));
+ if (IS_ERR(hdmi_data->codec_dev)) {
+ dev_err(&pdev->dev, "failed to register HDMI audio codec\n");
+ ret = PTR_ERR(hdmi_data->codec_dev);
+ goto fail;
+ }
+
+ hdmi_data->dma_dev = platform_device_alloc("imx-hdmi-audio", -1);
+ if (!hdmi_data->dma_dev) {
+ ret = -ENOMEM;
+ goto fail_dma;
+ }
+
+ platform_set_drvdata(hdmi_data->dma_dev, hdmi_data);
+
+ ret = platform_device_add(hdmi_data->dma_dev);
+ if (ret) {
+ platform_device_put(hdmi_data->dma_dev);
+ goto fail_dma;
+ }
+
+ return 0;
+
+fail_dma:
+ platform_device_unregister(hdmi_data->codec_dev);
+fail:
+ snd_soc_unregister_component(&pdev->dev);
+
+ return ret;
+}
+
+static int fsl_hdmi_dai_remove(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_data = platform_get_drvdata(pdev);
+
+ platform_device_unregister(hdmi_data->dma_dev);
+ platform_device_unregister(hdmi_data->codec_dev);
+ snd_soc_unregister_component(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_hdmi_dai_dt_ids[] = {
+ { .compatible = "fsl,imx6dl-hdmi-audio", },
+ { .compatible = "fsl,imx6q-hdmi-audio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_hdmi_dai_dt_ids);
+
+static struct platform_driver fsl_hdmi_driver = {
+ .probe = fsl_hdmi_dai_probe,
+ .remove = fsl_hdmi_dai_remove,
+ .driver = {
+ .name = "fsl-hdmi-dai",
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_hdmi_dai_dt_ids,
+ },
+};
+module_platform_driver(fsl_hdmi_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX DAI");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:fsl-hdmi-dai");
diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c
index f7f2d29f1bfe..817ad689fa1b 100644
--- a/sound/soc/fsl/fsl_micfil.c
+++ b/sound/soc/fsl/fsl_micfil.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright 2018 NXP
+#include <linux/atomic.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/interrupt.h>
@@ -32,7 +33,10 @@ struct fsl_micfil {
struct regmap *regmap;
const struct fsl_micfil_soc_data *soc;
struct clk *mclk;
+ struct clk *clk_src[MICFIL_CLK_SRC_NUM];
struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct kobject *hwvad_kobject;
+ unsigned int vad_channel;
unsigned int dataline;
char name[32];
int irq[MICFIL_IRQ_LINES];
@@ -40,6 +44,25 @@ struct fsl_micfil {
int quality; /*QUALITY 2-0 bits */
bool slave_mode;
int channel_gain[8];
+ int clk_src_id;
+ int dc_remover;
+ int vad_sound_gain;
+ int vad_noise_gain;
+ int vad_input_gain;
+ int vad_frame_time;
+ int vad_init_time;
+ int vad_init_mode;
+ int vad_nfil_adjust;
+ int vad_hpf;
+ int vad_zcd_th;
+ int vad_zcd_auto;
+ int vad_zcd_en;
+ int vad_zcd_adj;
+ int vad_rate_index;
+ atomic_t recording_state;
+ atomic_t hwvad_state;
+ /* spinlock to control HWVAD enable/disable */
+ spinlock_t hwvad_lock;
};
struct fsl_micfil_soc_data {
@@ -49,6 +72,11 @@ struct fsl_micfil_soc_data {
bool imx;
};
+static char *envp[] = {
+ "EVENT=PDM_VOICE_DETECT",
+ NULL,
+};
+
static struct fsl_micfil_soc_data fsl_micfil_imx8mm = {
.imx = true,
.fifos = 8,
@@ -77,11 +105,525 @@ static const char * const micfil_quality_select_texts[] = {
"VLow0", "Low",
};
+static const char * const micfil_hwvad_init_mode[] = {
+ "Envelope mode", "Energy mode",
+};
+
+static const char * const micfil_hwvad_hpf_texts[] = {
+ "Filter bypass",
+ "Cut-off @1750Hz",
+ "Cut-off @215Hz",
+ "Cut-off @102Hz",
+};
+
+static const char * const micfil_hwvad_zcd_enable[] = {
+ "OFF", "ON",
+};
+
+static const char * const micfil_hwvad_zcdauto_enable[] = {
+ "OFF", "ON",
+};
+
+static const char * const micfil_hwvad_noise_decimation[] = {
+ "Disabled", "Enabled",
+};
+
+/* when adding new rate text, also add it to the
+ * micfil_hwvad_rate_ints
+ */
+static const char * const micfil_hwvad_rate[] = {
+ "48KHz", "44.1KHz",
+};
+
+static const int micfil_hwvad_rate_ints[] = {
+ 48000, 44100,
+};
+
+static const char * const micfil_clk_src_texts[] = {
+ "Auto", "AudioPLL1", "AudioPLL2", "ExtClk3",
+};
+
+/* DC Remover Control
+ * Filter Bypassed 1 1
+ * Cut-off @21Hz 0 0
+ * Cut-off @83Hz 0 1
+ * Cut-off @152HZ 1 0
+ */
+static const char * const micfil_dc_remover_texts[] = {
+ "Cut-off @21Hz", "Cut-off @83Hz",
+ "Cut-off @152Hz", "Bypass",
+};
+
static const struct soc_enum fsl_micfil_quality_enum =
SOC_ENUM_SINGLE(REG_MICFIL_CTRL2,
MICFIL_CTRL2_QSEL_SHIFT,
ARRAY_SIZE(micfil_quality_select_texts),
micfil_quality_select_texts);
+static const struct soc_enum fsl_micfil_hwvad_init_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_init_mode),
+ micfil_hwvad_init_mode);
+static const struct soc_enum fsl_micfil_hwvad_hpf_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_hpf_texts),
+ micfil_hwvad_hpf_texts);
+static const struct soc_enum fsl_micfil_hwvad_zcd_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcd_enable),
+ micfil_hwvad_zcd_enable);
+static const struct soc_enum fsl_micfil_hwvad_zcdauto_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcdauto_enable),
+ micfil_hwvad_zcd_enable);
+static const struct soc_enum fsl_micfil_hwvad_ndec_enum =
+ SOC_ENUM_SINGLE(REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NOREN_SHIFT,
+ ARRAY_SIZE(micfil_hwvad_noise_decimation),
+ micfil_hwvad_noise_decimation);
+static const struct soc_enum fsl_micfil_hwvad_rate_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_rate),
+ micfil_hwvad_rate);
+static const struct soc_enum fsl_micfil_clk_src_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_clk_src_texts),
+ micfil_clk_src_texts);
+static const struct soc_enum fsl_micfil_dc_remover_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_dc_remover_texts),
+ micfil_dc_remover_texts);
+
+static int micfil_put_clk_src(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ micfil->clk_src_id = val;
+
+ return 0;
+}
+
+static int micfil_get_clk_src(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->clk_src_id;
+
+ return 0;
+}
+
+static int micfil_put_dc_remover_state(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ unsigned int *item = ucontrol->value.enumerated.item;
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+ int i = 0, ret = 0;
+ u32 reg_val = 0;
+
+ if (val < 0 || val > 3)
+ return -EINVAL;
+
+ micfil->dc_remover = val;
+
+ /* Calculate total value for all channels */
+ for (i = 0; i < 8; i++)
+ reg_val |= MICFIL_DC_MODE(val, i);
+
+ /* Update DC Remover mode for all channels */
+ ret = snd_soc_component_update_bits(comp, REG_MICFIL_DC_CTRL,
+ MICFIL_DC_CTRL_MASK, reg_val);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int micfil_get_dc_remover_state(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->dc_remover;
+
+ return 0;
+}
+
+static int hwvad_put_init_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ /* 0 - Envelope-based Mode
+ * 1 - Energy-based Mode
+ */
+ micfil->vad_init_mode = val;
+ return 0;
+}
+
+static int hwvad_get_init_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_init_mode;
+
+ return 0;
+}
+
+static int hwvad_put_hpf(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ /* 00 - HPF Bypass
+ * 01 - Cut-off frequency 1750Hz
+ * 10 - Cut-off frequency 215Hz
+ * 11 - Cut-off frequency 102Hz
+ */
+ micfil->vad_hpf = val;
+
+ return 0;
+}
+
+static int hwvad_get_hpf(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_hpf;
+
+ return 0;
+}
+
+static int hwvad_put_zcd_en(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ micfil->vad_zcd_en = val;
+
+ return 0;
+}
+
+static int hwvad_get_zcd_en(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_zcd_en;
+
+ return 0;
+}
+
+static int hwvad_put_rate(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ micfil->vad_rate_index = val;
+
+ return 0;
+}
+
+static int hwvad_get_rate(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_rate_index;
+
+ return 0;
+}
+
+static int hwvad_put_zcd_auto(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ micfil->vad_zcd_auto = val;
+
+ return 0;
+}
+
+static int hwvad_get_zcd_auto(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_zcd_auto;
+
+ return 0;
+}
+
+static int gain_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 = 0;
+ uinfo->value.integer.max = 0xf;
+
+ return 0;
+}
+
+static int hwvad_put_input_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_input_gain = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_input_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_input_gain;
+
+ return 0;
+}
+
+static int hwvad_put_sound_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_sound_gain = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_sound_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_sound_gain;
+
+ return 0;
+}
+
+static int hwvad_put_noise_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_noise_gain = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_noise_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_noise_gain;
+
+ return 0;
+}
+
+static int hwvad_framet_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 = 1;
+ uinfo->value.integer.max = 64;
+
+ return 0;
+}
+
+static int hwvad_put_frame_time(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_frame_time = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_frame_time(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_frame_time;
+
+ return 0;
+}
+
+static int hwvad_initt_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 = 1;
+ uinfo->value.integer.max = 32;
+
+ return 0;
+}
+
+static int hwvad_put_init_time(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_init_time = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_init_time(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_init_time;
+
+ return 0;
+}
+
+static int hwvad_nfiladj_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 = 1;
+ uinfo->value.integer.max = 32;
+
+ return 0;
+}
+
+static int hwvad_put_nfil_adjust(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_nfil_adjust = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_nfil_adjust(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_nfil_adjust;
+
+ return 0;
+}
+
+static int hwvad_zcdth_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 = 1;
+ uinfo->value.integer.max = 1024;
+
+ return 0;
+}
+
+static int hwvad_put_zcd_th(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_zcd_th = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_zcd_th(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_zcd_th;
+
+ return 0;
+}
+
+static int hwvad_zcdadj_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 = 1;
+ uinfo->value.integer.max = 16;
+
+ return 0;
+}
+
+static int hwvad_put_zcd_adj(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_zcd_adj = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_zcd_adj(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_zcd_adj;
+
+ return 0;
+}
static DECLARE_TLV_DB_SCALE(gain_tlv, 0, 100, 0);
@@ -105,8 +647,107 @@ static const struct snd_kcontrol_new fsl_micfil_snd_controls[] = {
SOC_ENUM_EXT("MICFIL Quality Select",
fsl_micfil_quality_enum,
snd_soc_get_enum_double, snd_soc_put_enum_double),
+ SOC_ENUM_EXT("HWVAD Initialization Mode",
+ fsl_micfil_hwvad_init_mode_enum,
+ hwvad_get_init_mode, hwvad_put_init_mode),
+ SOC_ENUM_EXT("HWVAD High-Pass Filter",
+ fsl_micfil_hwvad_hpf_enum,
+ hwvad_get_hpf, hwvad_put_hpf),
+ SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Enable",
+ fsl_micfil_hwvad_zcd_enum,
+ hwvad_get_zcd_en, hwvad_put_zcd_en),
+ SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Auto Threshold",
+ fsl_micfil_hwvad_zcdauto_enum,
+ hwvad_get_zcd_auto, hwvad_put_zcd_auto),
+ SOC_ENUM_EXT("HWVAD Noise OR Enable",
+ fsl_micfil_hwvad_ndec_enum,
+ snd_soc_get_enum_double, snd_soc_put_enum_double),
+ SOC_ENUM_EXT("HWVAD Sampling Rate",
+ fsl_micfil_hwvad_rate_enum,
+ hwvad_get_rate, hwvad_put_rate),
+ SOC_ENUM_EXT("Clock Source",
+ fsl_micfil_clk_src_enum,
+ micfil_get_clk_src, micfil_put_clk_src),
+ SOC_ENUM_EXT("MICFIL DC Remover Control", fsl_micfil_dc_remover_enum,
+ micfil_get_dc_remover_state, micfil_put_dc_remover_state),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Input Gain",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = gain_info,
+ .get = hwvad_get_input_gain,
+ .put = hwvad_put_input_gain,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Sound Gain",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = gain_info,
+ .get = hwvad_get_sound_gain,
+ .put = hwvad_put_sound_gain,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Noise Gain",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = gain_info,
+ .get = hwvad_get_noise_gain,
+ .put = hwvad_put_noise_gain,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Detector Frame Time",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_framet_info,
+ .get = hwvad_get_frame_time,
+ .put = hwvad_put_frame_time,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Detector Initialization Time",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_initt_info,
+ .get = hwvad_get_init_time,
+ .put = hwvad_put_init_time,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Noise Filter Adjustment",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_nfiladj_info,
+ .get = hwvad_get_nfil_adjust,
+ .put = hwvad_put_nfil_adjust,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Zero-Crossing Detector Threshold",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_zcdth_info,
+ .get = hwvad_get_zcd_th,
+ .put = hwvad_put_zcd_th,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Zero-Crossing Detector Adjustment",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_zcdadj_info,
+ .get = hwvad_get_zcd_adj,
+ .put = hwvad_put_zcd_adj,
+ },
+
};
+static int disable_hwvad(struct device *dev, bool sync);
+
+
static inline int get_pdm_clk(struct fsl_micfil *micfil,
unsigned int rate)
{
@@ -193,19 +834,590 @@ static int fsl_micfil_reset(struct device *dev)
return 0;
}
-static int fsl_micfil_set_mclk_rate(struct fsl_micfil *micfil,
+/* enable/disable hwvad interrupts */
+static int configure_hwvad_interrupts(struct device *dev,
+ int enable)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+ u32 vadie_reg = enable ? MICFIL_VAD0_CTRL1_IE : 0;
+ u32 vaderie_reg = enable ? MICFIL_VAD0_CTRL1_ERIE : 0;
+
+ /* Voice Activity Detector Error Interruption Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_ERIE_MASK,
+ vaderie_reg);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set/clear VADERIE in CTRL1_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Voice Activity Detector Interruption Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_IE_MASK,
+ vadie_reg);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set/clear VADIE in CTRL1_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* w1c */
+ regmap_write_bits(micfil->regmap, REG_MICFIL_STAT, 0xFF, 0xFF);
+
+ return 0;
+}
+
+static int init_hwvad_internal_filters(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+
+ /* Voice Activity Detector Internal Filters Initialization*/
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_ST10_MASK,
+ MICFIL_VAD0_CTRL1_ST10);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set VADST10 in CTRL1_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* sleep for 100ms - it should be enough for bit to stay
+ * pulsed for more than 2 cycles
+ */
+ mdelay(MICFIL_SLEEP);
+
+ /* Voice Activity Detector Enabled */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_ST10_MASK,
+ 0);
+ if (ret) {
+ dev_err(dev,
+ "Failed to clear VADST10 in CTRL1_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+ return 0;
+}
+
+/* Zero-Crossing Detector Initialization
+ * Optionally a Zero-Crossing Detection block (ZCD) could
+ * be enabled to avoid low energy voiced speech be missed,
+ * improving the voice detection performance.
+ * See Section 8.4.3
+ */
+static int __maybe_unused init_zcd(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+
+ /* exit if zcd is not enabled from userspace */
+ if (!micfil->vad_zcd_en)
+ return 0;
+
+ if (micfil->vad_zcd_auto) {
+ /* Zero-Crossing Detector Adjustment */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDADJ_MASK,
+ micfil->vad_zcd_adj);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set ZCDADJ in ZCD_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /* Zero-Crossing Detector Threshold */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDTH_MASK,
+ MICFIL_VAD0_ZCD_ZCDTH(micfil->vad_zcd_th));
+ if (ret) {
+ dev_err(dev, "Failed to set ZCDTH in ZCD_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Zero-Crossing Detector AND Behavior */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDAND_MASK,
+ MICFIL_HWVAD_ZCDAND);
+ if (ret) {
+ dev_err(dev, "Failed to set ZCDAND in ZCD_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Zero-Crossing Detector Automatic Threshold */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDAUT_MASK,
+ micfil->vad_zcd_auto);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set/clear ZCDAUT in ZCD_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Zero-Crossing Detector Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDEN_MASK,
+ MICFIL_VAD0_ZCD_ZCDEN);
+ if (ret) {
+ dev_err(dev, "Failed to set ZCDEN in ZCD_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Configuration done only in energy-based initialization mode */
+static int init_hwvad_energy_mode(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret, i;
+ u32 stat;
+ u32 flag;
+
+ dev_info(dev, "Energy-based mode initialization\n");
+
+ /* Voice Activity Detector Reset */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_RST_SHIFT,
+ MICFIL_VAD0_CTRL1_RST);
+ if (ret) {
+ dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Voice Activity Detector Enabled */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_EN_MASK,
+ MICFIL_VAD0_CTRL1_EN);
+ if (ret) {
+ dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* it would be a good idea to wait some time before VADEN
+ * is set
+ */
+ mdelay(5 * MICFIL_SLEEP);
+
+ /* Enable Interrupts */
+ ret = configure_hwvad_interrupts(dev, 1);
+
+ /* Initialize Zero Crossing Detector */
+ ret = init_zcd(dev);
+ if (ret)
+ return ret;
+
+ /* Enable MICFIL module */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK,
+ MICFIL_CTRL1_PDMIEN);
+ if (ret) {
+ dev_err(dev, "failed to enable the module\n");
+ return ret;
+ }
+
+ /* Wait for INITF to be asserted */
+ for (i = 0; i < MICFIL_MAX_RETRY; i++) {
+ ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat);
+ if (ret) {
+ dev_err(dev, "failed to read register %d\n",
+ REG_MICFIL_VAD0_STAT);
+ return ret;
+ }
+
+ flag = (stat & MICFIL_VAD0_STAT_INITF_MASK);
+ if (flag == 0)
+ break;
+
+ mdelay(MICFIL_SLEEP);
+ }
+
+ if (i == MICFIL_MAX_RETRY) {
+ dev_err(dev, "initf not asserted. Failed to init hwvad\n");
+ return -EBUSY;
+ }
+
+ /* Initialize Internal Filters */
+ ret = init_hwvad_internal_filters(dev);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+/* Configuration done only in envelope-based initialization mode */
+static int init_hwvad_envelope_mode(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret, i;
+ u32 stat;
+ u32 flag;
+
+ /* Frame energy disable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_FRENDIS_MASK,
+ MICFIL_VAD0_CTRL2_FRENDIS);
+ if (ret) {
+ dev_err(dev, "Failed to set FRENDIS in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Enable pre-filter Noise & Signal */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_PREFEN_MASK,
+ MICFIL_VAD0_CTRL2_PREFEN);
+ if (ret) {
+ dev_err(dev, "Failed to set PREFEN in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Enable Signal Filter */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SFILEN_MASK,
+ MICFIL_VAD0_SCONFIG_SFILEN);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set SFILEN in SCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Signal Maximum Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SMAXEN_MASK,
+ MICFIL_VAD0_SCONFIG_SMAXEN);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set SMAXEN in SCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Allways enable noise filter, not based on voice activity
+ * information
+ */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NFILAUT_MASK,
+ 0);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set NFILAUT in NCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Noise Minimum Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NMINEN_MASK,
+ MICFIL_VAD0_NCONFIG_NMINEN);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set NMINEN in NCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Noise Decimation Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NDECEN_MASK,
+ MICFIL_VAD0_NCONFIG_NDECEN);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set NDECEN in NCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Voice Activity Detector Reset */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_RST_MASK,
+ MICFIL_VAD0_CTRL1_RST);
+ if (ret) {
+ dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Initialize Zero Crossing Detector */
+ ret = init_zcd(dev);
+ if (ret)
+ return ret;
+
+ /* Voice Activity Detector Enabled */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_EN_MASK,
+ MICFIL_VAD0_CTRL1_EN);
+ if (ret) {
+ dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Enable MICFIL module */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK,
+ MICFIL_CTRL1_PDMIEN);
+ if (ret) {
+ dev_err(dev, "failed to enable the module\n");
+ return ret;
+ }
+
+ /* it would be a good idea to wait some time before VADEN
+ * is set
+ */
+ mdelay(3 * MICFIL_SLEEP);
+
+ /* Wait for INITF to be asserted */
+ for (i = 0; i < MICFIL_MAX_RETRY; i++) {
+ ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat);
+ if (ret) {
+ dev_err(dev, "failed to read register %d\n",
+ REG_MICFIL_VAD0_STAT);
+ return ret;
+ }
+
+ flag = (stat & MICFIL_VAD0_STAT_INITF_MASK);
+ if (flag == 0)
+ break;
+
+ mdelay(MICFIL_SLEEP);
+ }
+
+ if (i == MICFIL_MAX_RETRY) {
+ dev_err(dev, "initf not asserted. Failed to init hwvad\n");
+ return -EBUSY;
+ }
+
+ /* Initialize Internal Filters */
+ ret = init_hwvad_internal_filters(dev);
+ if (ret)
+ return ret;
+
+ /* Enable interrupts */
+ ret = configure_hwvad_interrupts(dev, 1);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+/* Hardware Voice Active Detection: The HWVAD takes data from the input
+ * of a selected PDM microphone to detect if there is any
+ * voice activity. When a voice activity is detected, an interrupt could
+ * be delivered to the system. Initialization in section 8.4:
+ * Can work in two modes:
+ * -> Eneveope-based mode (section 8.4.1)
+ * -> Energy-based mode (section 8.4.2)
+ *
+ * It is important to remark that the HWVAD detector could be enabled
+ * or reset only when the MICFIL isn't running i.e. when the BSY_FIL
+ * bit in STAT register is cleared
+ */
+static int __maybe_unused init_hwvad(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+ u32 reg_val;
+
+ /* configure CIC OSR in VADCICOSR */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_CICOSR_MASK,
+ MICFIL_CTRL2_OSR_DEFAULT);
+ if (ret) {
+ dev_err(dev, "Failed to set CICOSR in CTRL1_VAD0i [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure source channel in VADCHSEL */
+ reg_val = MICFIL_VAD0_CTRL1_CHSEL(micfil->vad_channel);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_CHSEL_MASK,
+ reg_val);
+ if (ret) {
+ dev_err(dev, "Failed to set CHSEL in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure detector frame time VADFRAMET */
+ reg_val = MICFIL_VAD0_CTRL2_FRAMET(micfil->vad_frame_time);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_FRAMET_MASK,
+ reg_val);
+ if (ret) {
+ dev_err(dev, "Failed to set FRAMET in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure initialization time in VADINITT */
+ reg_val = MICFIL_VAD0_CTRL1_INITT(micfil->vad_init_time);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_INITT_MASK,
+ reg_val);
+ if (ret) {
+ dev_err(dev, "Failed to set INITT in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure input gain in VADINPGAIN */
+ reg_val = MICFIL_VAD0_CTRL2_INPGAIN(micfil->vad_input_gain);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_INPGAIN_MASK,
+ reg_val);
+ if (ret) {
+ dev_err(dev, "Failed to set INPGAIN in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure sound gain in SGAIN */
+ reg_val = MICFIL_VAD0_SCONFIG_SGAIN(micfil->vad_sound_gain);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SGAIN_MASK,
+ reg_val);
+ if (ret) {
+ dev_err(dev, "Failed to set SGAIN in SCONFIG_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure noise gain in NGAIN */
+ reg_val = MICFIL_VAD0_NCONFIG_NGAIN(micfil->vad_noise_gain);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NGAIN_MASK,
+ reg_val);
+ if (ret) {
+ dev_err(dev, "Failed to set NGAIN in NCONFIG_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure or clear the VADNFILADJ based on mode */
+ reg_val = MICFIL_VAD0_NCONFIG_NFILADJ(micfil->vad_nfil_adjust);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NFILADJ_MASK,
+ reg_val);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set VADNFILADJ in NCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* enable the high-pass filter in VADHPF */
+ reg_val = MICFIL_VAD0_CTRL2_HPF(micfil->vad_hpf);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_HPF_MASK,
+ reg_val);
+ if (ret) {
+ dev_err(dev, "Failed to set HPF in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* envelope-based specific initialization */
+ if (micfil->vad_init_mode == MICFIL_HWVAD_ENVELOPE_MODE) {
+ ret = init_hwvad_envelope_mode(dev);
+ if (ret)
+ return ret;
+ } else {
+ ret = init_hwvad_energy_mode(dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static inline bool clk_in_list(struct clk *p, struct clk *clk_src[])
+{
+ int i;
+
+ for (i = 0; i < MICFIL_CLK_SRC_NUM; i++)
+ if (clk_is_match(p, clk_src[i]))
+ return true;
+
+ return false;
+}
+
+static int fsl_micfil_set_mclk_rate(struct fsl_micfil *micfil, int clk_id,
unsigned int freq)
{
+ struct clk *p = micfil->mclk, *pll = 0, *npll = 0;
struct device *dev = &micfil->pdev->dev;
+ u64 ratio = freq;
+ u64 clk_rate;
int ret;
+ int i;
+
+ /* Do not touch the clock if hwvad is already enabled
+ * since you can record only at hwvad rate and clock
+ * has already been set to the required frequency
+ */
+ if (atomic_read(&micfil->hwvad_state) == MICFIL_HWVAD_ON)
+ return 0;
+
+ /* check if all clock sources are valid */
+ for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) {
+ if (micfil->clk_src[i])
+ continue;
+
+ dev_err(dev, "Clock Source %d is not valid.\n", i);
+ return -EINVAL;
+ }
+
+ while (p) {
+ struct clk *pp = clk_get_parent(p);
+
+ if (clk_in_list(pp, micfil->clk_src)) {
+ pll = pp;
+ break;
+ }
+ p = pp;
+ }
+
+ if (!pll) {
+ dev_err(dev, "reached a null clock\n");
+ return -EINVAL;
+ }
+
+ if (micfil->clk_src_id == MICFIL_CLK_AUTO) {
+ for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) {
+ clk_rate = clk_get_rate(micfil->clk_src[i]);
+ /* This is an workaround since audio_pll2 clock
+ * has 722534399 rate and this will never divide
+ * to any known frequency ???
+ */
+ clk_rate = round_up(clk_rate, 10);
+ if (do_div(clk_rate, ratio) == 0)
+ npll = micfil->clk_src[i];
+ }
+ } else {
+ /* clock id is offseted by 1 since ID=0 means
+ * auto clock selection
+ */
+ npll = micfil->clk_src[micfil->clk_src_id - 1];
+ }
+
+ if (!npll) {
+ dev_err(dev,
+ "failed to find a suitable clock source\n");
+ return -EINVAL;
+ }
clk_disable_unprepare(micfil->mclk);
+ if (!clk_is_match(pll, npll)) {
+ ret = clk_set_parent(p, npll);
+ if (ret < 0)
+ dev_warn(dev,
+ "failed to set parrent %d\n", ret);
+ }
ret = clk_set_rate(micfil->mclk, freq * 1024);
if (ret)
dev_warn(dev, "failed to set rate (%u): %d\n",
freq * 1024, ret);
-
clk_prepare_enable(micfil->mclk);
return ret;
@@ -298,7 +1510,7 @@ static int fsl_set_clock_params(struct device *dev, unsigned int rate)
int clk_div;
int ret = 0;
- ret = fsl_micfil_set_mclk_rate(micfil, rate);
+ ret = fsl_micfil_set_mclk_rate(micfil, 0, rate);
if (ret < 0)
dev_err(dev, "failed to set mclk[%lu] to rate %u\n",
clk_get_rate(micfil->mclk), rate);
@@ -333,14 +1545,32 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream,
unsigned int channels = params_channels(params);
unsigned int rate = params_rate(params);
struct device *dev = &micfil->pdev->dev;
+ unsigned int hwvad_rate;
int ret;
+ u32 hwvad_state;
- /* 1. Disable the module */
- ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
- MICFIL_CTRL1_PDMIEN_MASK, 0);
- if (ret) {
- dev_err(dev, "failed to disable the module\n");
- return ret;
+ hwvad_rate = micfil_hwvad_rate_ints[micfil->vad_rate_index];
+ hwvad_state = atomic_read(&micfil->hwvad_state);
+
+ /* if hwvad is enabled, make sure you are recording at
+ * the same rate the hwvad is on or reject it to avoid
+ * changing the clock rate.
+ */
+ if (hwvad_state == MICFIL_HWVAD_ON && rate != hwvad_rate) {
+ dev_err(dev, "Record at hwvad rate %u\n", hwvad_rate);
+ return -EINVAL;
+ }
+
+ atomic_set(&micfil->recording_state, MICFIL_RECORDING_ON);
+
+ if (hwvad_state == MICFIL_HWVAD_OFF) {
+ /* 1. Disable the module */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK, 0);
+ if (ret) {
+ dev_err(dev, "failed to disable the module\n");
+ return ret;
+ }
}
/* enable channels */
@@ -358,11 +1588,22 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream,
return ret;
}
+ micfil->dma_params_rx.fifo_num = channels;
micfil->dma_params_rx.maxburst = channels * MICFIL_DMA_MAXBURST_RX;
return 0;
}
+static int fsl_micfil_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+
+ atomic_set(&micfil->recording_state, MICFIL_RECORDING_OFF);
+
+ return 0;
+}
+
static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
unsigned int freq, int dir)
{
@@ -374,7 +1615,7 @@ static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
if (!freq)
return 0;
- ret = fsl_micfil_set_mclk_rate(micfil, freq);
+ ret = fsl_micfil_set_mclk_rate(micfil, clk_id, freq);
if (ret < 0)
dev_err(dev, "failed to set mclk[%lu] to rate %u\n",
clk_get_rate(micfil->mclk), freq);
@@ -382,11 +1623,38 @@ static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
return ret;
}
+static int fsl_micfil_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+
+ /* DAI MODE */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* DAI CLK INVERSION */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ micfil->slave_mode = false;
+
+ return 0;
+}
+
static struct snd_soc_dai_ops fsl_micfil_dai_ops = {
.startup = fsl_micfil_startup,
.trigger = fsl_micfil_trigger,
.hw_params = fsl_micfil_hw_params,
+ .hw_free = fsl_micfil_hw_free,
.set_sysclk = fsl_micfil_set_dai_sysclk,
+ .set_fmt = fsl_micfil_set_dai_fmt,
};
static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai)
@@ -399,17 +1667,30 @@ static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai)
/* set qsel to medium */
ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2,
- MICFIL_CTRL2_QSEL_MASK, MICFIL_MEDIUM_QUALITY);
+ MICFIL_CTRL2_QSEL_MASK, MICFIL_VLOW0_QUALITY);
if (ret) {
dev_err(dev, "failed to set quality mode bits, reg 0x%X\n",
REG_MICFIL_CTRL2);
return ret;
}
- /* set default gain to max_gain */
- regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x77777777);
- for (i = 0; i < 8; i++)
- micfil->channel_gain[i] = 0xF;
+ /* set default gain to 2 */
+ regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x22222222);
+ for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++)
+ micfil->channel_gain[i] = 0xA;
+
+ /* set DC Remover in bypass mode*/
+ val = 0;
+ for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++)
+ val |= MICFIL_DC_MODE(MICFIL_DC_BYPASS, i);
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_DC_CTRL,
+ MICFIL_DC_CTRL_MASK, val);
+ if (ret) {
+ dev_err(dev, "failed to set DC Remover mode bits, reg 0x%X\n",
+ REG_MICFIL_DC_CTRL);
+ return ret;
+ }
+ micfil->dc_remover = MICFIL_DC_BYPASS;
snd_soc_dai_init_dma_data(cpu_dai, NULL,
&micfil->dma_params_rx);
@@ -566,6 +1847,72 @@ static const struct regmap_config fsl_micfil_regmap_config = {
/* END OF REGMAP */
+static irqreturn_t voice_detected_fn(int irq, void *devid)
+{
+ struct fsl_micfil *micfil = (struct fsl_micfil *)devid;
+ struct device *dev = &micfil->pdev->dev;
+ int ret;
+
+ /* disable hwvad */
+ spin_lock(&micfil->hwvad_lock);
+ ret = disable_hwvad(dev, true);
+ spin_unlock(&micfil->hwvad_lock);
+
+ if (ret)
+ dev_err(dev, "Failed to disable HWVAD module: %d\n", ret);
+
+ /* notify userspace that voice was detected */
+ kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t hwvad_isr(int irq, void *devid)
+{
+ struct fsl_micfil *micfil = (struct fsl_micfil *)devid;
+ struct device *dev = &micfil->pdev->dev;
+ int ret;
+ u32 vad0_reg;
+
+ regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg);
+
+ /* The only difference between MICFIL_VAD0_STAT_EF and
+ * MICFIL_VAD0_STAT_IF is that the former requires Write
+ * 1 to Clear. Since both flags are set, it is enough
+ * to only read one of them
+ */
+ if (vad0_reg & MICFIL_VAD0_STAT_IF_MASK) {
+ /* Write 1 to clear */
+ regmap_write_bits(micfil->regmap, REG_MICFIL_VAD0_STAT,
+ MICFIL_VAD0_STAT_IF_MASK,
+ MICFIL_VAD0_STAT_IF);
+
+ /* disable hwvad interrupts */
+ ret = configure_hwvad_interrupts(dev, 0);
+ if (ret)
+ dev_err(dev, "Failed to disable interrupts\n");
+ }
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t hwvad_err_isr(int irq, void *devid)
+{
+ struct fsl_micfil *micfil = (struct fsl_micfil *)devid;
+ struct device *dev = &micfil->pdev->dev;
+ u32 vad0_reg;
+
+ regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg);
+
+ if (vad0_reg & MICFIL_VAD0_STAT_INSATF_MASK)
+ dev_dbg(dev, "voice activity input overflow/underflow detected\n");
+
+ if (vad0_reg & MICFIL_VAD0_STAT_INITF_MASK)
+ dev_dbg(dev, "voice activity dectector is initializing\n");
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t micfil_isr(int irq, void *devid)
{
struct fsl_micfil *micfil = (struct fsl_micfil *)devid;
@@ -635,6 +1982,197 @@ static irqreturn_t micfil_err_isr(int irq, void *devid)
return IRQ_HANDLED;
}
+static int fsl_set_clock_params(struct device *, unsigned int);
+
+static int enable_hwvad(struct device *dev, bool sync)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+ int rate;
+ u32 state;
+
+ if (sync)
+ pm_runtime_get_sync(dev);
+
+ state = atomic_cmpxchg(&micfil->hwvad_state,
+ MICFIL_HWVAD_OFF,
+ MICFIL_HWVAD_ON);
+
+ /* we should not reenable when sync = true because
+ * this means enable was called for second time by
+ * user. However state = ON and sync = false can only
+ * occur when enable is called from system_resume. In
+ * this case we should enable the hwvad
+ */
+ if (sync && state == MICFIL_HWVAD_ON) {
+ dev_err(dev, "hwvad already on\n");
+ ret = -EBUSY;
+ goto enable_error;
+ }
+
+ if (micfil->vad_rate_index >= ARRAY_SIZE(micfil_hwvad_rate_ints)) {
+ dev_err(dev, "There are more select texts than rates\n");
+ ret = -EINVAL;
+ goto enable_error;
+ }
+
+ rate = micfil_hwvad_rate_ints[micfil->vad_rate_index];
+
+ /* This is required because if an arecord was done,
+ * suspend function will mark regmap as cache only
+ * and reads/writes in volatile regs will fail
+ */
+ regcache_cache_only(micfil->regmap, false);
+ regcache_mark_dirty(micfil->regmap);
+ regcache_sync(micfil->regmap);
+
+ ret = fsl_set_clock_params(dev, rate);
+ if (ret)
+ goto enable_error;
+
+ ret = fsl_micfil_reset(dev);
+ if (ret)
+ goto enable_error;
+
+ /* Initialize Hardware Voice Activity */
+ ret = init_hwvad(dev);
+ if (ret == 0)
+ return 0;
+
+enable_error:
+ if (state == MICFIL_HWVAD_OFF)
+ atomic_cmpxchg(&micfil->hwvad_state,
+ MICFIL_HWVAD_ON, MICFIL_HWVAD_OFF);
+ if (sync)
+ pm_runtime_put_sync(dev);
+ return ret;
+}
+
+static int disable_hwvad(struct device *dev, bool sync)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret = 0;
+ u32 state;
+
+ /* This is required because if an arecord was done,
+ * suspend function will mark regmap as cache only
+ * and reads/writes in volatile regs will fail
+ */
+ regcache_cache_only(micfil->regmap, false);
+ regcache_mark_dirty(micfil->regmap);
+ regcache_sync(micfil->regmap);
+
+ /* disable is called with sync = false only from
+ * system suspend and in this case, you should not
+ * change the hwvad_state so we know at system_resume
+ * to reenable hwvad
+ */
+ if (sync)
+ state = atomic_cmpxchg(&micfil->hwvad_state,
+ MICFIL_HWVAD_ON,
+ MICFIL_HWVAD_OFF);
+ else
+ state = atomic_read(&micfil->hwvad_state);
+
+ if (state == MICFIL_HWVAD_ON) {
+ /* Voice Activity Detector Reset */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_RST_SHIFT,
+ MICFIL_VAD0_CTRL1_RST);
+
+ /* Disable HWVAD */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_EN_MASK,
+ 0);
+
+ /* Disable Signal Filter */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SFILEN_MASK,
+ 0);
+
+ /* Signal Maximum Enable */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SMAXEN_MASK,
+ 0);
+
+ /* Enable pre-filter Noise & Signal */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_PREFEN_MASK,
+ 0);
+
+ /* Noise Decimation Enable */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NDECEN_MASK,
+ 0);
+
+ /* disable the module and clock only if recording
+ * is not done in parallel
+ */
+ state = atomic_read(&micfil->recording_state);
+ if (state == MICFIL_RECORDING_OFF) {
+ /* Disable MICFIL module */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK,
+ 0);
+ }
+
+ } else {
+ ret = -EPERM;
+ dev_err(dev, "HWVAD is not enabled %d\n", ret);
+ }
+
+ if (sync)
+ pm_runtime_put_sync(dev);
+
+ return ret;
+}
+
+static ssize_t micfil_hwvad_handler(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct kobject *nand_kobj = kobj->parent;
+ struct device *dev = container_of(nand_kobj, struct device, kobj);
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ unsigned long vad_channel, flags;
+ int ret;
+
+ ret = kstrtoul(buf, 16, &vad_channel);
+ if (ret < 0)
+ return -EINVAL;
+
+ spin_lock_irqsave(&micfil->hwvad_lock, flags);
+ if (vad_channel <= 7) {
+ micfil->vad_channel = vad_channel;
+ ret = enable_hwvad(dev, true);
+ } else {
+ micfil->vad_channel = -1;
+ ret = disable_hwvad(dev, true);
+ }
+ spin_unlock_irqrestore(&micfil->hwvad_lock, flags);
+
+ if (ret) {
+ dev_err(dev, "Failed to %s hwvad: %d\n",
+ vad_channel <= 7 ? "enable" : "disable", ret);
+ return ret;
+ }
+
+ return count;
+}
+
+static struct kobj_attribute hwvad_en_attr = __ATTR(enable,
+ 0660,
+ NULL,
+ micfil_hwvad_handler);
+
static int fsl_micfil_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
@@ -668,6 +2206,19 @@ static int fsl_micfil_probe(struct platform_device *pdev)
return PTR_ERR(micfil->mclk);
}
+ /* get audio pll1 and pll2 */
+ micfil->clk_src[MICFIL_AUDIO_PLL1] = devm_clk_get(&pdev->dev, "pll8k");
+ if (IS_ERR(micfil->clk_src[MICFIL_AUDIO_PLL1]))
+ micfil->clk_src[MICFIL_AUDIO_PLL1] = NULL;
+
+ micfil->clk_src[MICFIL_AUDIO_PLL2] = devm_clk_get(&pdev->dev, "pll11k");
+ if (IS_ERR(micfil->clk_src[MICFIL_AUDIO_PLL2]))
+ micfil->clk_src[MICFIL_AUDIO_PLL2] = NULL;
+
+ micfil->clk_src[MICFIL_CLK_EXT3] = devm_clk_get(&pdev->dev, "clkext3");
+ if (IS_ERR(micfil->clk_src[MICFIL_CLK_EXT3]))
+ micfil->clk_src[MICFIL_CLK_EXT3] = NULL;
+
/* init regmap */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(&pdev->dev, res);
@@ -711,6 +2262,30 @@ static int fsl_micfil_probe(struct platform_device *pdev)
if (of_property_read_bool(np, "fsl,shared-interrupt"))
irqflag = IRQF_SHARED;
+ /* Digital Microphone interface voice activity detector event
+ * interrupt - IRQ 44
+ */
+ ret = devm_request_threaded_irq(&pdev->dev, micfil->irq[2],
+ hwvad_isr, voice_detected_fn,
+ irqflag, micfil->name, micfil);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim hwvad event irq %u\n",
+ micfil->irq[0]);
+ return ret;
+ }
+
+ /* Digital Microphone interface voice activity detector error
+ * interrupt - IRQ 45
+ */
+ ret = devm_request_irq(&pdev->dev, micfil->irq[3],
+ hwvad_err_isr, irqflag,
+ micfil->name, micfil);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim hwvad error irq %u\n",
+ micfil->irq[1]);
+ return ret;
+ }
+
/* Digital Microphone interface interrupt - IRQ 109 */
ret = devm_request_irq(&pdev->dev, micfil->irq[0],
micfil_isr, irqflag,
@@ -731,10 +2306,16 @@ static int fsl_micfil_probe(struct platform_device *pdev)
return ret;
}
+ micfil->slave_mode = false;
+
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;
+ /* set default rate to first value in available vad rates */
+ micfil->vad_rate_index = 0;
+ /* init HWVAD enable/disable spinlock */
+ spin_lock_init(&micfil->hwvad_lock);
platform_set_drvdata(pdev, micfil);
@@ -748,21 +2329,48 @@ static int fsl_micfil_probe(struct platform_device *pdev)
return ret;
}
- ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
- if (ret)
+ if (micfil->soc->imx)
+ ret = imx_pcm_platform_register(&pdev->dev);
+ else
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+
+ if (ret) {
dev_err(&pdev->dev, "failed to pcm register\n");
+ return ret;
+ }
- return ret;
+ /* create sysfs entry used to enable hwvad from userspace */
+ micfil->hwvad_kobject = kobject_create_and_add("hwvad",
+ &pdev->dev.kobj);
+ if (!micfil->hwvad_kobject)
+ return -ENOMEM;
+
+ ret = sysfs_create_file(micfil->hwvad_kobject,
+ &hwvad_en_attr.attr);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to create file for hwvad_enable\n");
+ kobject_put(micfil->hwvad_kobject);
+ return -ENOMEM;
+ }
+
+ return 0;
}
#ifdef CONFIG_PM
static int __maybe_unused fsl_micfil_runtime_suspend(struct device *dev)
{
struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ u32 state;
+
+ state = atomic_read(&micfil->hwvad_state);
+ if (state == MICFIL_HWVAD_ON)
+ return 0;
regcache_cache_only(micfil->regmap, true);
- clk_disable_unprepare(micfil->mclk);
+ /* Disable the clock only if the hwvad is not enabled */
+ if (state == MICFIL_HWVAD_OFF)
+ clk_disable_unprepare(micfil->mclk);
return 0;
}
@@ -771,6 +2379,17 @@ static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev)
{
struct fsl_micfil *micfil = dev_get_drvdata(dev);
int ret;
+ u32 state;
+
+ state = atomic_read(&micfil->hwvad_state);
+
+ /* enable mclk only if the hwvad is not enabled
+ * When hwvad is enabled, clock won't be disabled
+ * in suspend since hwvad and recording share the
+ * same clock
+ */
+ if (state == MICFIL_HWVAD_ON)
+ return 0;
ret = clk_prepare_enable(micfil->mclk);
if (ret < 0)
@@ -787,6 +2406,19 @@ static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev)
#ifdef CONFIG_PM_SLEEP
static int __maybe_unused fsl_micfil_suspend(struct device *dev)
{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+ u32 state;
+
+ state = atomic_read(&micfil->hwvad_state);
+
+ if (state == MICFIL_HWVAD_ON) {
+ dev_err(dev, "Disabling hwvad on suspend");
+ ret = disable_hwvad(dev, false);
+ if (ret)
+ dev_warn(dev, "Failed to disable hwvad");
+ }
+
pm_runtime_force_suspend(dev);
return 0;
@@ -794,8 +2426,20 @@ static int __maybe_unused fsl_micfil_suspend(struct device *dev)
static int __maybe_unused fsl_micfil_resume(struct device *dev)
{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+ u32 state;
+
pm_runtime_force_resume(dev);
+ state = atomic_read(&micfil->hwvad_state);
+ if (state == MICFIL_HWVAD_ON) {
+ dev_err(dev, "Enabling hwvad on resume");
+ ret = enable_hwvad(dev, false);
+ if (ret)
+ dev_warn(dev, "Failed to re-enable hwvad");
+ }
+
return 0;
}
#endif /* CONFIG_PM_SLEEP */
diff --git a/sound/soc/fsl/fsl_micfil.h b/sound/soc/fsl/fsl_micfil.h
index bac825c3135a..14ad08b6b150 100644
--- a/sound/soc/fsl/fsl_micfil.h
+++ b/sound/soc/fsl/fsl_micfil.h
@@ -258,6 +258,40 @@
#define MICFIL_VAD0_STAT_IF_MASK BIT(MICFIL_VAD0_STAT_IF_SHIFT)
#define MICFIL_VAD0_STAT_IF BIT(MICFIL_VAD0_STAT_IF_SHIFT)
+/* HWVAD Constants */
+#define MICFIL_HWVAD_ENVELOPE_MODE 0
+#define MICFIL_HWVAD_ENERGY_MODE 1
+#define MICFIL_HWVAD_INIT_FRAMES 10
+#define MICFIL_HWVAD_INPGAIN 0
+#define MICFIL_HWVAD_SGAIN 6
+#define MICFIL_HWVAD_NGAIN 3
+#define MICFIL_HWVAD_NFILADJ 0
+#define MICFIL_HWVAD_ZCDADJ (1 << (MICFIL_VAD0_ZCD_ZCDADJ_WIDTH - 2))
+#define MICFIL_HWVAD_ZCDTH 10 /* initial threshold value */
+#define MICFIL_HWVAD_ZCDOR 0
+#define MICFIL_HWVAD_ZCDAND 1
+#define MICFIL_HWVAD_ZCD_MANUAL 0
+#define MICFIL_HWVAD_ZCD_AUTO 1
+#define MICFIL_HWVAD_HPF_BYPASS 0
+#define MICFIL_HWVAD_HPF_1750HZ 1
+#define MICFIL_HWVAD_HPF_215HZ 2
+#define MICFIL_HWVAD_HPF_102HZ 3
+#define MICFIL_HWVAD_FRAMET_DEFAULT 10
+
+/* MICFIL DC Remover Control Register -- REG_MICFIL_DC_CTRL */
+#define MICFIL_DC_CTRL_SHIFT 0
+#define MICFIL_DC_CTRL_MASK 0xFFFF
+#define MICFIL_DC_CTRL_WIDTH 2
+#define MICFIL_DC_CHX_SHIFT(v) (2 * (v))
+#define MICFIL_DC_CHX_MASK(v) ((BIT(MICFIL_DC_CTRL_WIDTH) - 1) \
+ << MICFIL_DC_CHX_SHIFT(v))
+#define MICFIL_DC_MODE(v1, v2) (((v1) << MICFIL_DC_CHX_SHIFT(v2)) \
+ & MICFIL_DC_CHX_MASK(v2))
+#define MICFIL_DC_CUTOFF_21HZ 0
+#define MICFIL_DC_CUTOFF_83HZ 1
+#define MICFIL_DC_CUTOFF_152Hz 2
+#define MICFIL_DC_BYPASS 3
+
/* MICFIL Output Control Register */
#define MICFIL_OUTGAIN_CHX_SHIFT(v) (4 * (v))
@@ -273,11 +307,24 @@
#define FIFO_PTRWID 3
#define FIFO_LEN BIT(FIFO_PTRWID)
-#define MICFIL_IRQ_LINES 2
+#define MICFIL_IRQ_LINES 4
#define MICFIL_MAX_RETRY 25
-#define MICFIL_SLEEP_MIN 90000 /* in us */
-#define MICFIL_SLEEP_MAX 100000 /* in us */
+#define MICFIL_SLEEP 100 /* in ms */
#define MICFIL_DMA_MAXBURST_RX 6
#define MICFIL_CTRL2_OSR_DEFAULT (0 << MICFIL_CTRL2_CICOSR_SHIFT)
+#define MICFIL_DEFAULT_RATE 48000
+#define MICFIL_CLK_SRC_NUM 3
+#define MICFIL_CLK_AUTO 0
+
+/* clock source ids */
+#define MICFIL_AUDIO_PLL1 0
+#define MICFIL_AUDIO_PLL2 1
+#define MICFIL_CLK_EXT3 2
+
+/* States of micfil */
+#define MICFIL_HWVAD_OFF 0
+#define MICFIL_HWVAD_ON 1
+#define MICFIL_RECORDING_OFF 0
+#define MICFIL_RECORDING_ON 1
#endif /* _FSL_MICFIL_H */
diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.c b/sound/soc/fsl/fsl_rpmsg_i2s.c
new file mode 100644
index 000000000000..f392817dd3fc
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg_i2s.c
@@ -0,0 +1,425 @@
+/*
+ * Freescale ALSA SoC rpmsg i2s driver.
+ *
+ * Copyright 2017 NXP
+ *
+ * This program is free software, you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 2 of the License, or(at your
+ * option) any later version.
+ *
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/of_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_i2s.h"
+#include "imx-pcm.h"
+
+#define FSL_RPMSG_I2S_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\
+ SNDRV_PCM_RATE_48000)
+#define FSL_RPMSG_I2S_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static int i2s_send_message(struct i2s_rpmsg *msg,
+ struct i2s_info *info)
+{
+ int err = 0;
+
+ mutex_lock(&info->tx_lock);
+ if (!info->rpdev) {
+ dev_err(info->dev, "rpmsg channel not ready, m4 image ready?\n");
+ mutex_unlock(&info->tx_lock);
+ return -EINVAL;
+ }
+
+ dev_dbg(&info->rpdev->dev, "send cmd %d\n", msg->send_msg.header.cmd);
+
+ if (!(msg->send_msg.header.type == I2S_TYPE_C))
+ reinit_completion(&info->cmd_complete);
+
+ err = rpmsg_send(info->rpdev->ept, (void *)&msg->send_msg,
+ sizeof(struct i2s_rpmsg_s));
+ if (err) {
+ dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err);
+ mutex_unlock(&info->tx_lock);
+ return err;
+ }
+
+ if (!(msg->send_msg.header.type == I2S_TYPE_C)) {
+ /* wait response from rpmsg */
+ err = wait_for_completion_timeout(&info->cmd_complete,
+ msecs_to_jiffies(RPMSG_TIMEOUT));
+ if (!err) {
+ dev_err(&info->rpdev->dev,
+ "rpmsg_send cmd %d timeout!\n",
+ msg->send_msg.header.cmd);
+ mutex_unlock(&info->tx_lock);
+ return -ETIMEDOUT;
+ }
+ memcpy(&msg->recv_msg, &info->recv_msg,
+ sizeof(struct i2s_rpmsg_r));
+ memcpy(&info->rpmsg[msg->recv_msg.header.cmd].recv_msg,
+ &msg->recv_msg, sizeof(struct i2s_rpmsg_r));
+
+ /*
+ * 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->send_msg.header.cmd) {
+ case I2S_TX_TERMINATE:
+ info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0;
+ break;
+ case I2S_RX_TERMINATE:
+ info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0;
+ break;
+ default:
+ break;
+ }
+ }
+
+ dev_dbg(&info->rpdev->dev, "cmd:%d, resp %d\n",
+ msg->send_msg.header.cmd,
+ info->recv_msg.param.resp);
+ mutex_unlock(&info->tx_lock);
+
+ return 0;
+}
+
+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_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,
+};
+
+static struct snd_soc_dai_driver fsl_rpmsg_i2s_dai = {
+ .playback = {
+ .stream_name = "CPU-Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_RPMSG_I2S_FORMATS,
+ },
+ .capture = {
+ .stream_name = "CPU-Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_RPMSG_I2S_FORMATS,
+ },
+ .symmetric_rates = 1,
+ .symmetric_channels = 1,
+ .symmetric_samplebits = 1,
+ .ops = &fsl_rpmsg_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_component = {
+ .name = "fsl-rpmsg-i2s",
+};
+
+static const struct of_device_id fsl_rpmsg_i2s_ids[] = {
+ { .compatible = "fsl,imx7ulp-rpmsg-i2s"},
+ { .compatible = "fsl,imx8mq-rpmsg-i2s"},
+ { .compatible = "fsl,imx8qxp-rpmsg-i2s"},
+ { .compatible = "fsl,imx8qm-rpmsg-i2s"},
+ { .compatible = "fsl,imx8mn-rpmsg-i2s"},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_rpmsg_i2s_ids);
+
+static void rpmsg_i2s_work(struct work_struct *work)
+{
+ struct work_of_rpmsg *work_of_rpmsg;
+ struct i2s_info *i2s_info;
+ bool is_period_done = false;
+ unsigned long flags;
+ struct i2s_rpmsg msg;
+
+ work_of_rpmsg = container_of(work, struct work_of_rpmsg, work);
+ i2s_info = work_of_rpmsg->i2s_info;
+
+ spin_lock_irqsave(&i2s_info->lock[0], flags);
+ if (i2s_info->period_done_msg_enabled[0]) {
+ memcpy(&msg, &i2s_info->period_done_msg[0], sizeof(struct i2s_rpmsg_s));
+ i2s_info->period_done_msg_enabled[0] = false;
+ spin_unlock_irqrestore(&i2s_info->lock[0], flags);
+
+ i2s_send_message(&msg, i2s_info);
+ } else
+ spin_unlock_irqrestore(&i2s_info->lock[0], flags);
+
+ if (i2s_info->period_done_msg_enabled[1]) {
+ i2s_send_message(&i2s_info->period_done_msg[1], i2s_info);
+ i2s_info->period_done_msg_enabled[1] = false;
+ }
+
+ if (work_of_rpmsg->msg.send_msg.header.type == I2S_TYPE_C &&
+ (work_of_rpmsg->msg.send_msg.header.cmd == I2S_TX_PERIOD_DONE ||
+ work_of_rpmsg->msg.send_msg.header.cmd == I2S_RX_PERIOD_DONE))
+ is_period_done = true;
+
+ if (!is_period_done)
+ i2s_send_message(&work_of_rpmsg->msg, i2s_info);
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ i2s_info->work_read_index++;
+ i2s_info->work_read_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+}
+
+static int fsl_rpmsg_i2s_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+ struct i2s_info *i2s_info;
+ int audioindex = 0;
+ int ret;
+ int i;
+
+ rpmsg_i2s = devm_kzalloc(&pdev->dev, sizeof(struct fsl_rpmsg_i2s),
+ GFP_KERNEL);
+ if (!rpmsg_i2s)
+ return -ENOMEM;
+
+ rpmsg_i2s->pdev = pdev;
+ i2s_info = &rpmsg_i2s->i2s_info;
+
+ ret = of_property_read_u32(np, "fsl,audioindex", &audioindex);
+ if (ret)
+ audioindex = 0;
+
+ /* Setup work queue */
+ i2s_info->rpmsg_wq = alloc_ordered_workqueue("rpmsg_i2s", WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE);
+ if (i2s_info->rpmsg_wq == NULL) {
+ dev_err(&pdev->dev, "workqueue create failed\n");
+ return -ENOMEM;
+ }
+
+ i2s_info->work_write_index = 1;
+ i2s_info->send_message = i2s_send_message;
+
+ for (i = 0; i < WORK_MAX_NUM; i++) {
+ INIT_WORK(&i2s_info->work_list[i].work, rpmsg_i2s_work);
+ i2s_info->work_list[i].i2s_info = i2s_info;
+ }
+
+ for (i = 0; i < I2S_CMD_MAX_NUM ; i++) {
+ i2s_info->rpmsg[i].send_msg.header.cate = IMX_RPMSG_AUDIO;
+ i2s_info->rpmsg[i].send_msg.header.major = IMX_RMPSG_MAJOR;
+ i2s_info->rpmsg[i].send_msg.header.minor = IMX_RMPSG_MINOR;
+ i2s_info->rpmsg[i].send_msg.header.type = I2S_TYPE_A;
+ i2s_info->rpmsg[i].send_msg.param.audioindex = audioindex;
+ }
+
+ mutex_init(&i2s_info->tx_lock);
+ mutex_init(&i2s_info->i2c_lock);
+ spin_lock_init(&i2s_info->lock[0]);
+ spin_lock_init(&i2s_info->lock[1]);
+ spin_lock_init(&i2s_info->wq_lock);
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx7ulp-rpmsg-i2s")) {
+ rpmsg_i2s->codec_wm8960 = 1;
+ rpmsg_i2s->version = 1;
+ rpmsg_i2s->rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000;
+ rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE;
+ fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats;
+ fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats;
+ }
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx8qxp-rpmsg-i2s")) {
+ rpmsg_i2s->codec_wm8960 = 1 + (1 << 16);
+ rpmsg_i2s->version = 1;
+ rpmsg_i2s->codec_cs42888 = 1 + (2 << 16);
+ }
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx8qm-rpmsg-i2s")) {
+ rpmsg_i2s->codec_wm8960 = 0;
+ rpmsg_i2s->version = 1;
+ rpmsg_i2s->codec_cs42888 = 1 + (0 << 16);
+ }
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx8mq-rpmsg-i2s")) {
+ rpmsg_i2s->codec_dummy = 0;
+ rpmsg_i2s->codec_ak4497 = 1;
+ rpmsg_i2s->version = 2;
+ rpmsg_i2s->rates = SNDRV_PCM_RATE_KNOT;
+ rpmsg_i2s->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;
+
+ fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats;
+ fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats;
+ }
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx8mn-rpmsg-i2s")) {
+ rpmsg_i2s->codec_dummy = 1;
+ rpmsg_i2s->version = 2;
+ rpmsg_i2s->rates = SNDRV_PCM_RATE_KNOT;
+ rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE;
+
+ fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats;
+ fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "fsl,enable-lpa"))
+ rpmsg_i2s->enable_lpa = 1;
+
+ if (of_property_read_u32(np, "fsl,dma-buffer-size",
+ &i2s_info->prealloc_buffer_size))
+ i2s_info->prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE;
+
+ platform_set_drvdata(pdev, rpmsg_i2s);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
+ &fsl_rpmsg_i2s_dai, 1);
+ if (ret)
+ return ret;
+
+ return imx_rpmsg_platform_register(&pdev->dev);
+}
+
+static int fsl_rpmsg_i2s_remove(struct platform_device *pdev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = platform_get_drvdata(pdev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+
+ if (i2s_info->rpmsg_wq)
+ destroy_workqueue(i2s_info->rpmsg_wq);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_rpmsg_i2s_runtime_resume(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+
+ pm_qos_add_request(&rpmsg_i2s->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, 0);
+ return 0;
+}
+
+static int fsl_rpmsg_i2s_runtime_suspend(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+
+ pm_qos_remove_request(&rpmsg_i2s->pm_qos_req);
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_rpmsg_i2s_suspend(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg_tx;
+ struct i2s_rpmsg *rpmsg_rx;
+
+ flush_workqueue(i2s_info->rpmsg_wq);
+ rpmsg_tx = &i2s_info->rpmsg[I2S_TX_SUSPEND];
+ rpmsg_rx = &i2s_info->rpmsg[I2S_RX_SUSPEND];
+
+ rpmsg_tx->send_msg.header.cmd = I2S_TX_SUSPEND;
+ i2s_send_message(rpmsg_tx, i2s_info);
+
+ rpmsg_rx->send_msg.header.cmd = I2S_RX_SUSPEND;
+ i2s_send_message(rpmsg_rx, i2s_info);
+
+ return 0;
+}
+
+static int fsl_rpmsg_i2s_resume(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg_tx;
+ struct i2s_rpmsg *rpmsg_rx;
+
+ rpmsg_tx = &i2s_info->rpmsg[I2S_TX_RESUME];
+ rpmsg_rx = &i2s_info->rpmsg[I2S_RX_RESUME];
+
+ rpmsg_tx->send_msg.header.cmd = I2S_TX_RESUME;
+ i2s_send_message(rpmsg_tx, i2s_info);
+
+ rpmsg_rx->send_msg.header.cmd = I2S_RX_RESUME;
+ i2s_send_message(rpmsg_rx, i2s_info);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_rpmsg_i2s_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_rpmsg_i2s_runtime_suspend,
+ fsl_rpmsg_i2s_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_rpmsg_i2s_suspend, fsl_rpmsg_i2s_resume)
+};
+
+static struct platform_driver fsl_rpmsg_i2s_driver = {
+ .probe = fsl_rpmsg_i2s_probe,
+ .remove = fsl_rpmsg_i2s_remove,
+ .driver = {
+ .name = "fsl-rpmsg-i2s",
+ .pm = &fsl_rpmsg_i2s_pm_ops,
+ .of_match_table = fsl_rpmsg_i2s_ids,
+ },
+};
+
+module_platform_driver(fsl_rpmsg_i2s_driver);
+
+MODULE_DESCRIPTION("Freescale Soc rpmsg_i2s Interface");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@freescale.com>");
+MODULE_ALIAS("platform:fsl-rpmsg_i2s");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.h b/sound/soc/fsl/fsl_rpmsg_i2s.h
new file mode 100644
index 000000000000..e42986ca14ff
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg_i2s.h
@@ -0,0 +1,454 @@
+/*
+ * Copyright 2017 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ ******************************************************************************
+ * 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 an Audio 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 an Audio TX Instance. |
+ * | | | | | Same as above command | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x02 | Data[0]: Audio Device Index | Pause an Audio TX Instance. |
+ * | | | | | Same as above command | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x03 | Data[0]: Audio Device Index | Resume an Audio TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x04 | Data[0]: Audio Device Index | Terminate an Audio TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x05 | Data[0]: Audio Device Index | Close an Audio TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x06 | Data[0]: Audio Device Index | Set Parameters for an Audio TX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-22]: reserved | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x07 | Data[0]: Audio Device Index | Set Audio 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 an Audio TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x09 | Data[0]: Audio Device Index | Resume an Audio 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 an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0B | Data[0]: Audio Device Index | Start an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0C | Data[0]: Audio Device Index | Pause an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0D | Data[0]: Audio Device Index | Resume an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0E | Data[0]: Audio Device Index | Terminate an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0F | Data[0]: Audio Device Index | Close an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x10 | Data[0]: Audio Device Index | Set Parameters for an Audio RX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-22]: reserved | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x11 | Data[0]: Audio Device Index | Set Audio 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 an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x13 | Data[0]: Audio Device Index | Resume an Audio 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 to codec. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-14]: value | |
+ * | | | | | Data[15-22]: reserved | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x15 | Data[0]: Audio Device Index | Get register value from codec. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | 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 an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x01 | Data[0]: Audio Device Index | Reply for Start an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x02 | Data[0]: Audio Device Index | Reply for Pause an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x03 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x04 | Data[0]: Audio Device Index | Reply for Terminate an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x05 | Data[0]: Audio Device Index | Reply for Close an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x06 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio |
+ * | | | | | Data[1]: Return code | TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x07 | Data[0]: Audio Device Index | Reply for Set Audio TX Buffer. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x08 | Data[0]: Audio Device Index | Reply for Suspend an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x09 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0A | Data[0]: Audio Device Index | Reply for Open an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0B | Data[0]: Audio Device Index | Reply for Start an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0C | Data[0]: Audio Device Index | Reply for Pause an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0D | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0E | Data[0]: Audio Device Index | Reply for Terminate an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0F | Data[0]: Audio Device Index | Reply for Close an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x10 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio |
+ * | | | | | Data[1]: Return code | RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x11 | Data[0]: Audio Device Index | Reply for Set Audio RX Buffer. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x12 | Data[0]: Audio Device Index | Reply for Supend an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x13 | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x14 | Data[0]: Audio Device Index | Reply for Set codec register value. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x15 | Data[0]: Audio Device Index | Reply for Get codec register value. |
+ * | | | | | Data[1]: Return code | |
+ * | | | | | 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 __FSL_RPMSG_I2S_H
+#define __FSL_RPMSG_I2S_H
+
+#include <linux/pm_qos.h>
+#include <linux/imx_rpmsg.h>
+#include <linux/interrupt.h>
+#include <sound/dmaengine_pcm.h>
+
+#define RPMSG_TIMEOUT 1000
+
+#define I2S_TX_OPEN 0x0
+#define I2S_TX_START 0x1
+#define I2S_TX_PAUSE 0x2
+#define I2S_TX_RESTART 0x3
+#define I2S_TX_TERMINATE 0x4
+#define I2S_TX_CLOSE 0x5
+#define I2S_TX_HW_PARAM 0x6
+#define I2S_TX_BUFFER 0x7
+#define I2S_TX_SUSPEND 0x8
+#define I2S_TX_RESUME 0x9
+
+#define I2S_RX_OPEN 0xA
+#define I2S_RX_START 0xB
+#define I2S_RX_PAUSE 0xC
+#define I2S_RX_RESTART 0xD
+#define I2S_RX_TERMINATE 0xE
+#define I2S_RX_CLOSE 0xF
+#define I2S_RX_HW_PARAM 0x10
+#define I2S_RX_BUFFER 0x11
+#define I2S_RX_SUSPEND 0x12
+#define I2S_RX_RESUME 0x13
+#define SET_CODEC_VALUE 0x14
+#define GET_CODEC_VALUE 0x15
+#define I2S_TX_POINTER 0x16
+#define I2S_RX_POINTER 0x17
+
+#define I2S_TYPE_A_NUM 0x18
+
+#define WORK_MAX_NUM 0x30
+
+#define I2S_TX_PERIOD_DONE 0x0
+#define I2S_RX_PERIOD_DONE 0x1
+
+#define I2S_TYPE_C_NUM 0x2
+
+#define I2S_CMD_MAX_NUM (I2S_TYPE_A_NUM + I2S_TYPE_C_NUM)
+
+#define I2S_TYPE_A 0x0
+#define I2S_TYPE_B 0x1
+#define I2S_TYPE_C 0x2
+
+#define I2S_RESP_NONE 0x0
+#define I2S_RESP_NOT_ALLOWED 0x1
+#define I2S_RESP_SUCCESS 0x2
+#define I2S_RESP_FAILED 0x3
+
+#define RPMSG_S16_LE 0x0
+#define RPMSG_S24_LE 0x1
+#define RPMSG_S32_LE 0x2
+#define RPMSG_DSD_U16_LE 0x3
+#define RPMSG_DSD_U24_LE 0x4
+#define RPMSG_DSD_U32_LE 0x5
+
+#define RPMSG_CH_LEFT 0x0
+#define RPMSG_CH_RIGHT 0x1
+#define RPMSG_CH_STEREO 0x2
+
+struct i2s_param_s {
+ unsigned char audioindex;
+ unsigned char format;
+ unsigned char channels;
+ unsigned int rate;
+ unsigned int buffer_addr; /* Register for SET_CODEC_VALUE*/
+ unsigned int buffer_size; /* register value for SET_CODEC_VALUE*/
+ unsigned int period_size;
+ unsigned int buffer_tail;
+} __packed;
+
+struct i2s_param_r {
+ unsigned char audioindex;
+ unsigned char resp;
+ unsigned char reserved1[1];
+ unsigned int buffer_offset; /* the consumed offset of buffer*/
+ unsigned int reg_addr;
+ unsigned int reg_data;
+ unsigned char reserved2[4];
+ unsigned int buffer_tail;
+} __packed;
+
+/* struct of send message */
+struct i2s_rpmsg_s {
+ struct imx_rpmsg_head header;
+ struct i2s_param_s param;
+};
+
+/* struct of received message */
+struct i2s_rpmsg_r {
+ struct imx_rpmsg_head header;
+ struct i2s_param_r param;
+};
+
+struct i2s_rpmsg {
+ struct i2s_rpmsg_s send_msg;
+ struct i2s_rpmsg_r recv_msg;
+};
+
+struct work_of_rpmsg {
+ struct i2s_info *i2s_info;
+ /* sent msg for each work */
+ struct i2s_rpmsg msg;
+ struct work_struct work;
+};
+
+struct stream_timer {
+ struct timer_list timer;
+ struct snd_pcm_substream *substream;
+};
+
+typedef void (*dma_callback)(void *arg);
+struct i2s_info {
+ struct rpmsg_device *rpdev;
+ struct device *dev;
+ struct completion cmd_complete;
+
+ /* received msg (global) */
+ struct i2s_rpmsg_r recv_msg;
+
+ struct i2s_rpmsg rpmsg[I2S_CMD_MAX_NUM];
+ struct i2s_rpmsg period_done_msg[2];
+ bool period_done_msg_enabled[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];
+ int (*send_message)(struct i2s_rpmsg *msg, struct i2s_info *info);
+ dma_callback callback[2];
+ spinlock_t lock[2];
+ spinlock_t wq_lock;
+ struct mutex tx_lock;
+ struct mutex i2c_lock;
+ struct stream_timer stream_timer[2];
+ int prealloc_buffer_size;
+};
+
+struct fsl_rpmsg_i2s {
+ struct platform_device *pdev;
+ struct i2s_info i2s_info;
+ struct pm_qos_request pm_qos_req;
+ int codec_dummy;
+ int codec_wm8960;
+ int codec_cs42888;
+ int codec_ak4497;
+ int force_lpa;
+ int version;
+ int rates;
+ u64 formats;
+ int enable_lpa;
+};
+
+#define RPMSG_CODEC_DRV_NAME_WM8960 "rpmsg-audio-codec-wm8960"
+#define RPMSG_CODEC_DRV_NAME_CS42888 "rpmsg-audio-codec-cs42888"
+#define RPMSG_CODEC_DRV_NAME_AK4497 "rpmsg-audio-codec-ak4497"
+
+struct fsl_rpmsg_codec {
+ int audioindex;
+
+ /*property for wm8960*/
+ bool capless;
+ bool shared_lrclk;
+
+ /*property for cs42xx8*/
+
+ char name[32];
+ int num_adcs;
+};
+
+#endif /* __FSL_RPMSG_I2S_H */
diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c
index b517e4bc1b87..b8d7b08b9b0a 100644
--- a/sound/soc/fsl/fsl_sai.c
+++ b/sound/soc/fsl/fsl_sai.c
@@ -2,34 +2,103 @@
//
// Freescale ALSA SoC Digital Audio Interface (SAI) driver.
//
-// Copyright 2012-2015 Freescale Semiconductor, Inc.
+// Copyright 2012-2016 Freescale Semiconductor, Inc.
#include <linux/clk.h>
+#include <linux/clk-provider.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/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 <linux/pm_domain.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 <linux/pm_runtime.h>
+#include <linux/busfreq-imx.h>
+#include "fsl_dsd.h"
#include "fsl_sai.h"
#include "imx-pcm.h"
#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\
FSL_SAI_CSR_FEIE)
+#define FSL_SAI_VERID_0301 0x0301
+
+static struct fsl_sai_soc_data fsl_sai_vf610 = {
+ .imx = false,
+ /*dataline is mask, not index*/
+ .dataline = 0x1,
+ .fifos = 1,
+ .fifo_depth = 32,
+ .flags = 0,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx6sx = {
+ .imx = true,
+ .dataline = 0x1,
+ .fifos = 1,
+ .fifo_depth = 32,
+ .flags = 0,
+ .reg_offset = 0,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx6ul = {
+ .imx = true,
+ .dataline = 0x1,
+ .fifos = 1,
+ .fifo_depth = 32,
+ .flags = 0,
+ .reg_offset = 0,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx7ulp = {
+ .imx = true,
+ .dataline = 0x3,
+ .fifos = 2,
+ .fifo_depth = 16,
+ .flags = SAI_FLAG_PMQOS,
+ .reg_offset = 8,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx8mq = {
+ .imx = true,
+ .dataline = 0xff,
+ .fifos = 8,
+ .fifo_depth = 128,
+ .flags = 0,
+ .reg_offset = 8,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx8qm = {
+ .imx = true,
+ .dataline = 0xf,
+ .fifos = 1,
+ .fifo_depth = 64,
+ .flags = 0,
+ .reg_offset = 0,
+ .constrain_period_size = true,
+};
+
static const unsigned int fsl_sai_rates[] = {
8000, 11025, 12000, 16000, 22050,
24000, 32000, 44100, 48000, 64000,
- 88200, 96000, 176400, 192000
+ 88200, 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000, 1411200, 2822400,
};
static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = {
@@ -40,7 +109,7 @@ static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = {
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;
+ unsigned char offset = sai->soc->reg_offset;
struct device *dev = &sai->pdev->dev;
u32 flags, xcsr, mask;
bool irq_none = true;
@@ -53,7 +122,7 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid)
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);
+ regmap_read(sai->regmap, FSL_SAI_TCSR(offset), &xcsr);
flags = xcsr & mask;
if (flags)
@@ -83,11 +152,11 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid)
xcsr &= ~FSL_SAI_CSR_xF_MASK;
if (flags)
- regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), flags | xcsr);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), flags | xcsr);
irq_rx:
/* Rx IRQ */
- regmap_read(sai->regmap, FSL_SAI_RCSR(ofs), &xcsr);
+ regmap_read(sai->regmap, FSL_SAI_RCSR(offset), &xcsr);
flags = xcsr & mask;
if (flags)
@@ -117,7 +186,7 @@ irq_rx:
xcsr &= ~FSL_SAI_CSR_xF_MASK;
if (flags)
- regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), flags | xcsr);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), flags | xcsr);
out:
if (irq_none)
@@ -137,21 +206,11 @@ static int fsl_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask,
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, int fsl_dir)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
- unsigned int ofs = sai->soc_data->reg_offset;
+ unsigned char offset = sai->soc->reg_offset;
bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
u32 val_cr2 = 0;
@@ -172,20 +231,89 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai,
return -EINVAL;
}
- regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, offset),
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);
+ struct clk *p = sai->mclk_clk[clk_id], *pll = 0, *npll = 0;
+ u64 ratio = freq;
+ int ret;
+
+ while (p && sai->pll8k_clk && sai->pll11k_clk) {
+ struct clk *pp = clk_get_parent(p);
+
+ if (clk_is_match(pp, sai->pll8k_clk) ||
+ clk_is_match(pp, sai->pll11k_clk)) {
+ pll = pp;
+ break;
+ }
+ p = pp;
+ }
+
+ if (pll) {
+ npll = (do_div(ratio, 8000) ? sai->pll11k_clk : sai->pll8k_clk);
+ if (!clk_is_match(pll, npll)) {
+ if (sai->mclk_streams == 0) {
+ 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);
+ } else {
+ dev_err(dai->dev,
+ "PLL %s is in use by a running stream.\n",
+ __clk_get_name(pll));
+ return -EINVAL;
+ }
+ }
+ }
+
+ 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_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai);
+
+ sai->bitclk_ratio = ratio;
+ return 0;
+}
+
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) {
+ 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;
+ }
+
+ 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,
FSL_FMT_TRANSMITTER);
if (ret) {
@@ -205,13 +333,14 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
unsigned int fmt, int fsl_dir)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
- unsigned int ofs = sai->soc_data->reg_offset;
+ unsigned char offset = sai->soc->reg_offset;
bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
u32 val_cr2 = 0, val_cr4 = 0;
if (!sai->is_lsb_first)
val_cr4 |= FSL_SAI_CR4_MF;
+ sai->is_dsp_mode = false;
/* DAI mode */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
@@ -250,6 +379,11 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
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_dsp_mode = true;
+ break;
case SND_SOC_DAIFMT_RIGHT_J:
/* To be done */
default:
@@ -278,31 +412,31 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
return -EINVAL;
}
+ sai->slave_mode[tx] = false;
+
/* DAI clock master masks */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
- sai->is_slave_mode = false;
break;
case SND_SOC_DAIFMT_CBM_CFM:
- sai->is_slave_mode = true;
+ sai->slave_mode[tx] = true;
break;
case SND_SOC_DAIFMT_CBS_CFM:
val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
- sai->is_slave_mode = false;
break;
case SND_SOC_DAIFMT_CBM_CFS:
val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
- sai->is_slave_mode = true;
+ sai->slave_mode[tx] = true;
break;
default:
return -EINVAL;
}
- regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, offset),
FSL_SAI_CR2_BCP | FSL_SAI_CR2_BCD_MSTR, val_cr2);
- regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset),
FSL_SAI_CR4_MF | FSL_SAI_CR4_FSE |
FSL_SAI_CR4_FSP | FSL_SAI_CR4_FSD_MSTR, val_cr4);
@@ -311,14 +445,23 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
int ret;
+ if (sai->masterflag[FSL_FMT_TRANSMITTER])
+ fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) |
+ sai->masterflag[FSL_FMT_TRANSMITTER];
+
ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER);
if (ret) {
dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret);
return ret;
}
+ if (sai->masterflag[FSL_FMT_RECEIVER])
+ fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) |
+ sai->masterflag[FSL_FMT_RECEIVER];
+
ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER);
if (ret)
dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret);
@@ -326,19 +469,64 @@ static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
return ret;
}
+static int fsl_sai_check_ver(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+ unsigned char offset = sai->soc->reg_offset;
+ unsigned int val;
+
+ if (FSL_SAI_TCSR(offset) == FSL_SAI_VERID)
+ return 0;
+
+ if (sai->verid.loaded)
+ return 0;
+
+ sai->verid.loaded = true;
+ regmap_read(sai->regmap, FSL_SAI_VERID, &val);
+ dev_dbg(cpu_dai->dev, "VERID: 0x%016X\n", val);
+
+ sai->verid.id = (val & FSL_SAI_VER_ID_MASK) >> FSL_SAI_VER_ID_SHIFT;
+ sai->verid.extfifo_en = (val & FSL_SAI_VER_EFIFO_EN);
+ sai->verid.timestamp_en = (val & FSL_SAI_VER_TSTMP_EN);
+
+ regmap_read(sai->regmap, FSL_SAI_PARAM, &val);
+ dev_dbg(cpu_dai->dev, "PARAM: 0x%016X\n", val);
+
+ /* max slots per frame, power of 2 */
+ sai->param.spf = 1 <<
+ ((val & FSL_SAI_PAR_SPF_MASK) >> FSL_SAI_PAR_SPF_SHIFT);
+
+ /* words per fifo, power of 2 */
+ sai->param.wpf = 1 <<
+ ((val & FSL_SAI_PAR_WPF_MASK) >> FSL_SAI_PAR_WPF_SHIFT);
+
+ /* number of datalines implemented */
+ sai->param.dln = val & FSL_SAI_PAR_DLN_MASK;
+
+ dev_dbg(cpu_dai->dev,
+ "Version: 0x%08X, SPF: %u, WPF: %u, DLN: %u\n",
+ sai->verid.id, sai->param.spf, sai->param.wpf, sai->param.dln
+ );
+
+ return 0;
+}
+
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 ofs = sai->soc_data->reg_offset;
+ unsigned char offset = sai->soc->reg_offset;
unsigned long clk_rate;
- u32 savediv = 0, ratio, savesub = freq;
+ unsigned int reg = 0;
+ u32 ratio, savesub = freq, saveratio = 0, savediv = 0;
u32 id;
int ret = 0;
/* Don't apply to slave mode */
- if (sai->is_slave_mode)
+ if (sai->slave_mode[tx])
return 0;
+ fsl_sai_check_ver(dai);
+
for (id = 0; id < FSL_SAI_MCLK_MAX; id++) {
clk_rate = clk_get_rate(sai->mclk_clk[id]);
if (!clk_rate)
@@ -359,22 +547,21 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
"ratio %d for freq %dHz based on clock %ldHz\n",
ratio, freq, clk_rate);
- if (ratio % 2 == 0 && ratio >= 2 && ratio <= 512)
- ratio /= 2;
- else
- continue;
+ if ((ratio % 2 == 0 && ratio >= 2 && ratio <= 512) ||
+ (ratio == 1 && sai->verid.id >= FSL_SAI_VERID_0301)) {
- if (ret < savesub) {
- savediv = ratio;
- sai->mclk_id[tx] = id;
- savesub = ret;
- }
+ if (ret < savesub) {
+ saveratio = ratio;
+ sai->mclk_id[tx] = id;
+ savesub = ret;
+ }
- if (ret == 0)
- break;
+ if (ret == 0)
+ break;
+ }
}
- if (savediv == 0) {
+ if (saveratio == 0) {
dev_err(dai->dev, "failed to derive required %cx rate: %d\n",
tx ? 'T' : 'R', freq);
return -EINVAL;
@@ -390,24 +577,32 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
* 4) For Tx and Rx are both Synchronous with another SAI, we just
* ignore it.
*/
- if ((sai->synchronous[TX] && !sai->synchronous[RX]) ||
- (!tx && !sai->synchronous[RX])) {
- regmap_update_bits(sai->regmap, FSL_SAI_RCR2(ofs),
- FSL_SAI_CR2_MSEL_MASK,
- FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
- regmap_update_bits(sai->regmap, FSL_SAI_RCR2(ofs),
- FSL_SAI_CR2_DIV_MASK, savediv - 1);
- } else if ((sai->synchronous[RX] && !sai->synchronous[TX]) ||
- (tx && !sai->synchronous[TX])) {
- regmap_update_bits(sai->regmap, FSL_SAI_TCR2(ofs),
- FSL_SAI_CR2_MSEL_MASK,
- FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
- regmap_update_bits(sai->regmap, FSL_SAI_TCR2(ofs),
- FSL_SAI_CR2_DIV_MASK, savediv - 1);
+ if ((!tx || sai->synchronous[TX]) && !sai->synchronous[RX])
+ reg = FSL_SAI_RCR2(offset);
+ else if ((tx || sai->synchronous[RX]) && !sai->synchronous[TX])
+ reg = FSL_SAI_TCR2(offset);
+
+ if (reg) {
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_MSEL_MASK,
+ FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
+
+ savediv = (saveratio == 1 ? 0 : (saveratio >> 1) - 1);
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_DIV_MASK, savediv);
+
+ if (sai->verid.id >= FSL_SAI_VERID_0301) {
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_BYP,
+ (saveratio == 1 ? FSL_SAI_CR2_BYP : 0));
+ }
+ }
+
+ if (sai->verid.id >= FSL_SAI_VERID_0301) {
+ /* SAI is in master mode at this point, so enable MCLK */
+ regmap_update_bits(sai->regmap, FSL_SAI_MCTL,
+ FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN);
}
- dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n",
- sai->mclk_id[tx], savediv, savesub);
+ dev_dbg(dai->dev, "best fit: clock id=%d, ratio=%d, deviation=%d\n",
+ sai->mclk_id[tx], saveratio, savesub);
return 0;
}
@@ -417,30 +612,65 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
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;
+ unsigned char offset = sai->soc->reg_offset;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
unsigned int channels = params_channels(params);
u32 word_width = params_width(params);
+ u32 rate = params_rate(params);
u32 val_cr4 = 0, val_cr5 = 0;
u32 slots = (channels == 1) ? 2 : channels;
u32 slot_width = word_width;
- int ret;
+ u32 pins, bclk;
+ int ret, i, trce_mask = 0, dl_cfg_cnt, dl_cfg_idx = 0;
+ struct fsl_sai_dl_cfg *dl_cfg;
if (sai->slots)
slots = sai->slots;
+ pins = DIV_ROUND_UP(channels, slots);
+ sai->is_dsd = fsl_is_dsd(params);
+ if (sai->is_dsd) {
+ pins = channels;
+ dl_cfg = sai->dsd_dl_cfg;
+ dl_cfg_cnt = sai->dsd_dl_cfg_cnt;
+ } else {
+ dl_cfg = sai->pcm_dl_cfg;
+ dl_cfg_cnt = sai->pcm_dl_cfg_cnt;
+ }
+
+ for (i = 0; i < dl_cfg_cnt; i++) {
+ if (dl_cfg[i].pins == pins) {
+ dl_cfg_idx = i;
+ break;
+ }
+ }
+
+ if (dl_cfg_idx >= dl_cfg_cnt) {
+ dev_err(cpu_dai->dev, "fsl,dataline%s invalid or not provided.\n",
+ sai->is_dsd ? ",dsd" : "");
+ return -EINVAL;
+ }
+
if (sai->slot_width)
slot_width = sai->slot_width;
- if (!sai->is_slave_mode) {
- if (sai->bclk_ratio)
- ret = fsl_sai_set_bclk(cpu_dai, tx,
- sai->bclk_ratio *
- params_rate(params));
- else
- ret = fsl_sai_set_bclk(cpu_dai, tx,
- slots * slot_width *
- params_rate(params));
+ bclk = rate*(sai->bitclk_ratio ? sai->bitclk_ratio : slots * slot_width);
+
+ if (!IS_ERR_OR_NULL(sai->pinctrl)) {
+ sai->pins_state = fsl_get_pins_state(sai->pinctrl, params, 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->slave_mode[tx]) {
+ ret = fsl_sai_set_bclk(cpu_dai, tx, bclk);
if (ret)
return ret;
@@ -460,13 +690,18 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
val_cr5 |= FSL_SAI_CR5_WNW(slot_width);
val_cr5 |= FSL_SAI_CR5_W0W(slot_width);
- if (sai->is_lsb_first)
+ if (sai->is_lsb_first || sai->is_dsd)
val_cr5 |= FSL_SAI_CR5_FBT(0);
else
val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1);
val_cr4 |= FSL_SAI_CR4_FRSZ(slots);
+ /* Output Mode - data pins transmit 0 when slots are masked
+ * or channels are disabled
+ */
+ val_cr4 |= FSL_SAI_CR4_CHMOD;
+
/*
* For SAI master mode, when Tx(Rx) sync with Rx(Tx) clock, Rx(Tx) will
* generate bclk and frame clock for Tx(Rx), we should set RCR4(TCR4),
@@ -474,36 +709,78 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
* error.
*/
- if (!sai->is_slave_mode) {
+ if (!sai->slave_mode[tx]) {
if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) {
- regmap_update_bits(sai->regmap, FSL_SAI_TCR4(ofs),
- FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR4(offset),
+ FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
+ FSL_SAI_CR4_CHMOD_MASK,
val_cr4);
- regmap_update_bits(sai->regmap, FSL_SAI_TCR5(ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR5(offset),
FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
FSL_SAI_CR5_FBT_MASK, val_cr5);
- regmap_write(sai->regmap, FSL_SAI_TMR,
- ~0UL - ((1 << channels) - 1));
} else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) {
- regmap_update_bits(sai->regmap, FSL_SAI_RCR4(ofs),
- FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR4(offset),
+ FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
+ FSL_SAI_CR4_CHMOD_MASK,
val_cr4);
- regmap_update_bits(sai->regmap, FSL_SAI_RCR5(ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR5(offset),
FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
FSL_SAI_CR5_FBT_MASK, val_cr5);
- regmap_write(sai->regmap, FSL_SAI_RMR,
- ~0UL - ((1 << channels) - 1));
}
}
- regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
- FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
+ if (sai->soc->dataline != 0x1) {
+
+ if (dl_cfg[dl_cfg_idx].mask[tx] <= 1 || sai->is_multi_lane)
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset),
+ FSL_SAI_CR4_FCOMB_MASK, 0);
+ else
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset),
+ FSL_SAI_CR4_FCOMB_MASK, FSL_SAI_CR4_FCOMB_SOFT);
+
+ if (sai->is_multi_lane) {
+ if (tx) {
+ sai->dma_params_tx.maxburst =
+ FSL_SAI_MAXBURST_TX * pins;
+ sai->dma_params_tx.fifo_num = pins +
+ (dl_cfg[dl_cfg_idx].offset[tx] << 4);
+ } else {
+ sai->dma_params_rx.maxburst =
+ FSL_SAI_MAXBURST_RX * pins;
+ sai->dma_params_rx.fifo_num = pins +
+ (dl_cfg[dl_cfg_idx].offset[tx] << 4);
+ }
+ }
+
+ snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx,
+ &sai->dma_params_rx);
+ }
+
+ if (__sw_hweight8(dl_cfg[dl_cfg_idx].mask[tx] & 0xFF) < pins) {
+ dev_err(cpu_dai->dev, "channel not supported\n");
+ return -EINVAL;
+ }
+
+ /*find a proper tcre setting*/
+ for (i = 0; i < 8; i++) {
+ trce_mask = (1 << (i + 1)) - 1;
+ if (__sw_hweight8(dl_cfg[dl_cfg_idx].mask[tx] & trce_mask) == pins)
+ break;
+ }
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset),
+ FSL_SAI_CR3_TRCE_MASK,
+ FSL_SAI_CR3_TRCE((dl_cfg[dl_cfg_idx].mask[tx] & trce_mask)));
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset),
+ 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),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx, offset),
FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
FSL_SAI_CR5_FBT_MASK, val_cr5);
- regmap_write(sai->regmap, FSL_SAI_xMR(tx), ~0UL - ((1 << channels) - 1));
-
+ regmap_write(sai->regmap, FSL_SAI_xMR(tx),
+ ~0UL - ((1 << min(channels, slots)) - 1));
return 0;
}
@@ -511,9 +788,13 @@ 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);
+ unsigned char offset = sai->soc->reg_offset;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
- if (!sai->is_slave_mode &&
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset),
+ FSL_SAI_CR3_TRCE_MASK, 0);
+
+ if (!sai->slave_mode[tx] &&
sai->mclk_streams & BIT(substream->stream)) {
clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[tx]]);
sai->mclk_streams &= ~BIT(substream->stream);
@@ -527,19 +808,46 @@ 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;
-
+ unsigned char offset = sai->soc->reg_offset;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u8 channels = substream->runtime->channels;
+ u32 slots = (channels == 1) ? 2 : channels;
u32 xcsr, count = 100;
+ u32 pins;
+ int i = 0, j = 0, k = 0, dl_cfg_cnt, dl_cfg_idx = 0;
+ struct fsl_sai_dl_cfg *dl_cfg;
+
+ if (sai->slots)
+ slots = sai->slots;
+
+ pins = DIV_ROUND_UP(channels, slots);
+
+ if (sai->is_dsd) {
+ pins = channels;
+ dl_cfg = sai->dsd_dl_cfg;
+ dl_cfg_cnt = sai->dsd_dl_cfg_cnt;
+ } else {
+ dl_cfg = sai->pcm_dl_cfg;
+ dl_cfg_cnt = sai->pcm_dl_cfg_cnt;
+ }
+
+ for (i = 0; i < dl_cfg_cnt; i++) {
+ if (dl_cfg[i].pins == pins) {
+ dl_cfg_idx = i;
+ break;
+ }
+ }
+
+ i = 0;
/*
* 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,
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2(offset), FSL_SAI_CR2_SYNC,
+ sai->synchronous[TX] ? FSL_SAI_CR2_SYNC : 0);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR2(offset), FSL_SAI_CR2_SYNC,
sai->synchronous[RX] ? FSL_SAI_CR2_SYNC : 0);
/*
@@ -550,44 +858,63 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int 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),
+
+ while (tx && i < channels) {
+ if (dl_cfg[dl_cfg_idx].mask[tx] & (1 << j)) {
+ regmap_write(sai->regmap, FSL_SAI_TDR0 + j * 0x4, 0x0);
+ i++;
+ k++;
+ }
+ j++;
+
+ if (k%pins == 0)
+ j = 0;
+ }
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE);
- regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
- regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
+ FSL_SAI_CSR_SE, FSL_SAI_CSR_SE);
+ if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) {
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), offset),
FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+ } else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) {
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), offset),
+ FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+ }
- regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
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),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
FSL_SAI_CSR_FRDE, 0);
- regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
FSL_SAI_CSR_xIE_MASK, 0);
/* Check if the opposite FRDE is also disabled */
- regmap_read(sai->regmap, FSL_SAI_xCSR(!tx, ofs), &xcsr);
+ regmap_read(sai->regmap, FSL_SAI_xCSR(!tx, offset), &xcsr);
if (!(xcsr & FSL_SAI_CSR_FRDE)) {
/* Disable both directions and reset their FIFOs */
- regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR(offset),
FSL_SAI_CSR_TERE, 0);
- regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR(offset),
FSL_SAI_CSR_TERE, 0);
/* TERE will remain set till the end of current frame */
do {
udelay(10);
- regmap_read(sai->regmap,
- FSL_SAI_xCSR(tx, ofs), &xcsr);
+ regmap_read(sai->regmap, FSL_SAI_xCSR(tx, offset), &xcsr);
} while (--count && xcsr & FSL_SAI_CSR_TERE);
- regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR(offset),
FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
- regmap_update_bits(sai->regmap, FSL_SAI_RCSR(ofs),
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR(offset),
FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
/*
@@ -597,15 +924,15 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
* This is a hardware bug, and will be fix in the
* next sai version.
*/
- if (!sai->is_slave_mode) {
+ if (!sai->slave_mode[tx]) {
/* 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);
+ regmap_write(sai->regmap,
+ FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR);
+ regmap_write(sai->regmap,
+ FSL_SAI_RCSR(offset), 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_write(sai->regmap, FSL_SAI_TCSR(offset), 0);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0);
}
}
break;
@@ -620,19 +947,16 @@ 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);
- unsigned int ofs = sai->soc_data->reg_offset;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int ret;
- regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs),
- FSL_SAI_CR3_TRCE_MASK,
- FSL_SAI_CR3_TRCE);
+ if (sai->is_stream_opened[tx])
+ return -EBUSY;
+ else
+ sai->is_stream_opened[tx] = true;
- /*
- * EDMA controller needs period size to be a multiple of
- * tx/rx maxburst
- */
- if (sai->soc_data->use_edma)
+ /* EDMA engine needs periods of size multiple of tx/rx maxburst */
+ if (sai->soc->constrain_period_size)
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
tx ? sai->dma_params_tx.maxburst :
@@ -648,15 +972,14 @@ static void fsl_sai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
- unsigned int ofs = sai->soc_data->reg_offset;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
- regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs),
- FSL_SAI_CR3_TRCE_MASK, 0);
+ if (sai->is_stream_opened[tx])
+ sai->is_stream_opened[tx] = false;
}
static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
- .set_bclk_ratio = fsl_sai_set_dai_bclk_ratio,
+ .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,
@@ -670,20 +993,21 @@ static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
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;
+ unsigned char offset = sai->soc->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);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 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_write(sai->regmap, FSL_SAI_TCSR(offset), 0);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0);
- regmap_update_bits(sai->regmap, FSL_SAI_TCR1(ofs),
- FSL_SAI_CR1_RFW_MASK,
- sai->soc_data->fifo_depth - FSL_SAI_MAXBURST_TX);
- regmap_update_bits(sai->regmap, FSL_SAI_RCR1(ofs),
- FSL_SAI_CR1_RFW_MASK, FSL_SAI_MAXBURST_RX - 1);
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR1(offset),
+ sai->soc->fifo_depth - 1,
+ sai->soc->fifo_depth - FSL_SAI_MAXBURST_TX);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR1(offset),
+ sai->soc->fifo_depth - 1,
+ FSL_SAI_MAXBURST_RX - 1);
snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx,
&sai->dma_params_rx);
@@ -693,6 +1017,23 @@ static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai)
return 0;
}
+static int fsl_sai_dai_resume(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ 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(cpu_dai->dev,
+ "failed to set proper pins state: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
static struct snd_soc_dai_driver fsl_sai_dai = {
.probe = fsl_sai_dai_probe,
.playback = {
@@ -700,7 +1041,7 @@ static struct snd_soc_dai_driver fsl_sai_dai = {
.channels_min = 1,
.channels_max = 32,
.rate_min = 8000,
- .rate_max = 192000,
+ .rate_max = 2822400,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
@@ -709,10 +1050,11 @@ static struct snd_soc_dai_driver fsl_sai_dai = {
.channels_min = 1,
.channels_max = 32,
.rate_min = 8000,
- .rate_max = 192000,
+ .rate_max = 2822400,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
+ .resume = fsl_sai_dai_resume,
.ops = &fsl_sai_pcm_dai_ops,
};
@@ -720,7 +1062,7 @@ static const struct snd_soc_component_driver fsl_component = {
.name = "fsl-sai",
};
-static struct reg_default fsl_sai_reg_defaults_ofs0[] = {
+static struct reg_default fsl_sai_v2_reg_defaults[] = {
{FSL_SAI_TCR1(0), 0},
{FSL_SAI_TCR2(0), 0},
{FSL_SAI_TCR3(0), 0},
@@ -728,22 +1070,16 @@ static struct reg_default fsl_sai_reg_defaults_ofs0[] = {
{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_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},
+ {FSL_SAI_RMR, 0},
};
-static struct reg_default fsl_sai_reg_defaults_ofs8[] = {
+static struct reg_default fsl_sai_v3_reg_defaults[] = {
{FSL_SAI_TCR1(8), 0},
{FSL_SAI_TCR2(8), 0},
{FSL_SAI_TCR3(8), 0},
@@ -757,24 +1093,26 @@ static struct reg_default fsl_sai_reg_defaults_ofs8[] = {
{FSL_SAI_TDR5, 0},
{FSL_SAI_TDR6, 0},
{FSL_SAI_TDR7, 0},
- {FSL_SAI_TMR, 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_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;
+ unsigned char offset = sai->soc->reg_offset;
- if (reg >= FSL_SAI_TCSR(ofs) && reg <= FSL_SAI_TCR5(ofs))
+ if (reg >= FSL_SAI_TCSR(offset) && reg <= FSL_SAI_TCR5(offset))
return true;
- if (reg >= FSL_SAI_RCSR(ofs) && reg <= FSL_SAI_RCR5(ofs))
+ if (reg >= FSL_SAI_RCSR(offset) && reg <= FSL_SAI_RCR5(offset))
return true;
switch (reg) {
@@ -804,6 +1142,10 @@ static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg)
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:
return true;
default:
return false;
@@ -813,9 +1155,13 @@ static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg)
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;
+ unsigned char offset = sai->soc->reg_offset;
- if (reg == FSL_SAI_TCSR(ofs) || reg == FSL_SAI_RCSR(ofs))
+ if (reg == FSL_SAI_TCSR(offset) || reg == FSL_SAI_RCSR(offset))
+ return true;
+
+ if (sai->soc->reg_offset == 8 && (reg == FSL_SAI_VERID ||
+ reg == FSL_SAI_PARAM))
return true;
switch (reg) {
@@ -852,12 +1198,12 @@ static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg)
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;
+ unsigned char offset = sai->soc->reg_offset;
- if (reg >= FSL_SAI_TCSR(ofs) && reg <= FSL_SAI_TCR5(ofs))
+ if (reg >= FSL_SAI_TCSR(offset) && reg <= FSL_SAI_TCR5(offset))
return true;
- if (reg >= FSL_SAI_RCSR(ofs) && reg <= FSL_SAI_RCR5(ofs))
+ if (reg >= FSL_SAI_RCSR(offset) && reg <= FSL_SAI_RCR5(offset))
return true;
switch (reg) {
@@ -871,30 +1217,128 @@ static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg)
case FSL_SAI_TDR7:
case FSL_SAI_TMR:
case FSL_SAI_RMR:
+ case FSL_SAI_MCTL:
+ case FSL_SAI_MDIV:
return true;
default:
return false;
}
}
-static struct regmap_config fsl_sai_regmap_config = {
+static const struct regmap_config fsl_sai_v2_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),
+ .reg_defaults = fsl_sai_v2_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_sai_v2_reg_defaults),
+ .readable_reg = fsl_sai_readable_reg,
+ .volatile_reg = fsl_sai_volatile_reg,
+ .writeable_reg = fsl_sai_writeable_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct regmap_config fsl_sai_v3_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .max_register = FSL_SAI_MDIV,
+ .reg_defaults = fsl_sai_v3_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_sai_v3_reg_defaults),
.readable_reg = fsl_sai_readable_reg,
.volatile_reg = fsl_sai_volatile_reg,
.writeable_reg = fsl_sai_writeable_reg,
.cache_type = REGCACHE_FLAT,
};
+static const struct of_device_id fsl_sai_ids[] = {
+ { .compatible = "fsl,vf610-sai", .data = &fsl_sai_vf610 },
+ { .compatible = "fsl,imx6sx-sai", .data = &fsl_sai_imx6sx },
+ { .compatible = "fsl,imx6ul-sai", .data = &fsl_sai_imx6ul },
+ { .compatible = "fsl,imx7ulp-sai", .data = &fsl_sai_imx7ulp },
+ { .compatible = "fsl,imx8mq-sai", .data = &fsl_sai_imx8mq },
+ { .compatible = "fsl,imx8qm-sai", .data = &fsl_sai_imx8qm },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_sai_ids);
+
+static unsigned int fsl_sai_calc_dl_off(unsigned int* dl_mask)
+{
+ int fbidx, nbidx, offset;
+
+ fbidx = find_first_bit((const unsigned long *)dl_mask, 8);
+ nbidx = find_next_bit((const unsigned long *)dl_mask, 8, fbidx+1);
+ offset = nbidx - fbidx - 1;
+
+ return (offset < 0 || offset >= 7 ? 0 : offset);
+}
+
+static int fsl_sai_read_dlcfg(struct platform_device *pdev, char *pn,
+ struct fsl_sai_dl_cfg **rcfg, unsigned int soc_dl)
+{
+ int ret, elems, i, index, num_cfg;
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_sai_dl_cfg *cfg;
+ u32 rx, tx, pins;
+
+ *rcfg = NULL;
+
+ elems = of_property_count_u32_elems(np, pn);
+
+ /* consider default value "0 0x1 0x1" if property is missing */
+ if (elems <= 0)
+ elems = 3;
+
+ if (elems % 3) {
+ dev_err(&pdev->dev,
+ "Number of elements in %s must be divisible to 3.\n", pn);
+ return -EINVAL;
+ }
+
+ num_cfg = elems / 3;
+ cfg = devm_kzalloc(&pdev->dev, num_cfg * sizeof(*cfg), GFP_KERNEL);
+ if (cfg == NULL) {
+ dev_err(&pdev->dev, "Cannot allocate memory for %s.\n", pn);
+ return -ENOMEM;
+ }
+
+ for (i = 0, index = 0; i < num_cfg; i++) {
+ ret = of_property_read_u32_index(np, pn, index++, &pins);
+ if (ret)
+ pins = 0;
+
+ ret = of_property_read_u32_index(np, pn, index++, &rx);
+ if (ret)
+ rx = 1;
+
+ ret = of_property_read_u32_index(np, pn, index++, &tx);
+ if (ret)
+ tx = 1;
+
+ if ((rx & ~soc_dl) || (tx & ~soc_dl)) {
+ dev_err(&pdev->dev,
+ "%s: dataline cfg[%d] setting error, mask is 0x%x\n",
+ pn, i, soc_dl);
+ return -EINVAL;
+ }
+
+ cfg[i].pins = pins;
+ cfg[i].mask[0] = rx;
+ cfg[i].offset[0] = fsl_sai_calc_dl_off(&rx);
+ cfg[i].mask[1] = tx;
+ cfg[i].offset[1] = fsl_sai_calc_dl_off(&tx);
+ }
+
+ *rcfg = cfg;
+ return num_cfg;
+}
+
static int fsl_sai_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *of_id;
struct fsl_sai *sai;
struct regmap *gpr;
struct resource *res;
@@ -902,32 +1346,36 @@ static int fsl_sai_probe(struct platform_device *pdev)
char tmp[8];
int irq, ret, i;
int index;
+ struct regmap_config fsl_sai_regmap_config = fsl_sai_v2_regmap_config;
+ unsigned long irqflags = 0;
+ int num_domains = 0;
sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
if (!sai)
return -ENOMEM;
sai->pdev = pdev;
- sai->soc_data = of_device_get_match_data(&pdev->dev);
+
+ of_id = of_match_device(fsl_sai_ids, &pdev->dev);
+ if (!of_id || !of_id->data)
+ return -EINVAL;
sai->is_lsb_first = of_property_read_bool(np, "lsb-first");
+ sai->soc = of_id->data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, 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.num_reg_defaults =
- ARRAY_SIZE(fsl_sai_reg_defaults_ofs8);
- }
+ if (sai->soc->reg_offset == 8)
+ fsl_sai_regmap_config = fsl_sai_v3_regmap_config;
sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
"bus", base, &fsl_sai_regmap_config);
/* Compatible with old DTB cases */
- if (IS_ERR(sai->regmap))
+ if (IS_ERR(sai->regmap) && PTR_ERR(sai->regmap) != -EPROBE_DEFER)
sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
"sai", base, &fsl_sai_regmap_config);
if (IS_ERR(sai->regmap)) {
@@ -943,22 +1391,86 @@ static int fsl_sai_probe(struct platform_device *pdev)
sai->bus_clk = NULL;
}
- sai->mclk_clk[0] = sai->bus_clk;
- for (i = 1; i < FSL_SAI_MCLK_MAX; i++) {
+ for (i = 0; i < FSL_SAI_MCLK_MAX; i++) {
sprintf(tmp, "mclk%d", i);
sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp);
if (IS_ERR(sai->mclk_clk[i])) {
dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n",
- i + 1, PTR_ERR(sai->mclk_clk[i]));
+ i, PTR_ERR(sai->mclk_clk[i]));
sai->mclk_clk[i] = NULL;
}
}
+ num_domains = of_count_phandle_with_args(np, "power-domains",
+ "#power-domain-cells");
+ for (i = 0; i < num_domains; i++) {
+ struct device *pd_dev;
+ struct device_link *link;
+
+ pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, i);
+ if (IS_ERR(pd_dev))
+ return PTR_ERR(pd_dev);
+
+ link = device_link_add(&pdev->dev, pd_dev,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME |
+ DL_FLAG_RPM_ACTIVE);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ }
+
+ sai->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k");
+ if (IS_ERR(sai->pll8k_clk))
+ sai->pll8k_clk = NULL;
+
+ sai->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k");
+ if (IS_ERR(sai->pll11k_clk))
+ sai->pll11k_clk = NULL;
+
+ if (of_find_property(np, "fsl,sai-multi-lane", NULL))
+ sai->is_multi_lane = true;
+
+ /*dataline mask for rx and tx*/
+ ret = fsl_sai_read_dlcfg(pdev, "fsl,dataline", &sai->pcm_dl_cfg,
+ sai->soc->dataline);
+ if (ret < 0)
+ return ret;
+
+ sai->pcm_dl_cfg_cnt = ret;
+
+ ret = fsl_sai_read_dlcfg(pdev, "fsl,dataline,dsd", &sai->dsd_dl_cfg,
+ sai->soc->dataline);
+ if (ret < 0)
+ return ret;
+
+ sai->dsd_dl_cfg_cnt = ret;
+
+ if ((of_find_property(np, "fsl,i2s-xtor", NULL) != NULL) ||
+ (of_find_property(np, "fsl,txm-rxs", NULL) != NULL))
+ {
+ sai->masterflag[FSL_FMT_TRANSMITTER] = SND_SOC_DAIFMT_CBS_CFS;
+ sai->masterflag[FSL_FMT_RECEIVER] = SND_SOC_DAIFMT_CBM_CFM;
+ } else {
+ if (!of_property_read_u32(np, "fsl,txmasterflag",
+ &sai->masterflag[FSL_FMT_TRANSMITTER]))
+ sai->masterflag[FSL_FMT_TRANSMITTER] <<= 12;
+ if (!of_property_read_u32(np, "fsl,rxmasterflag",
+ &sai->masterflag[FSL_FMT_RECEIVER]))
+ sai->masterflag[FSL_FMT_RECEIVER] <<= 12;
+ }
+
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
+ }
+
+ /* SAI shared interrupt */
+ if (of_property_read_bool(np, "fsl,shared-interrupt"))
+ irqflags = IRQF_SHARED;
- ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, 0, np->name, sai);
+ ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, irqflags, np->name,
+ sai);
if (ret) {
dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
return ret;
@@ -1007,22 +1519,28 @@ static int fsl_sai_probe(struct platform_device *pdev)
MCLK_DIR(index));
}
+ sai->dma_params_rx.chan_name = "rx";
+ sai->dma_params_tx.chan_name = "tx";
sai->dma_params_rx.addr = res->start + FSL_SAI_RDR0;
sai->dma_params_tx.addr = 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(&pdev->dev);
+ regcache_cache_only(sai->regmap, true);
+
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
&fsl_sai_dai, 1);
if (ret)
return ret;
- if (sai->soc_data->use_imx_pcm)
- return imx_pcm_dma_init(pdev, IMX_SAI_DMABUF_SIZE);
+ if (sai->soc->imx)
+ return imx_pcm_platform_register(&pdev->dev);
else
return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
}
@@ -1034,57 +1552,15 @@ static int fsl_sai_remove(struct platform_device *pdev)
return 0;
}
-static const struct fsl_sai_soc_data fsl_sai_vf610_data = {
- .use_imx_pcm = false,
- .use_edma = false,
- .fifo_depth = 32,
- .reg_offset = 0,
-};
-
-static const struct fsl_sai_soc_data fsl_sai_imx6sx_data = {
- .use_imx_pcm = true,
- .use_edma = false,
- .fifo_depth = 32,
- .reg_offset = 0,
-};
-
-static const struct fsl_sai_soc_data fsl_sai_imx7ulp_data = {
- .use_imx_pcm = true,
- .use_edma = false,
- .fifo_depth = 16,
- .reg_offset = 8,
-};
-
-static const struct fsl_sai_soc_data fsl_sai_imx8mq_data = {
- .use_imx_pcm = true,
- .use_edma = false,
- .fifo_depth = 128,
- .reg_offset = 8,
-};
-
-static const struct fsl_sai_soc_data fsl_sai_imx8qm_data = {
- .use_imx_pcm = true,
- .use_edma = true,
- .fifo_depth = 64,
- .reg_offset = 0,
-};
-
-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 },
- { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, fsl_sai_ids);
-
#ifdef CONFIG_PM
static int fsl_sai_runtime_suspend(struct device *dev)
{
struct fsl_sai *sai = dev_get_drvdata(dev);
+ regcache_cache_only(sai->regmap, true);
+
+ release_bus_freq(BUS_FREQ_AUDIO);
+
if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE))
clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]);
@@ -1093,6 +1569,9 @@ static int fsl_sai_runtime_suspend(struct device *dev)
clk_disable_unprepare(sai->bus_clk);
+ if (sai->soc->flags & SAI_FLAG_PMQOS)
+ pm_qos_remove_request(&sai->pm_qos_req);
+
regcache_cache_only(sai->regmap, true);
regcache_mark_dirty(sai->regmap);
@@ -1102,7 +1581,7 @@ static int fsl_sai_runtime_suspend(struct device *dev)
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;
+ unsigned char offset = sai->soc->reg_offset;
int ret;
ret = clk_prepare_enable(sai->bus_clk);
@@ -1123,12 +1602,20 @@ static int fsl_sai_runtime_resume(struct device *dev)
goto disable_tx_clk;
}
+ request_bus_freq(BUS_FREQ_AUDIO);
+
+ if (sai->soc->flags & SAI_FLAG_PMQOS)
+ pm_qos_add_request(&sai->pm_qos_req,
+ PM_QOS_CPU_DMA_LATENCY, 0);
+
regcache_cache_only(sai->regmap, false);
- regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), FSL_SAI_CSR_SR);
- regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), FSL_SAI_CSR_SR);
+ regcache_mark_dirty(sai->regmap);
+
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 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);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), 0);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0);
ret = regcache_sync(sai->regmap);
if (ret)
diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h
index 76b15deea80c..20cc5745b0cc 100644
--- a/sound/soc/fsl/fsl_sai.h
+++ b/sound/soc/fsl/fsl_sai.h
@@ -1,78 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
- * Copyright 2012-2013 Freescale Semiconductor, Inc.
+ * Copyright 2012-2016 Freescale Semiconductor, Inc.
*/
#ifndef __FSL_SAI_H
#define __FSL_SAI_H
+#include <linux/pm_qos.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_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_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_VERID 0x00 /* SAI Version ID Register */
+#define FSL_SAI_PARAM 0x04 /* SAI Parameter Register */
+#define FSL_SAI_TCSR(offset) (0x00 + offset) /* SAI Transmit Control */
+#define FSL_SAI_TCR1(offset) (0x04 + offset) /* SAI Transmit Configuration 1 */
+#define FSL_SAI_TCR2(offset) (0x08 + offset) /* SAI Transmit Configuration 2 */
+#define FSL_SAI_TCR3(offset) (0x0c + offset) /* SAI Transmit Configuration 3 */
+#define FSL_SAI_TCR4(offset) (0x10 + offset) /* SAI Transmit Configuration 4 */
+#define FSL_SAI_TCR5(offset) (0x14 + offset) /* SAI Transmit Configuration 5 */
+#define FSL_SAI_TDR0 0x20 /* SAI Transmit Data */
+#define FSL_SAI_TDR1 0x24 /* SAI Transmit Data */
+#define FSL_SAI_TDR2 0x28 /* SAI Transmit Data */
+#define FSL_SAI_TDR3 0x2C /* SAI Transmit Data */
+#define FSL_SAI_TDR4 0x30 /* SAI Transmit Data */
+#define FSL_SAI_TDR5 0x34 /* SAI Transmit Data */
+#define FSL_SAI_TDR6 0x38 /* SAI Transmit Data */
+#define FSL_SAI_TDR7 0x3C /* SAI Transmit Data */
+#define FSL_SAI_TFR0 0x40 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR1 0x44 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR2 0x48 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR3 0x4C /* SAI Transmit FIFO */
+#define FSL_SAI_TFR4 0x50 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR5 0x54 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR6 0x58 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR7 0x5C /* SAI Transmit FIFO */
#define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */
-#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_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(offset) (0x80 + offset) /* SAI Receive Control */
+#define FSL_SAI_RCR1(offset) (0x84 + offset) /* SAI Receive Configuration 1 */
+#define FSL_SAI_RCR2(offset) (0x88 + offset) /* SAI Receive Configuration 2 */
+#define FSL_SAI_RCR3(offset) (0x8c + offset) /* SAI Receive Configuration 3 */
+#define FSL_SAI_RCR4(offset) (0x90 + offset) /* SAI Receive Configuration 4 */
+#define FSL_SAI_RCR5(offset) (0x94 + offset) /* SAI Receive Configuration 5 */
+#define FSL_SAI_RDR0 0xa0 /* SAI Receive Data */
+#define FSL_SAI_RDR1 0xa4 /* SAI Receive Data */
+#define FSL_SAI_RDR2 0xa8 /* SAI Receive Data */
+#define FSL_SAI_RDR3 0xac /* SAI Receive Data */
+#define FSL_SAI_RDR4 0xb0 /* SAI Receive Data */
+#define FSL_SAI_RDR5 0xb4 /* SAI Receive Data */
+#define FSL_SAI_RDR6 0xb8 /* SAI Receive Data */
+#define FSL_SAI_RDR7 0xbc /* SAI Receive Data */
+#define FSL_SAI_RFR0 0xc0 /* SAI Receive FIFO */
+#define FSL_SAI_RFR1 0xc4 /* SAI Receive FIFO */
+#define FSL_SAI_RFR2 0xc8 /* SAI Receive FIFO */
+#define FSL_SAI_RFR3 0xcc /* SAI Receive FIFO */
+#define FSL_SAI_RFR4 0xd0 /* SAI Receive FIFO */
+#define FSL_SAI_RFR5 0xd4 /* SAI Receive FIFO */
+#define FSL_SAI_RFR6 0xd8 /* SAI Receive FIFO */
+#define FSL_SAI_RFR7 0xdc /* SAI Receive FIFO */
#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_xDR(tx, ofs) (tx ? FSL_SAI_TDR(ofs) : FSL_SAI_RDR(ofs))
-#define FSL_SAI_xFR(tx, ofs) (tx ? FSL_SAI_TFR(ofs) : FSL_SAI_RFR(ofs))
+#define FSL_SAI_xCSR(tx, off) (tx ? FSL_SAI_TCSR(off) : FSL_SAI_RCSR(off))
+#define FSL_SAI_xCR1(tx, off) (tx ? FSL_SAI_TCR1(off) : FSL_SAI_RCR1(off))
+#define FSL_SAI_xCR2(tx, off) (tx ? FSL_SAI_TCR2(off) : FSL_SAI_RCR2(off))
+#define FSL_SAI_xCR3(tx, off) (tx ? FSL_SAI_TCR3(off) : FSL_SAI_RCR3(off))
+#define FSL_SAI_xCR4(tx, off) (tx ? FSL_SAI_TCR4(off) : FSL_SAI_RCR4(off))
+#define FSL_SAI_xCR5(tx, off) (tx ? FSL_SAI_TCR5(off) : FSL_SAI_RCR5(off))
#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_FR BIT(25)
#define FSL_SAI_CSR_SR BIT(24)
#define FSL_SAI_CSR_xF_SHIFT 16
@@ -106,19 +122,29 @@
#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 BIT(16)
-#define FSL_SAI_CR3_TRCE_MASK GENMASK(23, 16)
+#define FSL_SAI_CR3_TRCE_MASK (0xff << 16)
+#define FSL_SAI_CR3_TRCE(x) (x << 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 (1 << 5)
+#define FSL_SAI_CR4_CHMOD_MASK (1 << 5)
#define FSL_SAI_CR4_MF BIT(4)
#define FSL_SAI_CR4_FSE BIT(3)
#define FSL_SAI_CR4_FSP BIT(1)
@@ -132,6 +158,33 @@
#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_VER_ID_SHIFT 16
+#define FSL_SAI_VER_ID_MASK (0xFFFF << FSL_SAI_VER_ID_SHIFT)
+#define FSL_SAI_VER_EFIFO_EN BIT(0)
+#define FSL_SAI_VER_TSTMP_EN BIT(1)
+
+/* SAI PARAM Register */
+#define FSL_SAI_PAR_SPF_SHIFT 16
+#define FSL_SAI_PAR_SPF_MASK (0x0F << FSL_SAI_PAR_SPF_SHIFT)
+#define FSL_SAI_PAR_WPF_SHIFT 8
+#define FSL_SAI_PAR_WPF_MASK (0x0F << FSL_SAI_PAR_WPF_SHIFT)
+#define FSL_SAI_PAR_DLN_MASK (0x0F)
+
+/* SAI MCLK Divide Register */
+#define FSL_SAI_MDIV_MASK 0xFFFFF
+
/* SAI type */
#define FSL_SAI_DMA BIT(0)
#define FSL_SAI_USE_AC97 BIT(1)
@@ -155,11 +208,36 @@
#define FSL_SAI_MAXBURST_TX 6
#define FSL_SAI_MAXBURST_RX 6
+#define SAI_FLAG_PMQOS BIT(0)
+
struct fsl_sai_soc_data {
- bool use_imx_pcm;
- bool use_edma;
unsigned int fifo_depth;
- unsigned int reg_offset;
+ unsigned int fifos;
+ unsigned int dataline;
+ unsigned int flags;
+ unsigned char reg_offset;
+ bool imx;
+ /* True for EDMA because it needs period size multiple of maxburst */
+ bool constrain_period_size;
+};
+
+struct fsl_sai_verid {
+ u32 id;
+ bool timestamp_en;
+ bool extfifo_en;
+ bool loaded;
+};
+
+struct fsl_sai_param {
+ u32 spf; /* max slots per frame */
+ u32 wpf; /* words in fifo */
+ u32 dln; /* number of datalines implemented */
+};
+
+struct fsl_sai_dl_cfg {
+ unsigned int pins;
+ unsigned int mask[2];
+ unsigned int offset[2];
};
struct fsl_sai {
@@ -167,21 +245,39 @@ struct fsl_sai {
struct regmap *regmap;
struct clk *bus_clk;
struct clk *mclk_clk[FSL_SAI_MCLK_MAX];
+ struct clk *pll8k_clk;
+ struct clk *pll11k_clk;
- bool is_slave_mode;
+ bool slave_mode[2];
bool is_lsb_first;
bool is_dsp_mode;
+ bool is_multi_lane;
bool synchronous[2];
+ bool is_stream_opened[2];
+ bool is_dsd;
+
+ int pcm_dl_cfg_cnt;
+ int dsd_dl_cfg_cnt;
+ struct fsl_sai_dl_cfg *pcm_dl_cfg;
+ struct fsl_sai_dl_cfg *dsd_dl_cfg;
+
+ unsigned int masterflag[2];
unsigned int mclk_id[2];
unsigned int mclk_streams;
unsigned int slots;
unsigned int slot_width;
- unsigned int bclk_ratio;
+ unsigned int bitclk_ratio;
- const struct fsl_sai_soc_data *soc_data;
struct snd_dmaengine_dai_dma_data dma_params_rx;
struct snd_dmaengine_dai_dma_data dma_params_tx;
+ const struct fsl_sai_soc_data *soc;
+ struct pm_qos_request pm_qos_req;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pins_state;
+
+ struct fsl_sai_verid verid;
+ struct fsl_sai_param param;
};
#define TX 1
diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c
index 7858a5499ac5..08d75e6388ca 100644
--- a/sound/soc/fsl/fsl_spdif.c
+++ b/sound/soc/fsl/fsl_spdif.c
@@ -2,7 +2,7 @@
//
// Freescale S/PDIF ALSA SoC Digital Audio Interface (DAI) driver
//
-// Copyright (C) 2013 Freescale Semiconductor, Inc.
+// Copyright (C) 2013-2016 Freescale Semiconductor, Inc.
//
// Based on stmp3xxx_spdif_dai.c
// Vladimir Barinov <vbarinov@embeddedalley.com>
@@ -11,11 +11,15 @@
#include <linux/bitrev.h>
#include <linux/clk.h>
+#include <linux/clk-provider.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 <linux/pm_domain.h>
+#include <linux/busfreq-imx.h>
#include <sound/asoundef.h>
#include <sound/dmaengine_pcm.h>
@@ -42,6 +46,16 @@ static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb };
#define DEFAULT_RXCLK_SRC 1
+struct fsl_spdif_soc_data {
+ bool imx;
+ bool constrain_period_size;
+ u32 tx_burst;
+ u32 rx_burst;
+ u32 interrupts;
+ u64 tx_formats;
+ u64 rx_rates;
+};
+
/*
* SPDIF control structure
* Defines channel status, subcode and Q sub
@@ -99,15 +113,58 @@ struct fsl_spdif_priv {
u16 sysclk_df[SPDIF_TXRATE_MAX];
u8 txclk_src[SPDIF_TXRATE_MAX];
u8 rxclk_src;
- struct clk *txclk[SPDIF_TXRATE_MAX];
+ struct clk *txclk[STC_TXCLK_SRC_MAX];
struct clk *rxclk;
struct clk *coreclk;
struct clk *sysclk;
struct clk *spbaclk;
+ const struct fsl_spdif_soc_data *soc;
struct snd_dmaengine_dai_dma_data dma_params_tx;
struct snd_dmaengine_dai_dma_data dma_params_rx;
/* regcache for SRPC */
u32 regcache_srpc;
+ struct clk *pll8k_clk;
+ struct clk *pll11k_clk;
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_vf610 = {
+ .imx = false,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .interrupts = 1,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+ .rx_rates = FSL_SPDIF_RATES_CAPTURE,
+ .constrain_period_size = false,
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx35 = {
+ .imx = true,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .interrupts = 1,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+ .rx_rates = FSL_SPDIF_RATES_CAPTURE,
+ .constrain_period_size = false,
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx8qm = {
+ .imx = true,
+ .tx_burst = 2,
+ .rx_burst = 2,
+ .interrupts = 2,
+ .tx_formats = SNDRV_PCM_FMTBIT_S24_LE,
+ .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000),
+ .constrain_period_size = true,
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx8mm = {
+ .imx = true,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .interrupts = 1,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+ .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000),
+ .constrain_period_size = false,
};
/* DPLL locked and lock loss interrupt handler */
@@ -378,7 +435,6 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream,
u32 stc, mask, rate;
u16 sysclk_df;
u8 clk, txclk_df;
- int ret;
switch (sample_rate) {
case 32000:
@@ -420,23 +476,10 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream,
sysclk_df = spdif_priv->sysclk_df[rate];
- /* Don't mess up the clocks from other modules */
- if (clk != STC_TXCLK_SPDIF_ROOT)
- goto clk_set_bypass;
-
- /* The S/PDIF block needs a clock of 64 * fs * txclk_df */
- ret = clk_set_rate(spdif_priv->txclk[rate],
- 64 * sample_rate * txclk_df);
- if (ret) {
- dev_err(&pdev->dev, "failed to set tx clock rate\n");
- return ret;
- }
-
-clk_set_bypass:
dev_dbg(&pdev->dev, "expected clock rate = %d\n",
(64 * sample_rate * txclk_df * sysclk_df));
dev_dbg(&pdev->dev, "actual clock rate = %ld\n",
- clk_get_rate(spdif_priv->txclk[rate]));
+ clk_get_rate(spdif_priv->txclk[clk]));
/* set fs field in consumer channel status */
spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs);
@@ -462,25 +505,10 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream,
struct platform_device *pdev = spdif_priv->pdev;
struct regmap *regmap = spdif_priv->regmap;
u32 scr, mask;
- int i;
int ret;
/* Reset module and interrupts only for first initialization */
if (!cpu_dai->active) {
- ret = clk_prepare_enable(spdif_priv->coreclk);
- if (ret) {
- dev_err(&pdev->dev, "failed to enable core clock\n");
- return ret;
- }
-
- if (!IS_ERR(spdif_priv->spbaclk)) {
- ret = clk_prepare_enable(spdif_priv->spbaclk);
- if (ret) {
- dev_err(&pdev->dev, "failed to enable spba clock\n");
- goto err_spbaclk;
- }
- }
-
ret = spdif_softreset(spdif_priv);
if (ret) {
dev_err(&pdev->dev, "failed to soft reset\n");
@@ -498,35 +526,30 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream,
mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK |
SCR_TXSEL_MASK | SCR_USRC_SEL_MASK |
SCR_TXFIFO_FSEL_MASK;
- for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
- ret = clk_prepare_enable(spdif_priv->txclk[i]);
- if (ret)
- goto disable_txclk;
- }
} else {
scr = SCR_RXFIFO_FSEL_IF8 | SCR_RXFIFO_AUTOSYNC;
mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK|
SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK;
- ret = clk_prepare_enable(spdif_priv->rxclk);
- if (ret)
- goto err;
}
regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr);
/* Power up SPDIF module */
regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, 0);
+ if (spdif_priv->soc->constrain_period_size) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ spdif_priv->dma_params_tx.maxburst);
+ else
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ spdif_priv->dma_params_rx.maxburst);
+ }
+
return 0;
-disable_txclk:
- for (i--; i >= 0; i--)
- clk_disable_unprepare(spdif_priv->txclk[i]);
err:
- if (!IS_ERR(spdif_priv->spbaclk))
- clk_disable_unprepare(spdif_priv->spbaclk);
-err_spbaclk:
- clk_disable_unprepare(spdif_priv->coreclk);
-
return ret;
}
@@ -536,20 +559,17 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
struct regmap *regmap = spdif_priv->regmap;
- u32 scr, mask, i;
+ 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;
- for (i = 0; i < SPDIF_TXRATE_MAX; i++)
- clk_disable_unprepare(spdif_priv->txclk[i]);
} else {
scr = SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO;
mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK|
SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK;
- clk_disable_unprepare(spdif_priv->rxclk);
}
regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr);
@@ -558,9 +578,6 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream,
spdif_intr_status_clear(spdif_priv);
regmap_update_bits(regmap, REG_SPDIF_SCR,
SCR_LOW_POWER, SCR_LOW_POWER);
- if (!IS_ERR(spdif_priv->spbaclk))
- clk_disable_unprepare(spdif_priv->spbaclk);
- clk_disable_unprepare(spdif_priv->coreclk);
}
}
@@ -623,14 +640,178 @@ static int fsl_spdif_trigger(struct snd_pcm_substream *substream,
return 0;
}
+static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
+ struct clk *clk, u64 savesub,
+ enum spdif_txrate index, bool round)
+{
+ static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
+ bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
+ u64 rate_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_actual = clk_get_rate(clk);
+
+ arate = rate_actual / 64;
+ arate /= txclk_df * sysclk_df;
+
+ if (arate == rate[index]) {
+ /* We are lucky */
+ savesub = 0;
+ spdif_priv->txclk_df[index] = txclk_df;
+ spdif_priv->sysclk_df[index] = sysclk_df;
+ spdif_priv->txrate[index] = arate;
+ goto out;
+ } else if (arate / rate[index] == 1) {
+ /* A little bigger than expect */
+ sub = (u64)(arate - rate[index]) * 100000;
+ do_div(sub, rate[index]);
+ if (sub >= savesub)
+ continue;
+ savesub = sub;
+ spdif_priv->txclk_df[index] = txclk_df;
+ spdif_priv->sysclk_df[index] = sysclk_df;
+ spdif_priv->txrate[index] = arate;
+ } else if (rate[index] / arate == 1) {
+ /* A little smaller than expect */
+ sub = (u64)(rate[index] - arate) * 100000;
+ do_div(sub, rate[index]);
+ if (sub >= savesub)
+ continue;
+ savesub = sub;
+ spdif_priv->txclk_df[index] = txclk_df;
+ spdif_priv->sysclk_df[index] = sysclk_df;
+ spdif_priv->txrate[index] = arate;
+ }
+ }
+ }
+
+out:
+ return savesub;
+}
+
+static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv,
+ enum spdif_txrate index)
+{
+ static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
+ struct platform_device *pdev = spdif_priv->pdev;
+ struct device *dev = &pdev->dev;
+ u64 savesub = 100000, ret;
+ struct clk *clk;
+ 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,
+ i == STC_TXCLK_SPDIF_ROOT);
+ 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(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
+ spdif_priv->txclk_src[index], rate[index]);
+ dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n",
+ spdif_priv->txclk_df[index], rate[index]);
+ if (clk_is_match(spdif_priv->txclk[spdif_priv->txclk_src[index]], spdif_priv->sysclk))
+ dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n",
+ spdif_priv->sysclk_df[index], rate[index]);
+ dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n",
+ rate[index], spdif_priv->txrate[index]);
+
+ return 0;
+}
+
+static int fsl_spdif_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct fsl_spdif_priv *data = snd_soc_dai_get_drvdata(cpu_dai);
+ struct platform_device *pdev = data->pdev;
+ struct device *dev = &pdev->dev;
+ struct clk *clk, *p, *pll = 0, *npll = 0;
+ u64 ratio = freq;
+ int ret, i;
+ bool reparent = false;
+
+ if (dir != SND_SOC_CLOCK_OUT || freq == 0 || clk_id != STC_TXCLK_SPDIF_ROOT)
+ return 0;
+
+ if (data->pll8k_clk == NULL || data->pll11k_clk == NULL)
+ return 0;
+
+ clk = data->txclk[clk_id];
+ if (IS_ERR_OR_NULL(clk)) {
+ dev_err(dev, "no rxtx%d clock in devicetree\n", clk_id);
+ return PTR_ERR(clk);
+ }
+
+ p = clk;
+ while (p && data->pll8k_clk && data->pll11k_clk) {
+ struct clk *pp = clk_get_parent(p);
+
+ if (clk_is_match(pp, data->pll8k_clk) ||
+ clk_is_match(pp, data->pll11k_clk)) {
+ pll = pp;
+ break;
+ }
+ p = pp;
+ }
+
+ npll = (do_div(ratio, 8000) ? data->pll11k_clk : data->pll8k_clk);
+ reparent = (pll && !clk_is_match(pll, npll));
+
+ clk_disable_unprepare(clk);
+ if (reparent) {
+ ret = clk_set_parent(p, npll);
+ if (ret < 0)
+ dev_warn(cpu_dai->dev, "failed to set parent %s: %d\n",
+ __clk_get_name(npll), ret);
+ }
+
+ ret = clk_set_rate(clk, freq);
+ if (ret < 0)
+ dev_warn(cpu_dai->dev, "failed to set clock rate (%u): %d\n",
+ freq, ret);
+ clk_prepare_enable(clk);
+
+ for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
+ ret = fsl_spdif_probe_txclk(data, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const struct snd_soc_dai_ops fsl_spdif_dai_ops = {
.startup = fsl_spdif_startup,
+ .set_sysclk = fsl_spdif_set_dai_sysclk,
.hw_params = fsl_spdif_hw_params,
.trigger = fsl_spdif_trigger,
.shutdown = fsl_spdif_shutdown,
};
-
/*
* FSL SPDIF IEC958 controller(mixer) functions
*
@@ -769,19 +950,23 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol,
}
/* Valid bit information */
-static int fsl_spdif_vbit_info(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
+/* 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)
{
- uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
- uinfo->count = 1;
- uinfo->value.integer.min = 0;
- uinfo->value.integer.max = 1;
+ 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;
}
-/* Get valid good bit from interrupt status register */
-static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol,
+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);
@@ -789,9 +974,56 @@ static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol,
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);
+ 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;
}
@@ -863,18 +1095,6 @@ static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol,
return 0;
}
-/* User bit sync mode info */
-static int fsl_spdif_usync_info(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
-{
- uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
- uinfo->count = 1;
- uinfo->value.integer.min = 0;
- uinfo->value.integer.max = 1;
-
- return 0;
-}
-
/*
* User bit sync mode:
* 1 CD User channel subcode
@@ -953,11 +1173,21 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = {
/* Valid bit error controller */
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
- .name = "IEC958 V-Bit Errors",
+ .name = "IEC958 Rx V-Bit Errors",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
- .info = fsl_spdif_vbit_info,
- .get = fsl_spdif_vbit_get,
+ .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 */
{
@@ -975,10 +1205,20 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = {
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_WRITE |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
- .info = fsl_spdif_usync_info,
+ .info = snd_ctl_boolean_mono_info,
.get = fsl_spdif_usync_get,
.put = fsl_spdif_usync_put,
},
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Rx Raw Capture Mode 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_rx_rcm_get,
+ .put = fsl_spdif_rx_rcm_put,
+ },
};
static int fsl_spdif_dai_probe(struct snd_soc_dai *dai)
@@ -1103,114 +1343,14 @@ static const struct regmap_config fsl_spdif_regmap_config = {
.cache_type = REGCACHE_FLAT,
};
-static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
- struct clk *clk, u64 savesub,
- enum spdif_txrate index, bool round)
-{
- static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
- bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
- u64 rate_ideal, rate_actual, sub;
- u32 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, 96000, 192000 };
- struct platform_device *pdev = spdif_priv->pdev;
- struct device *dev = &pdev->dev;
- u64 savesub = 100000, ret;
- struct clk *clk;
- char tmp[16];
- int i;
-
- for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
- sprintf(tmp, "rxtx%d", i);
- clk = devm_clk_get(&pdev->dev, tmp);
- if (IS_ERR(clk)) {
- dev_err(dev, "no rxtx%d clock in devicetree\n", i);
- return PTR_ERR(clk);
- }
- if (!clk_get_rate(clk))
- continue;
-
- ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index,
- i == STC_TXCLK_SPDIF_ROOT);
- if (savesub == ret)
- continue;
-
- savesub = ret;
- spdif_priv->txclk[index] = clk;
- spdif_priv->txclk_src[index] = i;
-
- /* To quick catch a divisor, we allow a 0.1% deviation */
- if (savesub < 100)
- break;
- }
-
- dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
- spdif_priv->txclk_src[index], rate[index]);
- dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n",
- spdif_priv->txclk_df[index], rate[index]);
- if (clk_is_match(spdif_priv->txclk[index], spdif_priv->sysclk))
- dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n",
- spdif_priv->sysclk_df[index], rate[index]);
- dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n",
- rate[index], spdif_priv->txrate[index]);
-
- return 0;
-}
+static const struct of_device_id fsl_spdif_dt_ids[] = {
+ { .compatible = "fsl,imx8mm-spdif", .data = &fsl_spdif_imx8mm, },
+ { .compatible = "fsl,imx8qm-spdif", .data = &fsl_spdif_imx8qm, },
+ { .compatible = "fsl,imx35-spdif", .data = &fsl_spdif_imx35, },
+ { .compatible = "fsl,vf610-spdif", .data = &fsl_spdif_vf610, },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids);
static int fsl_spdif_probe(struct platform_device *pdev)
{
@@ -1218,8 +1358,11 @@ static int fsl_spdif_probe(struct platform_device *pdev)
struct fsl_spdif_priv *spdif_priv;
struct spdif_mixer_control *ctrl;
struct resource *res;
+ const struct of_device_id *of_id;
void __iomem *regs;
int irq, ret, i;
+ char tmp[16];
+ int num_domains = 0;
if (!np)
return -ENODEV;
@@ -1230,9 +1373,19 @@ static int fsl_spdif_probe(struct platform_device *pdev)
spdif_priv->pdev = pdev;
+ of_id = of_match_device(fsl_spdif_dt_ids, &pdev->dev);
+ if (!of_id || !of_id->data)
+ return -EINVAL;
+
+ spdif_priv->soc = of_id->data;
+
/* 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;
+ spdif_priv->cpu_dai_drv.capture.rates =
+ spdif_priv->soc->rx_rates;
/* Get the addresses and IRQ */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -1248,8 +1401,10 @@ static int fsl_spdif_probe(struct platform_device *pdev)
}
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
+ }
ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0,
dev_name(&pdev->dev), spdif_priv);
@@ -1258,8 +1413,50 @@ static int fsl_spdif_probe(struct platform_device *pdev)
return ret;
}
+ if (spdif_priv->soc->interrupts > 1) {
+ irq = platform_get_irq(pdev, 1);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+ return irq;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0,
+ dev_name(&pdev->dev), spdif_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "could not claim irq %u\n", irq);
+ return ret;
+ }
+ }
+
+ num_domains = of_count_phandle_with_args(np, "power-domains",
+ "#power-domain-cells");
+ for (i = 0; i < num_domains; i++) {
+ struct device *pd_dev;
+ struct device_link *link;
+
+ pd_dev = dev_pm_domain_attach_by_id(&pdev->dev, i);
+ if (IS_ERR(pd_dev))
+ return PTR_ERR(pd_dev);
+
+ link = device_link_add(&pdev->dev, pd_dev,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME |
+ DL_FLAG_RPM_ACTIVE);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ }
+
+ 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 = devm_clk_get(&pdev->dev, "rxtx5");
+ 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);
@@ -1277,13 +1474,21 @@ static int fsl_spdif_probe(struct platform_device *pdev)
dev_warn(&pdev->dev, "no spba clock in devicetree\n");
/* Select clock source for rx/tx clock */
- spdif_priv->rxclk = devm_clk_get(&pdev->dev, "rxtx1");
+ 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;
+ spdif_priv->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k");
+ if (IS_ERR(spdif_priv->pll8k_clk))
+ spdif_priv->pll8k_clk = NULL;
+
+ spdif_priv->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k");
+ if (IS_ERR(spdif_priv->pll11k_clk))
+ spdif_priv->pll11k_clk = NULL;
+
for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
ret = fsl_spdif_probe_txclk(spdif_priv, i);
if (ret)
@@ -1304,11 +1509,17 @@ static int fsl_spdif_probe(struct platform_device *pdev)
spdif_priv->dpll_locked = false;
- spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML;
- spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML;
+ spdif_priv->dma_params_tx.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;
+ /*Clear the val bit for Tx*/
+ regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SCR,
+ SCR_VAL_MASK, 1 << SCR_VAL_OFFSET);
+
+ pm_runtime_enable(&pdev->dev);
+
/* Register with ASoC */
dev_set_drvdata(&pdev->dev, spdif_priv);
@@ -1326,44 +1537,87 @@ static int fsl_spdif_probe(struct platform_device *pdev)
return ret;
}
-#ifdef CONFIG_PM_SLEEP
-static int fsl_spdif_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int fsl_spdif_runtime_resume(struct device *dev)
{
struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
+ int ret;
+ int i;
- regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC,
- &spdif_priv->regcache_srpc);
+ ret = clk_prepare_enable(spdif_priv->coreclk);
+ if (ret) {
+ dev_err(dev, "failed to enable core clock\n");
+ return ret;
+ }
- regcache_cache_only(spdif_priv->regmap, true);
+ 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_spba_clk;
+ }
+
+ request_bus_freq(BUS_FREQ_HIGH);
+
+ 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:
+disable_spba_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;
}
-static int fsl_spdif_resume(struct device *dev)
+static int fsl_spdif_runtime_suspend(struct device *dev)
{
struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
+ int i;
- regcache_cache_only(spdif_priv->regmap, false);
+ regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC,
+ &spdif_priv->regcache_srpc);
+ regcache_cache_only(spdif_priv->regmap, true);
- regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SRPC,
- SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK,
- spdif_priv->regcache_srpc);
+ release_bus_freq(BUS_FREQ_HIGH);
- return regcache_sync(spdif_priv->regmap);
+ 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;
}
-#endif /* CONFIG_PM_SLEEP */
+#endif
static const struct dev_pm_ops fsl_spdif_pm = {
- SET_SYSTEM_SLEEP_PM_OPS(fsl_spdif_suspend, fsl_spdif_resume)
-};
-
-static const struct of_device_id fsl_spdif_dt_ids[] = {
- { .compatible = "fsl,imx35-spdif", },
- { .compatible = "fsl,vf610-spdif", },
- {}
+ 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)
};
-MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids);
static struct platform_driver fsl_spdif_driver = {
.driver = {
diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h
index e6c61e07bc1a..f60b64675d39 100644
--- a/sound/soc/fsl/fsl_spdif.h
+++ b/sound/soc/fsl/fsl_spdif.h
@@ -63,6 +63,7 @@
#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 (1 << 14)
#define SCR_LOW_POWER (1 << 13)
#define SCR_SOFT_RESET (1 << 12)
#define SCR_TXFIFO_CTRL_OFFSET 10
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
index c3c760067b50..e4a38ebd4fc4 100644
--- a/sound/soc/fsl/fsl_ssi.c
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -1536,8 +1536,10 @@ static int fsl_ssi_probe(struct platform_device *pdev)
}
ssi->irq = platform_get_irq(pdev, 0);
- if (ssi->irq < 0)
+ if (ssi->irq < 0) {
+ dev_err(dev, "no irq for node %s\n", pdev->name);
return ssi->irq;
+ }
/* Set software limitations for synchronous mode except AC97 */
if (ssi->synchronous && !fsl_ssi_is_ac97(ssi)) {
diff --git a/sound/soc/fsl/hdmi_pcm.S b/sound/soc/fsl/hdmi_pcm.S
new file mode 100644
index 000000000000..d8d95fd8f42f
--- /dev/null
+++ b/sound/soc/fsl/hdmi_pcm.S
@@ -0,0 +1,246 @@
+/**
+ * Copyright (C) 2010-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+.section .text
+
+.global hdmi_dma_copy_16_neon_lut
+.global hdmi_dma_copy_16_neon_fast
+.global hdmi_dma_copy_24_neon_lut
+.global hdmi_dma_copy_24_neon_fast
+
+
+/**
+ * hdmi_dma_copy_16_neon_lut
+ * Convert pcm sample to iec sample. Pcm sample is 16 bits.
+ * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
+ * int samples, unsigned char *lookup_table);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM16 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ * lookup_table Preconstructed header table. Channels interleaved.
+ */
+
+hdmi_dma_copy_16_neon_lut:
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d6, r12
+
+hdmi_dma_copy_16_neon_lut_start:
+
+ /* get 8 samples to q0 */
+ vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
+
+ /* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q1, q0 /* count of 1s */
+ vpadd.i8 d2, d2, d3 /* only care about the LST in every element */
+ vand d2, d2, d6 /* clear other bits while keep the least bit */
+ vshl.u8 d2, d2, #3 /* bit p: d2 = d2 << 3 */
+
+ /* get packet header */
+ vld1.8 {d5}, [r3]!
+ veor d4, d5, d2 /* xor bit c */
+
+ /* store: (d4 << 16 | q0) << 8 */
+ vmovl.u8 q2, d4 /* expand from char to short */
+ vzip.16 q0, q2
+ vshl.u32 q0, q0, #8
+ vshl.u32 q1, q2, #8
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_16_neon_lut_start
+
+ mov pc, lr
+
+/**
+ * hdmi_dma_copy_16_neon_fast
+ * Convert pcm sample to iec sample. Pcm sample is 16 bits.
+ * Frame index's between 48 and 191 inclusively.
+ * Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_16_neon_fast(unsigned short *src,
+ * unsigned int *dst, int samples);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM16 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ */
+
+hdmi_dma_copy_16_neon_fast:
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d6, r12
+
+hdmi_dma_copy_16_neon_fast_start:
+ /* get 8 samples to q0 */
+ vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
+
+ /* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q1, q0 /* count of 1s */
+ vpadd.i8 d2, d2, d3
+ vand d2, d2, d6 /* clear other bits while keep the LST */
+ /* finally we construct packet header */
+ vshl.u8 d4, d2, #3 /* bit p: d2 = d2 << 3 */
+
+ /* get packet header: always 0 */
+
+ /* store: (d4 << 16 | q0) << 8 */
+ vmovl.u8 q2, d4 /* expand from char to short */
+ vzip.16 q0, q2
+ vshl.u32 q0, q0, #8
+ vshl.u32 q1, q2, #8
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_16_neon_fast_start
+
+ mov pc, lr
+
+
+
+/**
+ * hdmi_dma_copy_24_neon_lut
+ * Convert pcm sample to iec sample. Pcm sample is 24 bits.
+ * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
+ * int samples, unsigned char *lookup_table);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM24 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ * lookup_table Preconstructed header table. Channels interleaved.
+ */
+
+hdmi_dma_copy_24_neon_lut:
+ vpush {d8}
+
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d8, r12
+
+hdmi_dma_copy_24_neon_lut_start:
+
+ /* get 8 samples to q0 and q1 */
+ vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
+
+ /* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q2, q0 /* count of 1s */
+ vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */
+ vcnt.8 q3, q1
+ vpadd.i8 d6, d6, d7
+ vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */
+ vand d4, d4, d8 /* clear other bits while keep the least bit */
+ vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */
+
+ /* get packet header */
+ vld1.8 {d5}, [r3]!/* d5: original header */
+ veor d5, d5, d4 /* fix bit p */
+
+ /* store: (d5 << 24 | q0) */
+ vmovl.u8 q3, d5 /* expand from char to short */
+ vmovl.u16 q2, d6 /* expand from short to int */
+ vmovl.u16 q3, d7
+ vshl.u32 q2, q2, #24
+ vshl.u32 q3, q3, #24
+ vorr q0, q0, q2
+ vorr q1, q1, q3
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_24_neon_lut_start
+
+ vpop {d8}
+ mov pc, lr
+
+/**
+ * hdmi_dma_copy_24_neon_fast
+ * Convert pcm sample to iec sample. Pcm sample is 24 bits.
+ * Frame index's between 48 and 191 inclusively.
+ * Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_24_neon_fast(unsigned int *src,
+ * unsigned int *dst, int samples);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM24 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ */
+
+hdmi_dma_copy_24_neon_fast:
+ vpush {d8}
+
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d8, r12
+
+hdmi_dma_copy_24_neon_fast_start:
+ /* get 8 samples to q0 and q1 */
+ vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
+
+ /* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q2, q0 /* count of 1s */
+ vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */
+ vcnt.8 q3, q1
+ vpadd.i8 d6, d6, d7
+ vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */
+ vand d4, d4, d8 /* clear other bits while keep the least bit */
+ vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */
+
+ /* store: (d4 << 24 | q0) */
+ vmovl.u8 q3, d4 /* expand from char to short */
+ vmovl.u16 q2, d6 /* expand from short to int */
+ vmovl.u16 q3, d7
+ vshl.u32 q2, q2, #24
+ vshl.u32 q3, q3, #24
+ vorr q0, q0, q2
+ vorr q1, q1, q3
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_24_neon_fast_start
+
+ vpop {d8}
+ mov pc, lr
diff --git a/sound/soc/fsl/imx-ak4458.c b/sound/soc/fsl/imx-ak4458.c
new file mode 100644
index 000000000000..08a437d5f89b
--- /dev/null
+++ b/sound/soc/fsl/imx-ak4458.c
@@ -0,0 +1,417 @@
+/* i.MX AK4458 audio support
+ *
+ * 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:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#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 "fsl_sai.h"
+#include "fsl_dsd.h"
+
+struct imx_ak4458_data {
+ struct snd_soc_card card;
+ int num_codec_conf;
+ struct snd_soc_codec_conf *codec_conf;
+ bool tdm_mode;
+ int pdn_gpio;
+ unsigned int slots;
+ unsigned int slot_width;
+ bool one2one_ratio;
+};
+
+static struct snd_soc_dapm_widget imx_ak4458_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+};
+
+/**
+ * Tables 3 & 4 - mapping LRCK fs and frame width
+ */
+static const struct imx_ak4458_fs_map {
+ unsigned int rmin;
+ unsigned int rmax;
+ unsigned int wmin;
+ unsigned int wmax;
+} fs_map[] = {
+ /* Normal, < 32kHz */
+ { .rmin = 8000, .rmax = 24000, .wmin = 1024, .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 const struct imx_ak4458_fs_mul {
+ unsigned int min;
+ unsigned int max;
+ unsigned int mul;
+} fs_mul_tdm[] = {
+ /*
+ * 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 const u32 ak4458_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000,
+};
+
+static const u32 ak4458_rates_tdm[] = {
+ 8000, 16000, 32000,
+ 48000, 96000,
+};
+
+static const u32 ak4458_channels[] = {
+ 1, 2, 4, 6, 8, 10, 12, 14, 16,
+};
+
+static const u32 ak4458_channels_tdm[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+};
+
+static unsigned long ak4458_get_mclk_rate(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_ak4458_data *data = snd_soc_card_get_drvdata(rtd->card);
+ unsigned int rate = params_rate(params);
+ unsigned int width = data->slots * data->slot_width;
+ int i, mode;
+
+ if (data->tdm_mode) {
+ /* can be 128, 256 or 512 */
+ mode = data->slots * data->slot_width;
+
+ for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) {
+ /* min = max = slots * slots_width */
+ if (mode != fs_mul_tdm[i].min)
+ continue;
+ return rate * fs_mul_tdm[i].mul;
+ }
+ } else {
+ for (i = 0; i < ARRAY_SIZE(fs_map); i++) {
+ if (rate >= fs_map[i].rmin && rate <= fs_map[i].rmax) {
+ width = max(width, fs_map[i].wmin);
+ width = min(width, fs_map[i].wmax);
+
+ /* Adjust SAI bclk:mclk ratio */
+ width *= 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 = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card);
+ unsigned int channels = params_channels(params);
+ unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS;
+ unsigned long mclk_freq;
+ bool is_dsd = fsl_is_dsd(params);
+ int ret, i;
+
+ if (is_dsd) {
+ channels = 1;
+ data->slots = 1;
+ data->slot_width = params_width(params);
+ fmt |= SND_SOC_DAIFMT_PDM;
+ } else if (data->tdm_mode) {
+ data->slots = 8;
+ data->slot_width = 32;
+ fmt |= SND_SOC_DAIFMT_DSP_B;
+ } else {
+ data->slots = 2;
+ data->slot_width = params_physical_width(params);
+ fmt |= SND_SOC_DAIFMT_I2S;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ data->slots, data->slot_width);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < rtd->num_codecs; i++) {
+ struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret) {
+ 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(channels) - 1, BIT(channels) - 1,
+ data->slots, data->slot_width);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai[%d] tdm slot: %d\n",
+ i, ret);
+ return ret;
+ }
+ }
+
+ /* set MCLK freq */
+ mclk_freq = ak4458_get_mclk_rate(substream, params);
+ if (is_dsd)
+ mclk_freq = 22579200;
+ ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ dev_err(dev, "failed to set cpui dai mclk1 rate (%lu): %d\n",
+ mclk_freq, ret);
+ return ret;
+}
+
+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_ak4458_data *data = snd_soc_card_get_drvdata(card);
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ static struct snd_pcm_hw_constraint_list constraint_channels;
+ int ret;
+
+ if (data->tdm_mode) {
+ constraint_channels.list = ak4458_channels_tdm;
+ constraint_channels.count = ARRAY_SIZE(ak4458_channels_tdm);
+ constraint_rates.list = ak4458_rates_tdm;
+ constraint_rates.count = ARRAY_SIZE(ak4458_rates_tdm);
+ } else {
+ constraint_channels.list = ak4458_channels;
+ constraint_channels.count = ARRAY_SIZE(ak4458_channels);
+ constraint_rates.list = ak4458_rates;
+ constraint_rates.count = ARRAY_SIZE(ak4458_rates);
+ }
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraint_channels);
+ if (ret)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_aif_ops = {
+ .hw_params = imx_aif_hw_params,
+ .startup = imx_aif_startup,
+};
+
+static struct snd_soc_dai_link_component ak4458_codecs[] = {
+ {
+ /* Playback */
+ .dai_name = "ak4458-aif",
+ },
+ {
+ /* Capture */
+ .dai_name = "ak4458-aif",
+ },
+};
+
+static struct snd_soc_dai_link imx_ak4458_dai = {
+ .name = "ak4458",
+ .stream_name = "Audio",
+ .codecs = ak4458_codecs,
+ .num_codecs = 2,
+ .ignore_pmdown_time = 1,
+ .ops = &imx_aif_ops,
+ .playback_only = 1,
+};
+
+static int imx_ak4458_probe(struct platform_device *pdev)
+{
+ struct imx_ak4458_data *priv;
+ struct device_node *cpu_np, *codec_np_0 = NULL, *codec_np_1 = NULL;
+ struct platform_device *cpu_pdev;
+ struct snd_soc_dai_link_component *dlc;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ dlc = devm_kzalloc(&pdev->dev, 2 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "audio dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np_0 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np_0) {
+ dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np_1 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
+ if (!codec_np_1) {
+ dev_err(&pdev->dev, "audio codec 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;
+ }
+
+ if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL))
+ priv->tdm_mode = true;
+
+ priv->num_codec_conf = 2;
+ priv->codec_conf = devm_kzalloc(&pdev->dev,
+ priv->num_codec_conf * sizeof(struct snd_soc_codec_conf),
+ GFP_KERNEL);
+ if (!priv->codec_conf) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ priv->codec_conf[0].name_prefix = "0";
+ priv->codec_conf[0].of_node = codec_np_0;
+ priv->codec_conf[1].name_prefix = "1";
+ priv->codec_conf[1].of_node = codec_np_1;
+
+ ak4458_codecs[0].of_node = codec_np_0;
+ ak4458_codecs[1].of_node = codec_np_1;
+
+ imx_ak4458_dai.cpus = &dlc[0];
+ imx_ak4458_dai.num_cpus = 1;
+ imx_ak4458_dai.platforms = &dlc[1];
+ imx_ak4458_dai.num_platforms = 1;
+
+ imx_ak4458_dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
+ imx_ak4458_dai.platforms->of_node = cpu_np;
+
+ priv->card.num_links = 1;
+ priv->card.dai_link = &imx_ak4458_dai;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ priv->card.dapm_widgets = imx_ak4458_dapm_widgets;
+ priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4458_dapm_widgets);
+ priv->card.codec_conf = priv->codec_conf;
+ priv->card.num_configs = priv->num_codec_conf;
+ priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx-audio-ak4458-mq");
+
+ priv->pdn_gpio = of_get_named_gpio(pdev->dev.of_node, "ak4458,pdn-gpio", 0);
+ if (gpio_is_valid(priv->pdn_gpio)) {
+ ret = devm_gpio_request_one(&pdev->dev, priv->pdn_gpio,
+ GPIOF_OUT_INIT_LOW, "ak4458,pdn");
+ if (ret) {
+ dev_err(&pdev->dev, "unable to get pdn gpio\n");
+ goto fail;
+ }
+
+ gpio_set_value_cansleep(priv->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ gpio_set_value_cansleep(priv->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret)
+ goto fail;
+
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np_0)
+ of_node_put(codec_np_0);
+ if (codec_np_1)
+ of_node_put(codec_np_1);
+
+ return ret;
+}
+
+static const struct of_device_id imx_ak4458_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-ak4458", },
+ { .compatible = "fsl,imx-audio-ak4458-mq", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx_ak4458_dt_ids);
+
+static struct platform_driver imx_ak4458_driver = {
+ .driver = {
+ .name = "imx-ak4458",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_ak4458_dt_ids,
+ },
+ .probe = imx_ak4458_probe,
+};
+module_platform_driver(imx_ak4458_driver);
+
+MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
+MODULE_DESCRIPTION("Freescale i.MX AK4458 ASoC machine driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-ak4458");
diff --git a/sound/soc/fsl/imx-ak4497.c b/sound/soc/fsl/imx-ak4497.c
new file mode 100644
index 000000000000..a4d24333026c
--- /dev/null
+++ b/sound/soc/fsl/imx-ak4497.c
@@ -0,0 +1,269 @@
+/* i.MX AK4458 audio support
+ *
+ * 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:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+
+#include "fsl_sai.h"
+
+struct imx_ak4497_data {
+ struct snd_soc_card card;
+ bool one2one_ratio;
+};
+
+static struct snd_soc_dapm_widget imx_ak4497_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+};
+
+static const struct imx_ak4497_fs_mul {
+ unsigned int min;
+ unsigned int max;
+ unsigned int mul;
+} fs_mul[] = {
+ /**
+ * Table 7 - mapping multiplier and speed mode
+ * Tables 8 & 9 - mapping speed mode and LRCK fs
+ */
+ { .min = 8000, .max = 32000, .mul = 1024 }, /* Normal, <= 32kHz */
+ { .min = 44100, .max = 48000, .mul = 512 }, /* Normal */
+ { .min = 88200, .max = 96000, .mul = 256 }, /* Double */
+ { .min = 176400, .max = 192000, .mul = 128 }, /* Quad */
+ { .min = 352800, .max = 384000, .mul = 2*64 }, /* Oct */
+ { .min = 705600, .max = 768000, .mul = 2*32 }, /* Hex */
+};
+
+static bool imx_ak4497_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 unsigned long imx_ak4497_compute_freq(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ unsigned int rate = params_rate(params);
+ int i;
+
+ /* Find the appropriate MCLK freq */
+ for (i = 0; i < ARRAY_SIZE(fs_mul); i++) {
+ if (rate >= fs_mul[i].min && rate <= fs_mul[i].max)
+ return rate * fs_mul[i].mul;
+ }
+
+ /* Let DAI manage MCLK frequency */
+ 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 = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ struct imx_ak4497_data *priv = snd_soc_card_get_drvdata(card);
+ unsigned int channels = params_channels(params);
+ unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS;
+ unsigned long freq = imx_ak4497_compute_freq(substream, params);
+ bool is_dsd = imx_ak4497_is_dsd(params);
+ int ret;
+
+ fmt |= (is_dsd ? SND_SOC_DAIFMT_PDM : SND_SOC_DAIFMT_I2S);
+
+ if (is_dsd && freq > 22579200 && priv->one2one_ratio)
+ freq = 22579200;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0) {
+ dev_err(dev, "failed to set cpu dai mclk1 rate(%lu): %d\n",
+ freq, ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ if (is_dsd)
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ 0x1, 0x1,
+ 1, params_width(params));
+ else
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ 2, params_physical_width(params));
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static const u32 support_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000,
+};
+
+static int imx_aif_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret = 0;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+
+ constraint_rates.list = support_rates;
+ constraint_rates.count = ARRAY_SIZE(support_rates);
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static struct snd_soc_ops imx_aif_ops = {
+ .startup = imx_aif_startup,
+ .hw_params = imx_aif_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak4497-aif")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link imx_ak4497_dai = {
+ .name = "ak4497",
+ .stream_name = "Audio",
+ .ops = &imx_aif_ops,
+ .playback_only = 1,
+ SND_SOC_DAILINK_REG(hifi),
+};
+
+static int imx_ak4497_probe(struct platform_device *pdev)
+{
+ struct imx_ak4497_data *priv;
+ struct device_node *cpu_np, *codec_np = NULL;
+ struct platform_device *cpu_pdev;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "audio dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "audio codec 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;
+ }
+
+ imx_ak4497_dai.codecs->of_node = codec_np;
+ imx_ak4497_dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
+ imx_ak4497_dai.platforms->of_node = cpu_np;
+ imx_ak4497_dai.playback_only = 1;
+
+ priv->card.dai_link = &imx_ak4497_dai;
+ priv->card.num_links = 1;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ priv->card.dapm_widgets = imx_ak4497_dapm_widgets;
+ priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4497_dapm_widgets);
+ priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx-audio-ak4497-mq");
+
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret)
+ goto fail;
+
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static const struct of_device_id imx_ak4497_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-ak4497", },
+ { .compatible = "fsl,imx-audio-ak4497-mq", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx_ak4497_dt_ids);
+
+static struct platform_driver imx_ak4497_driver = {
+ .driver = {
+ .name = "imx-ak4497",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_ak4497_dt_ids,
+ },
+ .probe = imx_ak4497_probe,
+};
+module_platform_driver(imx_ak4497_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
+MODULE_DESCRIPTION("Freescale i.MX AK4497 ASoC machine driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-ak4497");
diff --git a/sound/soc/fsl/imx-ak5558.c b/sound/soc/fsl/imx-ak5558.c
new file mode 100644
index 000000000000..98a057dc91a4
--- /dev/null
+++ b/sound/soc/fsl/imx-ak5558.c
@@ -0,0 +1,485 @@
+/* i.MX AK5558 audio support
+ *
+ * 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:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm.h>
+#include <sound/soc-dapm.h>
+
+#include "fsl_sai.h"
+#include "../codecs/ak5558.h"
+
+
+struct imx_ak5558_data {
+ struct snd_soc_card card;
+ bool tdm_mode;
+ unsigned long slots;
+ unsigned long slot_width;
+ bool one2one_ratio;
+ struct platform_device *asrc_pdev;
+ u32 asrc_rate;
+ u32 asrc_format;
+};
+
+/*
+ * imx_ack5558_fs_mul - sampling frequency multiplier
+ *
+ * min <= fs <= max, MCLK = mul * LRCK
+ */
+struct imx_ak5558_fs_mul {
+ unsigned int min;
+ unsigned int max;
+ unsigned int mul;
+};
+
+/*
+ * Auto MCLK selection based on LRCK for Normal Mode
+ * (Table 4 from datasheet)
+ */
+static const struct imx_ak5558_fs_mul fs_mul[] = {
+ { .min = 8000, .max = 32000, .mul = 1024 },
+ { .min = 44100, .max = 48000, .mul = 512 },
+ { .min = 88200, .max = 96000, .mul = 256 },
+ { .min = 176400, .max = 192000, .mul = 128 },
+ { .min = 352800, .max = 384000, .mul = 64 },
+ { .min = 705600, .max = 768000, .mul = 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 const struct imx_ak5558_fs_mul fs_mul_tdm[] = {
+ { .min = 128, .max = 128, .mul = 256 },
+ { .min = 256, .max = 256, .mul = 512 },
+ { .min = 512, .max = 512, .mul = 1024 },
+};
+
+static struct snd_soc_dapm_widget imx_ak5558_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static const u32 ak5558_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000,
+};
+
+static const u32 ak5558_tdm_rates[] = {
+ 8000, 16000, 32000,
+ 48000, 96000
+};
+
+static const u32 ak5558_channels[] = {
+ 1, 2, 4, 6, 8,
+};
+
+static unsigned long ak5558_get_mclk_rate(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_ak5558_data *data = snd_soc_card_get_drvdata(rtd->card);
+ unsigned int rate = params_rate(params);
+ unsigned int freq = 0; /* Let DAI manage clk frequency by default */
+ int mode;
+ int i;
+
+ if (data->tdm_mode) {
+ mode = data->slots * data->slot_width;
+
+ for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) {
+ /* min = max = slots * slots_width */
+ if (mode != fs_mul_tdm[i].min)
+ continue;
+ freq = rate * fs_mul_tdm[i].mul;
+ break;
+ }
+ } else {
+ for (i = 0; i < ARRAY_SIZE(fs_mul); i++) {
+ if (rate < fs_mul[i].min || rate > fs_mul[i].max)
+ continue;
+ freq = rate * fs_mul[i].mul;
+ break;
+ }
+ }
+
+ return freq;
+}
+
+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 = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card);
+ unsigned int channels = params_channels(params);
+ unsigned long mclk_freq;
+ unsigned int fmt;
+ int ret;
+
+ if (data->tdm_mode)
+ fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+ else
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ if (data->tdm_mode) {
+ /* support TDM256 (8 slots * 32 bits/per slot) */
+ data->slots = 8;
+ data->slot_width = 32;
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ data->slots, data->slot_width);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ 8, 32);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+ } else {
+ /* normal mode (I2S) */
+ data->slots = 2;
+ data->slot_width = params_physical_width(params);
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ data->slots, data->slot_width);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+ }
+
+ mclk_freq = ak5558_get_mclk_rate(substream, params);
+ ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ dev_err(dev, "failed to set cpu_dai mclk1 rate %lu\n",
+ mclk_freq);
+
+ return ret;
+}
+
+static int imx_ak5558_hw_rule_rate(struct snd_pcm_hw_params *p,
+ struct snd_pcm_hw_rule *r)
+{
+ struct imx_ak5558_data *data = r->private;
+ struct snd_interval t = { .min = 8000, .max = 8000, };
+ unsigned int fs;
+ unsigned long mclk_freq;
+ int i;
+
+ fs = hw_param_interval(p, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+ fs *= data->tdm_mode ? 8 : 2;
+
+ /* Identify maximum supported rate */
+ for (i = 0; i < ARRAY_SIZE(ak5558_rates); i++) {
+ mclk_freq = fs * ak5558_rates[i];
+ /* Adjust SAI bclk:mclk ratio */
+ mclk_freq *= data->one2one_ratio ? 1 : 2;
+
+ /* Skip rates for which MCLK is beyond supported value */
+ if (mclk_freq > 36864000)
+ continue;
+
+ if (t.max < ak5558_rates[i])
+ t.max = ak5558_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_ak5558_data *data = snd_soc_card_get_drvdata(card);
+
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ static struct snd_pcm_hw_constraint_list constraint_channels;
+ int ret;
+
+ if (data->tdm_mode) {
+ constraint_rates.list = ak5558_tdm_rates;
+ constraint_rates.count = ARRAY_SIZE(ak5558_tdm_rates);
+ } else {
+ constraint_rates.list = ak5558_rates;
+ constraint_rates.count = ARRAY_SIZE(ak5558_rates);
+ }
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ constraint_channels.list = ak5558_channels;
+ constraint_channels.count = ARRAY_SIZE(ak5558_channels);
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraint_channels);
+ if (ret < 0)
+ return ret;
+
+ return snd_pcm_hw_rule_add(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, imx_ak5558_hw_rule_rate, data,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+}
+
+static struct snd_soc_ops imx_aif_ops = {
+ .hw_params = imx_aif_hw_params,
+ .startup = imx_aif_startup,
+};
+
+static 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_ak5558_data *priv = snd_soc_card_get_drvdata(card);
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = priv->asrc_rate;
+ rate->min = priv->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, priv->asrc_format);
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak5558-aif")),
+ 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_CODEC(NULL, "ak5558-aif")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+static struct snd_soc_dai_link imx_ak5558_dai[] = {
+ {
+ .name = "ak5558",
+ .stream_name = "Audio",
+ .ops = &imx_aif_ops,
+ .capture_only = 1,
+ SND_SOC_DAILINK_REG(hifi),
+ },
+ {
+ .name = "HiFi-ASRC-FE",
+ .stream_name = "HiFi-ASRC-FE",
+ .dynamic = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 0,
+ .dpcm_capture = 1,
+ .dpcm_merged_chan = 1,
+ SND_SOC_DAILINK_REG(hifi_fe),
+ },
+ {
+ .name = "HiFi-ASRC-BE",
+ .stream_name = "HiFi-ASRC-BE",
+ .no_pcm = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 0,
+ .dpcm_capture = 1,
+ .ops = &imx_aif_ops_be,
+ .be_hw_params_fixup = be_hw_params_fixup,
+ SND_SOC_DAILINK_REG(hifi_be),
+ },
+
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"CPU-Capture", NULL, "Capture"},
+ {"ASRC-Capture", NULL, "CPU-Capture"},
+};
+
+static int imx_ak5558_probe(struct platform_device *pdev)
+{
+ struct imx_ak5558_data *priv;
+ struct device_node *cpu_np, *codec_np = NULL;
+ struct platform_device *cpu_pdev;
+ struct device_node *asrc_np = NULL;
+ struct platform_device *asrc_pdev = NULL;
+ int ret;
+ u32 width;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "audio dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "audio codec 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;
+ }
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
+ }
+
+ if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL))
+ priv->tdm_mode = true;
+
+ imx_ak5558_dai[0].codecs->of_node = codec_np;
+ imx_ak5558_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ imx_ak5558_dai[0].platforms->of_node = cpu_np;
+ imx_ak5558_dai[0].capture_only = 1;
+
+ priv->card.dai_link = &imx_ak5558_dai[0];
+ priv->card.num_links = 1;
+ priv->card.dapm_routes = audio_map;
+ priv->card.num_dapm_routes = 1;
+
+ /*if there is no asrc controller, we only enable one device*/
+ if (asrc_pdev) {
+ imx_ak5558_dai[1].cpus->of_node = asrc_np;
+ imx_ak5558_dai[1].platforms->of_node = asrc_np;
+
+ imx_ak5558_dai[2].codecs->of_node = codec_np;
+ imx_ak5558_dai[2].cpus->of_node = cpu_np;
+ priv->card.num_links = 3;
+ priv->card.num_dapm_routes += 1;
+
+ 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 fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ priv->card.dapm_widgets = imx_ak5558_dapm_widgets;
+ priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak5558_dapm_widgets);
+ priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx-audio-ak5558-mq");
+
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret)
+ goto fail;
+
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static const struct of_device_id imx_ak5558_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-ak5558", },
+ { .compatible = "fsl,imx-audio-ak5558-mq", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx_ak5558_dt_ids);
+
+static struct platform_driver imx_ak5558_driver = {
+ .driver = {
+ .name = "imx-ak5558",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_ak5558_dt_ids,
+ },
+ .probe = imx_ak5558_probe,
+};
+module_platform_driver(imx_ak5558_driver);
+
+MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
+MODULE_DESCRIPTION("Freescale i.MX AK5558 ASoC machine driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-ak5558");
diff --git a/sound/soc/fsl/imx-cdnhdmi.c b/sound/soc/fsl/imx-cdnhdmi.c
new file mode 100644
index 000000000000..ad280d7427a8
--- /dev/null
+++ b/sound/soc/fsl/imx-cdnhdmi.c
@@ -0,0 +1,561 @@
+/*
+ * Copyright 2017-2018 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:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/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 <sound/hdmi-codec.h>
+#include <drm/drm_connector.h>
+#include "fsl_sai.h"
+
+#define SUPPORT_RATE_NUM 10
+#define SUPPORT_CHANNEL_NUM 10
+
+struct imx_cdnhdmi_data {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+ int protocol;
+ u32 support_rates[SUPPORT_RATE_NUM];
+ u32 support_rates_num;
+ u32 support_channels[SUPPORT_CHANNEL_NUM];
+ u32 support_channels_num;
+ u32 edid_rates[SUPPORT_RATE_NUM];
+ u32 edid_rates_count;
+ u32 edid_channels[SUPPORT_CHANNEL_NUM];
+ u32 edid_channels_count;
+ uint8_t eld[MAX_ELD_BYTES];
+};
+
+static int imx_cdnhdmi_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_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ static struct snd_pcm_hw_constraint_list constraint_channels;
+ int ret;
+
+ constraint_rates.list = data->support_rates;
+ constraint_rates.count = data->support_rates_num;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ constraint_channels.list = data->support_channels;
+ constraint_channels.count = data->support_channels_num;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraint_channels);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret;
+
+ /* set cpu DAI configuration */
+ if (tx)
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ else
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+
+ if (of_device_is_compatible(dev->of_node,
+ "fsl,imx8mq-evk-cdnhdmi"))
+ ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1,
+ 256 * params_rate(params),
+ SND_SOC_CLOCK_OUT);
+ else
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0,
+ 0,
+ tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN);
+ if (ret) {
+ 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, 32);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_cdnhdmi_ops = {
+ .startup = imx_cdnhdmi_startup,
+ .hw_params = imx_cdnhdmi_hw_params,
+};
+
+static const unsigned int eld_rates[] = {
+ 32000,
+ 44100,
+ 48000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+};
+
+static unsigned int sad_max_channels(const u8 *sad)
+{
+ return 1 + (sad[0] & 7);
+}
+
+static int get_edid_info(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_component *component = codec_dai->component;
+ struct hdmi_codec_pdata *hcd = component->dev->platform_data;
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int i, j, ret;
+ const u8 *sad;
+ unsigned int channel_max = 0;
+ unsigned int rate_mask = 0;
+ unsigned int rate_mask_eld = 0;
+
+ ret = hcd->ops->get_eld(component->dev->parent, hcd->data,
+ data->eld, sizeof(data->eld));
+ sad = drm_eld_sad(data->eld);
+ if (sad) {
+ for (j = 0; j < data->support_rates_num; j++) {
+ for (i = 0; i < ARRAY_SIZE(eld_rates); i++)
+ if (eld_rates[i] == data->support_rates[j])
+ rate_mask |= BIT(i);
+ }
+
+ for (i = drm_eld_sad_count(data->eld); i > 0; i--, sad += 3) {
+ if (rate_mask & sad[1])
+ channel_max = max(channel_max, sad_max_channels(sad));
+
+ if (sad_max_channels(sad) >= 2)
+ rate_mask_eld |= sad[1];
+ }
+ }
+
+ rate_mask = rate_mask & rate_mask_eld;
+
+ data->edid_rates_count = 0;
+ data->edid_channels_count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(eld_rates); i++) {
+ if (rate_mask & BIT(i)) {
+ data->edid_rates[data->edid_rates_count] = eld_rates[i];
+ data->edid_rates_count++;
+ }
+ }
+
+ for (i = 0; i < data->support_channels_num; i++) {
+ if (data->support_channels[i] <= channel_max) {
+ data->edid_channels[data->edid_channels_count]
+ = data->support_channels[i];
+ data->edid_channels_count++;
+ }
+ }
+
+ return 0;
+}
+
+static int imx_cdnhdmi_channels_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+
+ get_edid_info(card);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = data->edid_channels_count;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_channels_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int i;
+
+ get_edid_info(card);
+
+ for (i = 0 ; i < data->edid_channels_count ; i++)
+ uvalue->value.integer.value[i] = data->edid_channels[i];
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rates_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+
+ get_edid_info(card);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = data->edid_rates_count;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rates_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int i;
+
+ get_edid_info(card);
+
+ for (i = 0 ; i < data->edid_rates_count; i++)
+ uvalue->value.integer.value[i] = data->edid_rates[i];
+
+ return 0;
+}
+
+static int imx_cdnhdmi_formats_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 3;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_formats_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ uvalue->value.integer.value[0] = 16;
+ uvalue->value.integer.value[1] = 24;
+ uvalue->value.integer.value[2] = 32;
+
+ return 0;
+}
+
+static int get_edid_rx_info(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_component *component = codec_dai->component;
+ struct hdmi_codec_pdata *hcd = component->dev->platform_data;
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = hcd->ops->get_eld(component->dev->parent, hcd->data,
+ data->eld, sizeof(data->eld));
+
+ if (ret)
+ return -EINVAL;
+
+ data->edid_rates[0] = data->eld[0] +
+ (data->eld[1] << 8) +
+ (data->eld[2] << 16) +
+ (data->eld[3] << 24);
+
+ data->edid_channels[0] = data->eld[4] +
+ (data->eld[5] << 8) +
+ (data->eld[6] << 16) +
+ (data->eld[7] << 24);
+
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rx_channels_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 = 2;
+ uinfo->value.integer.max = 8;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rx_channels_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = get_edid_rx_info(card);
+ if (ret)
+ return ret;
+ uvalue->value.integer.value[0] = data->edid_channels[0];
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rx_rates_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 int imx_cdnhdmi_rx_rates_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = get_edid_rx_info(card);
+ if (ret)
+ return ret;
+
+ uvalue->value.integer.value[0] = data->edid_rates[0];
+
+ return 0;
+}
+
+static struct snd_kcontrol_new imx_cdnhdmi_ctrls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Channels",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_channels_info,
+ .get = imx_cdnhdmi_channels_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Rates",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_rates_info,
+ .get = imx_cdnhdmi_rates_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Formats",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_formats_info,
+ .get = imx_cdnhdmi_formats_get,
+ },
+};
+
+static struct snd_kcontrol_new imx_cdnhdmi_rx_ctrls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Rx Channels",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_rx_channels_info,
+ .get = imx_cdnhdmi_rx_channels_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Rx Rates",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_rx_rates_info,
+ .get = imx_cdnhdmi_rx_rates_get,
+ },
+};
+
+static int imx_cdnhdmi_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *cdnhdmi_np = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_cdnhdmi_data *data;
+ struct snd_soc_dai_link_component *dlc;
+ int ret;
+ int i;
+
+ dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "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;
+ goto fail;
+ }
+
+ for (i = 0; i < SUPPORT_RATE_NUM; i++) {
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "constraint-rate",
+ i, &data->support_rates[i]);
+ if (!ret)
+ data->support_rates_num = i + 1;
+ else
+ break;
+ }
+
+ if (data->support_rates_num == 0) {
+ data->support_rates[0] = 48000;
+ data->support_rates[1] = 96000;
+ data->support_rates[2] = 32000;
+ data->support_rates[3] = 192000;
+ data->support_rates_num = 4;
+ }
+
+ data->support_channels[0] = 2;
+ data->support_channels[1] = 4;
+ data->support_channels[2] = 8;
+ data->support_channels_num = 3;
+
+ of_property_read_u32(pdev->dev.of_node, "protocol",
+ &data->protocol);
+
+ 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 = "imx8 hdmi";
+ data->dai.stream_name = "imx8 hdmi";
+ data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platforms->of_node = cpu_np;
+ data->dai.ops = &imx_cdnhdmi_ops;
+ data->dai.playback_only = true;
+ data->dai.capture_only = false;
+ data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ if (of_property_read_bool(pdev->dev.of_node, "hdmi-out")) {
+ data->dai.playback_only = true;
+ data->dai.capture_only = false;
+ data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+ data->dai.codecs->dai_name = "i2s-hifi";
+ data->dai.codecs->name = "hdmi-audio-codec.1";
+ data->card.controls = imx_cdnhdmi_ctrls;
+ data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_ctrls);
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "hdmi-in")) {
+ data->dai.playback_only = false;
+ data->dai.capture_only = true;
+ data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ data->dai.codecs->dai_name = "i2s-hifi";
+ data->dai.codecs->name = "hdmi-audio-codec.2";
+ data->card.controls = imx_cdnhdmi_rx_ctrls;
+ data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_rx_ctrls);
+ }
+
+ if ((data->dai.playback_only && data->dai.capture_only)
+ || (!data->dai.playback_only && !data->dai.capture_only)) {
+ dev_err(&pdev->dev, "Wrongly enable HDMI DAI link\n");
+ goto fail;
+ }
+
+ 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;
+
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (cdnhdmi_np)
+ of_node_put(cdnhdmi_np);
+ return ret;
+}
+
+static const struct of_device_id imx_cdnhdmi_dt_ids[] = {
+ { .compatible = "fsl,imx8mq-evk-cdnhdmi", },
+ { .compatible = "fsl,imx-audio-cdnhdmi", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_cdnhdmi_dt_ids);
+
+static struct platform_driver imx_cdnhdmi_driver = {
+ .driver = {
+ .name = "imx-cdnhdmi",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_cdnhdmi_dt_ids,
+ },
+ .probe = imx_cdnhdmi_probe,
+};
+module_platform_driver(imx_cdnhdmi_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX hdmi audio ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-cdnhdmi");
diff --git a/sound/soc/fsl/imx-cs42888.c b/sound/soc/fsl/imx-cs42888.c
new file mode 100644
index 000000000000..2b01fa5f132a
--- /dev/null
+++ b/sound/soc/fsl/imx-cs42888.c
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2010-2016 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_esai.h"
+
+#define CODEC_CLK_EXTER_OSC 1
+#define CODEC_CLK_ESAI_HCKT 2
+#define SUPPORT_RATE_NUM 10
+
+struct imx_priv {
+ struct clk *codec_clk;
+ struct clk *esai_clk;
+ unsigned int mclk_freq;
+ unsigned int esai_freq;
+ struct platform_device *pdev;
+ struct platform_device *asrc_pdev;
+ u32 asrc_rate;
+ u32 asrc_format;
+ bool is_codec_master;
+ bool is_codec_rpmsg;
+ bool is_stream_in_use[2];
+ bool is_stream_tdm[2];
+};
+
+static struct imx_priv card_priv;
+
+static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 channels = params_channels(params);
+ u32 max_tdm_rate;
+ u32 dai_format;
+ int ret = 0;
+
+ priv->is_stream_tdm[tx] = channels > 1 && channels % 2;
+ dai_format = SND_SOC_DAIFMT_NB_NF |
+ (priv->is_stream_tdm[tx] ? SND_SOC_DAIFMT_DSP_A :
+ SND_SOC_DAIFMT_LEFT_J);
+
+ priv->is_stream_in_use[tx] = true;
+
+ if (priv->is_stream_in_use[!tx] &&
+ (priv->is_stream_tdm[tx] != priv->is_stream_tdm[!tx])) {
+
+ dev_err(dev, "Don't support different fmt for tx & rx\n");
+ return -EINVAL;
+ }
+
+ priv->mclk_freq = clk_get_rate(priv->codec_clk);
+ priv->esai_freq = clk_get_rate(priv->esai_clk);
+
+ if (priv->is_codec_master) {
+ /* TDM is not supported by codec in master mode */
+ if (priv->is_stream_tdm[tx]) {
+ dev_err(dev, "%d channels are not supported in codec master mode\n",
+ channels);
+ return -EINVAL;
+ }
+ dai_format |= SND_SOC_DAIFMT_CBM_CFM;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL,
+ priv->mclk_freq, SND_SOC_CLOCK_IN);
+ else
+ ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL,
+ priv->mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ priv->mclk_freq, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ } else {
+ dai_format |= SND_SOC_DAIFMT_CBS_CFS;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL,
+ priv->mclk_freq, SND_SOC_CLOCK_OUT);
+ else
+ ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL,
+ priv->mclk_freq, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ priv->mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set codec sysclk: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ /* set i.MX active slot mask */
+ if (priv->is_stream_tdm[tx]) {
+ /* 2 required by ESAI BCLK divisors, 8 slots, 32 width */
+ if (priv->is_codec_master)
+ max_tdm_rate = priv->mclk_freq / (8*32);
+ else
+ max_tdm_rate = priv->esai_freq / (2*8*32);
+ if (params_rate(params) > max_tdm_rate) {
+ dev_err(dev,
+ "maximum supported sampling rate for %d channels is %dKHz\n",
+ channels, max_tdm_rate / 1000);
+ return -EINVAL;
+ }
+
+ /*
+ * Per datasheet, the codec expects 8 slots and 32 bits
+ * for every slot in TDM mode.
+ */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ 8, 32);
+ } else
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32);
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int imx_cs42888_surround_hw_free(struct snd_pcm_substream *substream)
+{
+ struct imx_priv *priv = &card_priv;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ priv->is_stream_in_use[tx] = false;
+
+ return 0;
+}
+
+static int imx_cs42888_surround_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ static u32 support_rates[SUPPORT_RATE_NUM];
+ int ret;
+
+ priv->mclk_freq = clk_get_rate(priv->codec_clk);
+
+ if (priv->mclk_freq % 12288000 == 0) {
+ support_rates[0] = 48000;
+ support_rates[1] = 96000;
+ support_rates[2] = 192000;
+ constraint_rates.list = support_rates;
+ constraint_rates.count = 3;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+ } else
+ dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq);
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_cs42888_surround_ops = {
+ .startup = imx_cs42888_surround_startup,
+ .hw_params = imx_cs42888_surround_hw_params,
+ .hw_free = imx_cs42888_surround_hw_free,
+};
+
+/**
+ * imx_cs42888_surround_startup() is to set constrain for hw parameter, but
+ * backend use same runtime as frontend, for p2p backend need to use different
+ * parameter, so backend can't use the startup.
+ */
+static struct snd_soc_ops imx_cs42888_surround_ops_be = {
+ .hw_params = imx_cs42888_surround_hw_params,
+ .hw_free = imx_cs42888_surround_hw_free,
+};
+
+
+static const struct snd_soc_dapm_widget imx_cs42888_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Line out jack */
+ {"Line Out Jack", NULL, "AOUT1L"},
+ {"Line Out Jack", NULL, "AOUT1R"},
+ {"Line Out Jack", NULL, "AOUT2L"},
+ {"Line Out Jack", NULL, "AOUT2R"},
+ {"Line Out Jack", NULL, "AOUT3L"},
+ {"Line Out Jack", NULL, "AOUT3R"},
+ {"Line Out Jack", NULL, "AOUT4L"},
+ {"Line Out Jack", NULL, "AOUT4R"},
+ {"AIN1L", NULL, "Line In Jack"},
+ {"AIN1R", NULL, "Line In Jack"},
+ {"AIN2L", NULL, "Line In Jack"},
+ {"AIN2R", NULL, "Line In Jack"},
+ {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */
+ {"CPU-Capture", NULL, "Capture"},
+ {"CPU-Playback", NULL, "ASRC-Playback"},
+ {"ASRC-Capture", NULL, "CPU-Capture"},
+};
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params) {
+
+ struct imx_priv *priv = &card_priv;
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = priv->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, priv->asrc_format);
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42888")),
+ 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_CODEC(NULL, "cs42888")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+static struct snd_soc_dai_link imx_cs42888_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .ops = &imx_cs42888_surround_ops,
+ .ignore_pmdown_time = 1,
+ SND_SOC_DAILINK_REG(hifi),
+ },
+ {
+ .name = "HiFi-ASRC-FE",
+ .stream_name = "HiFi-ASRC-FE",
+ .dynamic = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .dpcm_merged_chan = 1,
+ SND_SOC_DAILINK_REG(hifi_fe),
+ },
+ {
+ .name = "HiFi-ASRC-BE",
+ .stream_name = "HiFi-ASRC-BE",
+ .no_pcm = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &imx_cs42888_surround_ops_be,
+ .be_hw_params_fixup = be_hw_params_fixup,
+ SND_SOC_DAILINK_REG(hifi_be),
+ },
+};
+
+static struct snd_soc_card snd_soc_card_imx_cs42888 = {
+ .name = "cs42888-audio",
+ .dai_link = imx_cs42888_dai,
+ .dapm_widgets = imx_cs42888_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(imx_cs42888_dapm_widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+ .owner = THIS_MODULE,
+};
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ */
+static int imx_cs42888_probe(struct platform_device *pdev)
+{
+ struct device_node *esai_np, *codec_np;
+ struct device_node *asrc_np = NULL;
+ struct platform_device *esai_pdev;
+ struct platform_device *asrc_pdev = NULL;
+ struct imx_priv *priv = &card_priv;
+ int ret;
+ u32 width;
+
+ priv->pdev = pdev;
+ priv->asrc_pdev = NULL;
+
+ if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg"))
+ priv->is_codec_rpmsg = true;
+
+ esai_np = of_parse_phandle(pdev->dev.of_node, "esai-controller", 0);
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!esai_np || !codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
+ }
+
+ esai_pdev = of_find_device_by_node(esai_np);
+ if (!esai_pdev) {
+ dev_err(&pdev->dev, "failed to find ESAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (priv->is_codec_rpmsg) {
+ struct platform_device *codec_dev;
+
+ codec_dev = of_find_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL);
+ if (IS_ERR(priv->codec_clk)) {
+ ret = PTR_ERR(priv->codec_clk);
+ dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+
+ } else {
+ struct i2c_client *codec_dev;
+
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EPROBE_DEFER;
+ goto fail;
+ }
+
+ priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL);
+ if (IS_ERR(priv->codec_clk)) {
+ ret = PTR_ERR(priv->codec_clk);
+ dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+ }
+
+ if (priv->is_codec_rpmsg) {
+ imx_cs42888_dai[0].codecs->name = "rpmsg-audio-codec-cs42888";
+ imx_cs42888_dai[0].codecs->dai_name = "cs42888";
+ } else {
+ imx_cs42888_dai[0].codecs->of_node = codec_np;
+ }
+
+ /*if there is no asrc controller, we only enable one device*/
+ if (!asrc_pdev) {
+ imx_cs42888_dai[0].cpus->dai_name = dev_name(&esai_pdev->dev);
+ imx_cs42888_dai[0].platforms->of_node = esai_np;
+ snd_soc_card_imx_cs42888.num_links = 1;
+ snd_soc_card_imx_cs42888.num_dapm_routes =
+ ARRAY_SIZE(audio_map) - 2;
+ } else {
+ imx_cs42888_dai[0].cpus->dai_name = dev_name(&esai_pdev->dev);
+ imx_cs42888_dai[0].platforms->of_node = esai_np;
+ imx_cs42888_dai[1].cpus->of_node = asrc_np;
+ imx_cs42888_dai[1].platforms->of_node = asrc_np;
+ imx_cs42888_dai[2].cpus->dai_name = dev_name(&esai_pdev->dev);
+ snd_soc_card_imx_cs42888.num_links = 3;
+
+ if (priv->is_codec_rpmsg) {
+ imx_cs42888_dai[2].codecs->name = "rpmsg-audio-codec-cs42888";
+ imx_cs42888_dai[2].codecs->dai_name = "cs42888";
+ } else {
+ imx_cs42888_dai[2].codecs->of_node = codec_np;
+ }
+
+ 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 fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ priv->esai_clk = devm_clk_get(&esai_pdev->dev, "extal");
+ if (IS_ERR(priv->esai_clk)) {
+ ret = PTR_ERR(priv->esai_clk);
+ dev_err(&esai_pdev->dev, "failed to get cpu clk: %d\n", ret);
+ goto fail;
+ }
+
+ priv->is_codec_master = false;
+ if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
+ priv->is_codec_master = true;
+
+ snd_soc_card_imx_cs42888.dev = &pdev->dev;
+
+ platform_set_drvdata(pdev, &snd_soc_card_imx_cs42888);
+
+ ret = snd_soc_register_card(&snd_soc_card_imx_cs42888);
+ if (ret)
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+fail:
+ if (asrc_np)
+ of_node_put(asrc_np);
+ if (esai_np)
+ of_node_put(esai_np);
+ if (codec_np)
+ of_node_put(codec_np);
+ return ret;
+}
+
+static int imx_cs42888_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&snd_soc_card_imx_cs42888);
+ return 0;
+}
+
+static const struct of_device_id imx_cs42888_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-cs42888", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver imx_cs42888_driver = {
+ .probe = imx_cs42888_probe,
+ .remove = imx_cs42888_remove,
+ .driver = {
+ .name = "imx-cs42888",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_cs42888_dt_ids,
+ },
+};
+module_platform_driver(imx_cs42888_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("ALSA SoC cs42888 Machine Layer Driver");
+MODULE_ALIAS("platform:imx-cs42888");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-dsp.c b/sound/soc/fsl/imx-dsp.c
new file mode 100644
index 000000000000..f60d55073d8e
--- /dev/null
+++ b/sound/soc/fsl/imx-dsp.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: (GPL-2.0+
+//
+// DSP machine driver
+//
+// Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED.
+// Copyright 2018 NXP
+
+#include <linux/module.h>
+#include <linux/of_platform.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>
+
+struct imx_dsp_audio_data {
+ struct snd_soc_dai_link dai[2];
+ struct snd_soc_card card;
+};
+
+static int imx_dsp_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int ret;
+ u32 dai_format = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ 24576000, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(rtd->dev, "failed to set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret) {
+ dev_err(rtd->dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_dsp_ops_be = {
+ .hw_params = imx_dsp_hw_params,
+};
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params) {
+
+ struct snd_interval *rate;
+ struct snd_interval *channels;
+ struct snd_mask *mask;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = 48000;
+
+ channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ channels->max = channels->min = 2;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, SNDRV_PCM_FORMAT_S16_LE);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_route imx_dsp_audio_map[] = {
+ {"Playback", NULL, "Compress Playback"},/* dai route for be and fe */
+ {"Playback", NULL, "Playback"},
+};
+
+static int imx_dsp_audio_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np=NULL, *codec_np=NULL, *platform_np=NULL;
+ struct snd_soc_dai_link_component *comp;
+ struct platform_device *cpu_pdev;
+ struct imx_dsp_audio_data *data;
+ int ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ comp = devm_kzalloc(&pdev->dev, 6 * sizeof(*comp), GFP_KERNEL);
+ if (!comp) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 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 rpmsg platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ platform_np = of_parse_phandle(pdev->dev.of_node, "audio-platform", 0);
+ if (!platform_np) {
+ dev_err(&pdev->dev, "platform missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+
+ data->dai[0].cpus = &comp[0];
+ data->dai[0].codecs = &comp[1];
+ data->dai[0].platforms = &comp[2];
+
+ data->dai[0].num_cpus = 1;
+ data->dai[0].num_codecs = 1;
+ data->dai[0].num_platforms = 1;
+
+ data->dai[0].name = "dsp hifi fe";
+ data->dai[0].stream_name = "dsp hifi fe";
+ data->dai[0].codecs->dai_name = "snd-soc-dummy-dai";
+ data->dai[0].codecs->name = "snd-soc-dummy";
+ data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai[0].cpus->of_node = cpu_np;
+ data->dai[0].platforms->of_node = platform_np;
+ data->dai[0].playback_only = true;
+ data->dai[0].capture_only = false;
+ data->dai[0].dpcm_playback = 1;
+ data->dai[0].dpcm_capture = 0;
+ data->dai[0].dynamic = 1,
+ data->dai[0].ignore_pmdown_time = 1,
+ data->dai[0].dai_fmt = SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ data->dai[1].cpus = &comp[3];
+ data->dai[1].codecs = &comp[4];
+ data->dai[1].platforms = &comp[5];
+
+ data->dai[1].num_cpus = 1;
+ data->dai[1].num_codecs = 1;
+ data->dai[1].num_platforms = 1;
+
+ data->dai[1].name = "dsp hifi be";
+ data->dai[1].stream_name = "dsp hifi be";
+ data->dai[1].codecs->dai_name = "cs42888";
+ data->dai[1].codecs->of_node = codec_np;
+ data->dai[1].cpus->dai_name = "snd-soc-dummy-dai";
+ data->dai[1].cpus->name = "snd-soc-dummy";
+ data->dai[1].platforms->name = "snd-soc-dummy";
+ data->dai[1].playback_only = true;
+ data->dai[1].capture_only = false;
+ data->dai[1].dpcm_playback = 1;
+ data->dai[1].dpcm_capture = 0;
+ data->dai[1].no_pcm = 1,
+ data->dai[1].ignore_pmdown_time = 1,
+ data->dai[1].dai_fmt = SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+ data->dai[1].ops = &imx_dsp_ops_be;
+ data->dai[1].be_hw_params_fixup = be_hw_params_fixup;
+
+ data->card.dapm_routes = imx_dsp_audio_map;
+ data->card.num_dapm_routes = ARRAY_SIZE(imx_dsp_audio_map);
+ data->card.num_links = 2;
+ data->card.dai_link = data->dai;
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (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(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+ if (platform_np)
+ of_node_put(platform_np);
+ return ret;
+}
+
+static const struct of_device_id imx_dsp_audio_dt_ids[] = {
+ { .compatible = "fsl,imx-dsp-audio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_dsp_audio_dt_ids);
+
+static struct platform_driver imx_dsp_audio_driver = {
+ .driver = {
+ .name = "imx-dsp-audio",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_dsp_audio_dt_ids,
+ },
+ .probe = imx_dsp_audio_probe,
+};
+module_platform_driver(imx_dsp_audio_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/imx-hdmi-dma.c b/sound/soc/fsl/imx-hdmi-dma.c
new file mode 100644
index 000000000000..95402a23560a
--- /dev/null
+++ b/sound/soc/fsl/imx-hdmi-dma.c
@@ -0,0 +1,1193 @@
+/*
+ * imx-hdmi-dma.c -- HDMI DMA driver for ALSA Soc Audio Layer
+ *
+ * Copyright (C) 2011-2016 Freescale Semiconductor, Inc.
+ *
+ * based on imx-pcm-dma-mx2.c
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <linux/platform_data/dma-imx.h>
+
+#include <video/mxc_hdmi.h>
+
+#include "imx-hdmi.h"
+
+#define DRV_NAME "imx_hdmi_dma"
+
+#define HDMI_DMA_BURST_UNSPECIFIED_LEGNTH 0
+#define HDMI_DMA_BURST_INCR4 1
+#define HDMI_DMA_BURST_INCR8 2
+#define HDMI_DMA_BURST_INCR16 3
+
+#define HDMI_BASE_ADDR 0x00120000
+
+struct hdmi_sdma_script {
+ int control_reg_addr;
+ int status_reg_addr;
+ int dma_start_addr;
+ u32 buffer[20];
+};
+
+struct hdmi_dma_priv {
+ struct snd_pcm_substream *substream;
+ struct platform_device *pdev;
+
+ struct snd_dma_buffer hw_buffer;
+ unsigned long buffer_bytes;
+ unsigned long appl_bytes;
+
+ int periods;
+ int period_time;
+ int period_bytes;
+ int dma_period_bytes;
+ int buffer_ratio;
+
+ unsigned long offset;
+
+ snd_pcm_format_t format;
+ int sample_align;
+ int sample_bits;
+ int channels;
+ int rate;
+
+ int frame_idx;
+
+ bool tx_active;
+ spinlock_t irq_lock;
+
+ /* SDMA part */
+ dma_addr_t phy_hdmi_sdma_t;
+ struct hdmi_sdma_script *hdmi_sdma_t;
+ struct dma_chan *dma_channel;
+ struct dma_async_tx_descriptor *desc;
+ struct imx_hdmi_sdma_params sdma_params;
+};
+
+/* bit 0:0:0:b:p(0):c:(u)0:(v)0 */
+/* max 8 channels supported; channels are interleaved */
+static u8 g_packet_head_table[48 * 8];
+
+void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
+ int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_16_neon_fast(unsigned short *src, unsigned int *dst,
+ int samples);
+void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
+ int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_24_neon_fast(unsigned int *src, unsigned int *dst,
+ int samples);
+static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv);
+static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv);
+
+union hdmi_audio_header_t iec_header;
+EXPORT_SYMBOL(iec_header);
+
+/*
+ * Note that the period size for DMA != period size for ALSA because the
+ * driver adds iec frame info to the audio samples (in hdmi_dma_copy).
+ *
+ * Each 4 byte subframe = 1 byte of iec data + 3 byte audio sample.
+ *
+ * A 16 bit audio sample becomes 32 bits including the frame info. Ratio=2
+ * A 24 bit audio sample becomes 32 bits including the frame info. Ratio=3:4
+ * If the 24 bit raw audio is in 32 bit words, the
+ *
+ * Original Packed into subframe Ratio of size Format
+ * sample how many size of DMA buffer
+ * (bits) bits to ALSA buffer
+ * -------- ----------- -------- -------------- ------------------------
+ * 16 16 32 2 SNDRV_PCM_FORMAT_S16_LE
+ * 24 24 32 1.33 SNDRV_PCM_FORMAT_S24_3LE*
+ * 24 32 32 1 SNDRV_PCM_FORMAT_S24_LE
+ *
+ * *so SNDRV_PCM_FORMAT_S24_3LE is not supported.
+ */
+
+/*
+ * The minimum dma period is one IEC audio frame (192 * 4 * channels).
+ * The maximum dma period for the HDMI DMA is 8K.
+ *
+ * channels minimum maximum
+ * dma period dma period
+ * -------- ------------------ ----------
+ * 2 192 * 4 * 2 = 1536 * 4 = 6144
+ * 4 192 * 4 * 4 = 3072 * 2 = 6144
+ * 6 192 * 4 * 6 = 4608 * 1 = 4608
+ * 8 192 * 4 * 8 = 6144 * 1 = 6144
+ *
+ * Bottom line:
+ * 1. Must keep the ratio of DMA buffer to ALSA buffer consistent.
+ * 2. frame_idx is saved in the private data, so even if a frame cannot be
+ * transmitted in a period, it can be continued in the next period. This
+ * is necessary for 6 ch.
+ */
+#define HDMI_DMA_PERIOD_BYTES (12288)
+#define HDMI_DMA_BUF_SIZE (128 * 1024)
+#define HDMI_PCM_BUF_SIZE (128 * 1024)
+
+#define hdmi_audio_debug(dev, reg) \
+ dev_dbg(dev, #reg ": 0x%02x\n", hdmi_readb(reg))
+
+#ifdef DEBUG
+static void dumpregs(struct device *dev)
+{
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_START);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_STOP);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_STRADDR0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_STPADDR0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BSTADDR0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH1);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_STAT);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_INT);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_MASK);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_POL);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF1);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFSTAT);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFINT);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFMASK);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFPOL);
+ hdmi_audio_debug(dev, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+ hdmi_audio_debug(dev, HDMI_IH_AHBDMAAUD_STAT0);
+ hdmi_audio_debug(dev, HDMI_IH_MUTE);
+}
+
+static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv)
+{
+ dev_dbg(dev, "channels = %d\n", priv->channels);
+ dev_dbg(dev, "periods = %d\n", priv->periods);
+ dev_dbg(dev, "period_bytes = %d\n", priv->period_bytes);
+ dev_dbg(dev, "dma period_bytes = %d\n", priv->dma_period_bytes);
+ dev_dbg(dev, "buffer_ratio = %d\n", priv->buffer_ratio);
+ dev_dbg(dev, "hw dma buffer = 0x%08x\n", (int)priv->hw_buffer.addr);
+ dev_dbg(dev, "dma buf size = %d\n", (int)priv->buffer_bytes);
+ dev_dbg(dev, "sample_rate = %d\n", (int)priv->rate);
+}
+#else
+static void dumpregs(struct device *dev) {}
+static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) {}
+#endif
+
+/*
+ * Conditions for DMA to work:
+ * ((final_addr - initial_addr)>>2)+1) < 2k. So max period is 8k.
+ * (inital_addr & 0x3) == 0
+ * (final_addr & 0x3) == 0x3
+ *
+ * The DMA Period should be an integer multiple of the IEC 60958 audio
+ * frame size, which is 768 bytes (192 * 4).
+ */
+static void hdmi_dma_set_addr(int start_addr, int dma_period_bytes)
+{
+ int final_addr = start_addr + dma_period_bytes - 1;
+
+ hdmi_write4(start_addr, HDMI_AHB_DMA_STRADDR0);
+ hdmi_write4(final_addr, HDMI_AHB_DMA_STPADDR0);
+}
+
+static void hdmi_dma_irq_set(bool set)
+{
+ u8 val = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+ if (set)
+ val |= HDMI_AHB_DMA_DONE;
+ else
+ val &= (u8)~HDMI_AHB_DMA_DONE;
+
+ hdmi_writeb(val, HDMI_AHB_DMA_MASK);
+}
+
+static void hdmi_mask(int mask)
+{
+ u8 regval = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+ if (mask)
+ regval |= HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY;
+ else
+ regval &= (u8)~(HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY);
+
+ hdmi_writeb(regval, HDMI_AHB_DMA_MASK);
+}
+
+int odd_ones(unsigned a)
+{
+ a ^= a >> 8;
+ a ^= a >> 4;
+ a ^= a >> 2;
+ a ^= a >> 1;
+
+ return a & 1;
+}
+
+/* Add frame information for one pcm subframe */
+static u32 hdmi_dma_add_frame_info(struct hdmi_dma_priv *priv,
+ u32 pcm_data, int subframe_idx)
+{
+ union hdmi_audio_dma_data_t subframe;
+
+ subframe.U = 0;
+ iec_header.B.channel = subframe_idx;
+
+ /* fill b (start-of-block) */
+ subframe.B.b = (priv->frame_idx == 0) ? 1 : 0;
+
+ /* fill c (channel status) */
+ if (priv->frame_idx < 42)
+ subframe.B.c = (iec_header.U >> priv->frame_idx) & 0x1;
+ else
+ subframe.B.c = 0;
+
+ subframe.B.p = odd_ones(pcm_data);
+ subframe.B.p ^= subframe.B.c;
+ subframe.B.p ^= subframe.B.u;
+ subframe.B.p ^= subframe.B.v;
+
+ /* fill data */
+ if (priv->sample_bits == 16)
+ subframe.B.data = pcm_data << 8;
+ else
+ subframe.B.data = pcm_data;
+
+ return subframe.U;
+}
+
+static void init_table(int channels)
+{
+ unsigned char *p = g_packet_head_table;
+ int i, ch = 0;
+
+ for (i = 0; i < 48; i++) {
+ int b = 0;
+ if (i == 0)
+ b = 1;
+
+ for (ch = 0; ch < channels; ch++) {
+ int c = 0;
+ if (i < 42) {
+ iec_header.B.channel = ch+1;
+ c = (iec_header.U >> i) & 0x1;
+ }
+ /* preset bit p as c */
+ *p++ = (b << 4) | (c << 2) | (c << 3);
+ }
+ }
+}
+
+/*
+ * FIXME: Disable NEON Optimization in hdmi, or it will cause crash of
+ * pulseaudio in the userspace. There is no issue for the Optimization
+ * implemenation, if only use "VPUSH, VPOP" in the function, the pulseaudio
+ * will crash also. So for my assumption, we can't use the NEON in the
+ * interrupt.(tasklet is implemented by softirq.)
+ * Disable SMP, preempt, change the dma buffer to nocached, add protection of
+ * Dn register and fpscr, all these operation have no effect to the result.
+ */
+#define HDMI_DMA_NO_NEON
+
+#ifdef HDMI_DMA_NO_NEON
+/* Optimization for IEC head */
+static void hdmi_dma_copy_16_c_lut(u16 *src, u32 *dst, int samples,
+ u8 *lookup_table)
+{
+ u32 sample, head, p;
+ int i;
+
+ for (i = 0; i < samples; i++) {
+ /* get source sample */
+ sample = *src++;
+
+ /* xor every bit */
+ p = sample ^ (sample >> 8);
+ p ^= (p >> 4);
+ p ^= (p >> 2);
+ p ^= (p >> 1);
+ p &= 1; /* only want last bit */
+ p <<= 3; /* bit p */
+
+ /* get packet header */
+ head = *lookup_table++;
+
+ /* fix head */
+ head ^= p;
+
+ /* store */
+ *dst++ = (head << 24) | (sample << 8);
+ }
+}
+
+static void hdmi_dma_copy_16_c_fast(u16 *src, u32 *dst, int samples)
+{
+ u32 sample, p;
+ int i;
+
+ for (i = 0; i < samples; i++) {
+ /* get source sample */
+ sample = *src++;
+
+ /* xor every bit */
+ p = sample ^ (sample >> 8);
+ p ^= (p >> 4);
+ p ^= (p >> 2);
+ p ^= (p >> 1);
+ p &= 1; /* only want last bit */
+ p <<= 3; /* bit p */
+
+ /* store */
+ *dst++ = (p << 24) | (sample << 8);
+ }
+}
+
+static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecnt, int channelcnt)
+{
+ /* split input frames into 192-frame each */
+ int count_in_192 = (framecnt + 191) / 192;
+ int i;
+
+ for (i = 0; i < count_in_192; i++) {
+ int count, samples;
+
+ /* handles frame index [0, 48) */
+ count = (framecnt < 48) ? framecnt : 48;
+ samples = count * channelcnt;
+ hdmi_dma_copy_16_c_lut(src, dst, samples, g_packet_head_table);
+ framecnt -= count;
+ if (framecnt == 0)
+ break;
+
+ src += samples;
+ dst += samples;
+
+ /* handles frame index [48, 192) */
+ count = (framecnt < 192 - 48) ? framecnt : 192 - 48;
+ samples = count * channelcnt;
+ hdmi_dma_copy_16_c_fast(src, dst, samples);
+ framecnt -= count;
+ src += samples;
+ dst += samples;
+ }
+}
+#else
+/* NEON optimization for IEC head*/
+
+/**
+ * Convert pcm samples to iec samples suitable for HDMI transfer.
+ * PCM sample is 16 bits length.
+ * Frame index always starts from 0.
+ * Channel count can be 1, 2, 4, 6, or 8
+ * Sample count (frame_count * channel_count) is multipliable by 8.
+ */
+static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecount, int channelcount)
+{
+ /* split input frames into 192-frame each */
+ int i, count_in_192 = (framecount + 191) / 192;
+
+ for (i = 0; i < count_in_192; i++) {
+ int count, samples;
+
+ /* handles frame index [0, 48) */
+ count = (framecount < 48) ? framecount : 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_16_neon_lut(src, dst, samples, g_packet_head_table);
+ framecount -= count;
+ if (framecount == 0)
+ break;
+
+ src += samples;
+ dst += samples;
+
+ /* handles frame index [48, 192) */
+ count = (framecount < 192 - 48) ? framecount : (192 - 48);
+ samples = count * channelcount;
+ hdmi_dma_copy_16_neon_fast(src, dst, samples);
+ framecount -= count;
+ src += samples;
+ dst += samples;
+ }
+}
+#endif
+
+static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream,
+ int offset, int count)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ struct device *dev = component->dev;
+ u32 framecount, *dst;
+ u16 *src16;
+
+ framecount = count / (priv->sample_align * priv->channels);
+
+ /* hw_buffer is the destination for pcm data plus frame info. */
+ dst = (u32 *)(priv->hw_buffer.area + (offset * priv->buffer_ratio));
+
+ switch (priv->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ /* dma_buffer is the mmapped buffer we are copying pcm from. */
+ src16 = (u16 *)(runtime->dma_area + offset);
+ hdmi_dma_copy_16(src16, dst, framecount, priv->channels);
+ break;
+ default:
+ dev_err(dev, "unsupported sample format %s\n",
+ snd_pcm_format_name(priv->format));
+ return;
+ }
+}
+
+static void hdmi_dma_data_copy(struct snd_pcm_substream *substream,
+ struct hdmi_dma_priv *priv, char type)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long offset, count, appl_bytes, space_to_end;
+
+ if (runtime->access != SNDRV_PCM_ACCESS_MMAP_INTERLEAVED)
+ return;
+
+ appl_bytes = (runtime->status->hw_ptr % (priv->buffer_bytes/(runtime->frame_bits/8))) * (runtime->frame_bits/8);
+ if (type == 'p')
+ appl_bytes += 2 * priv->period_bytes;
+ offset = appl_bytes % priv->buffer_bytes;
+
+ switch (type) {
+ case 'p':
+ count = priv->period_bytes;
+ space_to_end = priv->period_bytes;
+ break;
+ case 'b':
+ count = priv->buffer_bytes;
+ space_to_end = priv->buffer_bytes - offset;
+
+ break;
+ default:
+ return;
+ }
+
+ if (count <= space_to_end) {
+ hdmi_dma_mmap_copy(substream, offset, count);
+ } else {
+ hdmi_dma_mmap_copy(substream, offset, space_to_end);
+ hdmi_dma_mmap_copy(substream, 0, count - space_to_end);
+ }
+}
+
+static void hdmi_sdma_callback(void *data)
+{
+ struct hdmi_dma_priv *priv = (struct hdmi_dma_priv *)data;
+ struct snd_pcm_substream *substream = priv->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->irq_lock, flags);
+
+ if (runtime && runtime->dma_area && priv->tx_active) {
+ priv->offset += priv->period_bytes;
+ priv->offset %= priv->period_bytes * priv->periods;
+
+ /* Copy data by period_bytes */
+ hdmi_dma_data_copy(substream, priv, 'p');
+
+ snd_pcm_period_elapsed(substream);
+ }
+
+ spin_unlock_irqrestore(&priv->irq_lock, flags);
+
+ return;
+}
+
+static int hdmi_dma_set_thrsld_incrtype(struct device *dev, int channels)
+{
+ u8 mask = HDMI_AHB_DMA_CONF0_BURST_MODE | HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK;
+ u8 val = hdmi_readb(HDMI_AHB_DMA_CONF0) & ~mask;
+ int incr_type, threshold;
+
+ switch (hdmi_readb(HDMI_REVISION_ID)) {
+ case 0x0a:
+ incr_type = HDMI_DMA_BURST_INCR4;
+ if (channels == 2)
+ threshold = 126;
+ else
+ threshold = 124;
+ break;
+ case 0x1a:
+ incr_type = HDMI_DMA_BURST_INCR8;
+ threshold = 128;
+ break;
+ default:
+ dev_err(dev, "unknown hdmi controller!\n");
+ return -ENODEV;
+ }
+
+ hdmi_writeb(threshold, HDMI_AHB_DMA_THRSLD);
+
+ switch (incr_type) {
+ case HDMI_DMA_BURST_UNSPECIFIED_LEGNTH:
+ break;
+ case HDMI_DMA_BURST_INCR4:
+ val |= HDMI_AHB_DMA_CONF0_BURST_MODE;
+ break;
+ case HDMI_DMA_BURST_INCR8:
+ val |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR8;
+ break;
+ case HDMI_DMA_BURST_INCR16:
+ val |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR16;
+ break;
+ default:
+ dev_err(dev, "invalid increment type: %d!", incr_type);
+ return -EINVAL;
+ }
+
+ hdmi_writeb(val, HDMI_AHB_DMA_CONF0);
+
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD);
+
+ return 0;
+}
+
+static int hdmi_dma_configure_dma(struct device *dev, int channels)
+{
+ u8 i, val = 0;
+ int ret;
+
+ if (channels <= 0 || channels > 8 || channels % 2 != 0) {
+ dev_err(dev, "unsupported channel number: %d\n", channels);
+ return -EINVAL;
+ }
+
+ hdmi_audio_writeb(AHB_DMA_CONF0, EN_HLOCK, 0x1);
+
+ ret = hdmi_dma_set_thrsld_incrtype(dev, channels);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < channels; i += 2)
+ val |= 0x3 << i;
+
+ hdmi_writeb(val, HDMI_AHB_DMA_CONF1);
+
+ return 0;
+}
+
+static void hdmi_dma_init_iec_header(void)
+{
+ iec_header.U = 0;
+
+ iec_header.B.consumer = 0; /* Consumer use */
+ iec_header.B.linear_pcm = 0; /* linear pcm audio */
+ iec_header.B.copyright = 1; /* no copyright */
+ iec_header.B.pre_emphasis = 0; /* 2 channels without pre-emphasis */
+ iec_header.B.mode = 0; /* Mode 0 */
+
+ iec_header.B.category_code = 0;
+
+ iec_header.B.source = 2; /* stereo */
+ iec_header.B.channel = 0;
+
+ iec_header.B.sample_freq = 0x02; /* 48 KHz */
+ iec_header.B.clock_acc = 0; /* Level II */
+
+ iec_header.B.word_length = 0x02; /* 16 bits */
+ iec_header.B.org_sample_freq = 0x0D; /* 48 KHz */
+
+ iec_header.B.cgms_a = 0; /* Copying is permitted without restriction */
+}
+
+static int hdmi_dma_update_iec_header(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ struct device *dev = component->dev;
+
+ iec_header.B.source = priv->channels;
+
+ switch (priv->rate) {
+ case 32000:
+ iec_header.B.sample_freq = 0x03;
+ iec_header.B.org_sample_freq = 0x0C;
+ break;
+ case 44100:
+ iec_header.B.sample_freq = 0x00;
+ iec_header.B.org_sample_freq = 0x0F;
+ break;
+ case 48000:
+ iec_header.B.sample_freq = 0x02;
+ iec_header.B.org_sample_freq = 0x0D;
+ break;
+ case 88200:
+ iec_header.B.sample_freq = 0x08;
+ iec_header.B.org_sample_freq = 0x07;
+ break;
+ case 96000:
+ iec_header.B.sample_freq = 0x0A;
+ iec_header.B.org_sample_freq = 0x05;
+ break;
+ case 176400:
+ iec_header.B.sample_freq = 0x0C;
+ iec_header.B.org_sample_freq = 0x03;
+ break;
+ case 192000:
+ iec_header.B.sample_freq = 0x0E;
+ iec_header.B.org_sample_freq = 0x01;
+ break;
+ default:
+ dev_err(dev, "unsupported sample rate\n");
+ return -EFAULT;
+ }
+
+ switch (priv->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iec_header.B.word_length = 0x02;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iec_header.B.word_length = 0x0b;
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+/*
+ * The HDMI block transmits the audio data without adding any of the audio
+ * frame bits. So we have to copy the raw dma data from the ALSA buffer
+ * to the DMA buffer, adding the frame information.
+ */
+static int hdmi_dma_copy_user(struct snd_pcm_substream *substream, int channel,
+ unsigned long pos_bytes, void __user *buf,
+ unsigned long count)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ u32 *hw_buf;
+ int subframe_idx;
+ u32 pcm_data;
+
+ /* Adding frame info to pcm data from userspace and copy to hw_buffer */
+ hw_buf = (u32 *)(priv->hw_buffer.area + (pos_bytes * priv->buffer_ratio));
+
+ while (count > 0) {
+ for (subframe_idx = 1 ; subframe_idx <= priv->channels ; subframe_idx++) {
+ if (copy_from_user(&pcm_data, buf, priv->sample_align))
+ return -EFAULT;
+
+ buf += priv->sample_align;
+ count -= priv->sample_align;
+
+ /* Save the header info to the audio dma buffer */
+ *hw_buf++ = hdmi_dma_add_frame_info(priv, pcm_data, subframe_idx);
+ }
+
+ priv->frame_idx++;
+ if (priv->frame_idx == 192)
+ priv->frame_idx = 0;
+ }
+
+ return 0;
+}
+
+static int hdmi_sdma_initbuf(struct device *dev, struct hdmi_dma_priv *priv)
+{
+ struct hdmi_sdma_script *hdmi_sdma_t = priv->hdmi_sdma_t;
+ u32 *head, *tail, i;
+
+ if (!hdmi_sdma_t) {
+ dev_err(dev, "hdmi private addr invalid!!!\n");
+ return -EINVAL;
+ }
+
+ hdmi_sdma_t->control_reg_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_START;
+ hdmi_sdma_t->status_reg_addr = HDMI_BASE_ADDR + HDMI_IH_AHBDMAAUD_STAT0;
+ hdmi_sdma_t->dma_start_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_STRADDR0;
+
+ head = &hdmi_sdma_t->buffer[0];
+ tail = &hdmi_sdma_t->buffer[1];
+
+ for (i = 0; i < priv->sdma_params.buffer_num; i++) {
+ *head = priv->hw_buffer.addr + i * priv->period_bytes * priv->buffer_ratio;
+ *tail = *head + priv->dma_period_bytes - 1;
+ head += 2;
+ tail += 2;
+ }
+
+ return 0;
+}
+
+static int hdmi_sdma_config(struct snd_pcm_substream *substream,
+ struct hdmi_dma_priv *priv)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct device *dai_dev = &priv->pdev->dev;
+ struct device *dev = component->dev;
+ struct dma_slave_config slave_config;
+ int ret;
+
+ priv->dma_channel = dma_request_slave_channel(dai_dev, "tx");
+ if (priv->dma_channel == NULL) {
+ dev_err(dev, "failed to alloc dma channel\n");
+ return -EBUSY;
+ }
+
+ slave_config.direction = DMA_TRANS_NONE;
+ slave_config.src_addr = (dma_addr_t)priv->sdma_params.buffer_num;
+ slave_config.dst_addr = (dma_addr_t)priv->sdma_params.phyaddr;
+
+ ret = dmaengine_slave_config(priv->dma_channel, &slave_config);
+ if (ret) {
+ dev_err(dev, "failed to config slave dma\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hdmi_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+
+ if (priv->dma_channel) {
+ dma_release_channel(priv->dma_channel);
+ priv->dma_channel = NULL;
+ }
+
+ return 0;
+}
+
+static int hdmi_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct device *dev = component->dev;
+ int ret;
+
+ priv->buffer_bytes = params_buffer_bytes(params);
+ priv->periods = params_periods(params);
+ priv->period_bytes = params_period_bytes(params);
+ priv->channels = params_channels(params);
+ priv->format = params_format(params);
+ priv->rate = params_rate(params);
+
+ priv->offset = 0;
+ priv->period_time = HZ / (priv->rate / params_period_size(params));
+
+ switch (priv->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ priv->buffer_ratio = 2;
+ priv->sample_align = 2;
+ priv->sample_bits = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ /* 24 bit audio in 32 bit word */
+ priv->buffer_ratio = 1;
+ priv->sample_align = 4;
+ priv->sample_bits = 24;
+ break;
+ default:
+ dev_err(dev, "unsupported sample format: %d\n", priv->format);
+ return -EINVAL;
+ }
+
+ priv->dma_period_bytes = priv->period_bytes * priv->buffer_ratio;
+ priv->sdma_params.buffer_num = priv->periods;
+ priv->sdma_params.phyaddr = priv->phy_hdmi_sdma_t;
+
+ ret = hdmi_sdma_initbuf(dev, priv);
+ if (ret)
+ return ret;
+
+ ret = hdmi_sdma_config(substream, priv);
+ if (ret)
+ return ret;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ ret = hdmi_dma_configure_dma(dev, priv->channels);
+ if (ret)
+ return ret;
+
+ hdmi_dma_set_addr(priv->hw_buffer.addr, priv->dma_period_bytes);
+
+ dumppriv(dev, priv);
+
+ hdmi_dma_update_iec_header(substream);
+
+ /* Init par for mmap optimizate */
+ init_table(priv->channels);
+
+ priv->appl_bytes = 0;
+
+ return 0;
+}
+
+static void hdmi_dma_trigger_init(struct snd_pcm_substream *substream,
+ struct hdmi_dma_priv *priv)
+{
+ unsigned long status;
+
+ priv->offset = 0;
+ priv->frame_idx = 0;
+
+ /* Copy data by buffer_bytes */
+ hdmi_dma_data_copy(substream, priv, 'b');
+
+ hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1);
+
+ /* Delay after reset */
+ udelay(1);
+
+ status = hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0);
+ hdmi_writeb(status, HDMI_IH_AHBDMAAUD_STAT0);
+}
+
+static int hdmi_dma_prepare_and_submit(struct snd_pcm_substream *substream,
+ struct hdmi_dma_priv *priv)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct device *dev = component->dev;
+
+ priv->desc = dmaengine_prep_dma_cyclic(priv->dma_channel, 0, 0, 0,
+ DMA_TRANS_NONE, 0);
+ if (!priv->desc) {
+ dev_err(dev, "failed to prepare slave dma\n");
+ return -EINVAL;
+ }
+
+ priv->desc->callback = hdmi_sdma_callback;
+ priv->desc->callback_param = (void *)priv;
+ dmaengine_submit(priv->desc);
+
+ return 0;
+}
+
+static int hdmi_dma_trigger(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_component *component =
+ snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ struct device *dev = component->dev;
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!check_hdmi_state())
+ return 0;
+ hdmi_dma_trigger_init(substream, priv);
+
+ dumpregs(dev);
+
+ priv->tx_active = true;
+ hdmi_audio_writeb(AHB_DMA_START, START, 0x1);
+ hdmi_dma_irq_set(false);
+ hdmi_set_dma_mode(1);
+ ret = hdmi_dma_prepare_and_submit(substream, priv);
+ if (ret)
+ return ret;
+ dma_async_issue_pending(priv->desc->chan);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dmaengine_terminate_all(priv->dma_channel);
+ hdmi_set_dma_mode(0);
+ hdmi_dma_irq_set(true);
+ hdmi_audio_writeb(AHB_DMA_STOP, STOP, 0x1);
+ priv->tx_active = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+
+ return bytes_to_frames(runtime, priv->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ .rate_min = 32000,
+ .channels_min = 2,
+ .channels_max = 8,
+ .buffer_bytes_max = HDMI_PCM_BUF_SIZE,
+ .period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2,
+ .period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2,
+ .periods_min = 8,
+ .periods_max = 8,
+ .fifo_size = 0,
+};
+
+static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv)
+{
+ unsigned long flags;
+
+ hdmi_writeb(0xff, HDMI_AHB_DMA_POL);
+ hdmi_writeb(0xff, HDMI_AHB_DMA_BUFFPOL);
+
+ spin_lock_irqsave(&priv->irq_lock, flags);
+
+ hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+ hdmi_dma_irq_set(false);
+ hdmi_mask(0);
+
+ spin_unlock_irqrestore(&priv->irq_lock, flags);
+}
+
+static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->irq_lock, flags);
+
+ hdmi_dma_irq_set(true);
+ hdmi_writeb(0x0, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0);
+ hdmi_mask(1);
+
+ spin_unlock_irqrestore(&priv->irq_lock, flags);
+}
+
+static int hdmi_dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct device *dev = component->dev;
+ struct hdmi_dma_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ runtime->private_data = priv;
+
+ ret = mxc_hdmi_register_audio(substream);
+ if (ret < 0) {
+ dev_err(dev, "HDMI Video is not ready!\n");
+ return ret;
+ }
+
+ hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1);
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+ hdmi_dma_irq_enable(priv);
+
+ return 0;
+}
+
+static int hdmi_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+
+ hdmi_dma_irq_disable(priv);
+ mxc_hdmi_unregister_audio(substream);
+
+ return 0;
+}
+
+static struct snd_pcm_ops imx_hdmi_dma_pcm_ops = {
+ .open = hdmi_dma_open,
+ .close = hdmi_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = hdmi_dma_hw_params,
+ .hw_free = hdmi_dma_hw_free,
+ .trigger = hdmi_dma_trigger,
+ .pointer = hdmi_dma_pointer,
+ .copy_user = hdmi_dma_copy_user,
+};
+
+static int imx_hdmi_dma_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct hdmi_dma_priv *priv = dev_get_drvdata(component->dev);
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm *pcm = rtd->pcm;
+ u64 dma_mask = DMA_BIT_MASK(32);
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &dma_mask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+ HDMI_PCM_BUF_SIZE, &substream->dma_buffer);
+ if (ret) {
+ dev_err(card->dev, "failed to alloc playback dma buffer\n");
+ return ret;
+ }
+
+ priv->substream = substream;
+
+ /* Alloc the hw_buffer */
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+ HDMI_DMA_BUF_SIZE, &priv->hw_buffer);
+ if (ret) {
+ dev_err(card->dev, "failed to alloc hw dma buffer\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm)
+{
+ int stream = SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct hdmi_dma_priv *priv = dev_get_drvdata(component->dev);
+
+ if (substream) {
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+ substream->dma_buffer.addr = 0;
+ }
+
+ /* Free the hw_buffer */
+ snd_dma_free_pages(&priv->hw_buffer);
+ priv->hw_buffer.area = NULL;
+ priv->hw_buffer.addr = 0;
+}
+
+static const struct snd_soc_component_driver imx_hdmi_component = {
+ .name = DRV_NAME,
+ .ops = &imx_hdmi_dma_pcm_ops,
+ .pcm_new = imx_hdmi_dma_pcm_new,
+ .pcm_free = imx_hdmi_dma_pcm_free,
+};
+
+static int imx_soc_platform_probe(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_drvdata = platform_get_drvdata(pdev);
+ struct hdmi_dma_priv *priv;
+ int ret = 0;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ dev_err(&pdev->dev, "Failed to alloc hdmi_dma\n");
+ return -ENOMEM;
+ }
+
+ ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ priv->hdmi_sdma_t = dma_alloc_coherent(&pdev->dev,
+ sizeof(struct hdmi_sdma_script),
+ &priv->phy_hdmi_sdma_t, GFP_KERNEL);
+ if (!priv->hdmi_sdma_t) {
+ dev_err(&pdev->dev, "Failed to alloc hdmi_sdma_t\n");
+ return -ENOMEM;
+ }
+
+ priv->tx_active = false;
+ spin_lock_init(&priv->irq_lock);
+
+ priv->pdev = hdmi_drvdata->pdev;
+
+ hdmi_dma_init_iec_header();
+
+ dev_set_drvdata(&pdev->dev, priv);
+
+ switch (hdmi_readb(HDMI_REVISION_ID)) {
+ case 0x0a:
+ snd_imx_hardware.period_bytes_max = HDMI_DMA_PERIOD_BYTES / 4;
+ snd_imx_hardware.period_bytes_min = HDMI_DMA_PERIOD_BYTES / 4;
+ break;
+ default:
+ break;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &imx_hdmi_component, NULL, 0);
+ if (ret)
+ goto err_plat;
+
+ return 0;
+
+err_plat:
+ dma_free_coherent(&pdev->dev, sizeof(struct hdmi_sdma_script),
+ priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t);
+
+ return ret;
+}
+
+static int imx_soc_platform_remove(struct platform_device *pdev)
+{
+ struct hdmi_dma_priv *priv = dev_get_drvdata(&pdev->dev);
+
+ dma_free_coherent(&pdev->dev, sizeof(struct hdmi_sdma_script),
+ priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t);
+
+ return 0;
+}
+
+static struct platform_driver imx_hdmi_dma_driver = {
+ .driver = {
+ .name = "imx-hdmi-audio",
+ .owner = THIS_MODULE,
+ },
+ .probe = imx_soc_platform_probe,
+ .remove = imx_soc_platform_remove,
+};
+
+module_platform_driver(imx_hdmi_dma_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX HDMI audio DMA");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-hdmi.c b/sound/soc/fsl/imx-hdmi.c
new file mode 100644
index 000000000000..f7435831949c
--- /dev/null
+++ b/sound/soc/fsl/imx-hdmi.c
@@ -0,0 +1,117 @@
+/*
+ * ASoC HDMI Transmitter driver for IMX development boards
+ *
+ * Copyright (C) 2011-2016 Freescale Semiconductor, Inc.
+ *
+ * based on stmp3780_devb_hdmi.c
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <sound/soc.h>
+
+#include "imx-hdmi.h"
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC("hdmi-audio-codec", "i2s-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("imx-hdmi-audio")));
+
+/* imx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link imx_hdmi_dai_link = {
+ .name = "i.MX HDMI Audio Tx",
+ .stream_name = "i.MX HDMI Audio Tx",
+ SND_SOC_DAILINK_REG(hifi),
+};
+
+static struct snd_soc_card snd_soc_card_imx_hdmi = {
+ .name = "imx-hdmi-soc",
+ .dai_link = &imx_hdmi_dai_link,
+ .num_links = 1,
+ .owner = THIS_MODULE,
+};
+
+static int imx_hdmi_audio_probe(struct platform_device *pdev)
+{
+ struct device_node *hdmi_np, *np = pdev->dev.of_node;
+ struct snd_soc_card *card = &snd_soc_card_imx_hdmi;
+ struct platform_device *hdmi_pdev;
+ int ret = 0;
+
+ if (!hdmi_get_registered()) {
+ dev_err(&pdev->dev, "initialize HDMI-audio failed. load HDMI-video first!\n");
+ return -ENODEV;
+ }
+
+ hdmi_np = of_parse_phandle(np, "hdmi-controller", 0);
+ if (!hdmi_np) {
+ dev_err(&pdev->dev, "failed to find hdmi-audio cpudai\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ hdmi_pdev = of_find_device_by_node(hdmi_np);
+ if (!hdmi_pdev) {
+ dev_err(&pdev->dev, "failed to find SSI platform device\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ card->dev = &pdev->dev;
+ card->dai_link->cpus->dai_name = dev_name(&hdmi_pdev->dev);
+
+ platform_set_drvdata(pdev, card);
+
+ ret = snd_soc_register_card(card);
+ if (ret)
+ dev_err(&pdev->dev, "failed to register card: %d\n", ret);
+
+end:
+ if (hdmi_np)
+ of_node_put(hdmi_np);
+
+ return ret;
+}
+
+static int imx_hdmi_audio_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_card(card);
+
+ return 0;
+}
+
+static const struct of_device_id imx_hdmi_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-hdmi", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids);
+
+static struct platform_driver imx_hdmi_audio_driver = {
+ .probe = imx_hdmi_audio_probe,
+ .remove = imx_hdmi_audio_remove,
+ .driver = {
+ .of_match_table = imx_hdmi_dt_ids,
+ .name = "imx-audio-hdmi",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ },
+};
+
+module_platform_driver(imx_hdmi_audio_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-audio-hdmi");
diff --git a/sound/soc/fsl/imx-hdmi.h b/sound/soc/fsl/imx-hdmi.h
new file mode 100644
index 000000000000..d06ce9c34d32
--- /dev/null
+++ b/sound/soc/fsl/imx-hdmi.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __IMX_HDMI_H
+#define __IMX_HDMI_H
+
+struct imx_hdmi_sdma_params {
+ dma_addr_t phyaddr;
+ u32 buffer_num;
+ int dma;
+};
+
+struct imx_hdmi {
+ struct snd_soc_dai_driver cpu_dai_drv;
+ struct platform_device *codec_dev;
+ struct platform_device *dma_dev;
+ struct platform_device *pdev;
+ struct clk *isfr_clk;
+ struct clk *iahb_clk;
+ struct clk *mipi_core_clk;
+};
+
+#define HDMI_MAX_RATES 7
+#define HDMI_MAX_SAMPLE_SIZE 3
+#define HDMI_MAX_CHANNEL_CONSTRAINTS 4
+
+#define MXC_HDMI_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 MXC_HDMI_FORMATS_PLAYBACK \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+union hdmi_audio_header_t {
+ uint64_t U;
+ struct {
+ unsigned consumer:1;
+ unsigned linear_pcm:1;
+ unsigned copyright:1;
+ unsigned pre_emphasis:3;
+ unsigned mode:2;
+
+ unsigned category_code:8;
+
+ unsigned source:4;
+ unsigned channel:4;
+
+ unsigned sample_freq:4;
+ unsigned clock_acc:2;
+ unsigned reserved0:2;
+
+ unsigned word_length:4;
+ unsigned org_sample_freq:4;
+
+ unsigned cgms_a:2;
+ unsigned reserved1:6;
+
+ unsigned reserved2:8;
+
+ unsigned reserved3:8;
+ } B;
+ unsigned char status[8];
+};
+
+union hdmi_audio_dma_data_t {
+ uint32_t U;
+ struct {
+ unsigned data:24;
+ unsigned v:1;
+ unsigned u:1;
+ unsigned c:1;
+ unsigned p:1;
+ unsigned b:1;
+ unsigned reserved:3;
+ } B;
+};
+
+extern union hdmi_audio_header_t iec_header;
+
+#define hdmi_audio_writeb(reg, bit, val) \
+ do { \
+ hdmi_mask_writeb(val, HDMI_ ## reg, \
+ HDMI_ ## reg ## _ ## bit ## _OFFSET, \
+ HDMI_ ## reg ## _ ## bit ## _MASK); \
+ pr_debug("Set reg: HDMI_" #reg " (0x%x) "\
+ "bit: HDMI_" #reg "_" #bit " (%d) to val: %x\n", \
+ HDMI_ ## reg, HDMI_ ## reg ## _ ## bit ## _OFFSET, val); \
+ } while (0)
+
+#endif /* __IMX_HDMI_H */
diff --git a/sound/soc/fsl/imx-micfil.c b/sound/soc/fsl/imx-micfil.c
new file mode 100644
index 000000000000..13e5c992d195
--- /dev/null
+++ b/sound/soc/fsl/imx-micfil.c
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2018 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:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/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 <linux/pinctrl/consumer.h>
+#include "fsl_micfil.h"
+
+#define RX 0
+#define TX 1
+
+struct imx_micfil_data {
+ char name[32];
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+};
+
+static int imx_micfil_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ int ret;
+ static u32 support_rates[] = {11025, 16000, 22050,
+ 32000, 44100, 48000,};
+
+ constraint_rates.list = support_rates;
+ constraint_rates.count = ARRAY_SIZE(support_rates);
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imx_micfil_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct device *dev = rtd->card->dev;
+ unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+ unsigned int rate = params_rate(params);
+ int ret, dir;
+
+ /* For playback the XTOR is slave, and for record is master */
+ fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM;
+ dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ /* Specific configurations of DAIs starts from here */
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0,
+ rate, dir);
+ if (ret) {
+ dev_err(dev,
+ "%s: failed to set cpu sysclk: %d\n", __func__,
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+struct snd_soc_ops imx_micfil_ops = {
+ .startup = imx_micfil_startup,
+ .hw_params = imx_micfil_hw_params,
+};
+
+static int imx_micfil_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np;
+ struct platform_device *cpu_pdev;
+ struct imx_micfil_data *data;
+ struct snd_soc_dai_link_component *dlc;
+ int ret;
+
+ dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ strncpy(data->name, cpu_np->name, sizeof(data->name) - 1);
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find MICFIL platform device\n");
+ ret = -EINVAL;
+ 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 = "micfil hifi";
+ data->dai.stream_name = "micfil hifi";
+ data->dai.codecs->dai_name = "snd-soc-dummy-dai";
+ data->dai.codecs->name = "snd-soc-dummy";
+ data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platforms->of_node = cpu_np;
+ data->dai.playback_only = false;
+ data->dai.ops = &imx_micfil_ops;
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (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(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ return ret;
+}
+
+static const struct of_device_id imx_micfil_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-micfil", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_micfil_dt_ids);
+
+static struct platform_driver imx_micfil_driver = {
+ .driver = {
+ .name = "imx-micfil",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_micfil_dt_ids,
+ },
+ .probe = imx_micfil_probe,
+};
+module_platform_driver(imx_micfil_driver);
+
+MODULE_AUTHOR("Cosmin-Gabriel Samoila <cosmin.samoila@nxp.com>");
+MODULE_DESCRIPTION("NXP Micfil ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-micfil");
diff --git a/sound/soc/fsl/imx-mqs.c b/sound/soc/fsl/imx-mqs.c
new file mode 100644
index 000000000000..6462003dc009
--- /dev/null
+++ b/sound/soc/fsl/imx-mqs.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2012, 2014-2016 Freescale Semiconductor, Inc.
+ * Copyright 2012 Linaro Ltd.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#define SUPPORT_RATE_NUM 10
+
+struct imx_priv {
+ struct clk *codec_clk;
+ unsigned int mclk_freq;
+ struct platform_device *pdev;
+ struct platform_device *asrc_pdev;
+ u32 asrc_rate;
+ u32 asrc_format;
+};
+
+static struct imx_priv card_priv;
+
+static int imx_mqs_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ static u32 support_rates[SUPPORT_RATE_NUM];
+ int ret;
+
+ priv->mclk_freq = clk_get_rate(priv->codec_clk);
+
+ if (priv->mclk_freq % 24576000 == 0) {
+ support_rates[0] = 48000;
+ constraint_rates.list = support_rates;
+ constraint_rates.count = 1;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+ } else
+ dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq);
+
+ return 0;
+}
+
+static int imx_mqs_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params));
+ if (ret) {
+ dev_err(card->dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static struct snd_soc_ops imx_mqs_ops = {
+ .startup = imx_mqs_startup,
+ .hw_params = imx_mqs_hw_params,
+};
+
+static struct snd_soc_ops imx_mqs_ops_be = {
+ .hw_params = imx_mqs_hw_params,
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"CPU-Playback", NULL, "ASRC-Playback"},
+ {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */
+};
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params) {
+
+ struct imx_priv *priv = &card_priv;
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = priv->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, priv->asrc_format);
+
+ return 0;
+}
+
+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_CODEC(NULL, "fsl-mqs-dai")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+static struct snd_soc_dai_link imx_mqs_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &imx_mqs_ops,
+ SND_SOC_DAILINK_REG(hifi),
+ },
+ {
+ .name = "HiFi-ASRC-FE",
+ .stream_name = "HiFi-ASRC-FE",
+ .dynamic = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 0,
+ .dpcm_merged_chan = 1,
+ SND_SOC_DAILINK_REG(hifi_fe),
+ },
+ {
+ .name = "HiFi-ASRC-BE",
+ .stream_name = "HiFi-ASRC-BE",
+ .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .no_pcm = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 0,
+ .ops = &imx_mqs_ops_be,
+ .be_hw_params_fixup = be_hw_params_fixup,
+ SND_SOC_DAILINK_REG(hifi_be),
+ },
+};
+
+static struct snd_soc_card snd_soc_card_imx_mqs = {
+ .name = "mqs-audio",
+ .dai_link = imx_mqs_dai,
+ .owner = THIS_MODULE,
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+};
+
+static int imx_mqs_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *codec_np;
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *codec_dev;
+ struct platform_device *asrc_pdev = NULL;
+ struct platform_device *cpu_pdev;
+ struct device_node *asrc_np;
+ int ret;
+ u32 width;
+
+ priv->pdev = pdev;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ 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;
+ }
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_dev = of_find_device_by_node(codec_np);
+ if (!codec_dev) {
+ dev_err(&codec_dev->dev, "failed to find codec device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ priv->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
+ if (IS_ERR(priv->codec_clk)) {
+ ret = PTR_ERR(priv->codec_clk);
+ dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+
+ imx_mqs_dai[0].codecs->dai_name = "fsl-mqs-dai";
+
+ if (!asrc_pdev) {
+ imx_mqs_dai[0].codecs->of_node = codec_np;
+ imx_mqs_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ imx_mqs_dai[0].platforms->of_node = cpu_np;
+ snd_soc_card_imx_mqs.num_links = 1;
+ } else {
+ imx_mqs_dai[0].codecs->of_node = codec_np;
+ imx_mqs_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ imx_mqs_dai[0].platforms->of_node = cpu_np;
+ imx_mqs_dai[1].cpus->of_node = asrc_np;
+ imx_mqs_dai[1].platforms->of_node = asrc_np;
+ imx_mqs_dai[2].codecs->of_node = codec_np;
+ imx_mqs_dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ snd_soc_card_imx_mqs.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 fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ snd_soc_card_imx_mqs.dev = &pdev->dev;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_imx_mqs);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+
+ return ret;
+}
+
+static const struct of_device_id imx_mqs_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-mqs", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_mqs_dt_ids);
+
+static struct platform_driver imx_mqs_driver = {
+ .driver = {
+ .name = "imx-mqs",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_mqs_dt_ids,
+ },
+ .probe = imx_mqs_probe,
+};
+module_platform_driver(imx_mqs_driver);
+
+MODULE_AUTHOR("Nicolin Chen <Guangyu.Chen@freescale.com>");
+MODULE_DESCRIPTION("Freescale i.MX MQS ASoC machine driver");
+MODULE_ALIAS("platform:imx-mqs");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/imx-pcm-dma-v2.c b/sound/soc/fsl/imx-pcm-dma-v2.c
new file mode 100644
index 000000000000..54b08131199b
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-dma-v2.c
@@ -0,0 +1,268 @@
+/*
+ * imx-pcm-dma-v2.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.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"
+
+static struct snd_pcm_hardware imx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = 65532, /* Limited by SDMA engine */
+ .periods_min = 2,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static bool imx_dma_filter_fn(struct dma_chan *chan, void *param)
+{
+ if (!imx_dma_is_general_purpose(chan))
+ return false;
+
+ chan->private = param;
+
+ return true;
+}
+
+/* this may get called several times by oss emulation */
+static int imx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ struct dma_slave_config config;
+ struct dma_chan *chan;
+ int err = 0;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME
+ */
+ if (!dma_data)
+ return 0;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ chan = snd_dmaengine_pcm_get_chan(substream);
+ if (!chan)
+ return -EINVAL;
+
+ /* fills in addr_width and direction */
+ err = snd_hwparams_to_dma_slave_config(substream, params, &config);
+ if (err)
+ return err;
+
+ snd_dmaengine_pcm_set_config_from_dai_data(substream,
+ dma_data,
+ &config);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ config.dst_fifo_num = dma_data->fifo_num;
+ else
+ config.src_fifo_num = dma_data->fifo_num;
+
+ return dmaengine_slave_config(chan, &config);
+}
+
+static int imx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static snd_pcm_uframes_t imx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ return snd_dmaengine_pcm_pointer(substream);
+}
+
+static void imx_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream,
+ struct device *dev)
+{
+ size_t size = imx_pcm_hardware.buffer_bytes_max;
+
+ snd_pcm_lib_preallocate_pages(substream,
+ SNDRV_DMA_TYPE_DEV_IRAM,
+ dev,
+ size,
+ size);
+}
+
+static void imx_pcm_free_dma_buffers(struct snd_pcm_substream *substream)
+{
+ snd_pcm_lib_preallocate_free(substream);
+}
+
+static int imx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ struct dma_slave_caps dma_caps;
+ struct dma_chan *chan;
+ u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+ int ret;
+ int i;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ /* DT boot: filter_data is the DMA name */
+ if (rtd->cpu_dai->dev->of_node) {
+ struct dma_chan *chan;
+
+ chan = dma_request_slave_channel(rtd->cpu_dai->dev,
+ dma_data->chan_name);
+ ret = snd_dmaengine_pcm_open(substream, chan);
+ if (ret)
+ return ret;
+ } else {
+ ret = snd_dmaengine_pcm_open_request_chan(substream,
+ imx_dma_filter_fn,
+ dma_data->filter_data);
+ if (ret)
+ return ret;
+ }
+
+ chan = snd_dmaengine_pcm_get_chan(substream);
+
+ ret = dma_get_slave_caps(chan, &dma_caps);
+ if (ret == 0) {
+ if (dma_caps.cmd_pause)
+ imx_pcm_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME;
+ if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT)
+ imx_pcm_hardware.info |= SNDRV_PCM_INFO_BATCH;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ addr_widths = dma_caps.dst_addr_widths;
+ else
+ addr_widths = dma_caps.src_addr_widths;
+ }
+
+ /*
+ * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep
+ * hw.formats set to 0, meaning no restrictions are in place.
+ * In this case it's the responsibility of the DAI driver to
+ * provide the supported format information.
+ */
+ if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK))
+ /*
+ * Prepare formats mask for valid/allowed sample types. If the
+ * dma does not have support for the given physical word size,
+ * it needs to be masked out so user space can not use the
+ * format which produces corrupted audio.
+ * In case the dma driver does not implement the slave_caps the
+ * default assumption is that it supports 1, 2 and 4 bytes
+ * widths.
+ */
+ for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) {
+ int bits = snd_pcm_format_physical_width(i);
+
+ /*
+ * Enable only samples with DMA supported physical
+ * widths
+ */
+ switch (bits) {
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ case 64:
+ if (addr_widths & (1 << (bits / 8)))
+ imx_pcm_hardware.formats |= (1LL << i);
+ break;
+ default:
+ /* Unsupported types */
+ break;
+ }
+ }
+
+ snd_soc_set_runtime_hwparams(substream, &imx_pcm_hardware);
+
+ imx_pcm_preallocate_dma_buffer(substream, chan->device->dev);
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int imx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_wc(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static int imx_pcm_close(struct snd_pcm_substream *substream)
+{
+ imx_pcm_free_dma_buffers(substream);
+
+ return snd_dmaengine_pcm_close_release_chan(substream);
+}
+
+static struct snd_pcm_ops imx_pcm_ops = {
+ .open = imx_pcm_open,
+ .close = imx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = imx_pcm_hw_params,
+ .hw_free = imx_pcm_hw_free,
+ .trigger = snd_dmaengine_pcm_trigger,
+ .pointer = imx_pcm_pointer,
+ .mmap = imx_pcm_mmap,
+};
+
+static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ int ret = 0;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static struct snd_soc_component_driver imx_soc_platform = {
+ .name = "imx-pcm-dma-v2",
+ .ops = &imx_pcm_ops,
+ .pcm_new = imx_pcm_new,
+};
+
+int imx_pcm_platform_register(struct device *dev)
+{
+ return devm_snd_soc_register_component(dev, &imx_soc_platform, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(imx_pcm_platform_register);
+
+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 000000000000..8da1345ccd06
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-rpmsg.c
@@ -0,0 +1,893 @@
+/*
+ * imx-rpmsg-platform.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2017 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/imx_rpmsg.h>
+#include <linux/delay.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_i2s.h"
+#include "../../core/pcm_local.h"
+
+#define DRV_NAME "imx_pcm_rpmsg"
+
+struct i2s_info *i2s_info_g;
+
+static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ 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_SAI_DMABUF_SIZE,
+ .period_bytes_min = 512,
+ .period_bytes_max = 65532, /* Limited by SDMA engine */
+ .periods_min = 2,
+ .periods_max = 6000,
+ .fifo_size = 0,
+};
+
+static int imx_rpmsg_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_HW_PARAM];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_HW_PARAM];
+
+ rpmsg->send_msg.param.rate = params_rate(params);
+
+ if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)
+ rpmsg->send_msg.param.format = RPMSG_S16_LE;
+ else if (params_format(params) == SNDRV_PCM_FORMAT_S24_LE)
+ rpmsg->send_msg.param.format = RPMSG_S24_LE;
+ else if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U16_LE)
+ rpmsg->send_msg.param.format = SNDRV_PCM_FORMAT_DSD_U16_LE;
+ else if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U32_LE)
+ rpmsg->send_msg.param.format = SNDRV_PCM_FORMAT_DSD_U32_LE;
+ else
+ rpmsg->send_msg.param.format = RPMSG_S32_LE;
+
+ if (params_channels(params) == 1)
+ rpmsg->send_msg.param.channels = RPMSG_CH_LEFT;
+ else
+ rpmsg->send_msg.param.channels = RPMSG_CH_STEREO;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_HW_PARAM;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_HW_PARAM;
+
+ i2s_info->send_message(rpmsg, i2s_info);
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static snd_pcm_uframes_t imx_rpmsg_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ unsigned int pos = 0;
+ struct i2s_rpmsg *rpmsg;
+ int buffer_tail = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
+
+ buffer_tail = rpmsg->recv_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 snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ unsigned long flags;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_PERIOD_DONE;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_PERIOD_DONE;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ } else
+ i2s_info->msg_drop_count[substream->stream]++;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+}
+
+static int imx_rpmsg_pcm_open(struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ int cmd;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_OPEN];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_OPEN];
+
+ imx_rpmsg_pcm_hardware.buffer_bytes_max =
+ i2s_info->prealloc_buffer_size;
+ 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);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_OPEN;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_OPEN;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ cmd = I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM;
+ i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0;
+ } else {
+ cmd = I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM;
+ i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0;
+ }
+
+ i2s_info->send_message(rpmsg, i2s_info);
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ i2s_info->msg_drop_count[substream->stream] = 0;
+
+ /*create thread*/
+ i2s_info->stream_timer[substream->stream].substream = substream;
+
+ timer_setup(&i2s_info->stream_timer[substream->stream].timer,
+ imx_rpmsg_timer_callback, 0);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_close(struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_CLOSE];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_CLOSE];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_CLOSE;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_CLOSE;
+ flush_workqueue(i2s_info->rpmsg_wq);
+ i2s_info->send_message(rpmsg, i2s_info);
+
+ del_timer(&i2s_info->stream_timer[substream->stream].timer);
+
+ rtd->dai_link->ignore_suspend = 0;
+
+ if (i2s_info->msg_drop_count[substream->stream])
+ dev_warn(rtd->dev, "Msg is dropped!, number is %d\n",
+ i2s_info->msg_drop_count[substream->stream]);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_prepare(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 = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+
+ /* NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts
+ * four condition to determine the lpa is enabled.
+ */
+ if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+ runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) &&
+ rpmsg_i2s->version == 2 &&
+ rpmsg_i2s->enable_lpa) {
+ rtd->dai_link->ignore_suspend = 1;
+ rpmsg_i2s->force_lpa = 1;
+ } else
+ rpmsg_i2s->force_lpa = 0;
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_wc(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static void imx_rpmsg_pcm_dma_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static int imx_rpmsg_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ unsigned long flags;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_BUFFER];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_BUFFER];
+
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_BUFFER;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_BUFFER;
+
+ rpmsg->send_msg.param.buffer_addr = substream->runtime->dma_addr;
+ rpmsg->send_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ rpmsg->send_msg.param.period_size = snd_pcm_lib_period_bytes(substream);
+ rpmsg->send_msg.param.buffer_tail = 0;
+
+ i2s_info->num_period[substream->stream] =
+ rpmsg->send_msg.param.buffer_size /
+ rpmsg->send_msg.param.period_size;
+
+ i2s_info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete;
+ i2s_info->callback_param[substream->stream] = substream;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+static int imx_rpmsg_async_issue_pending(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ unsigned long flags;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_START];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_START];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_START;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_START;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+static int imx_rpmsg_restart(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ unsigned long flags;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_RESTART];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_RESTART];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_RESTART;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_RESTART;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+ return 0;
+}
+
+static int imx_rpmsg_pause(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ unsigned long flags;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_PAUSE];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_PAUSE];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_PAUSE;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_PAUSE;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+ return 0;
+}
+
+static int imx_rpmsg_terminate_all(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ int cmd;
+ unsigned long flags;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_TERMINATE];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_TERMINATE];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_TERMINATE;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_TERMINATE;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ cmd = I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM;
+ i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0;
+ } else {
+ cmd = I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM;
+ i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0;
+ }
+
+ del_timer(&i2s_info->stream_timer[substream->stream].timer);
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+int imx_rpmsg_pcm_trigger(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 = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = imx_rpmsg_pcm_prepare_and_submit(substream);
+ if (ret)
+ return ret;
+ ret = imx_rpmsg_async_issue_pending(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (rpmsg_i2s->force_lpa)
+ break;
+ /* fall through */
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = imx_rpmsg_restart(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (!rpmsg_i2s->force_lpa) {
+ if (runtime->info & SNDRV_PCM_INFO_PAUSE)
+ ret = imx_rpmsg_pause(substream);
+ else
+ ret = imx_rpmsg_terminate_all(substream);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = imx_rpmsg_pause(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = imx_rpmsg_terminate_all(substream);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int imx_rpmsg_pcm_ack(struct snd_pcm_substream *substream)
+{
+ /*send the hw_avail size through rpmsg*/
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ int buffer_tail = 0;
+ int writen_num = 0;
+ snd_pcm_sframes_t avail;
+ unsigned long flags;
+
+ if (!rpmsg_i2s->force_lpa)
+ return 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_PERIOD_DONE;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_PERIOD_DONE;
+
+ rpmsg->send_msg.header.type = I2S_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);
+
+ if (buffer_tail != rpmsg->send_msg.param.buffer_tail) {
+ writen_num = buffer_tail - rpmsg->send_msg.param.buffer_tail;
+ if (writen_num < 0)
+ writen_num += runtime->periods;
+
+ rpmsg->send_msg.param.buffer_tail = buffer_tail;
+
+ spin_lock_irqsave(&i2s_info->lock[substream->stream], flags);
+ memcpy(&i2s_info->period_done_msg[substream->stream], rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+
+ i2s_info->period_done_msg_enabled[substream->stream] = true;
+ spin_unlock_irqrestore(&i2s_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);
+
+ if ((avail - writen_num * runtime->period_size) <= runtime->period_size) {
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq,
+ &i2s_info->work_list[index].work);
+ i2s_info->work_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ } else
+ i2s_info->msg_drop_count[substream->stream]++;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ if (rpmsg_i2s->force_lpa && !timer_pending(&i2s_info->stream_timer[substream->stream].timer)) {
+ int time_msec;
+ time_msec = (int)(runtime->period_size*1000/runtime->rate);
+ mod_timer(&i2s_info->stream_timer[substream->stream].timer,
+ jiffies + msecs_to_jiffies(time_msec));
+ }
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_pcm_ops imx_rpmsg_pcm_ops = {
+ .open = imx_rpmsg_pcm_open,
+ .close = imx_rpmsg_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = imx_rpmsg_pcm_hw_params,
+ .hw_free = imx_rpmsg_pcm_hw_free,
+ .trigger = imx_rpmsg_pcm_trigger,
+ .pointer = imx_rpmsg_pcm_pointer,
+ .mmap = imx_rpmsg_pcm_mmap,
+ .ack = imx_rpmsg_pcm_ack,
+ .prepare = imx_rpmsg_pcm_prepare,
+};
+
+static int imx_rpmsg_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
+ int stream, int size)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_wc(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+
+static void imx_rpmsg_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = SNDRV_PCM_STREAM_PLAYBACK;
+ stream < SNDRV_PCM_STREAM_LAST; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_wc(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static int imx_rpmsg_pcm_new(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 = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ int ret;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ i2s_info->prealloc_buffer_size);
+ if (ret)
+ goto out;
+ }
+
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE,
+ i2s_info->prealloc_buffer_size);
+ if (ret)
+ goto out;
+ }
+
+out:
+ /* free preallocated buffers in case of error */
+ if (ret)
+ imx_rpmsg_pcm_free_dma_buffers(pcm);
+
+ return ret;
+}
+
+static struct snd_soc_component_driver imx_rpmsg_soc_component = {
+ .name = DRV_NAME,
+ .ops = &imx_rpmsg_pcm_ops,
+ .pcm_new = imx_rpmsg_pcm_new,
+ .pcm_free = imx_rpmsg_pcm_free_dma_buffers,
+};
+
+int imx_rpmsg_platform_register(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+ struct snd_soc_component *component;
+ int ret;
+
+ i2s_info_g = &rpmsg_i2s->i2s_info;
+
+ ret = devm_snd_soc_register_component(dev, &imx_rpmsg_soc_component,
+ NULL, 0);
+ if (ret)
+ return ret;
+
+ component = snd_soc_lookup_component(dev, DRV_NAME);
+ if (!component)
+ return -EINVAL;
+
+#ifdef CONFIG_DEBUG_FS
+ component->debugfs_prefix = "dma";
+#endif
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imx_rpmsg_platform_register);
+
+static int i2s_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ struct i2s_rpmsg_r *msg = (struct i2s_rpmsg_r *)data;
+ struct i2s_rpmsg *rpmsg;
+ unsigned long flags;
+
+ dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n",
+ src, msg->header.cmd, msg->param.resp);
+
+ if (msg->header.type == I2S_TYPE_C) {
+ if (msg->header.cmd == I2S_TX_PERIOD_DONE) {
+ spin_lock_irqsave(&i2s_info_g->lock[0], flags);
+ rpmsg = &i2s_info_g->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
+
+ if (msg->header.major == 1 && msg->header.minor == 2)
+ rpmsg->recv_msg.param.buffer_tail =
+ msg->param.buffer_tail;
+ else
+ rpmsg->recv_msg.param.buffer_tail++;
+
+ rpmsg->recv_msg.param.buffer_tail %=
+ i2s_info_g->num_period[0];
+
+ spin_unlock_irqrestore(&i2s_info_g->lock[0], flags);
+ i2s_info_g->callback[0](i2s_info_g->callback_param[0]);
+
+ } else if (msg->header.cmd == I2S_RX_PERIOD_DONE) {
+ spin_lock_irqsave(&i2s_info_g->lock[1], flags);
+ rpmsg = &i2s_info_g->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
+
+ if (msg->header.major == 1 && msg->header.minor == 2)
+ rpmsg->recv_msg.param.buffer_tail =
+ msg->param.buffer_tail;
+ else
+ rpmsg->recv_msg.param.buffer_tail++;
+
+ rpmsg->recv_msg.param.buffer_tail %=
+ i2s_info_g->num_period[1];
+ spin_unlock_irqrestore(&i2s_info_g->lock[1], flags);
+ i2s_info_g->callback[1](i2s_info_g->callback_param[1]);
+ }
+ }
+
+ if (msg->header.type == I2S_TYPE_B) {
+ memcpy(&i2s_info_g->recv_msg, msg, sizeof(struct i2s_rpmsg_r));
+ complete(&i2s_info_g->cmd_complete);
+ }
+
+ return 0;
+}
+
+static int i2s_rpmsg_probe(struct rpmsg_device *rpdev)
+{
+ struct platform_device *codec_pdev;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = NULL;
+ struct fsl_rpmsg_codec rpmsg_codec[3];
+ int ret;
+
+ if (!i2s_info_g)
+ return 0;
+
+ i2s_info_g->rpdev = rpdev;
+
+ init_completion(&i2s_info_g->cmd_complete);
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
+ rpdev->src, rpdev->dst);
+
+ rpmsg_i2s = container_of(i2s_info_g, struct fsl_rpmsg_i2s, i2s_info);
+
+ if (rpmsg_i2s->codec_wm8960) {
+ rpmsg_codec[0].audioindex = rpmsg_i2s->codec_wm8960 >> 16;
+ rpmsg_codec[0].shared_lrclk = true;
+ rpmsg_codec[0].capless = false;
+ codec_pdev = platform_device_register_data(
+ &rpmsg_i2s->pdev->dev,
+ RPMSG_CODEC_DRV_NAME_WM8960,
+ PLATFORM_DEVID_NONE,
+ &rpmsg_codec[0], sizeof(struct fsl_rpmsg_codec));
+ if (IS_ERR(codec_pdev)) {
+ dev_err(&rpdev->dev,
+ "failed to register rpmsg audio codec\n");
+ ret = PTR_ERR(codec_pdev);
+ return ret;
+ }
+ }
+
+ if (rpmsg_i2s->codec_cs42888) {
+ rpmsg_codec[1].audioindex = rpmsg_i2s->codec_cs42888 >> 16;
+ strcpy(rpmsg_codec[1].name, "cs42888");
+ rpmsg_codec[1].num_adcs = 2;
+
+ codec_pdev = platform_device_register_data(
+ &rpmsg_i2s->pdev->dev,
+ RPMSG_CODEC_DRV_NAME_CS42888,
+ PLATFORM_DEVID_NONE,
+ &rpmsg_codec[1], sizeof(struct fsl_rpmsg_codec));
+ if (IS_ERR(codec_pdev)) {
+ dev_err(&rpdev->dev,
+ "failed to register rpmsg audio codec\n");
+ ret = PTR_ERR(codec_pdev);
+ return ret;
+ }
+ }
+
+ if (rpmsg_i2s->codec_ak4497) {
+ rpmsg_codec[2].audioindex = rpmsg_i2s->codec_ak4497 >> 16;
+ codec_pdev = platform_device_register_data(
+ &rpmsg_i2s->pdev->dev,
+ RPMSG_CODEC_DRV_NAME_AK4497,
+ PLATFORM_DEVID_NONE,
+ &rpmsg_codec[2], sizeof(struct fsl_rpmsg_codec));
+ if (IS_ERR(codec_pdev)) {
+ dev_err(&rpdev->dev,
+ "failed to register rpmsg audio codec\n");
+ ret = PTR_ERR(codec_pdev);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void i2s_rpmsg_remove(struct rpmsg_device *rpdev)
+{
+ dev_info(&rpdev->dev, "i2s rpmsg driver is removed\n");
+}
+
+static struct rpmsg_device_id i2s_rpmsg_id_table[] = {
+ { .name = "rpmsg-audio-channel" },
+ { },
+};
+
+static struct rpmsg_driver i2s_rpmsg_driver = {
+ .drv.name = "i2s_rpmsg",
+ .drv.owner = THIS_MODULE,
+ .id_table = i2s_rpmsg_id_table,
+ .probe = i2s_rpmsg_probe,
+ .callback = i2s_rpmsg_cb,
+ .remove = i2s_rpmsg_remove,
+};
+
+static int __init i2s_rpmsg_init(void)
+{
+ return register_rpmsg_driver(&i2s_rpmsg_driver);
+}
+
+static void __exit i2s_rpmsg_exit(void)
+{
+ unregister_rpmsg_driver(&i2s_rpmsg_driver);
+}
+module_init(i2s_rpmsg_init);
+module_exit(i2s_rpmsg_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h
index 5dd406774d3e..edbb268332bd 100644
--- a/sound/soc/fsl/imx-pcm.h
+++ b/sound/soc/fsl/imx-pcm.h
@@ -39,13 +39,27 @@ struct imx_pcm_fiq_params {
struct snd_dmaengine_dai_dma_data *dma_params_tx;
};
+#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_RPMSG)
+int imx_rpmsg_platform_register(struct device *dev);
+#else
+static inline int imx_rpmsg_platform_register(struct device *dev)
+{
+ return -ENODEV;
+}
+#endif
+
#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA)
int imx_pcm_dma_init(struct platform_device *pdev, size_t size);
+int imx_pcm_platform_register(struct device *dev);
#else
static inline int imx_pcm_dma_init(struct platform_device *pdev, size_t size)
{
return -ENODEV;
}
+static inline int imx_pcm_platform_register(struct device *dev)
+{
+ return -ENODEV;
+}
#endif
#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ)
diff --git a/sound/soc/fsl/imx-pdm.c b/sound/soc/fsl/imx-pdm.c
new file mode 100644
index 000000000000..9e61689d0278
--- /dev/null
+++ b/sound/soc/fsl/imx-pdm.c
@@ -0,0 +1,207 @@
+/*
+ * 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:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+
+#include "fsl_sai.h"
+
+struct imx_pdm_data {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+ unsigned int decimation;
+};
+
+static u32 imx_pdm_mic_rates[] = { 8000, 16000, 32000, 48000, 64000 };
+static struct snd_pcm_hw_constraint_list imx_pdm_mic_rate_constrains = {
+ .count = ARRAY_SIZE(imx_pdm_mic_rates),
+ .list = imx_pdm_mic_rates,
+};
+static u32 imx_pdm_mic_channels[] = { 1 };
+static struct snd_pcm_hw_constraint_list imx_pdm_mic_channels_constrains = {
+ .count = ARRAY_SIZE(imx_pdm_mic_channels),
+ .list = imx_pdm_mic_channels,
+};
+
+static int imx_pdm_mic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &imx_pdm_mic_rate_constrains);
+ if (ret) {
+ dev_err(card->dev,
+ "fail to set pcm hw rate constrains: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS, &imx_pdm_mic_channels_constrains);
+ if (ret) {
+ dev_err(card->dev,
+ "fail to set pcm hw channels constrains: %d", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_pdm_mic_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_pdm_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ /* set cpu dai format configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret) {
+ dev_err(card->dev, "fail to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ /* set tdm slots only one for now */
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 1, 32);
+ /* Set clock out */
+ ret = snd_soc_dai_set_bclk_ratio(cpu_dai, data->decimation);
+ if (ret) {
+ dev_err(card->dev, "fail to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops imx_pdm_mic_ops = {
+ .startup = imx_pdm_mic_startup,
+ .hw_params = imx_pdm_mic_hw_params,
+};
+
+static int imx_pdm_mic_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np = NULL;
+ struct device_node *np = pdev->dev.of_node;
+ struct platform_device *cpu_pdev;
+ struct imx_pdm_data *data;
+ struct snd_soc_dai_link_component *dlc;
+ 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, "fail to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = of_property_read_u32(np, "decimation", &data->decimation);
+ if (ret < 0) {
+ ret = -EINVAL;
+ 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 = "pdm hifi";
+ data->dai.stream_name = "pdm hifi";
+ data->dai.codecs->dai_name = "snd-soc-dummy-dai";
+ data->dai.codecs->name = "snd-soc-dummy";
+ data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platforms->of_node = cpu_np;
+ data->dai.capture_only = 1;
+ data->dai.ops = &imx_pdm_mic_ops;
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret) {
+ dev_err(&pdev->dev, "fail to find card model name\n");
+ goto fail;
+ }
+
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd soc register card failed: %d\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+
+ return ret;
+}
+
+static int imx_pdm_mic_remove(struct platform_device *pdev)
+{
+ struct imx_pdm_data *data = platform_get_drvdata(pdev);
+ /* unregister card */
+ snd_soc_unregister_card(&data->card);
+ return 0;
+}
+
+static const struct of_device_id imx_pdm_mic_dt_ids[] = {
+ { .compatible = "fsl,imx-pdm-mic", },
+ { /* sentinel*/ }
+};
+MODULE_DEVICE_TABLE(of, imx_pdm_mic_dt_ids);
+
+static struct platform_driver imx_pdm_mic_driver = {
+ .driver = {
+ .name = "imx-pdm-mic",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_pdm_mic_dt_ids,
+ },
+ .probe = imx_pdm_mic_probe,
+ .remove = imx_pdm_mic_remove,
+};
+module_platform_driver(imx_pdm_mic_driver);
+
+MODULE_DESCRIPTION("NXP i.MX PDM mic ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-pdm-mic");
diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c
new file mode 100644
index 000000000000..9db5f190bc4f
--- /dev/null
+++ b/sound/soc/fsl/imx-rpmsg.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 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:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/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 <linux/pinctrl/consumer.h>
+#include "fsl_rpmsg_i2s.h"
+
+struct imx_rpmsg_data {
+ struct snd_soc_dai_link dai[1];
+ struct snd_soc_card card;
+};
+
+static const struct snd_soc_dapm_widget imx_wm8960_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_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np;
+ struct platform_device *cpu_pdev;
+ struct imx_rpmsg_data *data;
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+ struct snd_soc_dai_link_component *dlc;
+ int ret;
+
+ dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find rpmsg platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ rpmsg_i2s = platform_get_drvdata(cpu_pdev);
+
+ data->dai[0].cpus = &dlc[0];
+ data->dai[0].num_cpus = 1;
+ data->dai[0].platforms = &dlc[1];
+ data->dai[0].num_platforms = 1;
+ data->dai[0].codecs = &dlc[2];
+ data->dai[0].num_codecs = 1;
+
+ data->dai[0].name = "rpmsg hifi";
+ data->dai[0].stream_name = "rpmsg hifi";
+ data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ if (rpmsg_i2s->codec_wm8960) {
+ data->dai[0].codecs->dai_name = "rpmsg-wm8960-hifi";
+ data->dai[0].codecs->name = "rpmsg-audio-codec-wm8960";
+ }
+
+ if (rpmsg_i2s->codec_dummy) {
+ data->dai[0].codecs->dai_name = "snd-soc-dummy-dai";
+ data->dai[0].codecs->name = "snd-soc-dummy";
+ }
+
+ if (rpmsg_i2s->codec_ak4497) {
+ data->dai[0].codecs->dai_name = "rpmsg-ak4497-aif";
+ data->dai[0].codecs->name = "rpmsg-audio-codec-ak4497";
+ data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+ }
+
+ data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai[0].platforms->of_node = cpu_np;
+ data->dai[0].playback_only = true;
+ data->dai[0].capture_only = true;
+ data->card.num_links = 1;
+ data->card.dai_link = data->dai;
+
+ if (of_property_read_bool(pdev->dev.of_node, "rpmsg-out"))
+ data->dai[0].capture_only = false;
+
+ if (of_property_read_bool(pdev->dev.of_node, "rpmsg-in"))
+ data->dai[0].playback_only = false;
+
+ if (data->dai[0].playback_only && data->dai[0].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;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+
+ if (rpmsg_i2s->codec_wm8960) {
+ ret = snd_soc_of_parse_audio_routing(&data->card,
+ "audio-routing");
+ if (ret)
+ goto fail;
+
+ data->card.dapm_widgets = imx_wm8960_dapm_widgets;
+ data->card.num_dapm_widgets =
+ ARRAY_SIZE(imx_wm8960_dapm_widgets);
+ }
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ return ret;
+}
+
+static const struct of_device_id imx_rpmsg_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-rpmsg", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_rpmsg_dt_ids);
+
+static struct platform_driver imx_rpmsg_driver = {
+ .driver = {
+ .name = "imx-audio-rpmsg",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_rpmsg_dt_ids,
+ },
+ .probe = imx_rpmsg_probe,
+};
+module_platform_driver(imx_rpmsg_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX rpmsg audio ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-rpmsg");
diff --git a/sound/soc/fsl/imx-si476x.c b/sound/soc/fsl/imx-si476x.c
new file mode 100644
index 000000000000..a5112a7d8d5a
--- /dev/null
+++ b/sound/soc/fsl/imx-si476x.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2008-2016 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <sound/soc.h>
+
+#include "imx-audmux.h"
+
+static int imx_audmux_config(int slave, int master)
+{
+ unsigned int ptcr, pdcr;
+ slave = slave - 1;
+ master = master - 1;
+
+ ptcr = IMX_AUDMUX_V2_PTCR_SYN |
+ IMX_AUDMUX_V2_PTCR_TFSDIR |
+ IMX_AUDMUX_V2_PTCR_TFSEL(slave) |
+ IMX_AUDMUX_V2_PTCR_TCLKDIR |
+ IMX_AUDMUX_V2_PTCR_TCSEL(slave);
+ pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(slave);
+ imx_audmux_v2_configure_port(master, ptcr, pdcr);
+
+ /*
+ * According to RM, RCLKDIR and SYN should not be changed at same time.
+ * So separate to two step for configuring this port.
+ */
+ ptcr |= IMX_AUDMUX_V2_PTCR_RFSDIR |
+ IMX_AUDMUX_V2_PTCR_RFSEL(slave) |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR |
+ IMX_AUDMUX_V2_PTCR_RCSEL(slave);
+ imx_audmux_v2_configure_port(master, ptcr, pdcr);
+
+ ptcr = IMX_AUDMUX_V2_PTCR_SYN;
+ pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(master);
+ imx_audmux_v2_configure_port(slave, ptcr, pdcr);
+
+ return 0;
+}
+
+static int imx_si476x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret = 0;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret) {
+ dev_err(cpu_dai->dev, "failed to set dai fmt\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops imx_si476x_ops = {
+ .hw_params = imx_si476x_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "si476x-codec")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link imx_dai = {
+ .name = "imx-si476x",
+ .stream_name = "imx-si476x",
+ .ops = &imx_si476x_ops,
+ SND_SOC_DAILINK_REG(hifi),
+};
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-audio-si476x",
+ .dai_link = &imx_dai,
+ .num_links = 1,
+ .owner = THIS_MODULE,
+};
+
+static int imx_si476x_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_card_imx_3stack;
+ struct device_node *ssi_np, *np = pdev->dev.of_node;
+ struct platform_device *ssi_pdev;
+ struct i2c_client *fm_dev;
+ struct device_node *fm_np = NULL;
+ int int_port, ext_port, 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;
+ }
+
+ imx_audmux_config(int_port, ext_port);
+
+ ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
+ if (!ssi_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ return -EINVAL;
+ }
+
+ ssi_pdev = of_find_device_by_node(ssi_np);
+ if (!ssi_pdev) {
+ dev_err(&pdev->dev, "failed to find SSI platform device\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ fm_np = of_parse_phandle(pdev->dev.of_node, "fm-controller", 0);
+ if (!fm_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ fm_dev = of_find_i2c_device_by_node(fm_np->parent);
+ if (!fm_dev || !fm_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find FM platform device\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ card->dev = &pdev->dev;
+ card->dai_link->cpus->dai_name = dev_name(&ssi_pdev->dev);
+ card->dai_link->platforms->of_node = ssi_np;
+ card->dai_link->codecs->of_node = fm_np;
+
+ platform_set_drvdata(pdev, card);
+
+ ret = snd_soc_register_card(card);
+ if (ret)
+ dev_err(&pdev->dev, "Failed to register card: %d\n", ret);
+
+end:
+ if (ssi_np)
+ of_node_put(ssi_np);
+ if (fm_np)
+ of_node_put(fm_np);
+
+ return ret;
+}
+
+static int imx_si476x_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_card_imx_3stack;
+
+ snd_soc_unregister_card(card);
+
+ return 0;
+}
+
+static const struct of_device_id imx_si476x_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-si476x", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_si476x_dt_ids);
+
+static struct platform_driver imx_si476x_driver = {
+ .driver = {
+ .name = "imx-tuner-si476x",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_si476x_dt_ids,
+ },
+ .probe = imx_si476x_probe,
+ .remove = imx_si476x_remove,
+};
+
+module_platform_driver(imx_si476x_driver);
+
+/* Module information */
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("ALSA SoC i.MX si476x");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-tuner-si476x");
diff --git a/sound/soc/fsl/imx-sii902x.c b/sound/soc/fsl/imx-sii902x.c
new file mode 100644
index 000000000000..d4b18e0fdf06
--- /dev/null
+++ b/sound/soc/fsl/imx-sii902x.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <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 <linux/pinctrl/consumer.h>
+#include "fsl_sai.h"
+
+#define SUPPORT_RATE_NUM 10
+
+struct imx_sii902x_data {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+ bool is_stream_opened[2];
+};
+
+static int imx_sii902x_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ static u32 support_rates[SUPPORT_RATE_NUM];
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card);
+ struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret;
+
+ data->is_stream_opened[tx] = true;
+ if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] ||
+ data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) {
+ data->is_stream_opened[tx] = false;
+ return -EBUSY;
+ }
+
+ support_rates[0] = 32000;
+ support_rates[1] = 48000;
+ support_rates[2] = 96000;
+ support_rates[3] = 192000;
+ constraint_rates.list = support_rates;
+ constraint_rates.count = 4;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
+ 1, 2);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imx_sii902x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ int ret;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ 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, 24);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void imx_sii902x_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ data->is_stream_opened[tx] = false;
+}
+
+static struct snd_soc_ops imx_sii902x_ops = {
+ .startup = imx_sii902x_startup,
+ .shutdown = imx_sii902x_shutdown,
+ .hw_params = imx_sii902x_hw_params,
+};
+
+static int imx_sii902x_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *sii902x_np = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_sii902x_data *data;
+ struct snd_soc_dai_link_component *dlc;
+ int ret;
+
+ dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 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;
+ 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 = "sii902x hdmi";
+ data->dai.stream_name = "sii902x hdmi";
+ data->dai.codecs->dai_name = "i2s-hifi";
+ data->dai.codecs->name = "hdmi-audio-codec.1";
+ data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platforms->of_node = cpu_np;
+ data->dai.ops = &imx_sii902x_ops;
+ data->dai.playback_only = true;
+ data->dai.capture_only = false;
+ data->dai.dai_fmt = SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ 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;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (sii902x_np)
+ of_node_put(sii902x_np);
+ return ret;
+}
+
+static const struct of_device_id imx_sii902x_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-sii902x", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_sii902x_dt_ids);
+
+static struct platform_driver imx_sii902x_driver = {
+ .driver = {
+ .name = "imx-sii902x",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_sii902x_dt_ids,
+ },
+ .probe = imx_sii902x_probe,
+};
+module_platform_driver(imx_sii902x_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX SII902X hdmi audio ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-sii902x");
diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c
index 6c4dadf60355..1a64302aa595 100644
--- a/sound/soc/fsl/imx-spdif.c
+++ b/sound/soc/fsl/imx-spdif.c
@@ -1,16 +1,44 @@
// SPDX-License-Identifier: GPL-2.0+
//
-// Copyright (C) 2013 Freescale Semiconductor, Inc.
+// Copyright (C) 2013-2019 Freescale Semiconductor, Inc.
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/soc.h>
+#include "fsl_spdif.h"
struct imx_spdif_data {
struct snd_soc_dai_link dai;
struct snd_soc_card card;
};
+#define CLK_8K_FREQ 24576000
+#define CLK_11K_FREQ 22579200
+
+static int imx_spdif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->card->dev;
+ int ret = 0;
+ u64 rate = params_rate(params);
+ unsigned int freq;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ freq = do_div(rate, 8000) ? CLK_11K_FREQ : CLK_8K_FREQ;
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, STC_TXCLK_SPDIF_ROOT,
+ freq, SND_SOC_CLOCK_OUT);
+ if (ret)
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops imx_spdif_ops = {
+ .hw_params = imx_spdif_hw_params,
+};
+
static int imx_spdif_audio_probe(struct platform_device *pdev)
{
struct device_node *spdif_np, *np = pdev->dev.of_node;
@@ -48,6 +76,7 @@ static int imx_spdif_audio_probe(struct platform_device *pdev)
data->dai.platforms->of_node = spdif_np;
data->dai.playback_only = true;
data->dai.capture_only = true;
+ data->dai.ops = &imx_spdif_ops;
if (of_property_read_bool(np, "spdif-out"))
data->dai.capture_only = false;
diff --git a/sound/soc/fsl/imx-ssi.c b/sound/soc/fsl/imx-ssi.c
index 42031ba7da31..9038b61317be 100644
--- a/sound/soc/fsl/imx-ssi.c
+++ b/sound/soc/fsl/imx-ssi.c
@@ -520,8 +520,10 @@ static int imx_ssi_probe(struct platform_device *pdev)
}
ssi->irq = platform_get_irq(pdev, 0);
- if (ssi->irq < 0)
+ if (ssi->irq < 0) {
+ dev_err(&pdev->dev, "Failed to get IRQ: %d\n", ssi->irq);
return ssi->irq;
+ }
ssi->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(ssi->clk)) {
diff --git a/sound/soc/fsl/imx-wm8524.c b/sound/soc/fsl/imx-wm8524.c
new file mode 100644
index 000000000000..6d2f1e840b8e
--- /dev/null
+++ b/sound/soc/fsl/imx-wm8524.c
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <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 <linux/pinctrl/consumer.h>
+#include <linux/mfd/syscon.h>
+
+struct imx_priv {
+ struct snd_soc_dai_link dai[3];
+ struct platform_device *pdev;
+ struct platform_device *asrc_pdev;
+ struct snd_soc_card card;
+ struct clk *codec_clk;
+ unsigned int clk_frequency;
+ u32 asrc_rate;
+ u32 asrc_format;
+};
+
+static const struct snd_soc_dapm_widget imx_wm8524_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+};
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ unsigned int fmt;
+ int ret = 0;
+
+ fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2,
+ params_physical_width(params));
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .hw_params = imx_hifi_hw_params,
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Playback", NULL, "CPU-Playback"},
+ {"CPU-Playback", NULL, "ASRC-Playback"},
+};
+
+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_priv *priv = snd_soc_card_get_drvdata(card);
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = priv->asrc_rate;
+ rate->min = priv->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, priv->asrc_format);
+
+ return 0;
+}
+
+static int imx_wm8524_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ priv->clk_frequency = clk_get_rate(priv->codec_clk);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, priv->clk_frequency,
+ SND_SOC_CLOCK_IN);
+
+ return 0;
+}
+
+static int imx_wm8524_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *codec_np = NULL;
+ struct device_node *asrc_np = NULL;
+ struct platform_device *asrc_pdev = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_priv *priv;
+ struct platform_device *codec_pdev = NULL;
+ struct snd_soc_dai_link_component *dlc;
+ int ret;
+ u32 width;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+
+ dlc = devm_kzalloc(&pdev->dev, 9 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
+ }
+
+ 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;
+ }
+
+ codec_pdev = of_find_device_by_node(codec_np);
+ if (!codec_pdev || !codec_pdev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EPROBE_DEFER;
+ goto fail;
+ }
+
+ priv->codec_clk = devm_clk_get(&codec_pdev->dev, "mclk");
+ if (IS_ERR(priv->codec_clk)) {
+ ret = PTR_ERR(priv->codec_clk);
+ dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+
+ priv->dai[0].cpus = &dlc[0];
+ priv->dai[0].num_cpus = 1;
+ priv->dai[0].platforms = &dlc[1];
+ priv->dai[0].num_platforms = 1;
+ priv->dai[0].codecs = &dlc[2];
+ priv->dai[0].num_codecs = 1;
+
+ priv->dai[0].name = "HiFi";
+ priv->dai[0].stream_name = "HiFi";
+ priv->dai[0].codecs->dai_name = "wm8524-hifi",
+ priv->dai[0].ops = &imx_hifi_ops,
+ priv->dai[0].codecs->of_node = codec_np;
+ priv->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ priv->dai[0].platforms->of_node = cpu_np;
+ priv->dai[0].playback_only = 1;
+
+ priv->card.late_probe = imx_wm8524_late_probe;
+ priv->card.num_links = 1;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ priv->card.dapm_widgets = imx_wm8524_dapm_widgets;
+ priv->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8524_dapm_widgets);
+ priv->card.dai_link = priv->dai;
+ priv->card.dapm_routes = audio_map;
+ priv->card.num_dapm_routes = 1;
+
+ /*if there is no asrc controller, we only enable one device*/
+ if (asrc_pdev) {
+ priv->dai[1].cpus = &dlc[3];
+ priv->dai[1].num_cpus = 1;
+ priv->dai[1].platforms = &dlc[4];
+ priv->dai[1].num_platforms = 1;
+ priv->dai[1].codecs = &dlc[5];
+ priv->dai[1].num_codecs = 1;
+
+ priv->dai[2].cpus = &dlc[6];
+ priv->dai[2].num_cpus = 1;
+ priv->dai[2].platforms = &dlc[7];
+ priv->dai[2].num_platforms = 1;
+ priv->dai[2].codecs = &dlc[8];
+ priv->dai[2].num_codecs = 1;
+
+ priv->dai[1].name = "HiFi-ASRC-FE";
+ priv->dai[1].stream_name = "HiFi-ASRC-FE";
+ priv->dai[1].codecs->dai_name = "snd-soc-dummy-dai";
+ priv->dai[1].codecs->name = "snd-soc-dummy";
+ priv->dai[1].cpus->of_node = asrc_np;
+ priv->dai[1].platforms->of_node = asrc_np;
+ priv->dai[1].dynamic = 1;
+ priv->dai[1].dpcm_playback = 1;
+ priv->dai[1].dpcm_capture = 0;
+
+ priv->dai[2].name = "HiFi-ASRC-BE";
+ priv->dai[2].stream_name = "HiFi-ASRC-BE";
+ priv->dai[2].codecs->dai_name = "wm8524-hifi";
+ priv->dai[2].codecs->of_node = codec_np;
+ priv->dai[2].cpus->of_node = cpu_np;
+ priv->dai[2].platforms->name = "snd-soc-dummy";
+ priv->dai[2].no_pcm = 1;
+ priv->dai[2].dpcm_playback = 1;
+ priv->dai[2].dpcm_capture = 0;
+ priv->dai[2].ops = &imx_hifi_ops,
+ priv->dai[2].be_hw_params_fixup = be_hw_params_fixup,
+ priv->card.num_links = 3;
+ priv->card.dai_link = &priv->dai[0];
+ priv->card.num_dapm_routes += 1;
+
+ 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 fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret)
+ goto fail;
+
+ ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing");
+ if (ret)
+ goto fail;
+
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static const struct of_device_id imx_wm8524_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-wm8524", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_wm8524_dt_ids);
+
+static struct platform_driver imx_wm8524_driver = {
+ .driver = {
+ .name = "imx-wm8524",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_wm8524_dt_ids,
+ },
+ .probe = imx_wm8524_probe,
+};
+module_platform_driver(imx_wm8524_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX WM8524 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-wm8524");
diff --git a/sound/soc/fsl/imx-wm8958.c b/sound/soc/fsl/imx-wm8958.c
new file mode 100644
index 000000000000..2ffbd1f69e2b
--- /dev/null
+++ b/sound/soc/fsl/imx-wm8958.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <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 <linux/pinctrl/consumer.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/mfd/syscon.h>
+#include "../fsl/fsl_sai.h"
+#include "../codecs/wm8994.h"
+
+#define DAI_NAME_SIZE 32
+
+struct imx_wm8958_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 *mclk;
+ unsigned int clk_frequency;
+ bool is_codec_master;
+ int sr_stream[2];
+ struct regmap *gpr;
+};
+
+struct imx_priv {
+ int hp_gpio;
+ int hp_active_low;
+ struct snd_soc_component *component;
+ struct platform_device *pdev;
+};
+
+static struct imx_priv card_priv;
+
+static struct snd_soc_jack imx_hp_jack;
+
+static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 250,
+ .invert = 1,
+};
+
+static int hpjack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ char *envp[3], *buf;
+ int hp_status, ret;
+
+ if (!gpio_is_valid(priv->hp_gpio))
+ return 0;
+
+ hp_status = gpio_get_value(priv->hp_gpio);
+ buf = kmalloc(32, GFP_ATOMIC);
+ if (!buf) {
+ dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (hp_status != priv->hp_active_low) {
+ snprintf(buf, 32, "STATE=%d", 2);
+ snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk");
+ ret = imx_hp_jack_gpio.report;
+ } else {
+ snprintf(buf, 32, "STATE=%d", 0);
+ snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk");
+ ret = 0;
+ }
+
+ envp[0] = "NAME=headphone";
+ envp[1] = buf;
+ envp[2] = NULL;
+ kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
+ kfree(buf);
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget imx_wm8958_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+static const struct snd_soc_dapm_route imx_wm8958_dapm_route[] = {
+ {"Headphone Jack", NULL, "HPOUT1L"},
+ {"Headphone Jack", NULL, "HPOUT1R"},
+ {"Ext Spk", NULL, "SPKOUTLP"},
+ {"Ext Spk", NULL, "SPKOUTLN"},
+ {"Ext Spk", NULL, "SPKOUTRP"},
+ {"Ext Spk", NULL, "SPKOUTRN"},
+ {"IN1LN", NULL, "MICBIAS2"},
+};
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ unsigned int sample_rate = params_rate(params);
+ unsigned int pll_out;
+ int ret;
+
+ if (tx && params_width(params) == 24) {
+ if (sample_rate == 88200 || sample_rate == 96000 ||
+ sample_rate == 48000 || sample_rate == 44100) {
+ dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate);
+ return -EINVAL;
+ }
+ } else if (!tx && params_width(params) == 24) {
+ if (sample_rate == 44100 || sample_rate == 48000) {
+ dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate);
+ return -EINVAL;
+ }
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, data->dai.dai_fmt);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, data->dai.dai_fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ data->clk_frequency = clk_get_rate(data->mclk);
+
+ if (!data->is_codec_master) {
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1,
+ data->clk_frequency, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set codec sysclk: %d\n", ret);
+ return ret;
+ }
+ } else {
+ data->sr_stream[tx] = sample_rate;
+
+ if (params_width(params) == 24)
+ pll_out = data->sr_stream[tx] * 384;
+ else
+ pll_out = data->sr_stream[tx] * 256;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1,
+ data->clk_frequency,
+ pll_out);
+ if (ret) {
+ dev_err(dev, "failed to set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+ pll_out, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set codec sysclk: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Set GPIO1 pin function to reserve, so that DAC1 and ADC1 using shared
+ * LRCLK from DACLRCK1.
+ */
+ snd_soc_component_update_bits(codec_dai->component, WM8994_GPIO_1, 0x1f, 0x2);
+
+ /*
+ * Clear ADC_OSR128 bit to support slower SYSCLK, and support ADC sample
+ * rate 8K, 11.025K and 12K.
+ */
+ snd_soc_component_update_bits(codec_dai->component, WM8994_OVERSAMPLING, 1<<1, 0);
+ return 0;
+}
+
+static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ if (data->is_codec_master &&
+ data->sr_stream[!tx] == 0 && data->sr_stream[tx]) {
+ /*
+ * We should connect AIF1CLK source to FLL after enable FLL, and
+ * disconnet AIF1CLK source to FLL before disable FLL, otherwise
+ * FLL worked abnormal.
+ */
+ snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1,
+ data->clk_frequency, SND_SOC_CLOCK_OUT);
+
+ /* Disable FLL1 after all stream finished. */
+ snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, 0, 0);
+ }
+
+ data->sr_stream[tx] = 0;
+
+ return 0;
+}
+
+static u32 imx_wm8958_adc_rates[] = {
+ 8000, 11025, 12000, 16000, 22050,
+ 24000, 32000, 44100, 48000
+};
+
+static u32 imx_wm8958_dac_rates[] = {
+ 8000, 11025, 12000, 16000, 22050,
+ 24000, 32000, 44100, 48000, 88200, 96000
+};
+
+static struct snd_pcm_hw_constraint_list imx_wm8958_adc_rate_constraints = {
+ .count = ARRAY_SIZE(imx_wm8958_adc_rates),
+ .list = imx_wm8958_adc_rates,
+};
+
+static struct snd_pcm_hw_constraint_list imx_wm8958_dac_rate_constraints = {
+ .count = ARRAY_SIZE(imx_wm8958_dac_rates),
+ .list = imx_wm8958_dac_rates,
+};
+
+static int imx_hifi_startup(struct snd_pcm_substream *substream)
+{
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret = 0;
+
+ if (!tx)
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_adc_rate_constraints);
+ else
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_dac_rate_constraints);
+ return ret;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .hw_params = imx_hifi_hw_params,
+ .hw_free = imx_hifi_hw_free,
+ .startup = imx_hifi_startup,
+};
+
+static int imx_wm8958_gpio_init(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ int ret;
+
+ priv->component = codec_dai->component;
+
+ if (gpio_is_valid(priv->hp_gpio)) {
+ imx_hp_jack_gpio.gpio = priv->hp_gpio;
+ imx_hp_jack_gpio.jack_status_check = hpjack_status_check;
+
+ ret = snd_soc_card_jack_new(card, "Headphone Jack",
+ SND_JACK_HEADPHONE, &imx_hp_jack,
+ imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_gpios(&imx_hp_jack, 1,
+ &imx_hp_jack_gpio);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static ssize_t headphone_show(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int hp_status;
+
+ if (!gpio_is_valid(priv->hp_gpio)) {
+ strcpy(buf, "no detect gpio connected\n");
+ return strlen(buf);
+ }
+
+ /* Check if headphone is plugged in */
+ hp_status = gpio_get_value(priv->hp_gpio);
+
+ if (hp_status != priv->hp_active_low)
+ strcpy(buf, "headphone\n");
+ else
+ strcpy(buf, "speaker\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR_RO(headphone);
+
+static int imx_wm8958_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ if (card->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ if (!IS_ERR(data->mclk)) {
+ ret = clk_prepare_enable(data->mclk);
+ if (ret) {
+ dev_err(card->dev,
+ "Failed to enable MCLK: %d\n",
+ ret);
+ return ret;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int imx_wm8958_set_bias_level_post(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_OFF:
+ if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY)
+ if (!IS_ERR(data->mclk))
+ clk_disable_unprepare(data->mclk);
+ break;
+ default:
+ break;
+ }
+
+ card->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int of_parse_gpr(struct platform_device *pdev,
+ struct imx_wm8958_data *data)
+{
+ int ret;
+ struct of_phandle_args args;
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx7d-12x12-lpddr3-arm2-wm8958"))
+ return 0;
+
+ ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+ "gpr", 3, 0, &args);
+ if (ret) {
+ dev_warn(&pdev->dev, "failed to get gpr property\n");
+ return ret;
+ }
+
+ data->gpr = syscon_node_to_regmap(args.np);
+ if (IS_ERR(data->gpr)) {
+ ret = PTR_ERR(data->gpr);
+ dev_err(&pdev->dev, "failed to get gpr regmap\n");
+ return ret;
+ }
+
+ regmap_update_bits(data->gpr, args.args[0], args.args[1],
+ args.args[2]);
+
+ return 0;
+}
+
+static int imx_wm8958_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *codec_np = NULL;
+ struct device_node *np = pdev->dev.of_node;
+ struct platform_device *cpu_pdev;
+ struct imx_priv *priv = &card_priv;
+ struct i2c_client *codec_dev;
+ struct imx_wm8958_data *data;
+ struct snd_soc_dai_link_component *dlc;
+ int ret;
+
+ priv->pdev = pdev;
+
+ dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(np, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(np, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = of_parse_gpr(pdev, data);
+ if (ret)
+ goto fail;
+
+ if (of_property_read_bool(np, "codec-master")) {
+ data->dai.dai_fmt = SND_SOC_DAIFMT_CBM_CFM;
+ data->is_codec_master = true;
+ } else
+ data->dai.dai_fmt = SND_SOC_DAIFMT_CBS_CFS;
+
+ data->mclk = devm_clk_get(&codec_dev->dev, "mclk1");
+ if (IS_ERR(data->mclk)) {
+ ret = PTR_ERR(data->mclk);
+ dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+
+ priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0,
+ (enum of_gpio_flags *)&priv->hp_active_low);
+
+ 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 = "HiFi";
+ data->dai.stream_name = "HiFi";
+ data->dai.codecs->dai_name = "wm8994-aif1";
+ data->dai.codecs->name = "wm8994-codec";
+ data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platforms->of_node = cpu_np;
+ data->dai.ops = &imx_hifi_ops;
+ data->dai.dai_fmt |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+ data->card.set_bias_level = imx_wm8958_set_bias_level;
+ data->card.set_bias_level_post = imx_wm8958_set_bias_level_post;
+ data->card.owner = THIS_MODULE;
+
+ data->card.dev = &pdev->dev;
+ 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;
+ data->card.dapm_widgets = imx_wm8958_dapm_widgets;
+ data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8958_dapm_widgets);
+ data->card.dapm_routes = imx_wm8958_dapm_route;
+ data->card.num_dapm_routes = ARRAY_SIZE(imx_wm8958_dapm_route);
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ ret = imx_wm8958_gpio_init(&data->card);
+
+ if (gpio_is_valid(priv->hp_gpio)) {
+ ret = driver_create_file(pdev->dev.driver,
+ &driver_attr_headphone);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "create hp attr failed (%d)\n", ret);
+ goto fail;
+ }
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static int imx_wm8958_remove(struct platform_device *pdev)
+{
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+ return 0;
+}
+
+static const struct of_device_id imx_wm8958_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-wm8958", },
+ { .compatible = "fsl,imx7d-12x12-lpddr3-arm2-wm8958", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_wm8958_dt_ids);
+
+static struct platform_driver imx_wm8958_driver = {
+ .driver = {
+ .name = "imx-wm8958",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_wm8958_dt_ids,
+ },
+ .probe = imx_wm8958_probe,
+ .remove = imx_wm8958_remove,
+};
+module_platform_driver(imx_wm8958_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX WM8958 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-wm8958");
diff --git a/sound/soc/fsl/imx-wm8960.c b/sound/soc/fsl/imx-wm8960.c
new file mode 100644
index 000000000000..9005b5e36569
--- /dev/null
+++ b/sound/soc/fsl/imx-wm8960.c
@@ -0,0 +1,700 @@
+/*
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <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 <linux/pinctrl/consumer.h>
+#include <linux/mfd/syscon.h>
+#include "../codecs/wm8960.h"
+#include "fsl_sai.h"
+
+struct imx_wm8960_data {
+ struct snd_soc_card card;
+ struct clk *codec_clk;
+ unsigned int clk_frequency;
+ bool is_codec_master;
+ bool is_codec_rpmsg;
+ bool is_stream_in_use[2];
+ bool is_stream_opened[2];
+ struct regmap *gpr;
+ unsigned int hp_det[2];
+ u32 asrc_rate;
+ u32 asrc_format;
+};
+
+struct imx_priv {
+ enum of_gpio_flags hp_active_low;
+ enum of_gpio_flags mic_active_low;
+ bool is_headset_jack;
+ struct platform_device *pdev;
+ struct platform_device *asrc_pdev;
+};
+
+static struct imx_priv card_priv;
+
+static struct snd_soc_jack imx_hp_jack;
+static struct snd_soc_jack_pin imx_hp_jack_pin = {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+};
+static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 250,
+ .invert = 0,
+};
+
+static struct snd_soc_jack imx_mic_jack;
+static struct snd_soc_jack_pin imx_mic_jack_pins = {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+};
+static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
+ .name = "mic detect",
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 250,
+ .invert = 0,
+};
+
+static int hp_jack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct snd_soc_jack *jack = data;
+ struct snd_soc_dapm_context *dapm = &jack->card->dapm;
+ int hp_status, ret;
+
+ hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);
+
+ if (hp_status != priv->hp_active_low) {
+ snd_soc_dapm_disable_pin(dapm, "Ext Spk");
+ if (priv->is_headset_jack) {
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin(dapm, "Main MIC");
+ }
+ ret = imx_hp_jack_gpio.report;
+ } else {
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ if (priv->is_headset_jack) {
+ snd_soc_dapm_disable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_enable_pin(dapm, "Main MIC");
+ }
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int mic_jack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct snd_soc_jack *jack = data;
+ struct snd_soc_dapm_context *dapm = &jack->card->dapm;
+ int mic_status, ret;
+
+ mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);
+
+ if (mic_status != priv->mic_active_low) {
+ snd_soc_dapm_disable_pin(dapm, "Main MIC");
+ ret = imx_mic_jack_gpio.report;
+ } else {
+ snd_soc_dapm_enable_pin(dapm, "Main MIC");
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget imx_wm8960_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_wm8960_jack_init(struct snd_soc_card *card,
+ struct snd_soc_jack *jack, struct snd_soc_jack_pin *pin,
+ struct snd_soc_jack_gpio *gpio)
+{
+ int ret;
+
+ ret = snd_soc_card_jack_new(card, pin->pin, pin->mask, jack, pin, 1);
+ if (ret) {
+ return ret;
+ }
+
+ ret = snd_soc_jack_add_gpios(jack, 1, gpio);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static ssize_t headphone_show(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int hp_status;
+
+ /* Check if headphone is plugged in */
+ hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);
+
+ if (hp_status != priv->hp_active_low)
+ strcpy(buf, "Headphone\n");
+ else
+ strcpy(buf, "Speaker\n");
+
+ return strlen(buf);
+}
+
+static ssize_t micphone_show(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int mic_status;
+
+ /* Check if headphone is plugged in */
+ mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);
+
+ if (mic_status != priv->mic_active_low)
+ strcpy(buf, "Mic Jack\n");
+ else
+ strcpy(buf, "Main MIC\n");
+
+ return strlen(buf);
+}
+static DRIVER_ATTR_RO(headphone);
+static DRIVER_ATTR_RO(micphone);
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct device *dev = card->dev;
+ unsigned int sample_rate = params_rate(params);
+ unsigned int pll_out;
+ unsigned int fmt;
+ int ret = 0;
+
+ data->is_stream_in_use[tx] = true;
+
+ if (data->is_stream_in_use[!tx])
+ return 0;
+
+ if (data->is_codec_master)
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ else
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ if (!data->is_codec_master) {
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params));
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+ return 0;
+ } else {
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+ }
+
+ data->clk_frequency = clk_get_rate(data->codec_clk);
+
+ /* Set codec pll */
+ if (params_width(params) == 24)
+ pll_out = sample_rate * 768;
+ else
+ pll_out = sample_rate * 512;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8960_SYSCLK_AUTO, 0, data->clk_frequency, pll_out);
+ if (ret)
+ return ret;
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8960_SYSCLK_AUTO, pll_out, 0);
+
+ return ret;
+}
+
+static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct device *dev = card->dev;
+ int ret;
+
+ data->is_stream_in_use[tx] = false;
+
+ if (data->is_codec_master && !data->is_stream_in_use[!tx]) {
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF);
+ if (ret)
+ dev_warn(dev, "failed to set codec dai fmt: %d\n", ret);
+ }
+
+ return 0;
+}
+
+static u32 imx_wm8960_rates[] = { 8000, 16000, 32000, 48000 };
+static struct snd_pcm_hw_constraint_list imx_wm8960_rate_constraints = {
+ .count = ARRAY_SIZE(imx_wm8960_rates),
+ .list = imx_wm8960_rates,
+};
+
+static int imx_hifi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+ int ret = 0;
+
+ data->is_stream_opened[tx] = true;
+ if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] ||
+ data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) {
+ data->is_stream_opened[tx] = false;
+ return -EBUSY;
+ }
+
+ if (!data->is_codec_master) {
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &imx_wm8960_rate_constraints);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static void imx_hifi_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ data->is_stream_opened[tx] = false;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .hw_params = imx_hifi_hw_params,
+ .hw_free = imx_hifi_hw_free,
+ .startup = imx_hifi_startup,
+ .shutdown = imx_hifi_shutdown,
+};
+
+static int imx_wm8960_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+
+ /*
+ * codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame
+ * clock for ADCs and DACs
+ */
+ snd_soc_component_update_bits(codec_dai->component, WM8960_IFACE2, 1<<6, 1<<6);
+
+ /* GPIO1 used as headphone detect output */
+ snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL4, 7<<4, 3<<4);
+
+ /* Enable headphone jack detect */
+ snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL2, 1<<6, 1<<6);
+ snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL2, 1<<5, data->hp_det[1]<<5);
+ snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL4, 3<<2, data->hp_det[0]<<2);
+ snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL1, 3, 3);
+
+ return 0;
+}
+
+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_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ struct imx_priv *priv = &card_priv;
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = data->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, data->asrc_format);
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEFS(hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")),
+ 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_CODEC(NULL, "wm8960-hifi")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+static struct snd_soc_dai_link imx_wm8960_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .ops = &imx_hifi_ops,
+ SND_SOC_DAILINK_REG(hifi),
+ },
+ {
+ .name = "HiFi-ASRC-FE",
+ .stream_name = "HiFi-ASRC-FE",
+ .dynamic = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .dpcm_merged_chan = 1,
+ SND_SOC_DAILINK_REG(hifi_fe),
+ },
+ {
+ .name = "HiFi-ASRC-BE",
+ .stream_name = "HiFi-ASRC-BE",
+ .no_pcm = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &imx_hifi_ops,
+ .be_hw_params_fixup = be_hw_params_fixup,
+ SND_SOC_DAILINK_REG(hifi_be),
+ },
+};
+
+static int of_parse_gpr(struct platform_device *pdev,
+ struct imx_wm8960_data *data)
+{
+ int ret;
+ struct of_phandle_args args;
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx7d-evk-wm8960"))
+ return 0;
+
+ ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+ "gpr", 3, 0, &args);
+ if (ret) {
+ dev_warn(&pdev->dev, "failed to get gpr property\n");
+ return ret;
+ }
+
+ data->gpr = syscon_node_to_regmap(args.np);
+ if (IS_ERR(data->gpr)) {
+ ret = PTR_ERR(data->gpr);
+ dev_err(&pdev->dev, "failed to get gpr regmap\n");
+ return ret;
+ }
+
+ regmap_update_bits(data->gpr, args.args[0], args.args[1],
+ args.args[2]);
+
+ return 0;
+}
+
+static int imx_wm8960_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np = NULL, *codec_np = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_priv *priv = &card_priv;
+ struct imx_wm8960_data *data;
+ struct platform_device *asrc_pdev = NULL;
+ struct device_node *asrc_np;
+ u32 width;
+ int ret;
+
+ priv->pdev = pdev;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg"))
+ data->is_codec_rpmsg = true;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 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;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (data->is_codec_rpmsg) {
+ struct platform_device *codec_dev;
+
+ codec_dev = of_find_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
+ if (IS_ERR(data->codec_clk)) {
+ ret = PTR_ERR(data->codec_clk);
+ dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+ } else {
+ struct i2c_client *codec_dev;
+
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EPROBE_DEFER;
+ goto fail;
+ }
+
+ data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
+ if (IS_ERR(data->codec_clk)) {
+ ret = PTR_ERR(data->codec_clk);
+ dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
+ data->is_codec_master = true;
+
+ ret = of_parse_gpr(pdev, data);
+ if (ret)
+ goto fail;
+
+ of_property_read_u32_array(pdev->dev.of_node, "hp-det", data->hp_det, 2);
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
+ }
+
+ data->card.dai_link = imx_wm8960_dai;
+
+ if (data->is_codec_rpmsg) {
+ imx_wm8960_dai[0].codecs->name = "rpmsg-audio-codec-wm8960";
+ imx_wm8960_dai[0].codecs->dai_name = "rpmsg-wm8960-hifi";
+ } else
+ imx_wm8960_dai[0].codecs->of_node = codec_np;
+
+ imx_wm8960_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ imx_wm8960_dai[0].platforms->of_node = cpu_np;
+
+ if (!asrc_pdev) {
+ data->card.num_links = 1;
+ } else {
+ imx_wm8960_dai[1].cpus->of_node = asrc_np;
+ imx_wm8960_dai[1].platforms->of_node = asrc_np;
+ if (data->is_codec_rpmsg) {
+ imx_wm8960_dai[2].codecs->name = "rpmsg-audio-codec-wm8960";
+ imx_wm8960_dai[2].codecs->dai_name = "rpmsg-wm8960-hifi";
+ } else
+ imx_wm8960_dai[2].codecs->of_node = codec_np;
+ imx_wm8960_dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->card.num_links = 3;
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
+ &data->asrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ data->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ data->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ 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.dapm_widgets = imx_wm8960_dapm_widgets;
+ data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets);
+
+ ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
+ if (ret)
+ goto fail;
+
+ data->card.late_probe = imx_wm8960_late_probe;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ imx_hp_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
+ "hp-det-gpios", 0, &priv->hp_active_low);
+
+ imx_mic_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
+ "mic-det-gpios", 0, &priv->mic_active_low);
+
+ if (gpio_is_valid(imx_hp_jack_gpio.gpio) &&
+ gpio_is_valid(imx_mic_jack_gpio.gpio) &&
+ imx_hp_jack_gpio.gpio == imx_mic_jack_gpio.gpio)
+ priv->is_headset_jack = true;
+
+ if (gpio_is_valid(imx_hp_jack_gpio.gpio)) {
+ if (priv->is_headset_jack) {
+ imx_hp_jack_pin.mask |= SND_JACK_MICROPHONE;
+ imx_hp_jack_gpio.report |= SND_JACK_MICROPHONE;
+ }
+
+ imx_hp_jack_gpio.jack_status_check = hp_jack_status_check;
+ imx_hp_jack_gpio.data = &imx_hp_jack;
+ ret = imx_wm8960_jack_init(&data->card, &imx_hp_jack,
+ &imx_hp_jack_pin, &imx_hp_jack_gpio);
+ if (ret) {
+ dev_warn(&pdev->dev, "hp jack init failed (%d)\n", ret);
+ goto out;
+ }
+
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+ if (ret)
+ dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret);
+ }
+
+ if (gpio_is_valid(imx_mic_jack_gpio.gpio)) {
+ if (!priv->is_headset_jack) {
+ imx_mic_jack_gpio.jack_status_check = mic_jack_status_check;
+ imx_mic_jack_gpio.data = &imx_mic_jack;
+ ret = imx_wm8960_jack_init(&data->card, &imx_mic_jack,
+ &imx_mic_jack_pins, &imx_mic_jack_gpio);
+ if (ret) {
+ dev_warn(&pdev->dev, "mic jack init failed (%d)\n", ret);
+ goto out;
+ }
+ }
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_micphone);
+ if (ret)
+ dev_warn(&pdev->dev, "create mic attr failed (%d)\n", ret);
+ }
+
+out:
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static int imx_wm8960_remove(struct platform_device *pdev)
+{
+ driver_remove_file(pdev->dev.driver, &driver_attr_micphone);
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+
+ return 0;
+}
+
+static const struct of_device_id imx_wm8960_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-wm8960", },
+ { .compatible = "fsl,imx7d-evk-wm8960" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
+
+static struct platform_driver imx_wm8960_driver = {
+ .driver = {
+ .name = "imx-wm8960",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_wm8960_dt_ids,
+ },
+ .probe = imx_wm8960_probe,
+ .remove = imx_wm8960_remove,
+};
+module_platform_driver(imx_wm8960_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX WM8960 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-wm8960");
diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c
new file mode 100644
index 000000000000..cb50a6fa48a9
--- /dev/null
+++ b/sound/soc/fsl/imx-wm8962.c
@@ -0,0 +1,860 @@
+/*
+ * Copyright (C) 2013-2016 Freescale Semiconductor, Inc.
+ *
+ * Based on imx-sgtl5000.c
+ * Copyright (C) 2012 Freescale Semiconductor, Inc.
+ * Copyright (C) 2012 Linaro Ltd.
+ * 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:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/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 <linux/pinctrl/consumer.h>
+
+#include "../codecs/wm8962.h"
+#include "imx-audmux.h"
+
+#define DAI_NAME_SIZE 32
+
+struct imx_wm8962_data {
+ struct snd_soc_dai_link dai[3];
+ 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;
+ bool is_codec_master;
+};
+
+struct imx_priv {
+ int hp_gpio;
+ int hp_active_low;
+ int mic_gpio;
+ int mic_active_low;
+ bool amic_mono;
+ bool dmic_mono;
+ struct snd_soc_component *component;
+ struct platform_device *pdev;
+ struct snd_pcm_substream *first_stream;
+ struct snd_pcm_substream *second_stream;
+ struct platform_device *asrc_pdev;
+ u32 asrc_rate;
+ u32 asrc_format;
+};
+static struct imx_priv card_priv;
+
+#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID
+static int sample_rate = 44100;
+static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE;
+#endif
+
+static struct snd_soc_jack imx_hp_jack;
+static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 250,
+ .invert = 0,
+};
+
+static struct snd_soc_jack imx_mic_jack;
+static struct snd_soc_jack_pin imx_mic_jack_pins[] = {
+ {
+ .pin = "AMIC",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
+ .name = "microphone detect",
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 250,
+ .invert = 0,
+};
+
+static int hpjack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ char *envp[3], *buf;
+ int hp_status, ret;
+
+ if (!gpio_is_valid(priv->hp_gpio))
+ return 0;
+
+ hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
+
+ buf = kmalloc(32, GFP_ATOMIC);
+ if (!buf) {
+ dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (hp_status != priv->hp_active_low) {
+ snprintf(buf, 32, "STATE=%d", 2);
+ snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk");
+ ret = imx_hp_jack_gpio.report;
+ } else {
+ snprintf(buf, 32, "STATE=%d", 0);
+ snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk");
+ ret = 0;
+ }
+
+ envp[0] = "NAME=headphone";
+ envp[1] = buf;
+ envp[2] = NULL;
+ kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
+ kfree(buf);
+
+ return ret;
+}
+
+static int micjack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ char *envp[3], *buf;
+ int mic_status, ret;
+
+ if (!gpio_is_valid(priv->mic_gpio))
+ return 0;
+
+ mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0;
+
+ if ((mic_status != priv->mic_active_low && priv->amic_mono)
+ || (mic_status == priv->mic_active_low && priv->dmic_mono))
+ snd_soc_component_update_bits(priv->component, WM8962_THREED1,
+ WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX);
+
+ else
+ snd_soc_component_update_bits(priv->component, WM8962_THREED1,
+ WM8962_ADC_MONOMIX_MASK, 0);
+
+ buf = kmalloc(32, GFP_ATOMIC);
+ if (!buf) {
+ dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (mic_status != priv->mic_active_low) {
+ snprintf(buf, 32, "STATE=%d", 2);
+ snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "DMIC");
+ ret = imx_mic_jack_gpio.report;
+ } else {
+ snprintf(buf, 32, "STATE=%d", 0);
+ snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "DMIC");
+ ret = 0;
+ }
+
+ envp[0] = "NAME=microphone";
+ envp[1] = buf;
+ envp[2] = NULL;
+ kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
+ kfree(buf);
+
+ return ret;
+}
+
+
+static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_MIC("AMIC", NULL),
+ SND_SOC_DAPM_MIC("DMIC", NULL),
+};
+
+static u32 imx_wm8962_rates[] = {32000, 48000, 96000};
+static struct snd_pcm_hw_constraint_list imx_wm8962_rate_constraints = {
+ .count = ARRAY_SIZE(imx_wm8962_rates),
+ .list = imx_wm8962_rates,
+};
+
+static int imx_hifi_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_wm8962_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ if (!data->is_codec_master) {
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &imx_wm8962_rate_constraints);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ u32 dai_format;
+ int ret = 0;
+
+ sample_rate = params_rate(params);
+ sample_format = params_format(params);
+
+ if (data->is_codec_master)
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ else
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .startup = imx_hifi_startup,
+ .hw_params = imx_hifi_hw_params,
+};
+
+static int imx_wm8962_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
+ struct device *dev = &priv->pdev->dev;
+ unsigned int pll_out;
+ int ret;
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ data->clk_frequency = clk_get_rate(data->codec_clk);
+
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
+ if (sample_format == SNDRV_PCM_FORMAT_S24_LE
+ || sample_format == SNDRV_PCM_FORMAT_S20_3LE)
+ pll_out = sample_rate * 384;
+ else
+ pll_out = sample_rate * 256;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
+ WM8962_FLL_MCLK, data->clk_frequency,
+ pll_out);
+ if (ret < 0) {
+ dev_err(dev, "failed to start FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ WM8962_SYSCLK_FLL, pll_out,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(dev, "failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (dapm->bias_level == SND_SOC_BIAS_PREPARE) {
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ WM8962_SYSCLK_MCLK, data->clk_frequency,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(dev,
+ "failed to switch away from FLL: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
+ 0, 0, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to stop FLL: %d\n", ret);
+ return ret;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+#else
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ struct snd_soc_card *card = platform_get_drvdata(priv->pdev);
+ struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
+ unsigned int sample_rate = params_rate(params);
+ snd_pcm_format_t sample_format = params_format(params);
+ u32 dai_format, pll_out;
+ int ret = 0;
+
+ if (!priv->first_stream) {
+ priv->first_stream = substream;
+ } else {
+ priv->second_stream = substream;
+
+ /* We suppose the two substream are using same params */
+ return 0;
+ }
+
+ data->clk_frequency = clk_get_rate(data->codec_clk);
+
+ if (data->is_codec_master)
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ else
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ if (sample_format == SNDRV_PCM_FORMAT_S24_LE
+ || sample_format == SNDRV_PCM_FORMAT_S20_3LE)
+ pll_out = sample_rate * 384;
+ else
+ pll_out = sample_rate * 256;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, WM8962_FLL_MCLK,
+ data->clk_frequency, pll_out);
+ if (ret) {
+ dev_err(dev, "failed to start FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_FLL,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ int ret;
+
+ /* We don't need to handle anything if there's no substream running */
+ if (!priv->first_stream)
+ return 0;
+
+ if (priv->first_stream == substream)
+ priv->first_stream = priv->second_stream;
+ priv->second_stream = NULL;
+
+ if (!priv->first_stream) {
+ /*
+ * Continuously setting FLL would cause playback distortion.
+ * We can fix it just by mute codec after playback.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dai_digital_mute(codec_dai, 1, substream->stream);
+
+ /*
+ * WM8962 doesn't allow us to continuously setting FLL,
+ * So we set MCLK as sysclk once, which'd remove the limitation.
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
+ 0, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(dev, "failed to switch away from FLL: %d\n", ret);
+ return ret;
+ }
+
+ /* Disable FLL and let codec do pm_runtime_put() */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
+ WM8962_FLL_MCLK, 0, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to stop FLL: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .startup = imx_hifi_startup,
+ .hw_params = imx_hifi_hw_params,
+ .hw_free = imx_hifi_hw_free,
+};
+#endif /* CONFIG_SND_SOC_IMX_WM8962_ANDROID */
+
+static int imx_wm8962_gpio_init(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+
+ priv->component = codec_dai->component;
+
+ if (gpio_is_valid(priv->hp_gpio)) {
+ imx_hp_jack_gpio.gpio = priv->hp_gpio;
+ imx_hp_jack_gpio.jack_status_check = hpjack_status_check;
+
+ snd_soc_card_jack_new(card, "Headphone Jack",
+ SND_JACK_HEADPHONE, &imx_hp_jack,
+ imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins));
+
+ snd_soc_jack_add_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio);
+ }
+
+ if (gpio_is_valid(priv->mic_gpio)) {
+ imx_mic_jack_gpio.gpio = priv->mic_gpio;
+ imx_mic_jack_gpio.jack_status_check = micjack_status_check;
+
+ snd_soc_card_jack_new(card, "AMIC",
+ SND_JACK_MICROPHONE, &imx_mic_jack,
+ imx_mic_jack_pins, ARRAY_SIZE(imx_mic_jack_pins));
+
+ snd_soc_jack_add_gpios(&imx_mic_jack, 1, &imx_mic_jack_gpio);
+ } else if (priv->amic_mono || priv->dmic_mono) {
+ /*
+ * Permanent set monomix bit if only one microphone
+ * is present on the board while it needs monomix.
+ */
+ snd_soc_component_update_bits(priv->component, WM8962_THREED1,
+ WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX);
+ }
+
+ return 0;
+}
+
+static ssize_t headphone_show(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int hp_status;
+
+ if (!gpio_is_valid(priv->hp_gpio)) {
+ strcpy(buf, "no detect gpio connected\n");
+ return strlen(buf);
+ }
+
+ /* Check if headphone is plugged in */
+ hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
+
+ if (hp_status != priv->hp_active_low)
+ strcpy(buf, "headphone\n");
+ else
+ strcpy(buf, "speaker\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR_RO(headphone);
+
+static ssize_t microphone_show(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int mic_status;
+
+ if (!gpio_is_valid(priv->mic_gpio)) {
+ strcpy(buf, "no detect gpio connected\n");
+ return strlen(buf);
+ }
+
+ /* Check if analog microphone is plugged in */
+ mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0;
+
+ if (mic_status != priv->mic_active_low)
+ strcpy(buf, "amic\n");
+ else
+ strcpy(buf, "dmic\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR_RO(microphone);
+
+static int imx_wm8962_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
+ struct device *dev = &priv->pdev->dev;
+ int ret;
+
+ data->clk_frequency = clk_get_rate(data->codec_clk);
+ rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
+ codec_dai = rtd->codec_dai;
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
+ data->clk_frequency, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ dev_err(dev, "failed to set sysclk in %s\n", __func__);
+
+ return ret;
+}
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params) {
+ struct imx_priv *priv = &card_priv;
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = priv->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, priv->asrc_format);
+
+ return 0;
+}
+
+static int imx_wm8962_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *cpu_np = NULL, *codec_np = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_priv *priv = &card_priv;
+ struct i2c_client *codec_dev;
+ struct imx_wm8962_data *data;
+ int int_port, ext_port, tmp_port;
+ int ret;
+ struct platform_device *asrc_pdev = NULL;
+ struct device_node *asrc_np;
+ struct snd_soc_dai_link_component *dlc;
+ u32 width;
+
+ priv->pdev = pdev;
+ priv->asrc_pdev = NULL;
+
+ dlc = devm_kzalloc(&pdev->dev, 9 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
+ data->is_codec_master = true;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (!strstr(cpu_np->name, "ssi"))
+ goto audmux_bypass;
+
+ ret = of_property_read_u32(np, "mux-int-port", &int_port);
+ if (ret) {
+ dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
+ goto fail;
+ }
+ ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
+ if (ret) {
+ dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
+ 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--;
+ if (data->is_codec_master) {
+ tmp_port = int_port;
+ int_port = ext_port;
+ ext_port = tmp_port;
+ }
+
+ ret = imx_audmux_v2_configure_port(ext_port,
+ IMX_AUDMUX_V2_PTCR_SYN |
+ IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_TFSDIR |
+ IMX_AUDMUX_V2_PTCR_TCLKDIR,
+ IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
+ if (ret) {
+ dev_err(&pdev->dev, "audmux internal port setup failed\n");
+ goto fail;
+ }
+ ret = imx_audmux_v2_configure_port(int_port,
+ IMX_AUDMUX_V2_PTCR_SYN,
+ IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
+ if (ret) {
+ dev_err(&pdev->dev, "audmux external port setup failed\n");
+ goto fail;
+ }
+
+audmux_bypass:
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SSI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
+ }
+
+ priv->first_stream = NULL;
+ priv->second_stream = NULL;
+
+ data->codec_clk = clk_get(&codec_dev->dev, NULL);
+ if (IS_ERR(data->codec_clk)) {
+ ret = PTR_ERR(data->codec_clk);
+ dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+
+ priv->amic_mono = of_property_read_bool(codec_np, "amic-mono");
+ priv->dmic_mono = of_property_read_bool(codec_np, "dmic-mono");
+
+ priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0,
+ (enum of_gpio_flags *)&priv->hp_active_low);
+ priv->mic_gpio = of_get_named_gpio_flags(np, "mic-det-gpios", 0,
+ (enum of_gpio_flags *)&priv->mic_active_low);
+
+ data->dai[0].cpus = &dlc[0];
+ data->dai[0].num_cpus = 1;
+ data->dai[0].platforms = &dlc[1];
+ data->dai[0].num_platforms = 1;
+ data->dai[0].codecs = &dlc[2];
+ data->dai[0].num_codecs = 1;
+
+ data->dai[0].name = "HiFi";
+ data->dai[0].stream_name = "HiFi";
+ data->dai[0].codecs->dai_name = "wm8962";
+ data->dai[0].codecs->of_node = codec_np;
+ data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai[0].platforms->of_node = cpu_np;
+ data->dai[0].ops = &imx_hifi_ops;
+ data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+ if (data->is_codec_master)
+ data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+ else
+ data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
+
+ data->card.num_links = 1;
+
+ if (asrc_pdev) {
+ data->dai[1].cpus = &dlc[3];
+ data->dai[1].num_cpus = 1;
+ data->dai[1].platforms = &dlc[4];
+ data->dai[1].num_platforms = 1;
+ data->dai[1].codecs = &dlc[5];
+ data->dai[1].num_codecs = 1;
+
+ data->dai[2].cpus = &dlc[6];
+ data->dai[2].num_cpus = 1;
+ data->dai[2].platforms = &dlc[7];
+ data->dai[2].num_platforms = 1;
+ data->dai[2].codecs = &dlc[8];
+ data->dai[2].num_codecs = 1;
+
+ data->dai[0].ignore_pmdown_time = 1;
+ data->dai[1].name = "HiFi-ASRC-FE";
+ data->dai[1].stream_name = "HiFi-ASRC-FE";
+ data->dai[1].codecs->name = "snd-soc-dummy";
+ data->dai[1].codecs->dai_name = "snd-soc-dummy-dai";
+ data->dai[1].cpus->of_node = asrc_np;
+ data->dai[1].platforms->of_node = asrc_np;
+ data->dai[1].dynamic = 1;
+ data->dai[1].ignore_pmdown_time = 1;
+ data->dai[1].dpcm_playback = 1;
+ data->dai[1].dpcm_capture = 1;
+ data->dai[1].dpcm_merged_chan = 1;
+
+ data->dai[2].name = "HiFi-ASRC-BE";
+ data->dai[2].stream_name = "HiFi-ASRC-BE";
+ data->dai[2].codecs->dai_name = "wm8962";
+ data->dai[2].codecs->of_node = codec_np;
+ data->dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai[2].platforms->name = "snd-soc-dummy";
+ data->dai[2].ops = &imx_hifi_ops;
+ data->dai[2].be_hw_params_fixup = be_hw_params_fixup;
+ data->dai[2].no_pcm = 1;
+ data->dai[2].ignore_pmdown_time = 1;
+ data->dai[2].dpcm_playback = 1;
+ data->dai[2].dpcm_capture = 1;
+ data->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 fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ data->card.dev = &pdev->dev;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+ ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
+ if (ret)
+ goto fail;
+ data->card.owner = THIS_MODULE;
+ data->card.dai_link = data->dai;
+ data->card.dapm_widgets = imx_wm8962_dapm_widgets;
+ data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8962_dapm_widgets);
+
+ data->card.late_probe = imx_wm8962_late_probe;
+
+#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID
+ data->card.set_bias_level = imx_wm8962_set_bias_level;
+#endif
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ imx_wm8962_gpio_init(&data->card);
+
+ if (gpio_is_valid(priv->hp_gpio)) {
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+ if (ret) {
+ dev_err(&pdev->dev, "create hp attr failed (%d)\n", ret);
+ goto fail_hp;
+ }
+ }
+
+ if (gpio_is_valid(priv->mic_gpio)) {
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_microphone);
+ if (ret) {
+ dev_err(&pdev->dev, "create mic attr failed (%d)\n", ret);
+ goto fail_mic;
+ }
+ }
+
+ goto fail;
+
+fail_mic:
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+fail_hp:
+fail:
+ of_node_put(cpu_np);
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static int imx_wm8962_remove(struct platform_device *pdev)
+{
+ driver_remove_file(pdev->dev.driver, &driver_attr_microphone);
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+
+ return 0;
+}
+
+static const struct of_device_id imx_wm8962_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-wm8962", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_wm8962_dt_ids);
+
+static struct platform_driver imx_wm8962_driver = {
+ .driver = {
+ .name = "imx-wm8962",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_wm8962_dt_ids,
+ },
+ .probe = imx_wm8962_probe,
+ .remove = imx_wm8962_remove,
+};
+module_platform_driver(imx_wm8962_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX WM8962 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-wm8962");
diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c
index 2a4ffe945177..4b1170562751 100644
--- a/sound/soc/kirkwood/kirkwood-i2s.c
+++ b/sound/soc/kirkwood/kirkwood-i2s.c
@@ -537,8 +537,10 @@ static int kirkwood_i2s_dev_probe(struct platform_device *pdev)
return PTR_ERR(priv->io);
priv->irq = platform_get_irq(pdev, 0);
- if (priv->irq < 0)
+ if (priv->irq < 0) {
+ dev_err(&pdev->dev, "platform_get_irq failed: %d\n", priv->irq);
return priv->irq;
+ }
if (np) {
priv->burst = 128; /* might be 32 or 128 */
diff --git a/sound/soc/mediatek/common/mtk-btcvsd.c b/sound/soc/mediatek/common/mtk-btcvsd.c
index d00608c73c6e..c7a81c4be068 100644
--- a/sound/soc/mediatek/common/mtk-btcvsd.c
+++ b/sound/soc/mediatek/common/mtk-btcvsd.c
@@ -1335,8 +1335,10 @@ static int mtk_btcvsd_snd_probe(struct platform_device *pdev)
/* irq */
irq_id = platform_get_irq(pdev, 0);
- if (irq_id <= 0)
+ if (irq_id <= 0) {
+ dev_err(dev, "%pOFn no irq found\n", dev->of_node);
return irq_id < 0 ? irq_id : -ENXIO;
+ }
ret = devm_request_irq(dev, irq_id, mtk_btcvsd_snd_irq_handler,
IRQF_TRIGGER_LOW, "BTCVSD_ISR_Handle",
diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c
index 76502ba261c8..0239f840b096 100644
--- a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c
+++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c
@@ -1350,8 +1350,10 @@ static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev)
return -ENOMEM;
irq_id = platform_get_irq_byname(pdev, "asys");
- if (irq_id < 0)
+ if (irq_id < 0) {
+ dev_err(dev, "unable to get ASYS IRQ\n");
return irq_id;
+ }
ret = devm_request_irq(dev, irq_id, mt2701_asys_isr,
IRQF_TRIGGER_NONE, "asys-isr", (void *)afe);
diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c
index 0ee29255e731..90bd2c92cae7 100644
--- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c
+++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c
@@ -1074,8 +1074,10 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev)
afe->dev = &pdev->dev;
irq_id = platform_get_irq(pdev, 0);
- if (irq_id <= 0)
+ if (irq_id <= 0) {
+ dev_err(afe->dev, "np %pOFn no irq\n", afe->dev->of_node);
return irq_id < 0 ? irq_id : -ENXIO;
+ }
ret = devm_request_irq(afe->dev, irq_id, mt8173_afe_irq_handler,
0, "Afe_ISR_Handle", (void *)afe);
if (ret) {
diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c
index 1e38ce858326..a2c79426513b 100644
--- a/sound/soc/mxs/mxs-saif.c
+++ b/sound/soc/mxs/mxs-saif.c
@@ -790,8 +790,12 @@ static int mxs_saif_probe(struct platform_device *pdev)
return PTR_ERR(saif->base);
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
- return irq;
+ if (irq < 0) {
+ ret = irq;
+ dev_err(&pdev->dev, "failed to get irq resource: %d\n",
+ ret);
+ return ret;
+ }
saif->dev = &pdev->dev;
ret = devm_request_irq(&pdev->dev, irq, mxs_saif_irq, 0,
diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c
index 4c745baa39f7..cf7a299f4547 100644
--- a/sound/soc/qcom/lpass-platform.c
+++ b/sound/soc/qcom/lpass-platform.c
@@ -564,8 +564,11 @@ int asoc_qcom_lpass_platform_register(struct platform_device *pdev)
int ret;
drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif");
- if (drvdata->lpaif_irq < 0)
+ if (drvdata->lpaif_irq < 0) {
+ dev_err(&pdev->dev, "error getting irq handle: %d\n",
+ drvdata->lpaif_irq);
return -ENODEV;
+ }
/* ensure audio hardware is disabled */
ret = regmap_write(drvdata->lpaif_map,
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 88978a3036c4..9d3b546bae7b 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -1886,6 +1886,8 @@ match:
/* convert non BE into BE */
dai_link->no_pcm = 1;
+ dai_link->dpcm_playback = 1;
+ dai_link->dpcm_capture = 1;
/* override any BE fixups */
dai_link->be_hw_params_fixup =
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
index b600d3eaaf5c..c2a78174f249 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -1295,7 +1295,7 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card,
}
/* dai link name and stream name set correctly ? */
- dev_err(card->dev, "ASoC: can't get %s BE for %s\n",
+ dev_dbg(card->dev, "ASoC: can't get %s BE for %s\n",
stream ? "capture" : "playback", widget->name);
return NULL;
}
@@ -1445,7 +1445,7 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream,
/* is there a valid BE rtd for this widget */
be = dpcm_get_be(card, list->widgets[i], stream);
if (!be) {
- dev_err(fe->dev, "ASoC: no BE found for %s\n",
+ dev_dbg(fe->dev, "ASoC: no BE found for %s\n",
list->widgets[i]->name);
continue;
}
@@ -2818,6 +2818,26 @@ static void soc_pcm_private_free(struct snd_pcm *pcm)
snd_soc_pcm_component_free(pcm);
}
+static int soc_rtdcom_ack(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_rtdcom_list *rtdcom;
+ struct snd_soc_component *component;
+
+ for_each_rtdcom(rtd, rtdcom) {
+ component = rtdcom->component;
+
+ if (!component->driver->ops ||
+ !component->driver->ops->ack)
+ continue;
+
+ /* FIXME. it returns 1st ask now */
+ return component->driver->ops->ack(substream);
+ }
+
+ return -EINVAL;
+}
+
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
@@ -2941,6 +2961,8 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
if (!ops)
continue;
+ if (ops->ack)
+ rtd->ops.ack = soc_rtdcom_ack;
if (ops->copy_user)
rtd->ops.copy_user = snd_soc_pcm_component_copy_user;
if (ops->page)
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c
index 81f28f7ff1a0..6a32eb8c0795 100644
--- a/sound/soc/sof/core.c
+++ b/sound/soc/sof/core.c
@@ -10,7 +10,6 @@
#include <linux/firmware.h>
#include <linux/module.h>
-#include <asm/unaligned.h>
#include <sound/soc.h>
#include <sound/sof.h>
#include "sof-priv.h"
@@ -196,67 +195,9 @@ out:
EXPORT_SYMBOL(snd_sof_get_status);
/*
- * Generic buffer page table creation.
- * Take the each physical page address and drop the least significant unused
- * bits from each (based on PAGE_SIZE). Then pack valid page address bits
- * into compressed page table.
- */
-
-int snd_sof_create_page_table(struct snd_sof_dev *sdev,
- struct snd_dma_buffer *dmab,
- unsigned char *page_table, size_t size)
-{
- int i, pages;
-
- pages = snd_sgbuf_aligned_pages(size);
-
- dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n",
- dmab->area, size, pages);
-
- for (i = 0; i < pages; i++) {
- /*
- * The number of valid address bits for each page is 20.
- * idx determines the byte position within page_table
- * where the current page's address is stored
- * in the compressed page_table.
- * This can be calculated by multiplying the page number by 2.5.
- */
- u32 idx = (5 * i) >> 1;
- u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT;
- u8 *pg_table;
-
- dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn);
-
- pg_table = (u8 *)(page_table + idx);
-
- /*
- * pagetable compression:
- * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5
- * ___________pfn 0__________ __________pfn 1___________ _pfn 2...
- * .... .... .... .... .... .... .... .... .... .... ....
- * It is created by:
- * 1. set current location to 0, PFN index i to 0
- * 2. put pfn[i] at current location in Little Endian byte order
- * 3. calculate an intermediate value as
- * x = (pfn[i+1] << 4) | (pfn[i] & 0xf)
- * 4. put x at offset (current location + 2) in LE byte order
- * 5. increment current location by 5 bytes, increment i by 2
- * 6. continue to (2)
- */
- if (i & 1)
- put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4,
- pg_table);
- else
- put_unaligned_le32(pfn, pg_table);
- }
-
- return pages;
-}
-
-/*
* SOF Driver enumeration.
*/
-static int sof_machine_check(struct snd_sof_dev *sdev)
+int sof_machine_check(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *plat_data = sdev->pdata;
#if IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)
@@ -277,8 +218,10 @@ static int sof_machine_check(struct snd_sof_dev *sdev)
if (!machine)
return -ENOMEM;
- ret = sof_nocodec_setup(sdev->dev, plat_data, machine,
- plat_data->desc, plat_data->desc->ops);
+ machine->drv_name = "sof-nocodec";
+ plat_data->fw_filename = plat_data->desc->nocodec_fw_filename;
+ plat_data->tplg_filename = plat_data->desc->nocodec_tplg_filename;
+ ret = sof_nocodec_setup(sdev->dev, plat_data->desc->ops);
if (ret < 0)
return ret;
@@ -287,13 +230,45 @@ static int sof_machine_check(struct snd_sof_dev *sdev)
return 0;
#endif
}
+EXPORT_SYMBOL(sof_machine_check);
-static int sof_probe_continue(struct snd_sof_dev *sdev)
+int sof_machine_register(struct snd_sof_dev *sdev, void *pdata)
{
- struct snd_sof_pdata *plat_data = sdev->pdata;
+ struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata;
const char *drv_name;
const void *mach;
int size;
+
+ drv_name = plat_data->machine->drv_name;
+ mach = (const void *)plat_data->machine;
+ size = sizeof(*plat_data->machine);
+
+ /* register machine driver, pass machine info as pdata */
+ plat_data->pdev_mach =
+ platform_device_register_data(sdev->dev, drv_name,
+ PLATFORM_DEVID_NONE, mach, size);
+ if (IS_ERR(plat_data->pdev_mach))
+ return PTR_ERR(plat_data->pdev_mach);
+
+ dev_dbg(sdev->dev, "created machine %s\n",
+ dev_name(&plat_data->pdev_mach->dev));
+
+ return 0;
+}
+EXPORT_SYMBOL(sof_machine_register);
+
+void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata)
+{
+ struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata;
+
+ if (!IS_ERR_OR_NULL(plat_data->pdev_mach))
+ platform_device_unregister(plat_data->pdev_mach);
+}
+EXPORT_SYMBOL(sof_machine_unregister);
+
+static int sof_probe_continue(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *plat_data = sdev->pdata;
int ret;
/* probe the DSP hardware */
@@ -304,7 +279,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
}
/* check machine info */
- ret = sof_machine_check(sdev);
+ ret = snd_sof_machine_check(sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to get machine info %d\n",
ret);
@@ -371,22 +346,17 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
goto fw_run_err;
}
- drv_name = plat_data->machine->drv_name;
- mach = (const void *)plat_data->machine;
- size = sizeof(*plat_data->machine);
-
- /* register machine driver, pass machine info as pdata */
- plat_data->pdev_mach =
- platform_device_register_data(sdev->dev, drv_name,
- PLATFORM_DEVID_NONE, mach, size);
-
- if (IS_ERR(plat_data->pdev_mach)) {
- ret = PTR_ERR(plat_data->pdev_mach);
+ ret = snd_sof_machine_register(sdev, plat_data);
+ if (ret < 0)
goto fw_run_err;
- }
- dev_dbg(sdev->dev, "created machine %s\n",
- dev_name(&plat_data->pdev_mach->dev));
+ /*
+ * Some platforms in SOF, ex: BYT, may not have their platform PM
+ * callbacks set. Increment the usage count so as to
+ * prevent the device from entering runtime suspend.
+ */
+ if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume)
+ pm_runtime_get_noresume(sdev->dev);
if (plat_data->sof_probe_complete)
plat_data->sof_probe_complete(sdev->dev);
@@ -504,9 +474,7 @@ int snd_sof_device_remove(struct device *dev)
* will remove the component driver and unload the topology
* before freeing the snd_card.
*/
- if (!IS_ERR_OR_NULL(pdata->pdev_mach))
- platform_device_unregister(pdata->pdev_mach);
-
+ snd_sof_machine_unregister(sdev, pdata);
/*
* Unregistering the machine driver results in unloading the topology.
* Some widgets, ex: scheduler, attempt to power down the core they are
diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c
index 8dc7a5558da4..1f46bfba0f1b 100644
--- a/sound/soc/sof/intel/apl.c
+++ b/sound/soc/sof/intel/apl.c
@@ -53,6 +53,11 @@ const struct snd_sof_dsp_ops sof_apl_ops = {
.ipc_msg_data = hda_ipc_msg_data,
.ipc_pcm_params = hda_ipc_pcm_params,
+ /* machine driver */
+ .machine_check = sof_machine_check,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+
/* debug */
.debug_map = apl_dsp_debugfs,
.debug_map_count = ARRAY_SIZE(apl_dsp_debugfs),
diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c
index 80e2826fb447..116174df4eec 100644
--- a/sound/soc/sof/intel/bdw.c
+++ b/sound/soc/sof/intel/bdw.c
@@ -483,8 +483,11 @@ static int bdw_probe(struct snd_sof_dev *sdev)
/* register our IRQ */
sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc);
- if (sdev->ipc_irq < 0)
+ if (sdev->ipc_irq < 0) {
+ dev_err(sdev->dev, "error: failed to get IRQ at index %d\n",
+ desc->irqindex_host_ipc);
return sdev->ipc_irq;
+ }
dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq);
ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq,
@@ -554,6 +557,11 @@ const struct snd_sof_dsp_ops sof_bdw_ops = {
.ipc_msg_data = intel_ipc_msg_data,
.ipc_pcm_params = intel_ipc_pcm_params,
+ /* machine driver */
+ .machine_check = sof_machine_check,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+
/* debug */
.debug_map = bdw_debugfs,
.debug_map_count = ARRAY_SIZE(bdw_debugfs),
diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c
index a1e514f71739..b55aecd6fe3f 100644
--- a/sound/soc/sof/intel/byt.c
+++ b/sound/soc/sof/intel/byt.c
@@ -493,6 +493,11 @@ const struct snd_sof_dsp_ops sof_tng_ops = {
.ipc_msg_data = intel_ipc_msg_data,
.ipc_pcm_params = intel_ipc_pcm_params,
+ /* machine driver */
+ .machine_check = sof_machine_check,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+
/* debug */
.debug_map = byt_debugfs,
.debug_map_count = ARRAY_SIZE(byt_debugfs),
@@ -599,8 +604,11 @@ static int byt_acpi_probe(struct snd_sof_dev *sdev)
irq:
/* register our IRQ */
sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc);
- if (sdev->ipc_irq < 0)
+ if (sdev->ipc_irq < 0) {
+ dev_err(sdev->dev, "error: failed to get IRQ at index %d\n",
+ desc->irqindex_host_ipc);
return sdev->ipc_irq;
+ }
dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq);
ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq,
@@ -654,6 +662,11 @@ const struct snd_sof_dsp_ops sof_byt_ops = {
.ipc_msg_data = intel_ipc_msg_data,
.ipc_pcm_params = intel_ipc_pcm_params,
+ /* machine driver */
+ .machine_check = sof_machine_check,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+
/* debug */
.debug_map = byt_debugfs,
.debug_map_count = ARRAY_SIZE(byt_debugfs),
@@ -713,6 +726,11 @@ const struct snd_sof_dsp_ops sof_cht_ops = {
.ipc_msg_data = intel_ipc_msg_data,
.ipc_pcm_params = intel_ipc_pcm_params,
+ /* machine driver */
+ .machine_check = sof_machine_check,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+
/* debug */
.debug_map = cht_debugfs,
.debug_map_count = ARRAY_SIZE(cht_debugfs),
diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c
index 4ddd73762d81..eba1bf4624a2 100644
--- a/sound/soc/sof/intel/cnl.c
+++ b/sound/soc/sof/intel/cnl.c
@@ -211,6 +211,11 @@ const struct snd_sof_dsp_ops sof_cnl_ops = {
.ipc_msg_data = hda_ipc_msg_data,
.ipc_pcm_params = hda_ipc_pcm_params,
+ /* machine driver */
+ .machine_check = sof_machine_check,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+
/* debug */
.debug_map = cnl_dsp_debugfs,
.debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs),
diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c
index 3d128e5a132c..2233146386cc 100644
--- a/sound/soc/sof/nocodec.c
+++ b/sound/soc/sof/nocodec.c
@@ -63,23 +63,11 @@ static int sof_nocodec_bes_setup(struct device *dev,
}
int sof_nocodec_setup(struct device *dev,
- struct snd_sof_pdata *sof_pdata,
- struct snd_soc_acpi_mach *mach,
- const struct sof_dev_desc *desc,
const struct snd_sof_dsp_ops *ops)
{
struct snd_soc_dai_link *links;
int ret;
- if (!mach)
- return -EINVAL;
-
- sof_pdata->drv_name = "sof-nocodec";
-
- mach->drv_name = "sof-nocodec";
- sof_pdata->fw_filename = desc->nocodec_fw_filename;
- sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
-
/* create dummy BE dai_links */
links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) *
ops->num_drv, GFP_KERNEL);
diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h
index 824d36fe59fd..97f4feb73368 100644
--- a/sound/soc/sof/ops.h
+++ b/sound/soc/sof/ops.h
@@ -381,6 +381,32 @@ snd_sof_pcm_platform_pointer(struct snd_sof_dev *sdev,
return 0;
}
+/* machine driver */
+static inline int
+snd_sof_machine_register(struct snd_sof_dev *sdev, void *pdata)
+{
+ if (sof_ops(sdev) && sof_ops(sdev)->machine_register)
+ return sof_ops(sdev)->machine_register(sdev, pdata);
+
+ return 0;
+}
+
+static inline void
+snd_sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata)
+{
+ if (sof_ops(sdev) && sof_ops(sdev)->machine_unregister)
+ sof_ops(sdev)->machine_unregister(sdev, pdata);
+}
+
+static inline int
+snd_sof_machine_check(struct snd_sof_dev *sdev)
+{
+ if (sof_ops(sdev) && sof_ops(sdev)->machine_check)
+ return sof_ops(sdev)->machine_check(sdev);
+
+ return 0;
+}
+
static inline const struct snd_sof_dsp_ops
*sof_get_ops(const struct sof_dev_desc *d,
const struct sof_ops_table mach_ops[], int asize)
diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c
index 2b876d497447..7fc5306b955d 100644
--- a/sound/soc/sof/pcm.c
+++ b/sound/soc/sof/pcm.c
@@ -34,7 +34,7 @@ static int create_page_table(struct snd_pcm_substream *substream,
if (!spcm)
return -EINVAL;
- return snd_sof_create_page_table(sdev, dmab,
+ return snd_sof_create_page_table(sdev->dev, dmab,
spcm->stream[stream].page_table.area, size);
}
@@ -691,6 +691,19 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
case SOF_DAI_INTEL_ALH:
/* do nothing for ALH dai_link */
break;
+ case SOF_DAI_IMX_ESAI:
+ channels->min = dai->dai_config->esai.tdm_slots;
+ channels->max = dai->dai_config->esai.tdm_slots;
+
+ break;
+ case SOF_DAI_IMX_SAI:
+ channels->min = dai->dai_config->sai.tdm_slots;
+ channels->max = dai->dai_config->sai.tdm_slots;
+
+ dev_dbg(sdev->dev,
+ "channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+ break;
default:
dev_err(sdev->dev, "error: invalid DAI type %d\n",
dai->dai_config->type);
@@ -724,14 +737,6 @@ static int sof_pcm_probe(struct snd_soc_component *component)
return ret;
}
- /*
- * Some platforms in SOF, ex: BYT, may not have their platform PM
- * callbacks set. Increment the usage count so as to
- * prevent the device from entering runtime suspend.
- */
- if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume)
- pm_runtime_get_noresume(sdev->dev);
-
return ret;
}
@@ -747,7 +752,11 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev)
struct snd_sof_pdata *plat_data = sdev->pdata;
const char *drv_name;
- drv_name = plat_data->machine->drv_name;
+
+ if (plat_data->machine)
+ drv_name = plat_data->machine->drv_name;
+ else
+ drv_name = "asoc-simple-card";
pd->name = "sof-audio-component";
pd->probe = sof_pcm_probe;
diff --git a/sound/soc/sof/sof-acpi-dev.c b/sound/soc/sof/sof-acpi-dev.c
index ea7b8b895412..9b77916d8aaf 100644
--- a/sound/soc/sof/sof-acpi-dev.c
+++ b/sound/soc/sof/sof-acpi-dev.c
@@ -39,6 +39,7 @@ static const struct sof_dev_desc sof_acpi_haswell_desc = {
.chip_info = &hsw_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-hsw.ri",
.nocodec_fw_filename = "sof-hsw.ri",
.nocodec_tplg_filename = "sof-hsw-nocodec.tplg",
.ops = &sof_hsw_ops,
@@ -56,6 +57,7 @@ static const struct sof_dev_desc sof_acpi_broadwell_desc = {
.chip_info = &bdw_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-bdw.ri",
.nocodec_fw_filename = "sof-bdw.ri",
.nocodec_tplg_filename = "sof-bdw-nocodec.tplg",
.ops = &sof_bdw_ops,
@@ -75,6 +77,7 @@ static const struct sof_dev_desc sof_acpi_baytrailcr_desc = {
.chip_info = &byt_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-byt.ri",
.nocodec_fw_filename = "sof-byt.ri",
.nocodec_tplg_filename = "sof-byt-nocodec.tplg",
.ops = &sof_byt_ops,
@@ -90,6 +93,7 @@ static const struct sof_dev_desc sof_acpi_baytrail_desc = {
.chip_info = &byt_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-byt.ri",
.nocodec_fw_filename = "sof-byt.ri",
.nocodec_tplg_filename = "sof-byt-nocodec.tplg",
.ops = &sof_byt_ops,
@@ -105,6 +109,7 @@ static const struct sof_dev_desc sof_acpi_cherrytrail_desc = {
.chip_info = &cht_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-cht.ri",
.nocodec_fw_filename = "sof-cht.ri",
.nocodec_tplg_filename = "sof-cht-nocodec.tplg",
.ops = &sof_cht_ops,
@@ -164,7 +169,11 @@ static int sof_acpi_probe(struct platform_device *pdev)
mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL);
if (!mach)
return -ENOMEM;
- ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops);
+
+ mach->drv_name = "sof-nocodec";
+ sof_pdata->fw_filename = desc->nocodec_fw_filename;
+ sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
+ ret = sof_nocodec_setup(dev, ops);
if (ret < 0)
return ret;
#else
diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c
index 28a9692974e5..ef5737f6918d 100644
--- a/sound/soc/sof/sof-of-dev.c
+++ b/sound/soc/sof/sof-of-dev.c
@@ -19,6 +19,7 @@ extern struct snd_sof_dsp_ops sof_imx8_ops;
static struct sof_dev_desc sof_of_imx8qxp_desc = {
.default_fw_path = "imx/sof",
.default_tplg_path = "imx/sof-tplg",
+ .default_fw_filename = "sof-imx8.ri",
.nocodec_fw_filename = "sof-imx8.ri",
.nocodec_tplg_filename = "sof-imx8-nocodec.tplg",
.ops = &sof_imx8_ops,
@@ -72,18 +73,17 @@ static int sof_of_probe(struct platform_device *pdev)
mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL);
if (!mach)
return -ENOMEM;
- ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops);
+
+ mach->drv_name = "sof-nocodec";
+ sof_pdata->fw_filename = desc->nocodec_fw_filename;
+ sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
+ ret = sof_nocodec_setup(dev, ops);
if (ret < 0)
return ret;
-#else
- /* TODO: implement case where we actually have a codec */
- return -ENODEV;
#endif
- if (mach)
- mach->mach_params.platform = dev_name(dev);
-
- sof_pdata->machine = mach;
+ /* TODO: replace machine with info from DT */
+ sof_pdata->machine = NULL;
sof_pdata->desc = desc;
sof_pdata->dev = &pdev->dev;
sof_pdata->platform = dev_name(dev);
@@ -92,6 +92,10 @@ static int sof_of_probe(struct platform_device *pdev)
sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path;
sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path;
+ sof_pdata->fw_filename = desc->default_fw_filename;
+ /* FIXME: Add proper value for tplg_filename */
+ sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
+
#if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)
/* set callback to enable runtime_pm */
sof_pdata->sof_probe_complete = sof_of_probe_complete;
diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c
index d66412a77873..a42f43b40042 100644
--- a/sound/soc/sof/sof-pci-dev.c
+++ b/sound/soc/sof/sof-pci-dev.c
@@ -40,6 +40,7 @@ static const struct sof_dev_desc bxt_desc = {
.chip_info = &apl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-apl.ri",
.nocodec_fw_filename = "sof-apl.ri",
.nocodec_tplg_filename = "sof-apl-nocodec.tplg",
.ops = &sof_apl_ops,
@@ -58,6 +59,7 @@ static const struct sof_dev_desc glk_desc = {
.chip_info = &apl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-glk.ri",
.nocodec_fw_filename = "sof-glk.ri",
.nocodec_tplg_filename = "sof-glk-nocodec.tplg",
.ops = &sof_apl_ops,
@@ -86,6 +88,7 @@ static const struct sof_dev_desc tng_desc = {
.chip_info = &tng_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-byt.ri",
.nocodec_fw_filename = "sof-byt.ri",
.nocodec_tplg_filename = "sof-byt.tplg",
.ops = &sof_tng_ops,
@@ -104,6 +107,7 @@ static const struct sof_dev_desc cnl_desc = {
.chip_info = &cnl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-cnl.ri",
.nocodec_fw_filename = "sof-cnl.ri",
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
@@ -113,7 +117,7 @@ static const struct sof_dev_desc cnl_desc = {
#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE)
static const struct sof_dev_desc cfl_desc = {
- .machines = snd_soc_acpi_intel_cnl_machines,
+ .machines = snd_soc_acpi_intel_cfl_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
@@ -122,7 +126,8 @@ static const struct sof_dev_desc cfl_desc = {
.chip_info = &cnl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-cnl.ri",
+ .default_fw_filename = "sof-cfl.ri",
+ .nocodec_fw_filename = "sof-cfl.ri",
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
.arch_ops = &sof_xtensa_arch_ops
@@ -133,7 +138,7 @@ static const struct sof_dev_desc cfl_desc = {
IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE_H)
static const struct sof_dev_desc cml_desc = {
- .machines = snd_soc_acpi_intel_cnl_machines,
+ .machines = snd_soc_acpi_intel_cml_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
@@ -142,7 +147,8 @@ static const struct sof_dev_desc cml_desc = {
.chip_info = &cnl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-cnl.ri",
+ .default_fw_filename = "sof-cml.ri",
+ .nocodec_fw_filename = "sof-cml.ri",
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
.arch_ops = &sof_xtensa_arch_ops
@@ -160,6 +166,7 @@ static const struct sof_dev_desc icl_desc = {
.chip_info = &icl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-icl.ri",
.nocodec_fw_filename = "sof-icl.ri",
.nocodec_tplg_filename = "sof-icl-nocodec.tplg",
.ops = &sof_cnl_ops,
@@ -214,6 +221,7 @@ static const struct sof_dev_desc tgl_desc = {
.chip_info = &tgl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-tgl.ri",
.nocodec_fw_filename = "sof-tgl.ri",
.nocodec_tplg_filename = "sof-tgl-nocodec.tplg",
.ops = &sof_cnl_ops,
@@ -232,6 +240,7 @@ static const struct sof_dev_desc ehl_desc = {
.chip_info = &ehl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-ehl.ri",
.nocodec_fw_filename = "sof-ehl.ri",
.nocodec_tplg_filename = "sof-ehl-nocodec.tplg",
.ops = &sof_cnl_ops,
@@ -306,7 +315,10 @@ static int sof_pci_probe(struct pci_dev *pci,
ret = -ENOMEM;
goto release_regions;
}
- ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops);
+ mach->drv_name = "sof-nocodec";
+ sof_pdata->fw_filename = desc->nocodec_fw_filename;
+ sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
+ ret = sof_nocodec_setup(dev, ops);
if (ret < 0)
goto release_regions;
diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h
index 730f3259dd02..8ee185bbf6f0 100644
--- a/sound/soc/sof/sof-priv.h
+++ b/sound/soc/sof/sof-priv.h
@@ -202,6 +202,13 @@ struct snd_sof_dsp_ops {
int (*get_window_offset)(struct snd_sof_dev *sdev,
u32 id);/* mandatory for common loader code */
+ /* machine driver ops */
+ int (*machine_register)(struct snd_sof_dev *sdev,
+ void *pdata); /* optional */
+ void (*machine_unregister)(struct snd_sof_dev *sdev,
+ void *pdata); /* optional */
+ int (*machine_check)(struct snd_sof_dev *sdev); /* optional */
+
/* DAI ops */
struct snd_soc_dai_driver *drv;
int num_drv;
@@ -458,10 +465,14 @@ int snd_sof_suspend(struct device *dev);
void snd_sof_new_platform_drv(struct snd_sof_dev *sdev);
-int snd_sof_create_page_table(struct snd_sof_dev *sdev,
+int snd_sof_create_page_table(struct device *dev,
struct snd_dma_buffer *dmab,
unsigned char *page_table, size_t size);
+int sof_machine_register(struct snd_sof_dev *sdev, void *pdata);
+void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata);
+int sof_machine_check(struct snd_sof_dev *sdev);
+
/*
* Firmware loading.
*/
@@ -542,8 +553,6 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_ipc *ipc,
* There is no snd_sof_free_topology since topology components will
* be freed by snd_soc_unregister_component,
*/
-int snd_sof_init_topology(struct snd_sof_dev *sdev,
- struct snd_soc_tplg_ops *ops);
int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file);
int snd_sof_complete_pipeline(struct snd_sof_dev *sdev,
struct snd_sof_widget *swidget);
diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c
index 4452594c2e17..d424413dc531 100644
--- a/sound/soc/sof/topology.c
+++ b/sound/soc/sof/topology.c
@@ -799,6 +799,21 @@ static const struct sof_topology_token dmic_tokens[] = {
};
+/* ESAI */
+static const struct sof_topology_token esai_tokens[] = {
+ {SOF_TKN_IMX_ESAI_MCLK_ID,
+ SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_esai_params, mclk_id), 0},
+};
+
+/* SAI */
+static const struct sof_topology_token sai_tokens[] = {
+ {SOF_TKN_IMX_SAI_MCLK_ID,
+ SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_sai_params, mclk_id), 0},
+};
+
+
/*
* DMIC PDM Tokens
* SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token
@@ -2526,8 +2541,66 @@ static int sof_link_sai_load(struct snd_soc_component *scomp, int index,
struct snd_soc_tplg_hw_config *hw_config,
struct sof_ipc_dai_config *config)
{
- /*TODO: Add implementation */
- return 0;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_soc_tplg_private *private = &cfg->priv;
+ struct sof_ipc_reply reply;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* handle master/slave and inverted clocks */
+ sof_dai_set_format(hw_config, config);
+
+ /* init IPC */
+ memset(&config->sai, 0, sizeof(struct sof_ipc_dai_sai_params));
+ config->hdr.size = size;
+
+ ret = sof_parse_tokens(scomp, &config->sai, sai_tokens,
+ ARRAY_SIZE(sai_tokens), private->array,
+ le32_to_cpu(private->size));
+ if (ret != 0) {
+ dev_err(sdev->dev, "error: parse sai tokens failed %d\n",
+ le32_to_cpu(private->size));
+ return ret;
+ }
+
+ config->sai.mclk_rate = le32_to_cpu(hw_config->mclk_rate);
+ config->sai.mclk_direction = hw_config->mclk_direction;
+
+ config->sai.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
+ config->sai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width);
+ config->sai.rx_slots = le32_to_cpu(hw_config->rx_slots);
+ config->sai.tx_slots = le32_to_cpu(hw_config->tx_slots);
+
+ dev_info(sdev->dev,
+ "tplg: config SAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n",
+ config->dai_index, config->format,
+ config->sai.mclk_rate, config->sai.tdm_slot_width,
+ config->sai.tdm_slots, config->sai.mclk_id);
+
+ if (config->sai.tdm_slots < 1 || config->sai.tdm_slots > 8) {
+ dev_err(sdev->dev, "error: invalid channel count for SAI%d\n",
+ config->dai_index);
+ return -EINVAL;
+ }
+
+ /* send message to DSP */
+ ret = sof_ipc_tx_message(sdev->ipc,
+ config->hdr.cmd, config, size, &reply,
+ sizeof(reply));
+
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: failed to set DAI config for SAI%d\n",
+ config->dai_index);
+ return ret;
+ }
+
+ /* set config for all DAI's with name matching the link name */
+ ret = sof_set_dai_config(sdev, size, link, config);
+ if (ret < 0)
+ dev_err(sdev->dev, "error: failed to save DAI config for SAI%d\n",
+ config->dai_index);
+
+ return ret;
}
static int sof_link_esai_load(struct snd_soc_component *scomp, int index,
@@ -2536,8 +2609,66 @@ static int sof_link_esai_load(struct snd_soc_component *scomp, int index,
struct snd_soc_tplg_hw_config *hw_config,
struct sof_ipc_dai_config *config)
{
- /*TODO: Add implementation */
- return 0;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_soc_tplg_private *private = &cfg->priv;
+ struct sof_ipc_reply reply;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* handle master/slave and inverted clocks */
+ sof_dai_set_format(hw_config, config);
+
+ /* init IPC */
+ memset(&config->esai, 0, sizeof(struct sof_ipc_dai_esai_params));
+ config->hdr.size = size;
+
+ ret = sof_parse_tokens(scomp, &config->esai, esai_tokens,
+ ARRAY_SIZE(esai_tokens), private->array,
+ le32_to_cpu(private->size));
+ if (ret != 0) {
+ dev_err(sdev->dev, "error: parse esai tokens failed %d\n",
+ le32_to_cpu(private->size));
+ return ret;
+ }
+
+ config->esai.mclk_rate = le32_to_cpu(hw_config->mclk_rate);
+ config->esai.bclk_rate = le32_to_cpu(hw_config->bclk_rate);
+ config->esai.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
+ config->esai.mclk_direction = hw_config->mclk_direction;
+ config->esai.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
+ config->esai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width);
+ config->esai.rx_slots = le32_to_cpu(hw_config->rx_slots);
+ config->esai.tx_slots = le32_to_cpu(hw_config->tx_slots);
+
+ dev_info(sdev->dev,
+ "tplg: config ESAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n",
+ config->dai_index, config->format,
+ config->esai.mclk_rate, config->esai.tdm_slot_width,
+ config->esai.tdm_slots, config->esai.mclk_id);
+
+ if (config->esai.tdm_slots < 1 || config->esai.tdm_slots > 8) {
+ dev_err(sdev->dev, "error: invalid channel count for ESAI%d\n",
+ config->dai_index);
+ return -EINVAL;
+ }
+
+ /* send message to DSP */
+ ret = sof_ipc_tx_message(sdev->ipc,
+ config->hdr.cmd, config, size, &reply,
+ sizeof(reply));
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: failed to set DAI config for ESAI%d\n",
+ config->dai_index);
+ return ret;
+ }
+
+ /* set config for all DAI's with name matching the link name */
+ ret = sof_set_dai_config(sdev, size, link, config);
+ if (ret < 0)
+ dev_err(sdev->dev, "error: failed to save DAI config for ESAI%d\n",
+ config->dai_index);
+
+ return ret;
}
static int sof_link_dmic_load(struct snd_soc_component *scomp, int index,
@@ -2968,7 +3099,9 @@ found:
case SOF_DAI_INTEL_SSP:
case SOF_DAI_INTEL_DMIC:
case SOF_DAI_INTEL_ALH:
- /* no resource needs to be released for SSP, DMIC and ALH */
+ case SOF_DAI_IMX_SAI:
+ case SOF_DAI_IMX_ESAI:
+ /* no resource needs to be released for all cases above */
break;
case SOF_DAI_INTEL_HDA:
ret = sof_link_hda_unload(sdev, link);
@@ -3299,15 +3432,6 @@ static struct snd_soc_tplg_ops sof_tplg_ops = {
.bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops),
};
-int snd_sof_init_topology(struct snd_sof_dev *sdev,
- struct snd_soc_tplg_ops *ops)
-{
- /* TODO: support linked list of topologies */
- sdev->tplg_ops = ops;
- return 0;
-}
-EXPORT_SYMBOL(snd_sof_init_topology);
-
int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file)
{
const struct firmware *fw;
diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c
index 4c3cff031fd6..17453d2da45d 100644
--- a/sound/soc/sof/trace.c
+++ b/sound/soc/sof/trace.c
@@ -244,8 +244,8 @@ int snd_sof_init_trace(struct snd_sof_dev *sdev)
}
/* create compressed page table for audio firmware */
- ret = snd_sof_create_page_table(sdev, &sdev->dmatb, sdev->dmatp.area,
- sdev->dmatb.bytes);
+ ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb,
+ sdev->dmatp.area, sdev->dmatb.bytes);
if (ret < 0)
goto table_err;
diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/utils.c
index 2ac4c3da0320..9831eb57df6c 100644
--- a/sound/soc/sof/utils.c
+++ b/sound/soc/sof/utils.c
@@ -10,6 +10,7 @@
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/platform_device.h>
+#include <asm/unaligned.h>
#include <sound/soc.h>
#include <sound/sof.h>
#include "sof-priv.h"
@@ -110,3 +111,62 @@ void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest,
memcpy_fromio(dest, src, size);
}
EXPORT_SYMBOL(sof_block_read);
+
+/*
+ * Generic buffer page table creation.
+ * Take the each physical page address and drop the least significant unused
+ * bits from each (based on PAGE_SIZE). Then pack valid page address bits
+ * into compressed page table.
+ */
+
+int snd_sof_create_page_table(struct device *dev,
+ struct snd_dma_buffer *dmab,
+ unsigned char *page_table, size_t size)
+{
+ int i, pages;
+
+ pages = snd_sgbuf_aligned_pages(size);
+
+ dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n",
+ dmab->area, size, pages);
+
+ for (i = 0; i < pages; i++) {
+ /*
+ * The number of valid address bits for each page is 20.
+ * idx determines the byte position within page_table
+ * where the current page's address is stored
+ * in the compressed page_table.
+ * This can be calculated by multiplying the page number by 2.5.
+ */
+ u32 idx = (5 * i) >> 1;
+ u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT;
+ u8 *pg_table;
+
+ dev_vdbg(dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn);
+
+ pg_table = (u8 *)(page_table + idx);
+
+ /*
+ * pagetable compression:
+ * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5
+ * ___________pfn 0__________ __________pfn 1___________ _pfn 2...
+ * .... .... .... .... .... .... .... .... .... .... ....
+ * It is created by:
+ * 1. set current location to 0, PFN index i to 0
+ * 2. put pfn[i] at current location in Little Endian byte order
+ * 3. calculate an intermediate value as
+ * x = (pfn[i+1] << 4) | (pfn[i] & 0xf)
+ * 4. put x at offset (current location + 2) in LE byte order
+ * 5. increment current location by 5 bytes, increment i by 2
+ * 6. continue to (2)
+ */
+ if (i & 1)
+ put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4,
+ pg_table);
+ else
+ put_unaligned_le32(pfn, pg_table);
+ }
+
+ return pages;
+}
+EXPORT_SYMBOL(snd_sof_create_page_table);
diff --git a/sound/soc/sprd/sprd-mcdt.c b/sound/soc/sprd/sprd-mcdt.c
index f439e5503a3c..7448015a4935 100644
--- a/sound/soc/sprd/sprd-mcdt.c
+++ b/sound/soc/sprd/sprd-mcdt.c
@@ -959,8 +959,10 @@ static int sprd_mcdt_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, mcdt);
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(&pdev->dev, "Failed to get MCDT interrupt\n");
return irq;
+ }
ret = devm_request_irq(&pdev->dev, irq, sprd_mcdt_irq_handler,
0, "sprd-mcdt", mcdt);
diff --git a/sound/soc/sti/sti_uniperif.c b/sound/soc/sti/sti_uniperif.c
index ee4a0151e63e..645bcbe91601 100644
--- a/sound/soc/sti/sti_uniperif.c
+++ b/sound/soc/sti/sti_uniperif.c
@@ -426,8 +426,10 @@ static int sti_uniperiph_cpu_dai_of(struct device_node *node,
UNIPERIF_FIFO_DATA_OFFSET(uni);
uni->irq = platform_get_irq(priv->pdev, 0);
- if (uni->irq < 0)
+ if (uni->irq < 0) {
+ dev_err(dev, "Failed to get IRQ resource\n");
return -ENXIO;
+ }
uni->type = dev_data->type;
diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c
index 3e7226a53e53..ba6452dab69b 100644
--- a/sound/soc/stm/stm32_i2s.c
+++ b/sound/soc/stm/stm32_i2s.c
@@ -855,8 +855,11 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev,
/* Get irqs */
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
+ if (irq < 0) {
+ if (irq != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
+ }
ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, IRQF_ONESHOT,
dev_name(&pdev->dev), i2s);
diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c
index ef4273361d0d..1ac5103cea78 100644
--- a/sound/soc/stm/stm32_sai.c
+++ b/sound/soc/stm/stm32_sai.c
@@ -193,8 +193,10 @@ static int stm32_sai_probe(struct platform_device *pdev)
/* init irqs */
sai->irq = platform_get_irq(pdev, 0);
- if (sai->irq < 0)
+ if (sai->irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return sai->irq;
+ }
/* reset */
rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c
index cd4b235fce57..ee71b898897b 100644
--- a/sound/soc/stm/stm32_spdifrx.c
+++ b/sound/soc/stm/stm32_spdifrx.c
@@ -909,8 +909,10 @@ static int stm32_spdifrx_parse_of(struct platform_device *pdev,
}
spdifrx->irq = platform_get_irq(pdev, 0);
- if (spdifrx->irq < 0)
+ if (spdifrx->irq < 0) {
+ dev_err(&pdev->dev, "No irq for node %s\n", pdev->name);
return spdifrx->irq;
+ }
return 0;
}
diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c
index d0a8d5810c0a..de448c8d060b 100644
--- a/sound/soc/sunxi/sun4i-i2s.c
+++ b/sound/soc/sunxi/sun4i-i2s.c
@@ -1198,8 +1198,10 @@ static int sun4i_i2s_probe(struct platform_device *pdev)
return PTR_ERR(regs);
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(&pdev->dev, "Can't retrieve our interrupt\n");
return irq;
+ }
i2s->variant = of_device_get_match_data(&pdev->dev);
if (!i2s->variant) {
diff --git a/sound/soc/uniphier/aio-dma.c b/sound/soc/uniphier/aio-dma.c
index e8446cc4e8f8..862346d66774 100644
--- a/sound/soc/uniphier/aio-dma.c
+++ b/sound/soc/uniphier/aio-dma.c
@@ -289,8 +289,10 @@ int uniphier_aiodma_soc_register_platform(struct platform_device *pdev)
return PTR_ERR(chip->regmap);
irq = platform_get_irq(pdev, 0);
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(dev, "Could not get irq.\n");
return irq;
+ }
ret = devm_request_irq(dev, irq, aiodma_irq,
IRQF_SHARED, dev_name(dev), pdev);
diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c
index 48970efe7838..dc8721f4f56b 100644
--- a/sound/soc/xilinx/xlnx_formatter_pcm.c
+++ b/sound/soc/xilinx/xlnx_formatter_pcm.c
@@ -613,6 +613,7 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev)
aud_drv_data->mm2s_irq = platform_get_irq_byname(pdev,
"irq_mm2s");
if (aud_drv_data->mm2s_irq < 0) {
+ dev_err(dev, "xlnx audio mm2s irq resource failed\n");
ret = aud_drv_data->mm2s_irq;
goto clk_err;
}
@@ -639,6 +640,7 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev)
aud_drv_data->s2mm_irq = platform_get_irq_byname(pdev,
"irq_s2mm");
if (aud_drv_data->s2mm_irq < 0) {
+ dev_err(dev, "xlnx audio s2mm irq resource failed\n");
ret = aud_drv_data->s2mm_irq;
goto clk_err;
}
diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c
index efd374f114a0..9da395d14a8d 100644
--- a/sound/soc/xtensa/xtfpga-i2s.c
+++ b/sound/soc/xtensa/xtfpga-i2s.c
@@ -570,6 +570,7 @@ static int xtfpga_i2s_probe(struct platform_device *pdev)
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
+ dev_err(&pdev->dev, "No IRQ resource\n");
err = irq;
goto err;
}