diff options
46 files changed, 4692 insertions, 469 deletions
diff --git a/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-scc-qmc.yaml b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-scc-qmc.yaml new file mode 100644 index 000000000000..4ebbc7d52981 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-scc-qmc.yaml @@ -0,0 +1,172 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/fsl/cpm_qe/fsl,cpm1-scc-qmc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PowerQUICC CPM QUICC Multichannel Controller (QMC) + +maintainers: + - Herve Codina <herve.codina@bootlin.com> + +description: + The QMC (QUICC Multichannel Controller) emulates up to 64 channels within one + serial controller using the same TDM physical interface routed from TSA. + +properties: + compatible: + items: + - enum: + - fsl,mpc885-scc-qmc + - fsl,mpc866-scc-qmc + - const: fsl,cpm1-scc-qmc + + reg: + items: + - description: SCC (Serial communication controller) register base + - description: SCC parameter ram base + - description: Dual port ram base + + reg-names: + items: + - const: scc_regs + - const: scc_pram + - const: dpram + + interrupts: + maxItems: 1 + description: SCC interrupt line in the CPM interrupt controller + + fsl,tsa-serial: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: phandle to TSA node + - enum: [1, 2, 3] + description: | + TSA serial interface (dt-bindings/soc/cpm1-fsl,tsa.h defines these + values) + - 1: SCC2 + - 2: SCC3 + - 3: SCC4 + description: + Should be a phandle/number pair. The phandle to TSA node and the TSA + serial interface to use. + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + '#fsl,chan-cells': + $ref: /schemas/types.yaml#/definitions/uint32 + const: 1 + description: + QMC consumers that use a phandle to QMC need to pass the channel number + with this phandle. + For instance "fsl,qmc-chan = <&qmc 16>;". + +patternProperties: + '^channel@([0-9]|[1-5][0-9]|6[0-3])$': + description: + A channel managed by this controller + type: object + + properties: + reg: + minimum: 0 + maximum: 63 + description: + The channel number + + fsl,operational-mode: + $ref: /schemas/types.yaml#/definitions/string + enum: [transparent, hdlc] + default: transparent + description: | + The channel operational mode + - hdlc: The channel handles HDLC frames + - transparent: The channel handles raw data without any processing + + fsl,reverse-data: + $ref: /schemas/types.yaml#/definitions/flag + description: + The bit order as seen on the channels is reversed, + transmitting/receiving the MSB of each octet first. + This flag is used only in 'transparent' mode. + + fsl,tx-ts-mask: + $ref: /schemas/types.yaml#/definitions/uint64 + description: + Channel assigned Tx time-slots within the Tx time-slots routed by the + TSA to this cell. + + fsl,rx-ts-mask: + $ref: /schemas/types.yaml#/definitions/uint64 + description: + Channel assigned Rx time-slots within the Rx time-slots routed by the + TSA to this cell. + + required: + - reg + - fsl,tx-ts-mask + - fsl,rx-ts-mask + +required: + - compatible + - reg + - reg-names + - interrupts + - fsl,tsa-serial + - '#address-cells' + - '#size-cells' + - '#fsl,chan-cells' + +additionalProperties: false + +examples: + - | + #include <dt-bindings/soc/cpm1-fsl,tsa.h> + + qmc@a60 { + compatible = "fsl,mpc885-scc-qmc", "fsl,cpm1-scc-qmc"; + reg = <0xa60 0x20>, + <0x3f00 0xc0>, + <0x2000 0x1000>; + reg-names = "scc_regs", "scc_pram", "dpram"; + interrupts = <27>; + interrupt-parent = <&CPM_PIC>; + + #address-cells = <1>; + #size-cells = <0>; + #fsl,chan-cells = <1>; + + fsl,tsa-serial = <&tsa FSL_CPM_TSA_SCC4>; + + channel@16 { + /* Ch16 : First 4 even TS from all routed from TSA */ + reg = <16>; + fsl,mode = "transparent"; + fsl,reverse-data; + fsl,tx-ts-mask = <0x00000000 0x000000aa>; + fsl,rx-ts-mask = <0x00000000 0x000000aa>; + }; + + channel@17 { + /* Ch17 : First 4 odd TS from all routed from TSA */ + reg = <17>; + fsl,mode = "transparent"; + fsl,reverse-data; + fsl,tx-ts-mask = <0x00000000 0x00000055>; + fsl,rx-ts-mask = <0x00000000 0x00000055>; + }; + + channel@19 { + /* Ch19 : 8 TS (TS 8..15) from all routed from TSA */ + reg = <19>; + fsl,mode = "hdlc"; + fsl,tx-ts-mask = <0x00000000 0x0000ff00>; + fsl,rx-ts-mask = <0x00000000 0x0000ff00>; + }; + }; diff --git a/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-tsa.yaml b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-tsa.yaml new file mode 100644 index 000000000000..332e902bcc21 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-tsa.yaml @@ -0,0 +1,215 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/fsl/cpm_qe/fsl,cpm1-tsa.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PowerQUICC CPM Time-slot assigner (TSA) controller + +maintainers: + - Herve Codina <herve.codina@bootlin.com> + +description: + The TSA is the time-slot assigner that can be found on some PowerQUICC SoC. + Its purpose is to route some TDM time-slots to other internal serial + controllers. + +properties: + compatible: + items: + - enum: + - fsl,mpc885-tsa + - fsl,mpc866-tsa + - const: fsl,cpm1-tsa + + reg: + items: + - description: SI (Serial Interface) register base + - description: SI RAM base + + reg-names: + items: + - const: si_regs + - const: si_ram + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + '#fsl,serial-cells': + $ref: /schemas/types.yaml#/definitions/uint32 + const: 1 + description: + TSA consumers that use a phandle to TSA need to pass the serial identifier + with this phandle (defined in dt-bindings/soc/fsl,tsa.h). + For instance "fsl,tsa-serial = <&tsa FSL_CPM_TSA_SCC4>;". + +patternProperties: + '^tdm@[0-1]$': + description: + The TDM managed by this controller + type: object + + additionalProperties: false + + properties: + reg: + minimum: 0 + maximum: 1 + description: + The TDM number for this TDM, 0 for TDMa and 1 for TDMb + + fsl,common-rxtx-pins: + $ref: /schemas/types.yaml#/definitions/flag + description: + The hardware can use four dedicated pins for Tx clock, Tx sync, Rx + clock and Rx sync or use only two pins, Tx/Rx clock and Tx/Rx sync. + Without the 'fsl,common-rxtx-pins' property, the four pins are used. + With the 'fsl,common-rxtx-pins' property, two pins are used. + + clocks: + minItems: 2 + items: + - description: External clock connected to L1RSYNC pin + - description: External clock connected to L1RCLK pin + - description: External clock connected to L1TSYNC pin + - description: External clock connected to L1TCLK pin + + clock-names: + minItems: 2 + items: + - const: l1rsync + - const: l1rclk + - const: l1tsync + - const: l1tclk + + fsl,rx-frame-sync-delay-bits: + enum: [0, 1, 2, 3] + default: 0 + description: | + Receive frame sync delay in number of bits. + Indicates the delay between the Rx sync and the first bit of the Rx + frame. 0 for no bit delay. 1, 2 or 3 for 1, 2 or 3 bits delay. + + fsl,tx-frame-sync-delay-bits: + enum: [0, 1, 2, 3] + default: 0 + description: | + Transmit frame sync delay in number of bits. + Indicates the delay between the Tx sync and the first bit of the Tx + frame. 0 for no bit delay. 1, 2 or 3 for 1, 2 or 3 bits delay. + + fsl,clock-falling-edge: + $ref: /schemas/types.yaml#/definitions/flag + description: + Data is sent on falling edge of the clock (and received on the rising + edge). If 'clock-falling-edge' is not present, data is sent on the + rising edge (and received on the falling edge). + + fsl,fsync-rising-edge: + $ref: /schemas/types.yaml#/definitions/flag + description: + Frame sync pulses are sampled with the rising edge of the channel + clock. If 'fsync-rising-edge' is not present, pulses are sampled with + the falling edge. + + fsl,double-speed-clock: + $ref: /schemas/types.yaml#/definitions/flag + description: + The channel clock is twice the data rate. + + patternProperties: + '^fsl,[rt]x-ts-routes$': + $ref: /schemas/types.yaml#/definitions/uint32-matrix + description: | + A list of tuple that indicates the Tx or Rx time-slots routes. + items: + items: + - description: + The number of time-slots + minimum: 1 + maximum: 64 + - description: | + The source (Tx) or destination (Rx) serial interface + (dt-bindings/soc/cpm1-fsl,tsa.h defines these values) + - 0: No destination + - 1: SCC2 + - 2: SCC3 + - 3: SCC4 + - 4: SMC1 + - 5: SMC2 + enum: [0, 1, 2, 3, 4, 5] + minItems: 1 + maxItems: 64 + + allOf: + # If fsl,common-rxtx-pins is present, only 2 clocks are needed. + # Else, the 4 clocks must be present. + - if: + required: + - fsl,common-rxtx-pins + then: + properties: + clocks: + maxItems: 2 + clock-names: + maxItems: 2 + else: + properties: + clocks: + minItems: 4 + clock-names: + minItems: 4 + + required: + - reg + - clocks + - clock-names + +required: + - compatible + - reg + - reg-names + - '#address-cells' + - '#size-cells' + - '#fsl,serial-cells' + +additionalProperties: false + +examples: + - | + #include <dt-bindings/soc/cpm1-fsl,tsa.h> + + tsa@ae0 { + compatible = "fsl,mpc885-tsa", "fsl,cpm1-tsa"; + reg = <0xae0 0x10>, + <0xc00 0x200>; + reg-names = "si_regs", "si_ram"; + + #address-cells = <1>; + #size-cells = <0>; + #fsl,serial-cells = <1>; + + tdm@0 { + /* TDMa */ + reg = <0>; + + clocks = <&clk_l1rsynca>, <&clk_l1rclka>; + clock-names = "l1rsync", "l1rclk"; + + fsl,common-rxtx-pins; + fsl,fsync-rising-edge; + + fsl,tx-ts-routes = <2 0>, /* TS 0..1 */ + <24 FSL_CPM_TSA_SCC4>, /* TS 2..25 */ + <1 0>, /* TS 26 */ + <5 FSL_CPM_TSA_SCC3>; /* TS 27..31 */ + + fsl,rx-ts-routes = <2 0>, /* TS 0..1 */ + <24 FSL_CPM_TSA_SCC4>, /* 2..25 */ + <1 0>, /* TS 26 */ + <5 FSL_CPM_TSA_SCC3>; /* TS 27..31 */ + }; + }; diff --git a/Documentation/devicetree/bindings/sound/fsl,qmc-audio.yaml b/Documentation/devicetree/bindings/sound/fsl,qmc-audio.yaml new file mode 100644 index 000000000000..ff5cd9241941 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,qmc-audio.yaml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/fsl,qmc-audio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: QMC audio + +maintainers: + - Herve Codina <herve.codina@bootlin.com> + +description: | + The QMC audio is an ASoC component which uses QMC (QUICC Multichannel + Controller) channels to transfer the audio data. + It provides as many DAI as the number of QMC channel used. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: fsl,qmc-audio + + '#address-cells': + const: 1 + '#size-cells': + const: 0 + '#sound-dai-cells': + const: 1 + +patternProperties: + '^dai@([0-9]|[1-5][0-9]|6[0-3])$': + description: + A DAI managed by this controller + type: object + + properties: + reg: + minimum: 0 + maximum: 63 + description: + The DAI number + + fsl,qmc-chan: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: phandle to QMC node + - description: Channel number + description: + Should be a phandle/number pair. The phandle to QMC node and the QMC + channel to use for this DAI. + + required: + - reg + - fsl,qmc-chan + +required: + - compatible + - '#address-cells' + - '#size-cells' + - '#sound-dai-cells' + +additionalProperties: false + +examples: + - | + audio_controller: audio-controller { + compatible = "fsl,qmc-audio"; + #address-cells = <1>; + #size-cells = <0>; + #sound-dai-cells = <1>; + dai@16 { + reg = <16>; + fsl,qmc-chan = <&qmc 16>; + }; + dai@17 { + reg = <17>; + fsl,qmc-chan = <&qmc 17>; + }; + }; + + sound { + compatible = "simple-audio-card"; + #address-cells = <1>; + #size-cells = <0>; + simple-audio-card,dai-link@0 { + reg = <0>; + format = "dsp_b"; + cpu { + sound-dai = <&audio_controller 16>; + }; + codec { + sound-dai = <&codec1>; + dai-tdm-slot-num = <4>; + dai-tdm-slot-width = <8>; + /* TS 3, 5, 7, 9 */ + dai-tdm-slot-tx-mask = <0 0 0 1 0 1 0 1 0 1>; + dai-tdm-slot-rx-mask = <0 0 0 1 0 1 0 1 0 1>; + }; + }; + simple-audio-card,dai-link@1 { + reg = <1>; + format = "dsp_b"; + cpu { + sound-dai = <&audio_controller 17>; + }; + codec { + sound-dai = <&codec2>; + dai-tdm-slot-num = <4>; + dai-tdm-slot-width = <8>; + /* TS 2, 4, 6, 8 */ + dai-tdm-slot-tx-mask = <0 0 1 0 1 0 1 0 1>; + dai-tdm-slot-rx-mask = <0 0 1 0 1 0 1 0 1>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/max9867.txt b/Documentation/devicetree/bindings/sound/max9867.txt deleted file mode 100644 index b8bd914ee697..000000000000 --- a/Documentation/devicetree/bindings/sound/max9867.txt +++ /dev/null @@ -1,17 +0,0 @@ -max9867 codec - -This device supports I2C mode only. - -Required properties: - -- compatible : "maxim,max9867" -- reg : The chip select number on the I2C bus - -Example: - -&i2c { - max9867: max9867@18 { - compatible = "maxim,max9867"; - reg = <0x18>; - }; -}; diff --git a/Documentation/devicetree/bindings/sound/maxim,max9867.yaml b/Documentation/devicetree/bindings/sound/maxim,max9867.yaml new file mode 100644 index 000000000000..6f27029b137d --- /dev/null +++ b/Documentation/devicetree/bindings/sound/maxim,max9867.yaml @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/maxim,max9867.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim Integrated MAX9867 CODEC + +description: | + This device supports I2C only. + Pins on the device (for linking into audio routes): + * LOUT + * ROUT + * LINL + * LINR + * MICL + * MICR + * DMICL + * DMICR + +maintainers: + - Ladislav Michl <ladis@linux-mips.org> + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + enum: + - maxim,max9867 + + '#sound-dai-cells': + const: 0 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - clocks + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + codec@18 { + compatible = "maxim,max9867"; + #sound-dai-cells = <0>; + reg = <0x18>; + clocks = <&codec_clk>; + }; + }; + + codec_clk: clock { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <12288000>; + }; +... diff --git a/Documentation/devicetree/bindings/sound/qcom,wcd9335.txt b/Documentation/devicetree/bindings/sound/qcom,wcd9335.txt deleted file mode 100644 index 1f75feec3dec..000000000000 --- a/Documentation/devicetree/bindings/sound/qcom,wcd9335.txt +++ /dev/null @@ -1,123 +0,0 @@ -QCOM WCD9335 Codec - -Qualcomm WCD9335 Codec is a standalone Hi-Fi audio codec IC, supports -Qualcomm Technologies, Inc. (QTI) multimedia solutions, including -the MSM8996, MSM8976, and MSM8956 chipsets. It has in-built -Soundwire controller, interrupt mux. It supports both I2S/I2C and -SLIMbus audio interfaces. - -Required properties with SLIMbus Interface: - -- compatible: - Usage: required - Value type: <stringlist> - Definition: For SLIMbus interface it should be "slimMID,PID", - textual representation of Manufacturer ID, Product Code, - shall be in lower case hexadecimal with leading zeroes - suppressed. Refer to slimbus/bus.txt for details. - Should be: - "slim217,1a0" for MSM8996 and APQ8096 SoCs with SLIMbus. - -- reg - Usage: required - Value type: <u32 u32> - Definition: Should be ('Device index', 'Instance ID') - -- interrupts - Usage: required - Value type: <prop-encoded-array> - Definition: Interrupts via WCD INTR1 and INTR2 pins - -- interrupt-names: - Usage: required - Value type: <String array> - Definition: Interrupt names of WCD INTR1 and INTR2 - Should be: "intr1", "intr2" - -- reset-gpios: - Usage: required - Value type: <String Array> - Definition: Reset gpio line - -- slim-ifc-dev: - Usage: required - Value type: <phandle> - Definition: SLIM interface device - -- clocks: - Usage: required - Value type: <prop-encoded-array> - Definition: See clock-bindings.txt section "consumers". List of - three clock specifiers for mclk, mclk2 and slimbus clock. - -- clock-names: - Usage: required - Value type: <string> - Definition: Must contain "mclk", "mclk2" and "slimbus" strings. - -- vdd-buck-supply: - Usage: required - Value type: <phandle> - Definition: Should contain a reference to the 1.8V buck supply - -- vdd-buck-sido-supply: - Usage: required - Value type: <phandle> - Definition: Should contain a reference to the 1.8V SIDO buck supply - -- vdd-rx-supply: - Usage: required - Value type: <phandle> - Definition: Should contain a reference to the 1.8V rx supply - -- vdd-tx-supply: - Usage: required - Value type: <phandle> - Definition: Should contain a reference to the 1.8V tx supply - -- vdd-vbat-supply: - Usage: Optional - Value type: <phandle> - Definition: Should contain a reference to the vbat supply - -- vdd-micbias-supply: - Usage: required - Value type: <phandle> - Definition: Should contain a reference to the micbias supply - -- vdd-io-supply: - Usage: required - Value type: <phandle> - Definition: Should contain a reference to the 1.8V io supply - -- interrupt-controller: - Usage: required - Definition: Indicating that this is a interrupt controller - -- #interrupt-cells: - Usage: required - Value type: <int> - Definition: should be 1 - -#sound-dai-cells - Usage: required - Value type: <u32> - Definition: Must be 1 - -audio-codec@1{ - compatible = "slim217,1a0"; - reg = <1 0>; - interrupts = <&msmgpio 54 IRQ_TYPE_LEVEL_HIGH>; - interrupt-names = "intr2" - reset-gpios = <&msmgpio 64 GPIO_ACTIVE_LOW>; - slim-ifc-dev = <&wc9335_ifd>; - clock-names = "mclk", "native"; - clocks = <&rpmcc RPM_SMD_DIV_CLK1>, - <&rpmcc RPM_SMD_BB_CLK1>; - vdd-buck-supply = <&pm8994_s4>; - vdd-rx-supply = <&pm8994_s4>; - vdd-buck-sido-supply = <&pm8994_s4>; - vdd-tx-supply = <&pm8994_s4>; - vdd-io-supply = <&pm8994_s4>; - #sound-dai-cells = <1>; -} diff --git a/Documentation/devicetree/bindings/sound/qcom,wcd9335.yaml b/Documentation/devicetree/bindings/sound/qcom,wcd9335.yaml new file mode 100644 index 000000000000..34f8fe4da9d4 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,wcd9335.yaml @@ -0,0 +1,156 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,wcd9335.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm WCD9335 Audio Codec + +maintainers: + - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +description: + Qualcomm WCD9335 Codec is a standalone Hi-Fi audio codec IC with in-built + Soundwire controller and interrupt mux. It supports both I2S/I2C and SLIMbus + audio interfaces. + +properties: + compatible: + const: slim217,1a0 + + reg: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: mclk + - const: slimbus + + interrupts: + maxItems: 2 + + interrupt-names: + items: + - const: intr1 + - const: intr2 + + interrupt-controller: true + + '#interrupt-cells': + const: 1 + + reset-gpios: + maxItems: 1 + + slim-ifc-dev: + description: SLIM IFC device interface + $ref: /schemas/types.yaml#/definitions/phandle + + '#sound-dai-cells': + const: 1 + + vdd-buck-supply: + description: 1.8V buck supply + + vdd-buck-sido-supply: + description: 1.8V SIDO buck supply + + vdd-io-supply: + description: 1.8V I/O supply + + vdd-micbias-supply: + description: micbias supply + + vdd-rx-supply: + description: 1.8V rx supply + + vdd-tx-supply: + description: 1.8V tx supply + + vdd-vbat-supply: + description: vbat supply + +required: + - compatible + - reg + +allOf: + - $ref: dai-common.yaml# + - if: + required: + - slim-ifc-dev + then: + required: + - clocks + - clock-names + - interrupts + - interrupt-names + - interrupt-controller + - '#interrupt-cells' + - reset-gpios + - slim-ifc-dev + - '#sound-dai-cells' + - vdd-buck-supply + - vdd-buck-sido-supply + - vdd-io-supply + - vdd-rx-supply + - vdd-tx-supply + else: + properties: + clocks: false + clock-names: false + interrupts: false + interrupt-names: false + interrupt-controller: false + '#interrupt-cells': false + reset-gpios: false + slim-ifc-dev: false + '#sound-dai-cells': false + vdd-buck-supply: false + vdd-buck-sido-supply: false + vdd-io-supply: false + vdd-micbias-supply: false + vdd-rx-supply: false + vdd-tx-supply: false + vdd-vbat-supply: false + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/qcom,rpmcc.h> + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + tasha_ifd: codec@0,0 { + compatible = "slim217,1a0"; + reg = <0 0>; + }; + + codec@1,0 { + compatible = "slim217,1a0"; + reg = <1 0>; + + clock-names = "mclk", "slimbus"; + clocks = <&div1_mclk>, <&rpmcc RPM_SMD_BB_CLK1>; + + interrupt-parent = <&tlmm>; + interrupts = <54 IRQ_TYPE_LEVEL_HIGH>, + <53 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "intr1", "intr2"; + interrupt-controller; + #interrupt-cells = <1>; + + reset-gpios = <&tlmm 64 GPIO_ACTIVE_LOW>; + slim-ifc-dev = <&tasha_ifd>; + #sound-dai-cells = <1>; + + vdd-buck-supply = <&vreg_s4a_1p8>; + vdd-buck-sido-supply = <&vreg_s4a_1p8>; + vdd-tx-supply = <&vreg_s4a_1p8>; + vdd-rx-supply = <&vreg_s4a_1p8>; + vdd-io-supply = <&vreg_s4a_1p8>; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,wcd934x.yaml b/Documentation/devicetree/bindings/sound/qcom,wcd934x.yaml index ea09590bfa30..ae8346b9d332 100644 --- a/Documentation/devicetree/bindings/sound/qcom,wcd934x.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,wcd934x.yaml @@ -151,6 +151,7 @@ required: - reg allOf: + - $ref: dai-common.yaml# - if: required: - slim-ifc-dev diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml b/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml index 12ccf29338d9..55e5213b90a1 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml @@ -101,17 +101,7 @@ properties: clock-names: description: List of necessary clock names. - minItems: 1 - maxItems: 31 - items: - oneOf: - - const: ssi-all - - pattern: '^ssi\.[0-9]$' - - pattern: '^src\.[0-9]$' - - pattern: '^mix\.[0-1]$' - - pattern: '^ctu\.[0-1]$' - - pattern: '^dvc\.[0-1]$' - - pattern: '^clk_(a|b|c|i)$' + # details are defined below ports: $ref: audio-graph-port.yaml#/definitions/port-base @@ -288,6 +278,11 @@ required: allOf: - $ref: dai-common.yaml# + + #-------------------- + # reg/reg-names + #-------------------- + # for Gen1 - if: properties: compatible: @@ -303,7 +298,15 @@ allOf: - scu - ssi - adg - else: + # for Gen2/Gen3 + - if: + properties: + compatible: + contains: + enum: + - renesas,rcar_sound-gen2 + - renesas,rcar_sound-gen3 + then: properties: reg: minItems: 5 @@ -315,6 +318,55 @@ allOf: - ssiu - ssi - audmapp + # for Gen4 + - if: + properties: + compatible: + contains: + const: renesas,rcar_sound-gen4 + then: + properties: + reg: + maxItems: 4 + reg-names: + items: + enum: + - adg + - ssiu + - ssi + - sdmc + + #-------------------- + # clock-names + #-------------------- + - if: + properties: + compatible: + contains: + const: renesas,rcar_sound-gen4 + then: + properties: + clock-names: + maxItems: 3 + items: + enum: + - ssi.0 + - ssiu.0 + - clkin + else: + properties: + clock-names: + minItems: 1 + maxItems: 31 + items: + oneOf: + - const: ssi-all + - pattern: '^ssi\.[0-9]$' + - pattern: '^src\.[0-9]$' + - pattern: '^mix\.[0-1]$' + - pattern: '^ctu\.[0-1]$' + - pattern: '^dvc\.[0-1]$' + - pattern: '^clk_(a|b|c|i)$' unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml b/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml index 196881d94396..3b5ae45eee4a 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml +++ b/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml @@ -25,14 +25,18 @@ properties: maxItems: 1 interrupts: - maxItems: 4 + minItems: 2 + maxItems: 3 interrupt-names: - items: - - const: int_req - - const: dma_rx - - const: dma_tx - - const: dma_rt + oneOf: + - items: + - const: int_req + - const: dma_rx + - const: dma_tx + - items: + - const: int_req + - const: dma_rt clocks: maxItems: 4 @@ -106,9 +110,8 @@ examples: reg = <0x10049c00 0x400>; interrupts = <GIC_SPI 326 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 327 IRQ_TYPE_EDGE_RISING>, - <GIC_SPI 328 IRQ_TYPE_EDGE_RISING>, - <GIC_SPI 329 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "int_req", "dma_rx", "dma_tx", "dma_rt"; + <GIC_SPI 328 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "int_req", "dma_rx", "dma_tx"; clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>, <&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>, <&audio_clk1>, diff --git a/Documentation/devicetree/bindings/sound/tas571x.txt b/Documentation/devicetree/bindings/sound/tas571x.txt index 7c8fd37c2f9e..1addc75989d5 100644 --- a/Documentation/devicetree/bindings/sound/tas571x.txt +++ b/Documentation/devicetree/bindings/sound/tas571x.txt @@ -12,6 +12,7 @@ Required properties: - "ti,tas5717", - "ti,tas5719", - "ti,tas5721" + - "ti,tas5733" - reg: The I2C address of the device - #sound-dai-cells: must be equal to 0 diff --git a/Documentation/devicetree/bindings/sound/wlf,wm8524.yaml b/Documentation/devicetree/bindings/sound/wlf,wm8524.yaml new file mode 100644 index 000000000000..4d951ece394e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wlf,wm8524.yaml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/wlf,wm8524.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Wolfson WM8524 24-bit 192KHz Stereo DAC + +maintainers: + - patches@opensource.cirrus.com + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: wlf,wm8524 + + "#sound-dai-cells": + const: 0 + + wlf,mute-gpios: + maxItems: 1 + description: + a GPIO spec for the MUTE pin. + +required: + - compatible + - wlf,mute-gpios + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + wm8524: codec { + compatible = "wlf,wm8524"; + wlf,mute-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>; + }; diff --git a/Documentation/devicetree/bindings/sound/wlf,wm8960.yaml b/Documentation/devicetree/bindings/sound/wlf,wm8960.yaml new file mode 100644 index 000000000000..ee8eba7f0104 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wlf,wm8960.yaml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/wlf,wm8960.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Wolfson WM8960 audio codec + +maintainers: + - patches@opensource.cirrus.com + +properties: + compatible: + const: wlf,wm8960 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: mclk + + '#sound-dai-cells': + const: 0 + + wlf,capless: + type: boolean + description: + If present, OUT3 pin will be enabled and disabled together with HP_L and + HP_R pins in response to jack detect events. + + wlf,gpio-cfg: + $ref: /schemas/types.yaml#/definitions/uint32-array + maxItems: 2 + description: | + A list of GPIO configuration register values. + - gpio-cfg[0]: ALRCGPIO of R9 (Audio interface) + - gpio-cfg[1]: {GPIOPOL:GPIOSEL[2:0]} of R48 (Additional Control 4). + + wlf,hp-cfg: + $ref: /schemas/types.yaml#/definitions/uint32-array + maxItems: 3 + description: | + A list of headphone jack detect configuration register values: + - hp-cfg[0]: HPSEL[1:0] of R48 (Additional Control 4). + - hp-cfg[1]: {HPSWEN:HPSWPOL} of R24 (Additional Control 2). + - hp-cfg[2]: {TOCLKSEL:TOEN} of R23 (Additional Control 1). + + wlf,shared-lrclk: + type: boolean + description: + If present, the LRCM bit of R24 (Additional control 2) gets set, + indicating that ADCLRC and DACLRC pins will be disabled only when ADC + (Left and Right) and DAC (Left and Right) are disabled. + When WM8960 works on synchronize mode and DACLRC pin is used to supply + frame clock, it will no frame clock for captrue unless enable DAC to + enable DACLRC pin. If shared-lrclk is present, no need to enable DAC for + captrue. + +required: + - compatible + - reg + +allOf: + - $ref: dai-common.yaml# + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + audio-codec@1a { + compatible = "wlf,wm8960"; + reg = <0x1a>; + clocks = <&clks 0>; + clock-names = "mclk"; + #sound-dai-cells = <0>; + wlf,hp-cfg = <3 2 3>; + wlf,gpio-cfg = <1 3>; + wlf,shared-lrclk; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/wm8524.txt b/Documentation/devicetree/bindings/sound/wm8524.txt deleted file mode 100644 index f6c0c263b135..000000000000 --- a/Documentation/devicetree/bindings/sound/wm8524.txt +++ /dev/null @@ -1,16 +0,0 @@ -WM8524 audio CODEC - -This device does not use I2C or SPI but a simple Hardware Control Interface. - -Required properties: - - - compatible : "wlf,wm8524" - - - wlf,mute-gpios: a GPIO spec for the MUTE pin. - -Example: - -wm8524: codec { - compatible = "wlf,wm8524"; - wlf,mute-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>; -}; diff --git a/Documentation/devicetree/bindings/sound/wm8960.txt b/Documentation/devicetree/bindings/sound/wm8960.txt deleted file mode 100644 index 85d3b287108c..000000000000 --- a/Documentation/devicetree/bindings/sound/wm8960.txt +++ /dev/null @@ -1,42 +0,0 @@ -WM8960 audio CODEC - -This device supports I2C only. - -Required properties: - - - compatible : "wlf,wm8960" - - - reg : the I2C address of the device. - -Optional properties: - - wlf,shared-lrclk: This is a boolean property. If present, the LRCM bit of - R24 (Additional control 2) gets set, indicating that ADCLRC and DACLRC pins - will be disabled only when ADC (Left and Right) and DAC (Left and Right) - are disabled. - When wm8960 works on synchronize mode and DACLRC pin is used to supply - frame clock, it will no frame clock for captrue unless enable DAC to enable - DACLRC pin. If shared-lrclk is present, no need to enable DAC for captrue. - - - wlf,capless: This is a boolean property. If present, OUT3 pin will be - enabled and disabled together with HP_L and HP_R pins in response to jack - detect events. - - - wlf,hp-cfg: A list of headphone jack detect configuration register values. - The list must be 3 entries long. - hp-cfg[0]: HPSEL[1:0] of R48 (Additional Control 4). - hp-cfg[1]: {HPSWEN:HPSWPOL} of R24 (Additional Control 2). - hp-cfg[2]: {TOCLKSEL:TOEN} of R23 (Additional Control 1). - - - wlf,gpio-cfg: A list of GPIO configuration register values. - The list must be 2 entries long. - gpio-cfg[0]: ALRCGPIO of R9 (Audio interface) - gpio-cfg[1]: {GPIOPOL:GPIOSEL[2:0]} of R48 (Additional Control 4). - -Example: - -wm8960: codec@1a { - compatible = "wlf,wm8960"; - reg = <0x1a>; - - wlf,shared-lrclk; -}; diff --git a/MAINTAINERS b/MAINTAINERS index 8d5bc223f305..6e7335ece603 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8247,6 +8247,23 @@ S: Maintained F: drivers/soc/fsl/qe/ F: include/soc/fsl/qe/ +FREESCALE QUICC ENGINE QMC DRIVER +M: Herve Codina <herve.codina@bootlin.com> +L: linuxppc-dev@lists.ozlabs.org +S: Maintained +F: Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-scc-qmc.yaml +F: drivers/soc/fsl/qe/qmc.c +F: include/soc/fsl/qe/qmc.h + +FREESCALE QUICC ENGINE TSA DRIVER +M: Herve Codina <herve.codina@bootlin.com> +L: linuxppc-dev@lists.ozlabs.org +S: Maintained +F: Documentation/devicetree/bindings/soc/fsl/cpm_qe/fsl,cpm1-tsa.yaml +F: drivers/soc/fsl/qe/tsa.c +F: drivers/soc/fsl/qe/tsa.h +F: include/dt-bindings/soc/cpm1-fsl,tsa.h + FREESCALE QUICC ENGINE UCC ETHERNET DRIVER M: Li Yang <leoyang.li@nxp.com> L: netdev@vger.kernel.org @@ -8298,6 +8315,14 @@ F: sound/soc/fsl/fsl* F: sound/soc/fsl/imx* F: sound/soc/fsl/mpc8610_hpcd.c +FREESCALE SOC SOUND QMC DRIVER +M: Herve Codina <herve.codina@bootlin.com> +L: alsa-devel@alsa-project.org (moderated for non-subscribers) +L: linuxppc-dev@lists.ozlabs.org +S: Maintained +F: Documentation/devicetree/bindings/sound/fsl,qmc-audio.yaml +F: sound/soc/fsl/fsl_qmc_audio.c + FREESCALE USB PERIPHERAL DRIVERS M: Li Yang <leoyang.li@nxp.com> L: linux-usb@vger.kernel.org @@ -13593,9 +13618,13 @@ F: Documentation/devicetree/bindings/serial/atmel,at91-usart.yaml F: drivers/spi/spi-at91-usart.c MICROCHIP AUDIO ASOC DRIVERS -M: Codrin Ciubotariu <codrin.ciubotariu@microchip.com> +M: Claudiu Beznea <claudiu.beznea@microchip.com> L: alsa-devel@alsa-project.org (moderated for non-subscribers) S: Supported +F: Documentation/devicetree/bindings/sound/atmel* +F: Documentation/devicetree/bindings/sound/axentia,tse850-pcm5142.txt +F: Documentation/devicetree/bindings/sound/microchip,sama7g5-* +F: Documentation/devicetree/bindings/sound/mikroe,mikroe-proto.txt F: sound/soc/atmel MICROCHIP CSI2DC DRIVER @@ -13768,9 +13797,10 @@ S: Supported F: drivers/spi/spi-atmel.* MICROCHIP SSC DRIVER -M: Codrin Ciubotariu <codrin.ciubotariu@microchip.com> +M: Claudiu Beznea <claudiu.beznea@microchip.com> L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Supported +F: Documentation/devicetree/bindings/misc/atmel-ssc.txt F: drivers/misc/atmel-ssc.c F: include/linux/atmel-ssc.h diff --git a/arch/powerpc/platforms/8xx/cpm1.c b/arch/powerpc/platforms/8xx/cpm1.c index bb38c8d8f8de..c57fcda7a4bb 100644 --- a/arch/powerpc/platforms/8xx/cpm1.c +++ b/arch/powerpc/platforms/8xx/cpm1.c @@ -94,7 +94,7 @@ int cpm_command(u32 command, u8 opcode) int i, ret; unsigned long flags; - if (command & 0xffffff0f) + if (command & 0xffffff03) return -EINVAL; spin_lock_irqsave(&cmd_lock, flags); diff --git a/drivers/soc/fsl/qe/Kconfig b/drivers/soc/fsl/qe/Kconfig index 357c5800b112..f90cfdf0c763 100644 --- a/drivers/soc/fsl/qe/Kconfig +++ b/drivers/soc/fsl/qe/Kconfig @@ -33,6 +33,29 @@ config UCC bool default y if UCC_FAST || UCC_SLOW +config CPM_TSA + tristate "CPM TSA support" + depends on OF && HAS_IOMEM + depends on CPM1 || COMPILE_TEST + help + Freescale CPM Time Slot Assigner (TSA) + controller. + + This option enables support for this + controller + +config CPM_QMC + tristate "CPM QMC support" + depends on OF && HAS_IOMEM + depends on CPM1 || (SOC_FSL && COMPILE_TEST) + depends on CPM_TSA + help + Freescale CPM QUICC Multichannel Controller + (QMC) + + This option enables support for this + controller + config QE_TDM bool default y if FSL_UCC_HDLC diff --git a/drivers/soc/fsl/qe/Makefile b/drivers/soc/fsl/qe/Makefile index 55a555304f3a..ec8506e13113 100644 --- a/drivers/soc/fsl/qe/Makefile +++ b/drivers/soc/fsl/qe/Makefile @@ -4,6 +4,8 @@ # obj-$(CONFIG_QUICC_ENGINE)+= qe.o qe_common.o qe_ic.o qe_io.o obj-$(CONFIG_CPM) += qe_common.o +obj-$(CONFIG_CPM_TSA) += tsa.o +obj-$(CONFIG_CPM_QMC) += qmc.o obj-$(CONFIG_UCC) += ucc.o obj-$(CONFIG_UCC_SLOW) += ucc_slow.o obj-$(CONFIG_UCC_FAST) += ucc_fast.o diff --git a/drivers/soc/fsl/qe/qmc.c b/drivers/soc/fsl/qe/qmc.c new file mode 100644 index 000000000000..cfa7207353e0 --- /dev/null +++ b/drivers/soc/fsl/qe/qmc.c @@ -0,0 +1,1533 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * QMC driver + * + * Copyright 2022 CS GROUP France + * + * Author: Herve Codina <herve.codina@bootlin.com> + */ + +#include <soc/fsl/qe/qmc.h> +#include <linux/dma-mapping.h> +#include <linux/hdlc.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <soc/fsl/cpm.h> +#include <sysdev/fsl_soc.h> +#include "tsa.h" + +/* SCC general mode register high (32 bits) */ +#define SCC_GSMRL 0x00 +#define SCC_GSMRL_ENR (1 << 5) +#define SCC_GSMRL_ENT (1 << 4) +#define SCC_GSMRL_MODE_QMC (0x0A << 0) + +/* SCC general mode register low (32 bits) */ +#define SCC_GSMRH 0x04 +#define SCC_GSMRH_CTSS (1 << 7) +#define SCC_GSMRH_CDS (1 << 8) +#define SCC_GSMRH_CTSP (1 << 9) +#define SCC_GSMRH_CDP (1 << 10) + +/* SCC event register (16 bits) */ +#define SCC_SCCE 0x10 +#define SCC_SCCE_IQOV (1 << 3) +#define SCC_SCCE_GINT (1 << 2) +#define SCC_SCCE_GUN (1 << 1) +#define SCC_SCCE_GOV (1 << 0) + +/* SCC mask register (16 bits) */ +#define SCC_SCCM 0x14 +/* Multichannel base pointer (32 bits) */ +#define QMC_GBL_MCBASE 0x00 +/* Multichannel controller state (16 bits) */ +#define QMC_GBL_QMCSTATE 0x04 +/* Maximum receive buffer length (16 bits) */ +#define QMC_GBL_MRBLR 0x06 +/* Tx time-slot assignment table pointer (16 bits) */ +#define QMC_GBL_TX_S_PTR 0x08 +/* Rx pointer (16 bits) */ +#define QMC_GBL_RXPTR 0x0A +/* Global receive frame threshold (16 bits) */ +#define QMC_GBL_GRFTHR 0x0C +/* Global receive frame count (16 bits) */ +#define QMC_GBL_GRFCNT 0x0E +/* Multichannel interrupt base address (32 bits) */ +#define QMC_GBL_INTBASE 0x10 +/* Multichannel interrupt pointer (32 bits) */ +#define QMC_GBL_INTPTR 0x14 +/* Rx time-slot assignment table pointer (16 bits) */ +#define QMC_GBL_RX_S_PTR 0x18 +/* Tx pointer (16 bits) */ +#define QMC_GBL_TXPTR 0x1A +/* CRC constant (32 bits) */ +#define QMC_GBL_C_MASK32 0x1C +/* Time slot assignment table Rx (32 x 16 bits) */ +#define QMC_GBL_TSATRX 0x20 +/* Time slot assignment table Tx (32 x 16 bits) */ +#define QMC_GBL_TSATTX 0x60 +/* CRC constant (16 bits) */ +#define QMC_GBL_C_MASK16 0xA0 + +/* TSA entry (16bit entry in TSATRX and TSATTX) */ +#define QMC_TSA_VALID (1 << 15) +#define QMC_TSA_WRAP (1 << 14) +#define QMC_TSA_MASK (0x303F) +#define QMC_TSA_CHANNEL(x) ((x) << 6) + +/* Tx buffer descriptor base address (16 bits, offset from MCBASE) */ +#define QMC_SPE_TBASE 0x00 + +/* Channel mode register (16 bits) */ +#define QMC_SPE_CHAMR 0x02 +#define QMC_SPE_CHAMR_MODE_HDLC (1 << 15) +#define QMC_SPE_CHAMR_MODE_TRANSP ((0 << 15) | (1 << 13)) +#define QMC_SPE_CHAMR_ENT (1 << 12) +#define QMC_SPE_CHAMR_POL (1 << 8) +#define QMC_SPE_CHAMR_HDLC_IDLM (1 << 13) +#define QMC_SPE_CHAMR_HDLC_CRC (1 << 7) +#define QMC_SPE_CHAMR_HDLC_NOF (0x0f << 0) +#define QMC_SPE_CHAMR_TRANSP_RD (1 << 14) +#define QMC_SPE_CHAMR_TRANSP_SYNC (1 << 10) + +/* Tx internal state (32 bits) */ +#define QMC_SPE_TSTATE 0x04 +/* Tx buffer descriptor pointer (16 bits) */ +#define QMC_SPE_TBPTR 0x0C +/* Zero-insertion state (32 bits) */ +#define QMC_SPE_ZISTATE 0x14 +/* Channel’s interrupt mask flags (16 bits) */ +#define QMC_SPE_INTMSK 0x1C +/* Rx buffer descriptor base address (16 bits, offset from MCBASE) */ +#define QMC_SPE_RBASE 0x20 +/* HDLC: Maximum frame length register (16 bits) */ +#define QMC_SPE_MFLR 0x22 +/* TRANSPARENT: Transparent maximum receive length (16 bits) */ +#define QMC_SPE_TMRBLR 0x22 +/* Rx internal state (32 bits) */ +#define QMC_SPE_RSTATE 0x24 +/* Rx buffer descriptor pointer (16 bits) */ +#define QMC_SPE_RBPTR 0x2C +/* Packs 4 bytes to 1 long word before writing to buffer (32 bits) */ +#define QMC_SPE_RPACK 0x30 +/* Zero deletion state (32 bits) */ +#define QMC_SPE_ZDSTATE 0x34 + +/* Transparent synchronization (16 bits) */ +#define QMC_SPE_TRNSYNC 0x3C +#define QMC_SPE_TRNSYNC_RX(x) ((x) << 8) +#define QMC_SPE_TRNSYNC_TX(x) ((x) << 0) + +/* Interrupt related registers bits */ +#define QMC_INT_V (1 << 15) +#define QMC_INT_W (1 << 14) +#define QMC_INT_NID (1 << 13) +#define QMC_INT_IDL (1 << 12) +#define QMC_INT_GET_CHANNEL(x) (((x) & 0x0FC0) >> 6) +#define QMC_INT_MRF (1 << 5) +#define QMC_INT_UN (1 << 4) +#define QMC_INT_RXF (1 << 3) +#define QMC_INT_BSY (1 << 2) +#define QMC_INT_TXB (1 << 1) +#define QMC_INT_RXB (1 << 0) + +/* BD related registers bits */ +#define QMC_BD_RX_E (1 << 15) +#define QMC_BD_RX_W (1 << 13) +#define QMC_BD_RX_I (1 << 12) +#define QMC_BD_RX_L (1 << 11) +#define QMC_BD_RX_F (1 << 10) +#define QMC_BD_RX_CM (1 << 9) +#define QMC_BD_RX_UB (1 << 7) +#define QMC_BD_RX_LG (1 << 5) +#define QMC_BD_RX_NO (1 << 4) +#define QMC_BD_RX_AB (1 << 3) +#define QMC_BD_RX_CR (1 << 2) + +#define QMC_BD_TX_R (1 << 15) +#define QMC_BD_TX_W (1 << 13) +#define QMC_BD_TX_I (1 << 12) +#define QMC_BD_TX_L (1 << 11) +#define QMC_BD_TX_TC (1 << 10) +#define QMC_BD_TX_CM (1 << 9) +#define QMC_BD_TX_UB (1 << 7) +#define QMC_BD_TX_PAD (0x0f << 0) + +/* Numbers of BDs and interrupt items */ +#define QMC_NB_TXBDS 8 +#define QMC_NB_RXBDS 8 +#define QMC_NB_INTS 128 + +struct qmc_xfer_desc { + union { + void (*tx_complete)(void *context); + void (*rx_complete)(void *context, size_t length); + }; + void *context; +}; + +struct qmc_chan { + struct list_head list; + unsigned int id; + struct qmc *qmc; + void *__iomem s_param; + enum qmc_mode mode; + u64 tx_ts_mask; + u64 rx_ts_mask; + bool is_reverse_data; + + spinlock_t tx_lock; + cbd_t __iomem *txbds; + cbd_t __iomem *txbd_free; + cbd_t __iomem *txbd_done; + struct qmc_xfer_desc tx_desc[QMC_NB_TXBDS]; + u64 nb_tx_underrun; + bool is_tx_stopped; + + spinlock_t rx_lock; + cbd_t __iomem *rxbds; + cbd_t __iomem *rxbd_free; + cbd_t __iomem *rxbd_done; + struct qmc_xfer_desc rx_desc[QMC_NB_RXBDS]; + u64 nb_rx_busy; + int rx_pending; + bool is_rx_halted; + bool is_rx_stopped; +}; + +struct qmc { + struct device *dev; + struct tsa_serial *tsa_serial; + void *__iomem scc_regs; + void *__iomem scc_pram; + void *__iomem dpram; + u16 scc_pram_offset; + cbd_t __iomem *bd_table; + dma_addr_t bd_dma_addr; + size_t bd_size; + u16 __iomem *int_table; + u16 __iomem *int_curr; + dma_addr_t int_dma_addr; + size_t int_size; + struct list_head chan_head; + struct qmc_chan *chans[64]; +}; + +static inline void qmc_write16(void *__iomem addr, u16 val) +{ + iowrite16be(val, addr); +} + +static inline u16 qmc_read16(void *__iomem addr) +{ + return ioread16be(addr); +} + +static inline void qmc_setbits16(void *__iomem addr, u16 set) +{ + qmc_write16(addr, qmc_read16(addr) | set); +} + +static inline void qmc_clrbits16(void *__iomem addr, u16 clr) +{ + qmc_write16(addr, qmc_read16(addr) & ~clr); +} + +static inline void qmc_write32(void *__iomem addr, u32 val) +{ + iowrite32be(val, addr); +} + +static inline u32 qmc_read32(void *__iomem addr) +{ + return ioread32be(addr); +} + +static inline void qmc_setbits32(void *__iomem addr, u32 set) +{ + qmc_write32(addr, qmc_read32(addr) | set); +} + + +int qmc_chan_get_info(struct qmc_chan *chan, struct qmc_chan_info *info) +{ + struct tsa_serial_info tsa_info; + int ret; + + /* Retrieve info from the TSA related serial */ + ret = tsa_serial_get_info(chan->qmc->tsa_serial, &tsa_info); + if (ret) + return ret; + + info->mode = chan->mode; + info->rx_fs_rate = tsa_info.rx_fs_rate; + info->rx_bit_rate = tsa_info.rx_bit_rate; + info->nb_tx_ts = hweight64(chan->tx_ts_mask); + info->tx_fs_rate = tsa_info.tx_fs_rate; + info->tx_bit_rate = tsa_info.tx_bit_rate; + info->nb_rx_ts = hweight64(chan->rx_ts_mask); + + return 0; +} +EXPORT_SYMBOL(qmc_chan_get_info); + +int qmc_chan_set_param(struct qmc_chan *chan, const struct qmc_chan_param *param) +{ + if (param->mode != chan->mode) + return -EINVAL; + + switch (param->mode) { + case QMC_HDLC: + if ((param->hdlc.max_rx_buf_size % 4) || + (param->hdlc.max_rx_buf_size < 8)) + return -EINVAL; + + qmc_write16(chan->qmc->scc_pram + QMC_GBL_MRBLR, + param->hdlc.max_rx_buf_size - 8); + qmc_write16(chan->s_param + QMC_SPE_MFLR, + param->hdlc.max_rx_frame_size); + if (param->hdlc.is_crc32) { + qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, + QMC_SPE_CHAMR_HDLC_CRC); + } else { + qmc_clrbits16(chan->s_param + QMC_SPE_CHAMR, + QMC_SPE_CHAMR_HDLC_CRC); + } + break; + + case QMC_TRANSPARENT: + qmc_write16(chan->s_param + QMC_SPE_TMRBLR, + param->transp.max_rx_buf_size); + break; + + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(qmc_chan_set_param); + +int qmc_chan_write_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length, + void (*complete)(void *context), void *context) +{ + struct qmc_xfer_desc *xfer_desc; + unsigned long flags; + cbd_t *__iomem bd; + u16 ctrl; + int ret; + + /* + * R bit UB bit + * 0 0 : The BD is free + * 1 1 : The BD is in used, waiting for transfer + * 0 1 : The BD is in used, waiting for completion + * 1 0 : Should not append + */ + + spin_lock_irqsave(&chan->tx_lock, flags); + bd = chan->txbd_free; + + ctrl = qmc_read16(&bd->cbd_sc); + if (ctrl & (QMC_BD_TX_R | QMC_BD_TX_UB)) { + /* We are full ... */ + ret = -EBUSY; + goto end; + } + + qmc_write16(&bd->cbd_datlen, length); + qmc_write32(&bd->cbd_bufaddr, addr); + + xfer_desc = &chan->tx_desc[bd - chan->txbds]; + xfer_desc->tx_complete = complete; + xfer_desc->context = context; + + /* Activate the descriptor */ + ctrl |= (QMC_BD_TX_R | QMC_BD_TX_UB); + wmb(); /* Be sure to flush the descriptor before control update */ + qmc_write16(&bd->cbd_sc, ctrl); + + if (!chan->is_tx_stopped) + qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_POL); + + if (ctrl & QMC_BD_TX_W) + chan->txbd_free = chan->txbds; + else + chan->txbd_free++; + + ret = 0; + +end: + spin_unlock_irqrestore(&chan->tx_lock, flags); + return ret; +} +EXPORT_SYMBOL(qmc_chan_write_submit); + +static void qmc_chan_write_done(struct qmc_chan *chan) +{ + struct qmc_xfer_desc *xfer_desc; + void (*complete)(void *context); + unsigned long flags; + void *context; + cbd_t *__iomem bd; + u16 ctrl; + + /* + * R bit UB bit + * 0 0 : The BD is free + * 1 1 : The BD is in used, waiting for transfer + * 0 1 : The BD is in used, waiting for completion + * 1 0 : Should not append + */ + + spin_lock_irqsave(&chan->tx_lock, flags); + bd = chan->txbd_done; + + ctrl = qmc_read16(&bd->cbd_sc); + while (!(ctrl & QMC_BD_TX_R)) { + if (!(ctrl & QMC_BD_TX_UB)) + goto end; + + xfer_desc = &chan->tx_desc[bd - chan->txbds]; + complete = xfer_desc->tx_complete; + context = xfer_desc->context; + xfer_desc->tx_complete = NULL; + xfer_desc->context = NULL; + + qmc_write16(&bd->cbd_sc, ctrl & ~QMC_BD_TX_UB); + + if (ctrl & QMC_BD_TX_W) + chan->txbd_done = chan->txbds; + else + chan->txbd_done++; + + if (complete) { + spin_unlock_irqrestore(&chan->tx_lock, flags); + complete(context); + spin_lock_irqsave(&chan->tx_lock, flags); + } + + bd = chan->txbd_done; + ctrl = qmc_read16(&bd->cbd_sc); + } + +end: + spin_unlock_irqrestore(&chan->tx_lock, flags); +} + +int qmc_chan_read_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length, + void (*complete)(void *context, size_t length), void *context) +{ + struct qmc_xfer_desc *xfer_desc; + unsigned long flags; + cbd_t *__iomem bd; + u16 ctrl; + int ret; + + /* + * E bit UB bit + * 0 0 : The BD is free + * 1 1 : The BD is in used, waiting for transfer + * 0 1 : The BD is in used, waiting for completion + * 1 0 : Should not append + */ + + spin_lock_irqsave(&chan->rx_lock, flags); + bd = chan->rxbd_free; + + ctrl = qmc_read16(&bd->cbd_sc); + if (ctrl & (QMC_BD_RX_E | QMC_BD_RX_UB)) { + /* We are full ... */ + ret = -EBUSY; + goto end; + } + + qmc_write16(&bd->cbd_datlen, 0); /* data length is updated by the QMC */ + qmc_write32(&bd->cbd_bufaddr, addr); + + xfer_desc = &chan->rx_desc[bd - chan->rxbds]; + xfer_desc->rx_complete = complete; + xfer_desc->context = context; + + /* Activate the descriptor */ + ctrl |= (QMC_BD_RX_E | QMC_BD_RX_UB); + wmb(); /* Be sure to flush data before descriptor activation */ + qmc_write16(&bd->cbd_sc, ctrl); + + /* Restart receiver if needed */ + if (chan->is_rx_halted && !chan->is_rx_stopped) { + /* Restart receiver */ + if (chan->mode == QMC_TRANSPARENT) + qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080); + else + qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080); + qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000); + chan->is_rx_halted = false; + } + chan->rx_pending++; + + if (ctrl & QMC_BD_RX_W) + chan->rxbd_free = chan->rxbds; + else + chan->rxbd_free++; + + ret = 0; +end: + spin_unlock_irqrestore(&chan->rx_lock, flags); + return ret; +} +EXPORT_SYMBOL(qmc_chan_read_submit); + +static void qmc_chan_read_done(struct qmc_chan *chan) +{ + void (*complete)(void *context, size_t size); + struct qmc_xfer_desc *xfer_desc; + unsigned long flags; + cbd_t *__iomem bd; + void *context; + u16 datalen; + u16 ctrl; + + /* + * E bit UB bit + * 0 0 : The BD is free + * 1 1 : The BD is in used, waiting for transfer + * 0 1 : The BD is in used, waiting for completion + * 1 0 : Should not append + */ + + spin_lock_irqsave(&chan->rx_lock, flags); + bd = chan->rxbd_done; + + ctrl = qmc_read16(&bd->cbd_sc); + while (!(ctrl & QMC_BD_RX_E)) { + if (!(ctrl & QMC_BD_RX_UB)) + goto end; + + xfer_desc = &chan->rx_desc[bd - chan->rxbds]; + complete = xfer_desc->rx_complete; + context = xfer_desc->context; + xfer_desc->rx_complete = NULL; + xfer_desc->context = NULL; + + datalen = qmc_read16(&bd->cbd_datlen); + qmc_write16(&bd->cbd_sc, ctrl & ~QMC_BD_RX_UB); + + if (ctrl & QMC_BD_RX_W) + chan->rxbd_done = chan->rxbds; + else + chan->rxbd_done++; + + chan->rx_pending--; + + if (complete) { + spin_unlock_irqrestore(&chan->rx_lock, flags); + complete(context, datalen); + spin_lock_irqsave(&chan->rx_lock, flags); + } + + bd = chan->rxbd_done; + ctrl = qmc_read16(&bd->cbd_sc); + } + +end: + spin_unlock_irqrestore(&chan->rx_lock, flags); +} + +static int qmc_chan_command(struct qmc_chan *chan, u8 qmc_opcode) +{ + return cpm_command(chan->id << 2, (qmc_opcode << 4) | 0x0E); +} + +static int qmc_chan_stop_rx(struct qmc_chan *chan) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&chan->rx_lock, flags); + + /* Send STOP RECEIVE command */ + ret = qmc_chan_command(chan, 0x0); + if (ret) { + dev_err(chan->qmc->dev, "chan %u: Send STOP RECEIVE failed (%d)\n", + chan->id, ret); + goto end; + } + + chan->is_rx_stopped = true; + +end: + spin_unlock_irqrestore(&chan->rx_lock, flags); + return ret; +} + +static int qmc_chan_stop_tx(struct qmc_chan *chan) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&chan->tx_lock, flags); + + /* Send STOP TRANSMIT command */ + ret = qmc_chan_command(chan, 0x1); + if (ret) { + dev_err(chan->qmc->dev, "chan %u: Send STOP TRANSMIT failed (%d)\n", + chan->id, ret); + goto end; + } + + chan->is_tx_stopped = true; + +end: + spin_unlock_irqrestore(&chan->tx_lock, flags); + return ret; +} + +int qmc_chan_stop(struct qmc_chan *chan, int direction) +{ + int ret; + + if (direction & QMC_CHAN_READ) { + ret = qmc_chan_stop_rx(chan); + if (ret) + return ret; + } + + if (direction & QMC_CHAN_WRITE) { + ret = qmc_chan_stop_tx(chan); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(qmc_chan_stop); + +static void qmc_chan_start_rx(struct qmc_chan *chan) +{ + unsigned long flags; + + spin_lock_irqsave(&chan->rx_lock, flags); + + /* Restart the receiver */ + if (chan->mode == QMC_TRANSPARENT) + qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080); + else + qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080); + qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000); + chan->is_rx_halted = false; + + chan->is_rx_stopped = false; + + spin_unlock_irqrestore(&chan->rx_lock, flags); +} + +static void qmc_chan_start_tx(struct qmc_chan *chan) +{ + unsigned long flags; + + spin_lock_irqsave(&chan->tx_lock, flags); + + /* + * Enable channel transmitter as it could be disabled if + * qmc_chan_reset() was called. + */ + qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_ENT); + + /* Set the POL bit in the channel mode register */ + qmc_setbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_POL); + + chan->is_tx_stopped = false; + + spin_unlock_irqrestore(&chan->tx_lock, flags); +} + +int qmc_chan_start(struct qmc_chan *chan, int direction) +{ + if (direction & QMC_CHAN_READ) + qmc_chan_start_rx(chan); + + if (direction & QMC_CHAN_WRITE) + qmc_chan_start_tx(chan); + + return 0; +} +EXPORT_SYMBOL(qmc_chan_start); + +static void qmc_chan_reset_rx(struct qmc_chan *chan) +{ + struct qmc_xfer_desc *xfer_desc; + unsigned long flags; + cbd_t *__iomem bd; + u16 ctrl; + + spin_lock_irqsave(&chan->rx_lock, flags); + bd = chan->rxbds; + do { + ctrl = qmc_read16(&bd->cbd_sc); + qmc_write16(&bd->cbd_sc, ctrl & ~(QMC_BD_RX_UB | QMC_BD_RX_E)); + + xfer_desc = &chan->rx_desc[bd - chan->rxbds]; + xfer_desc->rx_complete = NULL; + xfer_desc->context = NULL; + + bd++; + } while (!(ctrl & QMC_BD_RX_W)); + + chan->rxbd_free = chan->rxbds; + chan->rxbd_done = chan->rxbds; + qmc_write16(chan->s_param + QMC_SPE_RBPTR, + qmc_read16(chan->s_param + QMC_SPE_RBASE)); + + chan->rx_pending = 0; + chan->is_rx_stopped = false; + + spin_unlock_irqrestore(&chan->rx_lock, flags); +} + +static void qmc_chan_reset_tx(struct qmc_chan *chan) +{ + struct qmc_xfer_desc *xfer_desc; + unsigned long flags; + cbd_t *__iomem bd; + u16 ctrl; + + spin_lock_irqsave(&chan->tx_lock, flags); + + /* Disable transmitter. It will be re-enable on qmc_chan_start() */ + qmc_clrbits16(chan->s_param + QMC_SPE_CHAMR, QMC_SPE_CHAMR_ENT); + + bd = chan->txbds; + do { + ctrl = qmc_read16(&bd->cbd_sc); + qmc_write16(&bd->cbd_sc, ctrl & ~(QMC_BD_TX_UB | QMC_BD_TX_R)); + + xfer_desc = &chan->tx_desc[bd - chan->txbds]; + xfer_desc->tx_complete = NULL; + xfer_desc->context = NULL; + + bd++; + } while (!(ctrl & QMC_BD_TX_W)); + + chan->txbd_free = chan->txbds; + chan->txbd_done = chan->txbds; + qmc_write16(chan->s_param + QMC_SPE_TBPTR, + qmc_read16(chan->s_param + QMC_SPE_TBASE)); + + /* Reset TSTATE and ZISTATE to their initial value */ + qmc_write32(chan->s_param + QMC_SPE_TSTATE, 0x30000000); + qmc_write32(chan->s_param + QMC_SPE_ZISTATE, 0x00000100); + + spin_unlock_irqrestore(&chan->tx_lock, flags); +} + +int qmc_chan_reset(struct qmc_chan *chan, int direction) +{ + if (direction & QMC_CHAN_READ) + qmc_chan_reset_rx(chan); + + if (direction & QMC_CHAN_WRITE) + qmc_chan_reset_tx(chan); + + return 0; +} +EXPORT_SYMBOL(qmc_chan_reset); + +static int qmc_check_chans(struct qmc *qmc) +{ + struct tsa_serial_info info; + bool is_one_table = false; + struct qmc_chan *chan; + u64 tx_ts_mask = 0; + u64 rx_ts_mask = 0; + u64 tx_ts_assigned_mask; + u64 rx_ts_assigned_mask; + int ret; + + /* Retrieve info from the TSA related serial */ + ret = tsa_serial_get_info(qmc->tsa_serial, &info); + if (ret) + return ret; + + /* + * If more than 32 TS are assigned to this serial, one common table is + * used for Tx and Rx and so masks must be equal for all channels. + */ + if ((info.nb_tx_ts > 32) || (info.nb_rx_ts > 32)) { + if (info.nb_tx_ts != info.nb_rx_ts) { + dev_err(qmc->dev, "Number of TSA Tx/Rx TS assigned are not equal\n"); + return -EINVAL; + } + is_one_table = true; + } + + + tx_ts_assigned_mask = (((u64)1) << info.nb_tx_ts) - 1; + rx_ts_assigned_mask = (((u64)1) << info.nb_rx_ts) - 1; + + list_for_each_entry(chan, &qmc->chan_head, list) { + if (chan->tx_ts_mask > tx_ts_assigned_mask) { + dev_err(qmc->dev, "chan %u uses TSA unassigned Tx TS\n", chan->id); + return -EINVAL; + } + if (tx_ts_mask & chan->tx_ts_mask) { + dev_err(qmc->dev, "chan %u uses an already used Tx TS\n", chan->id); + return -EINVAL; + } + + if (chan->rx_ts_mask > rx_ts_assigned_mask) { + dev_err(qmc->dev, "chan %u uses TSA unassigned Rx TS\n", chan->id); + return -EINVAL; + } + if (rx_ts_mask & chan->rx_ts_mask) { + dev_err(qmc->dev, "chan %u uses an already used Rx TS\n", chan->id); + return -EINVAL; + } + + if (is_one_table && (chan->tx_ts_mask != chan->rx_ts_mask)) { + dev_err(qmc->dev, "chan %u uses different Rx and Tx TS\n", chan->id); + return -EINVAL; + } + + tx_ts_mask |= chan->tx_ts_mask; + rx_ts_mask |= chan->rx_ts_mask; + } + + return 0; +} + +static unsigned int qmc_nb_chans(struct qmc *qmc) +{ + unsigned int count = 0; + struct qmc_chan *chan; + + list_for_each_entry(chan, &qmc->chan_head, list) + count++; + + return count; +} + +static int qmc_of_parse_chans(struct qmc *qmc, struct device_node *np) +{ + struct device_node *chan_np; + struct qmc_chan *chan; + const char *mode; + u32 chan_id; + u64 ts_mask; + int ret; + + for_each_available_child_of_node(np, chan_np) { + ret = of_property_read_u32(chan_np, "reg", &chan_id); + if (ret) { + dev_err(qmc->dev, "%pOF: failed to read reg\n", chan_np); + of_node_put(chan_np); + return ret; + } + if (chan_id > 63) { + dev_err(qmc->dev, "%pOF: Invalid chan_id\n", chan_np); + of_node_put(chan_np); + return -EINVAL; + } + + chan = devm_kzalloc(qmc->dev, sizeof(*chan), GFP_KERNEL); + if (!chan) { + of_node_put(chan_np); + return -ENOMEM; + } + + chan->id = chan_id; + spin_lock_init(&chan->rx_lock); + spin_lock_init(&chan->tx_lock); + + ret = of_property_read_u64(chan_np, "fsl,tx-ts-mask", &ts_mask); + if (ret) { + dev_err(qmc->dev, "%pOF: failed to read fsl,tx-ts-mask\n", + chan_np); + of_node_put(chan_np); + return ret; + } + chan->tx_ts_mask = ts_mask; + + ret = of_property_read_u64(chan_np, "fsl,rx-ts-mask", &ts_mask); + if (ret) { + dev_err(qmc->dev, "%pOF: failed to read fsl,rx-ts-mask\n", + chan_np); + of_node_put(chan_np); + return ret; + } + chan->rx_ts_mask = ts_mask; + + mode = "transparent"; + ret = of_property_read_string(chan_np, "fsl,operational-mode", &mode); + if (ret && ret != -EINVAL) { + dev_err(qmc->dev, "%pOF: failed to read fsl,operational-mode\n", + chan_np); + of_node_put(chan_np); + return ret; + } + if (!strcmp(mode, "transparent")) { + chan->mode = QMC_TRANSPARENT; + } else if (!strcmp(mode, "hdlc")) { + chan->mode = QMC_HDLC; + } else { + dev_err(qmc->dev, "%pOF: Invalid fsl,operational-mode (%s)\n", + chan_np, mode); + of_node_put(chan_np); + return -EINVAL; + } + + chan->is_reverse_data = of_property_read_bool(chan_np, + "fsl,reverse-data"); + + list_add_tail(&chan->list, &qmc->chan_head); + qmc->chans[chan->id] = chan; + } + + return qmc_check_chans(qmc); +} + +static int qmc_setup_tsa_64rxtx(struct qmc *qmc, const struct tsa_serial_info *info) +{ + struct qmc_chan *chan; + unsigned int i; + u16 val; + + /* + * Use a common Tx/Rx 64 entries table. + * Everything was previously checked, Tx and Rx related stuffs are + * identical -> Used Rx related stuff to build the table + */ + + /* Invalidate all entries */ + for (i = 0; i < 64; i++) + qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), 0x0000); + + /* Set entries based on Rx stuff*/ + list_for_each_entry(chan, &qmc->chan_head, list) { + for (i = 0; i < info->nb_rx_ts; i++) { + if (!(chan->rx_ts_mask & (((u64)1) << i))) + continue; + + val = QMC_TSA_VALID | QMC_TSA_MASK | + QMC_TSA_CHANNEL(chan->id); + qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), val); + } + } + + /* Set Wrap bit on last entry */ + qmc_setbits16(qmc->scc_pram + QMC_GBL_TSATRX + ((info->nb_rx_ts - 1) * 2), + QMC_TSA_WRAP); + + /* Init pointers to the table */ + val = qmc->scc_pram_offset + QMC_GBL_TSATRX; + qmc_write16(qmc->scc_pram + QMC_GBL_RX_S_PTR, val); + qmc_write16(qmc->scc_pram + QMC_GBL_RXPTR, val); + qmc_write16(qmc->scc_pram + QMC_GBL_TX_S_PTR, val); + qmc_write16(qmc->scc_pram + QMC_GBL_TXPTR, val); + + return 0; +} + +static int qmc_setup_tsa_32rx_32tx(struct qmc *qmc, const struct tsa_serial_info *info) +{ + struct qmc_chan *chan; + unsigned int i; + u16 val; + + /* + * Use a Tx 32 entries table and a Rx 32 entries table. + * Everything was previously checked. + */ + + /* Invalidate all entries */ + for (i = 0; i < 32; i++) { + qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), 0x0000); + qmc_write16(qmc->scc_pram + QMC_GBL_TSATTX + (i * 2), 0x0000); + } + + /* Set entries based on Rx and Tx stuff*/ + list_for_each_entry(chan, &qmc->chan_head, list) { + /* Rx part */ + for (i = 0; i < info->nb_rx_ts; i++) { + if (!(chan->rx_ts_mask & (((u64)1) << i))) + continue; + + val = QMC_TSA_VALID | QMC_TSA_MASK | + QMC_TSA_CHANNEL(chan->id); + qmc_write16(qmc->scc_pram + QMC_GBL_TSATRX + (i * 2), val); + } + /* Tx part */ + for (i = 0; i < info->nb_tx_ts; i++) { + if (!(chan->tx_ts_mask & (((u64)1) << i))) + continue; + + val = QMC_TSA_VALID | QMC_TSA_MASK | + QMC_TSA_CHANNEL(chan->id); + qmc_write16(qmc->scc_pram + QMC_GBL_TSATTX + (i * 2), val); + } + } + + /* Set Wrap bit on last entries */ + qmc_setbits16(qmc->scc_pram + QMC_GBL_TSATRX + ((info->nb_rx_ts - 1) * 2), + QMC_TSA_WRAP); + qmc_setbits16(qmc->scc_pram + QMC_GBL_TSATTX + ((info->nb_tx_ts - 1) * 2), + QMC_TSA_WRAP); + + /* Init Rx pointers ...*/ + val = qmc->scc_pram_offset + QMC_GBL_TSATRX; + qmc_write16(qmc->scc_pram + QMC_GBL_RX_S_PTR, val); + qmc_write16(qmc->scc_pram + QMC_GBL_RXPTR, val); + + /* ... and Tx pointers */ + val = qmc->scc_pram_offset + QMC_GBL_TSATTX; + qmc_write16(qmc->scc_pram + QMC_GBL_TX_S_PTR, val); + qmc_write16(qmc->scc_pram + QMC_GBL_TXPTR, val); + + return 0; +} + +static int qmc_setup_tsa(struct qmc *qmc) +{ + struct tsa_serial_info info; + int ret; + + /* Retrieve info from the TSA related serial */ + ret = tsa_serial_get_info(qmc->tsa_serial, &info); + if (ret) + return ret; + + /* + * Setup one common 64 entries table or two 32 entries (one for Tx and + * one for Tx) according to assigned TS numbers. + */ + return ((info.nb_tx_ts > 32) || (info.nb_rx_ts > 32)) ? + qmc_setup_tsa_64rxtx(qmc, &info) : + qmc_setup_tsa_32rx_32tx(qmc, &info); +} + +static int qmc_setup_chan_trnsync(struct qmc *qmc, struct qmc_chan *chan) +{ + struct tsa_serial_info info; + u16 first_rx, last_tx; + u16 trnsync; + int ret; + + /* Retrieve info from the TSA related serial */ + ret = tsa_serial_get_info(chan->qmc->tsa_serial, &info); + if (ret) + return ret; + + /* Find the first Rx TS allocated to the channel */ + first_rx = chan->rx_ts_mask ? __ffs64(chan->rx_ts_mask) + 1 : 0; + + /* Find the last Tx TS allocated to the channel */ + last_tx = fls64(chan->tx_ts_mask); + + trnsync = 0; + if (info.nb_rx_ts) + trnsync |= QMC_SPE_TRNSYNC_RX((first_rx % info.nb_rx_ts) * 2); + if (info.nb_tx_ts) + trnsync |= QMC_SPE_TRNSYNC_TX((last_tx % info.nb_tx_ts) * 2); + + qmc_write16(chan->s_param + QMC_SPE_TRNSYNC, trnsync); + + dev_dbg(qmc->dev, "chan %u: trnsync=0x%04x, rx %u/%u 0x%llx, tx %u/%u 0x%llx\n", + chan->id, trnsync, + first_rx, info.nb_rx_ts, chan->rx_ts_mask, + last_tx, info.nb_tx_ts, chan->tx_ts_mask); + + return 0; +} + +static int qmc_setup_chan(struct qmc *qmc, struct qmc_chan *chan) +{ + unsigned int i; + cbd_t __iomem *bd; + int ret; + u16 val; + + chan->qmc = qmc; + + /* Set channel specific parameter base address */ + chan->s_param = qmc->dpram + (chan->id * 64); + /* 16 bd per channel (8 rx and 8 tx) */ + chan->txbds = qmc->bd_table + (chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS)); + chan->rxbds = qmc->bd_table + (chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS)) + QMC_NB_TXBDS; + + chan->txbd_free = chan->txbds; + chan->txbd_done = chan->txbds; + chan->rxbd_free = chan->rxbds; + chan->rxbd_done = chan->rxbds; + + /* TBASE and TBPTR*/ + val = chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS) * sizeof(cbd_t); + qmc_write16(chan->s_param + QMC_SPE_TBASE, val); + qmc_write16(chan->s_param + QMC_SPE_TBPTR, val); + + /* RBASE and RBPTR*/ + val = ((chan->id * (QMC_NB_TXBDS + QMC_NB_RXBDS)) + QMC_NB_TXBDS) * sizeof(cbd_t); + qmc_write16(chan->s_param + QMC_SPE_RBASE, val); + qmc_write16(chan->s_param + QMC_SPE_RBPTR, val); + qmc_write32(chan->s_param + QMC_SPE_TSTATE, 0x30000000); + qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000); + qmc_write32(chan->s_param + QMC_SPE_ZISTATE, 0x00000100); + if (chan->mode == QMC_TRANSPARENT) { + qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080); + qmc_write16(chan->s_param + QMC_SPE_TMRBLR, 60); + val = QMC_SPE_CHAMR_MODE_TRANSP | QMC_SPE_CHAMR_TRANSP_SYNC; + if (chan->is_reverse_data) + val |= QMC_SPE_CHAMR_TRANSP_RD; + qmc_write16(chan->s_param + QMC_SPE_CHAMR, val); + ret = qmc_setup_chan_trnsync(qmc, chan); + if (ret) + return ret; + } else { + qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080); + qmc_write16(chan->s_param + QMC_SPE_MFLR, 60); + qmc_write16(chan->s_param + QMC_SPE_CHAMR, + QMC_SPE_CHAMR_MODE_HDLC | QMC_SPE_CHAMR_HDLC_IDLM); + } + + /* Do not enable interrupts now. They will be enabled later */ + qmc_write16(chan->s_param + QMC_SPE_INTMSK, 0x0000); + + /* Init Rx BDs and set Wrap bit on last descriptor */ + BUILD_BUG_ON(QMC_NB_RXBDS == 0); + val = QMC_BD_RX_I; + for (i = 0; i < QMC_NB_RXBDS; i++) { + bd = chan->rxbds + i; + qmc_write16(&bd->cbd_sc, val); + } + bd = chan->rxbds + QMC_NB_RXBDS - 1; + qmc_write16(&bd->cbd_sc, val | QMC_BD_RX_W); + + /* Init Tx BDs and set Wrap bit on last descriptor */ + BUILD_BUG_ON(QMC_NB_TXBDS == 0); + val = QMC_BD_TX_I; + if (chan->mode == QMC_HDLC) + val |= QMC_BD_TX_L | QMC_BD_TX_TC; + for (i = 0; i < QMC_NB_TXBDS; i++) { + bd = chan->txbds + i; + qmc_write16(&bd->cbd_sc, val); + } + bd = chan->txbds + QMC_NB_TXBDS - 1; + qmc_write16(&bd->cbd_sc, val | QMC_BD_TX_W); + + return 0; +} + +static int qmc_setup_chans(struct qmc *qmc) +{ + struct qmc_chan *chan; + int ret; + + list_for_each_entry(chan, &qmc->chan_head, list) { + ret = qmc_setup_chan(qmc, chan); + if (ret) + return ret; + } + + return 0; +} + +static int qmc_finalize_chans(struct qmc *qmc) +{ + struct qmc_chan *chan; + int ret; + + list_for_each_entry(chan, &qmc->chan_head, list) { + /* Unmask channel interrupts */ + if (chan->mode == QMC_HDLC) { + qmc_write16(chan->s_param + QMC_SPE_INTMSK, + QMC_INT_NID | QMC_INT_IDL | QMC_INT_MRF | + QMC_INT_UN | QMC_INT_RXF | QMC_INT_BSY | + QMC_INT_TXB | QMC_INT_RXB); + } else { + qmc_write16(chan->s_param + QMC_SPE_INTMSK, + QMC_INT_UN | QMC_INT_BSY | + QMC_INT_TXB | QMC_INT_RXB); + } + + /* Forced stop the channel */ + ret = qmc_chan_stop(chan, QMC_CHAN_ALL); + if (ret) + return ret; + } + + return 0; +} + +static int qmc_setup_ints(struct qmc *qmc) +{ + unsigned int i; + u16 __iomem *last; + + /* Raz all entries */ + for (i = 0; i < (qmc->int_size / sizeof(u16)); i++) + qmc_write16(qmc->int_table + i, 0x0000); + + /* Set Wrap bit on last entry */ + if (qmc->int_size >= sizeof(u16)) { + last = qmc->int_table + (qmc->int_size / sizeof(u16)) - 1; + qmc_write16(last, QMC_INT_W); + } + + return 0; +} + +static void qmc_irq_gint(struct qmc *qmc) +{ + struct qmc_chan *chan; + unsigned int chan_id; + unsigned long flags; + u16 int_entry; + + int_entry = qmc_read16(qmc->int_curr); + while (int_entry & QMC_INT_V) { + /* Clear all but the Wrap bit */ + qmc_write16(qmc->int_curr, int_entry & QMC_INT_W); + + chan_id = QMC_INT_GET_CHANNEL(int_entry); + chan = qmc->chans[chan_id]; + if (!chan) { + dev_err(qmc->dev, "interrupt on invalid chan %u\n", chan_id); + goto int_next; + } + + if (int_entry & QMC_INT_TXB) + qmc_chan_write_done(chan); + + if (int_entry & QMC_INT_UN) { + dev_info(qmc->dev, "intr chan %u, 0x%04x (UN)\n", chan_id, + int_entry); + chan->nb_tx_underrun++; + } + + if (int_entry & QMC_INT_BSY) { + dev_info(qmc->dev, "intr chan %u, 0x%04x (BSY)\n", chan_id, + int_entry); + chan->nb_rx_busy++; + /* Restart the receiver if needed */ + spin_lock_irqsave(&chan->rx_lock, flags); + if (chan->rx_pending && !chan->is_rx_stopped) { + if (chan->mode == QMC_TRANSPARENT) + qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x18000080); + else + qmc_write32(chan->s_param + QMC_SPE_ZDSTATE, 0x00000080); + qmc_write32(chan->s_param + QMC_SPE_RSTATE, 0x31000000); + chan->is_rx_halted = false; + } else { + chan->is_rx_halted = true; + } + spin_unlock_irqrestore(&chan->rx_lock, flags); + } + + if (int_entry & QMC_INT_RXB) + qmc_chan_read_done(chan); + +int_next: + if (int_entry & QMC_INT_W) + qmc->int_curr = qmc->int_table; + else + qmc->int_curr++; + int_entry = qmc_read16(qmc->int_curr); + } +} + +static irqreturn_t qmc_irq_handler(int irq, void *priv) +{ + struct qmc *qmc = (struct qmc *)priv; + u16 scce; + + scce = qmc_read16(qmc->scc_regs + SCC_SCCE); + qmc_write16(qmc->scc_regs + SCC_SCCE, scce); + + if (unlikely(scce & SCC_SCCE_IQOV)) + dev_info(qmc->dev, "IRQ queue overflow\n"); + + if (unlikely(scce & SCC_SCCE_GUN)) + dev_err(qmc->dev, "Global transmitter underrun\n"); + + if (unlikely(scce & SCC_SCCE_GOV)) + dev_err(qmc->dev, "Global receiver overrun\n"); + + /* normal interrupt */ + if (likely(scce & SCC_SCCE_GINT)) + qmc_irq_gint(qmc); + + return IRQ_HANDLED; +} + +static int qmc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + unsigned int nb_chans; + struct resource *res; + struct qmc *qmc; + int irq; + int ret; + + qmc = devm_kzalloc(&pdev->dev, sizeof(*qmc), GFP_KERNEL); + if (!qmc) + return -ENOMEM; + + qmc->dev = &pdev->dev; + INIT_LIST_HEAD(&qmc->chan_head); + + qmc->scc_regs = devm_platform_ioremap_resource_byname(pdev, "scc_regs"); + if (IS_ERR(qmc->scc_regs)) + return PTR_ERR(qmc->scc_regs); + + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "scc_pram"); + if (!res) + return -EINVAL; + qmc->scc_pram_offset = res->start - get_immrbase(); + qmc->scc_pram = devm_ioremap_resource(qmc->dev, res); + if (IS_ERR(qmc->scc_pram)) + return PTR_ERR(qmc->scc_pram); + + qmc->dpram = devm_platform_ioremap_resource_byname(pdev, "dpram"); + if (IS_ERR(qmc->dpram)) + return PTR_ERR(qmc->dpram); + + qmc->tsa_serial = devm_tsa_serial_get_byphandle(qmc->dev, np, "fsl,tsa-serial"); + if (IS_ERR(qmc->tsa_serial)) { + return dev_err_probe(qmc->dev, PTR_ERR(qmc->tsa_serial), + "Failed to get TSA serial\n"); + } + + /* Connect the serial (SCC) to TSA */ + ret = tsa_serial_connect(qmc->tsa_serial); + if (ret) { + dev_err(qmc->dev, "Failed to connect TSA serial\n"); + return ret; + } + + /* Parse channels informationss */ + ret = qmc_of_parse_chans(qmc, np); + if (ret) + goto err_tsa_serial_disconnect; + + nb_chans = qmc_nb_chans(qmc); + + /* Init GMSR_H and GMSR_L registers */ + qmc_write32(qmc->scc_regs + SCC_GSMRH, + SCC_GSMRH_CDS | SCC_GSMRH_CTSS | SCC_GSMRH_CDP | SCC_GSMRH_CTSP); + + /* enable QMC mode */ + qmc_write32(qmc->scc_regs + SCC_GSMRL, SCC_GSMRL_MODE_QMC); + + /* + * Allocate the buffer descriptor table + * 8 rx and 8 tx descriptors per channel + */ + qmc->bd_size = (nb_chans * (QMC_NB_TXBDS + QMC_NB_RXBDS)) * sizeof(cbd_t); + qmc->bd_table = dmam_alloc_coherent(qmc->dev, qmc->bd_size, + &qmc->bd_dma_addr, GFP_KERNEL); + if (!qmc->bd_table) { + dev_err(qmc->dev, "Failed to allocate bd table\n"); + ret = -ENOMEM; + goto err_tsa_serial_disconnect; + } + memset(qmc->bd_table, 0, qmc->bd_size); + + qmc_write32(qmc->scc_pram + QMC_GBL_MCBASE, qmc->bd_dma_addr); + + /* Allocate the interrupt table */ + qmc->int_size = QMC_NB_INTS * sizeof(u16); + qmc->int_table = dmam_alloc_coherent(qmc->dev, qmc->int_size, + &qmc->int_dma_addr, GFP_KERNEL); + if (!qmc->int_table) { + dev_err(qmc->dev, "Failed to allocate interrupt table\n"); + ret = -ENOMEM; + goto err_tsa_serial_disconnect; + } + memset(qmc->int_table, 0, qmc->int_size); + + qmc->int_curr = qmc->int_table; + qmc_write32(qmc->scc_pram + QMC_GBL_INTBASE, qmc->int_dma_addr); + qmc_write32(qmc->scc_pram + QMC_GBL_INTPTR, qmc->int_dma_addr); + + /* Set MRBLR (valid for HDLC only) max MRU + max CRC */ + qmc_write16(qmc->scc_pram + QMC_GBL_MRBLR, HDLC_MAX_MRU + 4); + + qmc_write16(qmc->scc_pram + QMC_GBL_GRFTHR, 1); + qmc_write16(qmc->scc_pram + QMC_GBL_GRFCNT, 1); + + qmc_write32(qmc->scc_pram + QMC_GBL_C_MASK32, 0xDEBB20E3); + qmc_write16(qmc->scc_pram + QMC_GBL_C_MASK16, 0xF0B8); + + ret = qmc_setup_tsa(qmc); + if (ret) + goto err_tsa_serial_disconnect; + + qmc_write16(qmc->scc_pram + QMC_GBL_QMCSTATE, 0x8000); + + ret = qmc_setup_chans(qmc); + if (ret) + goto err_tsa_serial_disconnect; + + /* Init interrupts table */ + ret = qmc_setup_ints(qmc); + if (ret) + goto err_tsa_serial_disconnect; + + /* Disable and clear interrupts, set the irq handler */ + qmc_write16(qmc->scc_regs + SCC_SCCM, 0x0000); + qmc_write16(qmc->scc_regs + SCC_SCCE, 0x000F); + irq = platform_get_irq(pdev, 0); + if (irq < 0) + goto err_tsa_serial_disconnect; + ret = devm_request_irq(qmc->dev, irq, qmc_irq_handler, 0, "qmc", qmc); + if (ret < 0) + goto err_tsa_serial_disconnect; + + /* Enable interrupts */ + qmc_write16(qmc->scc_regs + SCC_SCCM, + SCC_SCCE_IQOV | SCC_SCCE_GINT | SCC_SCCE_GUN | SCC_SCCE_GOV); + + ret = qmc_finalize_chans(qmc); + if (ret < 0) + goto err_disable_intr; + + /* Enable transmiter and receiver */ + qmc_setbits32(qmc->scc_regs + SCC_GSMRL, SCC_GSMRL_ENR | SCC_GSMRL_ENT); + + platform_set_drvdata(pdev, qmc); + + return 0; + +err_disable_intr: + qmc_write16(qmc->scc_regs + SCC_SCCM, 0); + +err_tsa_serial_disconnect: + tsa_serial_disconnect(qmc->tsa_serial); + return ret; +} + +static int qmc_remove(struct platform_device *pdev) +{ + struct qmc *qmc = platform_get_drvdata(pdev); + + /* Disable transmiter and receiver */ + qmc_setbits32(qmc->scc_regs + SCC_GSMRL, 0); + + /* Disable interrupts */ + qmc_write16(qmc->scc_regs + SCC_SCCM, 0); + + /* Disconnect the serial from TSA */ + tsa_serial_disconnect(qmc->tsa_serial); + + return 0; +} + +static const struct of_device_id qmc_id_table[] = { + { .compatible = "fsl,cpm1-scc-qmc" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, qmc_id_table); + +static struct platform_driver qmc_driver = { + .driver = { + .name = "fsl-qmc", + .of_match_table = of_match_ptr(qmc_id_table), + }, + .probe = qmc_probe, + .remove = qmc_remove, +}; +module_platform_driver(qmc_driver); + +struct qmc_chan *qmc_chan_get_byphandle(struct device_node *np, const char *phandle_name) +{ + struct of_phandle_args out_args; + struct platform_device *pdev; + struct qmc_chan *qmc_chan; + struct qmc *qmc; + int ret; + + ret = of_parse_phandle_with_fixed_args(np, phandle_name, 1, 0, + &out_args); + if (ret < 0) + return ERR_PTR(ret); + + if (!of_match_node(qmc_driver.driver.of_match_table, out_args.np)) { + of_node_put(out_args.np); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(out_args.np); + of_node_put(out_args.np); + if (!pdev) + return ERR_PTR(-ENODEV); + + qmc = platform_get_drvdata(pdev); + if (!qmc) { + platform_device_put(pdev); + return ERR_PTR(-EPROBE_DEFER); + } + + if (out_args.args_count != 1) { + platform_device_put(pdev); + return ERR_PTR(-EINVAL); + } + + if (out_args.args[0] >= ARRAY_SIZE(qmc->chans)) { + platform_device_put(pdev); + return ERR_PTR(-EINVAL); + } + + qmc_chan = qmc->chans[out_args.args[0]]; + if (!qmc_chan) { + platform_device_put(pdev); + return ERR_PTR(-ENOENT); + } + + return qmc_chan; +} +EXPORT_SYMBOL(qmc_chan_get_byphandle); + +void qmc_chan_put(struct qmc_chan *chan) +{ + put_device(chan->qmc->dev); +} +EXPORT_SYMBOL(qmc_chan_put); + +static void devm_qmc_chan_release(struct device *dev, void *res) +{ + struct qmc_chan **qmc_chan = res; + + qmc_chan_put(*qmc_chan); +} + +struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev, + struct device_node *np, + const char *phandle_name) +{ + struct qmc_chan *qmc_chan; + struct qmc_chan **dr; + + dr = devres_alloc(devm_qmc_chan_release, sizeof(*dr), GFP_KERNEL); + if (!dr) + return ERR_PTR(-ENOMEM); + + qmc_chan = qmc_chan_get_byphandle(np, phandle_name); + if (!IS_ERR(qmc_chan)) { + *dr = qmc_chan; + devres_add(dev, dr); + } else { + devres_free(dr); + } + + return qmc_chan; +} +EXPORT_SYMBOL(devm_qmc_chan_get_byphandle); + +MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>"); +MODULE_DESCRIPTION("CPM QMC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/fsl/qe/tsa.c b/drivers/soc/fsl/qe/tsa.c new file mode 100644 index 000000000000..3646153117b3 --- /dev/null +++ b/drivers/soc/fsl/qe/tsa.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TSA driver + * + * Copyright 2022 CS GROUP France + * + * Author: Herve Codina <herve.codina@bootlin.com> + */ + +#include "tsa.h" +#include <dt-bindings/soc/cpm1-fsl,tsa.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + + +/* TSA SI RAM routing tables entry */ +#define TSA_SIRAM_ENTRY_LAST (1 << 16) +#define TSA_SIRAM_ENTRY_BYTE (1 << 17) +#define TSA_SIRAM_ENTRY_CNT(x) (((x) & 0x0f) << 18) +#define TSA_SIRAM_ENTRY_CSEL_MASK (0x7 << 22) +#define TSA_SIRAM_ENTRY_CSEL_NU (0x0 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SCC2 (0x2 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SCC3 (0x3 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SCC4 (0x4 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SMC1 (0x5 << 22) +#define TSA_SIRAM_ENTRY_CSEL_SMC2 (0x6 << 22) + +/* SI mode register (32 bits) */ +#define TSA_SIMODE 0x00 +#define TSA_SIMODE_SMC2 0x80000000 +#define TSA_SIMODE_SMC1 0x00008000 +#define TSA_SIMODE_TDMA(x) ((x) << 0) +#define TSA_SIMODE_TDMB(x) ((x) << 16) +#define TSA_SIMODE_TDM_MASK 0x0fff +#define TSA_SIMODE_TDM_SDM_MASK 0x0c00 +#define TSA_SIMODE_TDM_SDM_NORM 0x0000 +#define TSA_SIMODE_TDM_SDM_ECHO 0x0400 +#define TSA_SIMODE_TDM_SDM_INTL_LOOP 0x0800 +#define TSA_SIMODE_TDM_SDM_LOOP_CTRL 0x0c00 +#define TSA_SIMODE_TDM_RFSD(x) ((x) << 8) +#define TSA_SIMODE_TDM_DSC 0x0080 +#define TSA_SIMODE_TDM_CRT 0x0040 +#define TSA_SIMODE_TDM_STZ 0x0020 +#define TSA_SIMODE_TDM_CE 0x0010 +#define TSA_SIMODE_TDM_FE 0x0008 +#define TSA_SIMODE_TDM_GM 0x0004 +#define TSA_SIMODE_TDM_TFSD(x) ((x) << 0) + +/* SI global mode register (8 bits) */ +#define TSA_SIGMR 0x04 +#define TSA_SIGMR_ENB (1<<3) +#define TSA_SIGMR_ENA (1<<2) +#define TSA_SIGMR_RDM_MASK 0x03 +#define TSA_SIGMR_RDM_STATIC_TDMA 0x00 +#define TSA_SIGMR_RDM_DYN_TDMA 0x01 +#define TSA_SIGMR_RDM_STATIC_TDMAB 0x02 +#define TSA_SIGMR_RDM_DYN_TDMAB 0x03 + +/* SI status register (8 bits) */ +#define TSA_SISTR 0x06 + +/* SI command register (8 bits) */ +#define TSA_SICMR 0x07 + +/* SI clock route register (32 bits) */ +#define TSA_SICR 0x0C +#define TSA_SICR_SCC2(x) ((x) << 8) +#define TSA_SICR_SCC3(x) ((x) << 16) +#define TSA_SICR_SCC4(x) ((x) << 24) +#define TSA_SICR_SCC_MASK 0x0ff +#define TSA_SICR_SCC_GRX (1 << 7) +#define TSA_SICR_SCC_SCX_TSA (1 << 6) +#define TSA_SICR_SCC_RXCS_MASK (0x7 << 3) +#define TSA_SICR_SCC_RXCS_BRG1 (0x0 << 3) +#define TSA_SICR_SCC_RXCS_BRG2 (0x1 << 3) +#define TSA_SICR_SCC_RXCS_BRG3 (0x2 << 3) +#define TSA_SICR_SCC_RXCS_BRG4 (0x3 << 3) +#define TSA_SICR_SCC_RXCS_CLK15 (0x4 << 3) +#define TSA_SICR_SCC_RXCS_CLK26 (0x5 << 3) +#define TSA_SICR_SCC_RXCS_CLK37 (0x6 << 3) +#define TSA_SICR_SCC_RXCS_CLK48 (0x7 << 3) +#define TSA_SICR_SCC_TXCS_MASK (0x7 << 0) +#define TSA_SICR_SCC_TXCS_BRG1 (0x0 << 0) +#define TSA_SICR_SCC_TXCS_BRG2 (0x1 << 0) +#define TSA_SICR_SCC_TXCS_BRG3 (0x2 << 0) +#define TSA_SICR_SCC_TXCS_BRG4 (0x3 << 0) +#define TSA_SICR_SCC_TXCS_CLK15 (0x4 << 0) +#define TSA_SICR_SCC_TXCS_CLK26 (0x5 << 0) +#define TSA_SICR_SCC_TXCS_CLK37 (0x6 << 0) +#define TSA_SICR_SCC_TXCS_CLK48 (0x7 << 0) + +/* Serial interface RAM pointer register (32 bits) */ +#define TSA_SIRP 0x10 + +struct tsa_entries_area { + void *__iomem entries_start; + void *__iomem entries_next; + void *__iomem last_entry; +}; + +struct tsa_tdm { + bool is_enable; + struct clk *l1rclk_clk; + struct clk *l1rsync_clk; + struct clk *l1tclk_clk; + struct clk *l1tsync_clk; + u32 simode_tdm; +}; + +#define TSA_TDMA 0 +#define TSA_TDMB 1 + +struct tsa { + struct device *dev; + void *__iomem si_regs; + void *__iomem si_ram; + resource_size_t si_ram_sz; + spinlock_t lock; + int tdms; /* TSA_TDMx ORed */ + struct tsa_tdm tdm[2]; /* TDMa and TDMb */ + struct tsa_serial { + unsigned int id; + struct tsa_serial_info info; + } serials[6]; +}; + +static inline struct tsa *tsa_serial_get_tsa(struct tsa_serial *tsa_serial) +{ + /* The serials table is indexed by the serial id */ + return container_of(tsa_serial, struct tsa, serials[tsa_serial->id]); +} + +static inline void tsa_write32(void *__iomem addr, u32 val) +{ + iowrite32be(val, addr); +} + +static inline void tsa_write8(void *__iomem addr, u32 val) +{ + iowrite8(val, addr); +} + +static inline u32 tsa_read32(void *__iomem addr) +{ + return ioread32be(addr); +} + +static inline void tsa_clrbits32(void *__iomem addr, u32 clr) +{ + tsa_write32(addr, tsa_read32(addr) & ~clr); +} + +static inline void tsa_clrsetbits32(void *__iomem addr, u32 clr, u32 set) +{ + tsa_write32(addr, (tsa_read32(addr) & ~clr) | set); +} + +int tsa_serial_connect(struct tsa_serial *tsa_serial) +{ + struct tsa *tsa = tsa_serial_get_tsa(tsa_serial); + unsigned long flags; + u32 clear; + u32 set; + + switch (tsa_serial->id) { + case FSL_CPM_TSA_SCC2: + clear = TSA_SICR_SCC2(TSA_SICR_SCC_MASK); + set = TSA_SICR_SCC2(TSA_SICR_SCC_SCX_TSA); + break; + case FSL_CPM_TSA_SCC3: + clear = TSA_SICR_SCC3(TSA_SICR_SCC_MASK); + set = TSA_SICR_SCC3(TSA_SICR_SCC_SCX_TSA); + break; + case FSL_CPM_TSA_SCC4: + clear = TSA_SICR_SCC4(TSA_SICR_SCC_MASK); + set = TSA_SICR_SCC4(TSA_SICR_SCC_SCX_TSA); + break; + default: + dev_err(tsa->dev, "Unsupported serial id %u\n", tsa_serial->id); + return -EINVAL; + } + + spin_lock_irqsave(&tsa->lock, flags); + tsa_clrsetbits32(tsa->si_regs + TSA_SICR, clear, set); + spin_unlock_irqrestore(&tsa->lock, flags); + + return 0; +} +EXPORT_SYMBOL(tsa_serial_connect); + +int tsa_serial_disconnect(struct tsa_serial *tsa_serial) +{ + struct tsa *tsa = tsa_serial_get_tsa(tsa_serial); + unsigned long flags; + u32 clear; + + switch (tsa_serial->id) { + case FSL_CPM_TSA_SCC2: + clear = TSA_SICR_SCC2(TSA_SICR_SCC_MASK); + break; + case FSL_CPM_TSA_SCC3: + clear = TSA_SICR_SCC3(TSA_SICR_SCC_MASK); + break; + case FSL_CPM_TSA_SCC4: + clear = TSA_SICR_SCC4(TSA_SICR_SCC_MASK); + break; + default: + dev_err(tsa->dev, "Unsupported serial id %u\n", tsa_serial->id); + return -EINVAL; + } + + spin_lock_irqsave(&tsa->lock, flags); + tsa_clrsetbits32(tsa->si_regs + TSA_SICR, clear, 0); + spin_unlock_irqrestore(&tsa->lock, flags); + + return 0; +} +EXPORT_SYMBOL(tsa_serial_disconnect); + +int tsa_serial_get_info(struct tsa_serial *tsa_serial, struct tsa_serial_info *info) +{ + memcpy(info, &tsa_serial->info, sizeof(*info)); + return 0; +} +EXPORT_SYMBOL(tsa_serial_get_info); + +static void tsa_init_entries_area(struct tsa *tsa, struct tsa_entries_area *area, + u32 tdms, u32 tdm_id, bool is_rx) +{ + resource_size_t quarter; + resource_size_t half; + + quarter = tsa->si_ram_sz/4; + half = tsa->si_ram_sz/2; + + if (tdms == BIT(TSA_TDMA)) { + /* Only TDMA */ + if (is_rx) { + /* First half of si_ram */ + area->entries_start = tsa->si_ram; + area->entries_next = area->entries_start + half; + area->last_entry = NULL; + } else { + /* Second half of si_ram */ + area->entries_start = tsa->si_ram + half; + area->entries_next = area->entries_start + half; + area->last_entry = NULL; + } + } else { + /* Only TDMB or both TDMs */ + if (tdm_id == TSA_TDMA) { + if (is_rx) { + /* First half of first half of si_ram */ + area->entries_start = tsa->si_ram; + area->entries_next = area->entries_start + quarter; + area->last_entry = NULL; + } else { + /* First half of second half of si_ram */ + area->entries_start = tsa->si_ram + (2 * quarter); + area->entries_next = area->entries_start + quarter; + area->last_entry = NULL; + } + } else { + if (is_rx) { + /* Second half of first half of si_ram */ + area->entries_start = tsa->si_ram + quarter; + area->entries_next = area->entries_start + quarter; + area->last_entry = NULL; + } else { + /* Second half of second half of si_ram */ + area->entries_start = tsa->si_ram + (3 * quarter); + area->entries_next = area->entries_start + quarter; + area->last_entry = NULL; + } + } + } +} + +static const char *tsa_serial_id2name(struct tsa *tsa, u32 serial_id) +{ + switch (serial_id) { + case FSL_CPM_TSA_NU: return "Not used"; + case FSL_CPM_TSA_SCC2: return "SCC2"; + case FSL_CPM_TSA_SCC3: return "SCC3"; + case FSL_CPM_TSA_SCC4: return "SCC4"; + case FSL_CPM_TSA_SMC1: return "SMC1"; + case FSL_CPM_TSA_SMC2: return "SMC2"; + default: + break; + } + return NULL; +} + +static u32 tsa_serial_id2csel(struct tsa *tsa, u32 serial_id) +{ + switch (serial_id) { + case FSL_CPM_TSA_SCC2: return TSA_SIRAM_ENTRY_CSEL_SCC2; + case FSL_CPM_TSA_SCC3: return TSA_SIRAM_ENTRY_CSEL_SCC3; + case FSL_CPM_TSA_SCC4: return TSA_SIRAM_ENTRY_CSEL_SCC4; + case FSL_CPM_TSA_SMC1: return TSA_SIRAM_ENTRY_CSEL_SMC1; + case FSL_CPM_TSA_SMC2: return TSA_SIRAM_ENTRY_CSEL_SMC2; + default: + break; + } + return TSA_SIRAM_ENTRY_CSEL_NU; +} + +static int tsa_add_entry(struct tsa *tsa, struct tsa_entries_area *area, + u32 count, u32 serial_id) +{ + void *__iomem addr; + u32 left; + u32 val; + u32 cnt; + u32 nb; + + addr = area->last_entry ? area->last_entry + 4 : area->entries_start; + + nb = DIV_ROUND_UP(count, 8); + if ((addr + (nb * 4)) > area->entries_next) { + dev_err(tsa->dev, "si ram area full\n"); + return -ENOSPC; + } + + if (area->last_entry) { + /* Clear last flag */ + tsa_clrbits32(area->last_entry, TSA_SIRAM_ENTRY_LAST); + } + + left = count; + while (left) { + val = TSA_SIRAM_ENTRY_BYTE | tsa_serial_id2csel(tsa, serial_id); + + if (left > 16) { + cnt = 16; + } else { + cnt = left; + val |= TSA_SIRAM_ENTRY_LAST; + area->last_entry = addr; + } + val |= TSA_SIRAM_ENTRY_CNT(cnt - 1); + + tsa_write32(addr, val); + addr += 4; + left -= cnt; + } + + return 0; +} + +static int tsa_of_parse_tdm_route(struct tsa *tsa, struct device_node *tdm_np, + u32 tdms, u32 tdm_id, bool is_rx) +{ + struct tsa_entries_area area; + const char *route_name; + u32 serial_id; + int len, i; + u32 count; + const char *serial_name; + struct tsa_serial_info *serial_info; + struct tsa_tdm *tdm; + int ret; + u32 ts; + + route_name = is_rx ? "fsl,rx-ts-routes" : "fsl,tx-ts-routes"; + + len = of_property_count_u32_elems(tdm_np, route_name); + if (len < 0) { + dev_err(tsa->dev, "%pOF: failed to read %s\n", tdm_np, route_name); + return len; + } + if (len % 2 != 0) { + dev_err(tsa->dev, "%pOF: wrong %s format\n", tdm_np, route_name); + return -EINVAL; + } + + tsa_init_entries_area(tsa, &area, tdms, tdm_id, is_rx); + ts = 0; + for (i = 0; i < len; i += 2) { + of_property_read_u32_index(tdm_np, route_name, i, &count); + of_property_read_u32_index(tdm_np, route_name, i + 1, &serial_id); + + if (serial_id >= ARRAY_SIZE(tsa->serials)) { + dev_err(tsa->dev, "%pOF: invalid serial id (%u)\n", + tdm_np, serial_id); + return -EINVAL; + } + + serial_name = tsa_serial_id2name(tsa, serial_id); + if (!serial_name) { + dev_err(tsa->dev, "%pOF: unsupported serial id (%u)\n", + tdm_np, serial_id); + return -EINVAL; + } + + dev_dbg(tsa->dev, "tdm_id=%u, %s ts %u..%u -> %s\n", + tdm_id, route_name, ts, ts+count-1, serial_name); + ts += count; + + ret = tsa_add_entry(tsa, &area, count, serial_id); + if (ret) + return ret; + + serial_info = &tsa->serials[serial_id].info; + tdm = &tsa->tdm[tdm_id]; + if (is_rx) { + serial_info->rx_fs_rate = clk_get_rate(tdm->l1rsync_clk); + serial_info->rx_bit_rate = clk_get_rate(tdm->l1rclk_clk); + serial_info->nb_rx_ts += count; + } else { + serial_info->tx_fs_rate = tdm->l1tsync_clk ? + clk_get_rate(tdm->l1tsync_clk) : + clk_get_rate(tdm->l1rsync_clk); + serial_info->tx_bit_rate = tdm->l1tclk_clk ? + clk_get_rate(tdm->l1tclk_clk) : + clk_get_rate(tdm->l1rclk_clk); + serial_info->nb_tx_ts += count; + } + } + return 0; +} + +static inline int tsa_of_parse_tdm_rx_route(struct tsa *tsa, + struct device_node *tdm_np, + u32 tdms, u32 tdm_id) +{ + return tsa_of_parse_tdm_route(tsa, tdm_np, tdms, tdm_id, true); +} + +static inline int tsa_of_parse_tdm_tx_route(struct tsa *tsa, + struct device_node *tdm_np, + u32 tdms, u32 tdm_id) +{ + return tsa_of_parse_tdm_route(tsa, tdm_np, tdms, tdm_id, false); +} + +static int tsa_of_parse_tdms(struct tsa *tsa, struct device_node *np) +{ + struct device_node *tdm_np; + struct tsa_tdm *tdm; + struct clk *clk; + u32 tdm_id, val; + int ret; + int i; + + tsa->tdms = 0; + tsa->tdm[0].is_enable = false; + tsa->tdm[1].is_enable = false; + + for_each_available_child_of_node(np, tdm_np) { + ret = of_property_read_u32(tdm_np, "reg", &tdm_id); + if (ret) { + dev_err(tsa->dev, "%pOF: failed to read reg\n", tdm_np); + of_node_put(tdm_np); + return ret; + } + switch (tdm_id) { + case 0: + tsa->tdms |= BIT(TSA_TDMA); + break; + case 1: + tsa->tdms |= BIT(TSA_TDMB); + break; + default: + dev_err(tsa->dev, "%pOF: Invalid tdm_id (%u)\n", tdm_np, + tdm_id); + of_node_put(tdm_np); + return -EINVAL; + } + } + + for_each_available_child_of_node(np, tdm_np) { + ret = of_property_read_u32(tdm_np, "reg", &tdm_id); + if (ret) { + dev_err(tsa->dev, "%pOF: failed to read reg\n", tdm_np); + of_node_put(tdm_np); + return ret; + } + + tdm = &tsa->tdm[tdm_id]; + tdm->simode_tdm = TSA_SIMODE_TDM_SDM_NORM; + + val = 0; + ret = of_property_read_u32(tdm_np, "fsl,rx-frame-sync-delay-bits", + &val); + if (ret && ret != -EINVAL) { + dev_err(tsa->dev, + "%pOF: failed to read fsl,rx-frame-sync-delay-bits\n", + tdm_np); + of_node_put(tdm_np); + return ret; + } + if (val > 3) { + dev_err(tsa->dev, + "%pOF: Invalid fsl,rx-frame-sync-delay-bits (%u)\n", + tdm_np, val); + of_node_put(tdm_np); + return -EINVAL; + } + tdm->simode_tdm |= TSA_SIMODE_TDM_RFSD(val); + + val = 0; + ret = of_property_read_u32(tdm_np, "fsl,tx-frame-sync-delay-bits", + &val); + if (ret && ret != -EINVAL) { + dev_err(tsa->dev, + "%pOF: failed to read fsl,tx-frame-sync-delay-bits\n", + tdm_np); + of_node_put(tdm_np); + return ret; + } + if (val > 3) { + dev_err(tsa->dev, + "%pOF: Invalid fsl,tx-frame-sync-delay-bits (%u)\n", + tdm_np, val); + of_node_put(tdm_np); + return -EINVAL; + } + tdm->simode_tdm |= TSA_SIMODE_TDM_TFSD(val); + + if (of_property_read_bool(tdm_np, "fsl,common-rxtx-pins")) + tdm->simode_tdm |= TSA_SIMODE_TDM_CRT; + + if (of_property_read_bool(tdm_np, "fsl,clock-falling-edge")) + tdm->simode_tdm |= TSA_SIMODE_TDM_CE; + + if (of_property_read_bool(tdm_np, "fsl,fsync-rising-edge")) + tdm->simode_tdm |= TSA_SIMODE_TDM_FE; + + if (of_property_read_bool(tdm_np, "fsl,double-speed-clock")) + tdm->simode_tdm |= TSA_SIMODE_TDM_DSC; + + clk = of_clk_get_by_name(tdm_np, "l1rsync"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(tdm_np); + goto err; + } + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + of_node_put(tdm_np); + goto err; + } + tdm->l1rsync_clk = clk; + + clk = of_clk_get_by_name(tdm_np, "l1rclk"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(tdm_np); + goto err; + } + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + of_node_put(tdm_np); + goto err; + } + tdm->l1rclk_clk = clk; + + if (!(tdm->simode_tdm & TSA_SIMODE_TDM_CRT)) { + clk = of_clk_get_by_name(tdm_np, "l1tsync"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(tdm_np); + goto err; + } + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + of_node_put(tdm_np); + goto err; + } + tdm->l1tsync_clk = clk; + + clk = of_clk_get_by_name(tdm_np, "l1tclk"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + of_node_put(tdm_np); + goto err; + } + ret = clk_prepare_enable(clk); + if (ret) { + clk_put(clk); + of_node_put(tdm_np); + goto err; + } + tdm->l1tclk_clk = clk; + } + + ret = tsa_of_parse_tdm_rx_route(tsa, tdm_np, tsa->tdms, tdm_id); + if (ret) { + of_node_put(tdm_np); + goto err; + } + + ret = tsa_of_parse_tdm_tx_route(tsa, tdm_np, tsa->tdms, tdm_id); + if (ret) { + of_node_put(tdm_np); + goto err; + } + + tdm->is_enable = true; + } + return 0; + +err: + for (i = 0; i < 2; i++) { + if (tsa->tdm[i].l1rsync_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rsync_clk); + clk_put(tsa->tdm[i].l1rsync_clk); + } + if (tsa->tdm[i].l1rclk_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rclk_clk); + clk_put(tsa->tdm[i].l1rclk_clk); + } + if (tsa->tdm[i].l1tsync_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rsync_clk); + clk_put(tsa->tdm[i].l1rsync_clk); + } + if (tsa->tdm[i].l1tclk_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rclk_clk); + clk_put(tsa->tdm[i].l1rclk_clk); + } + } + return ret; +} + +static void tsa_init_si_ram(struct tsa *tsa) +{ + resource_size_t i; + + /* Fill all entries as the last one */ + for (i = 0; i < tsa->si_ram_sz; i += 4) + tsa_write32(tsa->si_ram + i, TSA_SIRAM_ENTRY_LAST); +} + +static int tsa_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *res; + struct tsa *tsa; + unsigned int i; + u32 val; + int ret; + + tsa = devm_kzalloc(&pdev->dev, sizeof(*tsa), GFP_KERNEL); + if (!tsa) + return -ENOMEM; + + tsa->dev = &pdev->dev; + + for (i = 0; i < ARRAY_SIZE(tsa->serials); i++) + tsa->serials[i].id = i; + + spin_lock_init(&tsa->lock); + + tsa->si_regs = devm_platform_ioremap_resource_byname(pdev, "si_regs"); + if (IS_ERR(tsa->si_regs)) + return PTR_ERR(tsa->si_regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "si_ram"); + if (!res) { + dev_err(tsa->dev, "si_ram resource missing\n"); + return -EINVAL; + } + tsa->si_ram_sz = resource_size(res); + tsa->si_ram = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tsa->si_ram)) + return PTR_ERR(tsa->si_ram); + + tsa_init_si_ram(tsa); + + ret = tsa_of_parse_tdms(tsa, np); + if (ret) + return ret; + + /* Set SIMODE */ + val = 0; + if (tsa->tdm[0].is_enable) + val |= TSA_SIMODE_TDMA(tsa->tdm[0].simode_tdm); + if (tsa->tdm[1].is_enable) + val |= TSA_SIMODE_TDMB(tsa->tdm[1].simode_tdm); + + tsa_clrsetbits32(tsa->si_regs + TSA_SIMODE, + TSA_SIMODE_TDMA(TSA_SIMODE_TDM_MASK) | + TSA_SIMODE_TDMB(TSA_SIMODE_TDM_MASK), + val); + + /* Set SIGMR */ + val = (tsa->tdms == BIT(TSA_TDMA)) ? + TSA_SIGMR_RDM_STATIC_TDMA : TSA_SIGMR_RDM_STATIC_TDMAB; + if (tsa->tdms & BIT(TSA_TDMA)) + val |= TSA_SIGMR_ENA; + if (tsa->tdms & BIT(TSA_TDMB)) + val |= TSA_SIGMR_ENB; + tsa_write8(tsa->si_regs + TSA_SIGMR, val); + + platform_set_drvdata(pdev, tsa); + + return 0; +} + +static int tsa_remove(struct platform_device *pdev) +{ + struct tsa *tsa = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < 2; i++) { + if (tsa->tdm[i].l1rsync_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rsync_clk); + clk_put(tsa->tdm[i].l1rsync_clk); + } + if (tsa->tdm[i].l1rclk_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rclk_clk); + clk_put(tsa->tdm[i].l1rclk_clk); + } + if (tsa->tdm[i].l1tsync_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rsync_clk); + clk_put(tsa->tdm[i].l1rsync_clk); + } + if (tsa->tdm[i].l1tclk_clk) { + clk_disable_unprepare(tsa->tdm[i].l1rclk_clk); + clk_put(tsa->tdm[i].l1rclk_clk); + } + } + return 0; +} + +static const struct of_device_id tsa_id_table[] = { + { .compatible = "fsl,cpm1-tsa" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, tsa_id_table); + +static struct platform_driver tsa_driver = { + .driver = { + .name = "fsl-tsa", + .of_match_table = of_match_ptr(tsa_id_table), + }, + .probe = tsa_probe, + .remove = tsa_remove, +}; +module_platform_driver(tsa_driver); + +struct tsa_serial *tsa_serial_get_byphandle(struct device_node *np, + const char *phandle_name) +{ + struct of_phandle_args out_args; + struct platform_device *pdev; + struct tsa_serial *tsa_serial; + struct tsa *tsa; + int ret; + + ret = of_parse_phandle_with_fixed_args(np, phandle_name, 1, 0, &out_args); + if (ret < 0) + return ERR_PTR(ret); + + if (!of_match_node(tsa_driver.driver.of_match_table, out_args.np)) { + of_node_put(out_args.np); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(out_args.np); + of_node_put(out_args.np); + if (!pdev) + return ERR_PTR(-ENODEV); + + tsa = platform_get_drvdata(pdev); + if (!tsa) { + platform_device_put(pdev); + return ERR_PTR(-EPROBE_DEFER); + } + + if (out_args.args_count != 1) { + platform_device_put(pdev); + return ERR_PTR(-EINVAL); + } + + if (out_args.args[0] >= ARRAY_SIZE(tsa->serials)) { + platform_device_put(pdev); + return ERR_PTR(-EINVAL); + } + + tsa_serial = &tsa->serials[out_args.args[0]]; + + /* + * Be sure that the serial id matches the phandle arg. + * The tsa_serials table is indexed by serial ids. The serial id is set + * during the probe() call and needs to be coherent. + */ + if (WARN_ON(tsa_serial->id != out_args.args[0])) { + platform_device_put(pdev); + return ERR_PTR(-EINVAL); + } + + return tsa_serial; +} +EXPORT_SYMBOL(tsa_serial_get_byphandle); + +void tsa_serial_put(struct tsa_serial *tsa_serial) +{ + struct tsa *tsa = tsa_serial_get_tsa(tsa_serial); + + put_device(tsa->dev); +} +EXPORT_SYMBOL(tsa_serial_put); + +static void devm_tsa_serial_release(struct device *dev, void *res) +{ + struct tsa_serial **tsa_serial = res; + + tsa_serial_put(*tsa_serial); +} + +struct tsa_serial *devm_tsa_serial_get_byphandle(struct device *dev, + struct device_node *np, + const char *phandle_name) +{ + struct tsa_serial *tsa_serial; + struct tsa_serial **dr; + + dr = devres_alloc(devm_tsa_serial_release, sizeof(*dr), GFP_KERNEL); + if (!dr) + return ERR_PTR(-ENOMEM); + + tsa_serial = tsa_serial_get_byphandle(np, phandle_name); + if (!IS_ERR(tsa_serial)) { + *dr = tsa_serial; + devres_add(dev, dr); + } else { + devres_free(dr); + } + + return tsa_serial; +} +EXPORT_SYMBOL(devm_tsa_serial_get_byphandle); + +MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>"); +MODULE_DESCRIPTION("CPM TSA driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/fsl/qe/tsa.h b/drivers/soc/fsl/qe/tsa.h new file mode 100644 index 000000000000..d9df89b6da3e --- /dev/null +++ b/drivers/soc/fsl/qe/tsa.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * TSA management + * + * Copyright 2022 CS GROUP France + * + * Author: Herve Codina <herve.codina@bootlin.com> + */ +#ifndef __SOC_FSL_TSA_H__ +#define __SOC_FSL_TSA_H__ + +#include <linux/types.h> + +struct device_node; +struct device; +struct tsa_serial; + +struct tsa_serial *tsa_serial_get_byphandle(struct device_node *np, + const char *phandle_name); +void tsa_serial_put(struct tsa_serial *tsa_serial); +struct tsa_serial *devm_tsa_serial_get_byphandle(struct device *dev, + struct device_node *np, + const char *phandle_name); + +/* Connect and disconnect the TSA serial */ +int tsa_serial_connect(struct tsa_serial *tsa_serial); +int tsa_serial_disconnect(struct tsa_serial *tsa_serial); + +/* Cell information */ +struct tsa_serial_info { + unsigned long rx_fs_rate; + unsigned long rx_bit_rate; + u8 nb_rx_ts; + unsigned long tx_fs_rate; + unsigned long tx_bit_rate; + u8 nb_tx_ts; +}; + +/* Get information */ +int tsa_serial_get_info(struct tsa_serial *tsa_serial, struct tsa_serial_info *info); + +#endif /* __SOC_FSL_TSA_H__ */ diff --git a/include/dt-bindings/soc/cpm1-fsl,tsa.h b/include/dt-bindings/soc/cpm1-fsl,tsa.h new file mode 100644 index 000000000000..2cc44e867dbe --- /dev/null +++ b/include/dt-bindings/soc/cpm1-fsl,tsa.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */ + +#ifndef __DT_BINDINGS_SOC_FSL_TSA_H +#define __DT_BINDINGS_SOC_FSL_TSA_H + +#define FSL_CPM_TSA_NU 0 /* Pseuso Cell Id for not used item */ +#define FSL_CPM_TSA_SCC2 1 +#define FSL_CPM_TSA_SCC3 2 +#define FSL_CPM_TSA_SCC4 3 +#define FSL_CPM_TSA_SMC1 4 +#define FSL_CPM_TSA_SMC2 5 + +#endif diff --git a/include/soc/fsl/qe/qmc.h b/include/soc/fsl/qe/qmc.h new file mode 100644 index 000000000000..3c61a50d2ae2 --- /dev/null +++ b/include/soc/fsl/qe/qmc.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * QMC management + * + * Copyright 2022 CS GROUP France + * + * Author: Herve Codina <herve.codina@bootlin.com> + */ +#ifndef __SOC_FSL_QMC_H__ +#define __SOC_FSL_QMC_H__ + +#include <linux/types.h> + +struct device_node; +struct device; +struct qmc_chan; + +struct qmc_chan *qmc_chan_get_byphandle(struct device_node *np, const char *phandle_name); +void qmc_chan_put(struct qmc_chan *chan); +struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev, struct device_node *np, + const char *phandle_name); + +enum qmc_mode { + QMC_TRANSPARENT, + QMC_HDLC, +}; + +struct qmc_chan_info { + enum qmc_mode mode; + unsigned long rx_fs_rate; + unsigned long rx_bit_rate; + u8 nb_rx_ts; + unsigned long tx_fs_rate; + unsigned long tx_bit_rate; + u8 nb_tx_ts; +}; + +int qmc_chan_get_info(struct qmc_chan *chan, struct qmc_chan_info *info); + +struct qmc_chan_param { + enum qmc_mode mode; + union { + struct { + u16 max_rx_buf_size; + u16 max_rx_frame_size; + bool is_crc32; + } hdlc; + struct { + u16 max_rx_buf_size; + } transp; + }; +}; + +int qmc_chan_set_param(struct qmc_chan *chan, const struct qmc_chan_param *param); + +int qmc_chan_write_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length, + void (*complete)(void *context), void *context); + +int qmc_chan_read_submit(struct qmc_chan *chan, dma_addr_t addr, size_t length, + void (*complete)(void *context, size_t length), + void *context); + +#define QMC_CHAN_READ (1<<0) +#define QMC_CHAN_WRITE (1<<1) +#define QMC_CHAN_ALL (QMC_CHAN_READ | QMC_CHAN_WRITE) + +int qmc_chan_start(struct qmc_chan *chan, int direction); +int qmc_chan_stop(struct qmc_chan *chan, int direction); +int qmc_chan_reset(struct qmc_chan *chan, int direction); + +#endif /* __SOC_FSL_QMC_H__ */ diff --git a/sound/soc/amd/vangogh/acp5x-mach.c b/sound/soc/amd/vangogh/acp5x-mach.c index eebf2650ad27..e5bcd1e6eb73 100644 --- a/sound/soc/amd/vangogh/acp5x-mach.c +++ b/sound/soc/amd/vangogh/acp5x-mach.c @@ -6,37 +6,40 @@ * Copyright 2021 Advanced Micro Devices, Inc. */ -#include <sound/soc.h> -#include <sound/soc-dapm.h> -#include <linux/module.h> -#include <linux/io.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> - -#include <sound/jack.h> -#include <linux/clk.h> -#include <linux/gpio.h> -#include <linux/gpio/consumer.h> -#include <linux/i2c.h> -#include <linux/input.h> #include <linux/acpi.h> #include <linux/dmi.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/input-event-codes.h> +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> #include "../../codecs/nau8821.h" -#include "../../codecs/cs35l41.h" - #include "acp5x.h" -#define DRV_NAME "acp5x_mach" -#define DUAL_CHANNEL 2 -#define ACP5X_NUVOTON_CODEC_DAI "nau8821-hifi" -#define VG_JUPITER 1 -#define ACP5X_NUVOTON_BCLK 3072000 -#define ACP5X_NAU8821_FREQ_OUT 12288000 +#define DRV_NAME "acp5x_mach" +#define DUAL_CHANNEL 2 +#define VG_JUPITER 1 +#define ACP5X_NAU8821_BCLK 3072000 +#define ACP5X_NAU8821_FREQ_OUT 12288000 +#define ACP5X_NAU8821_COMP_NAME "i2c-NVTN2020:00" +#define ACP5X_NAU8821_DAI_NAME "nau8821-hifi" +#define ACP5X_CS35L41_COMP_LNAME "spi-VLV1776:00" +#define ACP5X_CS35L41_COMP_RNAME "spi-VLV1776:01" +#define ACP5X_CS35L41_DAI_NAME "cs35l41-pcm" static unsigned long acp5x_machine_id; static struct snd_soc_jack vg_headset; +SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0"))); +SND_SOC_DAILINK_DEF(acp5x_i2s, DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0"))); +SND_SOC_DAILINK_DEF(acp5x_bt, DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1"))); +SND_SOC_DAILINK_DEF(nau8821, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_NAU8821_COMP_NAME, + ACP5X_NAU8821_DAI_NAME))); + static struct snd_soc_jack_pin acp5x_nau8821_jack_pins[] = { { .pin = "Headphone", @@ -48,18 +51,54 @@ static struct snd_soc_jack_pin acp5x_nau8821_jack_pins[] = { }, }; +static const struct snd_kcontrol_new acp5x_8821_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *dai; + int ret = 0; + + dai = snd_soc_card_get_codec_dai(card, ACP5X_NAU8821_DAI_NAME); + if (!dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "set sysclk err = %d\n", ret); + return -EIO; + } + } else { + ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_FLL_BLK, 0, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(dai->dev, "can't set BLK clock %d\n", ret); + ret = snd_soc_dai_set_pll(dai, 0, 0, ACP5X_NAU8821_BCLK, ACP5X_NAU8821_FREQ_OUT); + if (ret < 0) + dev_err(dai->dev, "can't set FLL: %d\n", ret); + } + + return ret; +} + static int acp5x_8821_init(struct snd_soc_pcm_runtime *rtd) { + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; int ret; - struct snd_soc_card *card = rtd->card; - struct snd_soc_component *component = - asoc_rtd_to_codec(rtd, 0)->component; /* * Headset buttons map to the google Reference headset. * These can be configured by userspace. */ - ret = snd_soc_card_jack_new_pins(card, "Headset Jack", + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_BTN_0, &vg_headset, acp5x_nau8821_jack_pins, ARRAY_SIZE(acp5x_nau8821_jack_pins)); @@ -70,12 +109,8 @@ static int acp5x_8821_init(struct snd_soc_pcm_runtime *rtd) snd_jack_set_key(vg_headset.jack, SND_JACK_BTN_0, KEY_MEDIA); nau8821_enable_jack_detect(component, &vg_headset); - return ret; -} -static int acp5x_cs35l41_init(struct snd_soc_pcm_runtime *rtd) -{ - return 0; + return ret; } static const unsigned int rates[] = { @@ -109,8 +144,7 @@ static int acp5x_8821_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_card *card = rtd->card; - struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card); + struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(rtd->card); machine->play_i2s_instance = I2S_SP_INSTANCE; machine->cap_i2s_instance = I2S_SP_INSTANCE; @@ -123,6 +157,7 @@ static int acp5x_8821_startup(struct snd_pcm_substream *substream) snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, &constraints_sample_bits); + return 0; } @@ -131,29 +166,36 @@ static int acp5x_nau8821_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_card *card = rtd->card; - struct snd_soc_dai *codec_dai = - snd_soc_card_get_codec_dai(card, - ACP5X_NUVOTON_CODEC_DAI); - int ret; + struct snd_soc_dai *dai = snd_soc_card_get_codec_dai(card, ACP5X_NAU8821_DAI_NAME); + int ret, bclk; - ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_FLL_BLK, 0, - SND_SOC_CLOCK_IN); + ret = snd_soc_dai_set_sysclk(dai, NAU8821_CLK_FLL_BLK, 0, SND_SOC_CLOCK_IN); if (ret < 0) dev_err(card->dev, "can't set FS clock %d\n", ret); - ret = snd_soc_dai_set_pll(codec_dai, 0, 0, snd_soc_params_to_bclk(params), - params_rate(params) * 256); + + bclk = snd_soc_params_to_bclk(params); + if (bclk < 0) { + dev_err(dai->dev, "Fail to get BCLK rate: %d\n", bclk); + return bclk; + } + + ret = snd_soc_dai_set_pll(dai, 0, 0, bclk, params_rate(params) * 256); if (ret < 0) dev_err(card->dev, "can't set FLL: %d\n", ret); return ret; } +static const struct snd_soc_ops acp5x_8821_ops = { + .startup = acp5x_8821_startup, + .hw_params = acp5x_nau8821_hw_params, +}; + static int acp5x_cs35l41_startup(struct snd_pcm_substream *substream) { - struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_card *card = rtd->card; - struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(card); + struct acp5x_platform_info *machine = snd_soc_card_get_drvdata(rtd->card); + struct snd_pcm_runtime *runtime = substream->runtime; machine->play_i2s_instance = I2S_HS_INSTANCE; @@ -162,6 +204,7 @@ static int acp5x_cs35l41_startup(struct snd_pcm_substream *substream) &constraints_channels); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + return 0; } @@ -169,80 +212,66 @@ static int acp5x_cs35l41_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_card *card = rtd->card; - struct snd_soc_dai *codec_dai; + unsigned int bclk, rate = params_rate(params); + struct snd_soc_component *comp; int ret, i; - unsigned int num_codecs = rtd->dai_link->num_codecs; - unsigned int bclk_val; - - ret = 0; - for (i = 0; i < num_codecs; i++) { - codec_dai = asoc_rtd_to_codec(rtd, i); - if (strcmp(codec_dai->name, "cs35l41-pcm") == 0) { - switch (params_rate(params)) { - case 48000: - bclk_val = 1536000; - break; - default: - dev_err(card->dev, "Invalid Samplerate:0x%x\n", - params_rate(params)); + + switch (rate) { + case 48000: + bclk = 1536000; + break; + default: + bclk = 0; + break; + } + + for_each_rtd_components(rtd, i, comp) { + if (!(strcmp(comp->name, ACP5X_CS35L41_COMP_LNAME)) || + !(strcmp(comp->name, ACP5X_CS35L41_COMP_RNAME))) { + if (!bclk) { + dev_err(comp->dev, "Invalid sample rate: 0x%x\n", rate); return -EINVAL; } - ret = snd_soc_component_set_sysclk(codec_dai->component, - 0, 0, bclk_val, SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(card->dev, "failed to set sysclk for CS35l41 dai\n"); + + ret = snd_soc_component_set_sysclk(comp, 0, 0, bclk, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(comp->dev, "failed to set SYSCLK: %d\n", ret); return ret; } } } - return ret; -} + return 0; -static const struct snd_soc_ops acp5x_8821_ops = { - .startup = acp5x_8821_startup, - .hw_params = acp5x_nau8821_hw_params, -}; +} static const struct snd_soc_ops acp5x_cs35l41_play_ops = { .startup = acp5x_cs35l41_startup, .hw_params = acp5x_cs35l41_hw_params, }; -static struct snd_soc_codec_conf cs35l41_conf[] = { +static struct snd_soc_codec_conf acp5x_cs35l41_conf[] = { { - .dlc = COMP_CODEC_CONF("spi-VLV1776:00"), + .dlc = COMP_CODEC_CONF(ACP5X_CS35L41_COMP_LNAME), .name_prefix = "Left", }, { - .dlc = COMP_CODEC_CONF("spi-VLV1776:01"), + .dlc = COMP_CODEC_CONF(ACP5X_CS35L41_COMP_RNAME), .name_prefix = "Right", }, }; -SND_SOC_DAILINK_DEF(acp5x_i2s, - DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.0"))); +SND_SOC_DAILINK_DEF(cs35l41, DAILINK_COMP_ARRAY(COMP_CODEC(ACP5X_CS35L41_COMP_LNAME, + ACP5X_CS35L41_DAI_NAME), + COMP_CODEC(ACP5X_CS35L41_COMP_RNAME, + ACP5X_CS35L41_DAI_NAME))); -SND_SOC_DAILINK_DEF(acp5x_bt, - DAILINK_COMP_ARRAY(COMP_CPU("acp5x_i2s_playcap.1"))); - -SND_SOC_DAILINK_DEF(nau8821, - DAILINK_COMP_ARRAY(COMP_CODEC("i2c-NVTN2020:00", - "nau8821-hifi"))); - -SND_SOC_DAILINK_DEF(cs35l41, - DAILINK_COMP_ARRAY(COMP_CODEC("spi-VLV1776:00", "cs35l41-pcm"), - COMP_CODEC("spi-VLV1776:01", "cs35l41-pcm"))); - -SND_SOC_DAILINK_DEF(platform, - DAILINK_COMP_ARRAY(COMP_PLATFORM("acp5x_i2s_dma.0"))); - -static struct snd_soc_dai_link acp5x_dai[] = { +static struct snd_soc_dai_link acp5x_8821_35l41_dai[] = { { .name = "acp5x-8821-play", .stream_name = "Playback/Capture", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, .dpcm_playback = 1, .dpcm_capture = 1, @@ -253,65 +282,28 @@ static struct snd_soc_dai_link acp5x_dai[] = { { .name = "acp5x-CS35L41-Stereo", .stream_name = "CS35L41 Stereo Playback", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC, .dpcm_playback = 1, .playback_only = 1, .ops = &acp5x_cs35l41_play_ops, - .init = acp5x_cs35l41_init, SND_SOC_DAILINK_REG(acp5x_bt, cs35l41, platform), }, }; -static int platform_clock_control(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - struct snd_soc_dapm_context *dapm = w->dapm; - struct snd_soc_card *card = dapm->card; - struct snd_soc_dai *codec_dai; - int ret = 0; - codec_dai = snd_soc_card_get_codec_dai(card, ACP5X_NUVOTON_CODEC_DAI); - if (!codec_dai) { - dev_err(card->dev, "Codec dai not found\n"); - return -EIO; - } - if (SND_SOC_DAPM_EVENT_OFF(event)) { - ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_INTERNAL, - 0, SND_SOC_CLOCK_IN); - if (ret < 0) { - dev_err(card->dev, "set sysclk err = %d\n", ret); - return -EIO; - } - } else { - ret = snd_soc_dai_set_sysclk(codec_dai, NAU8821_CLK_FLL_BLK, 0, - SND_SOC_CLOCK_IN); - if (ret < 0) - dev_err(codec_dai->dev, "can't set BLK clock %d\n", ret); - ret = snd_soc_dai_set_pll(codec_dai, 0, 0, ACP5X_NUVOTON_BCLK, - ACP5X_NAU8821_FREQ_OUT); - if (ret < 0) - dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); - } - return ret; -} - -static const struct snd_kcontrol_new acp5x_8821_controls[] = { - SOC_DAPM_PIN_SWITCH("Headphone"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), - SOC_DAPM_PIN_SWITCH("Int Mic"), -}; - -static const struct snd_soc_dapm_widget acp5x_8821_widgets[] = { +static const struct snd_soc_dapm_widget acp5x_8821_35l41_widgets[] = { SND_SOC_DAPM_HP("Headphone", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), SND_SOC_DAPM_MIC("Int Mic", NULL), SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, - platform_clock_control, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + platform_clock_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), }; -static const struct snd_soc_dapm_route acp5x_8821_audio_route[] = { +static const struct snd_soc_dapm_route acp5x_8821_35l41_audio_route[] = { /* HP jack connectors - unknown if we have jack detection */ { "Headphone", NULL, "HPOL" }, { "Headphone", NULL, "HPOR" }, @@ -324,17 +316,17 @@ static const struct snd_soc_dapm_route acp5x_8821_audio_route[] = { { "Int Mic", NULL, "Platform Clock" }, }; -static struct snd_soc_card acp5x_card = { +static struct snd_soc_card acp5x_8821_35l41_card = { .name = "acp5x", .owner = THIS_MODULE, - .dai_link = acp5x_dai, - .num_links = ARRAY_SIZE(acp5x_dai), - .dapm_widgets = acp5x_8821_widgets, - .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_widgets), - .dapm_routes = acp5x_8821_audio_route, - .num_dapm_routes = ARRAY_SIZE(acp5x_8821_audio_route), - .codec_conf = cs35l41_conf, - .num_configs = ARRAY_SIZE(cs35l41_conf), + .dai_link = acp5x_8821_35l41_dai, + .num_links = ARRAY_SIZE(acp5x_8821_35l41_dai), + .dapm_widgets = acp5x_8821_35l41_widgets, + .num_dapm_widgets = ARRAY_SIZE(acp5x_8821_35l41_widgets), + .dapm_routes = acp5x_8821_35l41_audio_route, + .num_dapm_routes = ARRAY_SIZE(acp5x_8821_35l41_audio_route), + .codec_conf = acp5x_cs35l41_conf, + .num_configs = ARRAY_SIZE(acp5x_cs35l41_conf), .controls = acp5x_8821_controls, .num_controls = ARRAY_SIZE(acp5x_8821_controls), }; @@ -342,6 +334,7 @@ static struct snd_soc_card acp5x_card = { static int acp5x_vg_quirk_cb(const struct dmi_system_id *id) { acp5x_machine_id = VG_JUPITER; + return 1; } @@ -358,33 +351,31 @@ static const struct dmi_system_id acp5x_vg_quirk_table[] = { static int acp5x_probe(struct platform_device *pdev) { - int ret; struct acp5x_platform_info *machine; + struct device *dev = &pdev->dev; struct snd_soc_card *card; + int ret; - machine = devm_kzalloc(&pdev->dev, sizeof(struct acp5x_platform_info), - GFP_KERNEL); + machine = devm_kzalloc(dev, sizeof(*machine), GFP_KERNEL); if (!machine) return -ENOMEM; dmi_check_system(acp5x_vg_quirk_table); switch (acp5x_machine_id) { case VG_JUPITER: - card = &acp5x_card; - acp5x_card.dev = &pdev->dev; + card = &acp5x_8821_35l41_card; break; default: return -ENODEV; } + card->dev = dev; platform_set_drvdata(pdev, card); snd_soc_card_set_drvdata(card, machine); - ret = devm_snd_soc_register_card(&pdev->dev, card); - if (ret) { - return dev_err_probe(&pdev->dev, ret, - "snd_soc_register_card(%s) failed\n", - acp5x_card.name); - } + ret = devm_snd_soc_register_card(dev, card); + if (ret) + return dev_err_probe(dev, ret, "Register card (%s) failed\n", card->name); + return 0; } diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c index 1aed3baa9369..81bfa98fd516 100644 --- a/sound/soc/atmel/mchp-pdmc.c +++ b/sound/soc/atmel/mchp-pdmc.c @@ -8,6 +8,7 @@ #include <dt-bindings/sound/microchip,pdmc.h> +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/module.h> #include <linux/of.h> @@ -49,8 +50,6 @@ #define MCHP_PDMC_MR_OSR256 (3 << 16) #define MCHP_PDMC_MR_SINCORDER_MASK GENMASK(23, 20) -#define MCHP_PDMC_MR_SINCORDER(order) (((order) << 20) & \ - MCHP_PDMC_MR_SINCORDER_MASK) #define MCHP_PDMC_MR_SINC_OSR_MASK GENMASK(27, 24) #define MCHP_PDMC_MR_SINC_OSR_DIS (0 << 24) @@ -62,8 +61,6 @@ #define MCHP_PDMC_MR_SINC_OSR_256 (6 << 24) #define MCHP_PDMC_MR_CHUNK_MASK GENMASK(31, 28) -#define MCHP_PDMC_MR_CHUNK(chunk) (((chunk) << 28) & \ - MCHP_PDMC_MR_CHUNK_MASK) /* * ---- Configuration Register (Read/Write) ---- @@ -617,10 +614,10 @@ static int mchp_pdmc_hw_params(struct snd_pcm_substream *substream, mr_val |= mchp_pdmc_mr_set_osr(dd->audio_filter_en, osr); - mr_val |= MCHP_PDMC_MR_SINCORDER(dd->sinc_order); + mr_val |= FIELD_PREP(MCHP_PDMC_MR_SINCORDER_MASK, dd->sinc_order); dd->addr.maxburst = mchp_pdmc_period_to_maxburst(snd_pcm_lib_period_bytes(substream)); - mr_val |= MCHP_PDMC_MR_CHUNK(dd->addr.maxburst); + mr_val |= FIELD_PREP(MCHP_PDMC_MR_CHUNK_MASK, dd->addr.maxburst); dev_dbg(comp->dev, "maxburst set to %d\n", dd->addr.maxburst); snd_soc_component_update_bits(comp, MCHP_PDMC_MR, @@ -762,12 +759,10 @@ static int mchp_pdmc_pcm_new(struct snd_soc_pcm_runtime *rtd, int ret; ret = mchp_pdmc_add_chmap_ctls(rtd->pcm, dd); - if (ret < 0) { + if (ret < 0) dev_err(dd->dev, "failed to add channel map controls: %d\n", ret); - return ret; - } - return 0; + return ret; } static struct snd_soc_dai_driver mchp_pdmc_dai = { @@ -788,7 +783,7 @@ static struct snd_soc_dai_driver mchp_pdmc_dai = { /* PDMC interrupt handler */ static irqreturn_t mchp_pdmc_interrupt(int irq, void *dev_id) { - struct mchp_pdmc *dd = (struct mchp_pdmc *)dev_id; + struct mchp_pdmc *dd = dev_id; u32 isr, msr, pending; irqreturn_t ret = IRQ_NONE; @@ -1082,7 +1077,7 @@ static int mchp_pdmc_probe(struct platform_device *pdev) } ret = devm_request_irq(dev, irq, mchp_pdmc_interrupt, 0, - dev_name(&pdev->dev), (void *)dd); + dev_name(&pdev->dev), dd); if (ret < 0) { dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", irq, ret); diff --git a/sound/soc/atmel/mchp-spdiftx.c b/sound/soc/atmel/mchp-spdiftx.c index 20d135c718b0..02a2fa7a42dd 100644 --- a/sound/soc/atmel/mchp-spdiftx.c +++ b/sound/soc/atmel/mchp-spdiftx.c @@ -72,11 +72,9 @@ /* Valid Bits per Sample */ #define SPDIFTX_MR_VBPS_MASK GENMASK(13, 8) -#define SPDIFTX_MR_VBPS(bps) FIELD_PREP(SPDIFTX_MR_VBPS_MASK, bps) /* Chunk Size */ #define SPDIFTX_MR_CHUNK_MASK GENMASK(19, 16) -#define SPDIFTX_MR_CHUNK(size) FIELD_PREP(SPDIFTX_MR_CHUNK_MASK, size) /* Validity Bits for Channels 1 and 2 */ #define SPDIFTX_MR_VALID1 BIT(24) @@ -89,7 +87,6 @@ /* Bytes per Sample */ #define SPDIFTX_MR_BPS_MASK GENMASK(29, 28) -#define SPDIFTX_MR_BPS(bytes) FIELD_PREP(SPDIFTX_MR_BPS_MASK, (bytes - 1)) /* * ---- Interrupt Enable/Disable/Mask/Status Register (Write/Read-only) ---- @@ -309,15 +306,10 @@ static int mchp_spdiftx_trigger(struct snd_pcm_substream *substream, int cmd, { struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); struct mchp_spdiftx_mixer_control *ctrl = &dev->control; - u32 mr; - int running; int ret; /* do not start/stop while channel status or user data is updated */ spin_lock(&ctrl->lock); - regmap_read(dev->regmap, SPDIFTX_MR, &mr); - running = !!(mr & SPDIFTX_MR_TXEN_ENABLE); - switch (cmd) { case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_START: @@ -326,10 +318,8 @@ static int mchp_spdiftx_trigger(struct snd_pcm_substream *substream, int cmd, dev->suspend_irq = 0; fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (!running) { - mr &= ~SPDIFTX_MR_TXEN_MASK; - mr |= SPDIFTX_MR_TXEN_ENABLE; - } + ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK, + SPDIFTX_MR_TXEN_ENABLE); break; case SNDRV_PCM_TRIGGER_SUSPEND: regmap_read(dev->regmap, SPDIFTX_IMR, &dev->suspend_irq); @@ -339,20 +329,15 @@ static int mchp_spdiftx_trigger(struct snd_pcm_substream *substream, int cmd, SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (running) { - mr &= ~SPDIFTX_MR_TXEN_MASK; - mr |= SPDIFTX_MR_TXEN_DISABLE; - } + ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK, + SPDIFTX_MR_TXEN_DISABLE); break; default: - spin_unlock(&ctrl->lock); - return -EINVAL; + ret = -EINVAL; } - - ret = regmap_write(dev->regmap, SPDIFTX_MR, mr); spin_unlock(&ctrl->lock); if (ret) - dev_err(dev->dev, "unable to disable TX: %d\n", ret); + dev_err(dev->dev, "unable to start/stop TX: %d\n", ret); return ret; } @@ -402,47 +387,47 @@ static int mchp_spdiftx_hw_params(struct snd_pcm_substream *substream, params_channels(params)); return -EINVAL; } - mr |= SPDIFTX_MR_CHUNK(dev->playback.maxburst); + mr |= FIELD_PREP(SPDIFTX_MR_CHUNK_MASK, dev->playback.maxburst); switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: - mr |= SPDIFTX_MR_VBPS(8); + mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 8); break; case SNDRV_PCM_FORMAT_S16_BE: mr |= SPDIFTX_MR_ENDIAN_BIG; fallthrough; case SNDRV_PCM_FORMAT_S16_LE: - mr |= SPDIFTX_MR_VBPS(16); + mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 16); break; case SNDRV_PCM_FORMAT_S18_3BE: mr |= SPDIFTX_MR_ENDIAN_BIG; fallthrough; case SNDRV_PCM_FORMAT_S18_3LE: - mr |= SPDIFTX_MR_VBPS(18); + mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 18); break; case SNDRV_PCM_FORMAT_S20_3BE: mr |= SPDIFTX_MR_ENDIAN_BIG; fallthrough; case SNDRV_PCM_FORMAT_S20_3LE: - mr |= SPDIFTX_MR_VBPS(20); + mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 20); break; case SNDRV_PCM_FORMAT_S24_3BE: mr |= SPDIFTX_MR_ENDIAN_BIG; fallthrough; case SNDRV_PCM_FORMAT_S24_3LE: - mr |= SPDIFTX_MR_VBPS(24); + mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24); break; case SNDRV_PCM_FORMAT_S24_BE: mr |= SPDIFTX_MR_ENDIAN_BIG; fallthrough; case SNDRV_PCM_FORMAT_S24_LE: - mr |= SPDIFTX_MR_VBPS(24); + mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24); break; case SNDRV_PCM_FORMAT_S32_BE: mr |= SPDIFTX_MR_ENDIAN_BIG; fallthrough; case SNDRV_PCM_FORMAT_S32_LE: - mr |= SPDIFTX_MR_VBPS(32); + mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 32); break; default: dev_err(dev->dev, "unsupported PCM format: %d\n", @@ -450,7 +435,7 @@ static int mchp_spdiftx_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - mr |= SPDIFTX_MR_BPS(bps); + mr |= FIELD_PREP(SPDIFTX_MR_BPS_MASK, bps - 1); switch (params_rate(params)) { case 22050: diff --git a/sound/soc/codecs/max9867.c b/sound/soc/codecs/max9867.c index e161ab037bf7..ae552d72beec 100644 --- a/sound/soc/codecs/max9867.c +++ b/sound/soc/codecs/max9867.c @@ -6,6 +6,7 @@ // Copyright 2018 Ladislav Michl <ladis@linux-mips.org> // +#include <linux/clk.h> #include <linux/delay.h> #include <linux/i2c.h> #include <linux/module.h> @@ -16,6 +17,7 @@ #include "max9867.h" struct max9867_priv { + struct clk *mclk; struct regmap *regmap; const struct snd_pcm_hw_constraint_list *constraints; unsigned int sysclk, pclk; @@ -577,6 +579,11 @@ static int max9867_set_bias_level(struct snd_soc_component *component, struct max9867_priv *max9867 = snd_soc_component_get_drvdata(component); switch (level) { + case SND_SOC_BIAS_ON: + err = clk_prepare_enable(max9867->mclk); + if (err) + return err; + break; case SND_SOC_BIAS_STANDBY: if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { err = regcache_sync(max9867->regmap); @@ -595,6 +602,7 @@ static int max9867_set_bias_level(struct snd_soc_component *component, return err; regcache_mark_dirty(max9867->regmap); + clk_disable_unprepare(max9867->mclk); break; default: break; @@ -663,9 +671,16 @@ static int max9867_i2c_probe(struct i2c_client *i2c) dev_info(&i2c->dev, "device revision: %x\n", reg); ret = devm_snd_soc_register_component(&i2c->dev, &max9867_component, max9867_dai, ARRAY_SIZE(max9867_dai)); - if (ret < 0) + if (ret < 0) { dev_err(&i2c->dev, "Failed to register component: %d\n", ret); - return ret; + return ret; + } + + max9867->mclk = devm_clk_get(&i2c->dev, NULL); + if (IS_ERR(max9867->mclk)) + return PTR_ERR(max9867->mclk); + + return 0; } static const struct i2c_device_id max9867_i2c_id[] = { diff --git a/sound/soc/codecs/nau8821.c b/sound/soc/codecs/nau8821.c index 4a72b94e8410..fee970427a24 100644 --- a/sound/soc/codecs/nau8821.c +++ b/sound/soc/codecs/nau8821.c @@ -322,12 +322,92 @@ static const struct soc_enum nau8821_dac_oversampl_enum = SOC_ENUM_SINGLE(NAU8821_R2C_DAC_CTRL1, NAU8821_DAC_OVERSAMPLE_SFT, ARRAY_SIZE(nau8821_dac_oversampl), nau8821_dac_oversampl); +static const char * const nau8821_adc_drc_noise_gate[] = { + "1:1", "2:1", "4:1", "8:1" }; + +static const struct soc_enum nau8821_adc_drc_noise_gate_enum = + SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES, NAU8821_DRC_NG_SLP_ADC_SFT, + ARRAY_SIZE(nau8821_adc_drc_noise_gate), + nau8821_adc_drc_noise_gate); + +static const char * const nau8821_adc_drc_expansion_slope[] = { + "1:1", "2:1", "4:1" }; + +static const struct soc_enum nau8821_adc_drc_expansion_slope_enum = + SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES, NAU8821_DRC_EXP_SLP_ADC_SFT, + ARRAY_SIZE(nau8821_adc_drc_expansion_slope), + nau8821_adc_drc_expansion_slope); + +static const char * const nau8821_adc_drc_lower_region[] = { + "0", "1:2", "1:4", "1:8", "1:16", "", "", "1:1" }; + +static const struct soc_enum nau8821_adc_drc_lower_region_enum = + SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES, + NAU8821_DRC_CMP2_SLP_ADC_SFT, + ARRAY_SIZE(nau8821_adc_drc_lower_region), + nau8821_adc_drc_lower_region); + +static const char * const nau8821_higher_region[] = { + "0", "1:2", "1:4", "1:8", "1:16", "", "", "1:1" }; + +static const struct soc_enum nau8821_higher_region_enum = + SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES, + NAU8821_DRC_CMP1_SLP_ADC_SFT, + ARRAY_SIZE(nau8821_higher_region), + nau8821_higher_region); + +static const char * const nau8821_limiter_slope[] = { + "0", "1:2", "1:4", "1:8", "1:16", "1:32", "1:64", "1:1" }; + +static const struct soc_enum nau8821_limiter_slope_enum = + SOC_ENUM_SINGLE(NAU8821_R38_ADC_DRC_SLOPES, + NAU8821_DRC_LMT_SLP_ADC_SFT, ARRAY_SIZE(nau8821_limiter_slope), + nau8821_limiter_slope); + +static const char * const nau8821_detection_attack_time[] = { + "Ts", "3Ts", "7Ts", "15Ts", "31Ts", "63Ts", "127Ts", "255Ts", + "", "511Ts" }; + +static const struct soc_enum nau8821_detection_attack_time_enum = + SOC_ENUM_SINGLE(NAU8821_R39_ADC_DRC_ATKDCY, + NAU8821_DRC_PK_COEF1_ADC_SFT, + ARRAY_SIZE(nau8821_detection_attack_time), + nau8821_detection_attack_time); + +static const char * const nau8821_detection_release_time[] = { + "63Ts", "127Ts", "255Ts", "511Ts", "1023Ts", "2047Ts", "4095Ts", + "8191Ts", "", "16383Ts" }; + +static const struct soc_enum nau8821_detection_release_time_enum = + SOC_ENUM_SINGLE(NAU8821_R39_ADC_DRC_ATKDCY, + NAU8821_DRC_PK_COEF2_ADC_SFT, + ARRAY_SIZE(nau8821_detection_release_time), + nau8821_detection_release_time); + +static const char * const nau8821_attack_time[] = { + "Ts", "3Ts", "7Ts", "15Ts", "31Ts", "63Ts", "127Ts", "255Ts", + "511Ts", "1023Ts", "2047Ts", "4095Ts", "8191Ts" }; + +static const struct soc_enum nau8821_attack_time_enum = + SOC_ENUM_SINGLE(NAU8821_R39_ADC_DRC_ATKDCY, NAU8821_DRC_ATK_ADC_SFT, + ARRAY_SIZE(nau8821_attack_time), nau8821_attack_time); + +static const char * const nau8821_decay_time[] = { + "63Ts", "127Ts", "255Ts", "511Ts", "1023Ts", "2047Ts", "4095Ts", + "8191Ts", "16383Ts", "32757Ts", "65535Ts" }; + +static const struct soc_enum nau8821_decay_time_enum = + SOC_ENUM_SINGLE(NAU8821_R39_ADC_DRC_ATKDCY, NAU8821_DRC_DCY_ADC_SFT, + ARRAY_SIZE(nau8821_decay_time), nau8821_decay_time); + static const DECLARE_TLV_DB_MINMAX_MUTE(adc_vol_tlv, -6600, 2400); static const DECLARE_TLV_DB_MINMAX_MUTE(sidetone_vol_tlv, -4200, 0); static const DECLARE_TLV_DB_MINMAX(hp_vol_tlv, -900, 0); static const DECLARE_TLV_DB_SCALE(playback_vol_tlv, -6600, 50, 1); static const DECLARE_TLV_DB_MINMAX(fepga_gain_tlv, -100, 3600); static const DECLARE_TLV_DB_MINMAX_MUTE(crosstalk_vol_tlv, -7000, 2400); +static const DECLARE_TLV_DB_MINMAX(drc_knee4_tlv, -9800, -3500); +static const DECLARE_TLV_DB_MINMAX(drc_knee3_tlv, -8100, -1800); static const struct snd_kcontrol_new nau8821_controls[] = { SOC_DOUBLE_TLV("Mic Volume", NAU8821_R35_ADC_DGAIN_CTRL1, @@ -346,6 +426,22 @@ static const struct snd_kcontrol_new nau8821_controls[] = { SOC_DOUBLE_TLV("Headphone Crosstalk Volume", NAU8821_R2F_DAC_DGAIN_CTRL, 0, 8, 0xff, 0, crosstalk_vol_tlv), + SOC_SINGLE_TLV("ADC DRC KNEE4", NAU8821_R37_ADC_DRC_KNEE_IP34, + NAU8821_DRC_KNEE4_IP_ADC_SFT, 0x3f, 1, drc_knee4_tlv), + SOC_SINGLE_TLV("ADC DRC KNEE3", NAU8821_R37_ADC_DRC_KNEE_IP34, + NAU8821_DRC_KNEE3_IP_ADC_SFT, 0x3f, 1, drc_knee3_tlv), + + SOC_ENUM("ADC DRC Noise Gate", nau8821_adc_drc_noise_gate_enum), + SOC_ENUM("ADC DRC Expansion Slope", nau8821_adc_drc_expansion_slope_enum), + SOC_ENUM("ADC DRC Lower Region", nau8821_adc_drc_lower_region_enum), + SOC_ENUM("ADC DRC Higher Region", nau8821_higher_region_enum), + SOC_ENUM("ADC DRC Limiter Slope", nau8821_limiter_slope_enum), + SOC_ENUM("ADC DRC Peak Detection Attack Time", nau8821_detection_attack_time_enum), + SOC_ENUM("ADC DRC Peak Detection Release Time", nau8821_detection_release_time_enum), + SOC_ENUM("ADC DRC Attack Time", nau8821_attack_time_enum), + SOC_ENUM("ADC DRC Decay Time", nau8821_decay_time_enum), + SOC_SINGLE("DRC Enable Switch", NAU8821_R36_ADC_DRC_KNEE_IP12, + NAU8821_DRC_ENA_ADC_SFT, 1, 0), SOC_ENUM("ADC Decimation Rate", nau8821_adc_decimation_enum), SOC_ENUM("DAC Oversampling Rate", nau8821_dac_oversampl_enum), diff --git a/sound/soc/codecs/nau8821.h b/sound/soc/codecs/nau8821.h index c44251f54d48..d962293c218e 100644 --- a/sound/soc/codecs/nau8821.h +++ b/sound/soc/codecs/nau8821.h @@ -350,6 +350,29 @@ #define NAU8821_ADCL_CH_VOL_SFT 0 #define NAU8821_ADCL_CH_VOL_MASK 0xff +/* ADC_DRC_KNEE_IP12 (0x36) */ +#define NAU8821_DRC_ENA_ADC_SFT 15 +#define NAU8821_DRC_ENA_ADC_EN (0x1 << NAU8821_DRC_ENA_ADC_SFT) + +/* ADC_DRC_KNEE_IP34 (0x37) */ +#define NAU8821_DRC_KNEE4_IP_ADC_SFT 8 +#define NAU8821_DRC_KNEE4_IP_ADC_MASK (0xff << NAU8821_DRC_KNEE4_IP_ADC_SFT) +#define NAU8821_DRC_KNEE3_IP_ADC_SFT 0 +#define NAU8821_DRC_KNEE3_IP_ADC_MASK 0xff + +/* ADC_DRC_SLOPES (0x38) */ +#define NAU8821_DRC_NG_SLP_ADC_SFT 12 +#define NAU8821_DRC_EXP_SLP_ADC_SFT 9 +#define NAU8821_DRC_CMP2_SLP_ADC_SFT 6 +#define NAU8821_DRC_CMP1_SLP_ADC_SFT 3 +#define NAU8821_DRC_LMT_SLP_ADC_SFT 0 + +/* ADC_DRC_ATKDCY (0x39) */ +#define NAU8821_DRC_PK_COEF1_ADC_SFT 12 +#define NAU8821_DRC_PK_COEF2_ADC_SFT 8 +#define NAU8821_DRC_ATK_ADC_SFT 4 +#define NAU8821_DRC_DCY_ADC_SFT 0 + /* BIQ1_COF10 (0x4a) */ #define NAU8821_BIQ1_DAC_EN_SFT 3 #define NAU8821_BIQ1_DAC_EN_EN (0x1 << NAU8821_BIQ1_DAC_EN_SFT) diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index 84ec1b527646..f39c3273b2fd 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -718,6 +718,63 @@ static const struct regmap_config tas5721_regmap_config = { .volatile_table = &tas571x_volatile_regs, }; +static const char *const tas5733_supply_names[] = { + "AVDD", + "DVDD", + "PVDD", +}; + +static const struct reg_default tas5733_reg_defaults[] = { + {TAS571X_CLK_CTRL_REG, 0x6c}, + {TAS571X_DEV_ID_REG, 0x00}, + {TAS571X_ERR_STATUS_REG, 0x00}, + {TAS571X_SYS_CTRL_1_REG, 0xa0}, + {TAS571X_SDI_REG, 0x05}, + {TAS571X_SYS_CTRL_2_REG, 0x40}, + {TAS571X_SOFT_MUTE_REG, 0x07}, + {TAS571X_MVOL_REG, 0x03ff}, + {TAS571X_CH1_VOL_REG, 0x00c0}, + {TAS571X_CH2_VOL_REG, 0x00c0}, + {TAS571X_CH3_VOL_REG, 0x00c0}, + {TAS571X_VOL_CFG_REG, 0xf0}, + {TAS571X_MODULATION_LIMIT_REG, 0x07}, + {TAS571X_IC_DELAY_CH1_REG, 0xb8}, + {TAS571X_IC_DELAY_CH2_REG, 0x60}, + {TAS571X_IC_DELAY_CH3_REG, 0xa0}, + {TAS571X_IC_DELAY_CH4_REG, 0x48}, + {TAS571X_PWM_CH_SDN_GROUP_REG, 0x30}, + {TAS571X_START_STOP_PERIOD_REG, 0x68}, + {TAS571X_OSC_TRIM_REG, 0x82}, + {TAS571X_BKND_ERR_REG, 0x02}, + {TAS571X_INPUT_MUX_REG, 0x00897772}, + {TAS571X_PWM_MUX_REG, 0x01021345}, + {TAS5717_CH1_RIGHT_CH_MIX_REG, 0x00}, + {TAS5717_CH1_LEFT_CH_MIX_REG, 0x800000}, + {TAS5717_CH2_LEFT_CH_MIX_REG, 0x00}, + {TAS5717_CH2_RIGHT_CH_MIX_REG, 0x800000}, +}; + +static const struct regmap_config tas5733_regmap_config = { + .reg_bits = 8, + .val_bits = 32, + .max_register = 0xff, + .reg_read = tas571x_reg_read, + .reg_write = tas571x_reg_write, + .reg_defaults = tas5733_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas5733_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .wr_table = &tas571x_write_regs, + .volatile_table = &tas571x_volatile_regs, +}; + +static const struct tas571x_chip tas5733_chip = { + .supply_names = tas5733_supply_names, + .num_supply_names = ARRAY_SIZE(tas5733_supply_names), + .controls = tas5717_controls, + .num_controls = ARRAY_SIZE(tas5717_controls), + .regmap_config = &tas5733_regmap_config, + .vol_reg_size = 2, +}; static const struct tas571x_chip tas5721_chip = { .supply_names = tas5721_supply_names, @@ -897,6 +954,7 @@ static const struct of_device_id tas571x_of_match[] __maybe_unused = { { .compatible = "ti,tas5717", .data = &tas5717_chip, }, { .compatible = "ti,tas5719", .data = &tas5717_chip, }, { .compatible = "ti,tas5721", .data = &tas5721_chip, }, + { .compatible = "ti,tas5733", .data = &tas5733_chip, }, { } }; MODULE_DEVICE_TABLE(of, tas571x_of_match); @@ -907,6 +965,7 @@ static const struct i2c_device_id tas571x_i2c_id[] = { { "tas5717", (kernel_ulong_t) &tas5717_chip }, { "tas5719", (kernel_ulong_t) &tas5717_chip }, { "tas5721", (kernel_ulong_t) &tas5721_chip }, + { "tas5733", (kernel_ulong_t) &tas5733_chip }, { } }; MODULE_DEVICE_TABLE(i2c, tas571x_i2c_id); diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 614eceda6b9e..17db29c25d96 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -172,6 +172,15 @@ config SND_MPC52xx_DMA config SND_SOC_POWERPC_DMA tristate +config SND_SOC_POWERPC_QMC_AUDIO + tristate "QMC ALSA SoC support" + depends on CPM_QMC + help + ALSA SoC Audio support using the Freescale QUICC Multichannel + Controller (QMC). + Say Y or M if you want to add support for SoC audio using Freescale + QMC. + comment "SoC Audio support for Freescale PPC boards:" config SND_SOC_MPC8610_HPCD diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index b54beb1a66fa..8db7e97d0bd5 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -28,6 +28,7 @@ snd-soc-fsl-easrc-objs := fsl_easrc.o snd-soc-fsl-xcvr-objs := fsl_xcvr.o snd-soc-fsl-aud2htx-objs := fsl_aud2htx.o snd-soc-fsl-rpmsg-objs := fsl_rpmsg.o +snd-soc-fsl-qmc-audio-objs := fsl_qmc_audio.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 @@ -44,6 +45,7 @@ obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o obj-$(CONFIG_SND_SOC_FSL_XCVR) += snd-soc-fsl-xcvr.o obj-$(CONFIG_SND_SOC_FSL_AUD2HTX) += snd-soc-fsl-aud2htx.o obj-$(CONFIG_SND_SOC_FSL_RPMSG) += snd-soc-fsl-rpmsg.o +obj-$(CONFIG_SND_SOC_POWERPC_QMC_AUDIO) += snd-soc-fsl-qmc-audio.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c new file mode 100644 index 000000000000..7cbb8e4758cc --- /dev/null +++ b/sound/soc/fsl/fsl_qmc_audio.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC using the QUICC Multichannel Controller (QMC) + * + * Copyright 2022 CS GROUP France + * + * Author: Herve Codina <herve.codina@bootlin.com> + */ + +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <soc/fsl/qe/qmc.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +struct qmc_dai { + char *name; + int id; + struct device *dev; + struct qmc_chan *qmc_chan; + unsigned int nb_tx_ts; + unsigned int nb_rx_ts; +}; + +struct qmc_audio { + struct device *dev; + unsigned int num_dais; + struct qmc_dai *dais; + struct snd_soc_dai_driver *dai_drivers; +}; + +struct qmc_dai_prtd { + struct qmc_dai *qmc_dai; + dma_addr_t dma_buffer_start; + dma_addr_t period_ptr_submitted; + dma_addr_t period_ptr_ended; + dma_addr_t dma_buffer_end; + size_t period_size; + struct snd_pcm_substream *substream; +}; + +static int qmc_audio_pcm_construct(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, card->dev, + 64*1024, 64*1024); + return 0; +} + +static int qmc_audio_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + + prtd->dma_buffer_start = runtime->dma_addr; + prtd->dma_buffer_end = runtime->dma_addr + params_buffer_bytes(params); + prtd->period_size = params_period_bytes(params); + prtd->period_ptr_submitted = prtd->dma_buffer_start; + prtd->period_ptr_ended = prtd->dma_buffer_start; + prtd->substream = substream; + + return 0; +} + +static void qmc_audio_pcm_write_complete(void *context) +{ + struct qmc_dai_prtd *prtd = context; + int ret; + + prtd->period_ptr_ended += prtd->period_size; + if (prtd->period_ptr_ended >= prtd->dma_buffer_end) + prtd->period_ptr_ended = prtd->dma_buffer_start; + + prtd->period_ptr_submitted += prtd->period_size; + if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) + prtd->period_ptr_submitted = prtd->dma_buffer_start; + + ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_write_complete, prtd); + if (ret) { + dev_err(prtd->qmc_dai->dev, "write_submit failed %d\n", + ret); + } + + snd_pcm_period_elapsed(prtd->substream); +} + +static void qmc_audio_pcm_read_complete(void *context, size_t length) +{ + struct qmc_dai_prtd *prtd = context; + int ret; + + if (length != prtd->period_size) { + dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp %zu\n", + length, prtd->period_size); + } + + prtd->period_ptr_ended += prtd->period_size; + if (prtd->period_ptr_ended >= prtd->dma_buffer_end) + prtd->period_ptr_ended = prtd->dma_buffer_start; + + prtd->period_ptr_submitted += prtd->period_size; + if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) + prtd->period_ptr_submitted = prtd->dma_buffer_start; + + ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_read_complete, prtd); + if (ret) { + dev_err(prtd->qmc_dai->dev, "read_submit failed %d\n", + ret); + } + + snd_pcm_period_elapsed(prtd->substream); +} + +static int qmc_audio_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + int ret; + + if (!prtd->qmc_dai) { + dev_err(component->dev, "qmc_dai is not set\n"); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Submit first chunk ... */ + ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_write_complete, prtd); + if (ret) { + dev_err(component->dev, "write_submit failed %d\n", + ret); + return ret; + } + + /* ... prepare next one ... */ + prtd->period_ptr_submitted += prtd->period_size; + if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) + prtd->period_ptr_submitted = prtd->dma_buffer_start; + + /* ... and send it */ + ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_write_complete, prtd); + if (ret) { + dev_err(component->dev, "write_submit failed %d\n", + ret); + return ret; + } + } else { + /* Submit first chunk ... */ + ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_read_complete, prtd); + if (ret) { + dev_err(component->dev, "read_submit failed %d\n", + ret); + return ret; + } + + /* ... prepare next one ... */ + prtd->period_ptr_submitted += prtd->period_size; + if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) + prtd->period_ptr_submitted = prtd->dma_buffer_start; + + /* ... and send it */ + ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan, + prtd->period_ptr_submitted, prtd->period_size, + qmc_audio_pcm_read_complete, prtd); + if (ret) { + dev_err(component->dev, "write_submit failed %d\n", + ret); + return ret; + } + } + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t qmc_audio_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, + prtd->period_ptr_ended - prtd->dma_buffer_start); +} + +static int qmc_audio_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + struct qmc_audio *qmc_audio = dev_get_drvdata(component->dev); + struct snd_soc_dai_driver *dai_driver; + int id = args->args[0]; + int i; + + for (i = 0; i < qmc_audio->num_dais; i++) { + dai_driver = qmc_audio->dai_drivers + i; + if (dai_driver->id == id) { + *dai_name = dai_driver->name; + return 0; + } + } + + return -EINVAL; +} + +static const struct snd_pcm_hardware qmc_audio_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE, + .period_bytes_min = 32, + .period_bytes_max = 64*1024, + .periods_min = 2, + .periods_max = 2*1024, + .buffer_bytes_max = 64*1024, +}; + +static int qmc_audio_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct qmc_dai_prtd *prtd; + int ret; + + snd_soc_set_runtime_hwparams(substream, &qmc_audio_pcm_hardware); + + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + runtime->private_data = prtd; + + return 0; +} + +static int qmc_audio_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + + kfree(prtd); + return 0; +} + +static const struct snd_soc_component_driver qmc_audio_soc_platform = { + .open = qmc_audio_pcm_open, + .close = qmc_audio_pcm_close, + .hw_params = qmc_audio_pcm_hw_params, + .trigger = qmc_audio_pcm_trigger, + .pointer = qmc_audio_pcm_pointer, + .pcm_construct = qmc_audio_pcm_construct, + .of_xlate_dai_name = qmc_audio_of_xlate_dai_name, +}; + +static unsigned int qmc_dai_get_index(struct snd_soc_dai *dai) +{ + struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai); + + return dai->driver - qmc_audio->dai_drivers; +} + +static struct qmc_dai *qmc_dai_get_data(struct snd_soc_dai *dai) +{ + struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai); + unsigned int index; + + index = qmc_dai_get_index(dai); + if (index > qmc_audio->num_dais) + return NULL; + + return qmc_audio->dais + index; +} + +/* + * The constraints for format/channel is to match with the number of 8bit + * time-slots available. + */ +static int qmc_dai_hw_rule_channels_by_format(struct qmc_dai *qmc_dai, + struct snd_pcm_hw_params *params, + unsigned int nb_ts) +{ + struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + snd_pcm_format_t format = params_format(params); + struct snd_interval ch = {0}; + + switch (snd_pcm_format_physical_width(format)) { + case 8: + ch.max = nb_ts; + break; + case 16: + ch.max = nb_ts/2; + break; + case 32: + ch.max = nb_ts/4; + break; + case 64: + ch.max = nb_ts/8; + break; + default: + dev_err(qmc_dai->dev, "format physical width %u not supported\n", + snd_pcm_format_physical_width(format)); + return -EINVAL; + } + + ch.min = ch.max ? 1 : 0; + + return snd_interval_refine(c, &ch); +} + +static int qmc_dai_hw_rule_playback_channels_by_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct qmc_dai *qmc_dai = rule->private; + + return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, qmc_dai->nb_tx_ts); +} + +static int qmc_dai_hw_rule_capture_channels_by_format( + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct qmc_dai *qmc_dai = rule->private; + + return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, qmc_dai->nb_rx_ts); +} + +static int qmc_dai_hw_rule_format_by_channels(struct qmc_dai *qmc_dai, + struct snd_pcm_hw_params *params, + unsigned int nb_ts) +{ + struct snd_mask *f_old = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + unsigned int channels = params_channels(params); + unsigned int slot_width; + struct snd_mask f_new; + unsigned int i; + + if (!channels || channels > nb_ts) { + dev_err(qmc_dai->dev, "channels %u not supported\n", + nb_ts); + return -EINVAL; + } + + slot_width = (nb_ts / channels) * 8; + + snd_mask_none(&f_new); + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + if (snd_mask_test(f_old, i)) { + if (snd_pcm_format_physical_width(i) <= slot_width) + snd_mask_set(&f_new, i); + } + } + + return snd_mask_refine(f_old, &f_new); +} + +static int qmc_dai_hw_rule_playback_format_by_channels( + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct qmc_dai *qmc_dai = rule->private; + + return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_tx_ts); +} + +static int qmc_dai_hw_rule_capture_format_by_channels( + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct qmc_dai *qmc_dai = rule->private; + + return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_rx_ts); +} + +static int qmc_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qmc_dai_prtd *prtd = substream->runtime->private_data; + snd_pcm_hw_rule_func_t hw_rule_channels_by_format; + snd_pcm_hw_rule_func_t hw_rule_format_by_channels; + struct qmc_dai *qmc_dai; + unsigned int frame_bits; + int ret; + + qmc_dai = qmc_dai_get_data(dai); + if (!qmc_dai) { + dev_err(dai->dev, "Invalid dai\n"); + return -EINVAL; + } + + prtd->qmc_dai = qmc_dai; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw_rule_channels_by_format = qmc_dai_hw_rule_capture_channels_by_format; + hw_rule_format_by_channels = qmc_dai_hw_rule_capture_format_by_channels; + frame_bits = qmc_dai->nb_rx_ts * 8; + } else { + hw_rule_channels_by_format = qmc_dai_hw_rule_playback_channels_by_format; + hw_rule_format_by_channels = qmc_dai_hw_rule_playback_format_by_channels; + frame_bits = qmc_dai->nb_tx_ts * 8; + } + + ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels_by_format, qmc_dai, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (ret) { + dev_err(dai->dev, "Failed to add channels rule (%d)\n", ret); + return ret; + } + + ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + hw_rule_format_by_channels, qmc_dai, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) { + dev_err(dai->dev, "Failed to add format rule (%d)\n", ret); + return ret; + } + + ret = snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_FRAME_BITS, + frame_bits); + if (ret < 0) { + dev_err(dai->dev, "Failed to add frame_bits constraint (%d)\n", ret); + return ret; + } + + return 0; +} + +static int qmc_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct qmc_chan_param chan_param = {0}; + struct qmc_dai *qmc_dai; + int ret; + + qmc_dai = qmc_dai_get_data(dai); + if (!qmc_dai) { + dev_err(dai->dev, "Invalid dai\n"); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + chan_param.mode = QMC_TRANSPARENT; + chan_param.transp.max_rx_buf_size = params_period_bytes(params); + ret = qmc_chan_set_param(qmc_dai->qmc_chan, &chan_param); + if (ret) { + dev_err(dai->dev, "set param failed %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct qmc_dai *qmc_dai; + int direction; + int ret; + + qmc_dai = qmc_dai_get_data(dai); + if (!qmc_dai) { + dev_err(dai->dev, "Invalid dai\n"); + return -EINVAL; + } + + direction = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + QMC_CHAN_WRITE : QMC_CHAN_READ; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = qmc_chan_start(qmc_dai->qmc_chan, direction); + if (ret) + return ret; + break; + + case SNDRV_PCM_TRIGGER_STOP: + ret = qmc_chan_stop(qmc_dai->qmc_chan, direction); + if (ret) + return ret; + ret = qmc_chan_reset(qmc_dai->qmc_chan, direction); + if (ret) + return ret; + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = qmc_chan_stop(qmc_dai->qmc_chan, direction); + if (ret) + return ret; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops qmc_dai_ops = { + .startup = qmc_dai_startup, + .trigger = qmc_dai_trigger, + .hw_params = qmc_dai_hw_params, +}; + +static u64 qmc_audio_formats(u8 nb_ts) +{ + u64 formats; + unsigned int chan_width; + unsigned int format_width; + int i; + + if (!nb_ts) + return 0; + + formats = 0; + chan_width = nb_ts * 8; + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + /* + * Support format other than little-endian (ie big-endian or + * without endianness such as 8bit formats) + */ + if (snd_pcm_format_little_endian(i) == 1) + continue; + + /* Support physical width multiple of 8bit */ + format_width = snd_pcm_format_physical_width(i); + if (format_width == 0 || format_width % 8) + continue; + + /* + * And support physical width that can fit N times in the + * channel + */ + if (format_width > chan_width || chan_width % format_width) + continue; + + formats |= (1ULL << i); + } + return formats; +} + +static int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *np, + struct qmc_dai *qmc_dai, struct snd_soc_dai_driver *qmc_soc_dai_driver) +{ + struct qmc_chan_info info; + u32 val; + int ret; + + qmc_dai->dev = qmc_audio->dev; + + ret = of_property_read_u32(np, "reg", &val); + if (ret) { + dev_err(qmc_audio->dev, "%pOF: failed to read reg\n", np); + return ret; + } + qmc_dai->id = val; + + qmc_dai->name = devm_kasprintf(qmc_audio->dev, GFP_KERNEL, "%s.%d", + np->parent->name, qmc_dai->id); + + qmc_dai->qmc_chan = devm_qmc_chan_get_byphandle(qmc_audio->dev, np, + "fsl,qmc-chan"); + if (IS_ERR(qmc_dai->qmc_chan)) { + ret = PTR_ERR(qmc_dai->qmc_chan); + return dev_err_probe(qmc_audio->dev, ret, + "dai %d get QMC channel failed\n", qmc_dai->id); + } + + qmc_soc_dai_driver->id = qmc_dai->id; + qmc_soc_dai_driver->name = qmc_dai->name; + + ret = qmc_chan_get_info(qmc_dai->qmc_chan, &info); + if (ret) { + dev_err(qmc_audio->dev, "dai %d get QMC channel info failed %d\n", + qmc_dai->id, ret); + return ret; + } + dev_info(qmc_audio->dev, "dai %d QMC channel mode %d, nb_tx_ts %u, nb_rx_ts %u\n", + qmc_dai->id, info.mode, info.nb_tx_ts, info.nb_rx_ts); + + if (info.mode != QMC_TRANSPARENT) { + dev_err(qmc_audio->dev, "dai %d QMC chan mode %d is not QMC_TRANSPARENT\n", + qmc_dai->id, info.mode); + return -EINVAL; + } + qmc_dai->nb_tx_ts = info.nb_tx_ts; + qmc_dai->nb_rx_ts = info.nb_rx_ts; + + qmc_soc_dai_driver->playback.channels_min = 0; + qmc_soc_dai_driver->playback.channels_max = 0; + if (qmc_dai->nb_tx_ts) { + qmc_soc_dai_driver->playback.channels_min = 1; + qmc_soc_dai_driver->playback.channels_max = qmc_dai->nb_tx_ts; + } + qmc_soc_dai_driver->playback.formats = qmc_audio_formats(qmc_dai->nb_tx_ts); + + qmc_soc_dai_driver->capture.channels_min = 0; + qmc_soc_dai_driver->capture.channels_max = 0; + if (qmc_dai->nb_rx_ts) { + qmc_soc_dai_driver->capture.channels_min = 1; + qmc_soc_dai_driver->capture.channels_max = qmc_dai->nb_rx_ts; + } + qmc_soc_dai_driver->capture.formats = qmc_audio_formats(qmc_dai->nb_rx_ts); + + qmc_soc_dai_driver->playback.rates = snd_pcm_rate_to_rate_bit(info.tx_fs_rate); + qmc_soc_dai_driver->playback.rate_min = info.tx_fs_rate; + qmc_soc_dai_driver->playback.rate_max = info.tx_fs_rate; + qmc_soc_dai_driver->capture.rates = snd_pcm_rate_to_rate_bit(info.rx_fs_rate); + qmc_soc_dai_driver->capture.rate_min = info.rx_fs_rate; + qmc_soc_dai_driver->capture.rate_max = info.rx_fs_rate; + + qmc_soc_dai_driver->ops = &qmc_dai_ops; + + return 0; +} + +static int qmc_audio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct qmc_audio *qmc_audio; + struct device_node *child; + unsigned int i; + int ret; + + qmc_audio = devm_kzalloc(&pdev->dev, sizeof(*qmc_audio), GFP_KERNEL); + if (!qmc_audio) + return -ENOMEM; + + qmc_audio->dev = &pdev->dev; + + qmc_audio->num_dais = of_get_available_child_count(np); + if (qmc_audio->num_dais) { + qmc_audio->dais = devm_kcalloc(&pdev->dev, qmc_audio->num_dais, + sizeof(*qmc_audio->dais), + GFP_KERNEL); + if (!qmc_audio->dais) + return -ENOMEM; + + qmc_audio->dai_drivers = devm_kcalloc(&pdev->dev, qmc_audio->num_dais, + sizeof(*qmc_audio->dai_drivers), + GFP_KERNEL); + if (!qmc_audio->dai_drivers) + return -ENOMEM; + } + + i = 0; + for_each_available_child_of_node(np, child) { + ret = qmc_audio_dai_parse(qmc_audio, child, + qmc_audio->dais + i, + qmc_audio->dai_drivers + i); + if (ret) { + of_node_put(child); + return ret; + } + i++; + } + + + platform_set_drvdata(pdev, qmc_audio); + + ret = devm_snd_soc_register_component(qmc_audio->dev, + &qmc_audio_soc_platform, + qmc_audio->dai_drivers, + qmc_audio->num_dais); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id qmc_audio_id_table[] = { + { .compatible = "fsl,qmc-audio" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, qmc_audio_id_table); + +static struct platform_driver qmc_audio_driver = { + .driver = { + .name = "fsl-qmc-audio", + .of_match_table = of_match_ptr(qmc_audio_id_table), + }, + .probe = qmc_audio_probe, +}; +module_platform_driver(qmc_audio_driver); + +MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>"); +MODULE_DESCRIPTION("CPM/QE QMC audio driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/intel/avs/path.c b/sound/soc/intel/avs/path.c index 05302ab705ae..adbe23a47847 100644 --- a/sound/soc/intel/avs/path.c +++ b/sound/soc/intel/avs/path.c @@ -478,7 +478,7 @@ static int avs_modext_create(struct avs_dev *adev, struct avs_path_module *mod) int ret, i; num_pins = tcfg->generic.num_input_pins + tcfg->generic.num_output_pins; - cfg_size = sizeof(*cfg) + sizeof(*cfg->pin_fmts) * num_pins; + cfg_size = struct_size(cfg, pin_fmts, num_pins); cfg = kzalloc(cfg_size, GFP_KERNEL); if (!cfg) diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index 96fe80241fb4..c1f24af17506 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -180,6 +180,18 @@ err_put_np: } EXPORT_SYMBOL_GPL(qcom_snd_parse_of); +static struct snd_soc_jack_pin qcom_headset_jack_pins[] = { + /* Headset */ + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, +}; + int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, struct snd_soc_jack *jack, bool *jack_setup) { @@ -189,13 +201,14 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, int rval, i; if (!*jack_setup) { - rval = snd_soc_card_jack_new(card, "Headset Jack", + rval = snd_soc_card_jack_new_pins(card, "Headset Jack", SND_JACK_HEADSET | SND_JACK_LINEOUT | SND_JACK_MECHANICAL | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3 | SND_JACK_BTN_4 | SND_JACK_BTN_5, - jack); + jack, qcom_headset_jack_pins, + ARRAY_SIZE(qcom_headset_jack_pins)); if (rval < 0) { dev_err(card->dev, "Unable to add Headphone Jack\n"); diff --git a/sound/soc/sh/rz-ssi.c b/sound/soc/sh/rz-ssi.c index 5d6bae33ae34..d502aa55c5a8 100644 --- a/sound/soc/sh/rz-ssi.c +++ b/sound/soc/sh/rz-ssi.c @@ -109,6 +109,7 @@ struct rz_ssi_priv { int irq_int; int irq_tx; int irq_rx; + int irq_rt; spinlock_t lock; @@ -565,6 +566,17 @@ static irqreturn_t rz_ssi_interrupt(int irq, void *data) rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0); } + if (irq == ssi->irq_rt) { + struct snd_pcm_substream *substream = strm->substream; + + if (rz_ssi_stream_is_play(ssi, substream)) { + strm->transfer(ssi, &ssi->playback); + } else { + strm->transfer(ssi, &ssi->capture); + rz_ssi_reg_mask_setl(ssi, SSIFSR, SSIFSR_RDF, 0); + } + } + return IRQ_HANDLED; } @@ -993,26 +1005,39 @@ static int rz_ssi_probe(struct platform_device *pdev) if (!rz_ssi_is_dma_enabled(ssi)) { /* Tx and Rx interrupts (pio only) */ ssi->irq_tx = platform_get_irq_byname(pdev, "dma_tx"); - if (ssi->irq_tx < 0) - return ssi->irq_tx; - - ret = devm_request_irq(&pdev->dev, ssi->irq_tx, - &rz_ssi_interrupt, 0, - dev_name(&pdev->dev), ssi); - if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "irq request error (dma_tx)\n"); - ssi->irq_rx = platform_get_irq_byname(pdev, "dma_rx"); - if (ssi->irq_rx < 0) - return ssi->irq_rx; - - ret = devm_request_irq(&pdev->dev, ssi->irq_rx, - &rz_ssi_interrupt, 0, - dev_name(&pdev->dev), ssi); - if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "irq request error (dma_rx)\n"); + if (ssi->irq_tx == -ENXIO && ssi->irq_rx == -ENXIO) { + ssi->irq_rt = platform_get_irq_byname(pdev, "dma_rt"); + if (ssi->irq_rt < 0) + return ssi->irq_rt; + + ret = devm_request_irq(&pdev->dev, ssi->irq_rt, + &rz_ssi_interrupt, 0, + dev_name(&pdev->dev), ssi); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "irq request error (dma_tx)\n"); + } else { + if (ssi->irq_tx < 0) + return ssi->irq_tx; + + if (ssi->irq_rx < 0) + return ssi->irq_rx; + + ret = devm_request_irq(&pdev->dev, ssi->irq_tx, + &rz_ssi_interrupt, 0, + dev_name(&pdev->dev), ssi); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "irq request error (dma_tx)\n"); + + ret = devm_request_irq(&pdev->dev, ssi->irq_rx, + &rz_ssi_interrupt, 0, + dev_name(&pdev->dev), ssi); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "irq request error (dma_rx)\n"); + } } ssi->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 71b022f7edfd..2faa0d8d0d8e 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1033,7 +1033,6 @@ static void snd_soc_runtime_get_dai_fmt(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai_link *dai_link = rtd->dai_link; struct snd_soc_dai *dai, *not_used; - struct device *dev = rtd->dev; u64 pos, possible_fmt; unsigned int mask = 0, dai_fmt = 0; int i, j, priority, pri, until; @@ -1075,8 +1074,6 @@ static void snd_soc_runtime_get_dai_fmt(struct snd_soc_pcm_runtime *rtd) */ until = snd_soc_dai_get_fmt_max_priority(rtd); for (priority = 1; priority <= until; priority++) { - - dev_dbg(dev, "priority = %d\n", priority); for_each_rtd_dais(rtd, j, not_used) { possible_fmt = ULLONG_MAX; @@ -1085,7 +1082,6 @@ static void snd_soc_runtime_get_dai_fmt(struct snd_soc_pcm_runtime *rtd) pri = (j >= i) ? priority : priority - 1; fmt = snd_soc_dai_get_fmt(dai, pri); - dev_dbg(dev, "%s: (pri, fmt) = (%d, %016llX)\n", dai->name, pri, fmt); possible_fmt &= fmt; } if (possible_fmt) @@ -1095,8 +1091,6 @@ static void snd_soc_runtime_get_dai_fmt(struct snd_soc_pcm_runtime *rtd) /* Not Found */ return; found: - dev_dbg(dev, "found auto selected format: %016llX\n", possible_fmt); - /* * convert POSSIBLE_DAIFMT to DAIFMT * @@ -1457,11 +1451,6 @@ static int soc_probe_link_dais(struct snd_soc_card *card) for_each_comp_order(order) { for_each_card_rtds(card, rtd) { - - dev_dbg(card->dev, - "ASoC: probe %s dai link %d late %d\n", - card->name, rtd->num, order); - /* probe all rtd connected DAIs in good order */ ret = snd_soc_pcm_dai_probe(rtd, order); if (ret) @@ -2421,8 +2410,6 @@ struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component, struct device *dev = component->dev; struct snd_soc_dai *dai; - dev_dbg(dev, "ASoC: dynamically register DAI %s\n", dev_name(dev)); - lockdep_assert_held(&client_mutex); dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL); diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index fcece5ca38c6..f951acb2ce36 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -367,6 +367,7 @@ got_gpio: ret = request_any_context_irq(gpiod_to_irq(gpios[i].desc), gpio_handler, + IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpios[i].name, diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c index 3aea36c077c9..a1037512da1f 100644 --- a/sound/soc/sof/intel/hda-ctrl.c +++ b/sound/soc/sof/intel/hda-ctrl.c @@ -158,16 +158,18 @@ void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable) */ int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; u32 val; /* enable/disable audio dsp clock gating */ val = enable ? PCI_CGCTL_ADSPDCGE : 0; snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val); - /* enable/disable DMI Link L1 support */ + /* disable the DMI link when requested. But enable only if it wasn't disabled previously */ val = enable ? HDA_VS_INTEL_EM2_L1SEN : 0; - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, - HDA_VS_INTEL_EM2_L1SEN, val); + if (!enable || !hda->l1_disabled) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, val); /* enable/disable audio dsp power gating */ val = enable ? 0 : PCI_PGCTL_ADSPPGD; diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 68eb06f13a1f..e3b69dbc1308 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -776,7 +776,7 @@ int hda_dsp_resume(struct snd_sof_dev *sdev) } /* restore L1SEN bit */ - if (hda->l1_support_changed) + if (hda->l1_disabled) snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, HDA_VS_INTEL_EM2_L1SEN, 0); @@ -868,11 +868,9 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state) } /* enable L1SEN to make sure the system can enter S0Ix */ - hda->l1_support_changed = - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, - HDA_VS_INTEL_EM2, - HDA_VS_INTEL_EM2_L1SEN, - HDA_VS_INTEL_EM2_L1SEN); + if (hda->l1_disabled) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN); /* stop the CORB/RIRB DMA if it is On */ hda_codec_suspend_cmd_io(sdev); diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index 7f0fd05a96e6..c37ef581637f 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -182,6 +182,8 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, struct hdac_ext_stream * hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) { + const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata); + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct hdac_bus *bus = sof_to_bus(sdev); struct sof_intel_hda_stream *hda_stream; struct hdac_ext_stream *hext_stream = NULL; @@ -220,12 +222,15 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) /* * Prevent DMI Link L1 entry for streams that don't support it. * Workaround to address a known issue with host DMA that results - * in xruns during pause/release in capture scenarios. + * in xruns during pause/release in capture scenarios. This is not needed for the ACE IP. */ - if (!(flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) + if (chip_info->hw_ip_version < SOF_INTEL_ACE_1_0 && + !(flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) { snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, HDA_VS_INTEL_EM2_L1SEN, 0); + hda->l1_disabled = true; + } return hext_stream; } @@ -233,6 +238,8 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) /* free a stream */ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) { + const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata); + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct hdac_bus *bus = sof_to_bus(sdev); struct sof_intel_hda_stream *hda_stream; struct hdac_ext_stream *hext_stream; @@ -264,9 +271,11 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) spin_unlock_irq(&bus->reg_lock); /* Enable DMI L1 if permitted */ - if (dmi_l1_enable) + if (chip_info->hw_ip_version < SOF_INTEL_ACE_1_0 && dmi_l1_enable) { snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN); + hda->l1_disabled = false; + } if (!found) { dev_err(sdev->dev, "%s: stream_tag %d not opened!\n", diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 45f9d4248f14..0679bebe4ad7 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -502,7 +502,7 @@ struct sof_intel_hda_dev { u32 stream_max; /* PM related */ - bool l1_support_changed;/* during suspend, is L1SEN changed or not */ + bool l1_disabled;/* is DMI link L1 disabled? */ /* DMIC device */ struct platform_device *dmic_dev; diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 307faad2ecf4..216fd07a3a93 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -280,6 +280,9 @@ static int mtl_dsp_post_fw_run(struct snd_sof_dev *sdev) } hda_sdw_int_enable(sdev, true); + + /* enable DMI L1 */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, MTL_EM2, MTL_EM2_L1SEN, MTL_EM2_L1SEN); return 0; } diff --git a/sound/soc/sof/intel/mtl.h b/sound/soc/sof/intel/mtl.h index 26418fb08807..ddc05304a9d5 100644 --- a/sound/soc/sof/intel/mtl.h +++ b/sound/soc/sof/intel/mtl.h @@ -28,6 +28,8 @@ #define MTL_HFINTIPPTR_PTR_MASK GENMASK(20, 0) #define MTL_HDA_VS_D0I3C 0x1D4A +#define MTL_EM2 0x1c44 +#define MTL_EM2_L1SEN BIT(13) #define MTL_DSP2CXCAP_PRIMARY_CORE 0x178D00 #define MTL_DSP2CXCTL_PRIMARY_CORE 0x178D04 diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index 72529179ac22..8507171a6e4d 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -266,8 +266,8 @@ struct sof_ipc4_control_data { int index; union { - struct sof_ipc4_ctrl_value_chan chanv[0]; - struct sof_abi_hdr data[0]; + DECLARE_FLEX_ARRAY(struct sof_ipc4_ctrl_value_chan, chanv); + DECLARE_FLEX_ARRAY(struct sof_abi_hdr, data); }; }; |