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-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-cs42888.txt25
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-mqs.txt18
-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/uapi/linux/mxc_asrc.h165
-rw-r--r--include/uapi/linux/mxc_dsp.h152
-rw-r--r--sound/soc/codecs/Kconfig3
-rw-r--r--sound/soc/codecs/Makefile2
-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/si476x.c19
-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/Kconfig191
-rw-r--r--sound/soc/fsl/Makefile38
-rw-r--r--sound/soc/fsl/fsl_asrc.c413
-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_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.c215
-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-cs42888.c502
-rw-r--r--sound/soc/fsl/imx-dsp.c227
-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.h14
-rw-r--r--sound/soc/fsl/imx-pdm.c207
-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-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/soc-pcm.c4
76 files changed, 18758 insertions, 336 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-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-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-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/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/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 229cc89f8c5a..5fda1c26bf1d 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -696,6 +696,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'
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index c498373dcc5f..8f71ebb862e2 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
@@ -370,6 +371,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
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/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/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 69401a3c7076..bf044d45fef3 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -74,6 +74,25 @@ 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.
+ This option is only useful for out-of-tree drivers since
+ in-tree drivers select it automatically.
+
config SND_SOC_FSL_UTILS
tristate
@@ -101,7 +120,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.
@@ -257,6 +276,135 @@ 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_ES8328
tristate "SoC Audio support for i.MX boards with the ES8328 codec"
depends on OF && (I2C || SPI)
@@ -280,6 +428,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
@@ -310,7 +466,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
@@ -322,6 +478,37 @@ 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
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index 6e9d1eff9341..c989f8b74d5d 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -13,8 +13,10 @@ 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
@@ -23,11 +25,14 @@ snd-soc-fsl-esai-objs := fsl_esai.o
snd-soc-fsl-micfil-objs := fsl_micfil.o
snd-soc-fsl-utils-objs := fsl_utils.o
snd-soc-fsl-dma-objs := fsl_dma.o
+snd-soc-fsl-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
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
@@ -36,6 +41,8 @@ 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
# MPC5200 Platform Support
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
@@ -53,6 +60,7 @@ 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
@@ -62,10 +70,24 @@ 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
@@ -74,10 +96,24 @@ 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_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
diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c
index cfa40ef6b1ca..34f4cda6fd0f 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_S20_3LE)
+ SNDRV_PCM_FMTBIT_S24_3LE)
+#define FSL_ASRC_FORMATS_TX (SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ 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)
@@ -920,14 +1104,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 +1183,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 +1192,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 +1206,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 +1227,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 +1274,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 +1294,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 +1324,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_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..8ade4c4d0918 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);
@@ -965,7 +1051,29 @@ static int fsl_esai_probe(struct platform_device *pdev)
if (irq < 0)
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 +1083,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 +1098,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 +1121,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 +1142,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 +1155,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/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-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-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.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-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-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/soc-pcm.c b/sound/soc/soc-pcm.c
index b600d3eaaf5c..0eed248a1c4b 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;
}