diff options
author | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:00:38 +0800 |
---|---|---|
committer | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:00:38 +0800 |
commit | 2e5482f337075fa86e34c736ddf38b8327047582 (patch) | |
tree | af59479d37f6baf544bfb5252c3dddec84f08271 | |
parent | bbeadc6b01df81d4e292681c042fc2b99604714d (diff) | |
parent | 005e9f296325dad20a2fc34152e7a4aae047c342 (diff) |
Merge remote-tracking branch 'origin/audio/card' into audio/next
* origin/audio/card: (127 commits)
ASoC: imx-pdm: Fix compile issue
ASoC: imx-wm8524: remove unused audio route
ASoC: imx-ak5558: remove unused audio route
ASoC: imx-wm8962: change cpu-dai to audio-cpu
ASoC: imx-sii902x: Fix compile error
...
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"); |