summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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-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--include/linux/busfreq-imx.h77
-rw-r--r--sound/soc/fsl/Kconfig151
-rw-r--r--sound/soc/fsl/Makefile28
-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-micfil.c184
-rw-r--r--sound/soc/fsl/imx-mqs.c291
-rw-r--r--sound/soc/fsl/imx-pdm.c207
-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
24 files changed, 5519 insertions, 4 deletions
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-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/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/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 65a76930f86f..f6302a139d3b 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -108,7 +108,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.
@@ -259,6 +259,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)
@@ -282,6 +411,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
@@ -312,7 +449,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
@@ -324,6 +461,16 @@ 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.
+
endif # SND_IMX_SOC
endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index 4f09adbebdde..5914033ef834 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -13,7 +13,6 @@ obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
# Freescale SSI/DMA/SAI/SPDIF Support
snd-soc-fsl-audmix-objs := fsl_audmix.o
-snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o
snd-soc-fsl-sai-objs := fsl_sai.o
snd-soc-fsl-ssi-y := fsl_ssi.o
@@ -26,7 +25,7 @@ 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
-obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
+snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o
obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o
obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o
@@ -36,6 +35,7 @@ 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_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
@@ -61,17 +61,41 @@ 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
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o
+obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o
obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
+obj-${CONFIG_SND_SOC_IMX_WM8958} += snd-soc-imx-wm8958.o
+obj-$(CONFIG_SND_SOC_IMX_WM8960) += snd-soc-imx-wm8960.o
+obj-$(CONFIG_SND_SOC_IMX_WM8524) += snd-soc-imx-wm8524.o
+obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o
+obj-$(CONFIG_SND_SOC_IMX_SII902X) += snd-soc-imx-sii902x.o
obj-$(CONFIG_SND_SOC_IMX_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
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-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-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-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");