diff options
39 files changed, 3242 insertions, 493 deletions
diff --git a/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.yaml b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.yaml index d61bc011e820..6f2398cdc82d 100644 --- a/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/st,stm32-dfsdm-adc.yaml @@ -199,8 +199,6 @@ patternProperties: description: From common IIO binding. Used to pipe external sigma delta modulator or internal ADC output to DFSDM channel. - This is not required for "st,stm32-dfsdm-pdm" compatibility as - PDM microphone is binded in Audio DT node. required: - io-channels @@ -235,6 +233,10 @@ patternProperties: description: child node properties: + compatible: + enum: + - st,stm32h7-dfsdm-dai + "#sound-dai-cells": const: 0 @@ -244,6 +246,7 @@ patternProperties: modulator or internal ADC output to DFSDM channel. required: + - compatible - "#sound-dai-cells" - io-channels diff --git a/Documentation/devicetree/bindings/sound/fsl,spdif.yaml b/Documentation/devicetree/bindings/sound/fsl,spdif.yaml index 2ac671f5cb9b..50449b6d1048 100644 --- a/Documentation/devicetree/bindings/sound/fsl,spdif.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,spdif.yaml @@ -20,6 +20,7 @@ properties: - fsl,imx35-spdif - fsl,vf610-spdif - fsl,imx6sx-spdif + - fsl,imx8qm-spdif reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/sound/fsl,xcvr.yaml b/Documentation/devicetree/bindings/sound/fsl,xcvr.yaml new file mode 100644 index 000000000000..223b8ea693dc --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,xcvr.yaml @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/fsl,xcvr.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP Audio Transceiver (XCVR) Controller + +maintainers: + - Viorel Suman <viorel.suman@nxp.com> + +description: | + NXP XCVR (Audio Transceiver) is a on-chip functional module + that allows CPU to receive and transmit digital audio via + HDMI2.1 eARC, HDMI1.4 ARC and SPDIF. + +properties: + $nodename: + pattern: "^xcvr@.*" + + compatible: + enum: + - fsl,imx8mp-xcvr + + reg: + items: + - description: 20K RAM for code and data + - description: registers space + - description: RX FIFO address + - description: TX FIFO address + + reg-names: + items: + - const: ram + - const: regs + - const: rxfifo + - const: txfifo + + interrupts: + maxItems: 1 + + clocks: + items: + - description: Peripheral clock + - description: PHY clock + - description: SPBA clock + - description: PLL clock + + clock-names: + items: + - const: ipg + - const: phy + - const: spba + - const: pll_ipg + + dmas: + items: + - description: DMA controller phandle and request line for RX + - description: DMA controller phandle and request line for TX + + dma-names: + items: + - const: rx + - const: tx + + resets: + maxItems: 1 + +required: + - compatible + - reg + - reg-names + - interrupts + - clocks + - clock-names + - dmas + - dma-names + - resets + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/clock/imx8mp-clock.h> + #include <dt-bindings/reset/imx8mp-reset.h> + + xcvr: xcvr@30cc0000 { + compatible = "fsl,imx8mp-xcvr"; + reg = <0x30cc0000 0x800>, + <0x30cc0800 0x400>, + <0x30cc0c00 0x080>, + <0x30cc0e00 0x080>; + reg-names = "ram", "regs", "rxfifo", "txfifo"; + interrupts = <0x0 128 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&audiomix_clk IMX8MP_CLK_AUDIOMIX_EARC_IPG>, + <&audiomix_clk IMX8MP_CLK_AUDIOMIX_EARC_PHY>, + <&audiomix_clk IMX8MP_CLK_AUDIOMIX_SPBA2_ROOT>, + <&audiomix_clk IMX8MP_CLK_AUDIOMIX_AUDPLL_ROOT>; + clock-names = "ipg", "phy", "spba", "pll_ipg"; + dmas = <&sdma2 30 2 0>, <&sdma2 31 2 0>; + dma-names = "rx", "tx"; + resets = <&audiomix_reset 0>; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml new file mode 100644 index 000000000000..b8f97fe6e92c --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -0,0 +1,161 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,sm8250.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies Inc. SM8250 ASoC sound card driver + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: + This bindings describes SC8250 SoC based sound cards + which uses LPASS internal codec for audio. + +properties: + compatible: + oneOf: + - const: qcom,qrb5165-rb5 + - items: + - const: qcom,sm8250 + - const: qcom,qrb5165-rb5 + + audio-routing: + $ref: /schemas/types.yaml#/definitions/non-unique-string-array + description: + 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, + MicBias of codec and the jacks on the board. + + model: + $ref: /schemas/types.yaml#/definitions/string + description: User visible long sound card name + +patternProperties: + ".*-dai-link$": + description: + Each subnode represents a dai link. Subnodes of each dai links would be + cpu/codec dais. + + type: object + + properties: + link-name: + description: Indicates dai-link name and PCM stream name. + $ref: /schemas/types.yaml#/definitions/string + maxItems: 1 + + cpu: + description: Holds subnode which indicates cpu dai. + type: object + properties: + sound-dai: true + + platform: + description: Holds subnode which indicates platform dai. + type: object + properties: + sound-dai: true + + codec: + description: Holds subnode which indicates codec dai. + type: object + properties: + sound-dai: true + + required: + - link-name + - cpu + + additionalProperties: false + +required: + - compatible + - model + +additionalProperties: false + +examples: + + - | + #include <dt-bindings/sound/qcom,q6afe.h> + #include <dt-bindings/sound/qcom,q6asm.h> + sound { + compatible = "qcom,qrb5165-rb5"; + model = "Qualcomm-qrb5165-RB5-WSA8815-Speakers-DMIC0"; + audio-routing = "SpkrLeft IN", "WSA_SPK1 OUT", + "SpkrRight IN", "WSA_SPK2 OUT", + "VA DMIC0", "vdd-micb", + "VA DMIC1", "vdd-micb", + "MM_DL1", "MultiMedia1 Playback", + "MM_DL2", "MultiMedia2 Playback", + "MultiMedia3 Capture", "MM_UL3"; + + mm1-dai-link { + link-name = "MultiMedia0"; + cpu { + sound-dai = <&q6asmdai MSM_FRONTEND_DAI_MULTIMEDIA1>; + }; + }; + + mm2-dai-link { + link-name = "MultiMedia2"; + cpu { + sound-dai = <&q6asmdai MSM_FRONTEND_DAI_MULTIMEDIA2>; + }; + }; + + mm3-dai-link { + link-name = "MultiMedia3"; + cpu { + sound-dai = <&q6asmdai MSM_FRONTEND_DAI_MULTIMEDIA3>; + }; + }; + + hdmi-dai-link { + link-name = "HDMI Playback"; + cpu { + sound-dai = <&q6afedai TERTIARY_MI2S_RX>; + }; + + platform { + sound-dai = <&q6routing>; + }; + + codec { + sound-dai = <<9611_codec 0>; + }; + }; + + wsa-dai-link { + link-name = "WSA Playback"; + cpu { + sound-dai = <&q6afedai WSA_CODEC_DMA_RX_0>; + }; + + platform { + sound-dai = <&q6routing>; + }; + + codec { + sound-dai = <&left_spkr>, <&right_spkr>, <&swr0 0>, <&wsamacro>; + }; + }; + + va-dai-link { + link-name = "VA Capture"; + cpu { + sound-dai = <&q6afedai VA_CODEC_DMA_TX_0>; + }; + + platform { + sound-dai = <&q6routing>; + }; + + codec { + sound-dai = <&vamacro 0>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt b/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt deleted file mode 100644 index 864f5b00b031..000000000000 --- a/Documentation/devicetree/bindings/sound/st,stm32-adfsdm.txt +++ /dev/null @@ -1,63 +0,0 @@ -STMicroelectronics Audio Digital Filter Sigma Delta modulators(DFSDM) - -The DFSDM allows PDM microphones capture through SPI interface. The Audio -interface is seems as a sub block of the DFSDM device. -For details on DFSDM bindings refer to ../iio/adc/st,stm32-dfsdm-adc.txt - -Required properties: - - compatible: "st,stm32h7-dfsdm-dai". - - - #sound-dai-cells : Must be equal to 0 - - - io-channels : phandle to iio dfsdm instance node. - -Example of a sound card using audio DFSDM node. - - sound_card { - compatible = "audio-graph-card"; - - dais = <&cpu_port>; - }; - - dfsdm: dfsdm@40017000 { - compatible = "st,stm32h7-dfsdm"; - reg = <0x40017000 0x400>; - clocks = <&rcc DFSDM1_CK>; - clock-names = "dfsdm"; - #interrupt-cells = <1>; - #address-cells = <1>; - #size-cells = <0>; - - dfsdm_adc0: filter@0 { - compatible = "st,stm32-dfsdm-dmic"; - reg = <0>; - interrupts = <110>; - dmas = <&dmamux1 101 0x400 0x00>; - dma-names = "rx"; - st,adc-channels = <1>; - st,adc-channel-names = "dmic0"; - st,adc-channel-types = "SPI_R"; - st,adc-channel-clk-src = "CLKOUT"; - st,filter-order = <5>; - - dfsdm_dai0: dfsdm-dai { - compatible = "st,stm32h7-dfsdm-dai"; - #sound-dai-cells = <0>; - io-channels = <&dfsdm_adc0 0>; - cpu_port: port { - dfsdm_endpoint: endpoint { - remote-endpoint = <&dmic0_endpoint>; - }; - }; - }; - }; - - dmic0: dmic@0 { - compatible = "dmic-codec"; - #sound-dai-cells = <0>; - port { - dmic0_endpoint: endpoint { - remote-endpoint = <&dfsdm_endpoint>; - }; - }; - }; diff --git a/Documentation/devicetree/bindings/sound/st,stm32-sai.txt b/Documentation/devicetree/bindings/sound/st,stm32-sai.txt deleted file mode 100644 index c42b91e525fa..000000000000 --- a/Documentation/devicetree/bindings/sound/st,stm32-sai.txt +++ /dev/null @@ -1,107 +0,0 @@ -STMicroelectronics STM32 Serial Audio Interface (SAI). - -The SAI interface (Serial Audio Interface) offers a wide set of audio protocols -as I2S standards, LSB or MSB-justified, PCM/DSP, TDM, and AC'97. -The SAI contains two independent audio sub-blocks. Each sub-block has -its own clock generator and I/O lines controller. - -Required properties: - - compatible: Should be "st,stm32f4-sai" or "st,stm32h7-sai" - - reg: Base address and size of SAI common register set. - - clocks: Must contain phandle and clock specifier pairs for each entry - in clock-names. - - clock-names: Must contain "pclk" "x8k" and "x11k" - "pclk": Clock which feeds the peripheral bus interface. - Mandatory for "st,stm32h7-sai" compatible. - Not used for "st,stm32f4-sai" compatible. - "x8k": SAI parent clock for sampling rates multiple of 8kHz. - "x11k": SAI parent clock for sampling rates multiple of 11.025kHz. - - interrupts: cpu DAI interrupt line shared by SAI sub-blocks - -Optional properties: - - resets: Reference to a reset controller asserting the SAI - -SAI subnodes: -Two subnodes corresponding to SAI sub-block instances A et B can be defined. -Subnode can be omitted for unsused sub-block. - -SAI subnodes required properties: - - compatible: Should be "st,stm32-sai-sub-a" or "st,stm32-sai-sub-b" - for SAI sub-block A or B respectively. - - reg: Base address and size of SAI sub-block register set. - - clocks: Must contain one phandle and clock specifier pair - for sai_ck which feeds the internal clock generator. - If the SAI shares a master clock, with another SAI set as MCLK - clock provider, SAI provider phandle must be specified here. - - clock-names: Must contain "sai_ck". - Must also contain "MCLK", if SAI shares a master clock, - with a SAI set as MCLK clock provider. - - dmas: see Documentation/devicetree/bindings/dma/st,stm32-dma.yaml - - dma-names: identifier string for each DMA request line - "tx": if sai sub-block is configured as playback DAI - "rx": if sai sub-block is configured as capture DAI - - pinctrl-names: should contain only value "default" - - pinctrl-0: see Documentation/devicetree/bindings/pinctrl/st,stm32-pinctrl.yaml - -SAI subnodes Optional properties: - - st,sync: specify synchronization mode. - By default SAI sub-block is in asynchronous mode. - This property sets SAI sub-block as slave of another SAI sub-block. - Must contain the phandle and index of the sai sub-block providing - the synchronization. - - st,iec60958: support S/PDIF IEC6958 protocol for playback - IEC60958 protocol is not available for capture. - By default, custom protocol is assumed, meaning that protocol is - configured according to protocol defined in related DAI link node, - such as i2s, left justified, right justified, dsp and pdm protocols. - Note: ac97 protocol is not supported by SAI driver - - #clock-cells: should be 0. This property must be present if the SAI device - is a master clock provider, according to clocks bindings, described in - Documentation/devicetree/bindings/clock/clock-bindings.txt. - -The device node should contain one 'port' child node with one child 'endpoint' -node, according to the bindings defined in Documentation/devicetree/bindings/ -graph.txt. - -Example: -sound_card { - compatible = "audio-graph-card"; - dais = <&sai1b_port>; -}; - -sai1: sai1@40015800 { - compatible = "st,stm32h7-sai"; - #address-cells = <1>; - #size-cells = <1>; - ranges = <0 0x40015800 0x400>; - reg = <0x40015800 0x4>; - clocks = <&rcc SAI1_CK>, <&rcc PLL1_Q>, <&rcc PLL2_P>; - clock-names = "pclk", "x8k", "x11k"; - interrupts = <87>; - - sai1a: audio-controller@40015804 { - compatible = "st,stm32-sai-sub-a"; - reg = <0x4 0x1C>; - clocks = <&rcc SAI1_CK>; - clock-names = "sai_ck"; - dmas = <&dmamux1 1 87 0x400 0x0>; - dma-names = "tx"; - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_sai1a>; - - sai1b_port: port { - cpu_endpoint: endpoint { - remote-endpoint = <&codec_endpoint>; - format = "i2s"; - }; - }; - }; -}; - -audio-codec { - codec_port: port { - codec_endpoint: endpoint { - remote-endpoint = <&cpu_endpoint>; - }; - }; -}; diff --git a/Documentation/devicetree/bindings/sound/st,stm32-sai.yaml b/Documentation/devicetree/bindings/sound/st,stm32-sai.yaml new file mode 100644 index 000000000000..6ad48c7681c1 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/st,stm32-sai.yaml @@ -0,0 +1,200 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/st,stm32-sai.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: STMicroelectronics STM32 Serial Audio Interface (SAI) + +maintainers: + - Olivier Moysan <olivier.moysan@st.com> + +description: + The SAI interface (Serial Audio Interface) offers a wide set of audio + protocols as I2S standards, LSB or MSB-justified, PCM/DSP, TDM, and AC'97. + The SAI contains two independent audio sub-blocks. Each sub-block has + its own clock generator and I/O lines controller. + +properties: + compatible: + enum: + - st,stm32f4-sai + - st,stm32h7-sai + + reg: + items: + - description: Base address and size of SAI common register set. + - description: Base address and size of SAI identification register set. + minItems: 1 + maxItems: 2 + + ranges: + maxItems: 1 + + interrupts: + maxItems: 1 + + resets: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + + clocks: + maxItems: 3 + + clock-names: + maxItems: 3 + +required: + - compatible + - reg + - ranges + - "#address-cells" + - "#size-cells" + - clocks + - clock-names + +patternProperties: + "^audio-controller@[0-9a-f]+$": + type: object + description: + Two subnodes corresponding to SAI sub-block instances A et B + can be defined. Subnode can be omitted for unsused sub-block. + + properties: + compatible: + description: Compatible for SAI sub-block A or B. + pattern: "st,stm32-sai-sub-[ab]" + + "#sound-dai-cells": + const: 0 + + reg: + maxItems: 1 + + clocks: + items: + - description: sai_ck clock feeding the internal clock generator. + - description: MCLK clock from a SAI set as master clock provider. + minItems: 1 + maxItems: 2 + + clock-names: + items: + - const: sai_ck + - const: MCLK + minItems: 1 + maxItems: 2 + + dmas: + maxItems: 1 + + dma-names: + description: | + rx: SAI sub-block is configured as a capture DAI. + tx: SAI sub-block is configured as a playback DAI. + enum: [ rx, tx ] + + st,sync: + description: + Configure the SAI sub-block as slave of another SAI sub-block. + By default SAI sub-block is in asynchronous mode. + Must contain the phandle and index of the SAI sub-block providing + the synchronization. + allOf: + - $ref: /schemas/types.yaml#definitions/phandle-array + - maxItems: 1 + + st,iec60958: + description: + If set, support S/PDIF IEC6958 protocol for playback. + IEC60958 protocol is not available for capture. + By default, custom protocol is assumed, meaning that protocol is + configured according to protocol defined in related DAI link node, + such as i2s, left justified, right justified, dsp and pdm protocols. + allOf: + - $ref: /schemas/types.yaml#definitions/flag + + "#clock-cells": + description: Configure the SAI device as master clock provider. + const: 0 + + required: + - compatible + - "#sound-dai-cells" + - reg + - clocks + - clock-names + - dmas + - dma-names + +allOf: + - if: + properties: + compatible: + contains: + const: st,stm32f4-sai + + - then: + properties: + clocks: + items: + - description: x8k, SAI parent clock for sampling rates multiple of 8kHz. + - description: x11k, SAI parent clock for sampling rates multiple of 11.025kHz. + + clock-names: + items: + - const: x8k + - const: x11k + + - else: + properties: + clocks: + items: + - description: pclk feeds the peripheral bus interface. + - description: x8k, SAI parent clock for sampling rates multiple of 8kHz. + - description: x11k, SAI parent clock for sampling rates multiple of 11.025kHz. + + clock-names: + items: + - const: pclk + - const: x8k + - const: x11k + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/clock/stm32mp1-clks.h> + #include <dt-bindings/reset/stm32mp1-resets.h> + sai2: sai@4400b000 { + compatible = "st,stm32h7-sai"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x4400b000 0x400>; + reg = <0x4400b000 0x4>, <0x4400b3f0 0x10>; + clocks = <&rcc SAI2>, <&rcc PLL3_Q>, <&rcc PLL3_R>; + clock-names = "pclk", "x8k", "x11k"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&sai2a_pins_a>, <&sai2b_pins_b>; + pinctrl-1 = <&sai2a_sleep_pins_a>, <&sai2b_sleep_pins_b>; + status = "okay"; + + sai2a: audio-controller@4400b004 { + #sound-dai-cells = <0>; + compatible = "st,stm32-sai-sub-a"; + reg = <0x4 0x1c>; + dmas = <&dmamux1 89 0x400 0x01>; + dma-names = "tx"; + clocks = <&rcc SAI2_K>; + clock-names = "sai_ck"; + status = "okay"; + }; + }; + +... diff --git a/include/dt-bindings/sound/adi,adau1977.h b/include/dt-bindings/sound/adi,adau1977.h new file mode 100644 index 000000000000..8eebec6570f2 --- /dev/null +++ b/include/dt-bindings/sound/adi,adau1977.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __DT_BINDINGS_ADI_ADAU1977_H__ +#define __DT_BINDINGS_ADI_ADAU1977_H__ + +#define ADAU1977_MICBIAS_5V0 0x0 +#define ADAU1977_MICBIAS_5V5 0x1 +#define ADAU1977_MICBIAS_6V0 0x2 +#define ADAU1977_MICBIAS_6V5 0x3 +#define ADAU1977_MICBIAS_7V0 0x4 +#define ADAU1977_MICBIAS_7V5 0x5 +#define ADAU1977_MICBIAS_8V0 0x6 +#define ADAU1977_MICBIAS_8V5 0x7 +#define ADAU1977_MICBIAS_9V0 0x8 + +#endif /* __DT_BINDINGS_ADI_ADAU1977_H__ */ diff --git a/include/linux/platform_data/adau1977.h b/include/linux/platform_data/adau1977.h deleted file mode 100644 index 86667235077a..000000000000 --- a/include/linux/platform_data/adau1977.h +++ /dev/null @@ -1,44 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * ADAU1977/ADAU1978/ADAU1979 driver - * - * Copyright 2014 Analog Devices Inc. - * Author: Lars-Peter Clausen <lars@metafoo.de> - */ - -#ifndef __LINUX_PLATFORM_DATA_ADAU1977_H__ -#define __LINUX_PLATFORM_DATA_ADAU1977_H__ - -/** - * enum adau1977_micbias - ADAU1977 MICBIAS pin voltage setting - * @ADAU1977_MICBIAS_5V0: MICBIAS is set to 5.0 V - * @ADAU1977_MICBIAS_5V5: MICBIAS is set to 5.5 V - * @ADAU1977_MICBIAS_6V0: MICBIAS is set to 6.0 V - * @ADAU1977_MICBIAS_6V5: MICBIAS is set to 6.5 V - * @ADAU1977_MICBIAS_7V0: MICBIAS is set to 7.0 V - * @ADAU1977_MICBIAS_7V5: MICBIAS is set to 7.5 V - * @ADAU1977_MICBIAS_8V0: MICBIAS is set to 8.0 V - * @ADAU1977_MICBIAS_8V5: MICBIAS is set to 8.5 V - * @ADAU1977_MICBIAS_9V0: MICBIAS is set to 9.0 V - */ -enum adau1977_micbias { - ADAU1977_MICBIAS_5V0 = 0x0, - ADAU1977_MICBIAS_5V5 = 0x1, - ADAU1977_MICBIAS_6V0 = 0x2, - ADAU1977_MICBIAS_6V5 = 0x3, - ADAU1977_MICBIAS_7V0 = 0x4, - ADAU1977_MICBIAS_7V5 = 0x5, - ADAU1977_MICBIAS_8V0 = 0x6, - ADAU1977_MICBIAS_8V5 = 0x7, - ADAU1977_MICBIAS_9V0 = 0x8, -}; - -/** - * struct adau1977_platform_data - Platform configuration data for the ADAU1977 - * @micbias: Specifies the voltage for the MICBIAS pin - */ -struct adau1977_platform_data { - enum adau1977_micbias micbias; -}; - -#endif diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 2c790ce95259..21f1d120b68e 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -220,6 +220,7 @@ struct snd_soc_component { /* function mark */ struct snd_pcm_substream *mark_module; struct snd_pcm_substream *mark_open; + struct snd_pcm_substream *mark_hw_params; void *mark_pm; #ifdef CONFIG_DEBUG_FS @@ -459,10 +460,9 @@ int snd_soc_pcm_component_new(struct snd_soc_pcm_runtime *rtd); void snd_soc_pcm_component_free(struct snd_soc_pcm_runtime *rtd); int snd_soc_pcm_component_prepare(struct snd_pcm_substream *substream); int snd_soc_pcm_component_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_component **last); + struct snd_pcm_hw_params *params); void snd_soc_pcm_component_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_component *last); + int rollback); int snd_soc_pcm_component_trigger(struct snd_pcm_substream *substream, int cmd); int snd_soc_pcm_component_pm_runtime_get(struct snd_soc_pcm_runtime *rtd, diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 2150bd4c7a05..7a85a6f83ca8 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -149,7 +149,8 @@ int snd_soc_dai_hw_params(struct snd_soc_dai *dai, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); void snd_soc_dai_hw_free(struct snd_soc_dai *dai, - struct snd_pcm_substream *substream); + struct snd_pcm_substream *substream, + int rollback); int snd_soc_dai_startup(struct snd_soc_dai *dai, struct snd_pcm_substream *substream); void snd_soc_dai_shutdown(struct snd_soc_dai *dai, @@ -390,6 +391,7 @@ struct snd_soc_dai { /* function mark */ struct snd_pcm_substream *mark_startup; + struct snd_pcm_substream *mark_hw_params; /* bit field */ unsigned int probed:1; diff --git a/include/sound/soc-link.h b/include/sound/soc-link.h index dac6c0ce6ede..eff34fc7d3d3 100644 --- a/include/sound/soc-link.h +++ b/include/sound/soc-link.h @@ -19,7 +19,8 @@ void snd_soc_link_shutdown(struct snd_pcm_substream *substream, int snd_soc_link_prepare(struct snd_pcm_substream *substream); int snd_soc_link_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); -void snd_soc_link_hw_free(struct snd_pcm_substream *substream); +void snd_soc_link_hw_free(struct snd_pcm_substream *substream, + int rollback); int snd_soc_link_trigger(struct snd_pcm_substream *substream, int cmd); int snd_soc_link_compr_startup(struct snd_compr_stream *cstream); diff --git a/include/sound/soc.h b/include/sound/soc.h index 3b038c563ae1..5ac578c9340c 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1161,6 +1161,7 @@ struct snd_soc_pcm_runtime { /* function mark */ struct snd_pcm_substream *mark_startup; + struct snd_pcm_substream *mark_hw_params; /* bit field */ unsigned int pop_wait:1; @@ -1183,21 +1184,15 @@ struct snd_soc_pcm_runtime { for ((i) = 0; \ ((i) < rtd->num_cpus) && ((dai) = asoc_rtd_to_cpu(rtd, i)); \ (i)++) -#define for_each_rtd_cpu_dais_rollback(rtd, i, dai) \ - for (; (--(i) >= 0) && ((dai) = asoc_rtd_to_cpu(rtd, i));) #define for_each_rtd_codec_dais(rtd, i, dai) \ for ((i) = 0; \ ((i) < rtd->num_codecs) && ((dai) = asoc_rtd_to_codec(rtd, i)); \ (i)++) -#define for_each_rtd_codec_dais_rollback(rtd, i, dai) \ - for (; (--(i) >= 0) && ((dai) = asoc_rtd_to_codec(rtd, i));) #define for_each_rtd_dais(rtd, i, dai) \ for ((i) = 0; \ ((i) < (rtd)->num_cpus + (rtd)->num_codecs) && \ ((dai) = (rtd)->dais[i]); \ (i)++) -#define for_each_rtd_dais_rollback(rtd, i, dai) \ - for (; (--(i) >= 0) && ((dai) = (rtd)->dais[i]);) void snd_soc_close_delayed_work(struct snd_soc_pcm_runtime *rtd); diff --git a/sound/soc/amd/raven/pci-acp3x.c b/sound/soc/amd/raven/pci-acp3x.c index 31b797c8bfe6..cd5b097a9402 100644 --- a/sound/soc/amd/raven/pci-acp3x.c +++ b/sound/soc/amd/raven/pci-acp3x.c @@ -231,9 +231,7 @@ static int snd_acp3x_probe(struct pci_dev *pci, } break; default: - dev_err(&pci->dev, "Invalid ACP audio mode : %d\n", val); - ret = -ENODEV; - goto disable_msi; + dev_info(&pci->dev, "ACP audio mode : %d\n", val); } pm_runtime_set_autosuspend_delay(&pci->dev, 2000); pm_runtime_use_autosuspend(&pci->dev); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 34c6dd04b85a..5791b7056af6 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1003,7 +1003,7 @@ config SND_SOC_PCM3168A_SPI select REGMAP_SPI config SND_SOC_PCM5102A - tristate + tristate "Texas Instruments PCM5102A CODEC" config SND_SOC_PCM512x tristate diff --git a/sound/soc/codecs/adau1977.c b/sound/soc/codecs/adau1977.c index 0a36e523584c..8260f49caa24 100644 --- a/sound/soc/codecs/adau1977.c +++ b/sound/soc/codecs/adau1977.c @@ -12,7 +12,6 @@ #include <linux/i2c.h> #include <linux/init.h> #include <linux/module.h> -#include <linux/platform_data/adau1977.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -24,6 +23,8 @@ #include <sound/soc.h> #include <sound/tlv.h> +#include <dt-bindings/sound/adi,adau1977.h> + #include "adau1977.h" #define ADAU1977_REG_POWER 0x00 @@ -881,13 +882,9 @@ static const struct snd_soc_component_driver adau1977_component_driver = { static int adau1977_setup_micbias(struct adau1977 *adau1977) { - struct adau1977_platform_data *pdata = adau1977->dev->platform_data; unsigned int micbias; - if (pdata) - micbias = pdata->micbias; - else if (device_property_read_u32(adau1977->dev, "adi,micbias", - &micbias)) + if (device_property_read_u32(adau1977->dev, "adi,micbias", &micbias)) micbias = ADAU1977_MICBIAS_8V5; if (micbias > ADAU1977_MICBIAS_9V0) { diff --git a/sound/soc/codecs/madera.c b/sound/soc/codecs/madera.c index 680f31a6493a..f4ed7e04673f 100644 --- a/sound/soc/codecs/madera.c +++ b/sound/soc/codecs/madera.c @@ -3019,11 +3019,11 @@ static int madera_hw_params_rate(struct snd_pcm_substream *substream, tar = 2 << MADERA_AIF1_RATE_SHIFT; break; case MADERA_CLK_ASYNCCLK_1: - reg = MADERA_ASYNC_SAMPLE_RATE_1, + reg = MADERA_ASYNC_SAMPLE_RATE_1; tar = 8 << MADERA_AIF1_RATE_SHIFT; break; case MADERA_CLK_ASYNCCLK_2: - reg = MADERA_ASYNC_SAMPLE_RATE_2, + reg = MADERA_ASYNC_SAMPLE_RATE_2; tar = 9 << MADERA_AIF1_RATE_SHIFT; break; default: diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index 2ed3fa67027d..70d353b63fe0 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -682,9 +682,7 @@ static int wm5102_out_comp_coeff_put(struct snd_kcontrol *kcontrol, struct arizona *arizona = dev_get_drvdata(component->dev->parent); mutex_lock(&arizona->dac_comp_lock); - memcpy(&arizona->dac_comp_coeff, ucontrol->value.bytes.data, - sizeof(arizona->dac_comp_coeff)); - arizona->dac_comp_coeff = be16_to_cpu(arizona->dac_comp_coeff); + arizona->dac_comp_coeff = get_unaligned_be16(ucontrol->value.bytes.data); mutex_unlock(&arizona->dac_comp_lock); return 0; diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index a6aa212fa0c8..15d42ce3b21d 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -218,7 +218,8 @@ static void wm8350_pga_work(struct work_struct *work) /* PGA volumes have 6 bits of resolution to ramp */ for (i = 0; i <= 63; i++) { - out1_complete = 1, out2_complete = 1; + out1_complete = 1; + out2_complete = 1; if (out1->ramp != WM8350_RAMP_NONE) out1_complete = wm8350_out1_ramp_step(wm8350_data); if (out2->ramp != WM8350_RAMP_NONE) diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 3f76ff71ea47..d04b64d32dc1 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -95,6 +95,16 @@ config SND_SOC_FSL_EASRC destination sample rate. It is a new design module compare with the old ASRC. +config SND_SOC_FSL_XCVR + tristate "NXP Audio Transceiver (XCVR) module support" + select REGMAP_MMIO + select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add Audio Transceiver (XCVR) support for NXP + iMX CPUs. XCVR is a digital module that supports HDMI2.1 eARC, + HDMI1.4 ARC and SPDIF. + config SND_SOC_FSL_UTILS tristate diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index b835eebf8825..1d2231f9cc47 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -25,6 +25,7 @@ snd-soc-fsl-utils-objs := fsl_utils.o snd-soc-fsl-dma-objs := fsl_dma.o snd-soc-fsl-mqs-objs := fsl_mqs.o snd-soc-fsl-easrc-objs := fsl_easrc.o +snd-soc-fsl-xcvr-objs := fsl_xcvr.o obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o @@ -38,6 +39,7 @@ obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o obj-$(CONFIG_SND_SOC_FSL_EASRC) += snd-soc-fsl-easrc.o obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o +obj-$(CONFIG_SND_SOC_FSL_XCVR) += snd-soc-fsl-xcvr.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c index b0f643fefe1e..5fa178f3f497 100644 --- a/sound/soc/fsl/fsl_spdif.c +++ b/sound/soc/fsl/fsl_spdif.c @@ -49,10 +49,18 @@ static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb }; * @imx: for imx platform * @shared_root_clock: flag of sharing a clock source with others; * so the driver shouldn't set root clock rate + * @interrupts: interrupt number + * @tx_burst: tx maxburst size + * @rx_burst: rx maxburst size + * @tx_formats: tx supported data format */ struct fsl_spdif_soc_data { bool imx; bool shared_root_clock; + u32 interrupts; + u32 tx_burst; + u32 rx_burst; + u64 tx_formats; }; /* @@ -128,16 +136,38 @@ struct fsl_spdif_priv { static struct fsl_spdif_soc_data fsl_spdif_vf610 = { .imx = false, .shared_root_clock = false, + .interrupts = 1, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, }; static struct fsl_spdif_soc_data fsl_spdif_imx35 = { .imx = true, .shared_root_clock = false, + .interrupts = 1, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, }; static struct fsl_spdif_soc_data fsl_spdif_imx6sx = { .imx = true, .shared_root_clock = true, + .interrupts = 1, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx8qm = { + .imx = true, + .shared_root_clock = true, + .interrupts = 2, + .tx_burst = 2, /* Applied for EDMA */ + .rx_burst = 2, /* Applied for EDMA */ + .tx_formats = SNDRV_PCM_FMTBIT_S24_LE, /* Applied for EDMA */ }; /* Check if clk is a root clock that does not share clock source with others */ @@ -429,10 +459,18 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream, rate = SPDIF_TXRATE_48000; csfs = IEC958_AES3_CON_FS_48000; break; + case 88200: + rate = SPDIF_TXRATE_88200; + csfs = IEC958_AES3_CON_FS_88200; + break; case 96000: rate = SPDIF_TXRATE_96000; csfs = IEC958_AES3_CON_FS_96000; break; + case 176400: + rate = SPDIF_TXRATE_176400; + csfs = IEC958_AES3_CON_FS_176400; + break; case 192000: rate = SPDIF_TXRATE_192000; csfs = IEC958_AES3_CON_FS_192000; @@ -827,7 +865,7 @@ static int fsl_spdif_rxrate_info(struct snd_kcontrol *kcontrol, uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 16000; - uinfo->value.integer.max = 96000; + uinfo->value.integer.max = 192000; return 0; } @@ -1145,7 +1183,8 @@ static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, struct clk *clk, u64 savesub, enum spdif_txrate index, bool round) { - static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; + static const u32 rate[] = { 32000, 44100, 48000, 88200, 96000, 176400, + 192000, }; bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk); u64 rate_ideal, rate_actual, sub; u32 arate; @@ -1205,7 +1244,8 @@ out: static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, enum spdif_txrate index) { - static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; + static const u32 rate[] = { 32000, 44100, 48000, 88200, 96000, 176400, + 192000, }; struct platform_device *pdev = spdif_priv->pdev; struct device *dev = &pdev->dev; u64 savesub = 100000, ret; @@ -1273,6 +1313,8 @@ static int fsl_spdif_probe(struct platform_device *pdev) /* Initialize this copy of the CPU DAI driver structure */ memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai)); spdif_priv->cpu_dai_drv.name = dev_name(&pdev->dev); + spdif_priv->cpu_dai_drv.playback.formats = + spdif_priv->soc->tx_formats; /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -1287,15 +1329,19 @@ static int fsl_spdif_probe(struct platform_device *pdev) return PTR_ERR(spdif_priv->regmap); } - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + for (i = 0; i < spdif_priv->soc->interrupts; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } - ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0, - dev_name(&pdev->dev), spdif_priv); - if (ret) { - dev_err(&pdev->dev, "could not claim irq %u\n", irq); - return ret; + ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0, + dev_name(&pdev->dev), spdif_priv); + if (ret) { + dev_err(&pdev->dev, "could not claim irq %u\n", irq); + return ret; + } } /* Get system clock for rx clock rate calculation */ @@ -1344,8 +1390,8 @@ static int fsl_spdif_probe(struct platform_device *pdev) spdif_priv->dpll_locked = false; - spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML; - spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML; + spdif_priv->dma_params_tx.maxburst = spdif_priv->soc->tx_burst; + spdif_priv->dma_params_rx.maxburst = spdif_priv->soc->rx_burst; spdif_priv->dma_params_tx.addr = res->start + REG_SPDIF_STL; spdif_priv->dma_params_rx.addr = res->start + REG_SPDIF_SRL; @@ -1458,6 +1504,7 @@ static const struct of_device_id fsl_spdif_dt_ids[] = { { .compatible = "fsl,imx35-spdif", .data = &fsl_spdif_imx35, }, { .compatible = "fsl,vf610-spdif", .data = &fsl_spdif_vf610, }, { .compatible = "fsl,imx6sx-spdif", .data = &fsl_spdif_imx6sx, }, + { .compatible = "fsl,imx8qm-spdif", .data = &fsl_spdif_imx8qm, }, {} }; MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h index e6c61e07bc1a..d5f1dfd58740 100644 --- a/sound/soc/fsl/fsl_spdif.h +++ b/sound/soc/fsl/fsl_spdif.h @@ -163,7 +163,9 @@ enum spdif_txrate { SPDIF_TXRATE_32000 = 0, SPDIF_TXRATE_44100, SPDIF_TXRATE_48000, + SPDIF_TXRATE_88200, SPDIF_TXRATE_96000, + SPDIF_TXRATE_176400, SPDIF_TXRATE_192000, }; #define SPDIF_TXRATE_MAX (SPDIF_TXRATE_192000 + 1) @@ -177,15 +179,20 @@ enum spdif_txrate { #define FSL_SPDIF_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | \ SNDRV_PCM_RATE_192000) #define FSL_SPDIF_RATES_CAPTURE (SNDRV_PCM_RATE_16000 | \ SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ SNDRV_PCM_RATE_64000 | \ - SNDRV_PCM_RATE_96000) + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000) #define FSL_SPDIF_FORMATS_PLAYBACK (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S20_3LE | \ diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c new file mode 100644 index 000000000000..c055179e6d11 --- /dev/null +++ b/sound/soc/fsl/fsl_xcvr.c @@ -0,0 +1,1359 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +#include <linux/bitrev.h> +#include <linux/clk.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_iec958.h> +#include <sound/pcm_params.h> + +#include "fsl_xcvr.h" +#include "imx-pcm.h" + +#define FSL_XCVR_CAPDS_SIZE 256 + +struct fsl_xcvr_soc_data { + const char *fw_name; +}; + +struct fsl_xcvr { + const struct fsl_xcvr_soc_data *soc_data; + struct platform_device *pdev; + struct regmap *regmap; + struct clk *ipg_clk; + struct clk *pll_ipg_clk; + struct clk *phy_clk; + struct clk *spba_clk; + struct reset_control *reset; + u8 streams; + u32 mode; + u32 arc_mode; + void __iomem *ram_addr; + struct snd_dmaengine_dai_dma_data dma_prms_rx; + struct snd_dmaengine_dai_dma_data dma_prms_tx; + struct snd_aes_iec958 rx_iec958; + struct snd_aes_iec958 tx_iec958; + u8 cap_ds[FSL_XCVR_CAPDS_SIZE]; +}; + +static const struct fsl_xcvr_pll_conf { + u8 mfi; /* min=0x18, max=0x38 */ + u32 mfn; /* signed int, 2's compl., min=0x3FFF0000, max=0x00010000 */ + u32 mfd; /* unsigned int */ + u32 fout; /* Fout = Fref*(MFI + MFN/MFD), Fref is 24MHz */ +} fsl_xcvr_pll_cfg[] = { + { .mfi = 54, .mfn = 1, .mfd = 6, .fout = 1300000000, }, /* 1.3 GHz */ + { .mfi = 32, .mfn = 96, .mfd = 125, .fout = 786432000, }, /* 8000 Hz */ + { .mfi = 30, .mfn = 66, .mfd = 625, .fout = 722534400, }, /* 11025 Hz */ + { .mfi = 29, .mfn = 1, .mfd = 6, .fout = 700000000, }, /* 700 MHz */ +}; + +/* + * HDMI2.1 spec defines 6- and 12-channels layout for one bit audio + * stream. Todo: to check how this case can be considered below + */ +static const u32 fsl_xcvr_earc_channels[] = { 1, 2, 8, 16, 32, }; +static const struct snd_pcm_hw_constraint_list fsl_xcvr_earc_channels_constr = { + .count = ARRAY_SIZE(fsl_xcvr_earc_channels), + .list = fsl_xcvr_earc_channels, +}; + +static const u32 fsl_xcvr_earc_rates[] = { + 32000, 44100, 48000, 64000, 88200, 96000, + 128000, 176400, 192000, 256000, 352800, 384000, + 512000, 705600, 768000, 1024000, 1411200, 1536000, +}; +static const struct snd_pcm_hw_constraint_list fsl_xcvr_earc_rates_constr = { + .count = ARRAY_SIZE(fsl_xcvr_earc_rates), + .list = fsl_xcvr_earc_rates, +}; + +static const u32 fsl_xcvr_spdif_channels[] = { 2, }; +static const struct snd_pcm_hw_constraint_list fsl_xcvr_spdif_channels_constr = { + .count = ARRAY_SIZE(fsl_xcvr_spdif_channels), + .list = fsl_xcvr_spdif_channels, +}; + +static const u32 fsl_xcvr_spdif_rates[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000, +}; +static const struct snd_pcm_hw_constraint_list fsl_xcvr_spdif_rates_constr = { + .count = ARRAY_SIZE(fsl_xcvr_spdif_rates), + .list = fsl_xcvr_spdif_rates, +}; + +static int fsl_xcvr_arc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + + xcvr->arc_mode = snd_soc_enum_item_to_val(e, item[0]); + + return 0; +} + +static int fsl_xcvr_arc_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + ucontrol->value.enumerated.item[0] = xcvr->arc_mode; + + return 0; +} + +static const u32 fsl_xcvr_phy_arc_cfg[] = { + FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN, FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN, +}; + +static const char * const fsl_xcvr_arc_mode[] = { "Single Ended", "Common", }; +static const struct soc_enum fsl_xcvr_arc_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fsl_xcvr_arc_mode), fsl_xcvr_arc_mode); +static struct snd_kcontrol_new fsl_xcvr_arc_mode_kctl = + SOC_ENUM_EXT("ARC Mode", fsl_xcvr_arc_mode_enum, + fsl_xcvr_arc_mode_get, fsl_xcvr_arc_mode_put); + +/* Capabilities data structure, bytes */ +static int fsl_xcvr_type_capds_bytes_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = FSL_XCVR_CAPDS_SIZE; + + return 0; +} + +static int fsl_xcvr_capds_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(ucontrol->value.bytes.data, xcvr->cap_ds, FSL_XCVR_CAPDS_SIZE); + + return 0; +} + +static int fsl_xcvr_capds_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(xcvr->cap_ds, ucontrol->value.bytes.data, FSL_XCVR_CAPDS_SIZE); + + return 0; +} + +static struct snd_kcontrol_new fsl_xcvr_earc_capds_kctl = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Capabilities Data Structure", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = fsl_xcvr_type_capds_bytes_info, + .get = fsl_xcvr_capds_get, + .put = fsl_xcvr_capds_put, +}; + +static int fsl_xcvr_activate_ctl(struct snd_soc_dai *dai, const char *name, + bool active) +{ + struct snd_soc_card *card = dai->component->card; + struct snd_kcontrol *kctl; + bool enabled; + + kctl = snd_soc_card_get_kcontrol(card, name); + if (kctl == NULL) + return -ENOENT; + + enabled = ((kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_WRITE) != 0); + if (active == enabled) + return 0; /* nothing to do */ + + if (active) + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_WRITE; + else + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE; + + snd_ctl_notify(card->snd_card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id); + + return 1; +} + +static int fsl_xcvr_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct snd_soc_card *card = dai->component->card; + struct snd_soc_pcm_runtime *rtd; + + xcvr->mode = snd_soc_enum_item_to_val(e, item[0]); + + fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, + (xcvr->mode == FSL_XCVR_MODE_ARC)); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, + (xcvr->mode == FSL_XCVR_MODE_EARC)); + /* Allow playback for SPDIF only */ + rtd = snd_soc_get_pcm_runtime(card, card->dai_link); + rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count = + (xcvr->mode == FSL_XCVR_MODE_SPDIF ? 1 : 0); + return 0; +} + +static int fsl_xcvr_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + ucontrol->value.enumerated.item[0] = xcvr->mode; + + return 0; +} + +static const char * const fsl_xcvr_mode[] = { "SPDIF", "ARC RX", "eARC", }; +static const struct soc_enum fsl_xcvr_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(fsl_xcvr_mode), fsl_xcvr_mode); +static struct snd_kcontrol_new fsl_xcvr_mode_kctl = + SOC_ENUM_EXT("XCVR Mode", fsl_xcvr_mode_enum, + fsl_xcvr_mode_get, fsl_xcvr_mode_put); + +/** phy: true => phy, false => pll */ +static int fsl_xcvr_ai_write(struct fsl_xcvr *xcvr, u8 reg, u32 data, bool phy) +{ + struct device *dev = &xcvr->pdev->dev; + u32 val, idx, tidx; + int ret; + + idx = BIT(phy ? 26 : 24); + tidx = BIT(phy ? 27 : 25); + + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_CLR, 0xFF); + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, reg); + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_WDATA, data); + regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_TOG, idx); + + ret = regmap_read_poll_timeout(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL, val, + (val & idx) != ((val & tidx) >> 1), + 10, 10000); + if (ret) + dev_err(dev, "AI timeout: failed to set %s reg 0x%02x=0x%08x\n", + phy ? "PHY" : "PLL", reg, data); + return ret; +} + +static int fsl_xcvr_en_phy_pll(struct fsl_xcvr *xcvr, u32 freq, bool tx) +{ + struct device *dev = &xcvr->pdev->dev; + u32 i, div = 0, log2; + int ret; + + for (i = 0; i < ARRAY_SIZE(fsl_xcvr_pll_cfg); i++) { + if (fsl_xcvr_pll_cfg[i].fout % freq == 0) { + div = fsl_xcvr_pll_cfg[i].fout / freq; + break; + } + } + + if (!div || i >= ARRAY_SIZE(fsl_xcvr_pll_cfg)) + return -EINVAL; + + log2 = ilog2(div); + + /* Release AI interface from reset */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, + FSL_XCVR_PHY_AI_CTRL_AI_RESETN); + if (ret < 0) { + dev_err(dev, "Error while setting IER0: %d\n", ret); + return ret; + } + + /* PLL: BANDGAP_SET: EN_VBG (enable bandgap) */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_BANDGAP_SET, + FSL_XCVR_PLL_BANDGAP_EN_VBG, 0); + + /* PLL: CTRL0: DIV_INTEGER */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0, fsl_xcvr_pll_cfg[i].mfi, 0); + /* PLL: NUMERATOR: MFN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_NUM, fsl_xcvr_pll_cfg[i].mfn, 0); + /* PLL: DENOMINATOR: MFD */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_DEN, fsl_xcvr_pll_cfg[i].mfd, 0); + /* PLL: CTRL0_SET: HOLD_RING_OFF, POWER_UP */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET, + FSL_XCVR_PLL_CTRL0_HROFF | FSL_XCVR_PLL_CTRL0_PWP, 0); + udelay(25); + /* PLL: CTRL0: Clear Hold Ring Off */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_CLR, + FSL_XCVR_PLL_CTRL0_HROFF, 0); + udelay(100); + if (tx) { /* TX is enabled for SPDIF only */ + /* PLL: POSTDIV: PDIV0 */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV, + FSL_XCVR_PLL_PDIVx(log2, 0), 0); + /* PLL: CTRL_SET: CLKMUX0_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET, + FSL_XCVR_PLL_CTRL0_CM0_EN, 0); + } else if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC RX */ + /* PLL: POSTDIV: PDIV1 */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV, + FSL_XCVR_PLL_PDIVx(log2, 1), 0); + /* PLL: CTRL_SET: CLKMUX1_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET, + FSL_XCVR_PLL_CTRL0_CM1_EN, 0); + } else { /* SPDIF / ARC RX */ + /* PLL: POSTDIV: PDIV2 */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_PDIV, + FSL_XCVR_PLL_PDIVx(log2, 2), 0); + /* PLL: CTRL_SET: CLKMUX2_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PLL_CTRL0_SET, + FSL_XCVR_PLL_CTRL0_CM2_EN, 0); + } + + if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC mode */ + /* PHY: CTRL_SET: TX_DIFF_OE, PHY_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_TSDIFF_OE | + FSL_XCVR_PHY_CTRL_PHY_EN, 1); + /* PHY: CTRL2_SET: EARC_TX_MODE */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL2_SET, + FSL_XCVR_PHY_CTRL2_EARC_TXMS, 1); + } else if (!tx) { /* SPDIF / ARC RX mode */ + if (xcvr->mode == FSL_XCVR_MODE_SPDIF) + /* PHY: CTRL_SET: SPDIF_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_SPDIF_EN, 1); + else /* PHY: CTRL_SET: ARC RX setup */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_PHY_EN | + FSL_XCVR_PHY_CTRL_RX_CM_EN | + fsl_xcvr_phy_arc_cfg[xcvr->arc_mode], 1); + } + + dev_dbg(dev, "PLL Fexp: %u, Fout: %u, mfi: %u, mfn: %u, mfd: %d, div: %u, pdiv0: %u\n", + freq, fsl_xcvr_pll_cfg[i].fout, fsl_xcvr_pll_cfg[i].mfi, + fsl_xcvr_pll_cfg[i].mfn, fsl_xcvr_pll_cfg[i].mfd, div, log2); + return 0; +} + +static int fsl_xcvr_en_aud_pll(struct fsl_xcvr *xcvr, u32 freq) +{ + struct device *dev = &xcvr->pdev->dev; + int ret; + + clk_disable_unprepare(xcvr->phy_clk); + ret = clk_set_rate(xcvr->phy_clk, freq); + if (ret < 0) { + dev_err(dev, "Error while setting AUD PLL rate: %d\n", ret); + return ret; + } + ret = clk_prepare_enable(xcvr->phy_clk); + if (ret) { + dev_err(dev, "failed to start PHY clock: %d\n", ret); + return ret; + } + + /* Release AI interface from reset */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_PHY_AI_CTRL_SET, + FSL_XCVR_PHY_AI_CTRL_AI_RESETN); + if (ret < 0) { + dev_err(dev, "Error while setting IER0: %d\n", ret); + return ret; + } + + if (xcvr->mode == FSL_XCVR_MODE_EARC) { /* eARC mode */ + /* PHY: CTRL_SET: TX_DIFF_OE, PHY_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_TSDIFF_OE | + FSL_XCVR_PHY_CTRL_PHY_EN, 1); + /* PHY: CTRL2_SET: EARC_TX_MODE */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL2_SET, + FSL_XCVR_PHY_CTRL2_EARC_TXMS, 1); + } else { /* SPDIF mode */ + /* PHY: CTRL_SET: TX_CLK_AUD_SS | SPDIF_EN */ + fsl_xcvr_ai_write(xcvr, FSL_XCVR_PHY_CTRL_SET, + FSL_XCVR_PHY_CTRL_TX_CLK_AUD_SS | + FSL_XCVR_PHY_CTRL_SPDIF_EN, 1); + } + + dev_dbg(dev, "PLL Fexp: %u\n", freq); + + return 0; +} + +#define FSL_XCVR_SPDIF_RX_FREQ 175000000 +static int fsl_xcvr_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 m_ctl = 0, v_ctl = 0; + u32 r = substream->runtime->rate, ch = substream->runtime->channels; + u32 fout = 32 * r * ch * 10 * 2; + int ret = 0; + + switch (xcvr->mode) { + case FSL_XCVR_MODE_SPDIF: + case FSL_XCVR_MODE_ARC: + if (tx) { + ret = fsl_xcvr_en_aud_pll(xcvr, fout); + if (ret < 0) { + dev_err(dai->dev, "Failed to set TX freq %u: %d\n", + fout, ret); + return ret; + } + + ret = regmap_write(xcvr->regmap, FSL_XCVR_TX_DPTH_CTRL_SET, + FSL_XCVR_TX_DPTH_CTRL_FRM_FMT); + if (ret < 0) { + dev_err(dai->dev, "Failed to set TX_DPTH: %d\n", ret); + return ret; + } + + /** + * set SPDIF MODE - this flag is used to gate + * SPDIF output, useless for SPDIF RX + */ + m_ctl |= FSL_XCVR_EXT_CTRL_SPDIF_MODE; + v_ctl |= FSL_XCVR_EXT_CTRL_SPDIF_MODE; + } else { + /** + * Clear RX FIFO, flip RX FIFO bits, + * disable eARC related HW mode detects + */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_SET, + FSL_XCVR_RX_DPTH_CTRL_STORE_FMT | + FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO | + FSL_XCVR_RX_DPTH_CTRL_COMP | + FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL); + if (ret < 0) { + dev_err(dai->dev, "Failed to set RX_DPTH: %d\n", ret); + return ret; + } + + ret = fsl_xcvr_en_phy_pll(xcvr, FSL_XCVR_SPDIF_RX_FREQ, tx); + if (ret < 0) { + dev_err(dai->dev, "Failed to set RX freq %u: %d\n", + FSL_XCVR_SPDIF_RX_FREQ, ret); + return ret; + } + } + break; + case FSL_XCVR_MODE_EARC: + if (!tx) { + /** Clear RX FIFO, flip RX FIFO bits */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_SET, + FSL_XCVR_RX_DPTH_CTRL_STORE_FMT | + FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO); + if (ret < 0) { + dev_err(dai->dev, "Failed to set RX_DPTH: %d\n", ret); + return ret; + } + + /** Enable eARC related HW mode detects */ + ret = regmap_write(xcvr->regmap, FSL_XCVR_RX_DPTH_CTRL_CLR, + FSL_XCVR_RX_DPTH_CTRL_COMP | + FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL); + if (ret < 0) { + dev_err(dai->dev, "Failed to clr TX_DPTH: %d\n", ret); + return ret; + } + } + + /* clear CMDC RESET */ + m_ctl |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx); + /* set TX_RX_MODE */ + m_ctl |= FSL_XCVR_EXT_CTRL_TX_RX_MODE; + v_ctl |= (tx ? FSL_XCVR_EXT_CTRL_TX_RX_MODE : 0); + break; + } + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, FSL_XCVR_IRQ_EARC_ALL); + if (ret < 0) { + dev_err(dai->dev, "Error while setting IER0: %d\n", ret); + return ret; + } + + /* clear DPATH RESET */ + m_ctl |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx); + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, m_ctl, v_ctl); + if (ret < 0) { + dev_err(dai->dev, "Error while setting EXT_CTRL: %d\n", ret); + return ret; + } + + return 0; +} + +static int fsl_xcvr_constr(const struct snd_pcm_substream *substream, + const struct snd_pcm_hw_constraint_list *channels, + const struct snd_pcm_hw_constraint_list *rates) +{ + struct snd_pcm_runtime *rt = substream->runtime; + int ret; + + ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + channels); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE, + rates); + if (ret < 0) + return ret; + + return 0; +} + +static int fsl_xcvr_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0; + + if (xcvr->streams & BIT(substream->stream)) { + dev_err(dai->dev, "%sX busy\n", tx ? "T" : "R"); + return -EBUSY; + } + + switch (xcvr->mode) { + case FSL_XCVR_MODE_SPDIF: + case FSL_XCVR_MODE_ARC: + ret = fsl_xcvr_constr(substream, &fsl_xcvr_spdif_channels_constr, + &fsl_xcvr_spdif_rates_constr); + break; + case FSL_XCVR_MODE_EARC: + ret = fsl_xcvr_constr(substream, &fsl_xcvr_earc_channels_constr, + &fsl_xcvr_earc_rates_constr); + break; + } + if (ret < 0) + return ret; + + xcvr->streams |= BIT(substream->stream); + + /* Disable XCVR controls if there is stream started */ + fsl_xcvr_activate_ctl(dai, fsl_xcvr_mode_kctl.name, false); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, false); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, false); + + return 0; +} + +static void fsl_xcvr_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u32 mask = 0, val = 0; + int ret; + + xcvr->streams &= ~BIT(substream->stream); + + /* Enable XCVR controls if there is no stream started */ + if (!xcvr->streams) { + fsl_xcvr_activate_ctl(dai, fsl_xcvr_mode_kctl.name, true); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_arc_mode_kctl.name, + (xcvr->mode == FSL_XCVR_MODE_ARC)); + fsl_xcvr_activate_ctl(dai, fsl_xcvr_earc_capds_kctl.name, + (xcvr->mode == FSL_XCVR_MODE_EARC)); + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_IER0, + FSL_XCVR_IRQ_EARC_ALL, 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to set IER0: %d\n", ret); + return; + } + + /* clear SPDIF MODE */ + if (xcvr->mode == FSL_XCVR_MODE_SPDIF) + mask |= FSL_XCVR_EXT_CTRL_SPDIF_MODE; + } + + if (xcvr->mode == FSL_XCVR_MODE_EARC) { + /* set CMDC RESET */ + mask |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx); + val |= FSL_XCVR_EXT_CTRL_CMDC_RESET(tx); + } + + /* set DPATH RESET */ + mask |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx); + val |= FSL_XCVR_EXT_CTRL_DPTH_RESET(tx); + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, mask, val); + if (ret < 0) { + dev_err(dai->dev, "Err setting DPATH RESET: %d\n", ret); + return; + } +} + +static int fsl_xcvr_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (tx) { + switch (xcvr->mode) { + case FSL_XCVR_MODE_EARC: + /* set isr_cmdc_tx_en, w1c */ + ret = regmap_write(xcvr->regmap, + FSL_XCVR_ISR_SET, + FSL_XCVR_ISR_CMDC_TX_EN); + if (ret < 0) { + dev_err(dai->dev, "err updating isr %d\n", ret); + return ret; + } + fallthrough; + case FSL_XCVR_MODE_SPDIF: + ret = regmap_write(xcvr->regmap, + FSL_XCVR_TX_DPTH_CTRL_SET, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to start DATA_TX: %d\n", ret); + return ret; + } + break; + } + } + + /* enable DMA RD/WR */ + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx), 0); + if (ret < 0) { + dev_err(dai->dev, "Failed to enable DMA: %d\n", ret); + return ret; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* disable DMA RD/WR */ + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_DMA_DIS(tx), + FSL_XCVR_EXT_CTRL_DMA_DIS(tx)); + if (ret < 0) { + dev_err(dai->dev, "Failed to disable DMA: %d\n", ret); + return ret; + } + + if (tx) { + switch (xcvr->mode) { + case FSL_XCVR_MODE_SPDIF: + ret = regmap_write(xcvr->regmap, + FSL_XCVR_TX_DPTH_CTRL_CLR, + FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX); + if (ret < 0) { + dev_err(dai->dev, "Failed to stop DATA_TX: %d\n", ret); + return ret; + } + fallthrough; + case FSL_XCVR_MODE_EARC: + /* clear ISR_CMDC_TX_EN, W1C */ + ret = regmap_write(xcvr->regmap, + FSL_XCVR_ISR_CLR, + FSL_XCVR_ISR_CMDC_TX_EN); + if (ret < 0) { + dev_err(dai->dev, + "Err updating ISR %d\n", ret); + return ret; + } + break; + } + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fsl_xcvr_load_firmware(struct fsl_xcvr *xcvr) +{ + struct device *dev = &xcvr->pdev->dev; + const struct firmware *fw; + int ret = 0, rem, off, out, page = 0, size = FSL_XCVR_REG_OFFSET; + u32 mask, val; + + ret = request_firmware(&fw, xcvr->soc_data->fw_name, dev); + if (ret) { + dev_err(dev, "failed to request firmware.\n"); + return ret; + } + + rem = fw->size; + + /* RAM is 20KiB = 16KiB code + 4KiB data => max 10 pages 2KiB each */ + if (rem > 16384) { + dev_err(dev, "FW size %d is bigger than 16KiB.\n", rem); + return -ENOMEM; + } + + for (page = 0; page < 10; page++) { + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_PAGE_MASK, + FSL_XCVR_EXT_CTRL_PAGE(page)); + if (ret < 0) { + dev_err(dev, "FW: failed to set page %d, err=%d\n", + page, ret); + goto err_firmware; + } + + off = page * size; + out = min(rem, size); + /* IPG clock is assumed to be running, otherwise it will hang */ + if (out > 0) { + /* write firmware into code memory */ + memcpy_toio(xcvr->ram_addr, fw->data + off, out); + rem -= out; + if (rem == 0) { + /* last part of firmware written */ + /* clean remaining part of code memory page */ + memset_io(xcvr->ram_addr + out, 0, size - out); + } + } else { + /* clean current page, including data memory */ + memset_io(xcvr->ram_addr, 0, size); + } + }; + +err_firmware: + release_firmware(fw); + if (ret < 0) + return ret; + + /* configure watermarks */ + mask = FSL_XCVR_EXT_CTRL_RX_FWM_MASK | FSL_XCVR_EXT_CTRL_TX_FWM_MASK; + val = FSL_XCVR_EXT_CTRL_RX_FWM(FSL_XCVR_FIFO_WMK_RX); + val |= FSL_XCVR_EXT_CTRL_TX_FWM(FSL_XCVR_FIFO_WMK_TX); + /* disable DMA RD/WR */ + mask |= FSL_XCVR_EXT_CTRL_DMA_RD_DIS | FSL_XCVR_EXT_CTRL_DMA_WR_DIS; + val |= FSL_XCVR_EXT_CTRL_DMA_RD_DIS | FSL_XCVR_EXT_CTRL_DMA_WR_DIS; + /* Data RAM is 4KiB, last two pages: 8 and 9. Select page 8. */ + mask |= FSL_XCVR_EXT_CTRL_PAGE_MASK; + val |= FSL_XCVR_EXT_CTRL_PAGE(8); + + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, mask, val); + if (ret < 0) { + dev_err(dev, "Failed to set watermarks: %d\n", ret); + return ret; + } + + /* Store Capabilities Data Structure into Data RAM */ + memcpy_toio(xcvr->ram_addr + FSL_XCVR_CAP_DATA_STR, xcvr->cap_ds, + FSL_XCVR_CAPDS_SIZE); + return 0; +} + +static int fsl_xcvr_type_iec958_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +static int fsl_xcvr_type_iec958_bytes_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = sizeof_field(struct snd_aes_iec958, status); + + return 0; +} + +static int fsl_xcvr_rx_cs_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(ucontrol->value.iec958.status, xcvr->rx_iec958.status, 24); + + return 0; +} + +static int fsl_xcvr_tx_cs_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(ucontrol->value.iec958.status, xcvr->tx_iec958.status, 24); + + return 0; +} + +static int fsl_xcvr_tx_cs_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + memcpy(xcvr->tx_iec958.status, ucontrol->value.iec958.status, 24); + + return 0; +} + +static struct snd_kcontrol_new fsl_xcvr_rx_ctls[] = { + /* Channel status controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = fsl_xcvr_type_iec958_info, + .get = fsl_xcvr_rx_cs_get, + }, + /* Capture channel status, bytes */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Capture Channel Status", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = fsl_xcvr_type_iec958_bytes_info, + .get = fsl_xcvr_rx_cs_get, + }, +}; + +static struct snd_kcontrol_new fsl_xcvr_tx_ctls[] = { + /* Channel status controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = fsl_xcvr_type_iec958_info, + .get = fsl_xcvr_tx_cs_get, + .put = fsl_xcvr_tx_cs_put, + }, + /* Playback channel status, bytes */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Playback Channel Status", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = fsl_xcvr_type_iec958_bytes_info, + .get = fsl_xcvr_tx_cs_get, + .put = fsl_xcvr_tx_cs_put, + }, +}; + +static struct snd_soc_dai_ops fsl_xcvr_dai_ops = { + .prepare = fsl_xcvr_prepare, + .startup = fsl_xcvr_startup, + .shutdown = fsl_xcvr_shutdown, + .trigger = fsl_xcvr_trigger, +}; + +static int fsl_xcvr_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_xcvr *xcvr = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &xcvr->dma_prms_tx, &xcvr->dma_prms_rx); + snd_soc_dai_set_drvdata(dai, xcvr); + + snd_soc_add_dai_controls(dai, &fsl_xcvr_mode_kctl, 1); + snd_soc_add_dai_controls(dai, &fsl_xcvr_arc_mode_kctl, 1); + snd_soc_add_dai_controls(dai, &fsl_xcvr_earc_capds_kctl, 1); + snd_soc_add_dai_controls(dai, fsl_xcvr_tx_ctls, + ARRAY_SIZE(fsl_xcvr_tx_ctls)); + snd_soc_add_dai_controls(dai, fsl_xcvr_rx_ctls, + ARRAY_SIZE(fsl_xcvr_rx_ctls)); + return 0; +} + +static struct snd_soc_dai_driver fsl_xcvr_dai = { + .probe = fsl_xcvr_dai_probe, + .ops = &fsl_xcvr_dai_ops, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 32000, + .rate_max = 1536000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 32000, + .rate_max = 1536000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, +}; + +static const struct snd_soc_component_driver fsl_xcvr_comp = { + .name = "fsl-xcvr-dai", +}; + +static const struct reg_default fsl_xcvr_reg_defaults[] = { + { FSL_XCVR_VERSION, 0x00000000 }, + { FSL_XCVR_EXT_CTRL, 0xF8204040 }, + { FSL_XCVR_EXT_STATUS, 0x00000000 }, + { FSL_XCVR_EXT_IER0, 0x00000000 }, + { FSL_XCVR_EXT_IER1, 0x00000000 }, + { FSL_XCVR_EXT_ISR, 0x00000000 }, + { FSL_XCVR_EXT_ISR_SET, 0x00000000 }, + { FSL_XCVR_EXT_ISR_CLR, 0x00000000 }, + { FSL_XCVR_EXT_ISR_TOG, 0x00000000 }, + { FSL_XCVR_IER, 0x00000000 }, + { FSL_XCVR_ISR, 0x00000000 }, + { FSL_XCVR_ISR_SET, 0x00000000 }, + { FSL_XCVR_ISR_CLR, 0x00000000 }, + { FSL_XCVR_ISR_TOG, 0x00000000 }, + { FSL_XCVR_RX_DPTH_CTRL, 0x00002C89 }, + { FSL_XCVR_RX_DPTH_CTRL_SET, 0x00002C89 }, + { FSL_XCVR_RX_DPTH_CTRL_CLR, 0x00002C89 }, + { FSL_XCVR_RX_DPTH_CTRL_TOG, 0x00002C89 }, + { FSL_XCVR_TX_DPTH_CTRL, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CTRL_SET, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CTRL_CLR, 0x00000000 }, + { FSL_XCVR_TX_DPTH_CTRL_TOG, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_0, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_1, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_2, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_3, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_4, 0x00000000 }, + { FSL_XCVR_TX_CS_DATA_5, 0x00000000 }, + { FSL_XCVR_DEBUG_REG_0, 0x00000000 }, + { FSL_XCVR_DEBUG_REG_1, 0x00000000 }, +}; + +static bool fsl_xcvr_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_XCVR_VERSION: + case FSL_XCVR_EXT_CTRL: + case FSL_XCVR_EXT_STATUS: + case FSL_XCVR_EXT_IER0: + case FSL_XCVR_EXT_IER1: + case FSL_XCVR_EXT_ISR: + case FSL_XCVR_EXT_ISR_SET: + case FSL_XCVR_EXT_ISR_CLR: + case FSL_XCVR_EXT_ISR_TOG: + case FSL_XCVR_IER: + case FSL_XCVR_ISR: + case FSL_XCVR_ISR_SET: + case FSL_XCVR_ISR_CLR: + case FSL_XCVR_ISR_TOG: + case FSL_XCVR_PHY_AI_CTRL: + case FSL_XCVR_PHY_AI_CTRL_SET: + case FSL_XCVR_PHY_AI_CTRL_CLR: + case FSL_XCVR_PHY_AI_CTRL_TOG: + case FSL_XCVR_PHY_AI_RDATA: + case FSL_XCVR_CLK_CTRL: + case FSL_XCVR_RX_DPTH_CTRL: + case FSL_XCVR_RX_DPTH_CTRL_SET: + case FSL_XCVR_RX_DPTH_CTRL_CLR: + case FSL_XCVR_RX_DPTH_CTRL_TOG: + case FSL_XCVR_TX_DPTH_CTRL: + case FSL_XCVR_TX_DPTH_CTRL_SET: + case FSL_XCVR_TX_DPTH_CTRL_CLR: + case FSL_XCVR_TX_DPTH_CTRL_TOG: + case FSL_XCVR_TX_CS_DATA_0: + case FSL_XCVR_TX_CS_DATA_1: + case FSL_XCVR_TX_CS_DATA_2: + case FSL_XCVR_TX_CS_DATA_3: + case FSL_XCVR_TX_CS_DATA_4: + case FSL_XCVR_TX_CS_DATA_5: + case FSL_XCVR_DEBUG_REG_0: + case FSL_XCVR_DEBUG_REG_1: + return true; + default: + return false; + } +} + +static bool fsl_xcvr_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_XCVR_EXT_CTRL: + case FSL_XCVR_EXT_IER0: + case FSL_XCVR_EXT_IER1: + case FSL_XCVR_EXT_ISR: + case FSL_XCVR_EXT_ISR_SET: + case FSL_XCVR_EXT_ISR_CLR: + case FSL_XCVR_EXT_ISR_TOG: + case FSL_XCVR_IER: + case FSL_XCVR_ISR_SET: + case FSL_XCVR_ISR_CLR: + case FSL_XCVR_ISR_TOG: + case FSL_XCVR_PHY_AI_CTRL: + case FSL_XCVR_PHY_AI_CTRL_SET: + case FSL_XCVR_PHY_AI_CTRL_CLR: + case FSL_XCVR_PHY_AI_CTRL_TOG: + case FSL_XCVR_PHY_AI_WDATA: + case FSL_XCVR_CLK_CTRL: + case FSL_XCVR_RX_DPTH_CTRL: + case FSL_XCVR_RX_DPTH_CTRL_SET: + case FSL_XCVR_RX_DPTH_CTRL_CLR: + case FSL_XCVR_RX_DPTH_CTRL_TOG: + case FSL_XCVR_TX_DPTH_CTRL_SET: + case FSL_XCVR_TX_DPTH_CTRL_CLR: + case FSL_XCVR_TX_DPTH_CTRL_TOG: + case FSL_XCVR_TX_CS_DATA_0: + case FSL_XCVR_TX_CS_DATA_1: + case FSL_XCVR_TX_CS_DATA_2: + case FSL_XCVR_TX_CS_DATA_3: + case FSL_XCVR_TX_CS_DATA_4: + case FSL_XCVR_TX_CS_DATA_5: + return true; + default: + return false; + } +} + +static bool fsl_xcvr_volatile_reg(struct device *dev, unsigned int reg) +{ + return fsl_xcvr_readable_reg(dev, reg); +} + +static const struct regmap_config fsl_xcvr_regmap_cfg = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = FSL_XCVR_MAX_REG, + .reg_defaults = fsl_xcvr_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_xcvr_reg_defaults), + .readable_reg = fsl_xcvr_readable_reg, + .volatile_reg = fsl_xcvr_volatile_reg, + .writeable_reg = fsl_xcvr_writeable_reg, + .cache_type = REGCACHE_FLAT, +}; + +static irqreturn_t irq0_isr(int irq, void *devid) +{ + struct fsl_xcvr *xcvr = (struct fsl_xcvr *)devid; + struct device *dev = &xcvr->pdev->dev; + struct regmap *regmap = xcvr->regmap; + void __iomem *reg_ctrl, *reg_buff; + u32 isr, isr_clr = 0, val, i; + + regmap_read(regmap, FSL_XCVR_EXT_ISR, &isr); + + if (isr & FSL_XCVR_IRQ_NEW_CS) { + dev_dbg(dev, "Received new CS block\n"); + isr_clr |= FSL_XCVR_IRQ_NEW_CS; + /* Data RAM is 4KiB, last two pages: 8 and 9. Select page 8. */ + regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_PAGE_MASK, + FSL_XCVR_EXT_CTRL_PAGE(8)); + + /* Find updated CS buffer */ + reg_ctrl = xcvr->ram_addr + FSL_XCVR_RX_CS_CTRL_0; + reg_buff = xcvr->ram_addr + FSL_XCVR_RX_CS_BUFF_0; + memcpy_fromio(&val, reg_ctrl, sizeof(val)); + if (!val) { + reg_ctrl = xcvr->ram_addr + FSL_XCVR_RX_CS_CTRL_1; + reg_buff = xcvr->ram_addr + FSL_XCVR_RX_CS_BUFF_1; + memcpy_fromio(&val, reg_ctrl, sizeof(val)); + } + + if (val) { + /* copy CS buffer */ + memcpy_fromio(&xcvr->rx_iec958.status, reg_buff, + sizeof(xcvr->rx_iec958.status)); + for (i = 0; i < 6; i++) { + val = *(u32 *)(xcvr->rx_iec958.status + i*4); + *(u32 *)(xcvr->rx_iec958.status + i*4) = + bitrev32(val); + } + /* clear CS control register */ + memset_io(reg_ctrl, 0, sizeof(val)); + } + } + if (isr & FSL_XCVR_IRQ_NEW_UD) { + dev_dbg(dev, "Received new UD block\n"); + isr_clr |= FSL_XCVR_IRQ_NEW_UD; + } + if (isr & FSL_XCVR_IRQ_MUTE) { + dev_dbg(dev, "HW mute bit detected\n"); + isr_clr |= FSL_XCVR_IRQ_MUTE; + } + if (isr & FSL_XCVR_IRQ_FIFO_UOFL_ERR) { + dev_dbg(dev, "RX/TX FIFO full/empty\n"); + isr_clr |= FSL_XCVR_IRQ_FIFO_UOFL_ERR; + } + if (isr & FSL_XCVR_IRQ_ARC_MODE) { + dev_dbg(dev, "CMDC SM falls out of eARC mode\n"); + isr_clr |= FSL_XCVR_IRQ_ARC_MODE; + } + if (isr & FSL_XCVR_IRQ_DMA_RD_REQ) { + dev_dbg(dev, "DMA read request\n"); + isr_clr |= FSL_XCVR_IRQ_DMA_RD_REQ; + } + if (isr & FSL_XCVR_IRQ_DMA_WR_REQ) { + dev_dbg(dev, "DMA write request\n"); + isr_clr |= FSL_XCVR_IRQ_DMA_WR_REQ; + } + + if (isr_clr) { + regmap_write(regmap, FSL_XCVR_EXT_ISR_CLR, isr_clr); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static const struct fsl_xcvr_soc_data fsl_xcvr_imx8mp_data = { + .fw_name = "imx/xcvr/xcvr-imx8mp.bin", +}; + +static const struct of_device_id fsl_xcvr_dt_ids[] = { + { .compatible = "fsl,imx8mp-xcvr", .data = &fsl_xcvr_imx8mp_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_xcvr_dt_ids); + +static int fsl_xcvr_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + struct fsl_xcvr *xcvr; + struct resource *ram_res, *regs_res, *rx_res, *tx_res; + void __iomem *regs; + int ret, irq; + + of_id = of_match_device(fsl_xcvr_dt_ids, dev); + if (!of_id) + return -EINVAL; + + xcvr = devm_kzalloc(dev, sizeof(*xcvr), GFP_KERNEL); + if (!xcvr) + return -ENOMEM; + + xcvr->pdev = pdev; + xcvr->soc_data = of_device_get_match_data(&pdev->dev); + + xcvr->ipg_clk = devm_clk_get(dev, "ipg"); + if (IS_ERR(xcvr->ipg_clk)) { + dev_err(dev, "failed to get ipg clock\n"); + return PTR_ERR(xcvr->ipg_clk); + } + + xcvr->phy_clk = devm_clk_get(dev, "phy"); + if (IS_ERR(xcvr->phy_clk)) { + dev_err(dev, "failed to get phy clock\n"); + return PTR_ERR(xcvr->phy_clk); + } + + xcvr->spba_clk = devm_clk_get(dev, "spba"); + if (IS_ERR(xcvr->spba_clk)) { + dev_err(dev, "failed to get spba clock\n"); + return PTR_ERR(xcvr->spba_clk); + } + + xcvr->pll_ipg_clk = devm_clk_get(dev, "pll_ipg"); + if (IS_ERR(xcvr->pll_ipg_clk)) { + dev_err(dev, "failed to get pll_ipg clock\n"); + return PTR_ERR(xcvr->pll_ipg_clk); + } + + ram_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ram"); + xcvr->ram_addr = devm_ioremap_resource(dev, ram_res); + if (IS_ERR(xcvr->ram_addr)) + return PTR_ERR(xcvr->ram_addr); + + regs_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); + regs = devm_ioremap_resource(dev, regs_res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + xcvr->regmap = devm_regmap_init_mmio_clk(dev, NULL, regs, + &fsl_xcvr_regmap_cfg); + if (IS_ERR(xcvr->regmap)) { + dev_err(dev, "failed to init XCVR regmap: %ld\n", + PTR_ERR(xcvr->regmap)); + return PTR_ERR(xcvr->regmap); + } + + xcvr->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(xcvr->reset)) { + dev_err(dev, "failed to get XCVR reset control\n"); + return PTR_ERR(xcvr->reset); + } + + /* get IRQs */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "no irq[0]: %d\n", irq); + return irq; + } + + ret = devm_request_irq(dev, irq, irq0_isr, 0, pdev->name, xcvr); + if (ret) { + dev_err(dev, "failed to claim IRQ0: %i\n", ret); + return ret; + } + + rx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rxfifo"); + tx_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "txfifo"); + xcvr->dma_prms_rx.chan_name = "rx"; + xcvr->dma_prms_tx.chan_name = "tx"; + xcvr->dma_prms_rx.addr = rx_res->start; + xcvr->dma_prms_tx.addr = tx_res->start; + xcvr->dma_prms_rx.maxburst = FSL_XCVR_MAXBURST_RX; + xcvr->dma_prms_tx.maxburst = FSL_XCVR_MAXBURST_TX; + + platform_set_drvdata(pdev, xcvr); + pm_runtime_enable(dev); + regcache_cache_only(xcvr->regmap, true); + + ret = devm_snd_soc_register_component(dev, &fsl_xcvr_comp, + &fsl_xcvr_dai, 1); + if (ret) { + dev_err(dev, "failed to register component %s\n", + fsl_xcvr_comp.name); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0); + if (ret) + dev_err(dev, "failed to pcm register\n"); + + return ret; +} + +static __maybe_unused int fsl_xcvr_runtime_suspend(struct device *dev) +{ + struct fsl_xcvr *xcvr = dev_get_drvdata(dev); + int ret; + + /* Assert M0+ reset */ + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_CORE_RESET, + FSL_XCVR_EXT_CTRL_CORE_RESET); + if (ret < 0) + dev_err(dev, "Failed to assert M0+ core: %d\n", ret); + + ret = reset_control_assert(xcvr->reset); + if (ret < 0) + dev_err(dev, "Failed to assert M0+ reset: %d\n", ret); + + regcache_cache_only(xcvr->regmap, true); + + clk_disable_unprepare(xcvr->spba_clk); + clk_disable_unprepare(xcvr->phy_clk); + clk_disable_unprepare(xcvr->pll_ipg_clk); + clk_disable_unprepare(xcvr->ipg_clk); + + return 0; +} + +static __maybe_unused int fsl_xcvr_runtime_resume(struct device *dev) +{ + struct fsl_xcvr *xcvr = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(xcvr->ipg_clk); + if (ret) { + dev_err(dev, "failed to start IPG clock.\n"); + return ret; + } + + ret = clk_prepare_enable(xcvr->pll_ipg_clk); + if (ret) { + dev_err(dev, "failed to start PLL IPG clock.\n"); + goto stop_ipg_clk; + } + + ret = clk_prepare_enable(xcvr->phy_clk); + if (ret) { + dev_err(dev, "failed to start PHY clock: %d\n", ret); + goto stop_pll_ipg_clk; + } + + ret = clk_prepare_enable(xcvr->spba_clk); + if (ret) { + dev_err(dev, "failed to start SPBA clock.\n"); + goto stop_phy_clk; + } + + regcache_cache_only(xcvr->regmap, false); + regcache_mark_dirty(xcvr->regmap); + ret = regcache_sync(xcvr->regmap); + + if (ret) { + dev_err(dev, "failed to sync regcache.\n"); + goto stop_spba_clk; + } + + ret = reset_control_deassert(xcvr->reset); + if (ret) { + dev_err(dev, "failed to deassert M0+ reset.\n"); + goto stop_spba_clk; + } + + ret = fsl_xcvr_load_firmware(xcvr); + if (ret) { + dev_err(dev, "failed to load firmware.\n"); + goto stop_spba_clk; + } + + /* Release M0+ reset */ + ret = regmap_update_bits(xcvr->regmap, FSL_XCVR_EXT_CTRL, + FSL_XCVR_EXT_CTRL_CORE_RESET, 0); + if (ret < 0) { + dev_err(dev, "M0+ core release failed: %d\n", ret); + goto stop_spba_clk; + } + + /* Let M0+ core complete firmware initialization */ + msleep(50); + + return 0; + +stop_spba_clk: + clk_disable_unprepare(xcvr->spba_clk); +stop_phy_clk: + clk_disable_unprepare(xcvr->phy_clk); +stop_pll_ipg_clk: + clk_disable_unprepare(xcvr->pll_ipg_clk); +stop_ipg_clk: + clk_disable_unprepare(xcvr->ipg_clk); + + return ret; +} + +static const struct dev_pm_ops fsl_xcvr_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_xcvr_runtime_suspend, + fsl_xcvr_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver fsl_xcvr_driver = { + .probe = fsl_xcvr_probe, + .driver = { + .name = "fsl,imx8mp-audio-xcvr", + .pm = &fsl_xcvr_pm_ops, + .of_match_table = fsl_xcvr_dt_ids, + }, +}; +module_platform_driver(fsl_xcvr_driver); + +MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); +MODULE_DESCRIPTION("NXP Audio Transceiver (XCVR) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_xcvr.h b/sound/soc/fsl/fsl_xcvr.h new file mode 100644 index 000000000000..7f2853c60085 --- /dev/null +++ b/sound/soc/fsl/fsl_xcvr.h @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * NXP XCVR ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright 2019 NXP + */ + +#ifndef __FSL_XCVR_H +#define __FSL_XCVR_H + +#define FSL_XCVR_MODE_SPDIF 0 +#define FSL_XCVR_MODE_ARC 1 +#define FSL_XCVR_MODE_EARC 2 + +/* XCVR Registers */ +#define FSL_XCVR_REG_OFFSET 0x800 /* regs offset */ +#define FSL_XCVR_FIFO_SIZE 0x80 /* 128 */ +#define FSL_XCVR_FIFO_WMK_RX (FSL_XCVR_FIFO_SIZE >> 1) /* 64 */ +#define FSL_XCVR_FIFO_WMK_TX (FSL_XCVR_FIFO_SIZE >> 1) /* 64 */ +#define FSL_XCVR_MAXBURST_RX (FSL_XCVR_FIFO_WMK_RX >> 2) /* 16 */ +#define FSL_XCVR_MAXBURST_TX (FSL_XCVR_FIFO_WMK_TX >> 2) /* 16 */ + +#define FSL_XCVR_RX_FIFO_ADDR 0x0C00 +#define FSL_XCVR_TX_FIFO_ADDR 0x0E00 + +#define FSL_XCVR_VERSION 0x00 /* Version */ +#define FSL_XCVR_EXT_CTRL 0x10 /* Control */ +#define FSL_XCVR_EXT_STATUS 0x20 /* Status */ +#define FSL_XCVR_EXT_IER0 0x30 /* Interrupt en 0 */ +#define FSL_XCVR_EXT_IER1 0x40 /* Interrupt en 1 */ +#define FSL_XCVR_EXT_ISR 0x50 /* Interrupt status */ +#define FSL_XCVR_EXT_ISR_SET 0x54 /* Interrupt status */ +#define FSL_XCVR_EXT_ISR_CLR 0x58 /* Interrupt status */ +#define FSL_XCVR_EXT_ISR_TOG 0x5C /* Interrupt status */ +#define FSL_XCVR_IER 0x70 /* Interrupt en for M0+ */ +#define FSL_XCVR_ISR 0x80 /* Interrupt status */ +#define FSL_XCVR_ISR_SET 0x84 /* Interrupt status set */ +#define FSL_XCVR_ISR_CLR 0x88 /* Interrupt status clear */ +#define FSL_XCVR_ISR_TOG 0x8C /* Interrupt status toggle */ +#define FSL_XCVR_PHY_AI_CTRL 0x90 +#define FSL_XCVR_PHY_AI_CTRL_SET 0x94 +#define FSL_XCVR_PHY_AI_CTRL_CLR 0x98 +#define FSL_XCVR_PHY_AI_CTRL_TOG 0x9C +#define FSL_XCVR_PHY_AI_WDATA 0xA0 +#define FSL_XCVR_PHY_AI_RDATA 0xA4 +#define FSL_XCVR_CLK_CTRL 0xB0 +#define FSL_XCVR_RX_DPTH_CTRL 0x180 /* RX datapath ctrl reg */ +#define FSL_XCVR_RX_DPTH_CTRL_SET 0x184 +#define FSL_XCVR_RX_DPTH_CTRL_CLR 0x188 +#define FSL_XCVR_RX_DPTH_CTRL_TOG 0x18c + +#define FSL_XCVR_TX_DPTH_CTRL 0x220 /* TX datapath ctrl reg */ +#define FSL_XCVR_TX_DPTH_CTRL_SET 0x224 +#define FSL_XCVR_TX_DPTH_CTRL_CLR 0x228 +#define FSL_XCVR_TX_DPTH_CTRL_TOG 0x22C +#define FSL_XCVR_TX_CS_DATA_0 0x230 /* TX channel status bits regs */ +#define FSL_XCVR_TX_CS_DATA_1 0x234 +#define FSL_XCVR_TX_CS_DATA_2 0x238 +#define FSL_XCVR_TX_CS_DATA_3 0x23C +#define FSL_XCVR_TX_CS_DATA_4 0x240 +#define FSL_XCVR_TX_CS_DATA_5 0x244 +#define FSL_XCVR_DEBUG_REG_0 0x2E0 +#define FSL_XCVR_DEBUG_REG_1 0x2F0 + +#define FSL_XCVR_MAX_REG FSL_XCVR_DEBUG_REG_1 + +#define FSL_XCVR_EXT_CTRL_CORE_RESET BIT(31) + +#define FSL_XCVR_EXT_CTRL_RX_CMDC_RESET BIT(30) +#define FSL_XCVR_EXT_CTRL_TX_CMDC_RESET BIT(29) +#define FSL_XCVR_EXT_CTRL_CMDC_RESET(t) (t ? BIT(29) : BIT(30)) + +#define FSL_XCVR_EXT_CTRL_RX_DPTH_RESET BIT(28) +#define FSL_XCVR_EXT_CTRL_TX_DPTH_RESET BIT(27) +#define FSL_XCVR_EXT_CTRL_DPTH_RESET(t) (t ? BIT(27) : BIT(28)) + +#define FSL_XCVR_EXT_CTRL_TX_RX_MODE BIT(26) +#define FSL_XCVR_EXT_CTRL_DMA_RD_DIS BIT(25) +#define FSL_XCVR_EXT_CTRL_DMA_WR_DIS BIT(24) +#define FSL_XCVR_EXT_CTRL_DMA_DIS(t) (t ? BIT(24) : BIT(25)) +#define FSL_XCVR_EXT_CTRL_SPDIF_MODE BIT(23) +#define FSL_XCVR_EXT_CTRL_SLEEP_MODE BIT(21) + +#define FSL_XCVR_EXT_CTRL_TX_FWM_SHFT 0 +#define FSL_XCVR_EXT_CTRL_TX_FWM_MASK GENMASK(6, 0) +#define FSL_XCVR_EXT_CTRL_TX_FWM(i) (((i) << FSL_XCVR_EXT_CTRL_TX_FWM_SHFT) \ + & FSL_XCVR_EXT_CTRL_TX_FWM_MASK) +#define FSL_XCVR_EXT_CTRL_RX_FWM_SHFT 8 +#define FSL_XCVR_EXT_CTRL_RX_FWM_MASK GENMASK(14, 8) +#define FSL_XCVR_EXT_CTRL_RX_FWM(i) (((i) << FSL_XCVR_EXT_CTRL_RX_FWM_SHFT) \ + & FSL_XCVR_EXT_CTRL_RX_FWM_MASK) +#define FSL_XCVR_EXT_CTRL_PAGE_SHFT 16 +#define FSL_XCVR_EXT_CTRL_PAGE_MASK GENMASK(19, 16) +#define FSL_XCVR_EXT_CTRL_PAGE(i) (((i) << FSL_XCVR_EXT_CTRL_PAGE_SHFT) \ + & FSL_XCVR_EXT_CTRL_PAGE_MASK) + +#define FSL_XCVR_EXT_STUS_NT_FIFO_ENTR GENMASK(7, 0) +#define FSL_XCVR_EXT_STUS_NR_FIFO_ENTR GENMASK(15, 8) +#define FSL_XCVR_EXT_STUS_CM0_SLEEPING BIT(16) +#define FSL_XCVR_EXT_STUS_CM0_DEEP_SLP BIT(17) +#define FSL_XCVR_EXT_STUS_CM0_SLP_HACK BIT(18) +#define FSL_XCVR_EXT_STUS_RX_CMDC_RSTO BIT(23) +#define FSL_XCVR_EXT_STUS_TX_CMDC_RSTO BIT(24) +#define FSL_XCVR_EXT_STUS_RX_CMDC_COTO BIT(25) +#define FSL_XCVR_EXT_STUS_TX_CMDC_COTO BIT(26) +#define FSL_XCVR_EXT_STUS_HB_STATUS BIT(27) +#define FSL_XCVR_EXT_STUS_NEW_UD4_REC BIT(28) +#define FSL_XCVR_EXT_STUS_NEW_UD5_REC BIT(29) +#define FSL_XCVR_EXT_STUS_NEW_UD6_REC BIT(30) +#define FSL_XCVR_EXT_STUS_HPD_INPUT BIT(31) + +#define FSL_XCVR_IRQ_NEW_CS BIT(0) +#define FSL_XCVR_IRQ_NEW_UD BIT(1) +#define FSL_XCVR_IRQ_MUTE BIT(2) +#define FSL_XCVR_IRQ_CMDC_RESP_TO BIT(3) +#define FSL_XCVR_IRQ_ECC_ERR BIT(4) +#define FSL_XCVR_IRQ_PREAMBLE_MISMATCH BIT(5) +#define FSL_XCVR_IRQ_FIFO_UOFL_ERR BIT(6) +#define FSL_XCVR_IRQ_HOST_WAKEUP BIT(7) +#define FSL_XCVR_IRQ_HOST_OHPD BIT(8) +#define FSL_XCVR_IRQ_DMAC_NO_DATA_REC BIT(9) +#define FSL_XCVR_IRQ_DMAC_FMT_CHG_DET BIT(10) +#define FSL_XCVR_IRQ_HB_STATE_CHG BIT(11) +#define FSL_XCVR_IRQ_CMDC_STATUS_UPD BIT(12) +#define FSL_XCVR_IRQ_TEMP_UPD BIT(13) +#define FSL_XCVR_IRQ_DMA_RD_REQ BIT(14) +#define FSL_XCVR_IRQ_DMA_WR_REQ BIT(15) +#define FSL_XCVR_IRQ_DMAC_BME_BIT_ERR BIT(16) +#define FSL_XCVR_IRQ_PREAMBLE_MATCH BIT(17) +#define FSL_XCVR_IRQ_M_W_PRE_MISMATCH BIT(18) +#define FSL_XCVR_IRQ_B_PRE_MISMATCH BIT(19) +#define FSL_XCVR_IRQ_UNEXP_PRE_REC BIT(20) +#define FSL_XCVR_IRQ_ARC_MODE BIT(21) +#define FSL_XCVR_IRQ_CH_UD_OFLOW BIT(22) +#define FSL_XCVR_IRQ_EARC_ALL (FSL_XCVR_IRQ_NEW_CS | \ + FSL_XCVR_IRQ_NEW_UD | \ + FSL_XCVR_IRQ_MUTE | \ + FSL_XCVR_IRQ_FIFO_UOFL_ERR | \ + FSL_XCVR_IRQ_HOST_WAKEUP | \ + FSL_XCVR_IRQ_ARC_MODE) + +#define FSL_XCVR_ISR_CMDC_TX_EN BIT(3) +#define FSL_XCVR_ISR_HPD_TGL BIT(15) +#define FSL_XCVR_ISR_DMAC_SPARE_INT BIT(19) +#define FSL_XCVR_ISR_SET_SPDIF_RX_INT BIT(20) +#define FSL_XCVR_ISR_SET_SPDIF_TX_INT BIT(21) +#define FSL_XCVR_ISR_SET_SPDIF_MODE(t) (t ? BIT(21) : BIT(20)) +#define FSL_XCVR_ISR_SET_ARC_CM_INT BIT(22) +#define FSL_XCVR_ISR_SET_ARC_SE_INT BIT(23) + +#define FSL_XCVR_PHY_AI_ADDR_MASK GENMASK(7, 0) +#define FSL_XCVR_PHY_AI_RESETN BIT(15) +#define FSL_XCVR_PHY_AI_TOG_PLL BIT(24) +#define FSL_XCVR_PHY_AI_TOG_DONE_PLL BIT(25) +#define FSL_XCVR_PHY_AI_TOG_PHY BIT(26) +#define FSL_XCVR_PHY_AI_TOG_DONE_PHY BIT(27) +#define FSL_XCVR_PHY_AI_RW_MASK BIT(31) + +#define FSL_XCVR_RX_DPTH_CTRL_PAPB_FIFO_STATUS BIT(0) +#define FSL_XCVR_RX_DPTH_CTRL_DIS_PRE_ERR_CHK BIT(1) +#define FSL_XCVR_RX_DPTH_CTRL_DIS_NOD_REC_CHK BIT(2) +#define FSL_XCVR_RX_DPTH_CTRL_ECC_VUC_BIT_CHK BIT(3) +#define FSL_XCVR_RX_DPTH_CTRL_EN_CMP_PAR_CALC BIT(4) +#define FSL_XCVR_RX_DPTH_CTRL_RST_PKT_CNT_FIFO BIT(5) +#define FSL_XCVR_RX_DPTH_CTRL_STORE_FMT BIT(6) +#define FSL_XCVR_RX_DPTH_CTRL_EN_PAR_CALC BIT(7) +#define FSL_XCVR_RX_DPTH_CTRL_UDR BIT(8) +#define FSL_XCVR_RX_DPTH_CTRL_CSR BIT(9) +#define FSL_XCVR_RX_DPTH_CTRL_UDA BIT(10) +#define FSL_XCVR_RX_DPTH_CTRL_CSA BIT(11) +#define FSL_XCVR_RX_DPTH_CTRL_CLR_RX_FIFO BIT(12) +#define FSL_XCVR_RX_DPTH_CTRL_DIS_B_PRE_ERR_CHK BIT(13) +#define FSL_XCVR_RX_DPTH_CTRL_PABS BIT(19) +#define FSL_XCVR_RX_DPTH_CTRL_DTS_CDS BIT(20) +#define FSL_XCVR_RX_DPTH_CTRL_BLKC BIT(21) +#define FSL_XCVR_RX_DPTH_CTRL_MUTE_CTRL BIT(22) +#define FSL_XCVR_RX_DPTH_CTRL_MUTE_MODE BIT(23) +#define FSL_XCVR_RX_DPTH_CTRL_FMT_CHG_CTRL BIT(24) +#define FSL_XCVR_RX_DPTH_CTRL_FMT_CHG_MODE BIT(25) +#define FSL_XCVR_RX_DPTH_CTRL_LAYB_CTRL BIT(26) +#define FSL_XCVR_RX_DPTH_CTRL_LAYB_MODE BIT(27) +#define FSL_XCVR_RX_DPTH_CTRL_PRC BIT(28) +#define FSL_XCVR_RX_DPTH_CTRL_COMP BIT(29) +#define FSL_XCVR_RX_DPTH_CTRL_FSM GENMASK(31, 30) + +#define FSL_XCVR_TX_DPTH_CTRL_CS_ACK BIT(0) +#define FSL_XCVR_TX_DPTH_CTRL_UD_ACK BIT(1) +#define FSL_XCVR_TX_DPTH_CTRL_CS_MOD BIT(2) +#define FSL_XCVR_TX_DPTH_CTRL_UD_MOD BIT(3) +#define FSL_XCVR_TX_DPTH_CTRL_VLD_MOD BIT(4) +#define FSL_XCVR_TX_DPTH_CTRL_FRM_VLD BIT(5) +#define FSL_XCVR_TX_DPTH_CTRL_EN_PARITY BIT(6) +#define FSL_XCVR_TX_DPTH_CTRL_EN_PREAMBLE BIT(7) +#define FSL_XCVR_TX_DPTH_CTRL_EN_ECC_INTER BIT(8) +#define FSL_XCVR_TX_DPTH_CTRL_BYPASS_FEM BIT(10) +#define FSL_XCVR_TX_DPTH_CTRL_FRM_FMT BIT(11) +#define FSL_XCVR_TX_DPTH_CTRL_STRT_DATA_TX BIT(14) +#define FSL_XCVR_TX_DPTH_CTRL_ADD_CYC_TX_OE_STR BIT(15) +#define FSL_XCVR_TX_DPTH_CTRL_ADD_CYC_TX_OE_END BIT(16) +#define FSL_XCVR_TX_DPTH_CTRL_CLK_RATIO BIT(29) +#define FSL_XCVR_TX_DPTH_CTRL_TM_NO_PRE_BME GENMASK(31, 30) + +#define FSL_XCVR_PHY_AI_CTRL_AI_RESETN BIT(15) + +#define FSL_XCVR_PLL_CTRL0 0x00 +#define FSL_XCVR_PLL_CTRL0_SET 0x04 +#define FSL_XCVR_PLL_CTRL0_CLR 0x08 +#define FSL_XCVR_PLL_NUM 0x20 +#define FSL_XCVR_PLL_DEN 0x30 +#define FSL_XCVR_PLL_PDIV 0x40 +#define FSL_XCVR_PLL_BANDGAP_SET 0x54 +#define FSL_XCVR_PHY_CTRL 0x00 +#define FSL_XCVR_PHY_CTRL_SET 0x04 +#define FSL_XCVR_PHY_CTRL_CLR 0x08 +#define FSL_XCVR_PHY_CTRL2 0x70 +#define FSL_XCVR_PHY_CTRL2_SET 0x74 +#define FSL_XCVR_PHY_CTRL2_CLR 0x78 + +#define FSL_XCVR_PLL_BANDGAP_EN_VBG BIT(0) +#define FSL_XCVR_PLL_CTRL0_HROFF BIT(13) +#define FSL_XCVR_PLL_CTRL0_PWP BIT(14) +#define FSL_XCVR_PLL_CTRL0_CM0_EN BIT(24) +#define FSL_XCVR_PLL_CTRL0_CM1_EN BIT(25) +#define FSL_XCVR_PLL_CTRL0_CM2_EN BIT(26) +#define FSL_XCVR_PLL_PDIVx(v, i) ((v & 0x7) << (4 * i)) + +#define FSL_XCVR_PHY_CTRL_PHY_EN BIT(0) +#define FSL_XCVR_PHY_CTRL_RX_CM_EN BIT(1) +#define FSL_XCVR_PHY_CTRL_TSDIFF_OE BIT(5) +#define FSL_XCVR_PHY_CTRL_SPDIF_EN BIT(8) +#define FSL_XCVR_PHY_CTRL_ARC_MODE_SE_EN BIT(9) +#define FSL_XCVR_PHY_CTRL_ARC_MODE_CM_EN BIT(10) +#define FSL_XCVR_PHY_CTRL_TX_CLK_MASK GENMASK(26, 25) +#define FSL_XCVR_PHY_CTRL_TX_CLK_HDMI_SS BIT(25) +#define FSL_XCVR_PHY_CTRL_TX_CLK_AUD_SS BIT(26) +#define FSL_XCVR_PHY_CTRL2_EARC_TXMS BIT(14) + +#define FSL_XCVR_CS_DATA_0_FS_MASK GENMASK(31, 24) +#define FSL_XCVR_CS_DATA_0_FS_32000 0x3000000 +#define FSL_XCVR_CS_DATA_0_FS_44100 0x0000000 +#define FSL_XCVR_CS_DATA_0_FS_48000 0x2000000 +#define FSL_XCVR_CS_DATA_0_FS_64000 0xB000000 +#define FSL_XCVR_CS_DATA_0_FS_88200 0x8000000 +#define FSL_XCVR_CS_DATA_0_FS_96000 0xA000000 +#define FSL_XCVR_CS_DATA_0_FS_176400 0xC000000 +#define FSL_XCVR_CS_DATA_0_FS_192000 0xE000000 + +#define FSL_XCVR_CS_DATA_0_CH_MASK 0x3A +#define FSL_XCVR_CS_DATA_0_CH_U2LPCM 0x00 +#define FSL_XCVR_CS_DATA_0_CH_UMLPCM 0x20 +#define FSL_XCVR_CS_DATA_0_CH_U1BAUD 0x30 + +#define FSL_XCVR_CS_DATA_1_CH_MASK 0xF000 +#define FSL_XCVR_CS_DATA_1_CH_2 0x0000 +#define FSL_XCVR_CS_DATA_1_CH_8 0x7000 +#define FSL_XCVR_CS_DATA_1_CH_16 0xB000 +#define FSL_XCVR_CS_DATA_1_CH_32 0x3000 + +/* Data memory structures */ +#define FSL_XCVR_RX_CS_CTRL_0 0x20 /* First RX CS control register */ +#define FSL_XCVR_RX_CS_CTRL_1 0x24 /* Second RX CS control register */ +#define FSL_XCVR_RX_CS_BUFF_0 0x80 /* First RX CS buffer */ +#define FSL_XCVR_RX_CS_BUFF_1 0xA0 /* Second RX CS buffer */ +#define FSL_XCVR_CAP_DATA_STR 0x300 /* Capabilities data structure */ + +#endif /* __FSL_XCVR_H */ diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index 688b5e0a49e3..64d3fc4a3225 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -143,7 +143,7 @@ static int byt_rt5651_prepare_and_enable_pll1(struct snd_soc_dai *codec_dai, /* Configure the PLL before selecting it */ if (!(byt_rt5651_quirk & BYT_RT5651_MCLK_EN)) { - clk_id = RT5651_PLL1_S_BCLK1, + clk_id = RT5651_PLL1_S_BCLK1; clk_freq = rate * bclk_ratio; } else { clk_id = RT5651_PLL1_S_MCLK; diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index c7bd20104b20..e8bc7ca5ee5e 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -26,9 +26,6 @@ #include "jz4740-i2s.h" -#define JZ4740_DMA_TYPE_AIC_TRANSMIT 24 -#define JZ4740_DMA_TYPE_AIC_RECEIVE 25 - #define JZ_REG_AIC_CONF 0x00 #define JZ_REG_AIC_CTRL 0x04 #define JZ_REG_AIC_I2S_FMT 0x10 @@ -377,13 +374,11 @@ static void jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s) /* Playback */ dma_data = &i2s->playback_dma_data; dma_data->maxburst = 16; - dma_data->slave_id = JZ4740_DMA_TYPE_AIC_TRANSMIT; dma_data->addr = i2s->phys_base + JZ_REG_AIC_FIFO; /* Capture */ dma_data = &i2s->capture_dma_data; dma_data->maxburst = 16; - dma_data->slave_id = JZ4740_DMA_TYPE_AIC_RECEIVE; dma_data->addr = i2s->phys_base + JZ_REG_AIC_FIFO; } diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 2696ffcba880..484cad31da25 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -127,4 +127,15 @@ config SND_SOC_SDM845 SDM845 SoC-based systems. Say Y if you want to use audio device on this SoCs. +config SND_SOC_SM8250 + tristate "SoC Machine driver for SM8250 boards" + depends on QCOM_APR && SOUNDWIRE + depends on COMMON_CLK + select SND_SOC_QDSP6 + select SND_SOC_QCOM_COMMON + help + To add support for audio on Qualcomm Technologies Inc. + SM8250 SoC-based systems. + Say Y if you want to use audio device on this SoCs. + endif #SND_SOC_QCOM diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index 0bd90d74e3db..effa4b3f58fa 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -19,12 +19,14 @@ snd-soc-storm-objs := storm.o snd-soc-apq8016-sbc-objs := apq8016_sbc.o snd-soc-apq8096-objs := apq8096.o snd-soc-sdm845-objs := sdm845.o +snd-soc-sm8250-objs := sm8250.o snd-soc-qcom-common-objs := common.o obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-apq8096.o obj-$(CONFIG_SND_SOC_SDM845) += snd-soc-sdm845.o +obj-$(CONFIG_SND_SOC_SM8250) += snd-soc-sm8250.o obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o #DSP lib diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c new file mode 100644 index 000000000000..7d43de6d909f --- /dev/null +++ b/sound/soc/qcom/sm8250.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020, Linaro Limited + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <linux/soundwire/sdw.h> +#include "qdsp6/q6afe.h" +#include "common.h" + +#define DRIVER_NAME "sm8250" +#define MI2S_BCLK_RATE 1536000 +#define MAX_SDW_STREAMS 16 + +struct sm8250_snd_data { + bool stream_prepared[MAX_SDW_STREAMS]; + struct snd_soc_card *card; + struct sdw_stream_runtime *sruntime[MAX_SDW_STREAMS]; +}; + +static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + return 0; +} + +static int sm8250_snd_startup(struct snd_pcm_substream *substream) +{ + unsigned int codec_dai_fmt = SND_SOC_DAIFMT_CBS_CFS; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + + switch (cpu_dai->id) { + case TERTIARY_MI2S_RX: + codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF; + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT, + MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; + default: + break; + } + return 0; +} + +static int sm8250_snd_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; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sm8250_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); + struct sdw_stream_runtime *sruntime; + int i; + + switch (cpu_dai->id) { + case WSA_CODEC_DMA_RX_0: + for_each_rtd_codec_dais(rtd, i, codec_dai) { + sruntime = snd_soc_dai_get_sdw_stream(codec_dai, + substream->stream); + if (sruntime != ERR_PTR(-ENOTSUPP)) + pdata->sruntime[cpu_dai->id] = sruntime; + } + break; + } + + return 0; + +} + +static int sm8250_snd_wsa_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; + int ret; + + if (!sruntime) + return 0; + + if (data->stream_prepared[cpu_dai->id]) { + sdw_disable_stream(sruntime); + sdw_deprepare_stream(sruntime); + data->stream_prepared[cpu_dai->id] = false; + } + + ret = sdw_prepare_stream(sruntime); + if (ret) + return ret; + + /** + * NOTE: there is a strict hw requirement about the ordering of port + * enables and actual WSA881x PA enable. PA enable should only happen + * after soundwire ports are enabled if not DC on the line is + * accumulated resulting in Click/Pop Noise + * PA enable/mute are handled as part of codec DAPM and digital mute. + */ + + ret = sdw_enable_stream(sruntime); + if (ret) { + sdw_deprepare_stream(sruntime); + return ret; + } + data->stream_prepared[cpu_dai->id] = true; + + return ret; +} + +static int sm8250_snd_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + + switch (cpu_dai->id) { + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + return sm8250_snd_wsa_dma_prepare(substream); + default: + break; + } + + return 0; +} + +static int sm8250_snd_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; + + switch (cpu_dai->id) { + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + if (sruntime && data->stream_prepared[cpu_dai->id]) { + sdw_disable_stream(sruntime); + sdw_deprepare_stream(sruntime); + data->stream_prepared[cpu_dai->id] = false; + } + break; + default: + break; + } + + return 0; +} + +static const struct snd_soc_ops sm8250_be_ops = { + .startup = sm8250_snd_startup, + .hw_params = sm8250_snd_hw_params, + .hw_free = sm8250_snd_hw_free, + .prepare = sm8250_snd_prepare, +}; + +static void sm8250_add_be_ops(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link; + int i; + + for_each_card_prelinks(card, i, link) { + if (link->no_pcm == 1) { + link->be_hw_params_fixup = sm8250_be_hw_params_fixup; + link->ops = &sm8250_be_ops; + } + } +} + +static int sm8250_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct sm8250_snd_data *data; + struct device *dev = &pdev->dev; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + /* Allocate the private data */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + card->dev = dev; + dev_set_drvdata(dev, card); + snd_soc_card_set_drvdata(card, data); + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + + card->driver_name = DRIVER_NAME; + sm8250_add_be_ops(card); + return devm_snd_soc_register_card(dev, card); +} + +static const struct of_device_id snd_sm8250_dt_match[] = { + {.compatible = "qcom,sdm8250"}, + {.compatible = "qcom,qrb5165-rb5"}, + {} +}; + +MODULE_DEVICE_TABLE(of, snd_sm8250_dt_match); + +static struct platform_driver snd_sm8250_driver = { + .probe = sm8250_platform_probe, + .driver = { + .name = "snd-sm8250", + .of_match_table = snd_sm8250_dt_match, + }, +}; +module_platform_driver(snd_sm8250_driver); +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("SM8250 ASoC Machine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/samsung/snow.c b/sound/soc/samsung/snow.c index 07163f07c6d5..989af624dd11 100644 --- a/sound/soc/samsung/snow.c +++ b/sound/soc/samsung/snow.c @@ -189,7 +189,7 @@ static int snow_probe(struct platform_device *pdev) return PTR_ERR(priv->clk_i2s_bus); } } else { - link->codecs->dai_name = "HiFi", + link->codecs->dai_name = "HiFi"; link->cpus->of_node = of_parse_phandle(dev->of_node, "samsung,i2s-controller", 0); diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index 728e93f35ffb..6d719c2db92e 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -779,8 +779,7 @@ int snd_soc_pcm_component_prepare(struct snd_pcm_substream *substream) } int snd_soc_pcm_component_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_component **last) + struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_component *component; @@ -790,33 +789,35 @@ int snd_soc_pcm_component_hw_params(struct snd_pcm_substream *substream, if (component->driver->hw_params) { ret = component->driver->hw_params(component, substream, params); - if (ret < 0) { - *last = component; + if (ret < 0) return soc_component_ret(component, ret); - } } + /* mark substream if succeeded */ + soc_component_mark_push(component, substream, hw_params); } - *last = NULL; return 0; } void snd_soc_pcm_component_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_component *last) + int rollback) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_component *component; int i, ret; for_each_rtd_components(rtd, i, component) { - if (component == last) - break; + if (rollback && !soc_component_mark_match(component, substream, hw_params)) + continue; if (component->driver->hw_free) { ret = component->driver->hw_free(component, substream); if (ret < 0) soc_component_ret(component, ret); } + + /* remove marked substream */ + soc_component_mark_pop(component, substream, hw_params); } } diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index 4705c3da6280..2686a566649b 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -335,16 +335,27 @@ int snd_soc_dai_hw_params(struct snd_soc_dai *dai, if (dai->driver->ops && dai->driver->ops->hw_params) ret = dai->driver->ops->hw_params(substream, params, dai); + + /* mark substream if succeeded */ + if (ret == 0) + soc_dai_mark_push(dai, substream, hw_params); end: return soc_dai_ret(dai, ret); } void snd_soc_dai_hw_free(struct snd_soc_dai *dai, - struct snd_pcm_substream *substream) + struct snd_pcm_substream *substream, + int rollback) { + if (rollback && !soc_dai_mark_match(dai, substream, hw_params)) + return; + if (dai->driver->ops && dai->driver->ops->hw_free) dai->driver->ops->hw_free(substream, dai); + + /* remove marked substream */ + soc_dai_mark_pop(dai, substream, hw_params); } int snd_soc_dai_startup(struct snd_soc_dai *dai, diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 980f2c330b87..a6ef160a6af5 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -3955,13 +3955,13 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w, substream->stream = SNDRV_PCM_STREAM_CAPTURE; snd_soc_dapm_widget_for_each_source_path(w, path) { source = path->source->priv; - snd_soc_dai_hw_free(source, substream); + snd_soc_dai_hw_free(source, substream, 0); } substream->stream = SNDRV_PCM_STREAM_PLAYBACK; snd_soc_dapm_widget_for_each_sink_path(w, path) { sink = path->sink->priv; - snd_soc_dai_hw_free(sink, substream); + snd_soc_dai_hw_free(sink, substream, 0); } substream->stream = SNDRV_PCM_STREAM_CAPTURE; @@ -4764,7 +4764,7 @@ void snd_soc_dapm_init(struct snd_soc_dapm_context *dapm, if (component) { dapm->dev = component->dev; - dapm->idle_bias_off = !component->driver->idle_bias_on, + dapm->idle_bias_off = !component->driver->idle_bias_on; dapm->suspend_bias_off = component->driver->suspend_bias_off; } else { dapm->dev = card->dev; diff --git a/sound/soc/soc-link.c b/sound/soc/soc-link.c index 2a8881978930..409ae4940da3 100644 --- a/sound/soc/soc-link.c +++ b/sound/soc/soc-link.c @@ -119,16 +119,26 @@ int snd_soc_link_hw_params(struct snd_pcm_substream *substream, rtd->dai_link->ops->hw_params) ret = rtd->dai_link->ops->hw_params(substream, params); + /* mark substream if succeeded */ + if (ret == 0) + soc_link_mark_push(rtd, substream, hw_params); + return soc_link_ret(rtd, ret); } -void snd_soc_link_hw_free(struct snd_pcm_substream *substream) +void snd_soc_link_hw_free(struct snd_pcm_substream *substream, int rollback) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + if (rollback && !soc_link_mark_match(rtd, substream, hw_params)) + return; + if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free) rtd->dai_link->ops->hw_free(substream); + + /* remove marked substream */ + soc_link_mark_pop(rtd, substream, hw_params); } int snd_soc_link_trigger(struct snd_pcm_substream *substream, int cmd) diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index dcab9527ba3d..17ff3a369631 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -860,6 +860,54 @@ static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params, interval->max = channels; } +static int soc_pcm_hw_clean(struct snd_pcm_substream *substream, int rollback) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *dai; + int i; + + mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); + + /* clear the corresponding DAIs parameters when going to be inactive */ + for_each_rtd_dais(rtd, i, dai) { + int active = snd_soc_dai_stream_active(dai, substream->stream); + + if (snd_soc_dai_active(dai) == 1) { + dai->rate = 0; + dai->channels = 0; + dai->sample_bits = 0; + } + + if (active == 1) + snd_soc_dai_digital_mute(dai, 1, substream->stream); + } + + /* free any machine hw params */ + snd_soc_link_hw_free(substream, rollback); + + /* free any component resources */ + snd_soc_pcm_component_hw_free(substream, rollback); + + /* now free hw params for the DAIs */ + for_each_rtd_dais(rtd, i, dai) { + if (!snd_soc_dai_stream_valid(dai, substream->stream)) + continue; + + snd_soc_dai_hw_free(dai, substream, rollback); + } + + mutex_unlock(&rtd->card->pcm_mutex); + return 0; +} + +/* + * Frees resources allocated by hw_params, can be called multiple times + */ +static int soc_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return soc_pcm_hw_clean(substream, 0); +} + /* * Called by ALSA when the hardware params are set by application. This * function can also be called multiple times and can allocate buffers @@ -869,7 +917,6 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_component *component; struct snd_soc_dai *cpu_dai; struct snd_soc_dai *codec_dai; int i, ret = 0; @@ -921,7 +968,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, ret = snd_soc_dai_hw_params(codec_dai, substream, &codec_params); if(ret < 0) - goto codec_err; + goto out; codec_dai->rate = params_rate(&codec_params); codec_dai->channels = params_channels(&codec_params); @@ -941,7 +988,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, ret = snd_soc_dai_hw_params(cpu_dai, substream, params); if (ret < 0) - goto interface_err; + goto out; /* store the parameters for each DAI */ cpu_dai->rate = params_rate(params); @@ -952,88 +999,16 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, snd_soc_dapm_update_dai(substream, params, cpu_dai); } - ret = snd_soc_pcm_component_hw_params(substream, params, &component); - if (ret < 0) - goto component_err; - + ret = snd_soc_pcm_component_hw_params(substream, params); out: mutex_unlock(&rtd->card->pcm_mutex); - return ret; - -component_err: - snd_soc_pcm_component_hw_free(substream, component); - - i = rtd->num_cpus; - -interface_err: - for_each_rtd_cpu_dais_rollback(rtd, i, cpu_dai) { - if (!snd_soc_dai_stream_valid(cpu_dai, substream->stream)) - continue; - - snd_soc_dai_hw_free(cpu_dai, substream); - cpu_dai->rate = 0; - } - i = rtd->num_codecs; - -codec_err: - for_each_rtd_codec_dais_rollback(rtd, i, codec_dai) { - if (!snd_soc_dai_stream_valid(codec_dai, substream->stream)) - continue; - - snd_soc_dai_hw_free(codec_dai, substream); - codec_dai->rate = 0; - } - - snd_soc_link_hw_free(substream); + if (ret < 0) + soc_pcm_hw_clean(substream, 1); - mutex_unlock(&rtd->card->pcm_mutex); return ret; } -/* - * Frees resources allocated by hw_params, can be called multiple times - */ -static int soc_pcm_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_dai *dai; - int i; - - mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); - - /* clear the corresponding DAIs parameters when going to be inactive */ - for_each_rtd_dais(rtd, i, dai) { - int active = snd_soc_dai_stream_active(dai, substream->stream); - - if (snd_soc_dai_active(dai) == 1) { - dai->rate = 0; - dai->channels = 0; - dai->sample_bits = 0; - } - - if (active == 1) - snd_soc_dai_digital_mute(dai, 1, substream->stream); - } - - /* free any machine hw params */ - snd_soc_link_hw_free(substream); - - /* free any component resources */ - snd_soc_pcm_component_hw_free(substream, NULL); - - /* now free hw params for the DAIs */ - for_each_rtd_dais(rtd, i, dai) { - if (!snd_soc_dai_stream_valid(dai, substream->stream)) - continue; - - snd_soc_dai_hw_free(dai, substream); - } - - mutex_unlock(&rtd->card->pcm_mutex); - return 0; -} - static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { int ret = -EINVAL; diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 18ff1c2f5376..2b001151fe37 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -44,7 +44,7 @@ int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask) reset = HDA_DSP_ADSPCS_CRST_MASK(core_mask); snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS, - reset, reset), + reset, reset); /* poll with timeout to check if operation successful */ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index 9cd7009cb570..69b9d8515335 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -14,6 +14,7 @@ config SND_SUN8I_CODEC tristate "Allwinner SUN8I audio codec" depends on OF depends on MACH_SUN8I || (ARM64 && ARCH_SUNXI) || COMPILE_TEST + select COMMON_CLK select REGMAP_MMIO help This option enables the digital part of the internal audio codec for diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c index 7590c4b04d14..180442c62be1 100644 --- a/sound/soc/sunxi/sun8i-codec.c +++ b/sound/soc/sunxi/sun8i-codec.c @@ -33,24 +33,26 @@ #define SUN8I_SYSCLK_CTL_SYSCLK_SRC_AIF2CLK (0x1 << 0) #define SUN8I_MOD_CLK_ENA 0x010 #define SUN8I_MOD_CLK_ENA_AIF1 15 +#define SUN8I_MOD_CLK_ENA_AIF2 14 +#define SUN8I_MOD_CLK_ENA_AIF3 13 #define SUN8I_MOD_CLK_ENA_ADC 3 #define SUN8I_MOD_CLK_ENA_DAC 2 #define SUN8I_MOD_RST_CTL 0x014 #define SUN8I_MOD_RST_CTL_AIF1 15 +#define SUN8I_MOD_RST_CTL_AIF2 14 +#define SUN8I_MOD_RST_CTL_AIF3 13 #define SUN8I_MOD_RST_CTL_ADC 3 #define SUN8I_MOD_RST_CTL_DAC 2 #define SUN8I_SYS_SR_CTRL 0x018 #define SUN8I_SYS_SR_CTRL_AIF1_FS 12 #define SUN8I_SYS_SR_CTRL_AIF2_FS 8 -#define SUN8I_AIF1CLK_CTRL 0x040 -#define SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD 15 -#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV 14 -#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV 13 -#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV 9 -#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV 6 -#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ 4 -#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_16 (1 << 4) -#define SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT 2 +#define SUN8I_AIF_CLK_CTRL(n) (0x040 * (1 + (n))) +#define SUN8I_AIF_CLK_CTRL_MSTR_MOD 15 +#define SUN8I_AIF_CLK_CTRL_CLK_INV 13 +#define SUN8I_AIF_CLK_CTRL_BCLK_DIV 9 +#define SUN8I_AIF_CLK_CTRL_LRCK_DIV 6 +#define SUN8I_AIF_CLK_CTRL_WORD_SIZ 4 +#define SUN8I_AIF_CLK_CTRL_DATA_FMT 2 #define SUN8I_AIF1_ADCDAT_CTRL 0x044 #define SUN8I_AIF1_ADCDAT_CTRL_AIF1_AD0L_ENA 15 #define SUN8I_AIF1_ADCDAT_CTRL_AIF1_AD0R_ENA 14 @@ -70,6 +72,32 @@ #define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACR 10 #define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_ADCR 9 #define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACL 8 +#define SUN8I_AIF2_ADCDAT_CTRL 0x084 +#define SUN8I_AIF2_ADCDAT_CTRL_AIF2_ADCL_ENA 15 +#define SUN8I_AIF2_ADCDAT_CTRL_AIF2_ADCR_ENA 14 +#define SUN8I_AIF2_ADCDAT_CTRL_AIF2_ADCL_SRC 10 +#define SUN8I_AIF2_ADCDAT_CTRL_AIF2_ADCR_SRC 8 +#define SUN8I_AIF2_DACDAT_CTRL 0x088 +#define SUN8I_AIF2_DACDAT_CTRL_AIF2_DACL_ENA 15 +#define SUN8I_AIF2_DACDAT_CTRL_AIF2_DACR_ENA 14 +#define SUN8I_AIF2_DACDAT_CTRL_AIF2_DACL_SRC 10 +#define SUN8I_AIF2_DACDAT_CTRL_AIF2_DACR_SRC 8 +#define SUN8I_AIF2_MXR_SRC 0x08c +#define SUN8I_AIF2_MXR_SRC_ADCL_MXR_SRC_AIF1DA0L 15 +#define SUN8I_AIF2_MXR_SRC_ADCL_MXR_SRC_AIF1DA1L 14 +#define SUN8I_AIF2_MXR_SRC_ADCL_MXR_SRC_AIF2DACR 13 +#define SUN8I_AIF2_MXR_SRC_ADCL_MXR_SRC_ADCL 12 +#define SUN8I_AIF2_MXR_SRC_ADCR_MXR_SRC_AIF1DA0R 11 +#define SUN8I_AIF2_MXR_SRC_ADCR_MXR_SRC_AIF1DA1R 10 +#define SUN8I_AIF2_MXR_SRC_ADCR_MXR_SRC_AIF2DACL 9 +#define SUN8I_AIF2_MXR_SRC_ADCR_MXR_SRC_ADCR 8 +#define SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_AIF1 (0x0 << 0) +#define SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_AIF2 (0x1 << 0) +#define SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_AIF1CLK (0x2 << 0) +#define SUN8I_AIF3_PATH_CTRL 0x0cc +#define SUN8I_AIF3_PATH_CTRL_AIF3_ADC_SRC 10 +#define SUN8I_AIF3_PATH_CTRL_AIF2_DAC_SRC 8 +#define SUN8I_AIF3_PATH_CTRL_AIF3_PINS_TRI 7 #define SUN8I_ADC_DIG_CTRL 0x100 #define SUN8I_ADC_DIG_CTRL_ENAD 15 #define SUN8I_ADC_DIG_CTRL_ADOUT_DTS 2 @@ -90,10 +118,44 @@ #define SUN8I_SYSCLK_CTL_AIF2CLK_SRC_MASK GENMASK(5, 4) #define SUN8I_SYS_SR_CTRL_AIF1_FS_MASK GENMASK(15, 12) #define SUN8I_SYS_SR_CTRL_AIF2_FS_MASK GENMASK(11, 8) -#define SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV_MASK GENMASK(12, 9) -#define SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_MASK GENMASK(8, 6) -#define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_MASK GENMASK(5, 4) -#define SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT_MASK GENMASK(3, 2) +#define SUN8I_AIF_CLK_CTRL_CLK_INV_MASK GENMASK(14, 13) +#define SUN8I_AIF_CLK_CTRL_BCLK_DIV_MASK GENMASK(12, 9) +#define SUN8I_AIF_CLK_CTRL_LRCK_DIV_MASK GENMASK(8, 6) +#define SUN8I_AIF_CLK_CTRL_WORD_SIZ_MASK GENMASK(5, 4) +#define SUN8I_AIF_CLK_CTRL_DATA_FMT_MASK GENMASK(3, 2) +#define SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_MASK GENMASK(1, 0) + +#define SUN8I_CODEC_PASSTHROUGH_SAMPLE_RATE 48000 + +#define SUN8I_CODEC_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 |\ + SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE|\ + SNDRV_PCM_FMTBIT_S24_3LE) + +#define SUN8I_CODEC_PCM_RATES (SNDRV_PCM_RATE_8000_48000|\ + SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_176400 |\ + SNDRV_PCM_RATE_192000 |\ + SNDRV_PCM_RATE_KNOT) + +enum { + SUN8I_CODEC_AIF1, + SUN8I_CODEC_AIF2, + SUN8I_CODEC_AIF3, + SUN8I_CODEC_NAIFS +}; + +struct sun8i_codec_aif { + unsigned int lrck_div_order; + unsigned int sample_rate; + unsigned int slots; + unsigned int slot_width; + unsigned int active_streams : 2; + unsigned int open_streams : 2; +}; struct sun8i_codec_quirks { bool legacy_widgets : 1; @@ -104,8 +166,13 @@ struct sun8i_codec { struct regmap *regmap; struct clk *clk_module; const struct sun8i_codec_quirks *quirks; + struct sun8i_codec_aif aifs[SUN8I_CODEC_NAIFS]; + unsigned int sysclk_rate; + int sysclk_refcnt; }; +static struct snd_soc_dai_driver sun8i_codec_dais[]; + static int sun8i_codec_runtime_resume(struct device *dev) { struct sun8i_codec *scodec = dev_get_drvdata(dev); @@ -132,32 +199,34 @@ static int sun8i_codec_runtime_suspend(struct device *dev) return 0; } -static int sun8i_codec_get_hw_rate(struct snd_pcm_hw_params *params) +static int sun8i_codec_get_hw_rate(unsigned int sample_rate) { - unsigned int rate = params_rate(params); - - switch (rate) { - case 8000: + switch (sample_rate) { case 7350: + case 8000: return 0x0; case 11025: return 0x1; case 12000: return 0x2; + case 14700: case 16000: return 0x3; case 22050: return 0x4; case 24000: return 0x5; + case 29400: case 32000: return 0x6; case 44100: return 0x7; case 48000: return 0x8; + case 88200: case 96000: return 0x9; + case 176400: case 192000: return 0xa; default: @@ -165,10 +234,37 @@ static int sun8i_codec_get_hw_rate(struct snd_pcm_hw_params *params) } } -static int sun8i_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +static int sun8i_codec_update_sample_rate(struct sun8i_codec *scodec) +{ + unsigned int max_rate = 0; + int hw_rate, i; + + for (i = SUN8I_CODEC_AIF1; i < SUN8I_CODEC_NAIFS; ++i) { + struct sun8i_codec_aif *aif = &scodec->aifs[i]; + + if (aif->active_streams) + max_rate = max(max_rate, aif->sample_rate); + } + + /* Set the sample rate for ADC->DAC passthrough when no AIF is active. */ + if (!max_rate) + max_rate = SUN8I_CODEC_PASSTHROUGH_SAMPLE_RATE; + + hw_rate = sun8i_codec_get_hw_rate(max_rate); + if (hw_rate < 0) + return hw_rate; + + regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL, + SUN8I_SYS_SR_CTRL_AIF1_FS_MASK, + hw_rate << SUN8I_SYS_SR_CTRL_AIF1_FS); + + return 0; +} + +static int sun8i_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai); - u32 value; + u32 dsp_format, format, invert, value; /* clock masters */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -181,65 +277,162 @@ static int sun8i_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) default: return -EINVAL; } - regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, - BIT(SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD), - value << SUN8I_AIF1CLK_CTRL_AIF1_MSTR_MOD); - /* clock inversion */ - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: /* Normal */ - value = 0x0; - break; - case SND_SOC_DAIFMT_IB_IF: /* Inversion */ - value = 0x1; - break; - default: - return -EINVAL; + if (dai->id == SUN8I_CODEC_AIF3) { + /* AIF3 only supports master mode. */ + if (value) + return -EINVAL; + + /* Use the AIF2 BCLK and LRCK for AIF3. */ + regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(dai->id), + SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_MASK, + SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_AIF2); + } else { + regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(dai->id), + BIT(SUN8I_AIF_CLK_CTRL_MSTR_MOD), + value << SUN8I_AIF_CLK_CTRL_MSTR_MOD); } - regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, - BIT(SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV), - value << SUN8I_AIF1CLK_CTRL_AIF1_BCLK_INV); - - /* - * It appears that the DAI and the codec in the A33 SoC don't - * share the same polarity for the LRCK signal when they mean - * 'normal' and 'inverted' in the datasheet. - * - * Since the DAI here is our regular i2s driver that have been - * tested with way more codecs than just this one, it means - * that the codec probably gets it backward, and we have to - * invert the value here. - */ - value ^= scodec->quirks->lrck_inversion; - regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, - BIT(SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV), - value << SUN8I_AIF1CLK_CTRL_AIF1_LRCK_INV); /* DAI format */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: - value = 0x0; + format = 0x0; break; case SND_SOC_DAIFMT_LEFT_J: - value = 0x1; + format = 0x1; break; case SND_SOC_DAIFMT_RIGHT_J: - value = 0x2; + format = 0x2; break; case SND_SOC_DAIFMT_DSP_A: + format = 0x3; + dsp_format = 0x0; /* Set LRCK_INV to 0 */ + break; case SND_SOC_DAIFMT_DSP_B: - value = 0x3; + format = 0x3; + dsp_format = 0x1; /* Set LRCK_INV to 1 */ + break; + default: + return -EINVAL; + } + + if (dai->id == SUN8I_CODEC_AIF3) { + /* AIF3 only supports DSP mode. */ + if (format != 3) + return -EINVAL; + } else { + regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(dai->id), + SUN8I_AIF_CLK_CTRL_DATA_FMT_MASK, + format << SUN8I_AIF_CLK_CTRL_DATA_FMT); + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* Normal */ + invert = 0x0; + break; + case SND_SOC_DAIFMT_NB_IF: /* Inverted LRCK */ + invert = 0x1; + break; + case SND_SOC_DAIFMT_IB_NF: /* Inverted BCLK */ + invert = 0x2; + break; + case SND_SOC_DAIFMT_IB_IF: /* Both inverted */ + invert = 0x3; break; default: return -EINVAL; } - regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, - SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT_MASK, - value << SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT); + + if (format == 0x3) { + /* Inverted LRCK is not available in DSP mode. */ + if (invert & BIT(0)) + return -EINVAL; + + /* Instead, the bit selects between DSP A/B formats. */ + invert |= dsp_format; + } else { + /* + * It appears that the DAI and the codec in the A33 SoC don't + * share the same polarity for the LRCK signal when they mean + * 'normal' and 'inverted' in the datasheet. + * + * Since the DAI here is our regular i2s driver that have been + * tested with way more codecs than just this one, it means + * that the codec probably gets it backward, and we have to + * invert the value here. + */ + invert ^= scodec->quirks->lrck_inversion; + } + + regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(dai->id), + SUN8I_AIF_CLK_CTRL_CLK_INV_MASK, + invert << SUN8I_AIF_CLK_CTRL_CLK_INV); return 0; } +static int sun8i_codec_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai); + struct sun8i_codec_aif *aif = &scodec->aifs[dai->id]; + + if (slot_width && !is_power_of_2(slot_width)) + return -EINVAL; + + aif->slots = slots; + aif->slot_width = slot_width; + + return 0; +} + +static const unsigned int sun8i_codec_rates[] = { + 7350, 8000, 11025, 12000, 14700, 16000, 22050, 24000, + 29400, 32000, 44100, 48000, 88200, 96000, 176400, 192000, +}; + +static const struct snd_pcm_hw_constraint_list sun8i_codec_all_rates = { + .list = sun8i_codec_rates, + .count = ARRAY_SIZE(sun8i_codec_rates), +}; + +static const struct snd_pcm_hw_constraint_list sun8i_codec_22M_rates = { + .list = sun8i_codec_rates, + .count = ARRAY_SIZE(sun8i_codec_rates), + .mask = 0x5555, +}; + +static const struct snd_pcm_hw_constraint_list sun8i_codec_24M_rates = { + .list = sun8i_codec_rates, + .count = ARRAY_SIZE(sun8i_codec_rates), + .mask = 0xaaaa, +}; + +static int sun8i_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai); + const struct snd_pcm_hw_constraint_list *list; + + /* hw_constraints is not relevant for codec2codec DAIs. */ + if (dai->id != SUN8I_CODEC_AIF1) + return 0; + + if (!scodec->sysclk_refcnt) + list = &sun8i_codec_all_rates; + else if (scodec->sysclk_rate == 22579200) + list = &sun8i_codec_22M_rates; + else if (scodec->sysclk_rate == 24576000) + list = &sun8i_codec_24M_rates; + else + return -EINVAL; + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, list); +} + struct sun8i_codec_clk_div { u8 div; u8 val; @@ -262,37 +455,37 @@ static const struct sun8i_codec_clk_div sun8i_codec_bclk_div[] = { { .div = 192, .val = 13 }, }; -static u8 sun8i_codec_get_bclk_div(struct sun8i_codec *scodec, - unsigned int rate, - unsigned int word_size) +static int sun8i_codec_get_bclk_div(unsigned int sysclk_rate, + unsigned int lrck_div_order, + unsigned int sample_rate) { - unsigned long clk_rate = clk_get_rate(scodec->clk_module); - unsigned int div = clk_rate / rate / word_size / 2; - unsigned int best_val = 0, best_diff = ~0; + unsigned int div = sysclk_rate / sample_rate >> lrck_div_order; int i; for (i = 0; i < ARRAY_SIZE(sun8i_codec_bclk_div); i++) { const struct sun8i_codec_clk_div *bdiv = &sun8i_codec_bclk_div[i]; - unsigned int diff = abs(bdiv->div - div); - if (diff < best_diff) { - best_diff = diff; - best_val = bdiv->val; - } + if (bdiv->div == div) + return bdiv->val; } - return best_val; + return -EINVAL; } -static int sun8i_codec_get_lrck_div(unsigned int channels, - unsigned int word_size) +static int sun8i_codec_get_lrck_div_order(unsigned int slots, + unsigned int slot_width) { - unsigned int div = word_size * channels; + unsigned int div = slots * slot_width; if (div < 16 || div > 256) return -EINVAL; - return ilog2(div) - 4; + return order_base_2(div); +} + +static unsigned int sun8i_codec_get_sysclk_rate(unsigned int sample_rate) +{ + return sample_rate % 4000 ? 22579200 : 24576000; } static int sun8i_codec_hw_params(struct snd_pcm_substream *substream, @@ -300,42 +493,225 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai); - int sample_rate, lrck_div; - u8 bclk_div; + struct sun8i_codec_aif *aif = &scodec->aifs[dai->id]; + unsigned int sample_rate = params_rate(params); + unsigned int slots = aif->slots ?: params_channels(params); + unsigned int slot_width = aif->slot_width ?: params_width(params); + unsigned int sysclk_rate = sun8i_codec_get_sysclk_rate(sample_rate); + int bclk_div, lrck_div_order, ret, word_size; + u32 clk_reg; + + /* word size */ + switch (params_width(params)) { + case 8: + word_size = 0x0; + break; + case 16: + word_size = 0x1; + break; + case 20: + word_size = 0x2; + break; + case 24: + word_size = 0x3; + break; + default: + return -EINVAL; + } + + regmap_update_bits(scodec->regmap, SUN8I_AIF_CLK_CTRL(dai->id), + SUN8I_AIF_CLK_CTRL_WORD_SIZ_MASK, + word_size << SUN8I_AIF_CLK_CTRL_WORD_SIZ); + + /* LRCK divider (BCLK/LRCK ratio) */ + lrck_div_order = sun8i_codec_get_lrck_div_order(slots, slot_width); + if (lrck_div_order < 0) + return lrck_div_order; + + if (dai->id == SUN8I_CODEC_AIF2 || dai->id == SUN8I_CODEC_AIF3) { + /* AIF2 and AIF3 share AIF2's BCLK and LRCK generation circuitry. */ + int partner = (SUN8I_CODEC_AIF2 + SUN8I_CODEC_AIF3) - dai->id; + const struct sun8i_codec_aif *partner_aif = &scodec->aifs[partner]; + const char *partner_name = sun8i_codec_dais[partner].name; + + if (partner_aif->open_streams && + (lrck_div_order != partner_aif->lrck_div_order || + sample_rate != partner_aif->sample_rate)) { + dev_err(dai->dev, + "%s sample and bit rates must match %s when both are used\n", + dai->name, partner_name); + return -EBUSY; + } + + clk_reg = SUN8I_AIF_CLK_CTRL(SUN8I_CODEC_AIF2); + } else { + clk_reg = SUN8I_AIF_CLK_CTRL(dai->id); + } + + regmap_update_bits(scodec->regmap, clk_reg, + SUN8I_AIF_CLK_CTRL_LRCK_DIV_MASK, + (lrck_div_order - 4) << SUN8I_AIF_CLK_CTRL_LRCK_DIV); + + /* BCLK divider (SYSCLK/BCLK ratio) */ + bclk_div = sun8i_codec_get_bclk_div(sysclk_rate, lrck_div_order, sample_rate); + if (bclk_div < 0) + return bclk_div; + + regmap_update_bits(scodec->regmap, clk_reg, + SUN8I_AIF_CLK_CTRL_BCLK_DIV_MASK, + bclk_div << SUN8I_AIF_CLK_CTRL_BCLK_DIV); /* - * The CPU DAI handles only a sample of 16 bits. Configure the - * codec to handle this type of sample resolution. + * SYSCLK rate + * + * Clock rate protection is reference counted; but hw_params may be + * called many times per substream, without matching calls to hw_free. + * Protect the clock rate once per AIF, on the first hw_params call + * for the first substream. clk_set_rate() will allow clock rate + * changes on subsequent calls if only one AIF has open streams. */ - regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, - SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_MASK, - SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_16); + ret = (aif->open_streams ? clk_set_rate : clk_set_rate_exclusive)(scodec->clk_module, + sysclk_rate); + if (ret == -EBUSY) + dev_err(dai->dev, + "%s sample rate (%u Hz) conflicts with other audio streams\n", + dai->name, sample_rate); + if (ret < 0) + return ret; - bclk_div = sun8i_codec_get_bclk_div(scodec, params_rate(params), 16); - regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, - SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV_MASK, - bclk_div << SUN8I_AIF1CLK_CTRL_AIF1_BCLK_DIV); + if (!aif->open_streams) + scodec->sysclk_refcnt++; + scodec->sysclk_rate = sysclk_rate; - lrck_div = sun8i_codec_get_lrck_div(params_channels(params), - params_physical_width(params)); - if (lrck_div < 0) - return lrck_div; + aif->lrck_div_order = lrck_div_order; + aif->sample_rate = sample_rate; + aif->open_streams |= BIT(substream->stream); - regmap_update_bits(scodec->regmap, SUN8I_AIF1CLK_CTRL, - SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV_MASK, - lrck_div << SUN8I_AIF1CLK_CTRL_AIF1_LRCK_DIV); + return sun8i_codec_update_sample_rate(scodec); +} - sample_rate = sun8i_codec_get_hw_rate(params); - if (sample_rate < 0) - return sample_rate; +static int sun8i_codec_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sun8i_codec *scodec = snd_soc_dai_get_drvdata(dai); + struct sun8i_codec_aif *aif = &scodec->aifs[dai->id]; - regmap_update_bits(scodec->regmap, SUN8I_SYS_SR_CTRL, - SUN8I_SYS_SR_CTRL_AIF1_FS_MASK, - sample_rate << SUN8I_SYS_SR_CTRL_AIF1_FS); + /* Drop references when the last substream for the AIF is freed. */ + if (aif->open_streams != BIT(substream->stream)) + goto done; + clk_rate_exclusive_put(scodec->clk_module); + scodec->sysclk_refcnt--; + aif->lrck_div_order = 0; + aif->sample_rate = 0; + +done: + aif->open_streams &= ~BIT(substream->stream); return 0; } +static const struct snd_soc_dai_ops sun8i_codec_dai_ops = { + .set_fmt = sun8i_codec_set_fmt, + .set_tdm_slot = sun8i_codec_set_tdm_slot, + .startup = sun8i_codec_startup, + .hw_params = sun8i_codec_hw_params, + .hw_free = sun8i_codec_hw_free, +}; + +static struct snd_soc_dai_driver sun8i_codec_dais[] = { + { + .name = "sun8i-codec-aif1", + .id = SUN8I_CODEC_AIF1, + .ops = &sun8i_codec_dai_ops, + /* capture capabilities */ + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SUN8I_CODEC_PCM_RATES, + .formats = SUN8I_CODEC_PCM_FORMATS, + .sig_bits = 24, + }, + /* playback capabilities */ + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SUN8I_CODEC_PCM_RATES, + .formats = SUN8I_CODEC_PCM_FORMATS, + }, + .symmetric_rates = true, + .symmetric_channels = true, + .symmetric_samplebits = true, + }, + { + .name = "sun8i-codec-aif2", + .id = SUN8I_CODEC_AIF2, + .ops = &sun8i_codec_dai_ops, + /* capture capabilities */ + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SUN8I_CODEC_PCM_RATES, + .formats = SUN8I_CODEC_PCM_FORMATS, + .sig_bits = 24, + }, + /* playback capabilities */ + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SUN8I_CODEC_PCM_RATES, + .formats = SUN8I_CODEC_PCM_FORMATS, + }, + .symmetric_rates = true, + .symmetric_channels = true, + .symmetric_samplebits = true, + }, + { + .name = "sun8i-codec-aif3", + .id = SUN8I_CODEC_AIF3, + .ops = &sun8i_codec_dai_ops, + /* capture capabilities */ + .capture = { + .stream_name = "AIF3 Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SUN8I_CODEC_PCM_RATES, + .formats = SUN8I_CODEC_PCM_FORMATS, + .sig_bits = 24, + }, + /* playback capabilities */ + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SUN8I_CODEC_PCM_RATES, + .formats = SUN8I_CODEC_PCM_FORMATS, + }, + .symmetric_rates = true, + .symmetric_channels = true, + .symmetric_samplebits = true, + }, +}; + +static int sun8i_codec_aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct sun8i_codec *scodec = snd_soc_component_get_drvdata(component); + struct sun8i_codec_aif *aif = &scodec->aifs[w->sname[3] - '1']; + int stream = w->id == snd_soc_dapm_aif_out; + + if (SND_SOC_DAPM_EVENT_ON(event)) + aif->active_streams |= BIT(stream); + else + aif->active_streams &= ~BIT(stream); + + return sun8i_codec_update_sample_rate(scodec); +} + static const char *const sun8i_aif_stereo_mux_enum_values[] = { "Stereo", "Reverse Stereo", "Sum Mono", "Mix Mono" }; @@ -350,6 +726,29 @@ static const struct snd_kcontrol_new sun8i_aif1_ad0_stereo_mux_control = SOC_DAPM_ENUM("AIF1 AD0 Stereo Capture Route", sun8i_aif1_ad0_stereo_mux_enum); +static SOC_ENUM_DOUBLE_DECL(sun8i_aif2_adc_stereo_mux_enum, + SUN8I_AIF2_ADCDAT_CTRL, + SUN8I_AIF2_ADCDAT_CTRL_AIF2_ADCL_SRC, + SUN8I_AIF2_ADCDAT_CTRL_AIF2_ADCR_SRC, + sun8i_aif_stereo_mux_enum_values); + +static const struct snd_kcontrol_new sun8i_aif2_adc_stereo_mux_control = + SOC_DAPM_ENUM("AIF2 ADC Stereo Capture Route", + sun8i_aif2_adc_stereo_mux_enum); + +static const char *const sun8i_aif3_adc_mux_enum_values[] = { + "None", "AIF2 ADCL", "AIF2 ADCR" +}; + +static SOC_ENUM_SINGLE_DECL(sun8i_aif3_adc_mux_enum, + SUN8I_AIF3_PATH_CTRL, + SUN8I_AIF3_PATH_CTRL_AIF3_ADC_SRC, + sun8i_aif3_adc_mux_enum_values); + +static const struct snd_kcontrol_new sun8i_aif3_adc_mux_control = + SOC_DAPM_ENUM("AIF3 ADC Source Capture Route", + sun8i_aif3_adc_mux_enum); + static const struct snd_kcontrol_new sun8i_aif1_ad0_mixer_controls[] = { SOC_DAPM_DOUBLE("AIF1 Slot 0 Digital ADC Capture Switch", SUN8I_AIF1_MXR_SRC, @@ -369,6 +768,38 @@ static const struct snd_kcontrol_new sun8i_aif1_ad0_mixer_controls[] = { SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACL, 1, 0), }; +static const struct snd_kcontrol_new sun8i_aif2_adc_mixer_controls[] = { + SOC_DAPM_DOUBLE("AIF2 ADC Mixer AIF1 DA0 Capture Switch", + SUN8I_AIF2_MXR_SRC, + SUN8I_AIF2_MXR_SRC_ADCL_MXR_SRC_AIF1DA0L, + SUN8I_AIF2_MXR_SRC_ADCR_MXR_SRC_AIF1DA0R, 1, 0), + SOC_DAPM_DOUBLE("AIF2 ADC Mixer AIF1 DA1 Capture Switch", + SUN8I_AIF2_MXR_SRC, + SUN8I_AIF2_MXR_SRC_ADCL_MXR_SRC_AIF1DA1L, + SUN8I_AIF2_MXR_SRC_ADCR_MXR_SRC_AIF1DA1R, 1, 0), + SOC_DAPM_DOUBLE("AIF2 ADC Mixer AIF2 DAC Rev Capture Switch", + SUN8I_AIF2_MXR_SRC, + SUN8I_AIF2_MXR_SRC_ADCL_MXR_SRC_AIF2DACR, + SUN8I_AIF2_MXR_SRC_ADCR_MXR_SRC_AIF2DACL, 1, 0), + SOC_DAPM_DOUBLE("AIF2 ADC Mixer ADC Capture Switch", + SUN8I_AIF2_MXR_SRC, + SUN8I_AIF2_MXR_SRC_ADCL_MXR_SRC_ADCL, + SUN8I_AIF2_MXR_SRC_ADCR_MXR_SRC_ADCR, 1, 0), +}; + +static const char *const sun8i_aif2_dac_mux_enum_values[] = { + "AIF2", "AIF3+2", "AIF2+3" +}; + +static SOC_ENUM_SINGLE_DECL(sun8i_aif2_dac_mux_enum, + SUN8I_AIF3_PATH_CTRL, + SUN8I_AIF3_PATH_CTRL_AIF2_DAC_SRC, + sun8i_aif2_dac_mux_enum_values); + +static const struct snd_kcontrol_new sun8i_aif2_dac_mux_control = + SOC_DAPM_ENUM("AIF2 DAC Source Playback Route", + sun8i_aif2_dac_mux_enum); + static SOC_ENUM_DOUBLE_DECL(sun8i_aif1_da0_stereo_mux_enum, SUN8I_AIF1_DACDAT_CTRL, SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_SRC, @@ -379,6 +810,16 @@ static const struct snd_kcontrol_new sun8i_aif1_da0_stereo_mux_control = SOC_DAPM_ENUM("AIF1 DA0 Stereo Playback Route", sun8i_aif1_da0_stereo_mux_enum); +static SOC_ENUM_DOUBLE_DECL(sun8i_aif2_dac_stereo_mux_enum, + SUN8I_AIF2_DACDAT_CTRL, + SUN8I_AIF2_DACDAT_CTRL_AIF2_DACL_SRC, + SUN8I_AIF2_DACDAT_CTRL_AIF2_DACR_SRC, + sun8i_aif_stereo_mux_enum_values); + +static const struct snd_kcontrol_new sun8i_aif2_dac_stereo_mux_control = + SOC_DAPM_ENUM("AIF2 DAC Stereo Playback Route", + sun8i_aif2_dac_stereo_mux_enum); + static const struct snd_kcontrol_new sun8i_dac_mixer_controls[] = { SOC_DAPM_DOUBLE("AIF1 Slot 0 Digital DAC Playback Switch", SUN8I_DAC_MXR_SRC, @@ -403,6 +844,9 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("AIF1CLK", SUN8I_SYSCLK_CTL, SUN8I_SYSCLK_CTL_AIF1CLK_ENA, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF2CLK", + SUN8I_SYSCLK_CTL, + SUN8I_SYSCLK_CTL_AIF2CLK_ENA, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("SYSCLK", SUN8I_SYSCLK_CTL, SUN8I_SYSCLK_CTL_SYSCLK_ENA, 0, NULL, 0), @@ -411,6 +855,12 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("CLK AIF1", SUN8I_MOD_CLK_ENA, SUN8I_MOD_CLK_ENA_AIF1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLK AIF2", + SUN8I_MOD_CLK_ENA, + SUN8I_MOD_CLK_ENA_AIF2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("CLK AIF3", + SUN8I_MOD_CLK_ENA, + SUN8I_MOD_CLK_ENA_AIF3, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("CLK ADC", SUN8I_MOD_CLK_ENA, SUN8I_MOD_CLK_ENA_ADC, 0, NULL, 0), @@ -422,6 +872,12 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("RST AIF1", SUN8I_MOD_RST_CTL, SUN8I_MOD_RST_CTL_AIF1, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RST AIF2", + SUN8I_MOD_RST_CTL, + SUN8I_MOD_RST_CTL_AIF2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RST AIF3", + SUN8I_MOD_RST_CTL, + SUN8I_MOD_RST_CTL_AIF3, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("RST ADC", SUN8I_MOD_RST_CTL, SUN8I_MOD_RST_CTL_ADC, 0, NULL, 0), @@ -438,39 +894,96 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { SUN8I_DAC_DIG_CTRL_ENDA, 0, NULL, 0), /* AIF "ADC" Outputs */ - SND_SOC_DAPM_AIF_OUT("AIF1 AD0L", "Capture", 0, - SUN8I_AIF1_ADCDAT_CTRL, - SUN8I_AIF1_ADCDAT_CTRL_AIF1_AD0L_ENA, 0), - SND_SOC_DAPM_AIF_OUT("AIF1 AD0R", "Capture", 1, + SND_SOC_DAPM_AIF_OUT_E("AIF1 AD0L", "AIF1 Capture", 0, + SUN8I_AIF1_ADCDAT_CTRL, + SUN8I_AIF1_ADCDAT_CTRL_AIF1_AD0L_ENA, 0, + sun8i_codec_aif_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("AIF1 AD0R", "AIF1 Capture", 1, SUN8I_AIF1_ADCDAT_CTRL, SUN8I_AIF1_ADCDAT_CTRL_AIF1_AD0R_ENA, 0), + SND_SOC_DAPM_AIF_OUT_E("AIF2 ADCL", "AIF2 Capture", 0, + SUN8I_AIF2_ADCDAT_CTRL, + SUN8I_AIF2_ADCDAT_CTRL_AIF2_ADCL_ENA, 0, + sun8i_codec_aif_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("AIF2 ADCR", "AIF2 Capture", 1, + SUN8I_AIF2_ADCDAT_CTRL, + SUN8I_AIF2_ADCDAT_CTRL_AIF2_ADCR_ENA, 0), + + SND_SOC_DAPM_AIF_OUT_E("AIF3 ADC", "AIF3 Capture", 0, + SND_SOC_NOPM, 0, 0, + sun8i_codec_aif_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + /* AIF "ADC" Mono/Stereo Muxes */ SND_SOC_DAPM_MUX("AIF1 AD0L Stereo Mux", SND_SOC_NOPM, 0, 0, &sun8i_aif1_ad0_stereo_mux_control), SND_SOC_DAPM_MUX("AIF1 AD0R Stereo Mux", SND_SOC_NOPM, 0, 0, &sun8i_aif1_ad0_stereo_mux_control), + SND_SOC_DAPM_MUX("AIF2 ADCL Stereo Mux", SND_SOC_NOPM, 0, 0, + &sun8i_aif2_adc_stereo_mux_control), + SND_SOC_DAPM_MUX("AIF2 ADCR Stereo Mux", SND_SOC_NOPM, 0, 0, + &sun8i_aif2_adc_stereo_mux_control), + + /* AIF "ADC" Output Muxes */ + SND_SOC_DAPM_MUX("AIF3 ADC Source Capture Route", SND_SOC_NOPM, 0, 0, + &sun8i_aif3_adc_mux_control), + /* AIF "ADC" Mixers */ SOC_MIXER_ARRAY("AIF1 AD0L Mixer", SND_SOC_NOPM, 0, 0, sun8i_aif1_ad0_mixer_controls), SOC_MIXER_ARRAY("AIF1 AD0R Mixer", SND_SOC_NOPM, 0, 0, sun8i_aif1_ad0_mixer_controls), + SOC_MIXER_ARRAY("AIF2 ADCL Mixer", SND_SOC_NOPM, 0, 0, + sun8i_aif2_adc_mixer_controls), + SOC_MIXER_ARRAY("AIF2 ADCR Mixer", SND_SOC_NOPM, 0, 0, + sun8i_aif2_adc_mixer_controls), + + /* AIF "DAC" Input Muxes */ + SND_SOC_DAPM_MUX("AIF2 DACL Source", SND_SOC_NOPM, 0, 0, + &sun8i_aif2_dac_mux_control), + SND_SOC_DAPM_MUX("AIF2 DACR Source", SND_SOC_NOPM, 0, 0, + &sun8i_aif2_dac_mux_control), + /* AIF "DAC" Mono/Stereo Muxes */ SND_SOC_DAPM_MUX("AIF1 DA0L Stereo Mux", SND_SOC_NOPM, 0, 0, &sun8i_aif1_da0_stereo_mux_control), SND_SOC_DAPM_MUX("AIF1 DA0R Stereo Mux", SND_SOC_NOPM, 0, 0, &sun8i_aif1_da0_stereo_mux_control), + SND_SOC_DAPM_MUX("AIF2 DACL Stereo Mux", SND_SOC_NOPM, 0, 0, + &sun8i_aif2_dac_stereo_mux_control), + SND_SOC_DAPM_MUX("AIF2 DACR Stereo Mux", SND_SOC_NOPM, 0, 0, + &sun8i_aif2_dac_stereo_mux_control), + /* AIF "DAC" Inputs */ - SND_SOC_DAPM_AIF_IN("AIF1 DA0L", "Playback", 0, - SUN8I_AIF1_DACDAT_CTRL, - SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA, 0), - SND_SOC_DAPM_AIF_IN("AIF1 DA0R", "Playback", 1, + SND_SOC_DAPM_AIF_IN_E("AIF1 DA0L", "AIF1 Playback", 0, + SUN8I_AIF1_DACDAT_CTRL, + SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA, 0, + sun8i_codec_aif_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN("AIF1 DA0R", "AIF1 Playback", 1, SUN8I_AIF1_DACDAT_CTRL, SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA, 0), + SND_SOC_DAPM_AIF_IN_E("AIF2 DACL", "AIF2 Playback", 0, + SUN8I_AIF2_DACDAT_CTRL, + SUN8I_AIF2_DACDAT_CTRL_AIF2_DACL_ENA, 0, + sun8i_codec_aif_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN("AIF2 DACR", "AIF2 Playback", 1, + SUN8I_AIF2_DACDAT_CTRL, + SUN8I_AIF2_DACDAT_CTRL_AIF2_DACR_ENA, 0), + + SND_SOC_DAPM_AIF_IN_E("AIF3 DAC", "AIF3 Playback", 0, + SND_SOC_NOPM, 0, 0, + sun8i_codec_aif_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + /* ADC Inputs (connected to analog codec DAPM context) */ SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0), @@ -500,6 +1013,20 @@ static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = { { "AIF1 DA0L", NULL, "RST AIF1" }, { "AIF1 DA0R", NULL, "RST AIF1" }, + { "CLK AIF2", NULL, "AIF2CLK" }, + { "CLK AIF2", NULL, "SYSCLK" }, + { "RST AIF2", NULL, "CLK AIF2" }, + { "AIF2 ADCL", NULL, "RST AIF2" }, + { "AIF2 ADCR", NULL, "RST AIF2" }, + { "AIF2 DACL", NULL, "RST AIF2" }, + { "AIF2 DACR", NULL, "RST AIF2" }, + + { "CLK AIF3", NULL, "AIF1CLK" }, + { "CLK AIF3", NULL, "SYSCLK" }, + { "RST AIF3", NULL, "CLK AIF3" }, + { "AIF3 ADC", NULL, "RST AIF3" }, + { "AIF3 DAC", NULL, "RST AIF3" }, + { "CLK ADC", NULL, "SYSCLK" }, { "RST ADC", NULL, "CLK ADC" }, { "ADC", NULL, "RST ADC" }, @@ -516,6 +1043,11 @@ static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = { { "AIF1 AD0L", NULL, "AIF1 AD0L Stereo Mux" }, { "AIF1 AD0R", NULL, "AIF1 AD0R Stereo Mux" }, + { "AIF2 ADCL", NULL, "AIF2 ADCL Stereo Mux" }, + { "AIF2 ADCR", NULL, "AIF2 ADCR Stereo Mux" }, + + { "AIF3 ADC", NULL, "AIF3 ADC Source Capture Route" }, + /* AIF "ADC" Mono/Stereo Mux Routes */ { "AIF1 AD0L Stereo Mux", "Stereo", "AIF1 AD0L Mixer" }, { "AIF1 AD0L Stereo Mux", "Reverse Stereo", "AIF1 AD0R Mixer" }, @@ -531,12 +1063,51 @@ static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = { { "AIF1 AD0R Stereo Mux", "Mix Mono", "AIF1 AD0L Mixer" }, { "AIF1 AD0R Stereo Mux", "Mix Mono", "AIF1 AD0R Mixer" }, + { "AIF2 ADCL Stereo Mux", "Stereo", "AIF2 ADCL Mixer" }, + { "AIF2 ADCL Stereo Mux", "Reverse Stereo", "AIF2 ADCR Mixer" }, + { "AIF2 ADCL Stereo Mux", "Sum Mono", "AIF2 ADCL Mixer" }, + { "AIF2 ADCL Stereo Mux", "Sum Mono", "AIF2 ADCR Mixer" }, + { "AIF2 ADCL Stereo Mux", "Mix Mono", "AIF2 ADCL Mixer" }, + { "AIF2 ADCL Stereo Mux", "Mix Mono", "AIF2 ADCR Mixer" }, + + { "AIF2 ADCR Stereo Mux", "Stereo", "AIF2 ADCR Mixer" }, + { "AIF2 ADCR Stereo Mux", "Reverse Stereo", "AIF2 ADCL Mixer" }, + { "AIF2 ADCR Stereo Mux", "Sum Mono", "AIF2 ADCL Mixer" }, + { "AIF2 ADCR Stereo Mux", "Sum Mono", "AIF2 ADCR Mixer" }, + { "AIF2 ADCR Stereo Mux", "Mix Mono", "AIF2 ADCL Mixer" }, + { "AIF2 ADCR Stereo Mux", "Mix Mono", "AIF2 ADCR Mixer" }, + + /* AIF "ADC" Output Mux Routes */ + { "AIF3 ADC Source Capture Route", "AIF2 ADCL", "AIF2 ADCL Mixer" }, + { "AIF3 ADC Source Capture Route", "AIF2 ADCR", "AIF2 ADCR Mixer" }, + /* AIF "ADC" Mixer Routes */ { "AIF1 AD0L Mixer", "AIF1 Slot 0 Digital ADC Capture Switch", "AIF1 DA0L Stereo Mux" }, + { "AIF1 AD0L Mixer", "AIF2 Digital ADC Capture Switch", "AIF2 DACL Source" }, { "AIF1 AD0L Mixer", "AIF1 Data Digital ADC Capture Switch", "ADCL" }, + { "AIF1 AD0L Mixer", "AIF2 Inv Digital ADC Capture Switch", "AIF2 DACR Source" }, { "AIF1 AD0R Mixer", "AIF1 Slot 0 Digital ADC Capture Switch", "AIF1 DA0R Stereo Mux" }, + { "AIF1 AD0R Mixer", "AIF2 Digital ADC Capture Switch", "AIF2 DACR Source" }, { "AIF1 AD0R Mixer", "AIF1 Data Digital ADC Capture Switch", "ADCR" }, + { "AIF1 AD0R Mixer", "AIF2 Inv Digital ADC Capture Switch", "AIF2 DACL Source" }, + + { "AIF2 ADCL Mixer", "AIF2 ADC Mixer AIF1 DA0 Capture Switch", "AIF1 DA0L Stereo Mux" }, + { "AIF2 ADCL Mixer", "AIF2 ADC Mixer AIF2 DAC Rev Capture Switch", "AIF2 DACR Source" }, + { "AIF2 ADCL Mixer", "AIF2 ADC Mixer ADC Capture Switch", "ADCL" }, + + { "AIF2 ADCR Mixer", "AIF2 ADC Mixer AIF1 DA0 Capture Switch", "AIF1 DA0R Stereo Mux" }, + { "AIF2 ADCR Mixer", "AIF2 ADC Mixer AIF2 DAC Rev Capture Switch", "AIF2 DACL Source" }, + { "AIF2 ADCR Mixer", "AIF2 ADC Mixer ADC Capture Switch", "ADCR" }, + + /* AIF "DAC" Input Mux Routes */ + { "AIF2 DACL Source", "AIF2", "AIF2 DACL Stereo Mux" }, + { "AIF2 DACL Source", "AIF3+2", "AIF3 DAC" }, + { "AIF2 DACL Source", "AIF2+3", "AIF2 DACL Stereo Mux" }, + + { "AIF2 DACR Source", "AIF2", "AIF2 DACR Stereo Mux" }, + { "AIF2 DACR Source", "AIF3+2", "AIF2 DACR Stereo Mux" }, + { "AIF2 DACR Source", "AIF2+3", "AIF3 DAC" }, /* AIF "DAC" Mono/Stereo Mux Routes */ { "AIF1 DA0L Stereo Mux", "Stereo", "AIF1 DA0L" }, @@ -553,15 +1124,31 @@ static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = { { "AIF1 DA0R Stereo Mux", "Mix Mono", "AIF1 DA0L" }, { "AIF1 DA0R Stereo Mux", "Mix Mono", "AIF1 DA0R" }, + { "AIF2 DACL Stereo Mux", "Stereo", "AIF2 DACL" }, + { "AIF2 DACL Stereo Mux", "Reverse Stereo", "AIF2 DACR" }, + { "AIF2 DACL Stereo Mux", "Sum Mono", "AIF2 DACL" }, + { "AIF2 DACL Stereo Mux", "Sum Mono", "AIF2 DACR" }, + { "AIF2 DACL Stereo Mux", "Mix Mono", "AIF2 DACL" }, + { "AIF2 DACL Stereo Mux", "Mix Mono", "AIF2 DACR" }, + + { "AIF2 DACR Stereo Mux", "Stereo", "AIF2 DACR" }, + { "AIF2 DACR Stereo Mux", "Reverse Stereo", "AIF2 DACL" }, + { "AIF2 DACR Stereo Mux", "Sum Mono", "AIF2 DACL" }, + { "AIF2 DACR Stereo Mux", "Sum Mono", "AIF2 DACR" }, + { "AIF2 DACR Stereo Mux", "Mix Mono", "AIF2 DACL" }, + { "AIF2 DACR Stereo Mux", "Mix Mono", "AIF2 DACR" }, + /* DAC Output Routes */ { "DACL", NULL, "DACL Mixer" }, { "DACR", NULL, "DACR Mixer" }, /* DAC Mixer Routes */ { "DACL Mixer", "AIF1 Slot 0 Digital DAC Playback Switch", "AIF1 DA0L Stereo Mux" }, + { "DACL Mixer", "AIF2 Digital DAC Playback Switch", "AIF2 DACL Source" }, { "DACL Mixer", "ADC Digital DAC Playback Switch", "ADCL" }, { "DACR Mixer", "AIF1 Slot 0 Digital DAC Playback Switch", "AIF1 DA0R Stereo Mux" }, + { "DACR Mixer", "AIF2 Digital DAC Playback Switch", "AIF2 DACR Source" }, { "DACR Mixer", "ADC Digital DAC Playback Switch", "ADCR" }, }; @@ -621,37 +1208,12 @@ static int sun8i_codec_component_probe(struct snd_soc_component *component) BIT(SUN8I_SYSCLK_CTL_SYSCLK_SRC), SUN8I_SYSCLK_CTL_SYSCLK_SRC_AIF1CLK); + /* Program the default sample rate. */ + sun8i_codec_update_sample_rate(scodec); + return 0; } -static const struct snd_soc_dai_ops sun8i_codec_dai_ops = { - .hw_params = sun8i_codec_hw_params, - .set_fmt = sun8i_set_fmt, -}; - -static struct snd_soc_dai_driver sun8i_codec_dai = { - .name = "sun8i", - /* playback capabilities */ - .playback = { - .stream_name = "Playback", - .channels_min = 1, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - /* capture capabilities */ - .capture = { - .stream_name = "Capture", - .channels_min = 1, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_8000_192000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .sig_bits = 24, - }, - /* pcm operations */ - .ops = &sun8i_codec_dai_ops, -}; - static const struct snd_soc_component_driver sun8i_soc_component = { .dapm_widgets = sun8i_codec_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_dapm_widgets), @@ -659,7 +1221,6 @@ static const struct snd_soc_component_driver sun8i_soc_component = { .num_dapm_routes = ARRAY_SIZE(sun8i_codec_dapm_routes), .probe = sun8i_codec_component_probe, .idle_bias_on = 1, - .use_pmdown_time = 1, .endianness = 1, .non_legacy_dai_naming = 1, }; @@ -714,7 +1275,8 @@ static int sun8i_codec_probe(struct platform_device *pdev) } ret = devm_snd_soc_register_component(&pdev->dev, &sun8i_soc_component, - &sun8i_codec_dai, 1); + sun8i_codec_dais, + ARRAY_SIZE(sun8i_codec_dais)); if (ret) { dev_err(&pdev->dev, "Failed to register codec\n"); goto err_suspend; |