diff --git a/Documentation/devicetree/bindings/sound/cix,sky1-i2s-mc.yaml b/Documentation/devicetree/bindings/sound/cix,sky1-i2s-mc.yaml new file mode 100644 index 00000000000000..e750009edbdb4b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cix,sky1-i2s-mc.yaml @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cix,sky1-i2s-mc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cadence I2S-MC controller + +maintainers: + - Joakim Zhang + +description: | + The Cadence I2S-MC multi-channel controller integrated in the CIX SKY1 + audio subsystem. It provides up to 8-channel playback and capture through + a configurable number of I2S data pins, and exposes two DAIs: a transmit + DAI and a receive DAI. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: cix,sky1-i2s-mc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 4 + + clock-names: + items: + - const: hst + - const: i2s + - const: audio-clk0 + - const: audio-clk2 + + resets: + maxItems: 1 + + dmas: + minItems: 1 + maxItems: 2 + description: + One DMA channel for instances wired for a single direction, either + "tx" or "rx", or two channels for both transmit and receive. + + dma-names: + minItems: 1 + items: + - enum: [ tx, rx ] + - const: rx + + "#sound-dai-cells": + const: 1 + description: + Cell selects the DAI, 0 for the transmit DAI (i2s-mc-aif1) and + 1 for the receive DAI (i2s-mc-aif2). + + cix,pin-out-num: + $ref: /schemas/types.yaml#/definitions/uint8 + minimum: 1 + maximum: 8 + description: + Number of I2S data output pins wired on this instance. Which of + the 8 controller data pins are wired out is fixed by the board + design and can be any subset. + + cix,pin-rx-mask: + $ref: /schemas/types.yaml#/definitions/uint8 + description: + Bit mask of I2S data pins used for capture, bit N selects data + pin N. + + cix,pin-tx-mask: + $ref: /schemas/types.yaml#/definitions/uint8 + description: + Bit mask of I2S data pins used for playback, bit N selects data + pin N. The rx and tx masks must not overlap, and the number of + set bits must not exceed cix,pin-out-num. + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - dmas + - dma-names + - "#sound-dai-cells" + - cix,pin-out-num + - cix,pin-rx-mask + - cix,pin-tx-mask + +unevaluatedProperties: false + +examples: + - | + #include + + i2s@7050000 { + compatible = "cix,sky1-i2s-mc"; + reg = <0x7050000 0x10000>; + interrupts = ; + clocks = <&audss_cru 17>, <&audss_cru 27>, + <&scmi_clk 100>, <&scmi_clk 101>; + clock-names = "hst", "i2s", "audio-clk0", "audio-clk2"; + resets = <&audss_cru 1>; + dmas = <&audss_dmac 2>, <&audss_dmac 3>; + dma-names = "tx", "rx"; + #sound-dai-cells = <1>; + cix,pin-out-num = /bits/ 8 <2>; + cix,pin-rx-mask = /bits/ 8 <0x1>; + cix,pin-tx-mask = /bits/ 8 <0x2>; + }; diff --git a/Documentation/devicetree/bindings/sound/cix,sky1-i2s-sc.yaml b/Documentation/devicetree/bindings/sound/cix,sky1-i2s-sc.yaml new file mode 100644 index 00000000000000..9ef11272d0fd89 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cix,sky1-i2s-sc.yaml @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cix,sky1-i2s-sc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cadence I2S-SC controller + +maintainers: + - Joakim Zhang + +description: | + The Cadence I2S-SC controller integrated in the CIX SKY1 audio subsystem. + It supports full-duplex stereo playback and capture in standard I2S, + left/right-justified and DSP modes, as well as TDM operation with up to + 16 slots. The transmitter and receiver are started and stopped together + by the controller. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: cix,sky1-i2s-sc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 4 + + clock-names: + items: + - const: hst + - const: i2s + - const: audio-clk0 + - const: audio-clk2 + + resets: + maxItems: 1 + + dmas: + minItems: 1 + maxItems: 2 + description: + One DMA channel for instances wired for a single direction, either + "tx" or "rx", or two channels for both transmit and receive. + + dma-names: + minItems: 1 + items: + - enum: [ tx, rx ] + - const: rx + + "#sound-dai-cells": + const: 0 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - dmas + - dma-names + - "#sound-dai-cells" + +unevaluatedProperties: false + +examples: + - | + #include + + i2s@7020000 { + compatible = "cix,sky1-i2s-sc"; + reg = <0x7020000 0x10000>; + interrupts = ; + clocks = <&audss_cru 16>, <&audss_cru 26>, + <&scmi_clk 100>, <&scmi_clk 101>; + clock-names = "hst", "i2s", "audio-clk0", "audio-clk2"; + resets = <&audss_cru 0>; + dmas = <&audss_dmac 0>, <&audss_dmac 1>; + dma-names = "tx", "rx"; + #sound-dai-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-alc5632.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-alc5632.yaml deleted file mode 100644 index 96f2f927a6f591..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-alc5632.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-alc5632.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with ALC5632 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-alc5632(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-alc5632 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headset Stereophone - - Int Spk - - Headset Mic - - Digital Mic - - # CODEC Pins - - SPKOUT - - SPKOUTN - - MICBIAS1 - - MIC1 - - HPR - - HPL - - DMICDAT - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-alc5632-paz00", - "nvidia,tegra-audio-alc5632"; - - nvidia,model = "Compal PAZ00"; - - nvidia,audio-routing = "Int Spk", "SPKOUT", - "Int Spk", "SPKOUTN", - "Headset Mic", "MICBIAS1", - "MIC1", "Headset Mic", - "Headset Stereophone", "HPR", - "Headset Stereophone", "HPL", - "DMICDAT", "Digital Mic"; - - nvidia,i2s-controller = <&i2s>; - nvidia,audio-codec = <&codec>; - - clocks = <&clk 112>, <&clk 113>, <&clk 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml deleted file mode 100644 index 316806fc7346ee..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml +++ /dev/null @@ -1,92 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-common.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Common properties for NVIDIA Tegra audio complexes - -maintainers: - - Jon Hunter - - Thierry Reding - -properties: - clocks: - items: - - description: PLL A clock - - description: PLL A OUT0 clock - - description: The Tegra cdev1/extern1 clock, which feeds the card's mclk - - clock-names: - items: - - const: pll_a - - const: pll_a_out0 - - const: mclk - - nvidia,model: - $ref: /schemas/types.yaml#/definitions/string - description: The user-visible name of this sound complex. - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - - nvidia,ac97-controller: - $ref: /schemas/types.yaml#/definitions/phandle - description: The phandle of the AC97 controller - - nvidia,i2s-controller: - $ref: /schemas/types.yaml#/definitions/phandle - description: The phandle of the Tegra I2S controller - - nvidia,audio-codec: - $ref: /schemas/types.yaml#/definitions/phandle - description: The phandle of audio codec - - nvidia,aux-devs: - $ref: /schemas/types.yaml#/definitions/phandle-array - description: List of phandles pointing to auxiliary devices, - such as amplifiers or dsp, to be added to the sound card. - - nvidia,spkr-en-gpios: - maxItems: 1 - description: The GPIO that enables the speakers - - nvidia,hp-mute-gpios: - maxItems: 1 - description: The GPIO that mutes the headphones - - nvidia,hp-det-gpios: - maxItems: 1 - description: The GPIO that detect headphones are plugged in - - nvidia,mic-det-gpios: - maxItems: 1 - description: The GPIO that detect microphone is plugged in - - nvidia,ear-sel-gpios: - maxItems: 1 - description: The GPIO that switch between the microphones - - nvidia,int-mic-en-gpios: - maxItems: 1 - description: The GPIO that enables the internal microphone - - nvidia,ext-mic-en-gpios: - maxItems: 1 - description: The GPIO that enables the external microphone - - nvidia,headset: - type: boolean - description: The Mic Jack represents state of the headset microphone pin - - nvidia,coupled-mic-hp-det: - type: boolean - description: The Mic detect GPIO is viable only if HP detect GPIO is active - -additionalProperties: true diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-complex.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-complex.yaml new file mode 100644 index 00000000000000..d5506ff4a4c698 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-complex.yaml @@ -0,0 +1,235 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-complex.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra audio complex + +maintainers: + - Jon Hunter + - Thierry Reding + - Svyatoslav Ryhel + +properties: + compatible: + oneOf: + - items: + - pattern: '^[a-z0-9]+,tegra-audio-max(98088|98089|98090)([-_][a-z0-9]+)+$' + - enum: + - nvidia,tegra-audio-max98088 + - nvidia,tegra-audio-max98089 + - nvidia,tegra-audio-max98090 + - items: + - pattern: '^[a-z0-9]+,tegra-audio-rt(5631|5639|5640|5677)([-_][a-z0-9]+)+$' + - enum: + - nvidia,tegra-audio-rt5631 + - nvidia,tegra-audio-rt5640 + - nvidia,tegra-audio-rt5677 + - items: + - pattern: '^[a-z0-9]+,tegra-audio-wm(8753|8903|8962|8994|9712)([-_][a-z0-9]+)+$' + - enum: + - nvidia,tegra-audio-wm8753 + - nvidia,tegra-audio-wm8903 + - nvidia,tegra-audio-wm8962 + - nvidia,tegra-audio-wm8994 + - nvidia,tegra-audio-wm9712 + - items: + - pattern: '^[a-z0-9]+,tegra-audio-alc5632(-[a-z0-9]+)+$' + - const: nvidia,tegra-audio-alc5632 + - items: + - pattern: '^motorola,tegra-audio-cpcap(-[a-z0-9]+)+$' + - const: nvidia,tegra-audio-cpcap + - items: + - pattern: '^nvidia,tegra-audio-max98090-nyan-(big|blaze)+$' + - const: nvidia,tegra-audio-max98090-nyan + - const: nvidia,tegra-audio-max98090 + - items: + - pattern: '^[a-z0-9]+,tegra-audio-sgtl5000([-_][a-z0-9]+)+$' + - const: nvidia,tegra-audio-sgtl5000 + - items: + const: nvidia,tegra-audio-trimslice + - items: + - pattern: ad,tegra-audio-plutux + - const: nvidia,tegra-audio-wm8903 + + clocks: + items: + - description: PLL A clock + - description: PLL A OUT0 clock + - description: The Tegra cdev1/extern1 clock, which feeds the card's mclk + + clock-names: + items: + - const: pll_a + - const: pll_a_out0 + - const: mclk + + nvidia,model: + $ref: /schemas/types.yaml#/definitions/string + description: The user-visible name of this sound complex. + + nvidia,audio-routing: + $ref: /schemas/types.yaml#/definitions/non-unique-string-array + description: | + A list of the connections between audio components. + Each entry is a pair of strings, the first being the connection's sink, + the second being the connection's source. Valid names for sources and + sinks are the codec pins and the jacks on the board. + + nvidia,ac97-controller: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of the AC97 controller + + nvidia,i2s-controller: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of the Tegra I2S controller + + nvidia,audio-codec: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of audio codec + + nvidia,aux-devs: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: List of phandles pointing to auxiliary devices, + such as amplifiers or dsp, to be added to the sound card. + + nvidia,spkr-en-gpios: + maxItems: 1 + description: The GPIO that enables the speakers + + nvidia,hp-mute-gpios: + maxItems: 1 + description: The GPIO that mutes the headphones + + nvidia,hp-det-gpios: + maxItems: 1 + description: The GPIO that detect headphones are plugged in + + nvidia,mic-det-gpios: + maxItems: 1 + description: The GPIO that detect microphone is plugged in + + nvidia,ear-sel-gpios: + maxItems: 1 + description: The GPIO that switch between the microphones + + nvidia,int-mic-en-gpios: + maxItems: 1 + description: The GPIO that enables the internal microphone + + nvidia,ext-mic-en-gpios: + maxItems: 1 + description: The GPIO that enables the external microphone + + nvidia,headset: + type: boolean + description: The Mic Jack represents state of the headset microphone pin + + nvidia,coupled-mic-hp-det: + type: boolean + description: The Mic detect GPIO is viable only if HP detect GPIO is active + +oneOf: + - required: + - nvidia,i2s-controller + - required: + - nvidia,ac97-controller + +additionalProperties: false + +examples: + - | + #include + #include + + sound { + compatible = "lg,tegra-audio-wm8994-star", + "nvidia,tegra-audio-wm8994"; + nvidia,model = "LG Optimus 2X WM8994"; + + nvidia,audio-routing = + "Headphone Jack", "HPOUT1L", + "Headphone Jack", "HPOUT1R", + "Int Spk", "SPKOUTLN", + "Int Spk", "SPKOUTLP", + "Earpiece", "HPOUT2N", + "Earpiece", "HPOUT2P", + "IN2RN", "MICBIAS2", + "IN2RN", "Mic Jack", + "IN1LN", "MICBIAS1", + "IN1LN", "Internal Mic 1", + "IN1RN", "MICBIAS1", + "IN1RN", "Internal Mic 2"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&wm8994>; + + nvidia,hp-det-gpios = <&gpio 51 GPIO_ACTIVE_HIGH>; + nvidia,mic-det-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; + nvidia,ext-mic-en-gpios = <&gpio 57 GPIO_ACTIVE_HIGH>; + nvidia,coupled-mic-hp-det; + + clocks = <&tegra_car TEGRA20_CLK_PLL_A>, + <&tegra_car TEGRA20_CLK_PLL_A_OUT0>, + <&tegra_car TEGRA20_CLK_CDEV1>; + clock-names = "pll_a", "pll_a_out0", "mclk"; + }; + - | + #include + #include + #include + + sound { + compatible = "asus,tegra-audio-rt5631-tf700t", + "nvidia,tegra-audio-rt5631"; + nvidia,model = "Asus Transformer Infinity TF700T RT5631"; + + nvidia,audio-routing = + "Headphone Jack", "HPOL", + "Headphone Jack", "HPOR", + "Int Spk", "SPOL", + "Int Spk", "SPOR", + "MIC1", "MIC Bias1", + "MIC1", "DSP Analog Mic", + "DSP AMIC", "Mic Jack", + "DMIC", "DSP Digital Mic", + "DSP DMIC", "Int Mic"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&rt5631>; + nvidia,aux-devs = <&fm34ne>; + + nvidia,hp-det-gpios = <&gpio 178 GPIO_ACTIVE_LOW>; + nvidia,mic-det-gpios = <&gpio 186 GPIO_ACTIVE_LOW>; + nvidia,coupled-mic-hp-det; + + clocks = <&tegra_car TEGRA30_CLK_PLL_A>, + <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, + <&tegra_pmc TEGRA_PMC_CLK_OUT_1>; + clock-names = "pll_a", "pll_a_out0", "mclk"; + }; + - | + #include + + sound { + compatible = "nvidia,tegra-audio-max98090-venice2", + "nvidia,tegra-audio-max98090"; + nvidia,model = "NVIDIA Tegra Venice2"; + + nvidia,audio-routing = + "Headphones", "HPR", + "Headphones", "HPL", + "Speakers", "SPKR", + "Speakers", "SPKL", + "Mic Jack", "MICBIAS", + "IN34", "Mic Jack"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&acodec>; + + clocks = <&tegra_car TEGRA124_CLK_PLL_A>, + <&tegra_car TEGRA124_CLK_PLL_A_OUT0>, + <&tegra_car TEGRA124_CLK_EXTERN1>; + clock-names = "pll_a", "pll_a_out0", "mclk"; + }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml deleted file mode 100644 index 69af2022d0fabf..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml +++ /dev/null @@ -1,90 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-cpcap.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with CPCAP CODEC - -maintainers: - - Svyatoslav Ryhel - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^motorola,tegra-audio-cpcap(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-cpcap - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: - A list of the connections between audio components. Each entry is a - pair of strings, the first being the connection's sink, the second - being the connection's source. Valid names for sources and sinks are - the pins (documented in the binding document), and the jacks on the - board. - minItems: 2 - items: - enum: - # Board Connectors - - Speakers - - Int Spk - - Earpiece - - Int Mic - - Headset Mic - - Internal Mic 1 - - Internal Mic 2 - - Headphone - - Headphones - - Headphone Jack - - Mic Jack - - # CODEC Pins - - MICR - - HSMIC - - EMUMIC - - MICL - - EXTR - - EXTL - - EP - - SPKR - - SPKL - - LINER - - LINEL - - HSR - - HSL - - EMUR - - EMUL - -unevaluatedProperties: false - -examples: - - | - #include - #include - sound { - compatible = "motorola,tegra-audio-cpcap-olympus", - "nvidia,tegra-audio-cpcap"; - nvidia,model = "Motorola Atrix 4G (MB860) CPCAP"; - - nvidia,audio-routing = - "Headphones", "HSR", - "Headphones", "HSL", - "Int Spk", "SPKR", - "Int Spk", "SPKL", - "Earpiece", "EP", - "HSMIC", "Mic Jack", - "MICR", "Internal Mic 1", - "MICL", "Internal Mic 2"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&cpcap_audio>; - - clocks = <&tegra_car TEGRA20_CLK_PLL_A>, - <&tegra_car TEGRA20_CLK_PLL_A_OUT0>, - <&tegra_car TEGRA20_CLK_CDEV1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml deleted file mode 100644 index 4957645a8e0358..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml +++ /dev/null @@ -1,95 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-max9808x.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with MAX9808x CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - oneOf: - - items: - - pattern: '^[a-z0-9]+,tegra-audio-max98088(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-max98088 - - items: - - pattern: '^[a-z0-9]+,tegra-audio-max98089(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-max98089 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Speakers - - Int Spk - - Headphone - - Headphones - - Headphone Jack - - Earpiece - - Headset Mic - - Mic Jack - - Int Mic - - Internal Mic 1 - - Internal Mic 2 - - # CODEC Pins - - HPL - - HPR - - SPKL - - SPKR - - RECL - - RECR - - INA1 - - INA2 - - INB1 - - INB2 - - MIC1 - - MIC2 - - MICBIAS - -unevaluatedProperties: false - -examples: - - | - #include - #include - sound { - compatible = "lg,tegra-audio-max98089-p895", - "nvidia,tegra-audio-max98089"; - nvidia,model = "LG Optimus Vu MAX98089"; - - nvidia,audio-routing = - "Headphone Jack", "HPL", - "Headphone Jack", "HPR", - "Int Spk", "SPKL", - "Int Spk", "SPKR", - "Earpiece", "RECL", - "Earpiece", "RECR", - "INA1", "Headset Mic", - "MIC1", "MICBIAS", - "MICBIAS", "Internal Mic 1", - "MIC2", "Internal Mic 2"; - - nvidia,i2s-controller = <&tegra_i2s0>; - nvidia,audio-codec = <&codec>; - - clocks = <&tegra_car TEGRA30_CLK_PLL_A>, - <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, - <&tegra_pmc TEGRA_PMC_CLK_OUT_1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max98090.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max98090.yaml deleted file mode 100644 index 4d912458b18ba0..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max98090.yaml +++ /dev/null @@ -1,97 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-max98090.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with MAX98090 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - oneOf: - - items: - - pattern: '^[a-z0-9]+,tegra-audio-max98090(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-max98090 - - items: - - enum: - - nvidia,tegra-audio-max98090-nyan-big - - nvidia,tegra-audio-max98090-nyan-blaze - - const: nvidia,tegra-audio-max98090-nyan - - const: nvidia,tegra-audio-max98090 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphones - - Speakers - - Mic Jack - - Int Mic - - # CODEC Pins - - MIC1 - - MIC2 - - DMICL - - DMICR - - IN1 - - IN2 - - IN3 - - IN4 - - IN5 - - IN6 - - IN12 - - IN34 - - IN56 - - HPL - - HPR - - SPKL - - SPKR - - RCVL - - RCVR - - MICBIAS - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - #include - - sound { - compatible = "nvidia,tegra-audio-max98090-venice2", - "nvidia,tegra-audio-max98090"; - nvidia,model = "NVIDIA Tegra Venice2"; - - nvidia,audio-routing = - "Headphones", "HPR", - "Headphones", "HPL", - "Speakers", "SPKR", - "Speakers", "SPKL", - "Mic Jack", "MICBIAS", - "IN34", "Mic Jack"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&acodec>; - - clocks = <&tegra_car TEGRA124_CLK_PLL_A>, - <&tegra_car TEGRA124_CLK_PLL_A_OUT0>, - <&tegra_car TEGRA124_CLK_EXTERN1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5631.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5631.yaml deleted file mode 100644 index 0c8067c3b05618..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5631.yaml +++ /dev/null @@ -1,85 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-rt5631.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with RT5631 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-rt5631(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-rt5631 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Int Spk - - Headphone Jack - - Mic Jack - - Int Mic - - # CODEC Pins - - MIC1 - - MIC2 - - AXIL - - AXIR - - MONOIN_RXN - - MONOIN_RXP - - DMIC - - MIC Bias1 - - MIC Bias2 - - MONO_IN - - AUXO1 - - AUXO2 - - SPOL - - SPOR - - HPOL - - HPOR - - MONO - -unevaluatedProperties: false - -examples: - - | - #include - #include - sound { - compatible = "asus,tegra-audio-rt5631-tf700t", - "nvidia,tegra-audio-rt5631"; - nvidia,model = "Asus Transformer Infinity TF700T RT5631"; - - nvidia,audio-routing = - "Headphone Jack", "HPOL", - "Headphone Jack", "HPOR", - "Int Spk", "SPOL", - "Int Spk", "SPOR", - "MIC1", "MIC Bias1", - "MIC Bias1", "Mic Jack", - "DMIC", "Int Mic"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&rt5631>; - - clocks = <&tegra_car TEGRA30_CLK_PLL_A>, - <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, - <&tegra_pmc TEGRA_PMC_CLK_OUT_1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5640.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5640.yaml deleted file mode 100644 index 2638592435b218..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5640.yaml +++ /dev/null @@ -1,84 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-rt5640.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with RT5639 or RT5640 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-rt56(39|40)(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-rt5640 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphones - - Speakers - - Mic Jack - - # CODEC Pins - - DMIC1 - - DMIC2 - - MICBIAS1 - - IN1P - - IN1R - - IN2P - - IN2R - - HPOL - - HPOR - - LOUTL - - LOUTR - - MONOP - - MONON - - SPOLP - - SPOLN - - SPORP - - SPORN - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-rt5640-dalmore", - "nvidia,tegra-audio-rt5640"; - nvidia,model = "NVIDIA Tegra Dalmore"; - - nvidia,audio-routing = - "Headphones", "HPOR", - "Headphones", "HPOL", - "Speakers", "SPORP", - "Speakers", "SPORN", - "Speakers", "SPOLP", - "Speakers", "SPOLN"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&rt5640>; - - nvidia,hp-det-gpios = <&gpio 143 0>; - - clocks = <&clk 216>, <&clk 217>, <&clk 120>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5677.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5677.yaml deleted file mode 100644 index 09e1d0b18d276b..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5677.yaml +++ /dev/null @@ -1,100 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-rt5677.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with RT5677 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-rt5677(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-rt5677 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone - - Speaker - - Headset Mic - - Internal Mic 1 - - Internal Mic 2 - - # CODEC Pins - - IN1P - - IN1N - - IN2P - - IN2N - - MICBIAS1 - - DMIC1 - - DMIC2 - - DMIC3 - - DMIC4 - - DMIC L1 - - DMIC L2 - - DMIC L3 - - DMIC L4 - - DMIC R1 - - DMIC R2 - - DMIC R3 - - DMIC R4 - - LOUT1 - - LOUT2 - - LOUT3 - - PDM1L - - PDM1R - - PDM2L - - PDM2R - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-rt5677-ryu", - "nvidia,tegra-audio-rt5677"; - nvidia,model = "NVIDIA Tegra Ryu"; - - nvidia,audio-routing = - "Headphone", "LOUT2", - "Headphone", "LOUT1", - "Headset Mic", "MICBIAS1", - "IN1P", "Headset Mic", - "IN1N", "Headset Mic", - "DMIC L1", "Internal Mic 1", - "DMIC R1", "Internal Mic 1", - "DMIC L2", "Internal Mic 2", - "DMIC R2", "Internal Mic 2", - "Speaker", "PDM1L", - "Speaker", "PDM1R"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&rt5677>; - - nvidia,hp-det-gpios = <&gpio 143 0>; - - clocks = <&clk 216>, - <&clk 217>, - <&clk 121>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-sgtl5000.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-sgtl5000.yaml deleted file mode 100644 index e5bc6a6ade2468..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-sgtl5000.yaml +++ /dev/null @@ -1,67 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-sgtl5000.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with SGTL5000 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-sgtl5000([-_][a-z0-9]+)+$' - - const: nvidia,tegra-audio-sgtl5000 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone Jack - - Line In Jack - - Mic Jack - - # CODEC Pins - - HP_OUT - - LINE_OUT - - LINE_IN - - MIC_IN - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - #include - - sound { - compatible = "toradex,tegra-audio-sgtl5000-apalis_t30", - "nvidia,tegra-audio-sgtl5000"; - nvidia,model = "Toradex Apalis T30 SGTL5000"; - nvidia,audio-routing = - "Headphone Jack", "HP_OUT", - "LINE_IN", "Line In Jack", - "MIC_IN", "Mic Jack"; - nvidia,i2s-controller = <&tegra_i2s2>; - nvidia,audio-codec = <&codec>; - clocks = <&tegra_car TEGRA30_CLK_PLL_A>, - <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, - <&tegra_car TEGRA30_CLK_EXTERN1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-trimslice.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-trimslice.yaml deleted file mode 100644 index 8c87cd166238fe..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-trimslice.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-trimslice.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with TrimSlice CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - const: nvidia,tegra-audio-trimslice - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-trimslice"; - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&codec>; - clocks = <&tegra_car 112>, <&tegra_car 113>, <&tegra_car 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8753.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8753.yaml deleted file mode 100644 index 3323d6a438f5ce..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8753.yaml +++ /dev/null @@ -1,79 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8753.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM8753 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm8753(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm8753 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone Jack - - Mic Jack - - # CODEC Pins - - LOUT1 - - LOUT2 - - ROUT1 - - ROUT2 - - MONO1 - - MONO2 - - OUT3 - - OUT4 - - LINE1 - - LINE2 - - RXP - - RXN - - ACIN - - ACOP - - MIC1N - - MIC1 - - MIC2N - - MIC2 - - Mic Bias - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-wm8753-whistler", - "nvidia,tegra-audio-wm8753"; - nvidia,model = "tegra-wm8753-harmony"; - - nvidia,audio-routing = - "Headphone Jack", "LOUT1", - "Headphone Jack", "ROUT1"; - - nvidia,i2s-controller = <&i2s1>; - nvidia,audio-codec = <&wm8753>; - - clocks = <&clk 112>, <&clk 113>, <&clk 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8903.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8903.yaml deleted file mode 100644 index 1be25ce4514b7a..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8903.yaml +++ /dev/null @@ -1,93 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8903.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM8903 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - oneOf: - - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm8903(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm8903 - - items: - - pattern: ad,tegra-audio-plutux - - const: nvidia,tegra-audio-wm8903 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone Jack - - Int Spk - - Mic Jack - - Int Mic - - # CODEC Pins - - IN1L - - IN1R - - IN2L - - IN2R - - IN3L - - IN3R - - DMICDAT - - HPOUTL - - HPOUTR - - LINEOUTL - - LINEOUTR - - LOP - - LON - - ROP - - RON - - MICBIAS - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-wm8903-harmony", - "nvidia,tegra-audio-wm8903"; - nvidia,model = "tegra-wm8903-harmony"; - - nvidia,audio-routing = - "Headphone Jack", "HPOUTR", - "Headphone Jack", "HPOUTL", - "Int Spk", "ROP", - "Int Spk", "RON", - "Int Spk", "LOP", - "Int Spk", "LON", - "Mic Jack", "MICBIAS", - "IN1L", "Mic Jack"; - - nvidia,i2s-controller = <&i2s1>; - nvidia,audio-codec = <&wm8903>; - - nvidia,spkr-en-gpios = <&codec 2 0>; - nvidia,hp-det-gpios = <&gpio 178 0>; - nvidia,int-mic-en-gpios = <&gpio 184 0>; - nvidia,ext-mic-en-gpios = <&gpio 185 0>; - - clocks = <&clk 112>, <&clk 113>, <&clk 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml deleted file mode 100644 index 2c3bf5a02a34fa..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml +++ /dev/null @@ -1,88 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8962.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM8962 CODEC - -maintainers: - - Svyatoslav Ryhel - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm8962(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm8962 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: - A list of the connections between audio components. Each entry is a - pair of strings, the first being the connection's sink, the second - being the connection's source. Valid names for sources and sinks are - the pins (documented in the binding document), and the jacks on the - board. - minItems: 2 - items: - enum: - # Board Connectors - - Speakers - - Int Spk - - Earpiece - - Int Mic - - Headset Mic - - Internal Mic 1 - - Internal Mic 2 - - Headphone - - Headphones - - Headphone Jack - - Mic Jack - - # CODEC Pins - - IN1L - - IN1R - - IN2L - - IN2R - - IN3L - - IN3R - - IN4L - - IN4R - - DMICDAT - - HPOUTL - - HPOUTR - - SPKOUT - - SPKOUTL - - SPKOUTR - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - #include - #include - sound { - compatible = "microsoft,tegra-audio-wm8962-surface-rt", - "nvidia,tegra-audio-wm8962"; - nvidia,model = "Microsoft Surface RT WM8962"; - - nvidia,audio-routing = - "Headphone Jack", "HPOUTR", - "Headphone Jack", "HPOUTL", - "Int Spk", "SPKOUTR", - "Int Spk", "SPKOUTL"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&wm8962>; - - clocks = <&tegra_car TEGRA30_CLK_PLL_A>, - <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, - <&tegra_pmc TEGRA_PMC_CLK_OUT_1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml deleted file mode 100644 index d2c317f831ced1..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml +++ /dev/null @@ -1,110 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8994.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM8994 CODEC - -maintainers: - - Svyatoslav Ryhel - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm8994(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm8994 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone Jack - - Int Spk - - Earpiece - - Line Out - - Mic Jack - - Int Mic - - Internal Mic 1 - - Internal Mic 2 - - FM Radio - - # CODEC Pins - - SPKOUTLP - - SPKOUTLN - - SPKOUTRP - - SPKOUTRN - - HPOUT1L - - HPOUT1R - - HPOUT2P - - HPOUT2N - - LINEOUT1P - - LINEOUT1N - - LINEOUT2P - - LINEOUT2N - - IN1LN - - IN1LP - - IN2LN - - IN2LP:VXRN - - IN1RN - - IN1RP - - IN2RN - - IN2RP:VXRP - - DMIC1DAT - - DMIC2DAT - - MICBIAS1 - - MICBIAS2 - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - #include - #include - - sound { - compatible = "lg,tegra-audio-wm8994-star", - "nvidia,tegra-audio-wm8994"; - nvidia,model = "LG Optimus 2X WM8994"; - - nvidia,audio-routing = - "Headphone Jack", "HPOUT1L", - "Headphone Jack", "HPOUT1R", - "Int Spk", "SPKOUTLN", - "Int Spk", "SPKOUTLP", - "Earpiece", "HPOUT2N", - "Earpiece", "HPOUT2P", - "IN2RN", "MICBIAS2", - "IN2RN", "Mic Jack", - "IN1LN", "MICBIAS1", - "IN1LN", "Internal Mic 1", - "IN1RN", "MICBIAS1", - "IN1RN", "Internal Mic 2"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&wm8994>; - - nvidia,hp-det-gpios = <&gpio 51 GPIO_ACTIVE_HIGH>; - nvidia,mic-det-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; - nvidia,ext-mic-en-gpios = <&gpio 57 GPIO_ACTIVE_HIGH>; - nvidia,coupled-mic-hp-det; - - clocks = <&tegra_car TEGRA20_CLK_PLL_A>, - <&tegra_car TEGRA20_CLK_PLL_A_OUT0>, - <&tegra_car TEGRA20_CLK_CDEV1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.yaml deleted file mode 100644 index 397306b8800d48..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm9712.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM9712 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm9712([-_][a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm9712 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone - - LineIn - - Mic - - # CODEC Pins - - MONOOUT - - HPOUTL - - HPOUTR - - LOUT2 - - ROUT2 - - OUT3 - - LINEINL - - LINEINR - - PHONE - - PCBEEP - - MIC1 - - MIC2 - - Mic Bias - -required: - - nvidia,ac97-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-wm9712-colibri_t20", - "nvidia,tegra-audio-wm9712"; - nvidia,model = "Toradex Colibri T20"; - - nvidia,audio-routing = - "Headphone", "HPOUTL", - "Headphone", "HPOUTR", - "LineIn", "LINEINL", - "LineIn", "LINEINR", - "Mic", "MIC1"; - - nvidia,ac97-controller = <&ac97>; - - clocks = <&clk 112>, <&clk 113>, <&clk 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/sound/codecs/cs35l56.rst b/Documentation/sound/codecs/cs35l56.rst index b3f8c1c2385185..ffbfbaed127144 100644 --- a/Documentation/sound/codecs/cs35l56.rst +++ b/Documentation/sound/codecs/cs35l56.rst @@ -235,19 +235,28 @@ No %s for algorithm %x HDA driver did not find a .bin file that matches this hardware. Calibration disabled due to missing firmware controls - Driver was not able to write EFI calibration data to firmware registers. - This typically means that either: - - * The driver did not find a suitable wmfw for this hardware, or + This isn't necessarily an error, only informative. The amplifier can + operate without calibration, as the firmware contains a default + calibration for the speakers, but it will tend to over-protect the + speakers. + + This message typically means that either: + * The driver did not find a suitable firmware .wmfw file for this + hardware, or * The amplifier has already been patched with firmware by something previously, and the driver does not have control of a hard RESET line to be able to reset the amplifier and download the firmware files it found. This situation is indicated by the device identification - string in the kernel log shows "(patched=1)" + string in the kernel log showing "(patched=1)" Failed to write calibration - Same meaning and cause as "Calibration disabled due to missing firmware - controls" + If the error code is -2 (ENOENT): + * The driver has been unbound and rebound without reprobing. + * The wrong firmware .wmfw file was loaded for this hardware so the + algorithm IDs in the firmware do not match what the driver is + looking for. + + Other error codes likely indicate a problem communicating with the hardware. Failed to read calibration data from EFI Factory calibration data in EFI is missing, empty or corrupt. diff --git a/Documentation/sound/hd-audio/notes.rst b/Documentation/sound/hd-audio/notes.rst index c16f83875b800c..c618bb2d7d3caf 100644 --- a/Documentation/sound/hd-audio/notes.rst +++ b/Documentation/sound/hd-audio/notes.rst @@ -486,6 +486,11 @@ inv_dmic_split (bool) indep_hp (bool) provide the independent headphone PCM stream and the corresponding mixer control, if available +indep_dig_outs (bool) + give the second digital output pin (in parsing order) its own PCM + stream, "Alt Digital", instead of making it follow the first one's + stream. The PCM takes the next free device number of its type, so + two S/PDIF outputs need dynamic minors. Default false add_stereo_mix_input (bool) add the stereo mix (analog-loopback mix) to the input mux if available diff --git a/drivers/base/regmap/regcache-maple.c b/drivers/base/regmap/regcache-maple.c index 16e488da5f230c..9bc217b1e6f9ed 100644 --- a/drivers/base/regmap/regcache-maple.c +++ b/drivers/base/regmap/regcache-maple.c @@ -41,7 +41,7 @@ static int regcache_maple_write(struct regmap *map, unsigned int reg, struct maple_tree *mt = map->cache; MA_STATE(mas, mt, reg, reg); unsigned long *entry, *upper, *lower; - unsigned long index, last; + unsigned long index, last, lower_reg, upper_reg; size_t lower_sz, upper_sz; int ret; @@ -54,18 +54,33 @@ static int regcache_maple_write(struct regmap *map, unsigned int reg, return 0; } - /* Any adjacent entries to extend/merge? */ - mas_set_range(&mas, reg - 1, reg + 1); + /* Avoid overflowing and wrapping */ + if (reg != 0) + lower_reg = reg - 1; + else + lower_reg = reg; + + if (reg != UINT_MAX) + upper_reg = reg + 1; + else + upper_reg = reg; + + /* + * Any adjacent entries to extend/merge? If we are here then + * reg won't exist so if we clamped lower_reg or upper_reg we + * won't get anything from mas_find(). + */ + mas_set_range(&mas, lower_reg, upper_reg); index = reg; last = reg; - lower = mas_find(&mas, reg - 1); + lower = mas_find(&mas, lower_reg); if (lower) { index = mas.index; lower_sz = (mas.last - mas.index + 1) * sizeof(unsigned long); } - upper = mas_find(&mas, reg + 1); + upper = mas_find(&mas, upper_reg); if (upper) { last = mas.last; upper_sz = (mas.last - mas.index + 1) * sizeof(unsigned long); diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index e2feed6dbecffd..26ac2c2a209508 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -494,8 +494,10 @@ static int regcache_rbtree_sync(struct regmap *map, unsigned int min, ret = regcache_sync_block(map, rbnode->block, rbnode->cache_present, rbnode->base_reg, start, end); - if (ret != 0) + if (ret != 0) { + map->async = false; return ret; + } } map->async = false; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 774e9c9bf3c49f..1447967ff00b87 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -589,6 +589,9 @@ int regcache_drop_region(struct regmap *map, unsigned int min, trace_regcache_drop_region(map, min, max); + if (min > max) + return -EINVAL; + return map->cache_ops->drop(map, min, max); } EXPORT_SYMBOL_GPL(regcache_drop_region); diff --git a/drivers/base/regmap/regmap-kunit.c b/drivers/base/regmap/regmap-kunit.c index c78772b660d6c5..c948219f46a39d 100644 --- a/drivers/base/regmap/regmap-kunit.c +++ b/drivers/base/regmap/regmap-kunit.c @@ -1347,6 +1347,26 @@ static void cache_drop(struct kunit *test) KUNIT_EXPECT_MEMEQ(test, &data->vals[param->from_reg], rval, sizeof(rval)); } +static void cache_drop_min_max(struct kunit *test) +{ + const struct regmap_test_param *param = test->param_value; + struct regmap *map; + struct regmap_config config; + struct regmap_ram_data *data; + + config = test_regmap_config; + config.num_reg_defaults = BLOCK_TEST_SIZE; + + map = gen_regmap(test, &config, &data); + KUNIT_ASSERT_FALSE(test, IS_ERR(map)); + if (IS_ERR(map)) + return; + + /* Drop some registers */ + KUNIT_EXPECT_EQ(test, -EINVAL, regcache_drop_region(map, param->from_reg + 5, + param->from_reg + 3)); +} + static void cache_drop_with_non_contiguous_ranges(struct kunit *test) { const struct regmap_test_param *param = test->param_value; @@ -2253,6 +2273,7 @@ static struct kunit_case regmap_test_cases[] = { KUNIT_CASE_PARAM(cache_sync_readonly, real_cache_types_gen_params), KUNIT_CASE_PARAM(cache_sync_patch, real_cache_types_gen_params), KUNIT_CASE_PARAM(cache_drop, sparse_cache_types_gen_params), + KUNIT_CASE_PARAM(cache_drop_min_max, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_drop_with_non_contiguous_ranges, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_drop_all_and_sync_marked_dirty, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_drop_all_and_sync_no_defaults, sparse_cache_types_gen_params), diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 83f1f59421356f..48e96bc9e7b657 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1215,7 +1215,7 @@ static void regmap_field_init(struct regmap_field *rm_field, rm_field->regmap = regmap; rm_field->reg = reg_field.reg; rm_field->shift = reg_field.lsb; - rm_field->mask = GENMASK(reg_field.msb, reg_field.lsb); + rm_field->mask = REG_FIELD_GENMASK(reg_field); WARN_ONCE(rm_field->mask == 0, "invalid empty mask defined\n"); diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 403e00b7327a8a..06a0d89a944378 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1384,7 +1384,9 @@ static int sdw_config_stream(struct device *dev, return -EINVAL; } - if (stream->params.bps && + /* PDM streams have no stream-wide bps; skip the match for them. */ + if (stream_config->type != SDW_STREAM_PDM && + stream->params.bps && stream->params.bps != stream_config->bps) { dev_err(dev, "bps not matching, stream:%s\n", stream->name); return -EINVAL; diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index b94680b60af958..9ad42dd9de7053 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -398,6 +398,31 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) u8 *tmpbuf; int ret; + if (op->panic_write) { + if (!spi_mem_controller_is_capable(ctlr, panic_write)) + return -EOPNOTSUPP; + + /* + * Only support panic writes if we'll go through .exec_op(). + * The regular SPI path (spi_sync(), etc.) explicitly mentions + * that it can only be used from a context that may sleep, which + * is not the case on a kernel panic. + * If this changes in the future, this could be reworked, but + * for now this will have to do. + */ + if (WARN_ON_ONCE(!ctlr->mem_ops || !ctlr->mem_ops->exec_op)) + return -EOPNOTSUPP; + + if (spi_get_csgpiod(mem->spi, 0)) + return -EPERM; + + /* On panic IRQs are disabled so we cannot wait on locks */ + if (mutex_is_locked(&ctlr->bus_lock_mutex) || + mutex_is_locked(&ctlr->io_mutex)) { + return -EPERM; + } + } + /* Make sure the operation frequency is correct before going futher */ spi_mem_adjust_op_freq(mem, (struct spi_mem_op *)op); @@ -420,15 +445,20 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) return -EOPNOTSUPP; if (ctlr->mem_ops && ctlr->mem_ops->exec_op && !spi_get_csgpiod(mem->spi, 0)) { - ret = spi_mem_access_start(mem); - if (ret) - return ret; + if (!op->panic_write) { + ret = spi_mem_access_start(mem); + if (ret) + return ret; + } else if (!pm_runtime_active(ctlr->dev.parent)) { + return -EPERM; + } trace_spi_mem_start_op(mem, op); ret = ctlr->mem_ops->exec_op(mem, op); trace_spi_mem_stop_op(mem, op); - spi_mem_access_end(mem); + if (!op->panic_write) + spi_mem_access_end(mem); /* * Some controllers only optimize specific paths (typically the @@ -441,6 +471,10 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) return ret; } + + /* On panic write we can't fall back to the fallback SPI path */ + if (op->panic_write) + return ret; } tmpbufsize = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; diff --git a/include/linux/regmap.h b/include/linux/regmap.h index c8aebd148e08ec..8f3034881f8a2e 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -10,6 +10,7 @@ * Author: Mark Brown */ +#include #include #include #include @@ -248,9 +249,9 @@ struct regmap_range { * check function will return true. "no_ranges" are searched first. */ struct regmap_access_table { - const struct regmap_range *yes_ranges; + const struct regmap_range *yes_ranges __counted_by_ptr(n_yes_ranges); unsigned int n_yes_ranges; - const struct regmap_range *no_ranges; + const struct regmap_range *no_ranges __counted_by_ptr(n_no_ranges); unsigned int n_no_ranges; }; @@ -1477,6 +1478,8 @@ struct reg_field { .id_offset = _offset, \ } +#define REG_FIELD_GENMASK(_reg_field) GENMASK(_reg_field.msb, _reg_field.lsb) + struct regmap_field *regmap_field_alloc(struct regmap *regmap, struct reg_field reg_field); void regmap_field_free(struct regmap_field *field); diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index 0cce6b57242ae0..2633307bfad50f 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -169,6 +169,7 @@ enum spi_mem_data_dir { * operation does not involve transferring data * @data.buf.in: input buffer (must be DMA-able) * @data.buf.out: output buffer (must be DMA-able) + * @panic_write: The command is executed in the context of a kernel panic * @max_freq: frequency limitation wrt this operation. 0 means there is no * specific constraint and the highest achievable frequency can be * attempted. @@ -211,6 +212,7 @@ struct spi_mem_op { } buf; } data; + bool panic_write; unsigned int max_freq; }; @@ -392,6 +394,8 @@ struct spi_controller_mem_ops { * is a pause in the transfer (eg. internal bus contention or * DMA arbitration on an interconnect). Features such as NAND * continuous reads shall not be leveraged. + * @panic_write: Set to true if the controller driver supports writing to a + * connected NAND/NOR memory in a kernel panic context */ struct spi_controller_mem_caps { bool dtr; @@ -400,6 +404,7 @@ struct spi_controller_mem_caps { bool per_op_freq; bool secondary_op_tmpl; bool no_cs_assertion; + bool panic_write; }; #define spi_mem_controller_is_capable(ctlr, cap) \ diff --git a/include/sound/core.h b/include/sound/core.h index d6584967fd491b..4cecf04204f406 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -271,12 +271,16 @@ int snd_register_device(int type, struct snd_card *card, int dev, void *private_data, struct device *device); int snd_unregister_device(struct device *dev); void *snd_lookup_minor_data(unsigned int minor, int type); +void *snd_lookup_minor_data_file(unsigned int minor, int type, + struct file *file); #ifdef CONFIG_SND_OSSEMUL int snd_register_oss_device(int type, struct snd_card *card, int dev, const struct file_operations *f_ops, void *private_data); int snd_unregister_oss_device(int type, struct snd_card *card, int dev); void *snd_lookup_oss_minor_data(unsigned int minor, int type); +void *snd_lookup_oss_minor_data_file(unsigned int minor, int type, + struct file *file); #endif int snd_minor_info_init(void); @@ -323,7 +327,10 @@ int snd_card_add_dev_attr(struct snd_card *card, const struct attribute_group *group); int snd_component_add(struct snd_card *card, const char *component); int snd_card_file_add(struct snd_card *card, struct file *file); +int snd_card_file_add_data(struct snd_card *card, struct file *file, + const void *data); int snd_card_file_remove(struct snd_card *card, struct file *file); +void snd_card_file_sync(struct snd_card *card, const void *data); struct snd_card *snd_card_ref(int card); diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h index 7542cabfa7266e..b6b338c55ff0bf 100644 --- a/include/sound/cs35l41.h +++ b/include/sound/cs35l41.h @@ -924,5 +924,7 @@ bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type); int cs35l41_mdsync_up(struct regmap *regmap); int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type, int enable, struct cs_dsp *dsp); +int cs35l41_wait_boot_done(struct device *dev, struct regmap *regmap); +int cs35l41_reset_and_boot(struct device *dev, struct regmap *regmap); #endif /* __CS35L41_H */ diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index c7f29ad64e02c8..084f06488a95f6 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -318,6 +318,7 @@ int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card, int snd_hda_codec_configure(struct hda_codec *codec); int snd_hda_codec_update_widgets(struct hda_codec *codec); void snd_hda_codec_register(struct hda_codec *codec); +int snd_hda_card_register(struct hda_bus *bus); void snd_hda_codec_unregister(struct hda_codec *codec); void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec); diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 04fd4794bceee5..682e10a0f6dbaa 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -367,6 +367,7 @@ struct hdac_bus { spinlock_t reg_lock; struct mutex cmd_mutex; struct mutex lock; + struct mutex codec_bind_lock; /* codec driver bind, card register */ /* DRM component interface */ struct drm_audio_component *audio_component; diff --git a/include/sound/hwdep.h b/include/sound/hwdep.h index b0da633184cde5..7357727f066033 100644 --- a/include/sound/hwdep.h +++ b/include/sound/hwdep.h @@ -57,6 +57,7 @@ struct snd_hwdep { struct mutex open_mutex; int used; /* reference counter */ + struct mutex dsp_mutex; /* serializes DSP load requests */ unsigned int dsp_loaded; /* bit fields of loaded dsp indices */ unsigned int exclusive:1; /* exclusive access mode */ }; diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 1cf511f8d880ea..04f58c2ec5c623 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -548,6 +548,7 @@ struct snd_pcm { bool internal; /* pcm is for internal use only */ bool nonatomic; /* whole PCM operations are in non-atomic context */ bool no_device_suspend; /* don't invoke device PM suspend */ + bool disconnected; /* disconnected, no new open */ #if IS_ENABLED(CONFIG_SND_PCM_OSS) struct snd_pcm_oss oss; #endif diff --git a/include/sound/soc-card.h b/include/sound/soc-card.h index 1f1e7d45f55335..ce2b8653f67d37 100644 --- a/include/sound/soc-card.h +++ b/include/sound/soc-card.h @@ -28,6 +28,8 @@ static inline void snd_soc_card_mutex_unlock(struct snd_soc_card *card) mutex_unlock(&card->mutex); } +int snd_soc_card_add_controls(struct snd_soc_card *soc_card, + const struct snd_kcontrol_new *controls, int num_controls); struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name); int snd_soc_card_jack_new(struct snd_soc_card *card, const char *id, int type, diff --git a/include/sound/soc.h b/include/sound/soc.h index f6b98324f3464b..338662645b42fa 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -540,8 +540,6 @@ static inline int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops) struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, void *data, const char *long_name, const char *prefix); -int snd_soc_add_card_controls(struct snd_soc_card *soc_card, - const struct snd_kcontrol_new *controls, int num_controls); int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, @@ -1539,5 +1537,6 @@ static inline void _snd_soc_dpcm_mutex_assert_held_r(struct snd_soc_pcm_runtime #define snd_soc_register_dai snd_soc_dai_register #define snd_soc_unregister_dai snd_soc_dai_unregister #define snd_soc_add_component_controls snd_soc_component_add_controls +#define snd_soc_add_card_controls snd_soc_card_add_controls #endif diff --git a/sound/core/control.c b/sound/core/control.c index 535ceba294acf8..42f16d2d0e7988 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -1759,9 +1759,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name)) return -EINVAL; + info->id.numid = 0; + /* Delete a control to replace them if needed. */ if (replace) { - info->id.numid = 0; err = snd_ctl_remove_user_ctl(file, &info->id); if (err) return err; diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c index 87f7ad5ab73911..cad00d4aa2048b 100644 --- a/sound/core/control_compat.c +++ b/sound/core/control_compat.c @@ -183,6 +183,10 @@ static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, info->id = *id; err = kctl->info(kctl, info); if (err >= 0) { + err = snd_ctl_check_elem_info(card, info); + if (err < 0) + return err; + err = info->type; *countp = info->count; } diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 501387287ae693..f9a776d1fef510 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -140,6 +140,7 @@ static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, { struct snd_ctl_led *led; struct snd_ctl_led_ctl *lctl; + enum snd_ctl_led_mode mode; int route; bool found; @@ -168,8 +169,9 @@ static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); } } + mode = led->mode; } - switch (led->mode) { + switch (mode) { case MODE_OFF: route = 1; break; case MODE_ON: route = 0; break; case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break; @@ -382,6 +384,8 @@ static ssize_t mode_show(struct device *dev, struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); const char *str = NULL; + guard(mutex)(&snd_ctl_led_mutex); + switch (led->mode) { case MODE_FOLLOW_MUTE: str = "follow-mute"; break; case MODE_FOLLOW_ROUTE: str = "follow-route"; break; @@ -447,22 +451,23 @@ static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = { NULL, }; +/* return the end of substring pointing to either comma or NUL */ static char *find_eos(char *s) { while (*s && *s != ',') s++; - if (*s) - s++; return s; } static char *parse_uint(char *s, unsigned int *val) { - unsigned long long res; - if (kstrtoull(s, 10, &res)) - res = 0; - *val = res; - return find_eos(s); + char *end = find_eos(s); + char delimiter = *end; + *end = '\0'; + if (kstrtouint(s, 10, val)) + *val = 0; + *end = delimiter; + return end; } static char *parse_string(char *s, char *val, size_t val_size) @@ -477,6 +482,8 @@ static char *parse_string(char *s, char *val, size_t val_size) } s++; } + if (*s) + s++; } else { while (*s && *s != ',') { if (val_size > 1) { @@ -487,8 +494,6 @@ static char *parse_string(char *s, char *val, size_t val_size) } } *val = '\0'; - if (*s) - s++; return s; } @@ -518,6 +523,11 @@ static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, si if (strscpy(buf2, buf, sizeof(buf2)) < 0) return -E2BIG; + + s = strim(buf2); + if (s != buf2) + memmove(buf2, s, strlen(s) + 1); + memset(&id, 0, sizeof(id)); id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; s = buf2; diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c index c6de5345e946f1..fb9034897c561b 100644 --- a/sound/core/hwdep.c +++ b/sound/core/hwdep.c @@ -212,6 +212,7 @@ static int snd_hwdep_dsp_load(struct snd_hwdep *hw, return -ENXIO; if (info->index >= 32) return -EINVAL; + guard(mutex)(&hw->dsp_mutex); /* check whether the dsp was already loaded */ if (hw->dsp_loaded & (1u << info->index)) return -EBUSY; @@ -380,6 +381,7 @@ int snd_hwdep_new(struct snd_card *card, char *id, int device, init_waitqueue_head(&hwdep->open_wait); mutex_init(&hwdep->open_mutex); + mutex_init(&hwdep->dsp_mutex); hwdep->card = card; hwdep->device = device; if (id) diff --git a/sound/core/info.c b/sound/core/info.c index 023b4736aa6deb..2c2a80a7d0d2ba 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -484,7 +484,7 @@ int snd_info_card_create(struct snd_card *card) if (snd_BUG_ON(!card)) return -ENXIO; - sprintf(str, "card%i", card->number); + scnprintf(str, sizeof(str), "card%i", card->number); entry = create_subdir(card->module, str); if (!entry) return -ENOMEM; diff --git a/sound/core/init.c b/sound/core/init.c index d518cb83877711..ee660dc4098b0f 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -24,6 +24,7 @@ /* monitor files for graceful shutdown (hotplug) */ struct snd_monitor_file { struct file *file; + const void *data; /* device data from the minor lookup, or NULL */ const struct file_operations *disconnected_f_op; struct list_head shutdown_list; /* still need to shutdown */ struct list_head list; /* link of monitor files */ @@ -1075,17 +1076,18 @@ int snd_component_add(struct snd_card *card, const char *component) EXPORT_SYMBOL(snd_component_add); /** - * snd_card_file_add - add the file to the file list of the card + * snd_card_file_add_data - add the file of a device to the file list * @card: soundcard structure * @file: file pointer + * @data: the device data the file is opened for * - * This function adds the file to the file linked-list of the card. - * This linked-list is used to keep tracking the connection state, - * and to avoid the release of busy resources by hotplug. + * Like snd_card_file_add(), but also records @data for + * snd_card_file_sync(). * * Return: zero or a negative error code. */ -int snd_card_file_add(struct snd_card *card, struct file *file) +int snd_card_file_add_data(struct snd_card *card, struct file *file, + const void *data) { struct snd_monitor_file *mfile; @@ -1093,6 +1095,7 @@ int snd_card_file_add(struct snd_card *card, struct file *file) if (mfile == NULL) return -ENOMEM; mfile->file = file; + mfile->data = data; mfile->disconnected_f_op = NULL; INIT_LIST_HEAD(&mfile->shutdown_list); guard(spinlock)(&card->files_lock); @@ -1104,6 +1107,22 @@ int snd_card_file_add(struct snd_card *card, struct file *file) get_device(&card->card_dev); return 0; } + +/** + * snd_card_file_add - add the file to the file list of the card + * @card: soundcard structure + * @file: file pointer + * + * This function adds the file to the file linked-list of the card. + * This linked-list is used to keep tracking the connection state, + * and to avoid the release of busy resources by hotplug. + * + * Return: zero or a negative error code. + */ +int snd_card_file_add(struct snd_card *card, struct file *file) +{ + return snd_card_file_add_data(card, file, NULL); +} EXPORT_SYMBOL(snd_card_file_add); /** @@ -1135,7 +1154,7 @@ int snd_card_file_remove(struct snd_card *card, struct file *file) break; } } - if (list_empty(&card->files_list)) + if (list_empty(&card->files_list) || (found && found->data)) wake_up_all(&card->remove_sleep); } if (!found) { @@ -1148,6 +1167,36 @@ int snd_card_file_remove(struct snd_card *card, struct file *file) } EXPORT_SYMBOL(snd_card_file_remove); +static bool snd_card_has_data_file(struct snd_card *card, const void *data) +{ + struct snd_monitor_file *mfile; + + list_for_each_entry(mfile, &card->files_list, list) { + if (mfile->data == data) + return true; + } + return false; +} + +/** + * snd_card_file_sync - wait until the files opened for a device are closed + * @card: soundcard structure + * @data: the device data registered for the minors of the device + * + * Waits until every file added for @data by snd_lookup_minor_data_file() + * or snd_lookup_oss_minor_data_file() has been removed from @card. The + * minors of the device must already be unregistered, so that no new file + * can be added for it. + */ +void snd_card_file_sync(struct snd_card *card, const void *data) +{ + guard(spinlock_irq)(&card->files_lock); + wait_event_lock_irq(card->remove_sleep, + !snd_card_has_data_file(card, data), + card->files_lock); +} +EXPORT_SYMBOL_GPL(snd_card_file_sync); + #ifdef CONFIG_PM /** * snd_power_ref_and_wait - wait until the card gets powered up diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index 5bc7e586b430f5..3118bb14231cec 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -506,7 +506,7 @@ static void *snd_dma_wc_alloc(struct snd_dma_buffer *dmab, size_t size) return NULL; dmab->addr = dma_map_single(dmab->dev.dev, p, size, DMA_BIDIRECTIONAL); if (dma_mapping_error(dmab->dev.dev, dmab->addr)) { - do_free_pages(dmab->area, size, true); + do_free_pages(p, size, true); return NULL; } return p; diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index a51bc68473e63e..de17a132a6a55c 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -185,7 +185,7 @@ static int snd_mixer_oss_get_recsrc(struct snd_mixer_oss_file *fmixer) return -EIO; guard(mutex)(&mixer->reg_mutex); if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ - unsigned int index; + unsigned int index = 0; result = mixer->get_recsrc(fmixer, &index); if (result < 0) return result; @@ -212,7 +212,7 @@ static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsr struct snd_mixer_oss *mixer = fmixer->mixer; struct snd_mixer_oss_slot *pslot; int chn, active; - unsigned int index; + unsigned int index = 0; int result = 0; if (mixer == NULL) @@ -222,8 +222,8 @@ static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsr if (recsrc & ~mixer->oss_recsrc) recsrc &= ~mixer->oss_recsrc; mixer->put_recsrc(fmixer, ffz(~recsrc)); - mixer->get_recsrc(fmixer, &index); - result = 1 << index; + if (mixer->get_recsrc(fmixer, &index) == 0) + result = 1 << index; } for (chn = 0; chn < 31; chn++) { pslot = &mixer->slots[chn]; @@ -826,6 +826,8 @@ static int snd_mixer_oss_get_recsrc2(struct snd_mixer_oss_file *fmixer, unsigned break; } } + if (idx == 32) + return -ENOENT; return 0; } diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index e8df27d09b9710..2d99df900f76dc 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -2497,15 +2497,12 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) nonseekable_open(inode, file); - pcm = snd_lookup_oss_minor_data(iminor(inode), - SNDRV_OSS_DEVICE_TYPE_PCM); - if (pcm == NULL) { - err = -ENODEV; - goto __error1; - } - err = snd_card_file_add(pcm->card, file); - if (err < 0) - goto __error1; + pcm = snd_lookup_oss_minor_data_file(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_PCM, file); + if (IS_ERR(pcm)) + return PTR_ERR(pcm); + if (!pcm) + return -ENODEV; if (!try_module_get(pcm->card->module)) { err = -ENODEV; goto __error2; @@ -2558,16 +2555,12 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) mutex_unlock(&pcm->open_mutex); if (err < 0) goto __error; - snd_card_unref(pcm->card); return err; __error: module_put(pcm->card->module); __error2: snd_card_file_remove(pcm->card, file); - __error1: - if (pcm) - snd_card_unref(pcm->card); return err; } diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c index acf5ca663ba8ca..3de71377999367 100644 --- a/sound/core/oss/pcm_plugin.c +++ b/sound/core/oss/pcm_plugin.c @@ -604,7 +604,7 @@ snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *plug, st if (err != frames1) { frames = err; if (plugin->src_frames) { - frames = plugin->src_frames(plugin, frames1); + frames = plugin->src_frames(plugin, err); if (frames <= 0) return frames; } diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 064df4cd3b50e6..3549f0b8ffd2d5 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -897,6 +897,8 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, stream != SNDRV_PCM_STREAM_CAPTURE)) return -EINVAL; *rsubstream = NULL; + if (pcm->disconnected) + return -ENODEV; pstr = &pcm->streams[stream]; if (pstr->substream == NULL || pstr->substream_count == 0) return -ENODEV; @@ -929,6 +931,8 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, return -ENODEV; if (! SUBSTREAM_BUSY(substream)) return -EBADFD; + if (is_oss_stream(substream)) + return -EINVAL; substream->ref_count++; *rsubstream = substream; return 0; @@ -1101,6 +1105,7 @@ static int snd_pcm_dev_disconnect(struct snd_device *device) guard(mutex)(®ister_mutex); scoped_guard(mutex, &pcm->open_mutex) { + pcm->disconnected = true; wake_up(&pcm->open_wait); list_del_init(&pcm->list); diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index 1306b04be171ae..41feb5312ffece 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -169,8 +169,7 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) if (!desc) return -ENOMEM; - desc->callback = dmaengine_pcm_dma_complete; - desc->callback_param = substream; + dmaengine_desc_set_callback(desc, dmaengine_pcm_dma_complete, NULL, substream); prtd->cookie = dmaengine_submit(desc); return 0; diff --git a/sound/core/pcm_local.h b/sound/core/pcm_local.h index ecb21697ae3a4d..4b134d435c24f3 100644 --- a/sound/core/pcm_local.h +++ b/sound/core/pcm_local.h @@ -80,4 +80,10 @@ static inline void snd_pcm_dma_buffer_sync(struct snd_pcm_substream *substream, snd_dma_buffer_sync(snd_pcm_get_dma_buf(substream), mode); } +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +#define is_oss_stream(substream) ((substream)->oss.oss) +#else +#define is_oss_stream(substream) false +#endif + #endif /* __SOUND_CORE_PCM_LOCAL_H */ diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 5cbf88da7626db..92372bde6fb66b 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -749,12 +749,6 @@ int snd_pcm_runtime_buffer_set_silence(struct snd_pcm_runtime *runtime) } EXPORT_SYMBOL_GPL(snd_pcm_runtime_buffer_set_silence); -#if IS_ENABLED(CONFIG_SND_PCM_OSS) -#define is_oss_stream(substream) ((substream)->oss.oss) -#else -#define is_oss_stream(substream) false -#endif - static void pcm_qos_req_clear(struct snd_pcm_substream *substream) { if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) @@ -776,14 +770,15 @@ static void pcm_qos_req_update(struct snd_pcm_substream *substream, int usecs) usecs); } -static int hw_params_state_check(struct snd_pcm_substream *substream) +static int hw_params_state_check(struct snd_pcm_substream *substream, + bool check_mmap) { guard(pcm_stream_lock_irq)(substream); switch (substream->runtime->state) { case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_SETUP: case SNDRV_PCM_STATE_PREPARED: - if (!is_oss_stream(substream) && + if (check_mmap && !is_oss_stream(substream) && atomic_read(&substream->mmap_count)) return -EBADFD; return 0; @@ -806,7 +801,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, err = snd_pcm_buffer_access_lock(runtime); if (err < 0) return err; - err = hw_params_state_check(substream); + err = hw_params_state_check(substream, true); if (err) goto unlock; @@ -944,7 +939,8 @@ static int snd_pcm_hw_refine_with_lock(struct snd_pcm_substream *substream, err = snd_pcm_buffer_access_lock(runtime); if (err < 0) return err; - err = hw_params_state_check(substream); + /* Refinement queries constraints without changing the mapped buffer. */ + err = hw_params_state_check(substream, false); if (err) goto unlock; @@ -2938,45 +2934,39 @@ static int snd_pcm_open_file(struct file *file, static int snd_pcm_playback_open(struct inode *inode, struct file *file) { struct snd_pcm *pcm; - int err; nonseekable_open(inode, file); - pcm = snd_lookup_minor_data(iminor(inode), - SNDRV_DEVICE_TYPE_PCM_PLAYBACK); - err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); - if (pcm) - snd_card_unref(pcm->card); - return err; + pcm = snd_lookup_minor_data_file(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_PLAYBACK, file); + if (IS_ERR(pcm)) + return PTR_ERR(pcm); + if (!pcm) + return -ENODEV; + return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); } static int snd_pcm_capture_open(struct inode *inode, struct file *file) { struct snd_pcm *pcm; - int err; nonseekable_open(inode, file); - pcm = snd_lookup_minor_data(iminor(inode), - SNDRV_DEVICE_TYPE_PCM_CAPTURE); - err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); - if (pcm) - snd_card_unref(pcm->card); - return err; + pcm = snd_lookup_minor_data_file(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_CAPTURE, file); + if (IS_ERR(pcm)) + return PTR_ERR(pcm); + if (!pcm) + return -ENODEV; + return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); } +/* called with the file added to the card by snd_lookup_minor_data_file() */ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) { int err; wait_queue_entry_t wait; - if (pcm == NULL) { - err = -ENODEV; - goto __error1; - } - err = snd_card_file_add(pcm->card, file); - if (err < 0) - goto __error1; if (!try_module_get(pcm->card->module)) { err = -ENODEV; goto __error2; @@ -3018,7 +3008,6 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) module_put(pcm->card->module); __error2: snd_card_file_remove(pcm->card, file); - __error1: return err; } diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c index ff4ee26adad1fc..62bdef6dca58c9 100644 --- a/sound/core/seq/seq_ump_convert.c +++ b/sound/core/seq/seq_ump_convert.c @@ -790,13 +790,22 @@ static int paf_ev_to_ump_midi2(const struct snd_seq_event *event, return 1; } +static void reset_rpn_data(struct ump_cvt_to_ump_bank *cc) +{ + cc->cc_data_msb = cc->cc_data_lsb = 0; + cc->cc_data_msb_set = cc->cc_data_lsb_set = 0; +} + +/* + * Deselect on a Null RPN/NRPN, but keep the 7f/7f numbers as a MIDI 1.0 + * receiver does. Otherwise, when the LSB was already 127, the second + * message of a Null would select RPN or NRPN 0/127. + */ static void reset_rpn(struct ump_cvt_to_ump_bank *cc) { cc->rpn_set = 0; cc->nrpn_set = 0; - cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; - cc->cc_data_msb = cc->cc_data_lsb = 0; - cc->cc_data_msb_set = cc->cc_data_lsb_set = 0; + reset_rpn_data(cc); } /* set up the MIDI2 RPN/NRPN packet data from the parsed info */ @@ -820,6 +829,8 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc, data->rpn.bank = cc->cc_nrpn_msb; data->rpn.index = cc->cc_nrpn_lsb; } else { + /* no RPN/NRPN selected (e.g. after a Null), drop the data */ + reset_rpn_data(cc); return 0; // skip } @@ -827,7 +838,8 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc, cc->cc_data_lsb); data->rpn.channel = channel; - reset_rpn(cc); + /* keep the RPN/NRPN selection for a following Data Entry */ + reset_rpn_data(cc); return 1; } @@ -847,6 +859,7 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, switch (index) { case UMP_CC_RPN_MSB: ret = fill_rpn(cc, data, channel, true); + cc->nrpn_set = 0; cc->rpn_set = 1; cc->cc_rpn_msb = val; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) @@ -854,6 +867,7 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, break; case UMP_CC_RPN_LSB: ret = fill_rpn(cc, data, channel, true); + cc->nrpn_set = 0; cc->rpn_set = 1; cc->cc_rpn_lsb = val; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) @@ -861,13 +875,19 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, break; case UMP_CC_NRPN_MSB: ret = fill_rpn(cc, data, channel, true); + cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_msb = val; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); break; case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, data, channel, true); + cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_lsb = val; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); break; case UMP_CC_DATA: cc->cc_data_msb_set = 1; @@ -975,6 +995,7 @@ static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event, case UMP_CC_RPN_MSB: case UMP_CC_RPN_LSB: ret = fill_rpn(cc, data, channel, true); + cc->nrpn_set = 0; cc->cc_rpn_msb = msb; cc->cc_rpn_lsb = lsb; cc->rpn_set = 1; @@ -984,9 +1005,12 @@ static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event, case UMP_CC_NRPN_MSB: case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, data, channel, true); + cc->rpn_set = 0; cc->cc_nrpn_msb = msb; cc->nrpn_set = 1; cc->cc_nrpn_lsb = lsb; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); return ret; case UMP_CC_DATA: case UMP_CC_DATA_LSB: diff --git a/sound/core/sound.c b/sound/core/sound.c index 24f40763a20c39..ba226aa7affd3c 100644 --- a/sound/core/sound.c +++ b/sound/core/sound.c @@ -115,6 +115,40 @@ void *snd_lookup_minor_data(unsigned int minor, int type) } EXPORT_SYMBOL(snd_lookup_minor_data); +/** + * snd_lookup_minor_data_file - get user data of a device and add the file + * @minor: the minor number + * @type: device type (SNDRV_DEVICE_TYPE_XXX) + * @file: the file being opened + * + * Like snd_lookup_minor_data(), but instead of taking a reference to the + * card, adds @file to the file list of the card while the minor is still + * registered, and records the user data for snd_card_file_sync(). The + * caller must call snd_card_file_remove() when it is done with the file. + * + * Return: The user data pointer if the specified device is found, %NULL if + * it is not found, or an error pointer if the file cannot be added. + */ +void *snd_lookup_minor_data_file(unsigned int minor, int type, + struct file *file) +{ + struct snd_minor *mreg; + int err; + + if (minor >= ARRAY_SIZE(snd_minors)) + return NULL; + guard(mutex)(&sound_mutex); + mreg = snd_minors[minor]; + if (!mreg || mreg->type != type || !mreg->private_data || + !mreg->card_ptr) + return NULL; + err = snd_card_file_add_data(mreg->card_ptr, file, mreg->private_data); + if (err < 0) + return ERR_PTR(err); + return mreg->private_data; +} +EXPORT_SYMBOL(snd_lookup_minor_data_file); + #ifdef CONFIG_MODULES static struct snd_minor *autoload_device(unsigned int minor) { @@ -184,6 +218,18 @@ static int snd_find_free_minor(int type, struct snd_card *card, int dev) if (type == SNDRV_DEVICE_TYPE_TIMER) return SNDRV_MINOR_TIMER; + switch (type) { + case SNDRV_DEVICE_TYPE_CONTROL: + case SNDRV_DEVICE_TYPE_HWDEP: + case SNDRV_DEVICE_TYPE_RAWMIDI: + case SNDRV_DEVICE_TYPE_PCM_PLAYBACK: + case SNDRV_DEVICE_TYPE_PCM_CAPTURE: + case SNDRV_DEVICE_TYPE_COMPRESS: + break; + default: + return -EINVAL; + } + for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { /* skip static minors still used for module auto loading */ if (SNDRV_MINOR_DEVICE(minor) == SNDRV_MINOR_CONTROL) diff --git a/sound/core/sound_oss.c b/sound/core/sound_oss.c index 7eb49f2cada5d0..2f0bc481a46d40 100644 --- a/sound/core/sound_oss.c +++ b/sound/core/sound_oss.c @@ -41,6 +41,27 @@ void *snd_lookup_oss_minor_data(unsigned int minor, int type) } EXPORT_SYMBOL(snd_lookup_oss_minor_data); +/* Like snd_lookup_minor_data_file(), for OSS minors */ +void *snd_lookup_oss_minor_data_file(unsigned int minor, int type, + struct file *file) +{ + struct snd_minor *mreg; + int err; + + if (minor >= ARRAY_SIZE(snd_oss_minors)) + return NULL; + guard(mutex)(&sound_oss_mutex); + mreg = snd_oss_minors[minor]; + if (!mreg || mreg->type != type || !mreg->private_data || + !mreg->card_ptr) + return NULL; + err = snd_card_file_add_data(mreg->card_ptr, file, mreg->private_data); + if (err < 0) + return ERR_PTR(err); + return mreg->private_data; +} +EXPORT_SYMBOL(snd_lookup_oss_minor_data_file); + static int snd_oss_kernel_minor(int type, struct snd_card *card, int dev) { int minor; diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index 0a7972d91ec52c..2f19efdc31fc28 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -69,7 +69,7 @@ static u32 upscale_14_to_32bit(u16 src) static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf) { buf[0] = ump_message_status_channel(data); - switch (ump_message_status_code(data)) { + switch (buf[0]) { case UMP_SYSTEM_STATUS_MIDI_TIME_CODE: case UMP_SYSTEM_STATUS_SONG_SELECT: buf[1] = (data >> 8) & 0x7f; @@ -288,13 +288,22 @@ static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt, return 4; } +static void reset_rpn_data(struct ump_cvt_to_ump_bank *cc) +{ + cc->cc_data_msb = cc->cc_data_lsb = 0; + cc->cc_data_msb_set = cc->cc_data_lsb_set = 0; +} + +/* + * Deselect on a Null RPN/NRPN, but keep the 7f/7f numbers as a MIDI 1.0 + * receiver does. Otherwise, when the LSB was already 127, the second + * message of a Null would select RPN or NRPN 0/127. + */ static void reset_rpn(struct ump_cvt_to_ump_bank *cc) { cc->rpn_set = 0; cc->nrpn_set = 0; - cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; - cc->cc_data_msb = cc->cc_data_lsb = 0; - cc->cc_data_msb_set = cc->cc_data_lsb_set = 0; + reset_rpn_data(cc); } static int fill_rpn(struct ump_cvt_to_ump_bank *cc, @@ -316,13 +325,16 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc, midi2->rpn.bank = cc->cc_nrpn_msb; midi2->rpn.index = cc->cc_nrpn_lsb; } else { + /* no RPN/NRPN selected (e.g. after a Null), drop the data */ + reset_rpn_data(cc); return 0; // skip } midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) | cc->cc_data_lsb); - reset_rpn(cc); + /* keep the RPN/NRPN selection for a following Data Entry */ + reset_rpn_data(cc); return 8; } @@ -378,6 +390,7 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, switch (buf[1]) { case UMP_CC_RPN_MSB: ret = fill_rpn(cc, midi2, true); + cc->nrpn_set = 0; cc->rpn_set = 1; cc->cc_rpn_msb = buf[2]; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) @@ -385,6 +398,7 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, return ret; case UMP_CC_RPN_LSB: ret = fill_rpn(cc, midi2, true); + cc->nrpn_set = 0; cc->rpn_set = 1; cc->cc_rpn_lsb = buf[2]; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) @@ -392,13 +406,19 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, return ret; case UMP_CC_NRPN_MSB: ret = fill_rpn(cc, midi2, true); + cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_msb = buf[2]; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); return ret; case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, midi2, true); + cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_lsb = buf[2]; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); return ret; case UMP_CC_DATA: cc->cc_data_msb_set = 1; diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c index 4ecd151a46c193..462766946e94ef 100644 --- a/sound/firewire/tascam/tascam-stream.c +++ b/sound/firewire/tascam/tascam-stream.c @@ -287,10 +287,10 @@ static int keep_resources(struct snd_tscm *tscm, unsigned int rate, if (stream == &tscm->tx_stream) { resources = &tscm->tx_resources; - speed = fw_parent_device(tscm->unit)->max_speed; + speed = SCODE_400; } else { resources = &tscm->rx_resources; - speed = SCODE_400; + speed = fw_parent_device(tscm->unit)->max_speed; } err = amdtp_tscm_set_parameters(stream, rate); diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index 896091c1144ffb..b3e768be8a6956 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -8416,6 +8416,10 @@ static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, struct hdac_stream *hstr = NULL; unsigned int format, stream_tag; u32 enc_words[4]; + struct { + u32 r100, r104, r400, r408, r40c, r410, rc04; + u32 ser_base[4], ser_clk[4], ser_out[4]; + } saved; __le32 *dst; int word_idx = 0; int frame_words; @@ -8501,6 +8505,24 @@ static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, /* Ensure DMA buffer writes are committed before triggering the HDA stream */ wmb(); + /* + * Save every BAR2 register the strip transport reprograms, so that the + * audio serializer/clock/routing setup done at codec init can be put + * back afterwards. Leaving them modified silences the analog outputs. + */ + saved.r100 = readl(spec->mem_base + 0x100); + saved.r104 = readl(spec->mem_base + 0x104); + saved.r400 = readl(spec->mem_base + 0x400); + saved.r408 = readl(spec->mem_base + 0x408); + saved.r40c = readl(spec->mem_base + 0x40c); + saved.r410 = readl(spec->mem_base + 0x410); + saved.rc04 = readl(spec->mem_base + 0xc04); + for (i = 0; i < 4; i++) { + saved.ser_base[i] = readl(CA0113_SERIALIZER_BASE(spec, i)); + saved.ser_clk[i] = readl(CA0113_SERIALIZER_CLK(spec, i)); + saved.ser_out[i] = readl(CA0113_SERIALIZER_OUT(spec, i)); + } + /* 2. Configure CA0113 BAR2 hardware registers */ /* Power up clocks (CtxHdb 0x141b7 / 0x14210) */ writel(readl(spec->mem_base + 0xc04) | 0x7, spec->mem_base + 0xc04); @@ -8559,8 +8581,19 @@ static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, snd_hdac_dsp_trigger(hstr, false); codec_dbg(codec, "AE5 strip: trigger complete\n"); - /* Teardown: clear 0x104 pin mux back to 0 */ - writel(0x00000000, spec->mem_base + 0x104); + /* Teardown: put back the registers exactly as the audio path left them */ + writel(saved.r104, spec->mem_base + 0x104); + for (i = 0; i < 4; i++) { + writel(saved.ser_out[i], CA0113_SERIALIZER_OUT(spec, i)); + writel(saved.ser_clk[i], CA0113_SERIALIZER_CLK(spec, i)); + writel(saved.ser_base[i], CA0113_SERIALIZER_BASE(spec, i)); + } + writel(saved.r100, spec->mem_base + 0x100); + writel(saved.r410, spec->mem_base + 0x410); + writel(saved.r40c, spec->mem_base + 0x40c); + writel(saved.r408, spec->mem_base + 0x408); + writel(saved.r400, spec->mem_base + 0x400); + writel(saved.rc04, spec->mem_base + 0xc04); snd_hdac_dsp_cleanup(hstr, &dmab); scoped_guard(spinlock_irq, &bus->reg_lock) @@ -8613,6 +8646,12 @@ static int ae5_strip_ctl_put(struct snd_kcontrol *kcontrol, } change = memcmp(spec->ae5_strip_cur_colors, colors, sizeof(spec->ae5_strip_cur_colors)) != 0; + /* + * alsactl restores this control at every card probe with the saved + * values; do not run the whole transmit routine when nothing changed. + */ + if (!change) + return 0; num_leds = spec->ae5_strip_cur_num_leds; ret = ae5_strip_send_frame(codec, colors, num_leds); if (ret < 0) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index a141c94eee13b4..88651a36dd3372 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -1193,6 +1193,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x1025, 0x1937, "Acer Aspire Go 16", CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x807C, "HP EliteBook 820 G3", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x80d6, "HP ZBook 17 G3", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x103c, 0x80FD, "HP ProBook 640 G2", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x8115, "HP Z1 Gen3", CXT_FIXUP_HP_GATE_MIC), SND_PCI_QUIRK(0x103c, 0x814f, "HP ZBook 15u G3", CXT_FIXUP_MUTE_LED_GPIO), diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c index 6120f797b45a54..12e40c382c16ae 100644 --- a/sound/hda/codecs/generic.c +++ b/sound/hda/codecs/generic.c @@ -168,6 +168,9 @@ static void parse_user_hints(struct hda_codec *codec) val = snd_hda_get_bool_hint(codec, "indep_hp"); if (val >= 0) spec->indep_hp = !!val; + val = snd_hda_get_bool_hint(codec, "indep_dig_outs"); + if (val >= 0) + spec->indep_dig_outs = !!val; val = snd_hda_get_bool_hint(codec, "add_stereo_mix_input"); if (val >= 0) spec->add_stereo_mix_input = !!val; @@ -4000,7 +4003,9 @@ static void parse_digital(struct hda_codec *codec) int i, nums; hda_nid_t dig_nid, pin; - /* support multiple SPDIFs; the secondary is set up as a follower */ + /* support multiple SPDIFs; the secondary is set up as a follower, + * or as an independent PCM with the indep_dig_outs hint + */ nums = 0; for (i = 0; i < spec->autocfg.dig_outs; i++) { pin = spec->autocfg.dig_out_pins[i]; @@ -4018,6 +4023,10 @@ static void parse_digital(struct hda_codec *codec) if (!nums) { spec->multiout.dig_out_nid = dig_nid; spec->dig_out_type = spec->autocfg.dig_out_type[0]; + } else if (spec->indep_dig_outs) { + /* autocfg gives at most two digital outs */ + spec->alt_dig_multiout.dig_out_nid = dig_nid; + spec->alt_dig_out_type = spec->autocfg.dig_out_type[i]; } else { spec->multiout.follower_dig_outs = spec->follower_dig_outs; if (nums >= ARRAY_SIZE(spec->follower_dig_outs) - 1) @@ -5228,6 +5237,14 @@ int snd_hda_gen_build_controls(struct hda_codec *codec) spec->multiout.share_spdif = 1; } } + if (spec->alt_dig_multiout.dig_out_nid) { + err = snd_hda_create_dig_out_ctls(codec, + spec->alt_dig_multiout.dig_out_nid, + spec->alt_dig_multiout.dig_out_nid, + spec->pcm_rec[3]->pcm_type); + if (err < 0) + return err; + } if (spec->dig_in_nid) { err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); if (err < 0) @@ -5324,14 +5341,12 @@ static int playback_pcm_prepare(struct hda_pcm_stream *hinfo, struct snd_pcm_substream *substream) { struct hda_gen_spec *spec = codec->spec; - int err; - err = snd_hda_multi_out_analog_prepare(codec, &spec->multiout, - stream_tag, format, substream); - if (!err) - call_pcm_playback_hook(hinfo, codec, substream, - HDA_GEN_PCM_ACT_PREPARE); - return err; + snd_hda_multi_out_analog_prepare(codec, &spec->multiout, + stream_tag, format, substream); + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_PREPARE); + return 0; } static int playback_pcm_cleanup(struct hda_pcm_stream *hinfo, @@ -5339,13 +5354,11 @@ static int playback_pcm_cleanup(struct hda_pcm_stream *hinfo, struct snd_pcm_substream *substream) { struct hda_gen_spec *spec = codec->spec; - int err; - err = snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); - if (!err) - call_pcm_playback_hook(hinfo, codec, substream, - HDA_GEN_PCM_ACT_CLEANUP); - return err; + snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_CLEANUP); + return 0; } static int playback_pcm_close(struct hda_pcm_stream *hinfo, @@ -5454,12 +5467,24 @@ static int alt_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, /* * Digital out */ + +/* the independent second digital out has its own multiout */ +static struct hda_multi_out *dig_multiout(struct hda_codec *codec, + struct hda_pcm_stream *hinfo) +{ + struct hda_gen_spec *spec = codec->spec; + + if (spec->alt_dig_multiout.dig_out_nid && + hinfo->nid == spec->alt_dig_multiout.dig_out_nid) + return &spec->alt_dig_multiout; + return &spec->multiout; +} + static int dig_playback_pcm_open(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { - struct hda_gen_spec *spec = codec->spec; - return snd_hda_multi_out_dig_open(codec, &spec->multiout); + return snd_hda_multi_out_dig_open(codec, dig_multiout(codec, hinfo)); } static int dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, @@ -5468,8 +5493,7 @@ static int dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, unsigned int format, struct snd_pcm_substream *substream) { - struct hda_gen_spec *spec = codec->spec; - return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + return snd_hda_multi_out_dig_prepare(codec, dig_multiout(codec, hinfo), stream_tag, format, substream); } @@ -5477,16 +5501,14 @@ static int dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { - struct hda_gen_spec *spec = codec->spec; - return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout); + return snd_hda_multi_out_dig_cleanup(codec, dig_multiout(codec, hinfo)); } static int dig_playback_pcm_close(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { - struct hda_gen_spec *spec = codec->spec; - return snd_hda_multi_out_dig_close(codec, &spec->multiout); + return snd_hda_multi_out_dig_close(codec, dig_multiout(codec, hinfo)); } /* @@ -5779,6 +5801,25 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec) spec->dig_in_nid); } + /* independent second digital out, stream index #3 */ + if (spec->alt_dig_multiout.dig_out_nid) { + fill_pcm_stream_name(spec->stream_name_alt_digital, + sizeof(spec->stream_name_alt_digital), + " Alt Digital", codec->core.chip_name); + info = snd_hda_codec_pcm_new(codec, "%s", + spec->stream_name_alt_digital); + if (!info) + return -ENOMEM; + spec->pcm_rec[3] = info; + if (spec->alt_dig_out_type) + info->pcm_type = spec->alt_dig_out_type; + else + info->pcm_type = HDA_PCM_TYPE_SPDIF; + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK], + &pcm_digital_playback, NULL, + spec->alt_dig_multiout.dig_out_nid); + } + if (spec->no_analog) return 0; diff --git a/sound/hda/codecs/generic.h b/sound/hda/codecs/generic.h index 524591821f8c98..e6971ff66ec18a 100644 --- a/sound/hda/codecs/generic.h +++ b/sound/hda/codecs/generic.h @@ -93,6 +93,7 @@ struct hda_gen_spec { const struct hda_pcm_stream *stream_analog_capture; char stream_name_alt_analog[32]; /* alternative analog PCM stream */ + char stream_name_alt_digital[32]; /* independent 2nd digital PCM */ const struct hda_pcm_stream *stream_analog_alt_playback; const struct hda_pcm_stream *stream_analog_alt_capture; @@ -112,6 +113,9 @@ struct hda_gen_spec { hda_nid_t alt_dac_nid; hda_nid_t follower_dig_outs[3]; /* optional - for auto-parsing */ int dig_out_type; + /* the 2nd digital out with the indep_dig_outs hint */ + struct hda_multi_out alt_dig_multiout; + int alt_dig_out_type; /* capture */ unsigned int num_adc_nids; @@ -148,7 +152,7 @@ struct hda_gen_spec { int const_channel_count; /* channel count for all */ /* PCM information */ - struct hda_pcm *pcm_rec[3]; /* used in build_pcms() */ + struct hda_pcm *pcm_rec[4]; /* used in build_pcms() */ /* dynamic controls, init_verbs and input_mux */ struct auto_pin_cfg autocfg; @@ -226,6 +230,7 @@ struct hda_gen_spec { unsigned int vmaster_mute_led:1; /* add SPK-LED flag to vmaster mute switch */ unsigned int mic_mute_led:1; /* add MIC-LED flag to capture mute switch */ unsigned int indep_hp:1; /* independent HP supported */ + unsigned int indep_dig_outs:1; /* 2nd digital out has its own PCM */ unsigned int prefer_hp_amp:1; /* enable HP amp for speaker if any */ unsigned int add_stereo_mix_input:2; /* add aamix as a capture src */ unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */ diff --git a/sound/hda/codecs/hdmi/nvhdmi.c b/sound/hda/codecs/hdmi/nvhdmi.c index 94671ad24b5ebe..e61ad1624dfba3 100644 --- a/sound/hda/codecs/hdmi/nvhdmi.c +++ b/sound/hda/codecs/hdmi/nvhdmi.c @@ -66,6 +66,102 @@ static const struct drm_audio_component_audio_ops nvhdmi_audio_ops = { .master_unbind = snd_hda_hdmi_acomp_master_unbind, }; +/* + * All pins of these codecs share one connection list, as on Intel. An idle + * pin left selecting the converter that another pin streams on takes that + * stream: the display that plays stays silent and the audio goes to the + * other display instead. Seen on GB202 (0x10de00aa) with an HDMI TV and a + * DP monitor: whenever the monitor's PCM was opened first (PipeWire probes + * every PCM at start-up), its pin kept the converter the TV got next. + * + * Move every other connected pin off the converter just assigned, to a + * converter nobody uses. + */ +static void nvhdmi_not_share_assigned_cvt(struct hda_codec *codec, + hda_nid_t pin_nid, int dev_id, + hda_nid_t cvt_nid) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin; + struct hdmi_spec_per_cvt *per_cvt; + int pin_idx, cvt_idx, mux_idx, curr, dev_id_saved; + + if (!cvt_nid) + return; + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + per_pin = get_pin(spec, pin_idx); + /* no display on this pin */ + if (!per_pin->pcm) + continue; + if (per_pin->pin_nid == pin_nid && per_pin->dev_id == dev_id) + continue; + if (per_pin->dev_id >= + snd_hda_get_num_devices(codec, per_pin->pin_nid) + 1) + continue; + + dev_id_saved = snd_hda_get_dev_select(codec, per_pin->pin_nid); + snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); + curr = snd_hda_codec_read(codec, per_pin->pin_nid, 0, + AC_VERB_GET_CONNECT_SEL, 0); + if (curr < 0 || curr >= per_pin->num_mux_nids || + per_pin->mux_nids[curr] != cvt_nid) + goto restore; + + for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) { + per_cvt = get_cvt(spec, cvt_idx); + if (per_cvt->assigned || per_cvt->cvt_nid == cvt_nid) + continue; + for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++) + if (per_pin->mux_nids[mux_idx] == per_cvt->cvt_nid) + break; + if (mux_idx == per_pin->num_mux_nids) + continue; + codec_dbg(codec, + "hdmi: move pin 0x%x off cvt 0x%x to cvt 0x%x\n", + per_pin->pin_nid, cvt_nid, per_cvt->cvt_nid); + snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, + AC_VERB_SET_CONNECT_SEL, + mux_idx); + break; + } +restore: + snd_hda_set_dev_select(codec, per_pin->pin_nid, dev_id_saved); + } +} + +/* + * The hardware can drop a pin's converter selection, e.g. on a display + * reset. snd_hda_hdmi_generic_pcm_prepare() relies on pin_cvt_fixup() to + * restore it, as i915_pin_cvt_fixup() does on Intel. + */ +static void nvhdmi_verify_pin_cvt_connect(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin) +{ + int curr; + + curr = snd_hda_codec_read(codec, per_pin->pin_nid, 0, + AC_VERB_GET_CONNECT_SEL, 0); + if (curr != per_pin->mux_idx) + snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, + AC_VERB_SET_CONNECT_SEL, + per_pin->mux_idx); +} + +static void nvhdmi_pin_cvt_fixup(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin, + hda_nid_t cvt_nid) +{ + if (per_pin) { + snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); + nvhdmi_verify_pin_cvt_connect(codec, per_pin); + nvhdmi_not_share_assigned_cvt(codec, per_pin->pin_nid, + per_pin->dev_id, per_pin->cvt_nid); + } else { + nvhdmi_not_share_assigned_cvt(codec, 0, 0, cvt_nid); + } +} + static int probe_generic(struct hda_codec *codec) { struct hdmi_spec *spec; @@ -77,6 +173,7 @@ static int probe_generic(struct hda_codec *codec) codec->dp_mst = true; spec = codec->spec; + spec->ops.pin_cvt_fixup = nvhdmi_pin_cvt_fixup; err = snd_hda_hdmi_parse_codec(codec); if (err < 0) { diff --git a/sound/hda/codecs/hdmi/tegrahdmi.c b/sound/hda/codecs/hdmi/tegrahdmi.c index 171d1ee154d9cd..6baa98a3c627e6 100644 --- a/sound/hda/codecs/hdmi/tegrahdmi.c +++ b/sound/hda/codecs/hdmi/tegrahdmi.c @@ -338,6 +338,7 @@ static const struct hda_device_id snd_hda_id_tegrahdmi[] = { HDA_CODEC_ID_MODEL(0x10de0033, "SoC 33 HDMI/DP", MODEL_TEGRA234), HDA_CODEC_ID_MODEL(0x10de0034, "Tegra264 HDMI/DP", MODEL_TEGRA234), HDA_CODEC_ID_MODEL(0x10de0035, "SoC 35 HDMI/DP", MODEL_TEGRA234), + HDA_CODEC_ID_MODEL(0x10de0037, "Tegra268 HDMI/DP", MODEL_TEGRA234), {} /* terminator */ }; MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_tegrahdmi); diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 3334e1e18422f1..06f8728f6dbe76 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4330,6 +4330,7 @@ enum { ALC255_FIXUP_DELL_HEADSET_MIC, ALC256_FIXUP_HUAWEI_MACH_WX9_PINS, ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS, + ALC256_FIXUP_HUAWEI_MACHC_WAX9_PINS, ALC298_FIXUP_HUAWEI_MBX_STEREO, ALC295_FIXUP_HP_X360, ALC295_FIXUP_HP_PAVILION_X360, @@ -4552,6 +4553,7 @@ enum { ALC256_FIXUP_IPASON_SMARTBOOK_S1, ALC256_FIXUP_ASUS_SPEAKER_COEFS, ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE, + ALC287_FIXUP_TAS2781_I2C_DAC, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -5063,6 +5065,15 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE }, + [ALC256_FIXUP_HUAWEI_MACHC_WAX9_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x04a11140 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, [ALC298_FIXUP_HUAWEI_MBX_STEREO] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_huawei_mbx_stereo, @@ -7051,6 +7062,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, + [ALC287_FIXUP_TAS2781_I2C_DAC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc295_fixup_asus_dacs, + .chained_before = true, + .chain_id = ALC287_FIXUP_TAS2781_I2C, + }, [ALC287_FIXUP_ASUS_ALLY_X] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -7594,6 +7611,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x097e, "Dell Precision", ALC289_FIXUP_DUAL_SPK), SND_PCI_QUIRK(0x1028, 0x098d, "Dell Precision", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x09bf, "Dell Precision", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x09d1, "Alienware m17 R3", ALC289_FIXUP_DUAL_SPK), SND_PCI_QUIRK(0x1028, 0x0a2e, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC), SND_PCI_QUIRK(0x1028, 0x0a30, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC), SND_PCI_QUIRK(0x1028, 0x0a38, "Dell Latitude 7520", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE_QUIET), @@ -7867,7 +7885,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8a2b, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a2c, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a2d, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8a2e, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a2e, "HP ENVY Laptop 17-cr0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8a30, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a31, "HP ENVY x360 2-in-1 Laptop 15-ey0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8a34, "HP Pavilion x360 2-in-1 Laptop 14-ek0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), @@ -8067,7 +8085,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8e36, "HP 14 Enstrom OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e37, "HP 16 Piston OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e3a, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8e3b, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3b, "HP OmniBook 7", ALC245_FIXUP_CS35L41_I2C_2_MUTE_MICMUTE_LED), SND_PCI_QUIRK(0x103c, 0x8e43, "HP Laptop 15-fd2xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), HDA_CODEC_QUIRK(0x103c, 0x84ee, "HP Pavilion AiO 24", ALC225_FIXUP_HP_AIO_SPK), SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED), @@ -8106,11 +8124,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8f3a, "HP EliteBook 6 G2i", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8f3c, "HP EliteBook 6 G2a", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8f3d, "HP EliteBook 6 G2a", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), - SND_PCI_QUIRK(0x103c, 0x8f40, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), - SND_PCI_QUIRK(0x103c, 0x8f41, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), - SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f40, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x103c, 0x8f41, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), SND_PCI_QUIRK(0x103c, 0x8f57, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), SND_PCI_QUIRK(0x103c, 0x8f7e, "HP Messi", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8f94, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), SND_PCI_QUIRK(0x103c, 0x8f95, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), @@ -8343,8 +8361,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc1cc, "Samsung Galaxy Book3 Ultra (NT960XFH)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x1458, 0x900e, "Gigabyte G5 KF5 (2023)", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), - SND_PCI_QUIRK(0x1462, 0x12fb, "MSI GE66 Raider 11UE", - ALC298_FIXUP_MSI_GE66_ALC1316_AMP), + SND_PCI_QUIRK(0x1462, 0x12fb, "MSI GE66 Raider 11UE", ALC298_FIXUP_MSI_GE66_ALC1316_AMP), + SND_PCI_QUIRK(0x1462, 0x1305, "MSI GE76 Raider 11UG", ALC298_FIXUP_MSI_GE66_ALC1316_AMP), SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1462, 0xb171, "Cubi N 8GL (MS-B171)", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x152d, 0x1082, "Quanta NL3", ALC269_FIXUP_LIFEBOOK), @@ -8558,8 +8576,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), - HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ - HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C_DAC), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C_DAC), /* this must match before PCI SSID 17aa:386f below */ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), @@ -8684,6 +8702,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1854, 0x0490, "LG Gram Style 14 (14Z90RS)", ALC298_FIXUP_LG_GRAM_STYLE_14), SND_PCI_QUIRK(0x1854, 0x0554, "LG gram 16 (16Z90TR)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), + SND_PCI_QUIRK(0x19e5, 0x320d, "Huawei WRT-WX9", ALC269_FIXUP_HEADSET_MODE), SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC), SND_PCI_QUIRK(0x19e5, 0x3e13, "Huawei MACHR-WX9", ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS), @@ -8728,6 +8747,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1e83, 0x3e21, "Huawei MACHC-WAX9", ALC256_FIXUP_HUAWEI_MACHC_WAX9_PINS), SND_PCI_QUIRK(0x1ee7, 0x204e, "HONOR DRA-XX M1020", ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS), SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index c1c7277b6754a8..a0efd4596c8807 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -284,6 +284,21 @@ static void alc662_fixup_csl_amp(struct hda_codec *codec, } } +static void alc897_fixup_asus_b650m_depop(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + alc_auto_setup_eapd(codec, false); + snd_hda_codec_write(codec, 0x1b, 0, + AC_VERB_SET_EAPD_BTLENABLE, 0); + msleep(200); + alc_auto_setup_eapd(codec, true); + snd_hda_codec_write(codec, 0x1b, 0, + AC_VERB_SET_EAPD_BTLENABLE, 2); +} + enum { ALC662_FIXUP_ASPIRE, ALC662_FIXUP_LED_GPIO1, @@ -345,6 +360,7 @@ enum { ALC662_FIXUP_CSL_GPIO, ALC897_FIXUP_BEELINK_SER6_AMP, ALC897_FIXUP_LIMIT_LINEIN_BOOST, + ALC897_FIXUP_ASUS_B650M_DEPOP, }; static const struct hda_fixup alc662_fixups[] = { @@ -810,6 +826,10 @@ static const struct hda_fixup alc662_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc897_fixup_limit_linein_boost, }, + [ALC897_FIXUP_ASUS_B650M_DEPOP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc897_fixup_asus_b650m_depop, + }, }; static const struct hda_quirk alc662_fixup_tbl[] = { @@ -858,6 +878,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1b73, "ASUS N55SF", ALC662_FIXUP_BASS_16), SND_PCI_QUIRK(0x1043, 0x1bf3, "ASUS N76VZ", ALC662_FIXUP_BASS_MODE4_CHMAP), SND_PCI_QUIRK(0x1043, 0x8469, "ASUS mobo", ALC662_FIXUP_NO_JACK_DETECT), + SND_PCI_QUIRK(0x1043, 0x87fb, "ASUS PRIME B650M-K", ALC897_FIXUP_ASUS_B650M_DEPOP), SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_FIXUP_ASUS_MODE2), SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x14cd, 0x5003, "USI", ALC662_FIXUP_USI_HEADSET_MODE), diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 1edb9b61df483f..3ff9db9c43d146 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -184,7 +184,6 @@ static const struct aw88399_prop_model aw88399_prop_model_table[] = { static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) { struct acpi_device *adev; - const char *sub; const struct aw88399_prop_model *model; aw88399->index = aw88399_hda_index_from_i2c(aw88399); @@ -202,14 +201,13 @@ static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) if (!physdev) return -ENODEV; - sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); + const char *sub __free(kfree) = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR_OR_NULL(sub)) { dev_err(aw88399->dev, "ACPI subsystem ID not available\n"); return -ENODEV; } aw88399->acpi_subsystem_id = devm_kstrdup(aw88399->dev, sub, GFP_KERNEL); - kfree(sub); if (!aw88399->acpi_subsystem_id) return -ENOMEM; @@ -356,6 +354,7 @@ const struct dev_pm_ops aw88399_hda_pm_ops = { EXPORT_SYMBOL_NS_GPL(aw88399_hda_pm_ops, "SND_HDA_SCODEC_AW88399"); MODULE_DESCRIPTION("AW88399 HDA driver"); +MODULE_IMPORT_NS("SND_SOC_AW88399_LIB"); MODULE_AUTHOR("Yakov Till "); MODULE_AUTHOR("Marco Giunta "); MODULE_LICENSE("GPL"); diff --git a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c index 186e13adbc6cf9..0b3fa75e09c78c 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c @@ -50,5 +50,7 @@ module_i2c_driver(aw88399_hda_i2c_driver); MODULE_DESCRIPTION("HDA AW88399 I2C driver"); MODULE_IMPORT_NS("SND_HDA_SCODEC_AW88399"); +MODULE_IMPORT_NS("SND_SOC_AW88399_LIB"); MODULE_AUTHOR("Yakov Till "); +MODULE_AUTHOR("Marco Giunta "); MODULE_LICENSE("GPL"); diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c index f879e3ffbb53d4..23582f23a88008 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c @@ -974,30 +974,6 @@ static int cs35l41_system_suspend(struct device *dev) return ret; } -static int cs35l41_wait_boot_done(struct cs35l41_hda *cs35l41) -{ - unsigned int int_status; - int ret; - - ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, int_status, - int_status & CS35L41_OTP_BOOT_DONE, 1000, 100000); - if (ret) { - dev_err(cs35l41->dev, "Failed waiting for OTP_BOOT_DONE\n"); - return ret; - } - - ret = regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status); - if (ret || (int_status & CS35L41_OTP_BOOT_ERR)) { - dev_err(cs35l41->dev, "OTP Boot status %x error\n", - int_status & CS35L41_OTP_BOOT_ERR); - if (!ret) - ret = -EIO; - return ret; - } - - return 0; -} - static int cs35l41_system_resume(struct device *dev) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); @@ -1020,10 +996,7 @@ static int cs35l41_system_resume(struct device *dev) regcache_cache_only(cs35l41->regmap, false); - regmap_write(cs35l41->regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); - usleep_range(2000, 2100); - - ret = cs35l41_wait_boot_done(cs35l41); + ret = cs35l41_reset_and_boot(cs35l41->dev, cs35l41->regmap); if (ret) return ret; @@ -2003,11 +1976,9 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); } - usleep_range(2000, 2100); - regmap_write(cs35l41->regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); usleep_range(2000, 2100); - ret = cs35l41_wait_boot_done(cs35l41); + ret = cs35l41_reset_and_boot(cs35l41->dev, cs35l41->regmap); if (ret) goto err; diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index c50c7e68953369..95843205828543 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -85,6 +85,7 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "103C8C51", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8CDD", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8CDE", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 }, + { "103C8E13", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, { "103C8E60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, { "103C8F07", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 }, { "104312AF", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 }, @@ -543,6 +544,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "103C8C6A", hp_i2c_int_2amp_dual_spkid }, { "CSC3551", "103C8CDD", generic_dsd_config }, { "CSC3551", "103C8CDE", generic_dsd_config }, + { "CSC3551", "103C8E13", generic_dsd_config }, { "CSC3551", "103C8E60", generic_dsd_config }, { "CSC3551", "103C8F07", generic_dsd_config }, { "CSC3551", "104312AF", generic_dsd_config }, diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c index bc207ab5b020a5..5982eebae15254 100644 --- a/sound/hda/codecs/side-codecs/cs35l56_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c @@ -562,7 +562,7 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) * downloaded previously then it needs to be powered down so that it * can be updated. */ - if (cs35l56->base.fw_patched) + if (cs35l56->cs_dsp.booted) cs_dsp_power_down(&cs35l56->cs_dsp); cs35l56->base.fw_patched = false; @@ -634,7 +634,7 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) regcache_mark_dirty(cs35l56->base.regmap); ret = cs35l56_wait_for_firmware_boot(&cs35l56->base); if (ret) - goto err_powered_up; + return; regcache_cache_only(cs35l56->base.regmap, false); } @@ -642,7 +642,7 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) /* Disable auto-hibernate so that runtime_pm has control */ ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE); if (ret) - goto err_powered_up; + return; regcache_sync(cs35l56->base.regmap); @@ -663,10 +663,6 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) cs_dsp_stop(&cs35l56->cs_dsp); cs35l56_log_tuning(&cs35l56->base, &cs35l56->cs_dsp); - -err_powered_up: - if (!cs35l56->base.fw_patched) - cs_dsp_power_down(&cs35l56->cs_dsp); } static void cs35l56_hda_dsp_work(struct work_struct *work) @@ -813,8 +809,10 @@ static void cs35l56_hda_unbind(struct device *dev, struct device *master, void * cs_dsp_cleanup_debugfs(&cs35l56->cs_dsp); debugfs_remove_recursive(cs35l56->debugfs_root); #endif + if (cs35l56->cs_dsp.running) + cs_dsp_stop(&cs35l56->cs_dsp); - if (cs35l56->base.fw_patched) + if (cs35l56->cs_dsp.booted) cs_dsp_power_down(&cs35l56->cs_dsp); comp = hda_component_from_index(parent, cs35l56->index); diff --git a/sound/hda/codecs/side-codecs/tas2781_hda.c b/sound/hda/codecs/side-codecs/tas2781_hda.c index e0da299df780c2..b9b6e44f8791af 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda.c @@ -50,6 +50,31 @@ static void cali_cnv(unsigned char *data, unsigned int base, int offset) (u32 *)®_data, TASDEV_CALIB_N); } +/* + * V1 data without CRC is only trusted if every expected device has a + * populated, self-consistent entry: R0 (Q27) * 1/R0 (Q31) must be + * close to 1.0 in Q58. + */ +static bool tas2781_v1_nocrc_valid(const unsigned int *v, int ndev) +{ + const u64 one = 1ULL << 58; + int i; + + if (ndev <= 0 || ndev > 4) + return false; + + for (i = 0; i < ndev; i++) { + const unsigned int *e = &v[i * 5]; + u64 prod = (u64)e[0] * e[1]; + + if (!e[0] || !e[1] || + prod < one - one / 10 || prod > one + one / 10) + return false; + } + + return true; +} + static void tas2781_apply_calib(struct tasdevice_priv *p) { struct calidata *cali_data = &p->cali_data; @@ -64,6 +89,7 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) TASDEVICE_REG(0, 0x18, 0x7c), }; unsigned int crc, oft, node_num; + bool custom_addr = false; unsigned char *buf; int i, j, k, l; @@ -104,6 +130,7 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) for (j = 0, k = 0; j < node_num; j++) { oft = j * 6 + 3; if (tmp_val[oft] == TASDEV_UEFI_CALI_REG_ADDR_FLG) { + custom_addr = true; for (i = 0; i < TASDEV_CALIB_N; i++) { buf = &data[(oft + i + 1) * 4]; cali_reg[i] = TASDEVICE_REG(buf[1], @@ -145,7 +172,7 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) * but leave TimeStamp and CRC zeroed. */ if (!tmp_val[20] && !tmp_val[21] && - tmp_val[0] && tmp_val[1]) { + tas2781_v1_nocrc_valid(tmp_val, p->ndev)) { dev_warn(p->dev, "%s: V1 data without CRC, accepting\n", __func__); @@ -163,7 +190,7 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) } } - if (p->dspbin_typ == TASDEV_BASIC) { + if (custom_addr || p->dspbin_typ == TASDEV_BASIC) { r->r0_reg = cali_reg[0]; r->invr0_reg = cali_reg[1]; r->r0_low_reg = cali_reg[2]; diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c index 957feace0f58ee..66949c4ff21ff7 100644 --- a/sound/hda/common/bind.c +++ b/sound/hda/common/bind.c @@ -84,6 +84,20 @@ int snd_hda_codec_set_name(struct hda_codec *codec, const char *name) } EXPORT_SYMBOL_GPL(snd_hda_codec_set_name); +/* + * A file opened on a codec PCM can use the PCM until it is closed, also + * after the PCM was disconnected. Wait for those files. + */ +static void hda_codec_sync_pcm_files(struct hda_codec *codec) +{ + struct hda_pcm *cpcm; + + list_for_each_entry(cpcm, &codec->pcm_list_head, list) { + if (cpcm->pcm) + snd_card_file_sync(codec->card, cpcm->pcm); + } +} + static int hda_codec_driver_probe(struct device *dev) { struct hda_codec *codec = dev_to_hda_codec(dev); @@ -104,6 +118,12 @@ static int hda_codec_driver_probe(struct device *dev) if (WARN_ON(!codec->preset)) return -EINVAL; + /* + * snd_card_register() below walks the device list of the card, which + * the removal of another codec changes when it frees its PCMs. + */ + mutex_lock(&codec->bus->core.codec_bind_lock); + err = snd_hda_codec_set_name(codec, codec->preset->name); if (err < 0) goto error; @@ -140,11 +160,14 @@ static int hda_codec_driver_probe(struct device *dev) } codec->core.lazy_cache = true; + mutex_unlock(&codec->bus->core.codec_bind_lock); return 0; error_module: snd_hdac_device_disable_unsol(&codec->core); cancel_delayed_work_sync(&codec->jackpoll_work); + snd_hda_codec_disconnect_pcms(codec); + hda_codec_sync_pcm_files(codec); if (probed && driver->ops->remove) driver->ops->remove(codec); error_module_put: @@ -153,6 +176,7 @@ static int hda_codec_driver_probe(struct device *dev) error: snd_hda_codec_cleanup_for_unbind(codec); codec->preset = NULL; + mutex_unlock(&codec->bus->core.codec_bind_lock); return err; } @@ -171,8 +195,10 @@ static int hda_codec_driver_remove(struct device *dev) return codec->bus->core.ext_ops->hdev_detach(&codec->core); } + guard(mutex)(&codec->bus->core.codec_bind_lock); snd_hda_codec_disconnect_pcms(codec); snd_hda_jack_tbl_disconnect(codec); + hda_codec_sync_pcm_files(codec); snd_refcount_sync(&codec->pcm_ref); snd_power_sync_ref(codec->bus->card); @@ -184,6 +210,23 @@ static int hda_codec_driver_remove(struct device *dev) return 0; } +/** + * snd_hda_card_register - register the card of an HD-audio bus + * @bus: HD-audio bus + * + * snd_card_register() walks the device list of the card, which codec driver + * probe and remove on the same bus change. Register the card with them + * excluded. + * + * Return: zero or a negative error code. + */ +int snd_hda_card_register(struct hda_bus *bus) +{ + guard(mutex)(&bus->core.codec_bind_lock); + return snd_card_register(bus->card); +} +EXPORT_SYMBOL_GPL(snd_hda_card_register); + static void hda_codec_driver_shutdown(struct device *dev) { snd_hda_codec_shutdown(dev_to_hda_codec(dev)); diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index e367cc1c6e78f2..51189e305ece67 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -3798,11 +3798,11 @@ EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open); * Set up the i/o for analog out. * When the digital out is available, copy the front out to digital out, too. */ -int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, - struct hda_multi_out *mout, - unsigned int stream_tag, - unsigned int format, - struct snd_pcm_substream *substream) +void snd_hda_multi_out_analog_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) { const hda_nid_t *nids = mout->dac_nids; int chs = substream->runtime->channels; @@ -3865,7 +3865,6 @@ int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, stream_tag, ch, format); } - return 0; } EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_prepare); @@ -3874,8 +3873,8 @@ EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_prepare); * @codec: the HDA codec * @mout: hda_multi_out object */ -int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, - struct hda_multi_out *mout) +void snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, + struct hda_multi_out *mout) { const hda_nid_t *nids = mout->dac_nids; int i; @@ -3897,7 +3896,6 @@ int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, cleanup_dig_out_stream(codec, mout->dig_out_nid); mout->dig_out_used = 0; } - return 0; } EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_cleanup); diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h index 947a3152fdb530..8bd4497a6b0909 100644 --- a/sound/hda/common/hda_local.h +++ b/sound/hda/common/hda_local.h @@ -241,13 +241,13 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, struct hda_multi_out *mout, struct snd_pcm_substream *substream, struct hda_pcm_stream *hinfo); -int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, - struct hda_multi_out *mout, - unsigned int stream_tag, - unsigned int format, - struct snd_pcm_substream *substream); -int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, - struct hda_multi_out *mout); +void snd_hda_multi_out_analog_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream); +void snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, + struct hda_multi_out *mout); /* * generic proc interface diff --git a/sound/hda/common/sysfs.c b/sound/hda/common/sysfs.c index bf65223fde10da..ac08ee6f9c10e0 100644 --- a/sound/hda/common/sysfs.c +++ b/sound/hda/common/sysfs.c @@ -144,7 +144,7 @@ static int reconfig_codec(struct hda_codec *codec) err = device_reprobe(hda_codec_dev(codec)); if (err < 0) return err; - return snd_card_register(codec->card); + return snd_hda_card_register(codec->bus); } /* diff --git a/sound/hda/controllers/acpi.c b/sound/hda/controllers/acpi.c index 04b261dde22eaf..87e5f3f14a2b92 100644 --- a/sound/hda/controllers/acpi.c +++ b/sound/hda/controllers/acpi.c @@ -166,7 +166,7 @@ static void hda_acpi_probe_work(struct work_struct *work) if (err < 0) return; - err = snd_card_register(chip->card); + err = snd_hda_card_register(&chip->bus); if (err < 0) return; diff --git a/sound/hda/controllers/cix-ipbloq.c b/sound/hda/controllers/cix-ipbloq.c index c1084a915c1363..c193dc1428c24d 100644 --- a/sound/hda/controllers/cix-ipbloq.c +++ b/sound/hda/controllers/cix-ipbloq.c @@ -80,7 +80,7 @@ static int cix_ipbloq_hda_probe_codec(struct cix_ipbloq_hda *hda) return err; } - err = snd_card_register(chip->card); + err = snd_hda_card_register(&chip->bus); if (err < 0) { dev_err(hda->dev, "card register failed: %d\n", err); return err; diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 29679fe7f40132..0a62723b345496 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -2434,7 +2434,7 @@ static int azx_probe_continue(struct azx *chip, bool free_on_error) } } - err = snd_card_register(chip->card); + err = snd_hda_card_register(&chip->bus); if (err < 0) goto out_free; diff --git a/sound/hda/controllers/tegra.c b/sound/hda/controllers/tegra.c index 674cc0f79c3c88..d601aa3db8b414 100644 --- a/sound/hda/controllers/tegra.c +++ b/sound/hda/controllers/tegra.c @@ -607,7 +607,7 @@ static void hda_tegra_probe_work(struct work_struct *work) if (err < 0) return; - err = snd_card_register(chip->card); + err = snd_hda_card_register(&chip->bus); if (err < 0) return; diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 02462bd035e731..304110c9a6a70a 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -44,6 +44,7 @@ int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev, spin_lock_init(&bus->reg_lock); mutex_init(&bus->cmd_mutex); mutex_init(&bus->lock); + mutex_init(&bus->codec_bind_lock); INIT_LIST_HEAD(&bus->hlink_list); init_waitqueue_head(&bus->rirb_wq); bus->irq = -1; diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index a7acd3418ae8a2..3929dd19501868 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -445,6 +445,13 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus) break; } + /* An all-zero header means no capability is present here */ + if (!cur_cap) { + dev_dbg(bus->dev, "Empty capability header at offset 0x%x\n", + offset); + break; + } + switch ((cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF) { case AZX_ML_CAP_ID: dev_dbg(bus->dev, "Found ML capability\n"); @@ -475,7 +482,8 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus) break; default: - dev_err(bus->dev, "Unknown capability %d\n", cur_cap); + dev_err(bus->dev, "Unknown capability ID 0x%x\n", + (cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF); cur_cap = 0; break; } @@ -764,10 +772,13 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) error_posbuf: snd_dma_free_pages(&bus->posbuf); + bus->posbuf.area = NULL; error_bdl: list_for_each_entry(s, &bus->stream_list, list) { - if (s->bdl.area) + if (s->bdl.area) { snd_dma_free_pages(&s->bdl); + s->bdl.area = NULL; + } } return -ENOMEM; } diff --git a/sound/i2c/cs8427.c b/sound/i2c/cs8427.c index 1de985ba85d9bf..773383471f17f3 100644 --- a/sound/i2c/cs8427.c +++ b/sound/i2c/cs8427.c @@ -424,9 +424,11 @@ static int snd_cs8427_spdif_get(struct snd_kcontrol *kcontrol, { struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol); struct cs8427 *chip = device->private_data; + const char *status = kcontrol->private_value ? + chip->playback.pcm_status : chip->playback.def_status; snd_i2c_lock(device->bus); - memcpy(ucontrol->value.iec958.status, chip->playback.def_status, 24); + memcpy(ucontrol->value.iec958.status, status, 24); snd_i2c_unlock(device->bus); return 0; } diff --git a/sound/isa/cs423x/cs4236.c b/sound/isa/cs423x/cs4236.c index ecce31b348b021..167a198fa0fb9c 100644 --- a/sound/isa/cs423x/cs4236.c +++ b/sound/isa/cs423x/cs4236.c @@ -236,7 +236,7 @@ static int snd_cs423x_pnp_init_ctrl(int dev, struct pnp_dev *pdev) } /* MPU initialization */ -static int snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev) +static void snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev) { if (pnp_activate_dev(pdev) < 0) { dev_err(&pdev->dev, IDENT " MPU401 PnP configure failed for WSS (out of resources?)\n"); @@ -253,7 +253,6 @@ static int snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev) } } dev_dbg(&pdev->dev, "isapnp MPU: port=0x%lx, irq=%i\n", mpu_port[dev], mpu_irq[dev]); - return 0; } static int snd_card_cs423x_pnp(int dev, struct snd_card_cs4236 *acard, @@ -296,10 +295,8 @@ static int snd_card_cs423x_pnpc(int dev, struct snd_card_cs4236 *acard, return -EBUSY; } /* MPU initialization */ - if (acard->mpu && mpu_port[dev] > 0) { - if (snd_cs423x_pnp_init_mpu(dev, acard->mpu) < 0) - return -EBUSY; - } + if (acard->mpu && mpu_port[dev] > 0) + snd_cs423x_pnp_init_mpu(dev, acard->mpu); return 0; } #endif /* CONFIG_PNP */ diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index 8295eee9b977f9..b86b9212964271 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -414,7 +414,7 @@ static unsigned short snd_ali_codec_read(struct snd_ac97 *ac97, * AC97 Reset */ -static int snd_ali_reset_5451(struct snd_ali *codec) +static void snd_ali_reset_5451(struct snd_ali *codec) { struct pci_dev *pci_dev; unsigned short wCount, wReg; @@ -442,13 +442,12 @@ static int snd_ali_reset_5451(struct snd_ali *codec) while(wCount--) { wReg = snd_ali_codec_peek(codec, 0, AC97_POWERDOWN); if ((wReg & 0x000f) == 0x000f) - return 0; + return; mdelay(5); } /* non-fatal if you have a non PM capable codec */ /* dev_warn(codec->card->dev, "ali5451: reset time out\n"); */ - return 0; } /* @@ -1901,10 +1900,7 @@ static int snd_ali_chip_init(struct snd_ali *codec) dev_dbg(codec->card->dev, "chip initializing ...\n"); - if (snd_ali_reset_5451(codec)) { - dev_err(codec->card->dev, "ali_chip_init: reset 5451 error.\n"); - return -1; - } + snd_ali_reset_5451(codec); if (codec->revision == ALI_5451_V02) { pci_dev = codec->pci_m1533; diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c index a77e6f5a14ada1..57b0a6321e0b6b 100644 --- a/sound/pci/cs46xx/dsp_spos.c +++ b/sound/pci/cs46xx/dsp_spos.c @@ -1104,6 +1104,8 @@ int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip) null_scb.entry_point = null_algorithm->address; ins->the_null_scb = cs46xx_dsp_create_scb(chip, "nullSCB", (u32 *)&null_scb, NULL_SCB_ADDR); + if (!ins->the_null_scb) + return -ENOMEM; ins->the_null_scb->task_entry = null_algorithm; ins->the_null_scb->sub_list_ptr = ins->the_null_scb; ins->the_null_scb->next_scb_ptr = ins->the_null_scb; diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c index 71eee5cb478f40..ac914bf784c816 100644 --- a/sound/pci/cs46xx/dsp_spos_scb_lib.c +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -274,7 +274,8 @@ _dsp_create_generic_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, u dev_dbg(chip->card->dev, "dsp_spos: creating SCB <%s>\n", name); scb = cs46xx_dsp_create_scb(chip,name,scb_data,dest); - + if (!scb) + return NULL; scb->sub_list_ptr = ins->the_null_scb; scb->next_scb_ptr = ins->the_null_scb; diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c index 26f5d5528e27f3..e3c88d19017088 100644 --- a/sound/pci/ice1712/ews.c +++ b/sound/pci/ice1712/ews.c @@ -32,6 +32,8 @@ enum { /* additional i2c devices for EWS boards */ struct ews_spec { struct snd_i2c_device *i2cdevs[3]; + unsigned char pcf8575_data; /* PHASE 88: shadow of the chip select byte */ + unsigned char pcf8575_input; /* PHASE 88: shadow of the input select byte */ }; /* @@ -128,12 +130,94 @@ static struct snd_i2c_bit_ops snd_ice1712_ewx_cs8427_bit_ops = { * AK4524 access */ +/* The PHASE 88 chip-select expander is write-only; keep a shadow value. */ +/* The bit-banged bus takes gpio_mutex internally; don't hold it here. */ +static int snd_ice1712_phase88_write(struct snd_ice1712 *ice, + unsigned char page, unsigned char value) +{ + struct ews_spec *spec = ice->spec; + unsigned char bytes[2]; + int err; + + bytes[0] = page; + bytes[1] = value; + snd_i2c_lock(ice->i2c); + err = snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2], bytes, 2); + snd_i2c_unlock(ice->i2c); + if (err != 2) { + dev_err(ice->card->dev, + "PHASE 88 expander write failed (page 0x%02x, addr 0x%02x, err %d)\n", + page, spec->i2cdevs[EWS_I2C_PCF2]->addr, err); + return -EIO; + } + return 0; +} + +static int snd_ice1712_phase88_chip_select(struct snd_ice1712 *ice, int chip_mask) +{ + struct ews_spec *spec = ice->spec; + unsigned char data; + int err; + + if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f)) + return -EINVAL; + data = (spec->pcf8575_data & 0xf0) | chip_mask; + err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_OUTPUT_PAGE, + data); + if (err >= 0) + spec->pcf8575_data = data; + return err; +} + +/* PHASE 88 Line/Mic bits are ordered 7/8 to 1/2 (0x10 to 0x80). */ +static int snd_ice1712_phase88_linemic_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + + ucontrol->value.integer.value[0] = + !!(spec->pcf8575_input & (0x80 >> kcontrol->private_value)); + return 0; +} + +static int snd_ice1712_phase88_linemic_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + unsigned char bit = 0x80 >> kcontrol->private_value; + unsigned char val = ucontrol->value.integer.value[0] ? + spec->pcf8575_input | bit : spec->pcf8575_input & ~bit; + int err; + + if (val == spec->pcf8575_input) + return 0; + err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_INPUT_PAGE, val); + if (err < 0) + return err; + spec->pcf8575_input = val; + return 1; +} + +static const struct snd_kcontrol_new snd_ice1712_phase88_linemic_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line/Mic 7/8 Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_ice1712_phase88_linemic_get, + .put = snd_ice1712_phase88_linemic_put, + .private_value = 3, /* bit 0x10: the pair nothing else drives */ +}; + /* AK4524 chip select; address 0x48 bit 0-3 */ static int snd_ice1712_ews88mt_chip_select(struct snd_ice1712 *ice, int chip_mask) { struct ews_spec *spec = ice->spec; unsigned char data, ndata; + if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_PHASE88) + return snd_ice1712_phase88_chip_select(ice, chip_mask); + if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f)) return -EINVAL; snd_i2c_lock(ice->i2c); @@ -458,9 +542,31 @@ static int snd_ice1712_ews_init(struct snd_ice1712 *ice) } snd_ice1712_6fire_write_pca(ice, PCF9554_REG_CONFIG, 0x80); break; + case ICE1712_SUBDEVICE_PHASE88: + err = snd_i2c_device_create(ice->i2c, "CS8404", + ICE1712_EWS88MT_CS8404_ADDR, + &spec->i2cdevs[EWS_I2C_CS8404]); + if (err < 0) + return err; + err = snd_i2c_device_create(ice->i2c, "Output expander", + ICE1712_PHASE88_OUTPUT_ADDR, + &spec->i2cdevs[EWS_I2C_PCF2]); + if (err < 0) + return err; + spec->pcf8575_data = ICE1712_PHASE88_OUTPUT_INIT; + /* Check if the chip select expander is present */ + err = snd_ice1712_ews88mt_chip_select(ice, 0x0f); + if (err < 0) + return err; + /* Clear the input register before codec setup. */ + spec->pcf8575_input = ICE1712_PHASE88_INPUT_INIT; + err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_INPUT_PAGE, + spec->pcf8575_input); + if (err < 0) + return err; + break; case ICE1712_SUBDEVICE_EWS88MT: case ICE1712_SUBDEVICE_EWS88MT_NEW: - case ICE1712_SUBDEVICE_PHASE88: case ICE1712_SUBDEVICE_TS88: err = snd_i2c_device_create(ice->i2c, "CS8404", @@ -982,9 +1088,15 @@ static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice) return err; } break; + case ICE1712_SUBDEVICE_PHASE88: + /* The PHASE 88 has no EWS88MT sensitivity controls. */ + err = snd_ctl_add(ice->card, + snd_ctl_new1(&snd_ice1712_phase88_linemic_ctl, ice)); + if (err < 0) + return err; + break; case ICE1712_SUBDEVICE_EWS88MT: case ICE1712_SUBDEVICE_EWS88MT_NEW: - case ICE1712_SUBDEVICE_PHASE88: case ICE1712_SUBDEVICE_TS88: err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88mt_input_sense, ice)); if (err < 0) @@ -1012,6 +1124,27 @@ static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice) } +static int snd_ice1712_ews_eeprom_init(struct snd_ice1712 *ice) +{ + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DMX6FIRE: + if (!ice->dxr_enable) { + /* Limit active ADCs and DACs to 6, and disable AC'97 + * as the standard 6fire card/box has no hardware for it. + */ + ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a; + } + break; + case ICE1712_SUBDEVICE_PHASE88: + /* No consumer AC'97 codec on PHASE 88. */ + ice->eeprom.data[ICE_EEP1_CODEC] |= ICE1712_CFG_NO_CON_AC97; + /* Enable the rear MIDI UART. */ + ice->eeprom.data[ICE_EEP1_CODEC] |= ICE1712_CFG_2xMPU401; + break; + } + return 0; +} + /* entry point */ struct snd_ice1712_card_info snd_ice1712_ews_cards[] = { { @@ -1039,13 +1172,14 @@ struct snd_ice1712_card_info snd_ice1712_ews_cards[] = { .subvendor = ICE1712_SUBDEVICE_PHASE88, .name = "TerraTec Phase88", .model = "phase88", + .eeprom_init = snd_ice1712_ews_eeprom_init, .chip_init = snd_ice1712_ews_init, .build_controls = snd_ice1712_ews_add_controls, }, { .subvendor = ICE1712_SUBDEVICE_TS88, .name = "terrasoniq TS88", - .model = "phase88", + .model = "ts88", .chip_init = snd_ice1712_ews_init, .build_controls = snd_ice1712_ews_add_controls, }, @@ -1060,6 +1194,7 @@ struct snd_ice1712_card_info snd_ice1712_ews_cards[] = { .subvendor = ICE1712_SUBDEVICE_DMX6FIRE, .name = "TerraTec DMX6Fire", .model = "dmx6fire", + .eeprom_init = snd_ice1712_ews_eeprom_init, .chip_init = snd_ice1712_ews_init, .build_controls = snd_ice1712_ews_add_controls, .mpu401_1_name = "MIDI-Front DMX6fire", diff --git a/sound/pci/ice1712/ews.h b/sound/pci/ice1712/ews.h index aec8dc69a9eadf..064675adf80191 100644 --- a/sound/pci/ice1712/ews.h +++ b/sound/pci/ice1712/ews.h @@ -58,6 +58,13 @@ extern struct snd_ice1712_card_info snd_ice1712_ews_cards[]; #define ICE1712_EWS88MT_OUTPUT_SENSE 0x40 /* mask */ #define ICE1712_EWS88D_PCF_ADDR (0x40>>1) +/* TerraTec PHASE 88 configuration definitions */ +#define ICE1712_PHASE88_OUTPUT_ADDR (0x42>>1) +#define ICE1712_PHASE88_OUTPUT_PAGE 0x01 /* output register */ +#define ICE1712_PHASE88_OUTPUT_INIT 0x20 /* initial output state */ +#define ICE1712_PHASE88_INPUT_PAGE 0x03 /* input register */ +#define ICE1712_PHASE88_INPUT_INIT 0x00 /* line inputs */ + /* TerraTec DMX 6Fire configuration definitions */ #define ICE1712_6FIRE_AK4524_CS_MASK 0x07 /* AK4524 chip select #1-#3 */ #define ICE1712_6FIRE_RW 0x08 /* read/write switch for i2c; high = write */ diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index 1fad8c418132c9..2d4c9bdfb62f2a 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -2283,19 +2283,28 @@ static int snd_ice1712_read_eeprom(struct snd_ice1712 *ice, -static int snd_ice1712_chip_init(struct snd_ice1712 *ice) +static int snd_ice1712_init_eeprom(struct snd_ice1712 *ice) +{ + const struct snd_ice1712_card_info * const *tbl, *c; + + for (tbl = card_tables; *tbl; tbl++) { + for (c = *tbl; c->subvendor; c++) { + if (c->subvendor != ice->eeprom.subvendor) + continue; + if (c->eeprom_init) + return c->eeprom_init(ice); + return 0; + } + } + return 0; +} + +static void snd_ice1712_chip_init(struct snd_ice1712 *ice) { outb(ICE1712_RESET | ICE1712_NATIVE, ICEREG(ice, CONTROL)); udelay(200); outb(ICE1712_NATIVE, ICEREG(ice, CONTROL)); udelay(200); - if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE && - !ice->dxr_enable) - /* Set eeprom value to limit active ADCs and DACs to 6; - * Also disable AC97 as no hardware in standard 6fire card/box - * Note: DXR extensions are not currently supported - */ - ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a; pci_write_config_byte(ice->pci, 0x60, ice->eeprom.data[ICE_EEP1_CODEC]); pci_write_config_byte(ice->pci, 0x61, ice->eeprom.data[ICE_EEP1_ACLINK]); pci_write_config_byte(ice->pci, 0x62, ice->eeprom.data[ICE_EEP1_I2SID]); @@ -2334,8 +2343,6 @@ static int snd_ice1712_chip_init(struct snd_ice1712 *ice) ICE1712_IRQ_PBKDS | ICE1712_IRQ_CONCAP | ICE1712_IRQ_CONPBK : 0), ICEREG(ice, IRQMASK)); outb(0x00, ICEMT(ice, IRQ)); - - return 0; } int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice) @@ -2502,10 +2509,13 @@ static int snd_ice1712_create(struct snd_card *card, card->sync_irq = ice->irq; card->private_free = snd_ice1712_free; - if (snd_ice1712_read_eeprom(ice, modelname) < 0) - return -EIO; - if (snd_ice1712_chip_init(ice) < 0) - return -EIO; + err = snd_ice1712_read_eeprom(ice, modelname); + if (err < 0) + return err; + err = snd_ice1712_init_eeprom(ice); + if (err < 0) + return err; + snd_ice1712_chip_init(ice); return 0; } @@ -2683,10 +2693,7 @@ static int snd_ice1712_resume(struct device *dev) else rate = PRO_RATE_DEFAULT; - if (snd_ice1712_chip_init(ice) < 0) { - snd_card_disconnect(card); - return -EIO; - } + snd_ice1712_chip_init(ice); ice->cur_rate = rate; diff --git a/sound/pci/ice1712/ice1712.h b/sound/pci/ice1712/ice1712.h index cd02710d827153..6e9c13b9a5282d 100644 --- a/sound/pci/ice1712/ice1712.h +++ b/sound/pci/ice1712/ice1712.h @@ -509,6 +509,7 @@ struct snd_ice1712_card_info { const char *name; const char *model; const char *driver; + int (*eeprom_init)(struct snd_ice1712 *ice); int (*chip_init)(struct snd_ice1712 *); void (*chip_exit)(struct snd_ice1712 *); int (*build_controls)(struct snd_ice1712 *); diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 651d9266356751..d4ed7050c533ae 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -2342,7 +2342,7 @@ static void snd_vt1724_chip_reset(struct snd_ice1712 *ice) msleep(10); } -static int snd_vt1724_chip_init(struct snd_ice1712 *ice) +static void snd_vt1724_chip_init(struct snd_ice1712 *ice) { outb(ice->eeprom.data[ICE_EEP2_SYSCONF], ICEREG1724(ice, SYS_CFG)); outb(ice->eeprom.data[ICE_EEP2_ACLINK], ICEREG1724(ice, AC97_CFG)); @@ -2364,8 +2364,6 @@ static int snd_vt1724_chip_init(struct snd_ice1712 *ice) * since they cause machine lockups */ outb(VT1724_MULTI_FIFO_ERR, ICEMT1724(ice, DMA_INT_MASK)); - - return 0; } static int snd_vt1724_spdif_build_controls(struct snd_ice1712 *ice) @@ -2511,8 +2509,7 @@ static int snd_vt1724_create(struct snd_card *card, snd_vt1724_chip_reset(ice); if (snd_vt1724_read_eeprom(ice, modelname) < 0) return -EIO; - if (snd_vt1724_chip_init(ice) < 0) - return -EIO; + snd_vt1724_chip_init(ice); return 0; } @@ -2703,10 +2700,7 @@ static int snd_vt1724_resume(struct device *dev) snd_vt1724_chip_reset(ice); - if (snd_vt1724_chip_init(ice) < 0) { - snd_card_disconnect(card); - return -EIO; - } + snd_vt1724_chip_init(ice); if (ice->pm_resume) ice->pm_resume(ice); diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c index 339024182a26eb..69fe4274775000 100644 --- a/sound/pci/intel8x0.c +++ b/sound/pci/intel8x0.c @@ -2289,10 +2289,11 @@ static int snd_intel8x0_mixer(struct intel8x0 *chip, int ac97_clock, pbus->isdin = 1; /* find the available PCM streams */ i = ARRAY_SIZE(ac97_pcm_defs); - if (chip->device_type != DEVICE_INTEL_ICH4) + if (chip->device_type != DEVICE_INTEL_ICH4) { i -= 2; /* do not allocate PCM2IN and MIC2 */ - if (chip->spdif_idx < 0) - i--; /* do not allocate S/PDIF */ + if (chip->spdif_idx < 0) + i--; /* do not allocate S/PDIF */ + } err = snd_ac97_pcm_assign(pbus, i, ac97_pcm_defs); if (err < 0) goto __err; diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index 7dce548b9f363e..00295930ce6f6f 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -478,6 +478,7 @@ struct hdsp { pid_t capture_pid; pid_t playback_pid; + unsigned int setup_streams; /* 1 << stream after hw_params */ int running; int system_sample_rate; const signed char *channel_map; @@ -1095,7 +1096,40 @@ static void hdsp_set_dds_value(struct hdsp *hdsp, int rate) hdsp_write(hdsp, HDSP_freqReg, hdsp->dds_value); } -static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally) +/* + * A speed mode change moves the channels to different DMA buffer slots + * (hdsp->channel_map) and changes the channel count. It is only safe if + * no substream other than the one asking for it (if any) was set up with + * the old layout. A substream of another process is already turned away + * by snd_hdsp_hw_params(). + * + * hdsp->setup_streams is used rather than the other substream's runtime + * state, because the PCM core only moves a substream to SETUP after + * snd_hdsp_hw_params() has returned and dropped hdsp->lock. It is read + * and updated under hdsp->lock. + */ +static bool hdsp_speed_change_blocked(struct hdsp *hdsp, + struct snd_pcm_substream *substream) +{ + int other; + + if (!substream) + return hdsp->capture_pid >= 0 || hdsp->playback_pid >= 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + other = SNDRV_PCM_STREAM_CAPTURE; + else + other = SNDRV_PCM_STREAM_PLAYBACK; + + return hdsp->setup_streams & (1 << other); +} + +/* + * substream is the stream asking for the rate in hw_params, or NULL for a + * request from a control or from card initialization. + */ +static int hdsp_set_rate(struct hdsp *hdsp, int rate, + struct snd_pcm_substream *substream) { int reject_if_open = 0; int current_rate; @@ -1107,7 +1141,7 @@ static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally) */ if (!(hdsp->control_register & HDSP_ClockModeMaster)) { - if (called_internally) { + if (!substream) { /* request from ctl or card initialization */ dev_err(hdsp->card->dev, "device is not running as a clock master: cannot set sample rate.\n"); @@ -1134,7 +1168,7 @@ static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally) current_rate = hdsp->system_sample_rate; /* Changing from a "single speed" to a "double speed" rate is - not allowed if any substreams are open. This is because + not allowed while another substream is set up. This is because such a change causes a shift in the location of the DMA buffers and a reduction in the number of available buffers. @@ -1196,7 +1230,7 @@ static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally) return -EINVAL; } - if (reject_if_open && (hdsp->capture_pid >= 0 || hdsp->playback_pid >= 0)) { + if (reject_if_open && hdsp_speed_change_blocked(hdsp, substream)) { dev_warn(hdsp->card->dev, "cannot change speed mode (capture PID = %d, playback PID = %d)\n", hdsp->capture_pid, @@ -1347,6 +1381,10 @@ static int snd_hdsp_midi_input_read (struct hdsp_midi *hmidi) } } hmidi->pending = 0; + } + + /* control_register is shared by all users, serialize on hdsp->lock */ + scoped_guard(spinlock_irqsave, &hmidi->hdsp->lock) { if (hmidi->id) hmidi->hdsp->control_register |= HDSP_Midi1InterruptEnable; else @@ -1939,7 +1977,7 @@ static int hdsp_set_clock_source(struct hdsp *hdsp, int mode) switch (mode) { case HDSP_CLOCK_SOURCE_AUTOSYNC: if (hdsp_external_sample_rate(hdsp) != 0) { - if (!hdsp_set_rate(hdsp, hdsp_external_sample_rate(hdsp), 1)) { + if (!hdsp_set_rate(hdsp, hdsp_external_sample_rate(hdsp), NULL)) { hdsp->control_register &= ~HDSP_ClockModeMaster; hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); return 0; @@ -1978,7 +2016,7 @@ static int hdsp_set_clock_source(struct hdsp *hdsp, int mode) } hdsp->control_register |= HDSP_ClockModeMaster; hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); - hdsp_set_rate(hdsp, rate, 1); + hdsp_set_rate(hdsp, rate, NULL); return 0; } @@ -3815,7 +3853,7 @@ static int snd_hdsp_set_defaults(struct hdsp *hdsp) /* set a default rate so that the channel map is set up. */ - hdsp_set_rate(hdsp, 48000, 1); + hdsp_set_rate(hdsp, 48000, NULL); return 0; } @@ -3869,8 +3907,10 @@ static irqreturn_t snd_hdsp_interrupt(int irq, void *dev_id) if (midi0 && midi0status) { if (hdsp->use_midi_work) { /* we disable interrupts for this input until processing is done */ - hdsp->control_register &= ~HDSP_Midi0InterruptEnable; - hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + scoped_guard(spinlock, &hdsp->lock) { + hdsp->control_register &= ~HDSP_Midi0InterruptEnable; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + } hdsp->midi[0].pending = 1; schedule = 1; } else { @@ -3880,8 +3920,10 @@ static irqreturn_t snd_hdsp_interrupt(int irq, void *dev_id) if (hdsp->io_type != Multiface && hdsp->io_type != RPM && hdsp->io_type != H9632 && midi1 && midi1status) { if (hdsp->use_midi_work) { /* we disable interrupts for this input until processing is done */ - hdsp->control_register &= ~HDSP_Midi1InterruptEnable; - hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + scoped_guard(spinlock, &hdsp->lock) { + hdsp->control_register &= ~HDSP_Midi1InterruptEnable; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + } hdsp->midi[1].pending = 1; schedule = 1; } else { @@ -4040,6 +4082,7 @@ static int snd_hdsp_hw_params(struct snd_pcm_substream *substream, /* We're fine. */ + hdsp->setup_streams |= 1 << substream->stream; return 0; } @@ -4048,7 +4091,7 @@ static int snd_hdsp_hw_params(struct snd_pcm_substream *substream, */ if (! hdsp->clock_source_locked) { - err = hdsp_set_rate(hdsp, params_rate(params), 0); + err = hdsp_set_rate(hdsp, params_rate(params), substream); if (err < 0) { _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); return err; @@ -4061,6 +4104,16 @@ static int snd_hdsp_hw_params(struct snd_pcm_substream *substream, return err; } + hdsp->setup_streams |= 1 << substream->stream; + return 0; +} + +static int snd_hdsp_hw_free(struct snd_pcm_substream *substream) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&hdsp->lock); + hdsp->setup_streams &= ~(1 << substream->stream); return 0; } @@ -4471,6 +4524,7 @@ static int snd_hdsp_playback_release(struct snd_pcm_substream *substream) scoped_guard(spinlock_irq, &hdsp->lock) { hdsp->playback_pid = -1; hdsp->playback_substream = NULL; + hdsp->setup_streams &= ~(1 << SNDRV_PCM_STREAM_PLAYBACK); } if (RPM != hdsp->io_type) { @@ -4533,6 +4587,7 @@ static int snd_hdsp_capture_release(struct snd_pcm_substream *substream) hdsp->capture_pid = -1; hdsp->capture_substream = NULL; + hdsp->setup_streams &= ~(1 << SNDRV_PCM_STREAM_CAPTURE); return 0; } @@ -4854,6 +4909,7 @@ static const struct snd_pcm_ops snd_hdsp_playback_ops = { .close = snd_hdsp_playback_release, .ioctl = snd_hdsp_ioctl, .hw_params = snd_hdsp_hw_params, + .hw_free = snd_hdsp_hw_free, .prepare = snd_hdsp_prepare, .trigger = snd_hdsp_trigger, .pointer = snd_hdsp_hw_pointer, @@ -4866,6 +4922,7 @@ static const struct snd_pcm_ops snd_hdsp_capture_ops = { .close = snd_hdsp_capture_release, .ioctl = snd_hdsp_ioctl, .hw_params = snd_hdsp_hw_params, + .hw_free = snd_hdsp_hw_free, .prepare = snd_hdsp_prepare, .trigger = snd_hdsp_trigger, .pointer = snd_hdsp_hw_pointer, diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c index cc4cf43e5a5b6a..135761278556c7 100644 --- a/sound/pci/rme9652/hdspm.c +++ b/sound/pci/rme9652/hdspm.c @@ -5383,9 +5383,11 @@ static irqreturn_t snd_hdspm_interrupt(int irq, void *dev_id) /* we disable interrupts for this input until * processing is done */ - hdspm->control_register &= ~hdspm->midi[i].ie; - hdspm_write(hdspm, HDSPM_controlRegister, - hdspm->control_register); + scoped_guard(spinlock, &hdspm->lock) { + hdspm->control_register &= ~hdspm->midi[i].ie; + hdspm_write(hdspm, HDSPM_controlRegister, + hdspm->control_register); + } hdspm->midi[i].pending = 1; schedule = 1; } diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c index 40f3716c478f49..348666b422f5c6 100644 --- a/sound/pci/vx222/vx222_ops.c +++ b/sound/pci/vx222/vx222_ops.c @@ -321,7 +321,8 @@ static void vx2_dma_read(struct vx_core *chip, struct snd_pcm_runtime *runtime, /* * transfer counts bits to PLX */ -static int put_xilinx_data(struct vx_core *chip, unsigned int port, unsigned int counts, unsigned char data) +static void put_xilinx_data(struct vx_core *chip, unsigned int port, + unsigned int counts, unsigned char data) { unsigned int i; @@ -347,7 +348,6 @@ static int put_xilinx_data(struct vx_core *chip, unsigned int port, unsigned int vx2_inl(chip, port); udelay(1); } - return 0; } /* @@ -374,8 +374,7 @@ static int vx2_load_xilinx_binary(struct vx_core *chip, const struct firmware *x image = xilinx->data; for (i = 0; i < xilinx->size; i++, image++) { - if (put_xilinx_data(chip, port, 8, *image) < 0) - return -EINVAL; + put_xilinx_data(chip, port, 8, *image); /* don't take too much time in this loop... */ cond_resched(); } diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index edfdcbf734fe69..d9b746ffd701ca 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -108,6 +108,7 @@ source "sound/soc/apple/Kconfig" source "sound/soc/atmel/Kconfig" source "sound/soc/au1x/Kconfig" source "sound/soc/bcm/Kconfig" +source "sound/soc/cdns/Kconfig" source "sound/soc/cirrus/Kconfig" source "sound/soc/dwc/Kconfig" source "sound/soc/fsl/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 21d8406767fcbc..ba3cf6eb937aa8 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_SND_SOC) += amd/ obj-$(CONFIG_SND_SOC) += atmel/ obj-$(CONFIG_SND_SOC) += au1x/ obj-$(CONFIG_SND_SOC) += bcm/ +obj-$(CONFIG_SND_SOC) += cdns/ obj-$(CONFIG_SND_SOC) += cirrus/ obj-$(CONFIG_SND_SOC) += dwc/ obj-$(CONFIG_SND_SOC) += fsl/ diff --git a/sound/soc/amd/acp/Makefile b/sound/soc/amd/acp/Makefile index ab5f9dc871a677..42e2bd34da0608 100644 --- a/sound/soc/amd/acp/Makefile +++ b/sound/soc/amd/acp/Makefile @@ -23,7 +23,7 @@ snd-acp70-y := acp70.o snd-acp-mach-y := acp-mach-common.o snd-acp-legacy-mach-y := acp-legacy-mach.o acp3x-es83xx/acp3x-es83xx.o snd-acp-sof-mach-y := acp-sof-mach.o -snd-soc-acpi-amd-match-y := amd-acp63-acpi-match.o amd-acp70-acpi-match.o +snd-soc-acpi-amd-match-y := amd-acp63-acpi-match.o amd-acp70-acpi-match.o amd-acp7x-acpi-match.o snd-soc-acpi-amd-match-$(CONFIG_SND_SOC_ACPI_AMD_SDCA_QUIRKS) += soc-acpi-amd-sdca-quirks.o snd-acp-sdw-mach-y := acp-sdw-mach-common.o snd-acp-sdw-sof-mach-y += acp-sdw-sof-mach.o diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index a8b62817b2344e..bd03789c784954 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -590,15 +590,19 @@ static int mc_probe(struct platform_device *pdev) card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, " cfg-amp:%d", amp_num); - if (!card->components) + if (!card->components) { + asoc_sdw_mc_dailink_exit_loop(card); return -ENOMEM; + } if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s mic:acp-dmic cfg-mics:%d", card->components, 1); - if (!card->components) + if (!card->components) { + asoc_sdw_mc_dailink_exit_loop(card); return -ENOMEM; + } } /* Register the card */ diff --git a/sound/soc/amd/acp/acp-sdw-mach-common.c b/sound/soc/amd/acp/acp-sdw-mach-common.c index e5f394dc2f4c02..40184674dc8060 100644 --- a/sound/soc/amd/acp/acp-sdw-mach-common.c +++ b/sound/soc/amd/acp/acp-sdw-mach-common.c @@ -93,6 +93,54 @@ int get_acp70_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct dev } EXPORT_SYMBOL_NS_GPL(get_acp70_cpu_pin_id, "SND_SOC_AMD_SDW_MACH"); +/* + * On ACP7x, each SoundWire data port has a dedicated DMA channel + * hardwired in hardware. This function selects only the ports required + * by the currently supported ACP7x machine topologies: + * AUDIO0_TX (0) - headset/jack playback + * AUDIO4_TX (4) - amplifier/speaker playback + * AUDIO0_RX (11) - headset/jack capture + * AUDIO4_RX (15) - amplifier/speaker capture (feedback) + * AUDIO5_RX (16) - DMIC capture + * The remaining port IDs are defined to reflect the full hardware + * data port layout. + */ +int get_acp7x_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct device *dev) +{ + switch (sdw_link_id) { + case AMD_SDW0: + case AMD_SDW1: + case AMD_SDW2: + case AMD_SDW3: + switch (be_id) { + case SOC_SDW_JACK_OUT_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO0_TX; + break; + case SOC_SDW_JACK_IN_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO0_RX; + break; + case SOC_SDW_AMP_OUT_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO4_TX; + break; + case SOC_SDW_AMP_IN_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO4_RX; + break; + case SOC_SDW_DMIC_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO5_RX; + break; + default: + dev_err(dev, "Invalid be id:%d\n", be_id); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + dev_dbg(dev, "sdw_link_id:%d, be_id:%d, cpu_pin_id:%d\n", sdw_link_id, be_id, *cpu_pin_id); + return 0; +} +EXPORT_SYMBOL_NS_GPL(get_acp7x_cpu_pin_id, "SND_SOC_AMD_SDW_MACH"); + MODULE_DESCRIPTION("AMD SoundWire Common Machine driver"); MODULE_AUTHOR("Vijendar Mukunda "); MODULE_LICENSE("GPL"); diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index ec3e1f5f1052c5..1d7487c8726575 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -28,6 +28,8 @@ static void log_quirks(struct device *dev) SOC_JACK_JDSRC(sof_sdw_quirk)); if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC) dev_dbg(dev, "quirk SOC_SDW_ACP_DMIC enabled\n"); + if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC1) + dev_dbg(dev, "quirk ASOC_SDW_ACP_DMIC1 enabled\n"); } static int sof_sdw_quirk_cb(const struct dmi_system_id *id) @@ -145,6 +147,13 @@ static int create_sdw_dailink(struct snd_soc_card *card, if (ret) return ret; break; + case ACP7B_PCI_REV: + case ACP7F_PCI_REV: + ret = get_acp7x_cpu_pin_id(ffs(sof_end->link_mask) - 1, + *be_id, &cpu_pin_id, dev); + if (ret) + return ret; + break; default: return -EINVAL; } @@ -258,11 +267,31 @@ static int create_dmic_dailinks(struct snd_soc_card *card, struct snd_soc_dai_link **dai_links, int *be_id, int no_pcm) { struct device *dev = card->dev; + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct amd_mc_ctx *amd_ctx = (struct amd_mc_ctx *)ctx->private; + const char *cpu_dai_name; int ret; + /* + * For ACP7.B/7.F platforms the PDM controller is selected from ACPI + * acp-audio-ep-port and stored in amd_ctx->pdm_sel. Use the + * matching SOF DAI name ("acp-sof-dmic0" or "acp-sof-dmic1"). + * All other platforms use the generic "acp-sof-dmic" DAI. + */ + switch (amd_ctx->acp_rev) { + case ACP7B_PCI_REV: + case ACP7F_PCI_REV: + cpu_dai_name = (amd_ctx->pdm_sel == ACP7X_PDM_DMIC1) ? + "acp-sof-dmic1" : "acp-sof-dmic0"; + break; + default: + cpu_dai_name = "acp-sof-dmic"; + break; + } + ret = asoc_sdw_init_simple_dai_link(dev, *dai_links, be_id, "acp-dmic-codec", 0, 1, // DMIC only supports capture - "acp-sof-dmic", platform_component->name, + cpu_dai_name, platform_component->name, "dmic-codec", "dmic-hifi", no_pcm, asoc_sdw_dmic_init, NULL); if (ret) @@ -279,6 +308,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) struct snd_soc_acpi_mach *mach = dev_get_platdata(card->dev); int sdw_be_num = 0, dmic_num = 0; struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct amd_mc_ctx *amd_ctx = (struct amd_mc_ctx *)ctx->private; struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; struct snd_soc_aux_dev *sof_aux; struct snd_soc_codec_conf *codec_conf; @@ -321,8 +351,13 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) sdw_be_num = ret; /* enable dmic */ - if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC || mach_params->dmic_num) - dmic_num = 1; + if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC || mach_params->dmic_num || + amd_ctx->pdm_sel) { + if (ctx->ignore_internal_dmic) + dev_dbg(dev, "ACP DMIC suppressed by SoundWire DMIC\n"); + else + dmic_num = 1; + } dev_dbg(dev, "sdw %d, dmic %d", sdw_be_num, dmic_num); @@ -353,13 +388,9 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) /* dmic */ if (dmic_num > 0) { - if (ctx->ignore_internal_dmic) { - dev_warn(dev, "Ignoring ACP DMIC\n"); - } else { - ret = create_dmic_dailinks(card, &dai_links, &be_id, 1); - if (ret) - return ret; - } + ret = create_dmic_dailinks(card, &dai_links, &be_id, 1); + if (ret) + return ret; } WARN_ON(codec_conf != card->codec_conf + card->num_configs); @@ -371,10 +402,11 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) static int mc_probe(struct platform_device *pdev) { struct snd_soc_acpi_mach *mach = dev_get_platdata(&pdev->dev); + struct amd_pdm_pdata *pdm_pdata; struct snd_soc_card *card; struct amd_mc_ctx *amd_ctx; struct asoc_sdw_mc_private *ctx; - int amp_num = 0, i; + int amp_num = 0, i, dmic_num; int ret; amd_ctx = devm_kzalloc(&pdev->dev, sizeof(*amd_ctx), GFP_KERNEL); @@ -382,7 +414,19 @@ static int mc_probe(struct platform_device *pdev) return -ENOMEM; amd_ctx->acp_rev = mach->mach_params.subsystem_rev; - amd_ctx->max_sdw_links = ACP63_SDW_MAX_LINKS; + switch (amd_ctx->acp_rev) { + case ACP7B_PCI_REV: + case ACP7F_PCI_REV: + amd_ctx->max_sdw_links = ACP7X_SDW_MAX_LINKS; + break; + default: + amd_ctx->max_sdw_links = ACP63_SDW_MAX_LINKS; + break; + } + if (mach->pdata) { + pdm_pdata = mach->pdata; + amd_ctx->pdm_sel = pdm_pdata->pdm_sel; + } ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; @@ -396,6 +440,11 @@ static int mc_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(card, ctx); + if (mach->mach_params.subsystem_id_set) + snd_soc_card_set_pci_ssid(card, + mach->mach_params.subsystem_vendor, + mach->mach_params.subsystem_device); + dmi_check_system(sof_sdw_quirk_table); if (quirk_override != -1) { @@ -404,6 +453,23 @@ static int mc_probe(struct platform_device *pdev) sof_sdw_quirk = quirk_override; } + /* + * If ACPI did not set pdm_sel (no acp-audio-ep-port in _DSD), derive + * it from the quirk bits. ACPI takes priority: quirks are only + * consulted when pdm_sel is still zero. PDM controller selection is + * only meaningful on ACP7B/7F platforms. + * ASOC_SDW_ACP_DMIC selects DMIC0 (default); ASOC_SDW_ACP_DMIC1 + * overrides to DMIC1. + */ + if (!amd_ctx->pdm_sel && + (amd_ctx->acp_rev == ACP7B_PCI_REV || + amd_ctx->acp_rev == ACP7F_PCI_REV)) { + if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC1) + amd_ctx->pdm_sel = ACP7X_PDM_DMIC1; + else if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC) + amd_ctx->pdm_sel = ACP7X_PDM_DMIC0; + } + log_quirks(&pdev->dev); ctx->mc_quirk = sof_sdw_quirk; @@ -425,8 +491,23 @@ static int mc_probe(struct platform_device *pdev) card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, " cfg-amp:%d", amp_num); - if (!card->components) + if (!card->components) { + asoc_sdw_mc_dailink_exit_loop(card); return -ENOMEM; + } + + if ((sof_sdw_quirk & ASOC_SDW_ACP_DMIC || mach->mach_params.dmic_num || + amd_ctx->pdm_sel) && !ctx->ignore_internal_dmic) { + dmic_num = mach->mach_params.dmic_num ? mach->mach_params.dmic_num : 1; + card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:acp-dmic cfg-mics:%d", + card->components, + dmic_num); + if (!card->components) { + asoc_sdw_mc_dailink_exit_loop(card); + return -ENOMEM; + } + } /* Register the card */ ret = devm_snd_soc_register_card(&pdev->dev, card); @@ -467,7 +548,7 @@ static struct platform_driver sof_sdw_driver = { module_platform_driver(sof_sdw_driver); MODULE_DESCRIPTION("ASoC AMD SoundWire Generic Machine driver"); -MODULE_AUTHOR("Vijendar Mukunda "); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS("SND_SOC_SDW_UTILS"); MODULE_IMPORT_NS("SND_SOC_AMD_SDW_MACH"); diff --git a/sound/soc/amd/acp/amd-acp7x-acpi-match.c b/sound/soc/amd/acp/amd-acp7x-acpi-match.c new file mode 100644 index 00000000000000..bf11ceb7478b52 --- /dev/null +++ b/sound/soc/amd/acp/amd-acp7x-acpi-match.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * amd-acp7x-acpi-match.c - tables and support for ACP7.x(7.B/7.F) platforms + * ACPI enumeration. + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include "../mach-config.h" + +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0 +}; + +static const struct snd_soc_acpi_endpoint jack_dmic_endpoints[] = { + /* Jack Endpoint */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* DMIC Endpoint */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +static const struct snd_soc_acpi_endpoint jack_amp_dmic_endpoints[] = { + /* Jack Endpoint */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp Endpoint */ + { + .num = 1, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* DMIC Endpoint */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +static const struct snd_soc_acpi_adr_device rt721_l0_jack_dmic_adr[] = { + { + .adr = 0x000030025d072101ull, + .num_endpoints = ARRAY_SIZE(jack_dmic_endpoints), + .endpoints = jack_dmic_endpoints, + .name_prefix = "rt721" + } +}; + +static const struct snd_soc_acpi_adr_device rt721_l0_jack_amp_dmic_adr[] = { + { + .adr = 0x000030025d072101ull, + .num_endpoints = ARRAY_SIZE(jack_amp_dmic_endpoints), + .endpoints = jack_amp_dmic_endpoints, + .name_prefix = "rt721" + } +}; + +static const struct snd_soc_acpi_link_adr acp7x_rt721_only[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt721_l0_jack_amp_dmic_adr), + .adr_d = rt721_l0_jack_amp_dmic_adr, + }, + {} +}; + +static const struct snd_soc_acpi_adr_device rt722_l0_jack_amp_dmic_adr[] = { + { + .adr = 0x000030025d072201ull, + .num_endpoints = ARRAY_SIZE(jack_amp_dmic_endpoints), + .endpoints = jack_amp_dmic_endpoints, + .name_prefix = "rt722" + } +}; + +static const struct snd_soc_acpi_link_adr acp7x_rt722_only[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt722_l0_jack_amp_dmic_adr), + .adr_d = rt722_l0_jack_amp_dmic_adr, + }, + {} +}; + +static const struct snd_soc_acpi_adr_device rt1320_l1_single_adr[] = { + { + .adr = 0x000130025d132001ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1320-1" + }, +}; + +static const struct snd_soc_acpi_adr_device rt1320_l3_single_adr[] = { + { + .adr = 0x000330025d132001ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1320-1" + }, +}; + +static const struct snd_soc_acpi_link_adr acp7x_rt721_l0_rt1320_l1[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt721_l0_jack_dmic_adr), + .adr_d = rt721_l0_jack_dmic_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1320_l1_single_adr), + .adr_d = rt1320_l1_single_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr acp7x_rt721_l0_rt1320_l3[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt721_l0_jack_dmic_adr), + .adr_d = rt721_l0_jack_dmic_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt1320_l3_single_adr), + .adr_d = rt1320_l3_single_adr, + }, + {} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_sdw_machines[] = { + { + .link_mask = BIT(0) | BIT(3), + .links = acp7x_rt721_l0_rt1320_l3, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7b-rt721-l0-rt1320-l3.tplg", + .fw_filename = "sof-acp7b.ri", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7b_sof_sdw_machines); + +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_sdw_machines[] = { + { + .link_mask = BIT(0) | BIT(3), + .links = acp7x_rt721_l0_rt1320_l3, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7f-rt721-l0-rt1320-l3.tplg", + .fw_filename = "sof-acp7f.ri", + }, + { + .link_mask = BIT(0) | BIT(1), + .links = acp7x_rt721_l0_rt1320_l1, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7f-rt721-l0-rt1320-l1.tplg", + .fw_filename = "sof-acp7f.ri", + }, + { + .link_mask = BIT(0), + .links = acp7x_rt721_only, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7f-rt721-l0.tplg", + .fw_filename = "sof-acp7f.ri", + }, + { + .link_mask = BIT(0), + .links = acp7x_rt722_only, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7f-rt722-l0.tplg", + .fw_filename = "sof-acp7f.ri", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7f_sof_sdw_machines); + +MODULE_DESCRIPTION("AMD ACP7.x SoundWire machine tables for ACPI enumeration"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vijendar Mukunda "); diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 034c9ab6e4ea67..21b059b7462671 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -15,22 +15,34 @@ #include #include "acp-mach-common.h" -#define ACP63_SDW_MAX_CPU_DAIS 8 #define ACP63_SDW_MAX_LINKS 2 +#define ACP7X_SDW_MAX_LINKS 4 #define AMD_SDW_MAX_GROUPS 9 #define ACP63_PCI_REV 0x63 #define ACP70_PCI_REV 0x70 #define ACP71_PCI_REV 0x71 #define ACP72_PCI_REV 0x72 +#define ACP7B_PCI_REV 0x7b +#define ACP7F_PCI_REV 0x7f #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) -#define ASOC_SDW_ACP_DMIC BIT(5) +/* + * ASOC_SDW_ACP_DMIC is a generic DMIC-enable flag used across all ACP + * platforms. On ACP7.B/7.F it additionally identifies the PDM0 controller; + * ASOC_SDW_ACP_DMIC1 selects PDM1 on ACP7.B/7.F instead. Both ACP7.B/7.F + * controllers support a 2-channel stereo capture path, and BIOS wires exactly + * one per platform. + */ +#define ASOC_SDW_ACP_DMIC BIT(5) /* enable DMIC; PDM0 on ACP7.B/7.F */ +#define ASOC_SDW_ACP_DMIC1 BIT(6) /* PDM1 / DMIC1, ACP7.B/7.F only */ #define ASOC_SDW_CODEC_SPKR BIT(15) #define AMD_SDW0 0 #define AMD_SDW1 1 +#define AMD_SDW2 2 +#define AMD_SDW3 3 #define ACP63_SW0_AUDIO0_TX 0 #define ACP63_SW0_AUDIO1_TX 1 #define ACP63_SW0_AUDIO2_TX 2 @@ -52,12 +64,50 @@ #define ACP70_SW_AUDIO1_RX 4 #define ACP70_SW_AUDIO2_RX 5 +/* + * ACP7.B/7.F SoundWire CPU pin IDs. + * + * ACP7x hardware exposes 11 TX and 11 RX data ports per SoundWire + * instance, each with a dedicated DMA channel hardwired in hardware. + * The CPU pin IDs use a flat linear numbering shared across all four + * SoundWire links (SW0-SW3): TX ports are numbered 0-10 and RX ports + * are numbered 11-21. All port IDs are defined here to represent the + * ACP7x hardware data port layout. The ID numbering differs from + * ACP70 due to hardware differences between ACP70 and ACP7x. + */ +#define ACP7X_SW_AUDIO0_TX 0 +#define ACP7X_SW_AUDIO1_TX 1 +#define ACP7X_SW_AUDIO2_TX 2 +#define ACP7X_SW_AUDIO3_TX 3 +#define ACP7X_SW_AUDIO4_TX 4 +#define ACP7X_SW_AUDIO5_TX 5 +#define ACP7X_SW_AUDIO6_TX 6 +#define ACP7X_SW_AUDIO7_TX 7 +#define ACP7X_SW_AUDIO8_TX 8 +#define ACP7X_SW_AUDIO9_TX 9 +#define ACP7X_SW_AUDIO10_TX 10 + +#define ACP7X_SW_AUDIO0_RX 11 +#define ACP7X_SW_AUDIO1_RX 12 +#define ACP7X_SW_AUDIO2_RX 13 +#define ACP7X_SW_AUDIO3_RX 14 +#define ACP7X_SW_AUDIO4_RX 15 +#define ACP7X_SW_AUDIO5_RX 16 +#define ACP7X_SW_AUDIO6_RX 17 +#define ACP7X_SW_AUDIO7_RX 18 +#define ACP7X_SW_AUDIO8_RX 19 +#define ACP7X_SW_AUDIO9_RX 20 +#define ACP7X_SW_AUDIO10_RX 21 + struct amd_mc_ctx { unsigned int acp_rev; unsigned int max_sdw_links; + /* PDM controller selection, copied from amd_pdm_pdata at probe time */ + unsigned int pdm_sel; }; int get_acp63_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct device *dev); int get_acp70_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct device *dev); +int get_acp7x_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct device *dev); #endif diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h index 02ce044bcbf722..09ea9f5259557c 100644 --- a/sound/soc/amd/mach-config.h +++ b/sound/soc/amd/mach-config.h @@ -29,7 +29,9 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_sdw_machines[]; struct config_entry { u32 flags; diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 4908f4cc147a34..7ab48e3629be8b 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -87,13 +87,6 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "21D0"), } }, - { - .driver_data = &acp6x_card, - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_NAME, "21D0"), - } - }, { .driver_data = &acp6x_card, .matches = { @@ -899,13 +892,6 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_BOARD_NAME, "BM1403CDA"), } }, - { - .driver_data = &acp6x_card, - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_BOARD_NAME, "BM1403CDA"), - } - }, { .driver_data = &acp6x_card, .matches = { diff --git a/sound/soc/cdns/Kconfig b/sound/soc/cdns/Kconfig new file mode 100644 index 00000000000000..d0ef3ced06b2b8 --- /dev/null +++ b/sound/soc/cdns/Kconfig @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0 + +config SND_SOC_CDNS_I2S_SC + tristate "Cadence I2S-SC controller support" + depends on ARCH_CIX || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for the Cadence I2S-SC + controller found in the CIX SKY1 audio subsystem. It provides + full-duplex stereo playback and capture in standard I2S and + TDM modes with up to 16 slots. + +config SND_SOC_CDNS_I2S_MC + tristate "Cadence I2S-MC controller support" + depends on ARCH_CIX || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for the Cadence I2S-MC + multi-channel controller found in the CIX SKY1 audio subsystem. + It provides multi-channel playback and capture through a + configurable number of I2S data pins. diff --git a/sound/soc/cdns/Makefile b/sound/soc/cdns/Makefile new file mode 100644 index 00000000000000..e440bb2bb5ca44 --- /dev/null +++ b/sound/soc/cdns/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +snd-soc-cdns-i2s-sc-y := cdns-i2s-sc.o +obj-$(CONFIG_SND_SOC_CDNS_I2S_SC) += snd-soc-cdns-i2s-sc.o + +snd-soc-cdns-i2s-mc-y := cdns-i2s-mc.o +obj-$(CONFIG_SND_SOC_CDNS_I2S_MC) += snd-soc-cdns-i2s-mc.o diff --git a/sound/soc/cdns/cdns-i2s-mc.c b/sound/soc/cdns/cdns-i2s-mc.c new file mode 100644 index 00000000000000..5bbac98101449d --- /dev/null +++ b/sound/soc/cdns/cdns-i2s-mc.c @@ -0,0 +1,1152 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2026 Cix Technology Group Co., Ltd. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Special Function Registers */ +/* Control Register */ +#define I2S_CTRL 0x00 +#define I2S_CTRL_RSYNC_LOOP_BACK BIT(28) +#define I2S_CTRL_TSYNC_LOOP_BACK BIT(27) +#define I2S_CTRL_RSYNC_RST BIT(26) +#define I2S_CTRL_TSYNC_RST BIT(25) +#define I2S_CTRL_RFIFO_RST BIT(24) +#define I2S_CTRL_TFIFO_RST BIT(23) +#define I2S_CTRL_R_MS BIT(22) +#define I2S_CTRL_T_MS BIT(21) +#define I2S_CTRL_SFR_RST BIT(20) +#define I2S_CTRL_LOOP_BACK_6_7 BIT(19) +#define I2S_CTRL_LOOP_BACK_4_5 BIT(18) +#define I2S_CTRL_LOOP_BACK_2_3 BIT(17) +#define I2S_CTRL_LOOP_BACK_0_1 BIT(16) +#define I2S_CTRL_TR_CFG GENMASK(15, 8) +#define I2S_CTRL_I2S_EN GENMASK(7, 0) + +/* Interrupt Status Register */ +#define I2S_INTR_STAT 0x04 +#define I2S_INTR_STAT_RFIFO_AFULL BIT(15) +#define I2S_INTR_STAT_RFIFO_FULL BIT(14) +#define I2S_INTR_STAT_RFIFO_AEMPTY BIT(13) +#define I2S_INTR_STAT_RFIFO_EMPTY BIT(12) +#define I2S_INTR_STAT_TFIFO_AFULL BIT(11) +#define I2S_INTR_STAT_TFIFO_FULL BIT(10) +#define I2S_INTR_STAT_TFIFO_AEMPTY BIT(9) +#define I2S_INTR_STAT_TFIFO_EMPTY BIT(8) +#define I2S_INTR_STAT_OVERR_CODE GENMASK(7, 5) +#define I2S_INTR_STAT_RDATA_OVERR BIT(4) +#define I2S_INTR_STAT_UNDERR_CODE GENMASK(3, 1) +#define I2S_INTR_STAT_TDATA_UNDERR BIT(0) + +/* Sample Rate And Resolution Control Register */ +#define I2S_SRR 0x08 +#define I2S_SRR_RRESOLUTION GENMASK(31, 27) +#define I2S_SRR_RSAMPLE_RATE GENMASK(26, 16) +#define I2S_SRR_TRESOLUTION GENMASK(15, 11) +#define I2S_SRR_TSAMPLE_RATE GENMASK(10, 0) + +/* Clock Strobes And Interrupt Masks Control Register */ +#define I2S_CID_CTRL 0x0c +#define I2S_CID_CTRL_RFIFO_AFULL_MASK BIT(31) +#define I2S_CID_CTRL_RFIFO_FULL_MASK BIT(30) +#define I2S_CID_CTRL_RFIFO_AEMPTY_MASK BIT(29) +#define I2S_CID_CTRL_RFIFO_EMPTY_MASK BIT(28) +#define I2S_CID_CTRL_TFIFO_AFULL_MASK BIT(27) +#define I2S_CID_CTRL_TFIFO_FULL_MASK BIT(26) +#define I2S_CID_CTRL_TFIFO_AEMPTY_MASK BIT(25) +#define I2S_CID_CTRL_TFIFO_EMPTY_MASK BIT(24) +#define I2S_CID_CTRL_I2S_MASK GENMASK(23, 16) +#define I2S_CID_CTRL_INTREQ_MASK BIT(15) +#define I2S_CID_CTRL_STROBE_RS BIT(9) +#define I2S_CID_CTRL_STROBE_TS BIT(8) +#define I2S_CID_CTRL_I2S_STROBE GENMASK(7, 0) + +/* Transmit FIFO Level Status Register, read only */ +#define I2S_TFIFO_STAT 0x10 + +/* Receive FIFO Level Status Register, read only */ +#define I2S_RFIFO_STAT 0x14 + +/* Transmit FIFO Thresholds Control Register */ +#define I2S_TFIFO_CTRL 0x18 +#define I2S_TFIFO_CTRL_TAFULL_THRESHOLD GENMASK(31, 16) +#define I2S_TFIFO_CTRL_TAEMPTY_THRESHOLD GENMASK(15, 0) + +/* Receive FIFO Thresholds Control Register */ +#define I2S_RFIFO_CTRL 0x1c +#define I2S_RFIFO_CTRL_RAFULL_THRESHOLD GENMASK(31, 16) +#define I2S_RFIFO_CTRL_RAEMPTY_THRESHOLD GENMASK(15, 0) + +/* Device Configuration Register */ +#define I2S_DEV_CONF 0x20 +#define I2S_DEV_CONF_REC_WS_DSP_MODE BIT(11) +#define I2S_DEV_CONF_REC_DATA_WS_DEL BIT(10) +#define I2S_DEV_CONF_REC_I2S_ALIGN_LR BIT(9) +#define I2S_DEV_CONF_REC_APB_ALIGN_LR BIT(8) +#define I2S_DEV_CONF_REC_WS_POLAR BIT(7) +#define I2S_DEV_CONF_REC_SCK_POLAR BIT(6) +#define I2S_DEV_CONF_TRAN_WS_DSP_MODE BIT(5) +#define I2S_DEV_CONF_TRAN_DATA_WS_DEL BIT(4) +#define I2S_DEV_CONF_TRAN_I2S_ALIGN_LR BIT(3) +#define I2S_DEV_CONF_TRAN_APB_ALIGN_LR BIT(2) +#define I2S_DEV_CONF_TRAN_WS_POLAR BIT(1) +#define I2S_DEV_CONF_TRAN_SCK_POLAR BIT(0) + +/* Status Register, read only */ +#define I2S_POLL_STAT 0x24 +#define I2S_POLL_STAT_RX_OVERRUN BIT(6) +#define I2S_POLL_STAT_RX_AFULL BIT(5) +#define I2S_POLL_STAT_RX_FULL BIT(4) +#define I2S_POLL_STAT_TX_UNDERRUN BIT(2) +#define I2S_POLL_STAT_TX_AEMPTY BIT(1) +#define I2S_POLL_STAT_TX_EMPTY BIT(0) + +/* Transmit And Recevie FIFOs Address */ +#define I2S_FIFO_ADDRESS 0x3c + +/* + * The I2S functional clock must run at a minimum ratio above the audio + * bit clock to safely cross clock domains; a bit clock sourced + * externally (slave mode) needs one more cycle of margin. See the + * controller reference manual, section 3.3 "Clocks and Clock Domain + * Crossing". + */ +#define I2S_CLK_RATIO_MIN_MASTER 6 +#define I2S_CLK_RATIO_MIN_SLAVE 7 + +#define DRV_NAME "cdns-i2s-mc" + +enum { + AUDIO_CLK0, + AUDIO_CLK2, + AUDIO_CLK_NUM, +}; + +static const char *cdns_i2s_mc_clk_pll_names[AUDIO_CLK_NUM] = { + [AUDIO_CLK0] = "audio-clk0", + [AUDIO_CLK2] = "audio-clk2", +}; + +enum { + I2S_MC_AIF1, + I2S_MC_AIF2, +}; + +struct cdns_i2s_mc_devtype_data { + u32 data_width; + u32 rfifo_aempty_threshold; + u32 rfifo_afull_threshold; + u32 tfifo_aempty_threshold; + u32 tfifo_afull_threshold; +}; + +struct cdns_i2s_mc_priv { + struct device *dev; + struct regmap *regmap; + struct reset_control *i2s_rst; + + int irq; + + struct clk *clk_hst; + struct clk *clk_i2s; + struct clk *clks[AUDIO_CLK_NUM]; + + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + + const struct cdns_i2s_mc_devtype_data *devtype_data; + + u8 pin_out_num; + u8 pin_rx_mask; + u8 pin_rx_mask_adjust; + u8 pin_tx_mask; + u8 pin_tx_mask_adjust; +}; + +static int cdns_i2s_mc_clks_enable(struct cdns_i2s_mc_priv *i2s_mc_priv) +{ + int ret; + + ret = clk_prepare_enable(i2s_mc_priv->clk_hst); + if (ret) + return ret; + + ret = clk_prepare_enable(i2s_mc_priv->clk_i2s); + if (ret) + clk_disable_unprepare(i2s_mc_priv->clk_hst); + + return ret; +} + +static void cdns_i2s_mc_clks_disable(struct cdns_i2s_mc_priv *i2s_mc_priv) +{ + clk_disable_unprepare(i2s_mc_priv->clk_i2s); + clk_disable_unprepare(i2s_mc_priv->clk_hst); +} + +static void cdns_i2s_mc_adjust_pin_config(u8 *pin_mask, u32 slots) +{ + u8 mask = 0, num = 0; + int i; + + /* + * Wired-out pins may sit at any index among the 8 data pins, so + * scan the whole mask and keep the lowest pins until enough slots + * are covered. + */ + for (i = 0; i < BITS_PER_BYTE; i++) { + if (*pin_mask & (0x1 << i)) { + mask |= (0x1 << i); + if (++num == slots / 2) { + *pin_mask = mask; + break; + } + } + } +} + +static void cdns_i2s_mc_rxtx_config(struct cdns_i2s_mc_priv *i2s_mc_priv, bool on, + u8 pin_mask, u32 strobe, u32 sync_rst) +{ + u32 irq_mask = 0, clk_mask = 0, i2s_mask = 0; + + irq_mask |= FIELD_PREP(I2S_CID_CTRL_I2S_MASK, pin_mask); + + clk_mask |= FIELD_PREP(I2S_CID_CTRL_I2S_STROBE, pin_mask) | strobe; + + i2s_mask |= FIELD_PREP(I2S_CTRL_I2S_EN, pin_mask); + + if (on) { + /* Data underrun/overrun interrupt unmask */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, irq_mask, irq_mask); + + /* Data pins clock enable */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, clk_mask, 0); + + /* + * Transmitter or receiver enable + * Synchronizing unit out of reset + */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + i2s_mask | sync_rst, + i2s_mask | sync_rst); + } else { + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + i2s_mask | sync_rst, 0); + + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, clk_mask, clk_mask); + + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, irq_mask, 0); + } +} + +static void cdns_i2s_mc_tx_config(struct cdns_i2s_mc_priv *i2s_mc_priv, bool on) +{ + cdns_i2s_mc_rxtx_config(i2s_mc_priv, on, i2s_mc_priv->pin_tx_mask_adjust, + I2S_CID_CTRL_STROBE_TS, I2S_CTRL_TSYNC_RST); +} + +static void cdns_i2s_mc_rx_config(struct cdns_i2s_mc_priv *i2s_mc_priv, bool on) +{ + cdns_i2s_mc_rxtx_config(i2s_mc_priv, on, i2s_mc_priv->pin_rx_mask_adjust, + I2S_CID_CTRL_STROBE_RS, I2S_CTRL_RSYNC_RST); +} + +static irqreturn_t cdns_i2s_mc_isr(int irq, void *devid) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = (struct cdns_i2s_mc_priv *)devid; + struct device *dev = i2s_mc_priv->dev; + u32 stat; + + if (regmap_read(i2s_mc_priv->regmap, I2S_INTR_STAT, &stat)) + return IRQ_NONE; + + stat &= I2S_INTR_STAT_TDATA_UNDERR | I2S_INTR_STAT_RDATA_OVERR; + if (!stat) + return IRQ_NONE; + + /* Clear the status */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_INTR_STAT, stat, 0); + + /* Transmitter status */ + if (stat & I2S_INTR_STAT_TDATA_UNDERR) + dev_dbg(dev, "isr: tx data underrun\n"); + + /* Receiver status */ + if (stat & I2S_INTR_STAT_RDATA_OVERR) + dev_dbg(dev, "isr: rx data overrun\n"); + + return IRQ_HANDLED; +} + +static int cdns_i2s_mc_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_mc_priv->dev; + u32 ctrl = 0, dev_conf = 0; + + dev_dbg(dev, "fmt = 0x%x\n", fmt); + + /* + * Configure the data pin channel directions, shared by both DAIs: + * a set TR_CFG bit marks the pin as a transmitter channel, a clear + * one as a receiver channel. Program the combined value so the + * write is idempotent and independent of the set_fmt call order. + */ + ctrl |= FIELD_PREP(I2S_CTRL_TR_CFG, + i2s_mc_priv->pin_tx_mask | + (u8)~i2s_mc_priv->pin_rx_mask); + + if (cpu_dai->id == I2S_MC_AIF1) { + /* DAI hardware signal polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* both normal clocks */ + dev_conf |= I2S_DEV_CONF_TRAN_SCK_POLAR; + dev_conf &= ~I2S_DEV_CONF_TRAN_WS_POLAR; + break; + case SND_SOC_DAIFMT_NB_IF: + /* invert frame clock */ + dev_conf |= I2S_DEV_CONF_TRAN_SCK_POLAR; + dev_conf |= I2S_DEV_CONF_TRAN_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_NF: + /* invert bit clock */ + dev_conf &= ~I2S_DEV_CONF_TRAN_SCK_POLAR; + dev_conf &= ~I2S_DEV_CONF_TRAN_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_IF: + /* invert both clocks */ + dev_conf &= ~I2S_DEV_CONF_TRAN_SCK_POLAR; + dev_conf |= I2S_DEV_CONF_TRAN_WS_POLAR; + break; + default: + return -EINVAL; + } + + /* DAI hardware audio formats */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR; + dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR | + I2S_DEV_CONF_TRAN_DATA_WS_DEL | + I2S_DEV_CONF_TRAN_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_RIGHT_J: + dev_conf |= I2S_DEV_CONF_TRAN_DATA_WS_DEL; + dev_conf &= ~(I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | + I2S_DEV_CONF_TRAN_APB_ALIGN_LR | + I2S_DEV_CONF_TRAN_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_LEFT_J: + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | I2S_DEV_CONF_TRAN_DATA_WS_DEL; + dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR | + I2S_DEV_CONF_TRAN_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_DSP_A: + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | + I2S_DEV_CONF_TRAN_WS_DSP_MODE; + dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR | + I2S_DEV_CONF_TRAN_DATA_WS_DEL); + break; + case SND_SOC_DAIFMT_DSP_B: + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | + I2S_DEV_CONF_TRAN_DATA_WS_DEL | + I2S_DEV_CONF_TRAN_WS_DSP_MODE; + dev_conf &= ~I2S_DEV_CONF_TRAN_APB_ALIGN_LR; + break; + default: + return -EINVAL; + } + + /* DAI hardware clock masters */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + ctrl &= ~I2S_CTRL_T_MS; + break; + case SND_SOC_DAIFMT_CBP_CFP: + ctrl |= I2S_CTRL_T_MS; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s_mc_priv->regmap, I2S_DEV_CONF, + I2S_DEV_CONF_TRAN_SCK_POLAR | I2S_DEV_CONF_TRAN_WS_POLAR | + I2S_DEV_CONF_TRAN_APB_ALIGN_LR | I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | + I2S_DEV_CONF_TRAN_DATA_WS_DEL | + I2S_DEV_CONF_TRAN_WS_DSP_MODE, dev_conf); + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_T_MS | I2S_CTRL_TR_CFG, ctrl); + } else if (cpu_dai->id == I2S_MC_AIF2) { + /* DAI hardware signal polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* both normal clocks */ + dev_conf |= I2S_DEV_CONF_REC_SCK_POLAR; + dev_conf &= ~I2S_DEV_CONF_REC_WS_POLAR; + break; + case SND_SOC_DAIFMT_NB_IF: + /* invert frame clock */ + dev_conf |= I2S_DEV_CONF_REC_SCK_POLAR; + dev_conf |= I2S_DEV_CONF_REC_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_NF: + /* invert bit clock */ + dev_conf &= ~I2S_DEV_CONF_REC_SCK_POLAR; + dev_conf &= ~I2S_DEV_CONF_REC_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_IF: + /* invert both clocks */ + dev_conf &= ~I2S_DEV_CONF_REC_SCK_POLAR; + dev_conf |= I2S_DEV_CONF_REC_WS_POLAR; + break; + default: + return -EINVAL; + } + + /* DAI hardware audio formats */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR; + dev_conf &= ~(I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_DATA_WS_DEL | + I2S_DEV_CONF_REC_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_RIGHT_J: + dev_conf |= I2S_DEV_CONF_REC_DATA_WS_DEL; + dev_conf &= ~(I2S_DEV_CONF_REC_I2S_ALIGN_LR | + I2S_DEV_CONF_REC_APB_ALIGN_LR | + I2S_DEV_CONF_REC_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_LEFT_J: + dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR | I2S_DEV_CONF_REC_DATA_WS_DEL; + dev_conf &= ~(I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_DSP_A: + dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR | + I2S_DEV_CONF_REC_WS_DSP_MODE; + dev_conf &= ~(I2S_DEV_CONF_REC_APB_ALIGN_LR | + I2S_DEV_CONF_REC_DATA_WS_DEL); + break; + case SND_SOC_DAIFMT_DSP_B: + dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR | + I2S_DEV_CONF_REC_DATA_WS_DEL | + I2S_DEV_CONF_REC_WS_DSP_MODE; + dev_conf &= ~I2S_DEV_CONF_REC_APB_ALIGN_LR; + break; + default: + return -EINVAL; + } + + /* DAI hardware clock masters */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + ctrl &= ~I2S_CTRL_R_MS; + break; + case SND_SOC_DAIFMT_CBP_CFP: + ctrl |= I2S_CTRL_R_MS; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s_mc_priv->regmap, I2S_DEV_CONF, + I2S_DEV_CONF_REC_SCK_POLAR | I2S_DEV_CONF_REC_WS_POLAR | + I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_I2S_ALIGN_LR | + I2S_DEV_CONF_REC_DATA_WS_DEL | + I2S_DEV_CONF_REC_WS_DSP_MODE, dev_conf); + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_R_MS | I2S_CTRL_TR_CFG, ctrl); + } else { + dev_err(i2s_mc_priv->dev, "Invalid dai id\n"); + return -EINVAL; + } + + return 0; +} + +static int cdns_i2s_mc_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + u32 ctrl = 0, fifo = 0; + + /* + * FIFO reset should meet two requirements + * 1) after strobe_ts and strobe_rs clock on + * 2) before dma ready to receive trigger + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Transmitter clock enable */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, + I2S_CID_CTRL_STROBE_TS, 0); + + /* + * Transmitter FIFO reset + * When '0', transmit FIFO pointers are reset to zero. Threshold level for + * this FIFO is unchanged. This bit is automatically set to '1' after one + * clock cycle if TX FIFO reset has been acknowledged. + * Deassert then assert this bit here, since I2S_CTRL register is not + * volatile, would not read from hardware any longer. If not, it would + * clear tx fifo every time when write this register. + */ + ctrl &= ~I2S_CTRL_TFIFO_RST; + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_TFIFO_RST, ctrl); + ctrl |= I2S_CTRL_TFIFO_RST; + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_TFIFO_RST, ctrl); + + /* Transmitter FIFO threshold set */ + fifo |= FIELD_PREP(I2S_TFIFO_CTRL_TAEMPTY_THRESHOLD, + i2s_mc_priv->devtype_data->tfifo_aempty_threshold) | + FIELD_PREP(I2S_TFIFO_CTRL_TAFULL_THRESHOLD, + i2s_mc_priv->devtype_data->tfifo_afull_threshold); + regmap_update_bits(i2s_mc_priv->regmap, I2S_TFIFO_CTRL, + I2S_TFIFO_CTRL_TAEMPTY_THRESHOLD | + I2S_TFIFO_CTRL_TAFULL_THRESHOLD, fifo); + } else { + /* Receiver clock enable */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, + I2S_CID_CTRL_STROBE_RS, 0); + + /* + * Receiver FIFO reset + * When '0', receive FIFO pointers are reset to zero. Threshold level for + * this FIFO is unchanged. This bit is automatically set to '1' after one + * clock cycle if RX FIFO reset has been acknowledged. + * Deassert then assert this bit here, since I2S_CTRL register is not + * volatile, would not read from hardware any longer. If not, it would + * clear rx fifo every time when write this register. + */ + ctrl &= ~I2S_CTRL_RFIFO_RST; + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_RFIFO_RST, ctrl); + ctrl |= I2S_CTRL_RFIFO_RST; + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_RFIFO_RST, ctrl); + + /* Receiver FIFO threshold set */ + fifo |= FIELD_PREP(I2S_RFIFO_CTRL_RAEMPTY_THRESHOLD, + i2s_mc_priv->devtype_data->rfifo_aempty_threshold) | + FIELD_PREP(I2S_RFIFO_CTRL_RAFULL_THRESHOLD, + i2s_mc_priv->devtype_data->rfifo_afull_threshold); + regmap_update_bits(i2s_mc_priv->regmap, I2S_RFIFO_CTRL, + I2S_RFIFO_CTRL_RAEMPTY_THRESHOLD | + I2S_RFIFO_CTRL_RAFULL_THRESHOLD, fifo); + } + + /* + * Enable global interrupt mask, for both transmitter and receiver, use + * individual interrupt masks + */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, + I2S_CID_CTRL_INTREQ_MASK, I2S_CID_CTRL_INTREQ_MASK); + + return 0; +} + +static bool cdns_i2s_mc_other_dai_active(struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_dai *dai; + + for_each_component_dais(cpu_dai->component, dai) { + if (dai == cpu_dai) + continue; + if (snd_soc_dai_active(dai)) + return true; + } + + return false; +} + +static int cdns_i2s_mc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_mc_priv->dev; + u32 rate, sample_rate = 0; + u32 slots, slot_width, resolution, ctrl; + unsigned long i2s_clk_rate; + u8 pin_tx_num, pin_rx_num; + struct clk *clk_parent; + bool is_master_mode; + int ret; + + rate = params_rate(params); + slot_width = i2s_mc_priv->devtype_data->data_width; + dev_dbg(dev, "rate = %d, slot_width = %d\n", rate, slot_width); + + /* switch clk mux to select the appropriate clk parent */ + if (rate % 8000 == 0) { + /* Sampling rate is a multiple of 8KHz, select "audio-clk0" */ + clk_parent = i2s_mc_priv->clks[AUDIO_CLK0]; + } else if (rate % 11025 == 0) { + /* Sampling rate is a multiple of 11.025KHz, select "audio-clk2" */ + clk_parent = i2s_mc_priv->clks[AUDIO_CLK2]; + } else { + dev_err(i2s_mc_priv->dev, "Invalid sample rate\n"); + return -EINVAL; + } + + if (cdns_i2s_mc_other_dai_active(cpu_dai) && + !clk_is_match(clk_get_parent(i2s_mc_priv->clk_i2s), clk_parent)) { + dev_err(dev, + "i2s clock parent conflicts with active stream on the other DAI\n"); + return -EBUSY; + } + + ret = clk_set_parent(i2s_mc_priv->clk_i2s, clk_parent); + if (ret) { + dev_err(i2s_mc_priv->dev, "Failed to set i2s clock parent\n"); + return ret; + } + + i2s_clk_rate = clk_get_rate(i2s_mc_priv->clk_i2s); + dev_dbg(dev, "i2s clk rate = %lu\n", i2s_clk_rate); + + regmap_read(i2s_mc_priv->regmap, I2S_CTRL, &ctrl); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + is_master_mode = ctrl & I2S_CTRL_T_MS; + else + is_master_mode = ctrl & I2S_CTRL_R_MS; + + if (is_master_mode) { + if (i2s_clk_rate < rate * 2 * slot_width * I2S_CLK_RATIO_MIN_MASTER) { + dev_err(i2s_mc_priv->dev, + "clk freq %lu is too low, must >= (%d * %d * %d * %d = %d)\n", + i2s_clk_rate, rate, slot_width, 2, I2S_CLK_RATIO_MIN_MASTER, + rate * slot_width * 2 * I2S_CLK_RATIO_MIN_MASTER); + return -EINVAL; + } + + sample_rate = DIV_ROUND_CLOSEST(i2s_clk_rate, (rate * 2 * slot_width)); + } else { + if (i2s_clk_rate < rate * 2 * slot_width * I2S_CLK_RATIO_MIN_SLAVE) { + dev_err(i2s_mc_priv->dev, + "clk freq %lu is too low, must >= (%d * %d * %d * %d = %d)\n", + i2s_clk_rate, rate, slot_width, 2, I2S_CLK_RATIO_MIN_SLAVE, + rate * slot_width * 2 * I2S_CLK_RATIO_MIN_SLAVE); + return -EINVAL; + } + } + + slots = params_channels(params); + resolution = params_width(params); + dev_dbg(dev, "slots = %d, resolution = %d\n", slots, resolution); + + /* Each data pin carries two slots, so the channel count must be even */ + if (slots % 2) { + dev_err(i2s_mc_priv->dev, "Channel number must be a multiple of two\n"); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_mc_priv->pin_tx_mask_adjust = i2s_mc_priv->pin_tx_mask; + + pin_tx_num = hweight8(i2s_mc_priv->pin_tx_mask); + if (slots > 2 * pin_tx_num) { + dev_warn(i2s_mc_priv->dev, + "Transmit channel number is larger than hardware config\n"); + return -EOPNOTSUPP; + } else if (slots < 2 * pin_tx_num) { + cdns_i2s_mc_adjust_pin_config(&i2s_mc_priv->pin_tx_mask_adjust, slots); + } + + regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_TSAMPLE_RATE, + FIELD_PREP(I2S_SRR_TSAMPLE_RATE, sample_rate)); + regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_TRESOLUTION, + FIELD_PREP(I2S_SRR_TRESOLUTION, (resolution - 1))); + } else { + i2s_mc_priv->pin_rx_mask_adjust = i2s_mc_priv->pin_rx_mask; + + pin_rx_num = hweight8(i2s_mc_priv->pin_rx_mask); + if (slots > 2 * pin_rx_num) { + dev_warn(i2s_mc_priv->dev, + "Receive channel number is larger than hardware config\n"); + return -EOPNOTSUPP; + } else if (slots < 2 * pin_rx_num) { + cdns_i2s_mc_adjust_pin_config(&i2s_mc_priv->pin_rx_mask_adjust, slots); + } + + regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_RSAMPLE_RATE, + FIELD_PREP(I2S_SRR_RSAMPLE_RATE, sample_rate)); + regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_RRESOLUTION, + FIELD_PREP(I2S_SRR_RRESOLUTION, (resolution - 1))); + } + + return 0; +} + +static int cdns_i2s_mc_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdns_i2s_mc_tx_config(i2s_mc_priv, true); + else + cdns_i2s_mc_rx_config(i2s_mc_priv, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdns_i2s_mc_tx_config(i2s_mc_priv, false); + else + cdns_i2s_mc_rx_config(i2s_mc_priv, false); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cdns_i2s_mc_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + u32 ctrl = 0, cid_ctrl = 0; + + /* + * Transceiver disable + * Transceiver clock disable + * All interrupts masked + * Reset for transmitter synchronizing unit + * Reset for receiver synchronizing unit + */ + ctrl |= FIELD_PREP(I2S_CTRL_I2S_EN, 0); + ctrl &= ~(I2S_CTRL_TSYNC_RST | I2S_CTRL_RSYNC_RST); + cid_ctrl |= I2S_CID_CTRL_STROBE_TS | I2S_CID_CTRL_STROBE_RS; + cid_ctrl &= ~I2S_CID_CTRL_INTREQ_MASK; + + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN | I2S_CTRL_TSYNC_RST | I2S_CTRL_RSYNC_RST, ctrl); + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, + I2S_CID_CTRL_STROBE_TS | I2S_CID_CTRL_STROBE_RS | + I2S_CID_CTRL_INTREQ_MASK, cid_ctrl); + + snd_soc_dai_init_dma_data(cpu_dai, &i2s_mc_priv->playback_dma_data, + &i2s_mc_priv->capture_dma_data); + + snd_soc_dai_set_drvdata(cpu_dai, i2s_mc_priv); + + return 0; +} + +static const struct snd_soc_dai_ops cdns_i2s_mc_dai_ops = { + .probe = cdns_i2s_mc_dai_probe, + .set_fmt = cdns_i2s_mc_set_fmt, + .hw_params = cdns_i2s_mc_hw_params, + .prepare = cdns_i2s_mc_prepare, + .trigger = cdns_i2s_mc_trigger, +}; + +static struct snd_soc_dai_driver cdns_i2s_mc_dai[] = { + { + .name = "i2s-mc-aif1", + .id = I2S_MC_AIF1, + .playback = { + .stream_name = "I2S-MC-Playback", + .channels_min = 2, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .ops = &cdns_i2s_mc_dai_ops, + }, + { + .name = "i2s-mc-aif2", + .id = I2S_MC_AIF2, + .capture = { + .stream_name = "I2S-MC-Capture", + .channels_min = 2, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .ops = &cdns_i2s_mc_dai_ops, + }, +}; + +static const struct snd_soc_component_driver cdns_i2s_mc_component = { + .name = DRV_NAME, + .legacy_dai_naming = 1, +}; + +static const struct reg_default cdns_i2s_mc_reg_defaults[] = { + { I2S_CTRL, 0x01900000 }, + { I2S_INTR_STAT, 0x00003300 }, + { I2S_SRR, 0x00000000 }, + { I2S_CID_CTRL, 0x00000000 }, + { I2S_TFIFO_STAT, 0x00000000 }, + { I2S_RFIFO_STAT, 0x00000000 }, + { I2S_TFIFO_CTRL, 0x000f0000 }, + { I2S_RFIFO_CTRL, 0x000f0000 }, + { I2S_DEV_CONF, 0x00000208 }, + { I2S_POLL_STAT, 0x00000003 }, +}; + +static bool cdns_i2s_mc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_CTRL: + case I2S_INTR_STAT: + case I2S_SRR: + case I2S_CID_CTRL: + case I2S_TFIFO_STAT: + case I2S_RFIFO_STAT: + case I2S_TFIFO_CTRL: + case I2S_RFIFO_CTRL: + case I2S_DEV_CONF: + case I2S_POLL_STAT: + return true; + default: + return false; + } +} + +static bool cdns_i2s_mc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_CTRL: + case I2S_INTR_STAT: + case I2S_SRR: + case I2S_CID_CTRL: + case I2S_TFIFO_CTRL: + case I2S_RFIFO_CTRL: + case I2S_DEV_CONF: + return true; + default: + return false; + } +} + +static bool cdns_i2s_mc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_INTR_STAT: + case I2S_TFIFO_STAT: + case I2S_RFIFO_STAT: + case I2S_POLL_STAT: + return true; + default: + return false; + } +} + +static const struct regmap_config cdns_i2s_mc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = I2S_POLL_STAT, + .reg_defaults = cdns_i2s_mc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cdns_i2s_mc_reg_defaults), + .readable_reg = cdns_i2s_mc_readable_reg, + .writeable_reg = cdns_i2s_mc_writeable_reg, + .volatile_reg = cdns_i2s_mc_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int cdns_i2s_get_pin_config(struct platform_device *pdev, + struct cdns_i2s_mc_priv *i2s_mc_priv) +{ + u8 rxtx_mask; + int ret; + + ret = device_property_read_u8(&pdev->dev, + "cix,pin-out-num", &i2s_mc_priv->pin_out_num); + if (ret) { + dev_err(&pdev->dev, "Failed to get pin-out-num: %d\n", ret); + return ret; + } + + if (!i2s_mc_priv->pin_out_num || i2s_mc_priv->pin_out_num > BITS_PER_BYTE) { + dev_err(&pdev->dev, "Pin out number should be in range [1, 8]\n"); + return -EINVAL; + } + + ret = device_property_read_u8(&pdev->dev, + "cix,pin-rx-mask", &i2s_mc_priv->pin_rx_mask); + if (ret) { + dev_err(&pdev->dev, "Failed to get pin-rx-mask: %d\n", ret); + return ret; + } + + ret = device_property_read_u8(&pdev->dev, + "cix,pin-tx-mask", &i2s_mc_priv->pin_tx_mask); + if (ret) { + dev_err(&pdev->dev, "Failed to get pin-tx-mask: %d\n", ret); + return ret; + } + + rxtx_mask = i2s_mc_priv->pin_rx_mask & i2s_mc_priv->pin_tx_mask; + if (rxtx_mask) { + dev_err(&pdev->dev, "Pin configuration for transmitter and receiver is conflict\n"); + return -EINVAL; + } + + rxtx_mask = i2s_mc_priv->pin_rx_mask | i2s_mc_priv->pin_tx_mask; + if (hweight8(rxtx_mask) > i2s_mc_priv->pin_out_num) { + dev_err(&pdev->dev, "Pin configuration is out of range\n"); + return -EINVAL; + } + + return 0; +} + +static void cdns_i2s_mc_rst(struct cdns_i2s_mc_priv *i2s_mc_priv) +{ + /* reset */ + reset_control_assert(i2s_mc_priv->i2s_rst); + + usleep_range(1, 2); + + /* release reset */ + reset_control_deassert(i2s_mc_priv->i2s_rst); +} + +static int cdns_i2s_mc_runtime_suspend(struct device *dev); + +static int cdns_i2s_mc_probe(struct platform_device *pdev) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv; + struct resource *res; + void __iomem *base; + int i, irq, ret; + + i2s_mc_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_mc_priv), GFP_KERNEL); + if (!i2s_mc_priv) + return -ENOMEM; + + platform_set_drvdata(pdev, i2s_mc_priv); + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + i2s_mc_priv->dev = &pdev->dev; + i2s_mc_priv->devtype_data = device_get_match_data(&pdev->dev); + if (!i2s_mc_priv->devtype_data) + return -ENODEV; + + i2s_mc_priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &cdns_i2s_mc_regmap_config); + if (IS_ERR(i2s_mc_priv->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->regmap), + "Failed to initialize managed register map\n"); + + i2s_mc_priv->clk_hst = devm_clk_get(&pdev->dev, "hst"); + if (IS_ERR(i2s_mc_priv->clk_hst)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->clk_hst), + "Failed to get hst clock\n"); + + i2s_mc_priv->clk_i2s = devm_clk_get(&pdev->dev, "i2s"); + if (IS_ERR(i2s_mc_priv->clk_i2s)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->clk_i2s), + "Failed to get i2s clock\n"); + + for (i = 0; i < AUDIO_CLK_NUM; i++) { + i2s_mc_priv->clks[i] = devm_clk_get(&pdev->dev, + cdns_i2s_mc_clk_pll_names[i]); + if (IS_ERR(i2s_mc_priv->clks[i])) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->clks[i]), + "Failed to get %s clock\n", + cdns_i2s_mc_clk_pll_names[i]); + } + + i2s_mc_priv->i2s_rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s_mc_priv->i2s_rst)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->i2s_rst), + "Failed to get reset control\n"); + + ret = cdns_i2s_get_pin_config(pdev, i2s_mc_priv); + if (ret) + return ret; + + i2s_mc_priv->playback_dma_data.addr = res->start + I2S_FIFO_ADDRESS; + /* Buswidth will be set by framework at runtime */ + i2s_mc_priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + i2s_mc_priv->playback_dma_data.maxburst = 4; + + i2s_mc_priv->capture_dma_data.addr = res->start + I2S_FIFO_ADDRESS; + /* Buswidth will be set by framework at runtime */ + i2s_mc_priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + i2s_mc_priv->capture_dma_data.maxburst = 4; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register dmaengine component\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + i2s_mc_priv->irq = irq; + + ret = cdns_i2s_mc_clks_enable(i2s_mc_priv); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to enable clocks\n"); + + cdns_i2s_mc_rst(i2s_mc_priv); + + ret = devm_request_irq(&pdev->dev, irq, cdns_i2s_mc_isr, 0, + dev_name(&pdev->dev), i2s_mc_priv); + if (ret) { + cdns_i2s_mc_clks_disable(i2s_mc_priv); + return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n"); + } + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &cdns_i2s_mc_component, + cdns_i2s_mc_dai, ARRAY_SIZE(cdns_i2s_mc_dai)); + if (ret) + goto err_pm; + + pm_runtime_put_sync(&pdev->dev); + + return 0; + +err_pm: + cdns_i2s_mc_runtime_suspend(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + return dev_err_probe(&pdev->dev, ret, "Failed to register soc component\n"); +} + +static int cdns_i2s_mc_runtime_suspend(struct device *dev) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = dev_get_drvdata(dev); + + /* + * Disable the IRQ before switching the regcache to cache-only, so an + * in-flight handler can never access the registers through a + * cache-only regmap. disable_irq() waits for the handler to finish. + */ + disable_irq(i2s_mc_priv->irq); + regcache_cache_only(i2s_mc_priv->regmap, true); + cdns_i2s_mc_clks_disable(i2s_mc_priv); + + return 0; +} + +static int cdns_i2s_mc_runtime_resume(struct device *dev) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = dev_get_drvdata(dev); + int ret; + + ret = cdns_i2s_mc_clks_enable(i2s_mc_priv); + if (ret) { + dev_err(dev, "Failed to enable clocks:%d\n", ret); + return ret; + } + + cdns_i2s_mc_rst(i2s_mc_priv); + + regcache_cache_only(i2s_mc_priv->regmap, false); + regcache_mark_dirty(i2s_mc_priv->regmap); + + ret = regcache_sync(i2s_mc_priv->regmap); + if (ret) { + dev_err(dev, "Failed to sync regcache: %d\n", ret); + regcache_cache_only(i2s_mc_priv->regmap, true); + cdns_i2s_mc_clks_disable(i2s_mc_priv); + return ret; + } + + /* Enable the IRQ only after the regcache is back in sync */ + enable_irq(i2s_mc_priv->irq); + + return 0; +} + +static int cdns_i2s_mc_system_suspend(struct device *dev) +{ + int ret; + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int cdns_i2s_mc_system_resume(struct device *dev) +{ + int ret; + + pinctrl_pm_select_default_state(dev); + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + return 0; +} + +static const struct dev_pm_ops cdns_i2s_mc_pm_ops = { + RUNTIME_PM_OPS(cdns_i2s_mc_runtime_suspend, cdns_i2s_mc_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(cdns_i2s_mc_system_suspend, cdns_i2s_mc_system_resume) +}; + +static const struct cdns_i2s_mc_devtype_data sky1_devtype_data = { + .data_width = 32, + .rfifo_aempty_threshold = 4, + .rfifo_afull_threshold = 12, + .tfifo_aempty_threshold = 4, + .tfifo_afull_threshold = 12, +}; + +static const struct of_device_id cdns_i2s_mc_of_match[] = { + { .compatible = "cix,sky1-i2s-mc", .data = &sky1_devtype_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cdns_i2s_mc_of_match); + +static void cdns_i2s_mc_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + cdns_i2s_mc_runtime_suspend(&pdev->dev); +} + +static struct platform_driver cdns_i2s_mc_driver = { + .probe = cdns_i2s_mc_probe, + .remove = cdns_i2s_mc_remove, + .driver = { + .name = DRV_NAME, + .pm = pm_ptr(&cdns_i2s_mc_pm_ops), + .of_match_table = cdns_i2s_mc_of_match, + }, +}; +module_platform_driver(cdns_i2s_mc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Joakim Zhang "); +MODULE_DESCRIPTION("Cadence I2S-MC Controller Driver"); diff --git a/sound/soc/cdns/cdns-i2s-sc.c b/sound/soc/cdns/cdns-i2s-sc.c new file mode 100644 index 00000000000000..cbc514db1e325f --- /dev/null +++ b/sound/soc/cdns/cdns-i2s-sc.c @@ -0,0 +1,1171 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2026 Cix Technology Group Co., Ltd. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Special Function Registers */ +/* Control Register */ +#define I2S_CTRL 0x00 +#define I2S_CTRL_LR_PACK BIT(31) +#define I2S_CTRL_FIFO_AFULL_MASK BIT(30) +#define I2S_CTRL_FIFO_FULL_MASK BIT(29) +#define I2S_CTRL_FIFO_AEMPTY_MASK BIT(28) +#define I2S_CTRL_FIFO_EMPTY_MASK BIT(27) +#define I2S_CTRL_I2S_MASK BIT(26) +#define I2S_CTRL_INTREQ_MASK BIT(25) +#define I2S_CTRL_I2S_STB BIT(24) +#define I2S_CTRL_HOST_DATA_ALIGN BIT(23) +#define I2S_CTRL_DATA_ORDER BIT(22) +#define I2S_CTRL_DATA_ALIGN BIT(21) +#define I2S_CTRL_DATA_WS_DEL GENMASK(20, 16) +#define I2S_CTRL_WS_POLAR BIT(15) +#define I2S_CTRL_SCK_POLAR BIT(14) +#define I2S_CTRL_AUDIO_MODE BIT(13) +#define I2S_CTRL_MONO_MODE BIT(12) +#define I2S_CTRL_WS_MODE GENMASK(11, 8) +#define I2S_CTRL_CHN_WIDTH GENMASK(7, 5) +#define I2S_CTRL_CHN_WIDTH_8 0 +#define I2S_CTRL_CHN_WIDTH_12 1 +#define I2S_CTRL_CHN_WIDTH_16 2 +#define I2S_CTRL_CHN_WIDTH_18 3 +#define I2S_CTRL_CHN_WIDTH_20 4 +#define I2S_CTRL_CHN_WIDTH_24 5 +#define I2S_CTRL_CHN_WIDTH_28 6 +#define I2S_CTRL_CHN_WIDTH_32 7 +#define I2S_CTRL_FIFO_RST BIT(4) +#define I2S_CTRL_SFR_RST BIT(3) +#define I2S_CTRL_MS_CFG BIT(2) +#define I2S_CTRL_DIR_CFG BIT(1) +#define I2S_CTRL_I2S_EN BIT(0) + +/* Full-Duplex Mode Control Register */ +#define I2S_CTRL_FDX 0x04 +#define I2S_CTRL_FDX_RFIFO_AFULL_MASK BIT(30) +#define I2S_CTRL_FDX_RFIFO_FULL_MASK BIT(29) +#define I2S_CTRL_FDX_RFIFO_AEMPTY_MASK BIT(28) +#define I2S_CTRL_FDX_RFIFO_EMPTY_MASK BIT(27) +#define I2S_CTRL_FDX_RI2S_MASK BIT(26) +#define I2S_CTRL_FDX_RFIFO_RST BIT(4) +#define I2S_CTRL_FDX_I2S_FRX_EN BIT(2) +#define I2S_CTRL_FDX_I2S_FTX_EN BIT(1) +#define I2S_CTRL_FDX_FULL_DUPLEX BIT(0) + +/* Sample Resolution Register */ +#define I2S_SRES 0x08 +#define I2S_SRES_RESOLUTION GENMASK(4, 0) + +/* Full-Duplex Mode Receive Sample Resolution Register */ +#define I2S_SRES_FDR 0x0c +#define I2S_SRES_FDR_RRESOLUTION GENMASK(4, 0) + +/* Transceiver Sample Rate Register */ +#define I2S_SRATE 0x10 +#define I2S_SRATE_SAMPLE_RATE GENMASK(19, 0) + +/* Status Flags Register */ +#define I2S_STAT 0x14 +#define I2S_STAT_RFIFO_AFULL BIT(19) +#define I2S_STAT_RFIFO_FULL BIT(18) +#define I2S_STAT_RFIFO_AEMPTY BIT(17) +#define I2S_STAT_RFIFO_EMPTY BIT(16) +#define I2S_STAT_FIFO_AFULL BIT(5) +#define I2S_STAT_FIFO_FULL BIT(4) +#define I2S_STAT_FIFO_AEMPTY BIT(3) +#define I2S_STAT_FIFO_EMPTY BIT(2) +#define I2S_STAT_RDATA_OVERR BIT(1) +#define I2S_STAT_TDATA_UNDERR BIT(0) + +/* FIFO Level Register (read only) */ +#define I2S_FIFO_LEVEL 0x18 + +/* FIFO Almost Empty Level Register */ +#define I2S_FIFO_AEMPTY 0x1c + +/* FIFO Almost Full Level Register */ +#define I2S_FIFO_AFULL 0x20 + +/* Full-Duplex Mode Receiver FIFO Level Register (read only) */ +#define I2S_FIFO_LEVEL_FDR 0x24 + +/* Full-Duplex Mode Receiver FIFO Almost Empty Level Register */ +#define I2S_FIFO_AEMPTY_FDR 0x28 + +/* Full-Duplex Mode Receiver FIFO Almost Full Level Register */ +#define I2S_FIFO_AFULL_FDR 0x2c + +/* Time Division Multiplexing Control Register */ +#define I2S_TDM_CTRL 0x30 +#define I2S_TDM_CTRL_CHN_EN GENMASK(31, 16) +#define I2S_TDM_CTRL_CHN_NO GENMASK(4, 1) +#define I2S_TDM_CTRL_TDM_EN BIT(0) + +/* Time Division Multiplexing Full-Duplex Mode Channels Direction Register */ +#define I2S_TDM_FD_DIR 0x34 +#define I2S_TDM_FD_DIR_CHN_RXEN GENMASK(31, 16) +#define I2S_TDM_FD_DIR_CHN_TXEN GENMASK(15, 0) + +/* Transmit And Receive FIFOs Address */ +#define I2S_FIFO_ADDRESS 0x40 + +/* + * The I2S functional clock must run at a minimum ratio above the audio + * bit clock to safely cross clock domains, regardless of whether the + * bit clock is generated internally or externally. See the controller + * reference manual, section 3.3 "Clocks and Clock Domain Crossing". + */ +#define I2S_CLK_RATIO_MIN 6 + +#define DRV_NAME "cdns-i2s-sc" + +enum { + AUDIO_CLK0, + AUDIO_CLK2, + AUDIO_CLK_NUM, +}; + +static const char *cdns_i2s_sc_clk_pll_names[AUDIO_CLK_NUM] = { + [AUDIO_CLK0] = "audio-clk0", + [AUDIO_CLK2] = "audio-clk2", +}; + +struct cdns_i2s_sc_devtype_data { + u32 rx_fifo_aempty_threshold; + u32 rx_fifo_afull_threshold; + u32 tx_fifo_aempty_threshold; + u32 tx_fifo_afull_threshold; +}; + +struct cdns_i2s_sc_tdm_config { + unsigned int tx_mask; + unsigned int rx_mask; + int slots; + int slot_width; +}; + +struct cdns_i2s_sc_priv { + struct device *dev; + struct regmap *regmap; + struct reset_control *i2s_rst; + + int irq; + + struct clk *clk_hst; + struct clk *clk_i2s; + struct clk *clks[AUDIO_CLK_NUM]; + + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + + const struct cdns_i2s_sc_devtype_data *devtype_data; + struct cdns_i2s_sc_tdm_config tdm_config; + + bool is_master_mode; + bool is_tdm_mode; + bool is_mono_in_i2s_mode; + + /* Serializes the transmitter/receiver start/stop state machine below */ + spinlock_t lock; + + /* + * NOTE: + * I2S_CTRL_FDX[I2S_FTX_EN] = 1 && I2S_CTRL[I2S_EN] = 1 to enable transmitter + * I2S_CTRL_FDX[I2S_FRX_EN] = 1 && I2S_CTRL[I2S_EN] = 1 to enable receiver + * We can dynamically switch of the transmitter and receiver enable separately, + * but need always enable I2S transceiver, so I2S-SC controller hopes to start + * the transmitter and receiver together, also to stop them when they are both + * try to stop. + * + * The playback and capture PCMs trigger independently (the PCM core holds + * only each substream's own lock), so tx_start/rx_start and the shared + * transceiver registers must be accessed under &lock. + */ + bool rx_start; + bool tx_start; +}; + +static int cdns_i2s_sc_clks_enable(struct cdns_i2s_sc_priv *i2s_sc_priv) +{ + int ret; + + ret = clk_prepare_enable(i2s_sc_priv->clk_hst); + if (ret) + return ret; + + ret = clk_prepare_enable(i2s_sc_priv->clk_i2s); + if (ret) + clk_disable_unprepare(i2s_sc_priv->clk_hst); + + return ret; +} + +static void cdns_i2s_sc_clks_disable(struct cdns_i2s_sc_priv *i2s_sc_priv) +{ + clk_disable_unprepare(i2s_sc_priv->clk_i2s); + clk_disable_unprepare(i2s_sc_priv->clk_hst); +} + +static void cdns_i2s_sc_rxtx_common_config(struct cdns_i2s_sc_priv *i2s_sc_priv, bool on) +{ + if (on) { + /* Full-duplex mode enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_FULL_DUPLEX, I2S_CTRL_FDX_FULL_DUPLEX); + + if (i2s_sc_priv->is_tdm_mode) { + /* TDM mode enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_TDM_EN, I2S_TDM_CTRL_TDM_EN); + + /* Number of supported audio channels in TDM mode */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_CHN_NO, + FIELD_PREP(I2S_TDM_CTRL_CHN_NO, + i2s_sc_priv->tdm_config.slots - 1)); + + /* TDM mode channels enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, I2S_TDM_CTRL_CHN_EN, + FIELD_PREP(I2S_TDM_CTRL_CHN_EN, + i2s_sc_priv->tdm_config.rx_mask | + i2s_sc_priv->tdm_config.tx_mask)); + } + + /* Transceiver clock enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_STB, 0); + + /* All interrupt requests unmask */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_INTREQ_MASK, I2S_CTRL_INTREQ_MASK); + } else { + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_INTREQ_MASK, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_STB, I2S_CTRL_I2S_STB); + + if (i2s_sc_priv->is_tdm_mode) { + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_TDM_EN, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_CHN_NO, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_CHN_EN, 0); + } + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_FULL_DUPLEX, 0); + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN, 0); + } +} + +static void cdns_i2s_sc_tx_config(struct cdns_i2s_sc_priv *i2s_sc_priv, bool on) +{ + u32 irq_mask = 0; + + irq_mask |= I2S_CTRL_I2S_MASK; + + if (on) { + cdns_i2s_sc_rxtx_common_config(i2s_sc_priv, on); + + /* Transmitter data underrun interrupt unmask */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, irq_mask, irq_mask); + + if (i2s_sc_priv->is_tdm_mode) { + /* TDM mode channels transmit enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_FD_DIR, + I2S_TDM_FD_DIR_CHN_TXEN, + FIELD_PREP(I2S_TDM_FD_DIR_CHN_TXEN, + i2s_sc_priv->tdm_config.tx_mask)); + } else { + /* + * I2S mode mono/stereo selection + * audio_mode: HIGH = only one channel active + * mono_mode: LOW = left channel, HIGH = right channel + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_AUDIO_MODE | I2S_CTRL_MONO_MODE, + i2s_sc_priv->is_mono_in_i2s_mode ? + I2S_CTRL_AUDIO_MODE : 0); + } + + /* Full-duplex mode transmitter enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_I2S_FTX_EN, I2S_CTRL_FDX_I2S_FTX_EN); + + /* Transceiver enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN, I2S_CTRL_I2S_EN); + + i2s_sc_priv->tx_start = true; + } else { + i2s_sc_priv->tx_start = false; + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_I2S_FTX_EN, 0); + + if (i2s_sc_priv->is_tdm_mode) + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_FD_DIR, + I2S_TDM_FD_DIR_CHN_TXEN, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, irq_mask, 0); + + if (!i2s_sc_priv->rx_start) + cdns_i2s_sc_rxtx_common_config(i2s_sc_priv, on); + } +} + +static void cdns_i2s_sc_rx_config(struct cdns_i2s_sc_priv *i2s_sc_priv, bool on) +{ + u32 irq_mask = 0; + + irq_mask |= I2S_CTRL_FDX_RI2S_MASK; + + if (on) { + cdns_i2s_sc_rxtx_common_config(i2s_sc_priv, on); + + /* Receiver data overrun interrupt unmask */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, irq_mask, irq_mask); + + if (i2s_sc_priv->is_tdm_mode) { + /* TDM mode channels receive enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_FD_DIR, + I2S_TDM_FD_DIR_CHN_RXEN, + FIELD_PREP(I2S_TDM_FD_DIR_CHN_RXEN, + i2s_sc_priv->tdm_config.rx_mask)); + } else { + /* + * I2S mode mono/stereo selection + * audio_mode: HIGH = only one channel active + * mono_mode: LOW = left channel, HIGH = right channel + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_AUDIO_MODE | I2S_CTRL_MONO_MODE, + i2s_sc_priv->is_mono_in_i2s_mode ? + I2S_CTRL_AUDIO_MODE : 0); + } + + /* Full-duplex mode receiver enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_I2S_FRX_EN, I2S_CTRL_FDX_I2S_FRX_EN); + + /* Transceiver enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN, I2S_CTRL_I2S_EN); + + i2s_sc_priv->rx_start = true; + } else { + i2s_sc_priv->rx_start = false; + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_I2S_FRX_EN, 0); + + if (i2s_sc_priv->is_tdm_mode) + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_FD_DIR, + I2S_TDM_FD_DIR_CHN_RXEN, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, irq_mask, 0); + + if (!i2s_sc_priv->tx_start) + cdns_i2s_sc_rxtx_common_config(i2s_sc_priv, on); + } +} + +static irqreturn_t cdns_i2s_sc_isr(int irq, void *devid) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = (struct cdns_i2s_sc_priv *)devid; + struct device *dev = i2s_sc_priv->dev; + u32 stat; + + if (regmap_read(i2s_sc_priv->regmap, I2S_STAT, &stat)) + return IRQ_NONE; + + stat &= I2S_STAT_TDATA_UNDERR | I2S_STAT_RDATA_OVERR; + if (!stat) + return IRQ_NONE; + + /* Clear the status */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_STAT, stat, 0); + + /* Transmitter status */ + if (stat & I2S_STAT_TDATA_UNDERR) + dev_dbg(dev, "isr: tx data underrun\n"); + + /* Receiver status */ + if (stat & I2S_STAT_RDATA_OVERR) + dev_dbg(dev, "isr: rx data overrun\n"); + + return IRQ_HANDLED; +} + +static int cdns_i2s_sc_set_tdm_slot(struct snd_soc_dai *cpu_dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_sc_priv->dev; + + dev_dbg(dev, "tx_mask = 0x%x, rx_mask = 0x%x, slots = %d, slot_width = %d\n", + tx_mask, rx_mask, slots, slot_width); + + if (!slots) { + i2s_sc_priv->is_tdm_mode = false; + return 0; + } + + if (slots < 0 || slots > 16) { + dev_err(i2s_sc_priv->dev, "TDM mode supports 1 to 16 slots\n"); + return -EINVAL; + } + + if (slot_width < 8 || slot_width > 32) { + dev_err(i2s_sc_priv->dev, "TDM slot width must be 8 to 32 bits\n"); + return -EINVAL; + } + + if ((tx_mask >> slots) || (rx_mask >> slots)) { + dev_err(i2s_sc_priv->dev, "TDM slot mask has bits outside the slots\n"); + return -EINVAL; + } + + i2s_sc_priv->tdm_config.slots = slots; + i2s_sc_priv->tdm_config.slot_width = slot_width; + i2s_sc_priv->tdm_config.rx_mask = rx_mask; + i2s_sc_priv->tdm_config.tx_mask = tx_mask; + + i2s_sc_priv->is_tdm_mode = true; + + return 0; +} + +static int cdns_i2s_sc_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_sc_priv->dev; + u32 ctrl = 0, ctrl_mask = 0; + + dev_dbg(dev, "format = 0x%x\n", fmt); + + /* DAI hardware audio formats */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 1) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 1); + ctrl &= ~(I2S_CTRL_DATA_ALIGN | I2S_CTRL_DATA_ORDER); + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 1) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 0) | + I2S_CTRL_DATA_ALIGN; + ctrl &= ~I2S_CTRL_DATA_ORDER; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 1) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 0); + ctrl &= ~(I2S_CTRL_DATA_ALIGN | I2S_CTRL_DATA_ORDER); + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 0) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 1); + ctrl &= ~(I2S_CTRL_DATA_ALIGN | I2S_CTRL_DATA_ORDER); + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 0) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 0); + ctrl &= ~(I2S_CTRL_DATA_ALIGN | I2S_CTRL_DATA_ORDER); + break; + default: + return -EINVAL; + } + + /* DAI hardware signal polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* both normal clocks */ + ctrl |= I2S_CTRL_SCK_POLAR; + ctrl &= ~I2S_CTRL_WS_POLAR; + break; + case SND_SOC_DAIFMT_NB_IF: + /* invert frame clock */ + ctrl |= I2S_CTRL_SCK_POLAR; + ctrl |= I2S_CTRL_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_NF: + /* invert bit clock */ + ctrl &= ~I2S_CTRL_SCK_POLAR; + ctrl &= ~I2S_CTRL_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_IF: + /* invert both clocks */ + ctrl &= ~I2S_CTRL_SCK_POLAR; + ctrl |= I2S_CTRL_WS_POLAR; + break; + default: + return -EINVAL; + } + + /* DAI hardware clock masters */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + ctrl &= ~I2S_CTRL_MS_CFG; + i2s_sc_priv->is_master_mode = false; + break; + case SND_SOC_DAIFMT_CBP_CFP: + ctrl |= I2S_CTRL_MS_CFG; + i2s_sc_priv->is_master_mode = true; + break; + default: + return -EINVAL; + } + + ctrl_mask |= I2S_CTRL_WS_MODE | I2S_CTRL_DATA_WS_DEL | I2S_CTRL_DATA_ALIGN | + I2S_CTRL_DATA_ORDER | I2S_CTRL_SCK_POLAR | I2S_CTRL_WS_POLAR | + I2S_CTRL_MS_CFG; + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, ctrl_mask, ctrl); + + return 0; +} + +static int cdns_i2s_sc_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + + /* + * The transceiver clock must stay on across the FIFO reset below, + * while the trigger() of the other stream may gate it through the + * shared start/stop state machine. Serialize against it with &lock. + */ + scoped_guard(spinlock_irqsave, &i2s_sc_priv->lock) { + /* Transceiver clock enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, I2S_CTRL_I2S_STB, 0); + + /* + * FIFO reset should meet two requirements + * 1) after i2s_stb clock on + * 2) before dma ready to receive trigger + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * Transmitter FIFO reset + * When LOW, FIFO pointer is reset to zero. Threshold levels for + * FIFO are unchanged. This bit is automatically set to HIGH after + * one clock cycle. + * Deassert then assert this bit here, since I2S_CTRL register is not + * volatile, would not read from hardware any longer. If not, it would + * clear tx fifo every time when write this register. + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, I2S_CTRL_FIFO_RST, 0); + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_FIFO_RST, I2S_CTRL_FIFO_RST); + + /* Transmitter FIFO threshold set */ + regmap_write(i2s_sc_priv->regmap, I2S_FIFO_AEMPTY, + i2s_sc_priv->devtype_data->tx_fifo_aempty_threshold); + regmap_write(i2s_sc_priv->regmap, I2S_FIFO_AFULL, + i2s_sc_priv->devtype_data->tx_fifo_afull_threshold); + } else { + /* + * Receiver FIFO reset + * When '0', RFIFO pointer is reset to zero. Threshold levels for RFIFO + * are unchanged. The bit is automatically set to '1' after one clock cycle. + * Deassert then assert this bit here, since I2S_CTRL_FDX register is not + * volatile, would not read from hardware any longer. If not, it would + * clear rx fifo every time when write this register. + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_RFIFO_RST, 0); + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_RFIFO_RST, I2S_CTRL_FDX_RFIFO_RST); + + /* Receiver FIFO threshold set */ + regmap_write(i2s_sc_priv->regmap, I2S_FIFO_AEMPTY_FDR, + i2s_sc_priv->devtype_data->rx_fifo_aempty_threshold); + regmap_write(i2s_sc_priv->regmap, I2S_FIFO_AFULL_FDR, + i2s_sc_priv->devtype_data->rx_fifo_afull_threshold); + } + } + + return 0; +} + +static int cdns_i2s_sc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_sc_priv->dev; + u32 slots, slot_width, resolution, val; + u32 rate, sample_rate, frame_slots; + unsigned long i2s_clk_rate; + struct clk *clk_parent; + int ret; + + /* Set sample slots, slot width and resolution */ + slots = params_channels(params); + resolution = params_width(params); + dev_dbg(dev, "slots = %d, resolution = %d\n", slots, resolution); + + if (i2s_sc_priv->is_tdm_mode) { + unsigned int mask, nch; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mask = i2s_sc_priv->tdm_config.tx_mask; + else + mask = i2s_sc_priv->tdm_config.rx_mask; + + nch = hweight32(mask); + if (slots != nch) { + dev_err(i2s_sc_priv->dev, + "Channel number does not match active tdm slots: %d\n", nch); + return -EINVAL; + } + + if (resolution != i2s_sc_priv->tdm_config.slot_width) { + dev_err(i2s_sc_priv->dev, + "Sample width does not match with specified tdm slot width\n"); + return -EINVAL; + } + + slot_width = i2s_sc_priv->tdm_config.slot_width; + + frame_slots = i2s_sc_priv->tdm_config.slots; + } else { + /* + * I2S mode: mono maps onto the left audio channel, while + * the frame still carries two slots. + */ + if (slots == 1) { + i2s_sc_priv->is_mono_in_i2s_mode = true; + slots = 2; + } else if (slots == 2) { + i2s_sc_priv->is_mono_in_i2s_mode = false; + } else { + dev_warn(i2s_sc_priv->dev, + "I2S mode supports up to two channels\n"); + return -EOPNOTSUPP; + } + + slot_width = 32; + + frame_slots = slots; + } + + dev_dbg(dev, "slot_width = %d\n", slot_width); + + if (slot_width == 8) { + val = I2S_CTRL_CHN_WIDTH_8; + } else if (slot_width == 12) { + val = I2S_CTRL_CHN_WIDTH_12; + } else if (slot_width == 16) { + val = I2S_CTRL_CHN_WIDTH_16; + } else if (slot_width == 18) { + val = I2S_CTRL_CHN_WIDTH_18; + } else if (slot_width == 20) { + val = I2S_CTRL_CHN_WIDTH_20; + } else if (slot_width == 24) { + val = I2S_CTRL_CHN_WIDTH_24; + } else if (slot_width == 28) { + val = I2S_CTRL_CHN_WIDTH_28; + } else if (slot_width == 32) { + val = I2S_CTRL_CHN_WIDTH_32; + } else { + dev_err(i2s_sc_priv->dev, "Slot width is invalid value\n"); + return -EINVAL; + } + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, I2S_CTRL_CHN_WIDTH, + FIELD_PREP(I2S_CTRL_CHN_WIDTH, val)); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_write(i2s_sc_priv->regmap, I2S_SRES, + FIELD_PREP(I2S_SRES_RESOLUTION, (resolution - 1))); + else + regmap_write(i2s_sc_priv->regmap, I2S_SRES_FDR, + FIELD_PREP(I2S_SRES_FDR_RRESOLUTION, (resolution - 1))); + + rate = params_rate(params); + dev_dbg(dev, "rate = %d\n", rate); + + /* switch clk mux to select the appropriate clk parent */ + if (rate % 8000 == 0) { + /* Sampling rate is a multiple of 8KHz, select "audio-clk0" */ + clk_parent = i2s_sc_priv->clks[AUDIO_CLK0]; + + ret = clk_set_parent(i2s_sc_priv->clk_i2s, clk_parent); + } else if (rate % 11025 == 0) { + /* Sampling rate is a multiple of 11.025KHz, select "audio-clk2" */ + clk_parent = i2s_sc_priv->clks[AUDIO_CLK2]; + + ret = clk_set_parent(i2s_sc_priv->clk_i2s, clk_parent); + } else { + dev_err(i2s_sc_priv->dev, "Invalid sample rate\n"); + return -EINVAL; + } + if (ret) { + dev_err(i2s_sc_priv->dev, "Failed to set i2s clock parent\n"); + return ret; + } + + i2s_clk_rate = clk_get_rate(i2s_sc_priv->clk_i2s); + dev_dbg(dev, "i2s clk rate = %lu\n", i2s_clk_rate); + + if (i2s_clk_rate < rate * slot_width * frame_slots * I2S_CLK_RATIO_MIN) { + dev_err(i2s_sc_priv->dev, + "clk freq %lu is too low, must >= (%d * %d * %d * %d = %d)\n", + i2s_clk_rate, rate, slot_width, frame_slots, I2S_CLK_RATIO_MIN, + rate * slot_width * frame_slots * I2S_CLK_RATIO_MIN); + return -EINVAL; + } + + /* Set sample rate */ + if (i2s_sc_priv->is_master_mode) { + sample_rate = DIV_ROUND_CLOSEST(i2s_clk_rate, (rate * frame_slots * slot_width)); + regmap_write(i2s_sc_priv->regmap, I2S_SRATE, + FIELD_PREP(I2S_SRATE_SAMPLE_RATE, sample_rate)); + } + + return 0; +} + +static int cdns_i2s_sc_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + scoped_guard(spinlock_irqsave, &i2s_sc_priv->lock) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdns_i2s_sc_tx_config(i2s_sc_priv, true); + else + cdns_i2s_sc_rx_config(i2s_sc_priv, true); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + scoped_guard(spinlock_irqsave, &i2s_sc_priv->lock) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdns_i2s_sc_tx_config(i2s_sc_priv, false); + else + cdns_i2s_sc_rx_config(i2s_sc_priv, false); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cdns_i2s_sc_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + u32 ctrl = 0; + + /* + * Transceiver disable + * Transceiver clock disable + * All interrupts masked + */ + ctrl &= ~(I2S_CTRL_I2S_EN | I2S_CTRL_INTREQ_MASK); + ctrl |= I2S_CTRL_I2S_STB; + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN | I2S_CTRL_INTREQ_MASK | + I2S_CTRL_LR_PACK | I2S_CTRL_I2S_STB, + ctrl); + + /* + * Full-duplex mode disable + * Full-duplex mode transmitter disable + * Full-duplex mode receiver disable + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_FULL_DUPLEX | I2S_CTRL_FDX_I2S_FTX_EN | + I2S_CTRL_FDX_I2S_FRX_EN, + 0); + + /* TDM mode disable, default works in standard stereo I2S mode */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, I2S_TDM_CTRL_TDM_EN, 0); + + snd_soc_dai_init_dma_data(cpu_dai, &i2s_sc_priv->playback_dma_data, + &i2s_sc_priv->capture_dma_data); + + snd_soc_dai_set_drvdata(cpu_dai, i2s_sc_priv); + + return 0; +} + +static const struct snd_soc_dai_ops cdns_i2s_sc_dai_ops = { + .probe = cdns_i2s_sc_dai_probe, + .set_tdm_slot = cdns_i2s_sc_set_tdm_slot, + .set_fmt = cdns_i2s_sc_set_fmt, + .hw_params = cdns_i2s_sc_hw_params, + .prepare = cdns_i2s_sc_prepare, + .trigger = cdns_i2s_sc_trigger, +}; + +static struct snd_soc_dai_driver cdns_i2s_sc_dai = { + .name = "i2s-sc-aif", + .playback = { + .stream_name = "I2S-SC-Playback", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .capture = { + .stream_name = "I2S-SC-Capture", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .ops = &cdns_i2s_sc_dai_ops, + .symmetric_rate = 1, + .symmetric_channels = 1, +}; + +static const struct snd_soc_component_driver cdns_i2s_sc_component = { + .name = DRV_NAME, + .legacy_dai_naming = 1, +}; + +static const struct reg_default cdns_i2s_sc_reg_defaults[] = { + { I2S_CTRL, 0x000001b8 }, + { I2S_CTRL_FDX, 0x00000010 }, + { I2S_SRES, 0x00000000 }, + { I2S_SRES_FDR, 0x00000000 }, + { I2S_SRATE, 0x00000000 }, + { I2S_STAT, 0x0003000c }, + { I2S_FIFO_LEVEL, 0x00000000 }, + { I2S_FIFO_AEMPTY, 0x00000000 }, + { I2S_FIFO_AFULL, 0x0000000f }, + { I2S_FIFO_LEVEL_FDR, 0x00000000 }, + { I2S_FIFO_AEMPTY_FDR, 0x00000000 }, + { I2S_FIFO_AFULL_FDR, 0x0000000f }, + { I2S_TDM_CTRL, 0xffff0000 }, + { I2S_TDM_FD_DIR, 0x0000ffff }, +}; + +static bool cdns_i2s_sc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_CTRL: + case I2S_CTRL_FDX: + case I2S_SRES: + case I2S_SRES_FDR: + case I2S_SRATE: + case I2S_STAT: + case I2S_FIFO_LEVEL: + case I2S_FIFO_AEMPTY: + case I2S_FIFO_AFULL: + case I2S_FIFO_LEVEL_FDR: + case I2S_FIFO_AEMPTY_FDR: + case I2S_FIFO_AFULL_FDR: + case I2S_TDM_CTRL: + case I2S_TDM_FD_DIR: + return true; + default: + return false; + } +} + +static bool cdns_i2s_sc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_CTRL: + case I2S_CTRL_FDX: + case I2S_SRES: + case I2S_SRES_FDR: + case I2S_SRATE: + case I2S_STAT: + case I2S_FIFO_AEMPTY: + case I2S_FIFO_AFULL: + case I2S_FIFO_AEMPTY_FDR: + case I2S_FIFO_AFULL_FDR: + case I2S_TDM_CTRL: + case I2S_TDM_FD_DIR: + return true; + default: + return false; + } +} + +static bool cdns_i2s_sc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_STAT: + case I2S_FIFO_LEVEL: + case I2S_FIFO_LEVEL_FDR: + return true; + default: + return false; + } +} + +static const struct regmap_config cdns_i2s_sc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = I2S_TDM_FD_DIR, + .reg_defaults = cdns_i2s_sc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cdns_i2s_sc_reg_defaults), + .readable_reg = cdns_i2s_sc_readable_reg, + .writeable_reg = cdns_i2s_sc_writeable_reg, + .volatile_reg = cdns_i2s_sc_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static void cdns_i2s_sc_rst(struct cdns_i2s_sc_priv *i2s_sc_priv) +{ + /* reset */ + reset_control_assert(i2s_sc_priv->i2s_rst); + + usleep_range(1, 2); + + /* release reset */ + reset_control_deassert(i2s_sc_priv->i2s_rst); +} + +static int cdns_i2s_sc_runtime_suspend(struct device *dev); + +static int cdns_i2s_sc_probe(struct platform_device *pdev) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv; + struct resource *res; + void __iomem *base; + int i, irq, ret; + + i2s_sc_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_sc_priv), GFP_KERNEL); + if (!i2s_sc_priv) + return -ENOMEM; + + spin_lock_init(&i2s_sc_priv->lock); + + platform_set_drvdata(pdev, i2s_sc_priv); + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + i2s_sc_priv->dev = &pdev->dev; + i2s_sc_priv->devtype_data = device_get_match_data(&pdev->dev); + if (!i2s_sc_priv->devtype_data) + return -ENODEV; + + i2s_sc_priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &cdns_i2s_sc_regmap_config); + if (IS_ERR(i2s_sc_priv->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->regmap), + "Failed to initialize managed register map\n"); + + i2s_sc_priv->clk_hst = devm_clk_get(&pdev->dev, "hst"); + if (IS_ERR(i2s_sc_priv->clk_hst)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->clk_hst), + "Failed to get hst clock\n"); + + i2s_sc_priv->clk_i2s = devm_clk_get(&pdev->dev, "i2s"); + if (IS_ERR(i2s_sc_priv->clk_i2s)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->clk_i2s), + "Failed to get i2s clock\n"); + + for (i = 0; i < AUDIO_CLK_NUM; i++) { + i2s_sc_priv->clks[i] = devm_clk_get(&pdev->dev, + cdns_i2s_sc_clk_pll_names[i]); + if (IS_ERR(i2s_sc_priv->clks[i])) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->clks[i]), + "Failed to get %s clock\n", + cdns_i2s_sc_clk_pll_names[i]); + } + + i2s_sc_priv->i2s_rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s_sc_priv->i2s_rst)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->i2s_rst), + "Failed to get reset control\n"); + + i2s_sc_priv->playback_dma_data.addr = res->start + I2S_FIFO_ADDRESS; + /* Buswidth will be set by framework at runtime */ + i2s_sc_priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + i2s_sc_priv->playback_dma_data.maxburst = 4; + + i2s_sc_priv->capture_dma_data.addr = res->start + I2S_FIFO_ADDRESS; + /* Buswidth will be set by framework at runtime */ + i2s_sc_priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + i2s_sc_priv->capture_dma_data.maxburst = 4; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register dmaengine component\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + i2s_sc_priv->irq = irq; + + ret = cdns_i2s_sc_clks_enable(i2s_sc_priv); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to enable clocks\n"); + + cdns_i2s_sc_rst(i2s_sc_priv); + + ret = devm_request_irq(&pdev->dev, irq, cdns_i2s_sc_isr, 0, + dev_name(&pdev->dev), i2s_sc_priv); + if (ret) { + cdns_i2s_sc_clks_disable(i2s_sc_priv); + return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n"); + } + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &cdns_i2s_sc_component, + &cdns_i2s_sc_dai, 1); + if (ret) + goto err_pm; + + pm_runtime_put_sync(&pdev->dev); + + return 0; + +err_pm: + cdns_i2s_sc_runtime_suspend(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + return dev_err_probe(&pdev->dev, ret, "Failed to register soc component\n"); +} + +static int cdns_i2s_sc_runtime_suspend(struct device *dev) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = dev_get_drvdata(dev); + + /* + * Disable the IRQ before switching the regcache to cache-only, so an + * in-flight handler can never access the registers through a + * cache-only regmap. disable_irq() waits for the handler to finish. + */ + disable_irq(i2s_sc_priv->irq); + regcache_cache_only(i2s_sc_priv->regmap, true); + cdns_i2s_sc_clks_disable(i2s_sc_priv); + + return 0; +} + +static int cdns_i2s_sc_runtime_resume(struct device *dev) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = dev_get_drvdata(dev); + int ret; + + ret = cdns_i2s_sc_clks_enable(i2s_sc_priv); + if (ret) { + dev_err(dev, "Failed to enable clocks:%d\n", ret); + return ret; + } + + cdns_i2s_sc_rst(i2s_sc_priv); + + regcache_cache_only(i2s_sc_priv->regmap, false); + regcache_mark_dirty(i2s_sc_priv->regmap); + + ret = regcache_sync(i2s_sc_priv->regmap); + if (ret) { + dev_err(dev, "Failed to sync regcache: %d\n", ret); + regcache_cache_only(i2s_sc_priv->regmap, true); + cdns_i2s_sc_clks_disable(i2s_sc_priv); + return ret; + } + + /* Enable the IRQ only after the regcache is back in sync */ + enable_irq(i2s_sc_priv->irq); + + return 0; +} + +static int cdns_i2s_sc_system_suspend(struct device *dev) +{ + int ret; + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int cdns_i2s_sc_system_resume(struct device *dev) +{ + int ret; + + pinctrl_pm_select_default_state(dev); + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + return 0; +} + +static const struct dev_pm_ops cdns_i2s_sc_pm_ops = { + RUNTIME_PM_OPS(cdns_i2s_sc_runtime_suspend, cdns_i2s_sc_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(cdns_i2s_sc_system_suspend, cdns_i2s_sc_system_resume) +}; + +static const struct cdns_i2s_sc_devtype_data sky1_devtype_data = { + .rx_fifo_aempty_threshold = 4, + .rx_fifo_afull_threshold = 12, + .tx_fifo_aempty_threshold = 4, + .tx_fifo_afull_threshold = 12, +}; + +static const struct of_device_id cdns_i2s_sc_of_match[] = { + { .compatible = "cix,sky1-i2s-sc", .data = &sky1_devtype_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cdns_i2s_sc_of_match); + +static void cdns_i2s_sc_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + cdns_i2s_sc_runtime_suspend(&pdev->dev); +} + +static struct platform_driver cdns_i2s_sc_driver = { + .probe = cdns_i2s_sc_probe, + .remove = cdns_i2s_sc_remove, + .driver = { + .name = DRV_NAME, + .pm = pm_ptr(&cdns_i2s_sc_pm_ops), + .of_match_table = cdns_i2s_sc_of_match, + }, +}; +module_platform_driver(cdns_i2s_sc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Joakim Zhang "); +MODULE_DESCRIPTION("Cadence I2S-SC Controller Driver"); diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index b4e8aae56805e6..b08baacad87b5e 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -23,7 +23,7 @@ const struct regmap_config aw88399_remap_config = { .reg_format_endian = REGMAP_ENDIAN_LITTLE, .val_format_endian = REGMAP_ENDIAN_BIG, }; -EXPORT_SYMBOL_GPL(aw88399_remap_config); +EXPORT_SYMBOL_NS_GPL(aw88399_remap_config, "SND_SOC_AW88399_LIB"); static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) { @@ -165,7 +165,7 @@ int aw_dev_check_syspll(struct aw_device *aw_dev) return 0; } -EXPORT_SYMBOL_GPL(aw_dev_check_syspll); +EXPORT_SYMBOL_NS_GPL(aw_dev_check_syspll, "SND_SOC_AW88399_LIB"); static int aw_dev_check_sysst(struct aw_device *aw_dev) { @@ -241,7 +241,7 @@ void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) if (ret) dev_dbg(aw_dev->dev, "%s failed\n", __func__); } -EXPORT_SYMBOL_GPL(aw_dev_dsp_enable); +EXPORT_SYMBOL_NS_GPL(aw_dev_dsp_enable, "SND_SOC_AW88399_LIB"); static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) { @@ -418,7 +418,7 @@ int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) return ret; } -EXPORT_SYMBOL_GPL(aw_dev_update_cali_re); +EXPORT_SYMBOL_NS_GPL(aw_dev_update_cali_re, "SND_SOC_AW88399_LIB"); static int aw_dev_fw_crc_check(struct aw_device *aw_dev) { @@ -572,7 +572,7 @@ int aw_dev_get_dsp_status(struct aw_device *aw_dev) return 0; } -EXPORT_SYMBOL_GPL(aw_dev_get_dsp_status); +EXPORT_SYMBOL_NS_GPL(aw_dev_get_dsp_status, "SND_SOC_AW88399_LIB"); static int aw_dev_dsp_check(struct aw_device *aw_dev) { @@ -625,7 +625,7 @@ int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) return ret; } -EXPORT_SYMBOL_GPL(aw_dev_set_volume); +EXPORT_SYMBOL_NS_GPL(aw_dev_set_volume, "SND_SOC_AW88399_LIB"); static void aw_dev_fade_in(struct aw_device *aw_dev) { @@ -682,7 +682,7 @@ void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) aw_dev_fade_in(aw_dev); } } -EXPORT_SYMBOL_GPL(aw88399_dev_mute); +EXPORT_SYMBOL_NS_GPL(aw88399_dev_mute, "SND_SOC_AW88399_LIB"); static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) { @@ -1038,7 +1038,7 @@ int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_n return 0; } -EXPORT_SYMBOL_GPL(aw88399_dev_get_prof_name); +EXPORT_SYMBOL_NS_GPL(aw88399_dev_get_prof_name, "SND_SOC_AW88399_LIB"); static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, struct aw_prof_desc **prof_desc) @@ -1163,7 +1163,7 @@ void aw88399_startup_work(struct work_struct *work) aw88399_start_pa(aw88399); mutex_unlock(&aw88399->lock); } -EXPORT_SYMBOL_GPL(aw88399_startup_work); +EXPORT_SYMBOL_NS_GPL(aw88399_startup_work, "SND_SOC_AW88399_LIB"); void aw88399_start(struct aw88399 *aw88399, bool sync_start) { @@ -1191,7 +1191,7 @@ void aw88399_start(struct aw88399 *aw88399, bool sync_start) &aw88399->start_work, AW88399_START_WORK_DELAY_MS); } -EXPORT_SYMBOL_GPL(aw88399_start); +EXPORT_SYMBOL_NS_GPL(aw88399_start, "SND_SOC_AW88399_LIB"); static int aw_dev_check_sysint(struct aw_device *aw_dev) { @@ -1244,7 +1244,7 @@ int aw88399_stop(struct aw_device *aw_dev) return 0; } -EXPORT_SYMBOL_GPL(aw88399_stop); +EXPORT_SYMBOL_NS_GPL(aw88399_stop, "SND_SOC_AW88399_LIB"); static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) { @@ -1323,7 +1323,7 @@ int aw88399_request_firmware_file(struct aw88399 *aw88399, const char *fw_name) return ret; } -EXPORT_SYMBOL_GPL(aw88399_request_firmware_file); +EXPORT_SYMBOL_NS_GPL(aw88399_request_firmware_file, "SND_SOC_AW88399_LIB"); void aw88399_hw_reset(struct aw88399 *aw88399) { @@ -1336,7 +1336,7 @@ void aw88399_hw_reset(struct aw88399 *aw88399) usleep_range(AW88399_1000_US, AW88399_1000_US + 10); } } -EXPORT_SYMBOL_GPL(aw88399_hw_reset); +EXPORT_SYMBOL_NS_GPL(aw88399_hw_reset, "SND_SOC_AW88399_LIB"); static void aw88399_parse_channel_dt(struct aw_device *aw_dev) { @@ -1401,13 +1401,13 @@ int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap return 0; } -EXPORT_SYMBOL_GPL(aw88399_init); +EXPORT_SYMBOL_NS_GPL(aw88399_init, "SND_SOC_AW88399_LIB"); void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel) { aw88399->aw_pa->channel = channel; } -EXPORT_SYMBOL_GPL(aw88399_dev_set_channel); +EXPORT_SYMBOL_NS_GPL(aw88399_dev_set_channel, "SND_SOC_AW88399_LIB"); MODULE_DESCRIPTION("AW88399 common device library"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c index 320dc1b87841c9..c398f66a526255 100644 --- a/sound/soc/codecs/aw88399.c +++ b/sound/soc/codecs/aw88399.c @@ -818,4 +818,5 @@ static struct i2c_driver aw88399_i2c_driver = { module_i2c_driver(aw88399_i2c_driver); MODULE_DESCRIPTION("ASoC AW88399 Smart PA Driver"); +MODULE_IMPORT_NS("SND_SOC_AW88399_LIB"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index eaaf141dea57e3..0e7c05f5bab607 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1754,14 +1754,14 @@ static const struct snd_soc_component_driver soc_codec_dev_cpcap = { static int cpcap_codec_probe(struct platform_device *pdev) { - struct device_node *codec_node = + struct device_node *codec_node __free(device_node) = of_get_child_by_name(pdev->dev.parent->of_node, "audio-codec"); struct cpcap_audio *cpcap; if (!codec_node) return -ENODEV; - pdev->dev.of_node = codec_node; + platform_device_set_of_node(pdev, codec_node); cpcap = devm_kzalloc(&pdev->dev, sizeof(*cpcap), GFP_KERNEL); if (!cpcap) diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c index b0fa80705be7d2..973713626ebd43 100644 --- a/sound/soc/codecs/cs35l41-lib.c +++ b/sound/soc/codecs/cs35l41-lib.c @@ -17,6 +17,8 @@ #include #define CS35L41_FIRMWARE_OLD_VERSION 0x001C00 /* v0.28.0 */ +#define CS35L41_GLOBAL_BOOT_DONE_TIMEOUT_US 100000 +#define CS35L41_GLOBAL_BOOT_DONE_SLEEP_US 2000 static const struct reg_default cs35l41_reg[] = { { CS35L41_PWR_CTRL1, 0x00000000 }, @@ -1586,6 +1588,71 @@ int cs35l41_exit_hibernate(struct device *dev, struct regmap *regmap) } EXPORT_SYMBOL_GPL(cs35l41_exit_hibernate); +int cs35l41_wait_boot_done(struct device *dev, struct regmap *regmap) +{ + ktime_t timeout; + unsigned int val = 0; + int ret = 0; + + /* + * The chip could NAK a read request while it is booting so ignore + * errors from regmap_read. + */ + timeout = ktime_add_us(ktime_get(), CS35L41_GLOBAL_BOOT_DONE_TIMEOUT_US); + regmap_read(regmap, CS35L41_IRQ1_STATUS4, &val); + while (!(val & CS35L41_OTP_BOOT_DONE) && !ktime_after(ktime_get(), timeout)) { + usleep_range(CS35L41_GLOBAL_BOOT_DONE_SLEEP_US / 2, + CS35L41_GLOBAL_BOOT_DONE_SLEEP_US); + regmap_read(regmap, CS35L41_IRQ1_STATUS4, &val); + } + + if (!(val & CS35L41_OTP_BOOT_DONE)) { + dev_err(dev, "Timeout waiting for OTP_BOOT_DONE\n"); + return -ETIMEDOUT; + } + + ret = regmap_read(regmap, CS35L41_IRQ1_STATUS3, &val); + if (ret || (val & CS35L41_OTP_BOOT_ERR)) { + dev_err(dev, "OTP Boot status %x error\n", + val & CS35L41_OTP_BOOT_ERR); + if (!ret) + ret = -EIO; + return ret; + } + return 0; +} +EXPORT_SYMBOL_GPL(cs35l41_wait_boot_done); + +int cs35l41_reset_and_boot(struct device *dev, struct regmap *regmap) +{ + int ret; + + ret = regmap_write(regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); + if (ret) { + dev_warn(dev, "Failed to write software reset: %d\n", ret); + + /* + * If the write failed, it is likely because the chip is still booting after a + * reset. Wait for boot, then retry the software reset. If the initial write failed + * for another reason, then either waiting for boot, or the second write will fail, + * So we can return the error then. + */ + ret = cs35l41_wait_boot_done(dev, regmap); + if (ret) + return ret; + + ret = regmap_write(regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); + if (ret) { + dev_err(dev, "Failed to write software reset on retry: %d\n", ret); + return ret; + } + } + usleep_range(2000, 2100); + + return cs35l41_wait_boot_done(dev, regmap); +} +EXPORT_SYMBOL_GPL(cs35l41_reset_and_boot); + MODULE_DESCRIPTION("CS35L41 library"); MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, "); MODULE_AUTHOR("Lucas Tanure, Cirrus Logic Inc, "); diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index d286025183f466..0e40444a10034d 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -1211,7 +1211,7 @@ static int cs35l41_get_system_name(struct cs35l41_private *cs35l41) int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *hw_cfg) { - u32 regid, reg_revid, i, mtl_revid, int_status, chipid_match; + u32 regid, reg_revid, i, mtl_revid, chipid_match; int irq_pol = 0; int ret; @@ -1260,21 +1260,9 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * usleep_range(2000, 2100); - ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, - int_status, int_status & CS35L41_OTP_BOOT_DONE, - 1000, 100000); - if (ret) { - dev_err_probe(cs35l41->dev, ret, - "Failed waiting for OTP_BOOT_DONE\n"); - goto err; - } - - regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status); - if (int_status & CS35L41_OTP_BOOT_ERR) { - dev_err(cs35l41->dev, "OTP Boot error\n"); - ret = -EINVAL; + ret = cs35l41_reset_and_boot(cs35l41->dev, cs35l41->regmap); + if (ret) goto err; - } ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id); if (ret < 0) { diff --git a/sound/soc/codecs/madera.c b/sound/soc/codecs/madera.c index 58255844b4df37..2f6256f8e87b2a 100644 --- a/sound/soc/codecs/madera.c +++ b/sound/soc/codecs/madera.c @@ -4608,8 +4608,17 @@ static int madera_fllhj_enable(struct madera_fll *fll) MADERA_FLL1_HOLD_MASK, 0); - if (!already_enabled) - madera_wait_for_fll(fll, true); + /* + * If configuring the FLL failed it is left disabled, so a later + * madera_fllhj_disable() will not see the ENA bit set and will not + * drop the runtime PM reference taken above. + */ + if (!already_enabled) { + if (ret) + pm_runtime_put_autosuspend(madera->dev); + else + madera_wait_for_fll(fll, true); + } return 0; } diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c index 6f45ea4869a75d..c1a263216ea6d8 100644 --- a/sound/soc/codecs/pcm3168a.c +++ b/sound/soc/codecs/pcm3168a.c @@ -725,16 +725,11 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap) dev_set_drvdata(dev, pcm3168a); /* - * Request the reset (connected to RST pin) gpio line as non exclusive - * as the same reset line might be connected to multiple pcm3168a codec - * * The RST is low active, we want the GPIO line to be high initially, so * request the initial level to LOW which in practice means DEASSERTED: * The deasserted level of GPIO_ACTIVE_LOW is HIGH. */ - pcm3168a->gpio_rst = devm_gpiod_get_optional(dev, "reset", - GPIOD_OUT_LOW | - GPIOD_FLAGS_BIT_NONEXCLUSIVE); + pcm3168a->gpio_rst = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(pcm3168a->gpio_rst)) return dev_err_probe(dev, PTR_ERR(pcm3168a->gpio_rst), "failed to acquire RST gpio\n"); diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index f3e20919042b4e..24194a8ec1a9f7 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -1772,24 +1772,31 @@ static int pcm512x_resume(struct device *dev) pcm512x->supplies); if (ret != 0) { dev_err(dev, "Failed to enable supplies: %d\n", ret); - return ret; + goto err_sclk; } regcache_cache_only(pcm512x->regmap, false); ret = regcache_sync(pcm512x->regmap); if (ret != 0) { dev_err(dev, "Failed to sync cache: %d\n", ret); - return ret; + goto err_supplies; } ret = regmap_update_bits(pcm512x->regmap, PCM512x_POWER, PCM512x_RQPD, 0); if (ret != 0) { dev_err(dev, "Failed to remove power down: %d\n", ret); - return ret; + goto err_supplies; } return 0; + +err_supplies: + regulator_bulk_disable(ARRAY_SIZE(pcm512x->supplies), pcm512x->supplies); +err_sclk: + if (!IS_ERR(pcm512x->sclk)) + clk_disable_unprepare(pcm512x->sclk); + return ret; } EXPORT_GPL_DEV_PM_OPS(pcm512x_pm_ops) = { diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index fd1f840d6794ea..91316537d68ee8 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -4864,17 +4864,10 @@ static void rt5677_init_gpio(struct i2c_client *i2c) rt5677->gpio_chip.parent = &i2c->dev; rt5677->gpio_chip.base = -1; - ret = gpiochip_add_data(&rt5677->gpio_chip, rt5677); + ret = devm_gpiochip_add_data(&i2c->dev, &rt5677->gpio_chip, rt5677); if (ret != 0) dev_err(&i2c->dev, "Failed to add GPIOs: %d\n", ret); } - -static void rt5677_free_gpio(struct i2c_client *i2c) -{ - struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); - - gpiochip_remove(&rt5677->gpio_chip); -} #else static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, int value) @@ -4884,10 +4877,6 @@ static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, static void rt5677_init_gpio(struct i2c_client *i2c) { } - -static void rt5677_free_gpio(struct i2c_client *i2c) -{ -} #endif static int rt5677_probe(struct snd_soc_component *component) @@ -5730,11 +5719,6 @@ static int rt5677_i2c_probe(struct i2c_client *i2c) rt5677_dai, ARRAY_SIZE(rt5677_dai)); } -static void rt5677_i2c_remove(struct i2c_client *i2c) -{ - rt5677_free_gpio(i2c); -} - static struct i2c_driver rt5677_i2c_driver = { .driver = { .name = RT5677_DRV_NAME, @@ -5743,7 +5727,6 @@ static struct i2c_driver rt5677_i2c_driver = { }, .id_table = rt5677_i2c_id, .probe = rt5677_i2c_probe, - .remove = rt5677_i2c_remove, }; module_i2c_driver(rt5677_i2c_driver); diff --git a/sound/soc/codecs/sn624x-sdca-sdw.c b/sound/soc/codecs/sn624x-sdca-sdw.c index cbe0af7062dc80..65ea90e440008e 100644 --- a/sound/soc/codecs/sn624x-sdca-sdw.c +++ b/sound/soc/codecs/sn624x-sdca-sdw.c @@ -1736,11 +1736,9 @@ static int sn624x_sdca_sdw_probe(struct sdw_slave *slave, regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave, &sn624x_sdca_regmap, &sn624x_sdca_mbq_cfg); - if (IS_ERR(regmap)) { - dev_err(&slave->dev, "sn624x: devm_regmap_init_sdw_mbq_cfg failed (%ld)\n", - PTR_ERR(regmap)); - return PTR_ERR(regmap); - } + if (IS_ERR(regmap)) + return dev_err_probe(&slave->dev, PTR_ERR(regmap), + "devm_regmap_init_sdw_mbq_cfg failed\n"); ret = sn624x_sdca_init(&slave->dev, regmap, slave); if (ret < 0) diff --git a/sound/soc/codecs/tlv320adcx140.c b/sound/soc/codecs/tlv320adcx140.c index c29584d77649b6..511d691923f2da 100644 --- a/sound/soc/codecs/tlv320adcx140.c +++ b/sound/soc/codecs/tlv320adcx140.c @@ -1018,7 +1018,7 @@ static int adcx140_codec_probe(struct snd_soc_component *component) u32 pdm_edges[ADCX140_NUM_PDM_EDGES]; u32 pdm_edge_val = 0; int gpi_count; - u32 gpi_inputs[ADCX140_NUM_GPI_PINS]; + u32 gpi_inputs[ADCX140_NUM_GPI_PINS] = {}; u32 gpi_input_val = 0; int i; int ret; diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 101bc952aa1446..72feb0ca427a0a 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -104,6 +104,7 @@ config SND_SOC_FSL_EASRC config SND_SOC_FSL_XCVR tristate "NXP Audio Transceiver (XCVR) module support" + select BITREVERSE select REGMAP_MMIO select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n select SND_SOC_GENERIC_DMAENGINE_PCM @@ -116,6 +117,8 @@ config SND_SOC_FSL_XCVR config SND_SOC_FSL_AUD2HTX tristate "AUDIO TO HDMI TX module support" depends on ARCH_MXC || COMPILE_TEST + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n help Say Y if you want to add AUDIO TO HDMI TX support for NXP. @@ -138,6 +141,7 @@ config SND_SOC_FSL_RPMSG config SND_SOC_FSL_LPC3XXX tristate "SoC Audio for NXP LPC32XX CPUs" depends on ARCH_LPC32XX || COMPILE_TEST + select REGMAP_MMIO select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y or M if you want to add support for the LPC3XXX I2S interface. diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index b417de6abebe4a..e5b7b283c16b7b 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -76,8 +76,7 @@ static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream, return -ENOMEM; } - pair->desc[!dir]->callback = fsl_asrc_dma_complete; - pair->desc[!dir]->callback_param = substream; + dmaengine_desc_set_callback(pair->desc[!dir], fsl_asrc_dma_complete, NULL, substream); dmaengine_submit(pair->desc[!dir]); @@ -251,7 +250,7 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, /* Get DMA request of Front-End */ tmp_chan = asrc->get_dma_channel(pair, dir); - if (!tmp_chan) { + if (IS_ERR(tmp_chan)) { dma_release_channel(pair->dma_chan[!dir]); pair->dma_chan[!dir] = NULL; return -EINVAL; diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 58101ce07c9f9f..796a78124765f4 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -176,8 +176,7 @@ static int asrc_dmaconfig(struct fsl_asrc_pair *pair, return -EINVAL; } - pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir); - pair->desc[dir]->callback_param = pair; + dmaengine_desc_set_callback(pair->desc[dir], ASRC_xPUT_DMA_CALLBACK(dir), NULL, pair); return 0; } @@ -258,12 +257,12 @@ static int asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_task /* Submit DMA request */ dmaengine_submit(pair->desc[IN]); - dma_async_issue_pending(pair->desc[IN]->chan); + dma_async_issue_pending(pair->dma_chan[IN]); if (out_dma_len > 0) { if (asrc->start_before_dma && asrc->m2m_output_ready) asrc->m2m_output_ready(pair); dmaengine_submit(pair->desc[OUT]); - dma_async_issue_pending(pair->desc[OUT]->chan); + dma_async_issue_pending(pair->dma_chan[OUT]); } if (!asrc->start_before_dma) diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index 4a530a6c33f07b..83a996377135c7 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -1099,11 +1100,11 @@ static void fsl_esai_remove(struct platform_device *pdev) { struct fsl_esai *esai_priv = platform_get_drvdata(pdev); + disable_work_sync(&esai_priv->work); + pm_runtime_disable(&pdev->dev); if (!pm_runtime_status_suspended(&pdev->dev)) fsl_esai_runtime_suspend(&pdev->dev); - - cancel_work_sync(&esai_priv->work); } static const struct of_device_id fsl_esai_dt_ids[] = { diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index d232c8f5306154..430e896d064e3b 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -88,14 +88,16 @@ static const struct snd_kcontrol_new fsl_sai_timestamp_ctrls[] = { }; /** - * fsl_sai_dir_is_synced - Check if stream is synced by the opposite stream + * fsl_sai_dir_is_synced - Check if the opposite stream is synced to @dir + * @sai: SAI context + * @dir: stream direction, TX or RX * - * SAI supports synchronous mode using bit/frame clocks of either Transmitter's - * or Receiver's for both streams. This function is used to check if clocks of - * the stream's are synced by the opposite stream. + * SAI supports synchronous mode, where one direction uses the bit clock and + * frame sync of the other. This function checks whether @dir provides the + * clocks, i.e. @dir runs in asynchronous mode while the opposite direction + * runs in synchronous mode. * - * @sai: SAI context - * @dir: stream direction + * Return: true if the opposite direction uses the clocks of @dir. */ static inline bool fsl_sai_dir_is_synced(struct fsl_sai *sai, int dir) { @@ -146,7 +148,8 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid) mask = (FSL_SAI_FLAGS >> FSL_SAI_CSR_xIE_SHIFT) << FSL_SAI_CSR_xF_SHIFT; /* Tx IRQ */ - regmap_read(sai->regmap, FSL_SAI_TCSR(ofs), &xcsr); + if (regmap_read(sai->regmap, FSL_SAI_TCSR(ofs), &xcsr)) + return IRQ_NONE; flags = xcsr & mask; if (flags) @@ -177,7 +180,8 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid) irq_rx: /* Rx IRQ */ - regmap_read(sai->regmap, FSL_SAI_RCSR(ofs), &xcsr); + if (regmap_read(sai->regmap, FSL_SAI_RCSR(ofs), &xcsr)) + goto out; flags = xcsr & mask; if (flags) @@ -628,8 +632,14 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, if (sai->slots[tx]) slots = sai->slots[tx]; - else if (sai->bclk_ratio) + else if (sai->bclk_ratio) { + if (sai->bclk_ratio < slot_width) { + dev_err(cpu_dai->dev, "bclk ratio %u < slot width %u\n", + sai->bclk_ratio, slot_width); + return -EINVAL; + } slots = sai->bclk_ratio / slot_width; + } pins = DIV_ROUND_UP(channels, slots); @@ -710,8 +720,7 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, if (fsl_sai_dir_is_synced(sai, adir)) { regmap_update_bits(sai->regmap, FSL_SAI_xCR4(!tx, ofs), - FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | - FSL_SAI_CR4_CHMOD_MASK, + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, val_cr4); regmap_update_bits(sai->regmap, FSL_SAI_xCR5(!tx, ofs), FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | @@ -849,7 +858,8 @@ static void fsl_sai_config_disable(struct fsl_sai *sai, int dir) { unsigned int ofs = sai->soc_data->reg_offset; bool tx = dir == TX; - u32 xcsr, count = 100, mask; + u32 xcsr, mask; + int ret; if (sai->soc_data->mclk_with_tere && sai->mclk_direction_output) mask = FSL_SAI_CSR_TERE; @@ -860,10 +870,15 @@ static void fsl_sai_config_disable(struct fsl_sai *sai, int dir) mask, 0); /* TERE will remain set till the end of current frame */ - do { - udelay(10); - regmap_read(sai->regmap, FSL_SAI_xCSR(tx, ofs), &xcsr); - } while (--count && xcsr & FSL_SAI_CSR_TERE); + ret = regmap_read_poll_timeout_atomic(sai->regmap, FSL_SAI_xCSR(tx, ofs), + xcsr, !(xcsr & FSL_SAI_CSR_TERE), + 10, 1000); + if (ret == -ETIMEDOUT) + dev_warn(&sai->pdev->dev, "%cx not disabled at frame end\n", + tx ? 'T' : 'R'); + else if (ret) + dev_err(&sai->pdev->dev, "failed to read %cSCR : %d\n", + tx ? 'T' : 'R', ret); regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs), FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); @@ -1495,24 +1510,21 @@ static int fsl_sai_read_dlcfg(struct fsl_sai *sai) */ ret = of_property_read_u32_index(np, propname, index++, &type); if (ret) - return -EINVAL; + return ret; ret = of_property_read_u32_index(np, propname, index++, &rx); if (ret) - return -EINVAL; + return ret; ret = of_property_read_u32_index(np, propname, index++, &tx); if (ret) - return -EINVAL; + return ret; if ((rx & ~soc_dl) || (tx & ~soc_dl)) { dev_err(dev, "dataline cfg[%d] setting error, mask is 0x%x\n", i, soc_dl); return -EINVAL; } - rx = rx & soc_dl; - tx = tx & soc_dl; - cfg[i].type = type; cfg[i].pins[0] = hweight8(rx); cfg[i].mask[0] = rx; @@ -1589,12 +1601,10 @@ static int fsl_sai_probe(struct platform_device *pdev) for (i = 1; i < FSL_SAI_MCLK_MAX; i++) { sprintf(tmp, "mclk%d", i); - sai->mclk_clk[i] = devm_clk_get(dev, tmp); - if (IS_ERR(sai->mclk_clk[i])) { - dev_err(dev, "failed to get mclk%d clock: %ld\n", - i, PTR_ERR(sai->mclk_clk[i])); - sai->mclk_clk[i] = NULL; - } + sai->mclk_clk[i] = devm_clk_get_optional(dev, tmp); + if (IS_ERR(sai->mclk_clk[i])) + return dev_err_probe(dev, PTR_ERR(sai->mclk_clk[i]), + "failed to get mclk%d clock\n", i); } if (sai->soc_data->mclk0_is_mclk1) @@ -1617,10 +1627,8 @@ static int fsl_sai_probe(struct platform_device *pdev) /* read dataline mask for rx and tx*/ ret = fsl_sai_read_dlcfg(sai); - if (ret < 0) { - dev_err(dev, "failed to read dlcfg %d\n", ret); + if (ret < 0) return ret; - } irq = platform_get_irq(pdev, 0); if (irq < 0) @@ -1700,9 +1708,7 @@ static int fsl_sai_probe(struct platform_device *pdev) if (ret < 0) dev_warn(dev, "Error reading SAI version: %d\n", ret); - ret = fsl_sai_reset_hw(dev); - if (ret < 0) - dev_warn(dev, "Failed to reset hardware: %d\n", ret); + fsl_sai_reset_hw(dev); /* Select MCLK direction */ if (sai->mclk_direction_output && @@ -1717,10 +1723,8 @@ static int fsl_sai_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, fsl_sai_isr, IRQF_SHARED, np->name, sai); - if (ret) { - dev_err(dev, "failed to claim irq %u\n", irq); + if (ret) goto err_pm_get_sync; - } if (of_device_is_compatible(np, "fsl,imx952-sai") && !of_property_read_string(np, "fsl,sai-amix-mode", &str)) { @@ -1981,7 +1985,7 @@ static int fsl_sai_runtime_resume(struct device *dev) ret = regcache_sync(sai->regmap); if (ret) - goto disable_rx_clk; + goto remove_qos; if (sai->soc_data->mclk_with_tere && sai->mclk_direction_output) regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs), @@ -1989,7 +1993,12 @@ static int fsl_sai_runtime_resume(struct device *dev) return 0; -disable_rx_clk: +remove_qos: + regcache_cache_only(sai->regmap, true); + + if (sai->soc_data->flags & PMQOS_CPU_LATENCY) + cpu_latency_qos_remove_request(&sai->pm_qos_req); + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]); disable_tx_clk: diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c index f8335a04595a76..d46f43ab66cf62 100644 --- a/sound/soc/fsl/imx-audmux.c +++ b/sound/soc/fsl/imx-audmux.c @@ -361,18 +361,7 @@ static struct platform_driver imx_audmux_driver = { .of_match_table = imx_audmux_dt_ids, } }; - -static int __init imx_audmux_init(void) -{ - return platform_driver_register(&imx_audmux_driver); -} -subsys_initcall(imx_audmux_init); - -static void __exit imx_audmux_exit(void) -{ - platform_driver_unregister(&imx_audmux_driver); -} -module_exit(imx_audmux_exit); +subsys_platform_driver(imx_audmux_driver); MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver"); MODULE_AUTHOR("Sascha Hauer "); diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c index 8bcf54ef709e1f..ac568210b4e649 100644 --- a/sound/soc/fsl/imx-sgtl5000.c +++ b/sound/soc/fsl/imx-sgtl5000.c @@ -20,7 +20,6 @@ struct imx_sgtl5000_data { struct snd_soc_card card; char codec_dai_name[DAI_NAME_SIZE]; char platform_name[DAI_NAME_SIZE]; - struct clk *codec_clk; unsigned int clk_frequency; }; @@ -54,8 +53,9 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) struct device_node *ssi_np, *codec_np; struct platform_device *ssi_pdev; struct i2c_client *codec_dev; - struct imx_sgtl5000_data *data = NULL; + struct imx_sgtl5000_data *data; struct snd_soc_dai_link_component *comp; + struct clk *codec_clk; int int_port, ext_port; int ret; @@ -129,13 +129,14 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) goto put_device; } - data->codec_clk = clk_get(&codec_dev->dev, NULL); - if (IS_ERR(data->codec_clk)) { - ret = PTR_ERR(data->codec_clk); + codec_clk = clk_get(&codec_dev->dev, NULL); + if (IS_ERR(codec_clk)) { + ret = PTR_ERR(codec_clk); goto put_device; } - data->clk_frequency = clk_get_rate(data->codec_clk); + data->clk_frequency = clk_get_rate(codec_clk); + clk_put(codec_clk); data->dai.cpus = &comp[0]; data->dai.codecs = &comp[1]; @@ -177,6 +178,8 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) goto put_device; } + put_device(&codec_dev->dev); + of_node_put(ssi_np); of_node_put(codec_np); @@ -185,22 +188,12 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) put_device: put_device(&codec_dev->dev); fail: - if (data && !IS_ERR(data->codec_clk)) - clk_put(data->codec_clk); of_node_put(ssi_np); of_node_put(codec_np); return ret; } -static void imx_sgtl5000_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card); - - clk_put(data->codec_clk); -} - static const struct of_device_id imx_sgtl5000_dt_ids[] = { { .compatible = "fsl,imx-audio-sgtl5000", }, { /* sentinel */ } @@ -214,7 +207,6 @@ static struct platform_driver imx_sgtl5000_driver = { .of_match_table = imx_sgtl5000_dt_ids, }, .probe = imx_sgtl5000_probe, - .remove = imx_sgtl5000_remove, }; module_platform_driver(imx_sgtl5000_driver); diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index 503170578cff81..f20301c3a4412b 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -1199,8 +1199,9 @@ static int graph_count_c2c(struct simple_util_priv *priv, struct link_info *li) { struct device_node *ports __free(device_node) = port_to_ports(lnk); - struct device_node *port0 = of_node_get(lnk); - struct device_node *port1 = of_node_get(of_graph_get_next_port(ports, of_node_get(port0))); + struct device_node *port0 __free(device_node) = of_node_get(lnk); + struct device_node *port1 __free(device_node) = + of_graph_get_next_port(ports, of_node_get(port0)); struct device_node *ep0 __free(device_node) = of_graph_get_next_port_endpoint(port0, NULL); struct device_node *ep1 __free(device_node) = of_graph_get_next_port_endpoint(port1, NULL); struct device_node *codec0 __free(device_node) = of_graph_get_remote_port(ep0); diff --git a/sound/soc/intel/boards/sof_cirrus_common.c b/sound/soc/intel/boards/sof_cirrus_common.c index c7e77e20a09645..12124917b5526b 100644 --- a/sound/soc/intel/boards/sof_cirrus_common.c +++ b/sound/soc/intel/boards/sof_cirrus_common.c @@ -45,6 +45,8 @@ static struct snd_soc_dai_link_component cs35l41_components[CS35L41_MAX_AMPS]; */ static struct snd_soc_codec_conf cs35l41_codec_conf[CS35L41_MAX_AMPS]; +static char cs35l41_physdev_names[CS35L41_MAX_AMPS][64]; + static int cs35l41_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_card *card = rtd->card; @@ -175,9 +177,11 @@ static int cs35l41_compute_codec_conf(void) uid, cs35l41_name_prefixes[uid]); return 0; } - cs35l41_components[sz].name = dev_name(physdev); + strscpy(cs35l41_physdev_names[sz], dev_name(physdev)); + put_device(physdev); + cs35l41_components[sz].name = cs35l41_physdev_names[sz]; cs35l41_components[sz].dai_name = CS35L41_CODEC_DAI; - cs35l41_codec_conf[sz].dlc.name = dev_name(physdev); + cs35l41_codec_conf[sz].dlc.name = cs35l41_physdev_names[sz]; cs35l41_codec_conf[sz].name_prefix = cs35l41_name_prefixes[uid]; sz++; } diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 58beaf906b9ffd..94af3b79c61dc1 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -562,6 +562,8 @@ static int q6apm_dai_open(struct snd_soc_component *component, return 0; err: + if (!IS_ERR_OR_NULL(prtd->graph)) + q6apm_graph_close(prtd->graph); kfree(prtd); return ret; @@ -832,7 +834,7 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE * COMPR_PLAYBACK_MAX_NUM_FRAGMENTS; ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, &prtd->dma_buffer); if (ret) - return ret; + goto err_close_graph; if (pdata->use_scm_assign && !pdata->has_reserved_mem) { ret = q6apm_dai_scm_assign(pdata, prtd->dma_buffer.addr, @@ -841,7 +843,7 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, dev_err(dev, "SCM assign compr buffer failed: %d\n", ret); snd_dma_free_pages(&prtd->dma_buffer); - return ret; + goto err_close_graph; } } @@ -855,6 +857,13 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, q6apm_enable_compress_module(dev, prtd->graph, true); return 0; + +err_close_graph: + runtime->private_data = NULL; + q6apm_graph_close(prtd->graph); + kfree(prtd); + + return ret; } static int q6apm_dai_compr_free(struct snd_soc_component *component, diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 83f2711bceeaaf..9f6d17f80de357 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -173,11 +173,16 @@ static int q6dma_hw_params(struct snd_pcm_substream *substream, struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; int channels = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS)->max; + int i; cfg->bit_width = params_width(params); cfg->sample_rate = params_rate(params); cfg->num_channels = channels; + memset(cfg->channel_map, 0, sizeof(cfg->channel_map)); audioreach_set_default_channel_mapping(cfg->channel_map, channels); + for (i = 0; i < channels && i < AR_PCM_MAX_NUM_CHANNEL; i++) + if (!cfg->channel_map[i]) + cfg->channel_map[i] = i + 1; return 0; } diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index ea511200c8d72c..0a55d1f384d4d8 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -1220,7 +1220,7 @@ static int q6pcm_routing_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - routing_data = kzalloc_obj(*routing_data); + routing_data = devm_kzalloc_obj(dev, *routing_data); if (!routing_data) return -ENOMEM; @@ -1235,7 +1235,6 @@ static int q6pcm_routing_probe(struct platform_device *pdev) static void q6pcm_routing_remove(struct platform_device *pdev) { - kfree(routing_data); routing_data = NULL; } diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c index e226dd1263643a..5e051a23868a7f 100644 --- a/sound/soc/qcom/qdsp6/q6usb.c +++ b/sound/soc/qcom/qdsp6/q6usb.c @@ -82,6 +82,7 @@ static int q6usb_hw_params(struct snd_pcm_substream *substream, /* Notify audio DSP about the devices being offloaded */ ret = afe_port_send_usb_dev_param(q6usb_afe, sdev->card_idx, sdev->ppcm_idx[sdev->num_playback - 1]); + q6afe_port_put(q6usb_afe); out: mutex_unlock(&data->mutex); diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c index 74e2ea002fbb1d..fa21cf47befffd 100644 --- a/sound/soc/renesas/fsi.c +++ b/sound/soc/renesas/fsi.c @@ -1375,8 +1375,7 @@ static int fsi_dma_transfer(struct fsi_priv *fsi, struct fsi_stream *io) goto fsi_dma_transfer_err; } - desc->callback = fsi_dma_complete; - desc->callback_param = io; + dmaengine_desc_set_callback(desc, fsi_dma_complete, NULL, io); if (dmaengine_submit(desc) < 0) { dev_err(dai->dev, "tx_submit() fail\n"); diff --git a/sound/soc/renesas/siu_pcm.c b/sound/soc/renesas/siu_pcm.c index fd5b20b469a4ad..7208514e16a43d 100644 --- a/sound/soc/renesas/siu_pcm.c +++ b/sound/soc/renesas/siu_pcm.c @@ -125,8 +125,7 @@ static int siu_pcm_wr_set(struct siu_port *port_info, return -ENOMEM; } - desc->callback = siu_dma_tx_complete; - desc->callback_param = siu_stream; + dmaengine_desc_set_callback(desc, siu_dma_tx_complete, NULL, siu_stream); cookie = dmaengine_submit(desc); if (cookie < 0) { dev_err(dev, "Failed to submit a dma transfer\n"); @@ -175,8 +174,7 @@ static int siu_pcm_rd_set(struct siu_port *port_info, return -ENOMEM; } - desc->callback = siu_dma_tx_complete; - desc->callback_param = siu_stream; + dmaengine_desc_set_callback(desc, siu_dma_tx_complete, NULL, siu_stream); cookie = dmaengine_submit(desc); if (cookie < 0) { dev_err(dev, "Failed to submit dma descriptor\n"); diff --git a/sound/soc/samsung/snow.c b/sound/soc/samsung/snow.c index 66ef49dff1ba37..ad99c153e9f8c7 100644 --- a/sound/soc/samsung/snow.c +++ b/sound/soc/samsung/snow.c @@ -211,9 +211,11 @@ static int snow_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(card, priv); ret = devm_snd_soc_register_card(dev, card); - if (ret) + if (ret) { + clk_put(priv->clk_i2s_bus); return dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n"); + } return 0; } diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 282d666dae9ec5..6137662cdb484b 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -6,10 +6,18 @@ // Kuninori Morimoto // +#include +#include #include +#include #include +#include #include +#include #include +#include "soc-internal.h" + +static LIST_HEAD(unbind_card_list); #define soc_card_ret(dai, ret) _soc_card_ret(dai, __func__, ret) static inline int _soc_card_ret(struct snd_soc_card *card, @@ -19,6 +27,591 @@ static inline int _soc_card_ret(struct snd_soc_card *card, "at %s() on %s\n", func, card->name); } +#ifdef CONFIG_DEBUG_FS +static void snd_soc_card_debugfs_init(struct snd_soc_card *card) +{ + card->debugfs_card_root = debugfs_create_dir(card->name, + snd_soc_debugfs_root); + + snd_soc_dapm_debugfs_init(snd_soc_card_to_dapm(card), card->debugfs_card_root); +} + +static void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card) +{ + debugfs_remove_recursive(card->debugfs_card_root); + card->debugfs_card_root = NULL; +} +#else +static inline void snd_soc_card_debugfs_init(struct snd_soc_card *card) { } +static inline void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card) { } +#endif /* CONFIG_DEBUG_FS */ + +#ifdef CONFIG_PM_SLEEP +/* + * deferred resume work, so resume can complete before we finished + * setting our codec back up, which can be very slow on I2C + */ +static void snd_soc_card_resume_deferred(struct work_struct *work) +{ + struct snd_soc_card *card = + container_of(work, struct snd_soc_card, + deferred_resume_work); + struct snd_soc_component *component; + + /* + * our power state is still SNDRV_CTL_POWER_D3hot from suspend time, + * so userspace apps are blocked from touching us + */ + + dev_dbg(card->dev, "ASoC: starting resume work\n"); + + /* Bring us up into D2 so that DAPM starts enabling things */ + snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2); + + snd_soc_card_resume_pre(card); + + for_each_card_components(card, component) { + if (snd_soc_component_is_suspended(component)) + snd_soc_component_resume(component); + } + + snd_soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_RESUME); + + /* unmute any active DACs */ + snd_soc_playback_digital_mute(card, 0); + + snd_soc_card_resume_post(card); + + dev_dbg(card->dev, "ASoC: resume work completed\n"); + + /* Recheck all endpoints too, their state is affected by suspend */ + snd_soc_dapm_mark_endpoints_dirty(card); + snd_soc_dapm_sync(snd_soc_card_to_dapm(card)); + + /* userspace can access us now we are back as we were before */ + snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0); +} + +static void snd_soc_card_resume_init(struct snd_soc_card *card) +{ + /* deferred resume work */ + INIT_WORK(&card->deferred_resume_work, snd_soc_card_resume_deferred); +} +#else +static inline void snd_soc_card_resume_init(struct snd_soc_card *card) { } +#endif /* CONFIG_PM_SLEEP */ + +static void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link; + int i; + + /* + * COMP_DUMMY() creates size 0 array on dai_link. + * Fill it as dummy DAI in case of CPU/Codec here. + * Do nothing for Platform. + */ + for_each_card_prelinks(card, i, dai_link) { + if (dai_link->num_cpus == 0 && dai_link->cpus) { + dai_link->num_cpus = 1; + dai_link->cpus = &snd_soc_dummy_dlc; + } + if (dai_link->num_codecs == 0 && dai_link->codecs) { + dai_link->num_codecs = 1; + dai_link->codecs = &snd_soc_dummy_dlc; + } + } +} + +static int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai_link *dai_link = rtd->dai_link; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int ret; + + /* do machine specific initialization */ + ret = snd_soc_link_init(rtd); + if (ret < 0) + return ret; + + ret = snd_soc_runtime_set_dai_fmt(rtd, snd_soc_dai_auto_select_format(rtd)); + if (ret) + goto err; + + /* add DPCM sysfs entries */ + soc_dpcm_debugfs_add(rtd); + + /* create compress_device if driver is supporting */ + ret = snd_soc_dai_compress_new(cpu_dai, rtd); + if (ret != -ENOTSUPP) { /* driver is supporting */ + if (ret < 0) + goto err; + goto end; + } + + /* create the pcm */ + ret = soc_new_pcm(rtd); + if (ret < 0) { + dev_err(card->dev, "ASoC: can't create pcm %s :%d\n", + dai_link->stream_name, ret); + goto err; + } + + ret = snd_soc_pcm_dai_new(rtd); + if (ret < 0) + goto err; +end: + rtd->initialized = true; + + return 0; +err: + snd_soc_link_exit(rtd); + + return ret; +} + +static void snd_soc_card_link_dais_remove(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + int order; + + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + /* remove all rtd connected DAIs in good order */ + snd_soc_pcm_dai_remove(rtd, order); + } + } +} + +static int snd_soc_card_link_dais_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + int order, ret; + + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + /* probe all rtd connected DAIs in good order */ + ret = snd_soc_pcm_dai_probe(rtd, order); + if (ret) + return ret; + } + } + + return 0; +} + +static void snd_soc_card_link_components_remove(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_pcm_runtime *rtd; + int i, order; + + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + for_each_rtd_components(rtd, i, component) { + if (component->driver->remove_order != order) + continue; + + snd_soc_component_remove(component, 1); + } + } + } +} + +static int snd_soc_card_link_components_probe(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_pcm_runtime *rtd; + int i, ret, order; + + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + for_each_rtd_components(rtd, i, component) { + if (component->driver->probe_order != order) + continue; + + ret = snd_soc_component_probe(component, card); + if (ret < 0) + return ret; + } + } + } + + return 0; +} + +static void snd_soc_card_aux_unbind(struct snd_soc_card *card) +{ + struct snd_soc_component *component, *_component; + + for_each_card_auxs_safe(card, component, _component) { + /* for snd_soc_component_init() */ + snd_soc_component_set_aux(component, NULL); + list_del(&component->card_aux_list); + } +} + +static int snd_soc_card_aux_bind(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_aux_dev *aux; + int i; + + for_each_card_pre_auxs(card, i, aux) { + /* codecs, usually analog devices */ + component = snd_soc_find_component(&aux->dlc); + if (!component) + return -EPROBE_DEFER; + + /* for snd_soc_component_init() */ + snd_soc_component_set_aux(component, aux); + /* see for_each_card_auxs */ + list_add(&component->card_aux_list, &card->aux_comp_list); + } + return 0; +} + +static int snd_soc_card_aux_probe(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + int order; + int ret; + + for_each_comp_order(order) { + for_each_card_auxs(card, component) { + if (component->driver->probe_order != order) + continue; + + ret = snd_soc_component_probe(component, card); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static void snd_soc_card_aux_remove(struct snd_soc_card *card) +{ + struct snd_soc_component *comp, *_comp; + int order; + + for_each_comp_order(order) { + for_each_card_auxs_safe(card, comp, _comp) { + if (comp->driver->remove_order == order) + snd_soc_component_remove(comp, 1); + } + } +} + +#ifdef CONFIG_DMI +/* + * If a DMI filed contain strings in this blacklist (e.g. + * "Type2 - Board Manufacturer" or "Type1 - TBD by OEM"), it will be taken + * as invalid and dropped when setting the card long name from DMI info. + */ +static const char * const dmi_blacklist[] = { + "To be filled by OEM", + "TBD by OEM", + "Default String", + "Board Manufacturer", + "Board Vendor Name", + "Board Product Name", + NULL, /* terminator */ +}; + +/* + * Trim special characters, and replace '-' with '_' since '-' is used to + * separate different DMI fields in the card long name. Only number and + * alphabet characters and a few separator characters are kept. + */ +static void cleanup_dmi_name(char *name) +{ + int i, j = 0; + + for (i = 0; name[i]; i++) { + if (isalnum(name[i]) || (name[i] == '.') + || (name[i] == '_')) + name[j++] = name[i]; + else if (name[i] == '-') + name[j++] = '_'; + } + + name[j] = '\0'; +} + +/* + * Check if a DMI field is valid, i.e. not containing any string + * in the black list and not the empty string. + */ +static int is_dmi_valid(const char *field) +{ + int i = 0; + + if (!field[0]) + return 0; + + while (dmi_blacklist[i]) { + if (strstr(field, dmi_blacklist[i])) + return 0; + i++; + } + + return 1; +} + +/* + * Append a string to dmi_longname with character cleanups. + */ +#define DMI_LONGNAME_LEN 80 +static void append_dmi_string(char *dst, const char *str) +{ + size_t dst_len = DMI_LONGNAME_LEN; + size_t len; + + len = strlen(dst); + snprintf(dst + len, dst_len - len, "-%s", str); + + len++; /* skip the separator "-" */ + if (len < dst_len) + cleanup_dmi_name(dst + len); +} + +/** + * snd_soc_card_set_dmi_name() - Register DMI names to card + * @card: The card to register DMI names + * + * An Intel machine driver may be used by many different devices but are + * difficult for userspace to differentiate, since machine drivers usually + * use their own name as the card short name and leave the card long name + * blank. To differentiate such devices and fix bugs due to lack of + * device-specific configurations, this function allows DMI info to be used + * as the sound card long name, in the format of + * "vendor-product-version-board" + * (Character '-' is used to separate different DMI fields here). + * This will help the user space to load the device-specific Use Case Manager + * (UCM) configurations for the card. + * + * Possible card long names may be: + * DellInc.-XPS139343-01-0310JH + * ASUSTeKCOMPUTERINC.-T100TA-1.0-T100TA + * Circuitco-MinnowboardMaxD0PLATFORM-D0-MinnowBoardMAX + * + * This function also supports flavoring the card longname to provide + * the extra differentiation, like "vendor-product-version-board-flavor". + * + * We only keep number and alphabet characters and a few separator characters + * in the card long name since UCM in the user space uses the card long names + * as card configuration directory names and AudoConf cannot support special + * characters like SPACE. + * + * Returns 0 on success, otherwise a negative error code. + */ +static int snd_soc_card_set_dmi_name(struct snd_soc_card *card) +{ + const char *vendor, *product, *board; + char *dmi_longname; + + if (card->long_name) + return 0; /* long name already set by driver or from DMI */ + + if (!dmi_available) + return 0; + + /* make up dmi long name as: vendor-product-version-board */ + vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + if (!vendor || !is_dmi_valid(vendor)) { + dev_warn(card->dev, "ASoC: no DMI vendor name!\n"); + return 0; + } + + dmi_longname = devm_kzalloc(card->dev, DMI_LONGNAME_LEN, GFP_KERNEL); + if (!dmi_longname) + return -ENOMEM; + + snprintf(dmi_longname, DMI_LONGNAME_LEN, "%s", vendor); + cleanup_dmi_name(dmi_longname); + + product = dmi_get_system_info(DMI_PRODUCT_NAME); + if (product && is_dmi_valid(product)) { + const char *product_version = dmi_get_system_info(DMI_PRODUCT_VERSION); + + append_dmi_string(dmi_longname, product); + + /* + * some vendors like Lenovo may only put a self-explanatory + * name in the product version field + */ + if (product_version && is_dmi_valid(product_version)) + append_dmi_string(dmi_longname, product_version); + } + + board = dmi_get_system_info(DMI_BOARD_NAME); + if (board && is_dmi_valid(board)) { + if (!product || strcasecmp(board, product)) + append_dmi_string(dmi_longname, board); + } else if (!product) { + /* fall back to using legacy name */ + dev_warn(card->dev, "ASoC: no DMI board/product name!\n"); + return 0; + } + + /* set the card long name */ + card->long_name = dmi_longname; + + return 0; +} +#else +static inline int snd_soc_card_set_dmi_name(struct snd_soc_card *card) +{ + return 0; +} +#endif /* CONFIG_DMI */ + +static void soc_card_check_tplg_fes(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_dai_link *dai_link; + int i; + + for_each_component(component) { + + /* does this component override BEs ? */ + if (!component->driver->ignore_machine) + continue; + + /* for this machine ? */ + if (!strcmp(component->driver->ignore_machine, + card->dev->driver->name)) + goto match; + if (strcmp(component->driver->ignore_machine, + dev_name(card->dev))) + continue; +match: + /* machine matches, so override the rtd data */ + for_each_card_prelinks(card, i, dai_link) { + + /* ignore this FE */ + if (dai_link->dynamic) { + dai_link->ignore = true; + continue; + } + + dev_dbg(card->dev, "info: override BE DAI link %s\n", + dai_link->name); + + /* override platform component */ + if (!dai_link->platforms) { + dev_err(card->dev, "init platform error"); + continue; + } + + if (component->dev->of_node) + dai_link->platforms->of_node = component->dev->of_node; + else + dai_link->platforms->name = component->name; + + /* convert non BE into BE */ + dai_link->no_pcm = 1; + + /* + * override any BE fixups + * see + * snd_soc_link_be_hw_params_fixup() + */ + dai_link->be_hw_params_fixup = + component->driver->be_hw_params_fixup; + + /* + * most BE links don't set stream name, so set it to + * dai link name if it's NULL to help bind widgets. + */ + if (!dai_link->stream_name) + dai_link->stream_name = dai_link->name; + } + + /* Inform userspace we are using alternate topology */ + snd_soc_card_set_topology_name(card, component->driver->topology_name_prefix); + } +} + +#define soc_card_setup_name(name, name1, name2, norm) \ + __soc_card_setup_name(name, sizeof(name), name1, name2, norm) +static void __soc_card_setup_name(char *name, int len, + const char *name1, const char *name2, + int normalization) +{ + const char *src = name1 ? name1 : name2; + int i; + + snprintf(name, len, "%s", src); + + if (!normalization) + return; + + /* + * Name normalization + * + * The driver name is somewhat special, as it's used as a key for + * searches in the user-space. + * + * ex) + * "abcd??efg" -> "abcd__efg" + */ + for (i = 0; i < len; i++) { + switch (name[i]) { + case '_': + case '-': + case '\0': + break; + default: + if (!isalnum(name[i])) + name[i] = '_'; + break; + } + } +} + +static void snd_soc_card_cleanup_resources(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd, *n; + + if (card->snd_card) + snd_card_disconnect_sync(card->snd_card); + + snd_soc_dapm_shutdown(card); + + /* release machine specific resources */ + for_each_card_rtds(card, rtd) + if (rtd->initialized) + snd_soc_link_exit(rtd); + /* flush delayed work before removing DAIs and DAPM widgets */ + snd_soc_flush_all_delayed_work(card); + + /* remove and free each DAI */ + snd_soc_card_link_dais_remove(card); + snd_soc_card_link_components_remove(card); + + for_each_card_rtds_safe(card, rtd, n) + snd_soc_remove_pcm_runtime(card, rtd); + + /* remove auxiliary devices */ + snd_soc_card_aux_remove(card); + snd_soc_card_aux_unbind(card); + + snd_soc_dapm_free(snd_soc_card_to_dapm(card)); + snd_soc_card_debugfs_cleanup(card); + + /* remove the card */ + snd_soc_card_remove(card); + + if (card->snd_card) { + snd_card_free(card->snd_card); + card->snd_card = NULL; + } +} + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { @@ -151,7 +744,7 @@ int snd_soc_card_probe(struct snd_soc_card *card) * about "late_probe". * * see - * snd_soc_bind_card() + * snd_soc_card_bind() * snd_soc_card_late_probe() */ card->probed = 1; @@ -176,7 +769,7 @@ int snd_soc_card_late_probe(struct snd_soc_card *card) * for all cases. * * see - * snd_soc_bind_card() + * snd_soc_card_bind() * snd_soc_card_probe() */ card->probed = 1; @@ -259,3 +852,291 @@ void snd_soc_card_set_topology_name(struct snd_soc_card *card, const char *prefi card->name = card->topology_shortname; } EXPORT_SYMBOL_GPL(snd_soc_card_set_topology_name); + +/** + * snd_soc_card_add_controls - add an array of controls to a SoC card. + * Convenience function to add a list of controls. + * + * @soc_card: SoC card to add controls to + * @controls: array of controls to add + * @num_controls: number of elements in the array + * + * Return 0 for success, else error. + */ +int snd_soc_card_add_controls(struct snd_soc_card *soc_card, + const struct snd_kcontrol_new *controls, int num_controls) +{ + struct snd_card *card = soc_card->snd_card; + + return snd_soc_add_controls(card, soc_card->dev, controls, num_controls, NULL, soc_card); +} +EXPORT_SYMBOL_GPL(snd_soc_card_add_controls); + +static void snd_soc_remove_device_links(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) { + if (component->card_device_link) { + device_link_del(component->card_device_link); + component->card_device_link = NULL; + } + } +} + +void snd_soc_card_unbind(struct snd_soc_card *card, bool reuse) +{ + if (snd_soc_card_is_instantiated(card)) { + card->instantiated = false; + + snd_soc_remove_device_links(card); + + snd_soc_card_cleanup_resources(card); + + if (reuse) + list_add(&card->list, &unbind_card_list); + } + + if (!reuse) + list_del(&card->list); +} + +static int snd_soc_card_bind(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *component; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + int ret; + + snd_soc_card_mutex_lock_root(card); + snd_soc_card_fill_dummy_dai(card); + + snd_soc_dapm_init(dapm, card, NULL); + list_del_init(&card->list); + + /* check whether any platform is ignore machine FE and using topology */ + soc_card_check_tplg_fes(card); + + /* bind aux_devs too */ + ret = snd_soc_card_aux_bind(card); + if (ret < 0) + goto probe_end; + + /* add predefined DAI links to the list */ + card->num_rtd = 0; + ret = snd_soc_add_pcm_runtimes(card, card->dai_link, card->num_links); + if (ret < 0) + goto probe_end; + + /* card bind complete so register a sound card */ + ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + card->owner, 0, &card->snd_card); + if (ret < 0) { + dev_err(card->dev, + "ASoC: can't create sound card for card %s: %d\n", + card->name, ret); + goto probe_end; + } + + snd_soc_card_debugfs_init(card); + + snd_soc_card_resume_init(card); + + ret = snd_soc_dapm_new_controls(dapm, card->dapm_widgets, + card->num_dapm_widgets); + if (ret < 0) + goto probe_end; + + ret = snd_soc_dapm_new_controls(dapm, card->of_dapm_widgets, + card->num_of_dapm_widgets); + if (ret < 0) + goto probe_end; + + /* initialise the sound card only once */ + ret = snd_soc_card_probe(card); + if (ret < 0) + goto probe_end; + + /* probe all components used by DAI links on this card */ + ret = snd_soc_card_link_components_probe(card); + if (ret < 0) { + if (ret != -EPROBE_DEFER) { + dev_err(card->dev, + "ASoC: failed to instantiate card %d\n", ret); + } + goto probe_end; + } + + /* probe auxiliary components */ + ret = snd_soc_card_aux_probe(card); + if (ret < 0) { + dev_err(card->dev, + "ASoC: failed to probe aux component %d\n", ret); + goto probe_end; + } + + /* probe all DAI links on this card */ + ret = snd_soc_card_link_dais_probe(card); + if (ret < 0) { + dev_err(card->dev, + "ASoC: failed to instantiate card %d\n", ret); + goto probe_end; + } + + for_each_card_rtds(card, rtd) { + ret = snd_soc_card_init_pcm_runtime(card, rtd); + if (ret < 0) + goto probe_end; + } + + snd_soc_dapm_link_dai_widgets(card); + snd_soc_dapm_connect_dai_link_widgets(card); + + ret = snd_soc_add_card_controls(card, card->controls, + card->num_controls); + if (ret < 0) + goto probe_end; + + ret = snd_soc_dapm_add_routes(dapm, card->dapm_routes, + card->num_dapm_routes); + if (ret < 0) + goto probe_end; + + ret = snd_soc_dapm_add_routes(dapm, card->of_dapm_routes, + card->num_of_dapm_routes); + if (ret < 0) + goto probe_end; + + /* try to set some sane longname if DMI is available */ + snd_soc_card_set_dmi_name(card); + + soc_card_setup_name(card->snd_card->shortname, + card->name, NULL, 0); + soc_card_setup_name(card->snd_card->longname, + card->long_name, card->name, 0); + soc_card_setup_name(card->snd_card->driver, + card->driver_name, card->name, 1); + + if (card->components) { + /* the current implementation of snd_component_add() accepts */ + /* multiple components in the string separated by space, */ + /* but the string collision (identical string) check might */ + /* not work correctly */ + ret = snd_component_add(card->snd_card, card->components); + if (ret < 0) { + dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n", + card->name, ret); + goto probe_end; + } + } + + /* + * Add device_link from card to component so that system_suspend + * will be done in the correct order. The card must suspend first + * to stop audio activity before the components suspend. + * + * If a driver pair already have a link in the opposite direction + * they must manage their own suspend order. + */ + for_each_card_components(card, component) { + if (card->dev == component->dev) + continue; + + component->card_device_link = device_link_add(card->dev, + component->dev, + DL_FLAG_STATELESS); + if (!component->card_device_link) { + dev_warn(card->dev, "Could not create device link to %s\n", + dev_name(component->dev)); + } + } + + ret = snd_soc_card_late_probe(card); + if (ret < 0) + goto probe_end; + + ret = snd_soc_dapm_ignore_suspend_widgets(card); + if (ret < 0) + goto probe_end; + + snd_soc_dapm_new_widgets(card); + for_each_card_components(card, component) { + ret = snd_soc_component_fixup_controls(component); + if (ret < 0) + goto probe_end; + } + snd_soc_card_fixup_controls(card); + + ret = snd_card_register(card->snd_card); + if (ret < 0) { + dev_err(card->dev, "ASoC: failed to register soundcard %d\n", + ret); + goto probe_end; + } + + card->instantiated = 1; + snd_soc_dapm_mark_endpoints_dirty(card); + snd_soc_dapm_sync(dapm); + + /* deactivate pins to sleep state */ + for_each_card_components(card, component) + if (!snd_soc_component_active(component)) + pinctrl_pm_select_sleep_state(component->dev); + +probe_end: + if (ret < 0) { + snd_soc_remove_device_links(card); + snd_soc_card_cleanup_resources(card); + } + + if (ret == -EPROBE_DEFER) { + list_add(&card->list, &unbind_card_list); + ret = 0; + } + snd_soc_card_mutex_unlock(card); + + return ret; +} + +void snd_soc_card_rebind(void) +{ + struct snd_soc_card *card, *c; + + list_for_each_entry_safe(card, c, &unbind_card_list, list) + snd_soc_card_bind_call(card); +} + +static void devm_snd_soc_card_bind_release(struct device *dev, void *res) +{ + snd_soc_unregister_card(*(struct snd_soc_card **)res); +} + +static int devm_snd_soc_card_bind(struct device *dev, struct snd_soc_card *card) +{ + struct snd_soc_card **ptr; + int ret; + + /* The procedure may be called many times during the lifetime of the card. */ + devres_destroy(dev, devm_snd_soc_card_bind_release, NULL, NULL); + + ptr = devres_alloc(devm_snd_soc_card_bind_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = snd_soc_card_bind(card); + if (ret == 0) { + *ptr = card; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} + +int snd_soc_card_bind_call(struct snd_soc_card *card) +{ + if (card->devres_dev) + return devm_snd_soc_card_bind(card->devres_dev, card); + return snd_soc_card_bind(card); +} diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 2c43c8c0e16ad7..ac72d9357840ac 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -39,15 +38,12 @@ #include #include #include -#include -#include #include "soc-internal.h" #define CREATE_TRACE_POINTS #include DEFINE_MUTEX(client_mutex); -static LIST_HEAD(unbind_card_list); /* * This is used if driver don't need to have CPU/Codec/Platform @@ -154,20 +150,6 @@ static int component_list_show(struct seq_file *m, void *v) } DEFINE_SHOW_ATTRIBUTE(component_list); -static void soc_init_card_debugfs(struct snd_soc_card *card) -{ - card->debugfs_card_root = debugfs_create_dir(card->name, - snd_soc_debugfs_root); - - snd_soc_dapm_debugfs_init(snd_soc_card_to_dapm(card), card->debugfs_card_root); -} - -static void soc_cleanup_card_debugfs(struct snd_soc_card *card) -{ - debugfs_remove_recursive(card->debugfs_card_root); - card->debugfs_card_root = NULL; -} - static void snd_soc_debugfs_init(void) { snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); @@ -188,8 +170,6 @@ static void snd_soc_debugfs_exit(void) #else -static inline void soc_init_card_debugfs(struct snd_soc_card *card) { } -static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) { } static inline void snd_soc_debugfs_init(void) { } static inline void snd_soc_debugfs_exit(void) { } @@ -442,29 +422,7 @@ static struct snd_soc_pcm_runtime *soc_new_pcm_runtime( return NULL; } -static void snd_soc_fill_dummy_dai(struct snd_soc_card *card) -{ - struct snd_soc_dai_link *dai_link; - int i; - - /* - * COMP_DUMMY() creates size 0 array on dai_link. - * Fill it as dummy DAI in case of CPU/Codec here. - * Do nothing for Platform. - */ - for_each_card_prelinks(card, i, dai_link) { - if (dai_link->num_cpus == 0 && dai_link->cpus) { - dai_link->num_cpus = 1; - dai_link->cpus = &snd_soc_dummy_dlc; - } - if (dai_link->num_codecs == 0 && dai_link->codecs) { - dai_link->num_codecs = 1; - dai_link->codecs = &snd_soc_dummy_dlc; - } - } -} - -static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card) +void snd_soc_flush_all_delayed_work(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; @@ -473,7 +431,7 @@ static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card) } #ifdef CONFIG_PM_SLEEP -static void soc_playback_digital_mute(struct snd_soc_card *card, int mute) +void snd_soc_playback_digital_mute(struct snd_soc_card *card, int mute) { struct snd_soc_pcm_runtime *rtd; struct snd_soc_dai *dai; @@ -492,7 +450,7 @@ static void soc_playback_digital_mute(struct snd_soc_card *card, int mute) } } -static void soc_dapm_suspend_resume(struct snd_soc_card *card, int event) +void snd_soc_dapm_suspend_resume(struct snd_soc_card *card, int event) { struct snd_soc_pcm_runtime *rtd; int stream; @@ -529,7 +487,7 @@ int snd_soc_suspend(struct device *dev) snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D3hot); /* mute any active DACs */ - soc_playback_digital_mute(card, 1); + snd_soc_playback_digital_mute(card, 1); /* suspend all pcms */ for_each_card_rtds(card, rtd) { @@ -544,7 +502,7 @@ int snd_soc_suspend(struct device *dev) /* close any waiting streams */ snd_soc_flush_all_delayed_work(card); - soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_SUSPEND); + snd_soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_SUSPEND); /* Recheck all endpoints too, their state is affected by suspend */ snd_soc_dapm_mark_endpoints_dirty(card); @@ -605,51 +563,6 @@ int snd_soc_suspend(struct device *dev) } EXPORT_SYMBOL_GPL(snd_soc_suspend); -/* - * deferred resume work, so resume can complete before we finished - * setting our codec back up, which can be very slow on I2C - */ -static void soc_resume_deferred(struct work_struct *work) -{ - struct snd_soc_card *card = - container_of(work, struct snd_soc_card, - deferred_resume_work); - struct snd_soc_component *component; - - /* - * our power state is still SNDRV_CTL_POWER_D3hot from suspend time, - * so userspace apps are blocked from touching us - */ - - dev_dbg(card->dev, "ASoC: starting resume work\n"); - - /* Bring us up into D2 so that DAPM starts enabling things */ - snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2); - - snd_soc_card_resume_pre(card); - - for_each_card_components(card, component) { - if (snd_soc_component_is_suspended(component)) - snd_soc_component_resume(component); - } - - soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_RESUME); - - /* unmute any active DACs */ - soc_playback_digital_mute(card, 0); - - snd_soc_card_resume_post(card); - - dev_dbg(card->dev, "ASoC: resume work completed\n"); - - /* Recheck all endpoints too, their state is affected by suspend */ - snd_soc_dapm_mark_endpoints_dirty(card); - snd_soc_dapm_sync(snd_soc_card_to_dapm(card)); - - /* userspace can access us now we are back as we were before */ - snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0); -} - /* powers up audio subsystem after a suspend */ int snd_soc_resume(struct device *dev) { @@ -673,15 +586,9 @@ int snd_soc_resume(struct device *dev) } EXPORT_SYMBOL_GPL(snd_soc_resume); -static void soc_resume_init(struct snd_soc_card *card) -{ - /* deferred resume work */ - INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); -} #else #define snd_soc_suspend NULL #define snd_soc_resume NULL -static inline void soc_resume_init(struct snd_soc_card *card) { } #endif struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, @@ -698,8 +605,7 @@ struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, } EXPORT_SYMBOL_GPL(snd_soc_copy_dai_args); -static struct snd_soc_component *soc_find_component( - const struct snd_soc_dai_link_component *dlc) +struct snd_soc_component *snd_soc_find_component(const struct snd_soc_dai_link_component *dlc) { struct snd_soc_component *component; @@ -785,7 +691,7 @@ static int soc_dai_link_sanity_check(struct snd_soc_card *card, * Defer card registration if codec component is not added to * component list. */ - if (!soc_find_component(dlc)) + if (!snd_soc_find_component(dlc)) goto component_not_found; } @@ -806,7 +712,7 @@ static int soc_dai_link_sanity_check(struct snd_soc_card *card, * Defer card registration if platform component is not added to * component list. */ - if (!soc_find_component(dlc)) + if (!snd_soc_find_component(dlc)) goto component_not_found; } @@ -831,7 +737,7 @@ static int soc_dai_link_sanity_check(struct snd_soc_card *card, /* * Defer card registration if Component is not added */ - if (!soc_find_component(dlc)) + if (!snd_soc_find_component(dlc)) goto component_not_found; } } @@ -859,7 +765,7 @@ static int soc_dai_link_sanity_check(struct snd_soc_card *card, return -EINVAL; } -#define MAX_DEFAULT_CH_MAP_SIZE 8 +#define MAX_DEFAULT_CH_MAP_SIZE 12 static struct snd_soc_dai_link_ch_map default_ch_map_sync[MAX_DEFAULT_CH_MAP_SIZE] = { { .cpu = 0, .codec = 0 }, { .cpu = 1, .codec = 1 }, @@ -869,6 +775,10 @@ static struct snd_soc_dai_link_ch_map default_ch_map_sync[MAX_DEFAULT_CH_MAP_SIZ { .cpu = 5, .codec = 5 }, { .cpu = 6, .codec = 6 }, { .cpu = 7, .codec = 7 }, + { .cpu = 8, .codec = 8 }, + { .cpu = 9, .codec = 9 }, + { .cpu = 10, .codec = 10 }, + { .cpu = 11, .codec = 11 }, }; static struct snd_soc_dai_link_ch_map default_ch_map_1cpu[MAX_DEFAULT_CH_MAP_SIZE] = { { .cpu = 0, .codec = 0 }, @@ -879,6 +789,10 @@ static struct snd_soc_dai_link_ch_map default_ch_map_1cpu[MAX_DEFAULT_CH_MAP_SIZ { .cpu = 0, .codec = 5 }, { .cpu = 0, .codec = 6 }, { .cpu = 0, .codec = 7 }, + { .cpu = 0, .codec = 8 }, + { .cpu = 0, .codec = 9 }, + { .cpu = 0, .codec = 10 }, + { .cpu = 0, .codec = 11 }, }; static struct snd_soc_dai_link_ch_map default_ch_map_1codec[MAX_DEFAULT_CH_MAP_SIZE] = { { .cpu = 0, .codec = 0 }, @@ -889,6 +803,10 @@ static struct snd_soc_dai_link_ch_map default_ch_map_1codec[MAX_DEFAULT_CH_MAP_S { .cpu = 5, .codec = 0 }, { .cpu = 6, .codec = 0 }, { .cpu = 7, .codec = 0 }, + { .cpu = 8, .codec = 0 }, + { .cpu = 9, .codec = 0 }, + { .cpu = 10, .codec = 0 }, + { .cpu = 11, .codec = 0 }, }; static int snd_soc_compensate_channel_connection_map(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) @@ -1165,745 +1083,6 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); -static int soc_init_pcm_runtime(struct snd_soc_card *card, - struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_dai_link *dai_link = rtd->dai_link; - struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - int ret; - - /* do machine specific initialization */ - ret = snd_soc_link_init(rtd); - if (ret < 0) - return ret; - - ret = snd_soc_runtime_set_dai_fmt(rtd, snd_soc_dai_auto_select_format(rtd)); - if (ret) - goto err; - - /* add DPCM sysfs entries */ - soc_dpcm_debugfs_add(rtd); - - /* create compress_device if possible */ - ret = snd_soc_dai_compress_new(cpu_dai, rtd); - if (ret != -ENOTSUPP) - goto err; - - /* create the pcm */ - ret = soc_new_pcm(rtd); - if (ret < 0) { - dev_err(card->dev, "ASoC: can't create pcm %s :%d\n", - dai_link->stream_name, ret); - goto err; - } - - ret = snd_soc_pcm_dai_new(rtd); - if (ret < 0) - goto err; - - rtd->initialized = true; - - return 0; -err: - snd_soc_link_exit(rtd); - return ret; -} - -static void soc_remove_link_dais(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd; - int order; - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - /* remove all rtd connected DAIs in good order */ - snd_soc_pcm_dai_remove(rtd, order); - } - } -} - -static int soc_probe_link_dais(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd; - int order, ret; - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - /* probe all rtd connected DAIs in good order */ - ret = snd_soc_pcm_dai_probe(rtd, order); - if (ret) - return ret; - } - } - - return 0; -} - -static void soc_remove_link_components(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - struct snd_soc_pcm_runtime *rtd; - int i, order; - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - for_each_rtd_components(rtd, i, component) { - if (component->driver->remove_order != order) - continue; - - snd_soc_component_remove(component, 1); - } - } - } -} - -static int soc_probe_link_components(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - struct snd_soc_pcm_runtime *rtd; - int i, ret, order; - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - for_each_rtd_components(rtd, i, component) { - if (component->driver->probe_order != order) - continue; - - ret = snd_soc_component_probe(component, card); - if (ret < 0) - return ret; - } - } - } - - return 0; -} - -static void soc_unbind_aux_dev(struct snd_soc_card *card) -{ - struct snd_soc_component *component, *_component; - - for_each_card_auxs_safe(card, component, _component) { - /* for snd_soc_component_init() */ - snd_soc_component_set_aux(component, NULL); - list_del(&component->card_aux_list); - } -} - -static int soc_bind_aux_dev(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - struct snd_soc_aux_dev *aux; - int i; - - for_each_card_pre_auxs(card, i, aux) { - /* codecs, usually analog devices */ - component = soc_find_component(&aux->dlc); - if (!component) - return -EPROBE_DEFER; - - /* for snd_soc_component_init() */ - snd_soc_component_set_aux(component, aux); - /* see for_each_card_auxs */ - list_add(&component->card_aux_list, &card->aux_comp_list); - } - return 0; -} - -static int soc_probe_aux_devices(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - int order; - int ret; - - for_each_comp_order(order) { - for_each_card_auxs(card, component) { - if (component->driver->probe_order != order) - continue; - - ret = snd_soc_component_probe(component, card); - if (ret < 0) - return ret; - } - } - - return 0; -} - -static void soc_remove_aux_devices(struct snd_soc_card *card) -{ - struct snd_soc_component *comp, *_comp; - int order; - - for_each_comp_order(order) { - for_each_card_auxs_safe(card, comp, _comp) { - if (comp->driver->remove_order == order) - snd_soc_component_remove(comp, 1); - } - } -} - -#ifdef CONFIG_DMI -/* - * If a DMI filed contain strings in this blacklist (e.g. - * "Type2 - Board Manufacturer" or "Type1 - TBD by OEM"), it will be taken - * as invalid and dropped when setting the card long name from DMI info. - */ -static const char * const dmi_blacklist[] = { - "To be filled by OEM", - "TBD by OEM", - "Default String", - "Board Manufacturer", - "Board Vendor Name", - "Board Product Name", - NULL, /* terminator */ -}; - -/* - * Trim special characters, and replace '-' with '_' since '-' is used to - * separate different DMI fields in the card long name. Only number and - * alphabet characters and a few separator characters are kept. - */ -static void cleanup_dmi_name(char *name) -{ - int i, j = 0; - - for (i = 0; name[i]; i++) { - if (isalnum(name[i]) || (name[i] == '.') - || (name[i] == '_')) - name[j++] = name[i]; - else if (name[i] == '-') - name[j++] = '_'; - } - - name[j] = '\0'; -} - -/* - * Check if a DMI field is valid, i.e. not containing any string - * in the black list and not the empty string. - */ -static int is_dmi_valid(const char *field) -{ - int i = 0; - - if (!field[0]) - return 0; - - while (dmi_blacklist[i]) { - if (strstr(field, dmi_blacklist[i])) - return 0; - i++; - } - - return 1; -} - -/* - * Append a string to dmi_longname with character cleanups. - */ -#define DMI_LONGNAME_LEN 80 -static void append_dmi_string(char *dst, const char *str) -{ - size_t dst_len = DMI_LONGNAME_LEN; - size_t len; - - len = strlen(dst); - snprintf(dst + len, dst_len - len, "-%s", str); - - len++; /* skip the separator "-" */ - if (len < dst_len) - cleanup_dmi_name(dst + len); -} - -/** - * snd_soc_set_dmi_name() - Register DMI names to card - * @card: The card to register DMI names - * - * An Intel machine driver may be used by many different devices but are - * difficult for userspace to differentiate, since machine drivers usually - * use their own name as the card short name and leave the card long name - * blank. To differentiate such devices and fix bugs due to lack of - * device-specific configurations, this function allows DMI info to be used - * as the sound card long name, in the format of - * "vendor-product-version-board" - * (Character '-' is used to separate different DMI fields here). - * This will help the user space to load the device-specific Use Case Manager - * (UCM) configurations for the card. - * - * Possible card long names may be: - * DellInc.-XPS139343-01-0310JH - * ASUSTeKCOMPUTERINC.-T100TA-1.0-T100TA - * Circuitco-MinnowboardMaxD0PLATFORM-D0-MinnowBoardMAX - * - * This function also supports flavoring the card longname to provide - * the extra differentiation, like "vendor-product-version-board-flavor". - * - * We only keep number and alphabet characters and a few separator characters - * in the card long name since UCM in the user space uses the card long names - * as card configuration directory names and AudoConf cannot support special - * characters like SPACE. - * - * Returns 0 on success, otherwise a negative error code. - */ -static int snd_soc_set_dmi_name(struct snd_soc_card *card) -{ - const char *vendor, *product, *board; - char *dmi_longname; - - if (card->long_name) - return 0; /* long name already set by driver or from DMI */ - - if (!dmi_available) - return 0; - - /* make up dmi long name as: vendor-product-version-board */ - vendor = dmi_get_system_info(DMI_BOARD_VENDOR); - if (!vendor || !is_dmi_valid(vendor)) { - dev_warn(card->dev, "ASoC: no DMI vendor name!\n"); - return 0; - } - - dmi_longname = devm_kzalloc(card->dev, DMI_LONGNAME_LEN, GFP_KERNEL); - if (!dmi_longname) - return -ENOMEM; - - snprintf(dmi_longname, DMI_LONGNAME_LEN, "%s", vendor); - cleanup_dmi_name(dmi_longname); - - product = dmi_get_system_info(DMI_PRODUCT_NAME); - if (product && is_dmi_valid(product)) { - const char *product_version = dmi_get_system_info(DMI_PRODUCT_VERSION); - - append_dmi_string(dmi_longname, product); - - /* - * some vendors like Lenovo may only put a self-explanatory - * name in the product version field - */ - if (product_version && is_dmi_valid(product_version)) - append_dmi_string(dmi_longname, product_version); - } - - board = dmi_get_system_info(DMI_BOARD_NAME); - if (board && is_dmi_valid(board)) { - if (!product || strcasecmp(board, product)) - append_dmi_string(dmi_longname, board); - } else if (!product) { - /* fall back to using legacy name */ - dev_warn(card->dev, "ASoC: no DMI board/product name!\n"); - return 0; - } - - /* set the card long name */ - card->long_name = dmi_longname; - - return 0; -} -#else -static inline int snd_soc_set_dmi_name(struct snd_soc_card *card) -{ - return 0; -} -#endif /* CONFIG_DMI */ - -static void soc_check_tplg_fes(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - struct snd_soc_dai_link *dai_link; - int i; - - for_each_component(component) { - - /* does this component override BEs ? */ - if (!component->driver->ignore_machine) - continue; - - /* for this machine ? */ - if (!strcmp(component->driver->ignore_machine, - card->dev->driver->name)) - goto match; - if (strcmp(component->driver->ignore_machine, - dev_name(card->dev))) - continue; -match: - /* machine matches, so override the rtd data */ - for_each_card_prelinks(card, i, dai_link) { - - /* ignore this FE */ - if (dai_link->dynamic) { - dai_link->ignore = true; - continue; - } - - dev_dbg(card->dev, "info: override BE DAI link %s\n", - dai_link->name); - - /* override platform component */ - if (!dai_link->platforms) { - dev_err(card->dev, "init platform error"); - continue; - } - - if (component->dev->of_node) - dai_link->platforms->of_node = component->dev->of_node; - else - dai_link->platforms->name = component->name; - - /* convert non BE into BE */ - dai_link->no_pcm = 1; - - /* - * override any BE fixups - * see - * snd_soc_link_be_hw_params_fixup() - */ - dai_link->be_hw_params_fixup = - component->driver->be_hw_params_fixup; - - /* - * most BE links don't set stream name, so set it to - * dai link name if it's NULL to help bind widgets. - */ - if (!dai_link->stream_name) - dai_link->stream_name = dai_link->name; - } - - /* Inform userspace we are using alternate topology */ - snd_soc_card_set_topology_name(card, component->driver->topology_name_prefix); - } -} - -#define soc_setup_card_name(name, name1, name2, norm) \ - __soc_setup_card_name(name, sizeof(name), name1, name2, norm) -static void __soc_setup_card_name(char *name, int len, - const char *name1, const char *name2, - int normalization) -{ - int i; - - snprintf(name, len, "%s", name1 ? name1 : name2); - - if (!normalization) - return; - - /* - * Name normalization - * - * The driver name is somewhat special, as it's used as a key for - * searches in the user-space. - * - * ex) - * "abcd??efg" -> "abcd__efg" - */ - for (i = 0; i < len; i++) { - switch (name[i]) { - case '_': - case '-': - case '\0': - break; - default: - if (!isalnum(name[i])) - name[i] = '_'; - break; - } - } -} - -static void soc_cleanup_card_resources(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd, *n; - - if (card->snd_card) - snd_card_disconnect_sync(card->snd_card); - - snd_soc_dapm_shutdown(card); - - /* release machine specific resources */ - for_each_card_rtds(card, rtd) - if (rtd->initialized) - snd_soc_link_exit(rtd); - /* flush delayed work before removing DAIs and DAPM widgets */ - snd_soc_flush_all_delayed_work(card); - - /* remove and free each DAI */ - soc_remove_link_dais(card); - soc_remove_link_components(card); - - for_each_card_rtds_safe(card, rtd, n) - snd_soc_remove_pcm_runtime(card, rtd); - - /* remove auxiliary devices */ - soc_remove_aux_devices(card); - soc_unbind_aux_dev(card); - - snd_soc_dapm_free(snd_soc_card_to_dapm(card)); - soc_cleanup_card_debugfs(card); - - /* remove the card */ - snd_soc_card_remove(card); - - if (card->snd_card) { - snd_card_free(card->snd_card); - card->snd_card = NULL; - } -} - -static void snd_soc_remove_device_links(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - - for_each_card_components(card, component) { - if (component->card_device_link) { - device_link_del(component->card_device_link); - component->card_device_link = NULL; - } - } -} - -static void snd_soc_unbind_card(struct snd_soc_card *card) -{ - if (snd_soc_card_is_instantiated(card)) { - card->instantiated = false; - - snd_soc_remove_device_links(card); - - soc_cleanup_card_resources(card); - } -} - -static int snd_soc_bind_card(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd; - struct snd_soc_component *component; - struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); - int ret; - - snd_soc_card_mutex_lock_root(card); - snd_soc_fill_dummy_dai(card); - - snd_soc_dapm_init(dapm, card, NULL); - list_del_init(&card->list); - - /* check whether any platform is ignore machine FE and using topology */ - soc_check_tplg_fes(card); - - /* bind aux_devs too */ - ret = soc_bind_aux_dev(card); - if (ret < 0) - goto probe_end; - - /* add predefined DAI links to the list */ - card->num_rtd = 0; - ret = snd_soc_add_pcm_runtimes(card, card->dai_link, card->num_links); - if (ret < 0) - goto probe_end; - - /* card bind complete so register a sound card */ - ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, - card->owner, 0, &card->snd_card); - if (ret < 0) { - dev_err(card->dev, - "ASoC: can't create sound card for card %s: %d\n", - card->name, ret); - goto probe_end; - } - - soc_init_card_debugfs(card); - - soc_resume_init(card); - - ret = snd_soc_dapm_new_controls(dapm, card->dapm_widgets, - card->num_dapm_widgets); - if (ret < 0) - goto probe_end; - - ret = snd_soc_dapm_new_controls(dapm, card->of_dapm_widgets, - card->num_of_dapm_widgets); - if (ret < 0) - goto probe_end; - - /* initialise the sound card only once */ - ret = snd_soc_card_probe(card); - if (ret < 0) - goto probe_end; - - /* probe all components used by DAI links on this card */ - ret = soc_probe_link_components(card); - if (ret < 0) { - if (ret != -EPROBE_DEFER) { - dev_err(card->dev, - "ASoC: failed to instantiate card %d\n", ret); - } - goto probe_end; - } - - /* probe auxiliary components */ - ret = soc_probe_aux_devices(card); - if (ret < 0) { - dev_err(card->dev, - "ASoC: failed to probe aux component %d\n", ret); - goto probe_end; - } - - /* probe all DAI links on this card */ - ret = soc_probe_link_dais(card); - if (ret < 0) { - dev_err(card->dev, - "ASoC: failed to instantiate card %d\n", ret); - goto probe_end; - } - - for_each_card_rtds(card, rtd) { - ret = soc_init_pcm_runtime(card, rtd); - if (ret < 0) - goto probe_end; - } - - snd_soc_dapm_link_dai_widgets(card); - snd_soc_dapm_connect_dai_link_widgets(card); - - ret = snd_soc_add_card_controls(card, card->controls, - card->num_controls); - if (ret < 0) - goto probe_end; - - ret = snd_soc_dapm_add_routes(dapm, card->dapm_routes, - card->num_dapm_routes); - if (ret < 0) - goto probe_end; - - ret = snd_soc_dapm_add_routes(dapm, card->of_dapm_routes, - card->num_of_dapm_routes); - if (ret < 0) - goto probe_end; - - /* try to set some sane longname if DMI is available */ - snd_soc_set_dmi_name(card); - - soc_setup_card_name(card->snd_card->shortname, - card->name, NULL, 0); - soc_setup_card_name(card->snd_card->longname, - card->long_name, card->name, 0); - soc_setup_card_name(card->snd_card->driver, - card->driver_name, card->name, 1); - - if (card->components) { - /* the current implementation of snd_component_add() accepts */ - /* multiple components in the string separated by space, */ - /* but the string collision (identical string) check might */ - /* not work correctly */ - ret = snd_component_add(card->snd_card, card->components); - if (ret < 0) { - dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n", - card->name, ret); - goto probe_end; - } - } - - /* - * Add device_link from card to component so that system_suspend - * will be done in the correct order. The card must suspend first - * to stop audio activity before the components suspend. - * - * If a driver pair already have a link in the opposite direction - * they must manage their own suspend order. - */ - for_each_card_components(card, component) { - if (card->dev == component->dev) - continue; - - component->card_device_link = device_link_add(card->dev, - component->dev, - DL_FLAG_STATELESS); - if (!component->card_device_link) { - dev_warn(card->dev, "Could not create device link to %s\n", - dev_name(component->dev)); - } - } - - ret = snd_soc_card_late_probe(card); - if (ret < 0) - goto probe_end; - - ret = snd_soc_dapm_ignore_suspend_widgets(card); - if (ret < 0) - goto probe_end; - - snd_soc_dapm_new_widgets(card); - for_each_card_components(card, component) { - ret = snd_soc_component_fixup_controls(component); - if (ret < 0) - goto probe_end; - } - snd_soc_card_fixup_controls(card); - - ret = snd_card_register(card->snd_card); - if (ret < 0) { - dev_err(card->dev, "ASoC: failed to register soundcard %d\n", - ret); - goto probe_end; - } - - card->instantiated = 1; - snd_soc_dapm_mark_endpoints_dirty(card); - snd_soc_dapm_sync(dapm); - - /* deactivate pins to sleep state */ - for_each_card_components(card, component) - if (!snd_soc_component_active(component)) - pinctrl_pm_select_sleep_state(component->dev); - -probe_end: - if (ret < 0) { - snd_soc_remove_device_links(card); - soc_cleanup_card_resources(card); - } - - if (ret == -EPROBE_DEFER) { - list_add(&card->list, &unbind_card_list); - ret = 0; - } - snd_soc_card_mutex_unlock(card); - - return ret; -} - -static void devm_card_bind_release(struct device *dev, void *res) -{ - snd_soc_unregister_card(*(struct snd_soc_card **)res); -} - -static int devm_snd_soc_bind_card(struct device *dev, struct snd_soc_card *card) -{ - struct snd_soc_card **ptr; - int ret; - - /* The procedure may be called many times during the lifetime of the card. */ - devres_destroy(dev, devm_card_bind_release, NULL, NULL); - - ptr = devres_alloc(devm_card_bind_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return -ENOMEM; - - ret = snd_soc_bind_card(card); - if (ret == 0) { - *ptr = card; - devres_add(dev, ptr); - } else { - devres_free(ptr); - } - - return ret; -} - -static int call_soc_bind_card(struct snd_soc_card *card) -{ - if (card->devres_dev) - return devm_snd_soc_bind_card(card->devres_dev, card); - return snd_soc_bind_card(card); -} - /* probes a new socdev */ static int soc_probe(struct platform_device *pdev) { @@ -2079,26 +1258,6 @@ int snd_soc_add_controls(struct snd_card *card, struct device *dev, return 0; } -/** - * snd_soc_add_card_controls - add an array of controls to a SoC card. - * Convenience function to add a list of controls. - * - * @soc_card: SoC card to add controls to - * @controls: array of controls to add - * @num_controls: number of elements in the array - * - * Return 0 for success, else error. - */ -int snd_soc_add_card_controls(struct snd_soc_card *soc_card, - const struct snd_kcontrol_new *controls, int num_controls) -{ - struct snd_card *card = soc_card->snd_card; - - return snd_soc_add_controls(card, soc_card->dev, controls, num_controls, - NULL, soc_card); -} -EXPORT_SYMBOL_GPL(snd_soc_add_card_controls); - /** * snd_soc_register_card - Register a card with the ASoC core * @@ -2132,7 +1291,7 @@ int snd_soc_register_card(struct snd_soc_card *card) guard(mutex)(&client_mutex); - return call_soc_bind_card(card); + return snd_soc_card_bind_call(card); } EXPORT_SYMBOL_GPL(snd_soc_register_card); @@ -2146,8 +1305,7 @@ void snd_soc_unregister_card(struct snd_soc_card *card) { guard(mutex)(&client_mutex); - snd_soc_unbind_card(card); - list_del(&card->list); + snd_soc_card_unbind(card, false); dev_dbg(card->dev, "ASoC: Unregistered card '%s'\n", card->name); } @@ -2301,16 +1459,11 @@ static void convert_endianness_formats(struct snd_soc_pcm_stream *stream) static void snd_soc_del_component_unlocked(struct snd_soc_component *component) { struct snd_soc_card *card = component->card; - bool instantiated; snd_soc_unregister_dais(component); - if (card) { - instantiated = card->instantiated; - snd_soc_unbind_card(card); - if (instantiated) - list_add(&card->list, &unbind_card_list); - } + if (card) + snd_soc_card_unbind(card, true); list_del(&component->list); } @@ -2355,7 +1508,6 @@ int snd_soc_component_register(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, int num_dai) { - struct snd_soc_card *card, *c; int ret; int i; guard(mutex)(&client_mutex); @@ -2383,8 +1535,7 @@ int snd_soc_component_register(struct snd_soc_component *component, /* see for_each_component */ list_add(&component->list, snd_soc_component_get_list_head()); - list_for_each_entry_safe(card, c, &unbind_card_list, list) - call_soc_bind_card(card); + snd_soc_card_rebind(); err_cleanup: if (ret < 0) @@ -3056,7 +2207,7 @@ int snd_soc_get_dai_id(struct device_node *ep) ret = -ENOTSUPP; scoped_guard(mutex, &client_mutex) { - struct snd_soc_component *component = soc_find_component(&dlc); + struct snd_soc_component *component = snd_soc_find_component(&dlc); if (component) ret = snd_soc_component_of_xlate_dai_id(component, ep); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 3866da8e3beee5..e09d283d443a84 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -23,6 +23,19 @@ char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *d int snd_soc_add_controls(struct snd_card *card, struct device *dev, const struct snd_kcontrol_new *controls, int num_controls, const char *prefix, void *data); +void snd_soc_flush_all_delayed_work(struct snd_soc_card *card); +struct snd_soc_component *snd_soc_find_component(const struct snd_soc_dai_link_component *dlc); +#ifdef CONFIG_PM_SLEEP +void snd_soc_playback_digital_mute(struct snd_soc_card *card, int mute); +void snd_soc_dapm_suspend_resume(struct snd_soc_card *card, int event); +#endif + +/* + * In soc-card + */ +void snd_soc_card_unbind(struct snd_soc_card *card, bool reuse); +void snd_soc_card_rebind(void); +int snd_soc_card_bind_call(struct snd_soc_card *card); /* * In soc-ops diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 86e2402b8ffd26..120dde4ab7773b 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1278,8 +1278,7 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) ret = amd_sof_sdw_probe(sdev); if (ret < 0) { dev_err(sdev->dev, "error: SoundWire probe error\n"); - free_irq(sdev->ipc_irq, sdev); - return ret; + goto free_ipc_irq; } skip_soundwire: diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 5a21ab8ebb4b9b..6aa16d1a12b952 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -47,6 +47,7 @@ static const struct sof_amd_acp_desc acp7b_chip_info = { static const struct sof_dev_desc acp7b_desc = { .machines = snd_soc_acpi_amd_acp7b_sof_machines, + .alt_machines = snd_soc_acpi_amd_acp7b_sof_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, @@ -89,6 +90,7 @@ static const struct sof_amd_acp_desc acp7f_chip_info = { static const struct sof_dev_desc acp7f_desc = { .machines = snd_soc_acpi_amd_acp7f_sof_machines, + .alt_machines = snd_soc_acpi_amd_acp7f_sof_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c index f790b959cd4e47..a810c9c8a0b5b1 100644 --- a/sound/soc/sprd/sprd-pcm-compress.c +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -247,8 +247,9 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, /* Only channel 1 transfer can wake up the AP system. */ if (!params->no_wake_mode && channel == 1) { - dma->desc->callback = sprd_platform_compr_dma_complete; - dma->desc->callback_param = cstream; + dmaengine_desc_set_callback(dma->desc, + sprd_platform_compr_dma_complete, + NULL, cstream); } kfree(sg); diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index bbd7516f81954e..968901ae4c335e 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -287,8 +287,9 @@ static int sprd_pcm_hw_params(struct snd_soc_component *component, } if (!runtime->no_period_wakeup) { - data->desc->callback = sprd_pcm_dma_complete; - data->desc->callback_param = dma_private; + dmaengine_desc_set_callback(data->desc, + sprd_pcm_dma_complete, + NULL, dma_private); } } diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index 844ddcfc421a7f..23c6f9e513c121 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1078,7 +1078,7 @@ int uni_player_init(struct platform_device *pdev, player->hw = &uni_player_pcm_hw; /* Get uniperif resource */ - player->clk = of_clk_get(pdev->dev.of_node, 0); + player->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(player->clk)) { dev_err(player->dev, "Failed to get clock\n"); return PTR_ERR(player->clk); diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index 88863dbd05d9bd..44467a25fb69a4 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -302,8 +302,7 @@ static int stm32_spdifrx_dma_ctrl_start(struct stm32_spdifrx_data *spdifrx) if (!spdifrx->desc) return -EINVAL; - spdifrx->desc->callback = stm32_spdifrx_dma_complete; - spdifrx->desc->callback_param = spdifrx; + dmaengine_desc_set_callback(spdifrx->desc, stm32_spdifrx_dma_complete, NULL, spdifrx); cookie = dmaengine_submit(spdifrx->desc); err = dma_submit_error(cookie); if (err) diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index 205f422d9ded19..c8a626396e9a49 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -58,6 +58,7 @@ config SND_SUN4I_SPDIF config SND_SUN50I_DMIC tristate "Allwinner H6 DMIC Support" + select REGMAP_MMIO select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y or M to add support for the DMIC audio block in the Allwinner diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index d979bb00312fb3..747ecd43dd2665 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -427,24 +427,28 @@ static int sun4i_spdif_get_status(struct snd_kcontrol *kcontrol, struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(cpu_dai); u8 *status = ucontrol->value.iec958.status; - unsigned long flags; unsigned int reg; + int ret; - spin_lock_irqsave(&host->lock, flags); + ret = pm_runtime_resume_and_get(cpu_dai->dev); + if (ret) + return ret; - regmap_read(host->regmap, SUN4I_SPDIF_TXCHSTA0, ®); + scoped_guard(spinlock_irqsave, &host->lock) { + regmap_read(host->regmap, SUN4I_SPDIF_TXCHSTA0, ®); - status[0] = reg & 0xff; - status[1] = (reg >> 8) & 0xff; - status[2] = (reg >> 16) & 0xff; - status[3] = (reg >> 24) & 0xff; + status[0] = reg & 0xff; + status[1] = (reg >> 8) & 0xff; + status[2] = (reg >> 16) & 0xff; + status[3] = (reg >> 24) & 0xff; - regmap_read(host->regmap, SUN4I_SPDIF_TXCHSTA1, ®); + regmap_read(host->regmap, SUN4I_SPDIF_TXCHSTA1, ®); - status[4] = reg & 0xff; - status[5] = (reg >> 8) & 0x3; + status[4] = reg & 0xff; + status[5] = (reg >> 8) & 0x3; + } - spin_unlock_irqrestore(&host->lock, flags); + pm_runtime_put(cpu_dai->dev); return 0; } @@ -455,35 +459,39 @@ static int sun4i_spdif_set_status(struct snd_kcontrol *kcontrol, struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(cpu_dai); u8 *status = ucontrol->value.iec958.status; - unsigned long flags; unsigned int reg; + int ret; bool chg0, chg1; - spin_lock_irqsave(&host->lock, flags); + ret = pm_runtime_resume_and_get(cpu_dai->dev); + if (ret) + return ret; - reg = (u32)status[3] << 24; - reg |= (u32)status[2] << 16; - reg |= (u32)status[1] << 8; - reg |= (u32)status[0]; + scoped_guard(spinlock_irqsave, &host->lock) { + reg = (u32)status[3] << 24; + reg |= (u32)status[2] << 16; + reg |= (u32)status[1] << 8; + reg |= (u32)status[0]; - regmap_update_bits_check(host->regmap, SUN4I_SPDIF_TXCHSTA0, - GENMASK(31,0), reg, &chg0); + regmap_update_bits_check(host->regmap, SUN4I_SPDIF_TXCHSTA0, + GENMASK(31, 0), reg, &chg0); - reg = (u32)status[5] << 8; - reg |= (u32)status[4]; + reg = (u32)status[5] << 8; + reg |= (u32)status[4]; - regmap_update_bits_check(host->regmap, SUN4I_SPDIF_TXCHSTA1, - GENMASK(9,0), reg, &chg1); + regmap_update_bits_check(host->regmap, SUN4I_SPDIF_TXCHSTA1, + GENMASK(9, 0), reg, &chg1); - reg = SUN4I_SPDIF_TXCFG_CHSTMODE; - if (status[0] & IEC958_AES0_NONAUDIO) - reg |= SUN4I_SPDIF_TXCFG_NONAUDIO; + reg = SUN4I_SPDIF_TXCFG_CHSTMODE; + if (status[0] & IEC958_AES0_NONAUDIO) + reg |= SUN4I_SPDIF_TXCFG_NONAUDIO; - regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, - SUN4I_SPDIF_TXCFG_CHSTMODE | - SUN4I_SPDIF_TXCFG_NONAUDIO, reg); + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, + SUN4I_SPDIF_TXCFG_CHSTMODE | + SUN4I_SPDIF_TXCFG_NONAUDIO, reg); + } - spin_unlock_irqrestore(&host->lock, flags); + pm_runtime_put(cpu_dai->dev); return chg0 || chg1; } @@ -635,15 +643,15 @@ static int sun4i_spdif_runtime_suspend(struct device *dev) static int sun4i_spdif_runtime_resume(struct device *dev) { - struct sun4i_spdif_dev *host = dev_get_drvdata(dev); + struct sun4i_spdif_dev *host = dev_get_drvdata(dev); int ret; - ret = clk_prepare_enable(host->spdif_clk); + ret = clk_prepare_enable(host->apb_clk); if (ret) return ret; - ret = clk_prepare_enable(host->apb_clk); + ret = clk_prepare_enable(host->spdif_clk); if (ret) - clk_disable_unprepare(host->spdif_clk); + clk_disable_unprepare(host->apb_clk); return ret; } diff --git a/sound/soc/sunxi/sun50i-dmic.c b/sound/soc/sunxi/sun50i-dmic.c index 5c784b1f68461a..be61990415b01c 100644 --- a/sound/soc/sunxi/sun50i-dmic.c +++ b/sound/soc/sunxi/sun50i-dmic.c @@ -286,22 +286,55 @@ MODULE_DEVICE_TABLE(of, sun50i_dmic_of_match); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(sun50i_dmic_vol_scale, -12000, 75, 1); -static const struct snd_kcontrol_new sun50i_dmic_controls[] = { +static int sun50i_dmic_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0) + return ret; + + ret = snd_soc_get_volsw(kcontrol, ucontrol); + pm_runtime_put(component->dev); + + return ret; +} + +static int sun50i_dmic_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0) + return ret; - SOC_DOUBLE_TLV("DMIC Channel 0 Capture Volume", SUN50I_DMIC_D0D1_VOL_CTR, - SUN50I_DMIC_D0D1_VOL_CTR_0L, SUN50I_DMIC_D0D1_VOL_CTR_0R, - 0xFF, 0, sun50i_dmic_vol_scale), - SOC_DOUBLE_TLV("DMIC Channel 1 Capture Volume", SUN50I_DMIC_D0D1_VOL_CTR, - SUN50I_DMIC_D0D1_VOL_CTR_1L, SUN50I_DMIC_D0D1_VOL_CTR_1R, - 0xFF, 0, sun50i_dmic_vol_scale), - SOC_DOUBLE_TLV("DMIC Channel 2 Capture Volume", SUN50I_DMIC_D2D3_VOL_CTR, - SUN50I_DMIC_D2D3_VOL_CTR_2L, SUN50I_DMIC_D2D3_VOL_CTR_2R, - 0xFF, 0, sun50i_dmic_vol_scale), - SOC_DOUBLE_TLV("DMIC Channel 3 Capture Volume", SUN50I_DMIC_D2D3_VOL_CTR, - SUN50I_DMIC_D2D3_VOL_CTR_3L, SUN50I_DMIC_D2D3_VOL_CTR_3R, - 0xFF, 0, sun50i_dmic_vol_scale), + ret = snd_soc_put_volsw(kcontrol, ucontrol); + pm_runtime_put(component->dev); + return ret; +} +static const struct snd_kcontrol_new sun50i_dmic_controls[] = { + SOC_DOUBLE_EXT_TLV("DMIC Channel 0 Capture Volume", SUN50I_DMIC_D0D1_VOL_CTR, + SUN50I_DMIC_D0D1_VOL_CTR_0L, SUN50I_DMIC_D0D1_VOL_CTR_0R, + 0xFF, 0, sun50i_dmic_get_volsw, sun50i_dmic_put_volsw, + sun50i_dmic_vol_scale), + SOC_DOUBLE_EXT_TLV("DMIC Channel 1 Capture Volume", SUN50I_DMIC_D0D1_VOL_CTR, + SUN50I_DMIC_D0D1_VOL_CTR_1L, SUN50I_DMIC_D0D1_VOL_CTR_1R, + 0xFF, 0, sun50i_dmic_get_volsw, sun50i_dmic_put_volsw, + sun50i_dmic_vol_scale), + SOC_DOUBLE_EXT_TLV("DMIC Channel 2 Capture Volume", SUN50I_DMIC_D2D3_VOL_CTR, + SUN50I_DMIC_D2D3_VOL_CTR_2L, SUN50I_DMIC_D2D3_VOL_CTR_2R, + 0xFF, 0, sun50i_dmic_get_volsw, sun50i_dmic_put_volsw, + sun50i_dmic_vol_scale), + SOC_DOUBLE_EXT_TLV("DMIC Channel 3 Capture Volume", SUN50I_DMIC_D2D3_VOL_CTR, + SUN50I_DMIC_D2D3_VOL_CTR_3L, SUN50I_DMIC_D2D3_VOL_CTR_3R, + 0xFF, 0, sun50i_dmic_get_volsw, sun50i_dmic_put_volsw, + sun50i_dmic_vol_scale), }; static const struct snd_soc_component_driver sun50i_dmic_component = { @@ -322,16 +355,16 @@ static int sun50i_dmic_runtime_suspend(struct device *dev) static int sun50i_dmic_runtime_resume(struct device *dev) { - struct sun50i_dmic_dev *host = dev_get_drvdata(dev); + struct sun50i_dmic_dev *host = dev_get_drvdata(dev); int ret; - ret = clk_prepare_enable(host->dmic_clk); + ret = clk_prepare_enable(host->bus_clk); if (ret) return ret; - ret = clk_prepare_enable(host->bus_clk); + ret = clk_prepare_enable(host->dmic_clk); if (ret) { - clk_disable_unprepare(host->dmic_clk); + clk_disable_unprepare(host->bus_clk); return ret; } diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c index 2bd49bf8274899..0fd086cac9552d 100644 --- a/sound/usb/bcd2000/bcd2000.c +++ b/sound/usb/bcd2000/bcd2000.c @@ -183,10 +183,24 @@ static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream) static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream) { struct bcd2000 *bcd2k = substream->rmidi->private_data; + struct urb *urb = NULL; - if (bcd2k->midi_out_active && bcd2k->midi_out_urb) { - usb_kill_urb(bcd2k->midi_out_urb); - bcd2k->midi_out_active = 0; + /* + * Reference the URB under the lock so that it stays valid until + * usb_kill_urb() returns, even if a disconnect frees it in the + * meantime. usb_kill_urb() cannot run under the lock, as the + * completion handler takes the same lock. + */ + scoped_guard(spinlock_irqsave, &bcd2k->midi_lock) { + if (bcd2k->midi_out_active && bcd2k->midi_out_urb) { + urb = usb_get_urb(bcd2k->midi_out_urb); + bcd2k->midi_out_active = 0; + } + } + + if (urb) { + usb_kill_urb(urb); + usb_put_urb(urb); } return 0; diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 2830a1da067da5..5a415d1acca4bf 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -863,6 +863,8 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) return 0; exit_free_idev: + usb_free_urb(cdev->ep4_in_urb); + cdev->ep4_in_urb = NULL; input_free_device(input); cdev->input_dev = NULL; return ret; diff --git a/sound/usb/format.c b/sound/usb/format.c index 4830f9f93ad77e..cf0da3ca3eaddc 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -57,6 +57,11 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, sample_width = fmt->bBitResolution; sample_bytes = fmt->bSubslotSize; + /* Steinberg UR22C workaround: config 2 capture sets 0xffffffff */ + if (chip->usb_id == USB_ID(0x0499, 0x172f) && + format == 0xffffffff) + format = 1; + if (format & UAC2_FORMAT_TYPE_I_RAW_DATA) { pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL; /* flag potentially raw DSD capable altsettings */ diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 17f49f567efbf2..626274e503c7aa 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -94,6 +94,18 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) if (runtime == NULL) return; + /* + * snd_line6_capture_open() constrains the buffer to hold at least + * two packets, so this cannot happen for a conforming device. Drop + * the packet rather than overrun the single wrap below. + */ + if (unlikely(frames > runtime->buffer_size)) { + dev_err_ratelimited(line6pcm->line6->ifcdev, + "packet of %d frames exceeds %lu-frame buffer, dropping\n", + frames, runtime->buffer_size); + return; + } + if (line6pcm->in.pos_done + frames > runtime->buffer_size) { /* The transferred area goes over buffer boundary, @@ -232,6 +244,23 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) if (err < 0) return err; + /* + * line6_capture_copy() wraps the ring buffer once per packet and + * advances pos_done by the packet, which only works when the buffer + * holds more than one packet: with exactly one, pos_done gains a + * buffer and loses it again on every packet. The packet size comes + * from the device, so bound the buffer by two packets here. The + * impulse-response path copies a playback URB into this ring, so + * take the larger of the two endpoints. + */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 2 * max(line6pcm->max_packet_size_in, + line6pcm->max_packet_size_out), + UINT_MAX); + if (err < 0) + return err; + line6_pcm_acquire(line6pcm, LINE6_STREAM_CAPTURE_HELPER, false); runtime->hw = line6pcm->properties->capture_hw; diff --git a/sound/usb/midi.c b/sound/usb/midi.c index e3d15755406138..5604c3fdbe3b75 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -194,10 +194,11 @@ static int snd_usbmidi_urb_error(const struct urb *urb) case -ESHUTDOWN: case -ENODEV: return -ENODEV; - /* errors that might occur during unplugging */ + /* errors that might occur during unplugging or switching off */ case -EPROTO: case -ETIME: case -EILSEQ: + case -EPIPE: return -EIO; default: dev_err(&urb->dev->dev, "urb status %d\n", urb->status); diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 98b4146216ae7c..9218ca6314ad79 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -374,6 +374,20 @@ static const struct usbmix_name_map dell_alc4020_map[] = { { 0 } }; +/* + * Weltrend XiiSound chips (040b:0897, e.g. Redragon H878): + * the playback feature unit is broken; its volume controls act like + * mute switches below the maximum value and the channel mapping is + * unreliable, so any hardware volume change mutes one or both + * channels. Skip the unit entirely and let userspace fall back to + * software volume; the unit is pinned to maximum/unmuted by the + * mixer quirk in mixer_quirks.c. + */ +static const struct usbmix_name_map xiisound_map[] = { + { 5, NULL }, /* playback feature unit */ + { 0 } +}; + /* * Corsair Virtuoso calls everything "Headset" without this, leading to * applications moving the sidetone control instead of the main one. @@ -790,6 +804,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x1395, 0x0025), .map = sennheiser_pc8_map, }, + { + /* Weltrend XiiSound (Redragon H878) */ + .id = USB_ID(0x040b, 0x0897), + .map = xiisound_map, + }, { /* Microsoft USB Link headset */ .id = USB_ID(0x045e, 0x083c), diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 01210cc3fae729..4c7d68311f2048 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -4419,6 +4420,242 @@ static int snd_djm_controls_create(struct usb_mixer_interface *mixer, return 0; } +/* + * Arturia AudioFuse (Rev2) + * + * The protocol was reverse engineered from Arturia AudioFuse Control Center. + * A parameter is written with a UAC2 class MEM request to the vendor entity + * 0x46 (wValue = parameter id, 1 byte payload), and the current device state + * can be read back as a 178 byte block from the vendor entity 0x47. + * Both entities live on the audio control interface 0. + * + * Layout of kcontrol->private_value: (parameter id << 8) | current value + */ +#define SND_AF_CTRL_IFNUM 0 +#define SND_AF_ENTITY_SET 0x46 +#define SND_AF_ENTITY_STATUS 0x47 +#define SND_AF_STATUS_LEN 178 + +#define SND_AF_PARAM_SHIFT 8 +#define SND_AF_VAL_MASK 0xff + +struct snd_audiofuse_ctl { + const char *name; + u16 param; /* wValue of the set request */ + u8 status_offset; /* offset of the value in the status block */ +}; + +static const struct snd_audiofuse_ctl snd_audiofuse_ctls[] = { + { + .name = "Input1 48V LED Switch", + .param = 0x0001, + .status_offset = 0x3e, + }, + { + .name = "Main Speaker Mute LED Switch", + .param = 0x0500, + .status_offset = 0x5c, + }, +}; + +static u16 snd_audiofuse_windex(struct usb_mixer_interface *mixer, u8 entity) +{ + return (entity << 8) | mixer->hostif->desc.bInterfaceNumber; +} + +static int snd_audiofuse_ctl_write(struct usb_mixer_interface *mixer, + u16 param, u8 val) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + + /* snd_usb_ctl_msg() bounces the buffer, so a stack variable is fine */ + err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + param, + snd_audiofuse_windex(mixer, SND_AF_ENTITY_SET), + &val, sizeof(val)); + + snd_usb_unlock_shutdown(chip); + return err; +} + +/* Returns the number of bytes read, or a negative error code */ +static int snd_audiofuse_read_status(struct usb_mixer_interface *mixer, + u8 *buf) +{ + struct snd_usb_audio *chip = mixer->chip; + int len; + + len = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, + snd_audiofuse_windex(mixer, SND_AF_ENTITY_STATUS), + buf, SND_AF_STATUS_LEN); + if (len < 0) + return len; + + /* enable with dynamic debug: file mixer_quirks.c +p */ + print_hex_dump_debug("AudioFuse status: ", DUMP_PREFIX_OFFSET, 16, 1, + buf, len, false); + return len; +} + +static int snd_audiofuse_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + kcontrol->private_value & SND_AF_VAL_MASK; + return 0; +} + +static int snd_audiofuse_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + unsigned long pval = kcontrol->private_value; + u16 param = pval >> SND_AF_PARAM_SHIFT; + u8 oldval = pval & SND_AF_VAL_MASK; + long newval = ucontrol->value.integer.value[0]; + int err; + + if (newval < 0 || newval > 1) + return -EINVAL; + if (newval == oldval) + return 0; + + err = snd_audiofuse_ctl_write(list->mixer, param, newval); + if (err < 0) + return err; + + /* update the cache only after the device accepted the value */ + kcontrol->private_value = ((unsigned long)param << SND_AF_PARAM_SHIFT) | + newval; + return 1; +} + +static int snd_audiofuse_ctl_resume(struct usb_mixer_elem_list *list) +{ + unsigned long pval = list->kctl->private_value; + + return snd_audiofuse_ctl_write(list->mixer, + pval >> SND_AF_PARAM_SHIFT, + pval & SND_AF_VAL_MASK); +} + +static int snd_audiofuse_controls_create(struct usb_mixer_interface *mixer) +{ + struct snd_kcontrol_new knew = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_mono_info, + .get = snd_audiofuse_ctl_get, + .put = snd_audiofuse_ctl_put, + }; + u8 *status; + int len, i, err = 0; + + /* + * The quirk is applied for every mixer of the card, but the vendor + * entities are only reachable through the control interface 0. + * Checking this per mixer (instead of a global flag) keeps the + * controls working after re-plugging and with multiple devices. + */ + if (mixer->hostif->desc.bInterfaceNumber != SND_AF_CTRL_IFNUM) + return 0; + + status = kmalloc(SND_AF_STATUS_LEN, GFP_KERNEL); + if (!status) + return -ENOMEM; + + /* a failed read is not fatal: start from "off" and keep audio working */ + len = snd_audiofuse_read_status(mixer, status); + if (len < 0) + usb_audio_warn(mixer->chip, + "AudioFuse: cannot read device status (%d)\n", + len); + + for (i = 0; i < ARRAY_SIZE(snd_audiofuse_ctls); i++) { + const struct snd_audiofuse_ctl *ctl = &snd_audiofuse_ctls[i]; + u8 val = 0; + + if (ctl->status_offset < len) + val = !!status[ctl->status_offset]; + + knew.name = ctl->name; + knew.private_value = + ((unsigned long)ctl->param << SND_AF_PARAM_SHIFT) | val; + + err = add_single_ctl_with_resume(mixer, 0, + snd_audiofuse_ctl_resume, + &knew, NULL); + if (err < 0) + break; + } + + kfree(status); + return err; +} + +/* + * Weltrend XiiSound chips (040b:0897, e.g. Redragon H878): + * the playback feature unit (id 5) is broken; its volume controls act like + * mute switches below the maximum value and the channel mapping is + * unreliable, so the unit is hidden from userspace entirely (see the + * quirk in mixer_maps.c) and userspace falls back to software volume. + * Pin the unit's master and channel volumes to the reported maximum + * and unmute them, so the audio path stays fully open. + */ +static int snd_xiisound_init_fu_pins(struct usb_mixer_interface *mixer) +{ + struct snd_usb_audio *chip = mixer->chip; + unsigned char buf[2]; + int ifnum, ch, err; + + if (mixer->protocol != UAC_VERSION_1) + return 0; + + ifnum = snd_usb_ctrl_intf(chip->ctrl_intf); + + /* unit 5 is the playback feature unit on these chips */ + for (ch = 0; ch <= 2; ch++) { + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + UAC_GET_MAX, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + (UAC_FU_VOLUME << 8) | ch, + (5 << 8) | ifnum, buf, 2); + if (err < 2) + return 0; /* no such unit/channels, nothing to pin */ + + err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + (UAC_FU_VOLUME << 8) | ch, + (5 << 8) | ifnum, buf, 2); + if (err < 0) + return err; + + buf[0] = 0; /* unmute */ + err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + (UAC_FU_MUTE << 8) | ch, + (5 << 8) | ifnum, buf, 1); + if (err < 0) + return err; + } + return 0; +} + int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) { int err = 0; @@ -4432,6 +4669,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x0644, 0x8047): err = snd_us16x08_controls_create(mixer); break; + case USB_ID(0x040b, 0x0897): + err = snd_xiisound_init_fu_pins(mixer); + break; case USB_ID(0x041e, 0x3020): case USB_ID(0x041e, 0x3040): case USB_ID(0x041e, 0x3042): @@ -4609,6 +4849,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */ err = hp_dock_mixer_create(mixer); break; + case USB_ID(0x1c75, 0xaf02): /* Arturia AudioFuse */ + err = snd_audiofuse_controls_create(mixer); + break; } return err; @@ -4620,6 +4863,9 @@ void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */ dell_dock_mixer_init(mixer); break; + case USB_ID(0x040b, 0x0897): /* Weltrend XiiSound (Redragon H878) */ + snd_xiisound_init_fu_pins(mixer); + break; } } @@ -4651,6 +4897,92 @@ void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, } } +static void snd_usb_apply_db_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl, + const unsigned int *scale, const char *desc) +{ + if (!scale) { + usb_audio_info(mixer->chip, "clearing %s dB scale on [%s] (%d/%d/%d)\n", + desc, kctl->id.name, cval->min, cval->max, cval->res); + + kctl->tlv.p = NULL; + kctl->vd[0].access &= ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + return; + } + + usb_audio_info(mixer->chip, "applying %s dB scale quirk on [%s] (%d/%d/%d)\n", + desc, kctl->id.name, cval->min, cval->max, cval->res); + + if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ && + !(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)) { + usb_audio_warn(mixer->chip, + "%s dB scale quirk overrides the previously applied one on [%s]\n", + desc, kctl->id.name); + } else if (cval->min_mute) { + /* + * We are clearing SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, + * resulting in min_mute becoming a no-op. + */ + usb_audio_warn(mixer->chip, "%s dB scale quirk overrides MIN_MUTE on [%s]\n", + desc, kctl->id.name); + } + + kctl->tlv.p = scale; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; +} + +static void snd_usb_apply_db_scale_val(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl, + const unsigned int *scale, const char *desc, + int min, int max, int res) +{ + snd_usb_apply_db_scale(mixer, cval, kctl, scale, desc); + + usb_audio_info(mixer->chip, + "overriding [%s] val (%d/%d/%d => %d/%d/%d) to fit %s dB scale quirk\n", + kctl->id.name, cval->min, cval->max, cval->res, min, max, res, desc); + + /* + * We are not fixing the cached values, as get_relative_value() will + * ensure userspace always sees compliant ones. + */ + cval->min = min; + cval->max = max; + cval->res = res; +} + +static void snd_usb_mixer_fu_quirk_linear_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + static const DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0); + + snd_usb_apply_db_scale(mixer, cval, kctl, scale, "linear volume"); +} + +/* Respect quirks set by module parameters or quirk flags table. */ +static inline bool deconflict_playback_tlv_quirks(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl) +{ + const u64 playback_quirk_flags = (QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL); + + return (!(mixer->chip->quirk_flags & playback_quirk_flags) && + strstr(kctl->id.name, "Playback")); +} + +static inline bool deconflict_capture_tlv_quirks(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl) +{ + const u64 capture_quirk_flags = (QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE | + QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL); + + return (!(mixer->chip->quirk_flags & capture_quirk_flags) && + strstr(kctl->id.name, "Capture")); +} + static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl) @@ -4671,18 +5003,156 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, 41, 50, TLV_DB_MINMAX_ITEM(-441, 0), ); - if (cval->min == 0 && cval->max == 50) { - usb_audio_info(mixer->chip, "applying DragonFly dB scale quirk (0-50 variant)\n"); - kctl->tlv.p = scale; - kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; - kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + if (!deconflict_playback_tlv_quirks(mixer, kctl)) + return; + if (cval->min == 0 && cval->max == 50) { + snd_usb_apply_db_scale(mixer, cval, kctl, scale, "DragonFly"); } else if (cval->min == 0 && cval->max <= 1000) { /* Some other clearly broken DragonFly variant. * At least a 0..53 variant (hw v1.0) exists. */ - usb_audio_info(mixer->chip, "ignoring too narrow dB range on a DragonFly device"); - kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + snd_usb_apply_db_scale(mixer, cval, kctl, NULL, "DragonFly"); + } +} + +static void snd_ab13x_quirks(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + /* + * Volume mapping based on voltage level measurement @ 1000Hz: + * +--------+--------+-----------+---------+----------+--------+ + * | hex | dec | dec => dB | mVrms * | mV => dB | res | + * +--------+--------+-----------+---------+----------+--------+ + * | 0xd300 | -11520 | -45.00 | | | | + * | 0xdb5a | -9382 | -36.65 | 1.285 | -26.68 | 2138 # | + * | 0xe2d0 | -7472 | -29.19 | 4.104 | -21.63 | 1910 # | + * | 0xe7cc | -6196 | -24.20 | 13.693 | -16.40 | 1276 # | + * | 0xeb8c | -5236 | -20.45 | 26.71 | -13.50 | 960 | + * | 0xee8d | -4467 | -17.45 | 42.29 | -11.50 | 769 | + * | 0xf10e | -3826 | -14.95 | 66.81 | -9.52 | 641 | + * | 0xf335 | -3275 | -12.79 | 94.43 | -8.01 | 551 | + * | 0xf517 | -2793 | -10.91 | 133.25 | -6.52 | 482 | + * | 0xf6c5 | -2363 | -9.23 | 168.73 | -5.49 | 430 | + * | 0xf847 | -1977 | -7.72 | 212.05 | -4.50 | 386 | + * | 0xf9a7 | -1625 | -6.35 | 267.1 | -3.50 | 352 | + * | 0xfae9 | -1303 | -5.09 | 336.3 | -2.50 | 322 | + * | 0xfc13 | -1005 | -3.93 | 377.4 | -2.00 | 298 | + * | 0xfd27 | -729 | -2.85 | 423.6 | -1.50 | 276 | + * | 0xfe29 | -471 | -1.84 | 475.3 | -1.00 | 258 | + * | 0xff69 | -151 | -0.59 | 533.1 | -0.50 | 320 | + * | 0x0000 | 0 | 0.00 | 597.7 | 0.00 | 151 | + * +--------+--------+-----------+---------+----------+--------+ + * + * Footnote *: measured through a power amplifier's loopback line out + * to ensure high input impedance (see #). The raw voltage at 0x0000 + * without going through a power amplifier is 677mVrms. + * + * Footnote #: low output power. Even the impedance of a multimeter is + * too low, causing a massive voltage drop (-10dB!), which means that + * they are inaudible when connected to earphones. Do not expose them. + * + * The volume control mixer has uneven resolutions in reality. Setting + * any value between [0xd300, 0xdb5a] results in the same audible volume + * as 0xdb5a, (0xdb5a, 0xe2d0] results in the same audible volume as + * 0xe2d0, and so on. The read back value always acts like res == 1. + */ + static const DECLARE_TLV_DB_RANGE(scale, + 0, 0, TLV_DB_SCALE_ITEM(-1350, 0, 0), /* -5500 */ + 1, 3, TLV_DB_SCALE_ITEM(-1150, 0, 0), /* -5200, -4900, -4600 */ + 4, 5, TLV_DB_SCALE_ITEM( -952, 0, 0), /* -4300, -4000 */ + 6, 7, TLV_DB_SCALE_ITEM( -801, 0, 0), /* -3700, -3400 */ + 8, 9, TLV_DB_SCALE_ITEM( -652, 0, 0), /* -3100, -2800 */ + 10, 13, TLV_DB_MINMAX_ITEM(-550, -250), /* -2500, -2200, -1900, -1600 */ + 14, 18, TLV_DB_MINMAX_ITEM(-200, 0), /* -1300, -1000, -700, -400, -100 */ + ); + + if (cval->min == -11520 && cval->max == 0 && cval->res == 1) { + if (!deconflict_playback_tlv_quirks(mixer, kctl)) + return; + + /* + * These values are carefully chosen so that volume steps with + * 0.5dB or 1.0dB resolution can be approximately represented + * togother. + * + * It's literally a miracle that these values can flawlessly + * align to 100. + */ + snd_usb_apply_db_scale_val(mixer, cval, kctl, scale, "AB13X", -5500, -100, 300); + } else if (cval->min == 0 && cval->max == 8191 && cval->res == 1) { + /* The final effects will be printed later. */ + usb_audio_info(mixer->chip, "applying AB13X Capture quirk (0/8191/1 variant)\n"); + + if (deconflict_capture_tlv_quirks(mixer, kctl)) + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); + } else { + usb_audio_dbg(mixer->chip, "not applying AB13X quirk on unknown variant\n"); + } +} + +static void snd_xiaomi_speaker_mini_quirk_db_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + /* + * Volume mapping based on normalized microphone measurement @ 1000Hz: + * +--------+--------+-----------+-------------+--------+ + * | hex | dec | dec => dB | measured dB | res | + * +--------+--------+-----------+-------------+--------+ + * | 0xd000 | -12288 | -48.00 | (mute) | | + * | 0xdd00 | -8960 | -35.00 | -68.6 | 3328 # | + * | 0xe540 | -6848 | -26.75 | -61.2 | 2112 | + * | 0xea40 | -5568 | -21.75 | -57.0 | 1280 | + * | 0xed80 | -4736 | -18.50 | -52.5 | 832 | + * | 0xf0a0 | -3936 | -15.38 | -48.3 | 800 | + * | 0xf2d0 | -3376 | -13.19 | -42.1 | 560 | + * | 0xf4d0 | -2864 | -11.19 | -36.1 | 512 | + * | 0xf6c0 | -2368 | -9.25 | -32.0 | 496 | + * | 0xf840 | -1984 | -7.75 | -24.0 | 384 | + * | 0xf9e0 | -1568 | -6.13 | -17.9 | 416 | + * | 0xfb30 | -1232 | -4.81 | -13.9 | 336 | + * | 0xfc80 | -896 | -3.50 | -10.0 | 336 | + * | 0xfd90 | -624 | -2.44 | -6.0 | 272 | + * | 0xfeb0 | -336 | -1.31 | -4.0 | 288 | + * | 0xffb0 | -80 | -0.31 | -2.0 | 256 | + * | 0x0000 | 0 | 0.00 | 0.0 | 80 | + * +--------+--------+-----------+-------------+--------+ + * + * The volume control mixer declares res == 256, but actually acts like + * res == 1 (i.e., accepts any value between min and max) and has uneven + * resolutions in reality. Setting any value between (0xd000, 0xdd00] + * results in the same audible volume as 0xdd00, and so on. + * + * Footnote #: running out of the 10-item limit of TLV ranges. + */ + static const DECLARE_TLV_DB_RANGE(scale, + 0, 0, TLV_DB_SCALE_ITEM( -6120, 0, 0), /* -7104 */ + 1, 5, TLV_DB_SCALE_ITEM( -5700, 0, 0), /* -6808, -6512, -6216, -5920, -5624 */ + 6, 8, TLV_DB_SCALE_ITEM( -5250, 0, 0), /* -5328, -5032, -4736 */ + 9, 10, TLV_DB_SCALE_ITEM( -4830, 0, 0), /* -4440, -4144 */ + 11, 12, TLV_DB_SCALE_ITEM( -4210, 0, 0), /* -3848, -3552 */ + 13, 14, TLV_DB_SCALE_ITEM( -3610, 0, 0), /* -3256, -2960 */ + 15, 16, TLV_DB_SCALE_ITEM( -3200, 0, 0), /* -2664, -2368 */ + 17, 18, TLV_DB_MINMAX_ITEM(-2400, -1790), /* -2072, -1776 */ + 19, 21, TLV_DB_MINMAX_ITEM(-1390, -600), /* -1480, -1184, -888 */ + 22, 24, TLV_DB_MINMAX_ITEM( -400, 0), /* -592, -296, 0 */ + ); + + if (cval->min == -12288 && cval->max == 0 && cval->res == 256) { + if (!deconflict_playback_tlv_quirks(mixer, kctl)) + return; + + /* + * These values are carefully chosen so that volume steps with + * 2dB or 4dB resolution can be approximately represented + * togother. + */ + snd_usb_apply_db_scale_val(mixer, cval, kctl, scale, + "Xiaomi Speaker Mini", -7104, 0, 296); + } else { + usb_audio_dbg(mixer->chip, + "not applying Xiaomi Speaker Mini quirk on unknown variant\n"); } } @@ -4694,13 +5164,11 @@ static void snd_usb_mv_silicon_quirks(struct usb_mixer_interface *mixer, /* The final effects will be printed later. */ usb_audio_info(mixer->chip, "applying MV-SILICON quirks (0/4096/1 variant)\n"); - /* Respect MIN_MUTE set by module parameters. */ - if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE)) - mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL; - if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE)) - mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL; + if (deconflict_playback_tlv_quirks(mixer, kctl) || + deconflict_capture_tlv_quirks(mixer, kctl)) + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); } else { - usb_audio_dbg(mixer->chip, "not applying MV-SILICON quirks on unknown variant"); + usb_audio_dbg(mixer->chip, "not applying MV-SILICON quirks on unknown variant\n"); } } @@ -4750,34 +5218,23 @@ static void snd_fix_plt_name(struct snd_usb_audio *chip, usb_audio_dbg(chip, "something wrong in kctl name %s\n", id->name); } -static void snd_usb_mixer_fu_quirk_linear_scale(struct usb_mixer_interface *mixer, - struct usb_mixer_elem_info *cval, - struct snd_kcontrol *kctl) -{ - static const DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0); - - if (cval->min_mute) { - /* - * We are clearing SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, - * resulting in min_mute being a no-op. - */ - usb_audio_warn(mixer->chip, "LINEAR_VOL overrides MIN_MUTE\n"); - } - - kctl->tlv.p = scale; - kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; - kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; -} - void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, int unitid, struct snd_kcontrol *kctl) { switch (mixer->chip->usb_id) { + case USB_ID(0x001f, 0x0b21): /* AB13X Headset Adapter */ + if (cval->control == UAC_FU_VOLUME) + snd_ab13x_quirks(mixer, cval, kctl); + break; case USB_ID(0x21b4, 0x0081): /* AudioQuest DragonFly */ if (unitid == 7 && cval->control == UAC_FU_VOLUME) snd_dragonfly_quirk_db_scale(mixer, cval, kctl); break; + case USB_ID(0x2717, 0x5096): /* Xiaomi Speaker Mini */ + if (cval->control == UAC_FU_VOLUME) + snd_xiaomi_speaker_mini_quirk_db_scale(mixer, cval, kctl); + break; } if (cval->control == UAC_FU_VOLUME && @@ -4801,18 +5258,12 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, } if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL) - if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Playback")) { - usb_audio_info(mixer->chip, - "applying playback linear volume quirk\n"); + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Playback")) snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); - } if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL) - if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Capture")) { - usb_audio_info(mixer->chip, - "applying capture linear volume quirk\n"); + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Capture")) snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); - } /* ALSA-ify some Plantronics headset control names */ if (USB_ID_VENDOR(mixer->chip->usb_id) == 0x047f && diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index ed5fe746d43856..2b916eddbbf3e0 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -453,6 +453,7 @@ static void scarlett2_notify_direct_monitor(struct usb_mixer_interface *mixer); static void scarlett2_notify_power_status(struct usb_mixer_interface *mixer); static void scarlett2_notify_pcm_input_switch( struct usb_mixer_interface *mixer); +static void scarlett2_notify_select_pcm_in(struct usb_mixer_interface *mixer); static void scarlett2_notify_bluetooth(struct usb_mixer_interface *mixer); /* Arrays of notification callback functions */ @@ -513,6 +514,23 @@ static const struct scarlett2_notification scarlett4_2i2_notifications[] = { { 0, NULL } }; +static const struct scarlett2_notification +scarlett4_2i2_2449_notifications[] = { + { 0x00000001, scarlett2_notify_ack }, + { 0x00000008, scarlett2_notify_sync }, + { 0x00200000, scarlett2_notify_input_safe }, + { 0x00400000, scarlett2_notify_autogain }, + { 0x00800000, scarlett2_notify_input_air }, + { 0x01000000, scarlett2_notify_direct_monitor }, + { 0x02000000, scarlett2_notify_select_pcm_in }, + { 0x04000000, scarlett2_notify_input_level }, + { 0x08000000, scarlett2_notify_input_phantom }, + { 0x10000000, NULL }, /* power status, ignored */ + { 0x40000000, scarlett2_notify_input_gain }, + { 0x80000000, NULL }, /* power status, ignored */ + { 0, NULL } +}; + static const struct scarlett2_notification scarlett4_4i4_notifications[] = { { 0x00000001, scarlett2_notify_ack }, { 0x00000008, scarlett2_notify_sync }, @@ -616,6 +634,7 @@ struct scarlett2_config_set { const unsigned int *input_gain_tlv; const char *const *autogain_status_texts; const struct scarlett2_config items[SCARLETT2_CONFIG_COUNT]; + const int (*port_count)[SCARLETT2_PORT_DIRNS]; }; /* Map firmware versions to config sets per-device. @@ -926,6 +945,42 @@ static const struct scarlett2_config_set scarlett2_config_set_gen4_solo_2417 = { } }; +/* Solo Gen 4, firmware version 2449 and above */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_solo_2449 = { + .notifications = scarlett4_solo_notifications, + .param_buf_addr = 0xe0, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x4f, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR] = { + .offset = 0x110, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x4e, .size = 8, .activate = 9, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x45, .size = 8, .activate = 10, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x46, .size = 8, .activate = 11, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PCM_INPUT_SWITCH] = { + .offset = 0x20e, .size = 8, .activate = 25, .pbuf = 1 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { + .offset = 0x23a, .size = 16, .activate = 26 }, + + [SCARLETT2_CONFIG_FP_BRIGHTNESS] = { + .offset = 0x24b, .size = 8, .activate = 27, .pbuf = 1 }, + + [SCARLETT2_CONFIG_FP_SLEEP_TIME] = { + .offset = 0x250, .size = 32, .activate = 29 } + } +}; + /* 2i2 Gen 4 */ static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2 = { .notifications = scarlett4_2i2_notifications, @@ -1043,6 +1098,76 @@ static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2_2417 = { } }; +/* 2i2 Gen 4, firmware version 2449 and above */ +static const int scarlett2_gen4_2i2_2449_port_count[][SCARLETT2_PORT_DIRNS] = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_MIX] = { 8, 6 }, + [SCARLETT2_PORT_TYPE_PCM] = { 2, 4 }, +}; + +static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2_2449 = { + .notifications = scarlett4_2i2_2449_notifications, + .port_count = scarlett2_gen4_2i2_2449_port_count, + .param_buf_addr = 0x114, + .input_gain_tlv = db_scale_gen4_gain, + .autogain_status_texts = scarlett2_autogain_status_gen4, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x61, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR] = { + .offset = 0x162, .size = 8, .activate = 16, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x14d, .size = 8, .activate = 10, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x14f, .size = 8 }, + + [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { + .offset = 0x149, .size = 8, .activate = 29, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { + .offset = 0x14a, .size = 8, .activate = 30, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x60, .size = 8, .activate = 11, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x63, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x54, .size = 8, .activate = 13, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x15f, .size = 8, .activate = 14, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x56, .size = 8, .activate = 15, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x163, .size = 8, .activate = 17, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x166, .size = 8, .activate = 18, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PCM_INPUT_SWITCH] = { + .offset = 0x4, .size = 8, .activate = 40, .pbuf = 1 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { + .offset = 0x2b0, .size = 16, .activate = 36 }, + + [SCARLETT2_CONFIG_FP_BRIGHTNESS] = { + .offset = 0x2d3, .size = 8, .activate = 37, .pbuf = 1 }, + + [SCARLETT2_CONFIG_FP_SLEEP_TIME] = { + .offset = 0x2d8, .size = 32, .activate = 39 } + } +}; + /* 4i4 Gen 4 */ static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4 = { .notifications = scarlett4_4i4_notifications, @@ -1167,6 +1292,136 @@ static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4_2417 = { } }; +/* 4i4 Gen 4, firmware version 2432 and above */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4_2432 = { + .notifications = scarlett4_4i4_notifications, + .param_buf_addr = 0x130, + .input_gain_tlv = db_scale_gen4_gain, + .autogain_status_texts = scarlett2_autogain_status_gen4, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x13e, .size = 8, .activate = 10, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x140, .size = 8 }, + + [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { + .offset = 0x13a, .size = 8, .activate = 23, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { + .offset = 0x13b, .size = 8, .activate = 24, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x5a, .size = 8, .activate = 11, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x5e, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x4e, .size = 8, .activate = 13, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x150, .size = 8, .activate = 14, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x50, .size = 8, .activate = 15, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x153, .size = 8, .activate = 16, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x156, .size = 8, .activate = 17, .pbuf = 1 }, + + [SCARLETT2_CONFIG_MASTER_VOLUME] = { + .offset = 0x32, .size = 16 }, + + [SCARLETT2_CONFIG_HEADPHONE_VOLUME] = { + .offset = 0x3a, .size = 16 }, + + [SCARLETT2_CONFIG_POWER_EXT] = { + .offset = 0x168, .size = 8 }, + + [SCARLETT2_CONFIG_POWER_LOW] = { + .offset = 0x16f, .size = 8 }, + + [SCARLETT2_CONFIG_FP_BRIGHTNESS] = { + .offset = 0x3c5, .size = 8, .activate = 36, .pbuf = 1 }, + + [SCARLETT2_CONFIG_FP_SLEEP_TIME] = { + .offset = 0x3c8, .size = 32, .activate = 38 } + } +}; + +/* 4i4 Gen 4, firmware version 2449 and above */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4_2449 = { + .notifications = scarlett4_4i4_notifications, + .param_buf_addr = 0x138, + .input_gain_tlv = db_scale_gen4_gain, + .autogain_status_texts = scarlett2_autogain_status_gen4, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x64, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x146, .size = 8, .activate = 10, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x148, .size = 8 }, + + [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { + .offset = 0x142, .size = 8, .activate = 23, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { + .offset = 0x143, .size = 8, .activate = 24, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x62, .size = 8, .activate = 11, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x66, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x56, .size = 8, .activate = 13, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x158, .size = 8, .activate = 14, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x58, .size = 8, .activate = 15, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x15b, .size = 8, .activate = 16, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x15e, .size = 8, .activate = 17, .pbuf = 1 }, + + [SCARLETT2_CONFIG_MASTER_VOLUME] = { + .offset = 0x3a, .size = 16 }, + + [SCARLETT2_CONFIG_HEADPHONE_VOLUME] = { + .offset = 0x42, .size = 16 }, + + [SCARLETT2_CONFIG_POWER_EXT] = { + .offset = 0x170, .size = 8 }, + + [SCARLETT2_CONFIG_POWER_LOW] = { + .offset = 0x177, .size = 8 }, + + [SCARLETT2_CONFIG_FP_BRIGHTNESS] = { + .offset = 0x3cd, .size = 8, .activate = 36, .pbuf = 1 }, + + [SCARLETT2_CONFIG_FP_SLEEP_TIME] = { + .offset = 0x3d0, .size = 32, .activate = 38 } + } +}; + /* Clarett USB and Clarett+ devices: 2Pre, 4Pre, 8Pre */ static const struct scarlett2_config_set scarlett2_config_set_clarett = { .notifications = scarlett2_notifications, @@ -1412,6 +1667,7 @@ struct scarlett2_data { struct delayed_work work; const struct scarlett2_device_info *info; const struct scarlett2_config_set *config_set; + const int (*port_count)[SCARLETT2_PORT_DIRNS]; const char *series_name; __u8 bInterfaceNumber; __u8 bEndpointAddress; @@ -2111,6 +2367,7 @@ static const struct scarlett2_device_info solo_gen4_info = { .config_sets = (const struct scarlett2_config_set_entry[]) { { 2115, &scarlett2_config_set_gen4_solo }, { 2417, &scarlett2_config_set_gen4_solo_2417 }, + { 2449, &scarlett2_config_set_gen4_solo_2449 }, { } }, .has_devmap = 1, @@ -2169,6 +2426,7 @@ static const struct scarlett2_device_info s2i2_gen4_info = { .config_sets = (const struct scarlett2_config_set_entry[]) { { 2115, &scarlett2_config_set_gen4_2i2 }, { 2417, &scarlett2_config_set_gen4_2i2_2417 }, + { 2449, &scarlett2_config_set_gen4_2i2_2449 }, { } }, .has_devmap = 1, @@ -2227,6 +2485,8 @@ static const struct scarlett2_device_info s4i4_gen4_info = { .config_sets = (const struct scarlett2_config_set_entry[]) { { 2089, &scarlett2_config_set_gen4_4i4 }, { 2417, &scarlett2_config_set_gen4_4i4_2417 }, + { 2432, &scarlett2_config_set_gen4_4i4_2432 }, + { 2449, &scarlett2_config_set_gen4_4i4_2449 }, { } }, .has_devmap = 1, @@ -3158,7 +3418,7 @@ static int scarlett2_usb_set_mix(struct usb_mixer_interface *mixer, NULL, 0); } -/* Convert a port number index (per info->port_count) to a hardware ID */ +/* Convert a port number index (per private->port_count) to a hardware ID */ static u32 scarlett2_mux_src_num_to_id( const int port_count[][SCARLETT2_PORT_DIRNS], int num) { @@ -3202,8 +3462,7 @@ static u32 scarlett2_mux_id_to_num( static void scarlett2_usb_populate_mux(struct scarlett2_data *private, u32 mux_entry) { - const struct scarlett2_device_info *info = private->info; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = private->port_count; int dst_idx, src_idx; @@ -3342,7 +3601,7 @@ static int scarlett2_usb_set_mux(struct usb_mixer_interface *mixer) { struct scarlett2_data *private = mixer->private_data; const struct scarlett2_device_info *info = private->info; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = private->port_count; int table; struct { @@ -7365,8 +7624,7 @@ static int scarlett2_mux_src_enum_ctl_info(struct snd_kcontrol *kctl, { struct usb_mixer_elem_info *elem = kctl->private_data; struct scarlett2_data *private = elem->head.mixer->private_data; - const struct scarlett2_device_info *info = private->info; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = private->port_count; unsigned int item = uinfo->value.enumerated.item; int items = private->num_mux_srcs; int port_type; @@ -7466,8 +7724,7 @@ static const struct snd_kcontrol_new scarlett2_mux_src_enum_ctl = { static int scarlett2_add_mux_enums(struct usb_mixer_interface *mixer) { struct scarlett2_data *private = mixer->private_data; - const struct scarlett2_device_info *info = private->info; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = private->port_count; int port_type, channel, i; for (i = 0, port_type = 0; @@ -8516,6 +8773,15 @@ static void scarlett2_notify_pcm_input_switch(struct usb_mixer_interface *mixer) scarlett2_notify_mux(mixer); } +/* Notify on input select or PCM input switch change; 2i2 Gen 4 + * firmware 2449 uses one notification for both + */ +static void scarlett2_notify_select_pcm_in(struct usb_mixer_interface *mixer) +{ + scarlett2_notify_input_select(mixer); + scarlett2_notify_pcm_input_switch(mixer); +} + /* Notify on Bluetooth change */ static void scarlett2_notify_bluetooth(struct usb_mixer_interface *mixer) { @@ -8696,9 +8962,12 @@ static void scarlett2_count_io(struct scarlett2_data *private) { const struct scarlett2_device_info *info = private->info; const struct scarlett2_config_set *config_set = private->config_set; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS]; int port_type, srcs = 0, dsts = 0, i; + port_count = config_set->port_count ?: info->port_count; + private->port_count = port_count; + /* Count the number of mux sources and destinations */ for (port_type = 0; port_type < SCARLETT2_PORT_TYPE_COUNT; diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index e4bfd43a2488b9..5ee568e0454cb4 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1969,8 +1969,13 @@ static int qc_usb_audio_probe(struct auxiliary_device *auxdev, QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, &uaudio_svc_ops_options, &uaudio_stream_req_handlers); + if (ret) + goto free_qmi_hdl; + ret = qmi_add_server(svc->uaudio_svc_hdl, UAUDIO_STREAM_SERVICE_ID_V01, UAUDIO_STREAM_SERVICE_VERS_V01, 0); + if (ret) + goto release_qmi_hdl; uaudio_svc = svc; @@ -1987,7 +1992,9 @@ static int qc_usb_audio_probe(struct auxiliary_device *auxdev, release_qmi: qc_usb_audio_cleanup_qmi_dev(); +release_qmi_hdl: qmi_handle_release(svc->uaudio_svc_hdl); +free_qmi_hdl: kfree(svc->uaudio_svc_hdl); free_svc: kfree(svc); diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 4793fd1f85fef3..b6da5bcb15e400 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -404,6 +404,20 @@ YAMAHA_DEVICE(0x105d, NULL), } } }, +{ + USB_DEVICE(0x0499, 0x1516), + QUIRK_DRIVER_INFO { + /* .vendor_name = "Yamaha", */ + /* .product_name = "ZG02", */ + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + { QUIRK_DATA_IGNORE(4) }, + QUIRK_COMPOSITE_END + } + } +}, { USB_DEVICE(0x0499, 0x1718), QUIRK_DRIVER_INFO { @@ -441,6 +455,20 @@ YAMAHA_DEVICE(0x7000, "DTX"), YAMAHA_DEVICE(0x7010, "UB99"), #undef YAMAHA_DEVICE #undef YAMAHA_INTERFACE +/* Steinberg UR22C (configuration 2) */ +{ + USB_DEVICE_VENDOR_SPEC(0x0499, 0x172f), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + { QUIRK_DATA_MIDI_YAMAHA(4) }, + QUIRK_COMPOSITE_END + } + } +}, /* this catches most recent vendor-specific Yamaha devices */ { .match_flags = USB_DEVICE_ID_MATCH_VENDOR | @@ -4437,6 +4465,48 @@ QUIRK_RME_DIGIFACE(0x3fa0), } }, +/* + * CVT Touch Device (1ff7:0f33) is a USB composite device with HID + UAC + * audio interfaces. The device iProduct is "Touch Device" but Windows + * displays the audio function as "MAXHUB Speaker" (from iTerminal). + * Override the card shortname to match the Windows display name, while + * keeping normal stream/mixer creation (ifnum = QUIRK_NO_INTERFACE). + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x1ff7, + .idProduct = 0x0f33, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .product_name = "MAXHUB Speaker", + .ifnum = QUIRK_NO_INTERFACE, + } +}, + +/* + * CVT Touch Device (1ff7:0f33) is a USB composite device with HID + UAC + * audio interfaces. The device iProduct is "Touch Device" but Windows + * displays the audio function as "MAXHUB Speaker" (from iTerminal). + * Override the card shortname to match the Windows display name, while + * keeping normal stream/mixer creation (ifnum = QUIRK_NO_INTERFACE). + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x1ff7, + .idProduct = 0x0f33, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .product_name = "MAXHUB Speaker", + .ifnum = QUIRK_NO_INTERFACE, + } +}, + #undef QUIRK_AF16RIG_CLOCK #undef QUIRK_AF16RIG diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 4ec1f2a0b359ad..7c581e283903ff 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2556,6 +2556,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x1516, /* Yamaha ZG02 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0499, 0x3108, /* Yamaha YIT-W12TX */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x04d8, 0xfeea, /* Benchmark DAC1 Pre */ @@ -2809,6 +2811,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x2b73, 0x0047, /* AlphaTheta EUPHONIA */ QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x2bdf, 0x0280, /* HIK 1080P Camera */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x2d95, 0x8011, /* VIVO USB-C HEADSET */ QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x2d95, 0x8021, /* VIVO USB-C-XE710 HEADSET */ diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index 2986a90ea33512..6e491736ca5ba5 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -129,19 +129,26 @@ static const struct vm_operations_struct usb_stream_hwdep_vm_ops = { static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file) { struct us122l *us122l = hw->private_data; - struct usb_interface *iface; + struct usb_interface *iface, *iface0 = NULL; if (hw->used >= 2) return -EBUSY; - if (!us122l->first) - us122l->first = file; - if (us122l->is_us144) { - iface = usb_ifnum_to_if(us122l->dev, 0); - usb_autopm_get_interface(iface); + iface0 = usb_ifnum_to_if(us122l->dev, 0); + if (!iface0) + return -ENODEV; } + iface = usb_ifnum_to_if(us122l->dev, 1); + if (!iface) + return -ENODEV; + + if (!us122l->first) + us122l->first = file; + + if (iface0) + usb_autopm_get_interface(iface0); usb_autopm_get_interface(iface); return 0; } @@ -153,10 +160,12 @@ static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file) if (us122l->is_us144) { iface = usb_ifnum_to_if(us122l->dev, 0); - usb_autopm_put_interface(iface); + if (iface) + usb_autopm_put_interface(iface); } iface = usb_ifnum_to_if(us122l->dev, 1); - usb_autopm_put_interface(iface); + if (iface) + usb_autopm_put_interface(iface); if (us122l->first == file) us122l->first = NULL; guard(mutex)(&us122l->mutex); @@ -179,6 +188,8 @@ static int usb_stream_hwdep_mmap(struct snd_hwdep *hw, offset = area->vm_pgoff << PAGE_SHIFT; guard(mutex)(&us122l->mutex); s = us122l->sk.s; + if (!s) + return -ENODEV; read = offset < s->read_size; if (read) { if (area->vm_flags & VM_WRITE) diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c index 7f153a2fcdd813..3800c5b5887c81 100644 --- a/sound/usb/usx2y/usX2Yhwdep.c +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -179,6 +179,9 @@ static int snd_usx2y_hwdep_dsp_load(struct snd_hwdep *hw, int lret, err; char *buf; + if (priv->chip_status & USX2Y_STAT_CHIP_HUP) + return -ENODEV; + buf = memdup_user(dsp->image, dsp->length); if (IS_ERR(buf)) return PTR_ERR(buf); @@ -201,11 +204,13 @@ static int snd_usx2y_hwdep_dsp_load(struct snd_hwdep *hw, err = usx2y_in04_init(priv); if (err) { dev_err(&dev->dev, "usx2y_in04_init error\n"); + priv->chip_status |= USX2Y_STAT_CHIP_HUP; return err; } err = usx2y_create_alsa_devices(hw->card); if (err) { dev_err(&dev->dev, "usx2y_create_alsa_devices error %i\n", err); + priv->chip_status |= USX2Y_STAT_CHIP_HUP; return err; } priv->chip_status |= USX2Y_STAT_CHIP_INIT; diff --git a/sound/usb/usx2y/usb_stream.c b/sound/usb/usx2y/usb_stream.c index 3122cf653273df..15e01c77567c61 100644 --- a/sound/usb/usx2y/usb_stream.c +++ b/sound/usb/usx2y/usb_stream.c @@ -682,16 +682,17 @@ int usb_stream_start(struct usb_stream_kernel *sk) s->sync_packet = -1; s->inpacket_head = -1; sk->iso_frame_balance = 0; + sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2]; + sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2]; + sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1]; + sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1]; ++try; for (u = 0; u < 2; u++) { struct urb *inurb = sk->inurb[u]; struct urb *outurb = sk->outurb[u]; playback_prep_freqn(sk, outurb); - inurb->number_of_packets = outurb->number_of_packets; - inurb->transfer_buffer_length = - inurb->number_of_packets * - inurb->iso_frame_desc[0].length; + prepare_inurb(outurb->number_of_packets, inurb); if (u == 0) { int now; @@ -739,11 +740,6 @@ int usb_stream_start(struct usb_stream_kernel *sk) return -EFAULT; } - sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2]; - sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2]; - sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1]; - sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1]; - /* wait, check */ { int wait_ms = 3000; diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c index a3a0bc13632b8c..798c9bb5386a2b 100644 --- a/sound/usb/usx2y/usbusx2yaudio.c +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -51,6 +51,18 @@ static int usx2y_urb_capt_retire(struct snd_usx2y_substream *subs) dev_dbg(&usx2y->dev->dev, "%s: 0 == len ERROR!\n", __func__); continue; } + /* + * snd_usx2y_pcm_open() constrains the buffer to hold at least + * two URBs, so this cannot happen for a device that keeps its + * packets at 50 frames. Drop the packet rather than exceed the + * single wrap below. + */ + if (unlikely(len > runtime->buffer_size)) { + dev_err_ratelimited(&usx2y->dev->dev, + "%s: packet of %i frames exceeds %lu-frame buffer, dropping\n", + __func__, len, runtime->buffer_size); + continue; + } /* copy a data chunk */ if ((hwptr_done + len) > runtime->buffer_size) { @@ -106,14 +118,31 @@ static int usx2y_urb_play_prepare(struct snd_usx2y_substream *subs, __func__, counts); return -EPIPE; } + if (counts * usx2y->stride > subs->maxpacksize) { + dev_err_ratelimited(&usx2y->dev->dev, + "%s: packet of %i bytes exceeds maxpacksize %u\n", + __func__, counts * usx2y->stride, + subs->maxpacksize); + return -EPIPE; + } /* set up descriptor */ urb->iso_frame_desc[pack].offset = pack ? urb->iso_frame_desc[pack - 1].offset + urb->iso_frame_desc[pack - 1].length : 0; - urb->iso_frame_desc[pack].length = cap_urb->iso_frame_desc[pack].actual_length; + urb->iso_frame_desc[pack].length = counts * usx2y->stride; } if (atomic_read(&subs->state) >= STATE_PRERUNNING) { + /* + * The copy below wraps once, which needs count <= buffer_size. + * snd_usx2y_pcm_open() guarantees it; guard anyway. + */ + if (unlikely(count > runtime->buffer_size)) { + dev_err_ratelimited(&usx2y->dev->dev, + "%s: URB of %i frames exceeds %lu-frame buffer, stopping\n", + __func__, count, runtime->buffer_size); + return -EPIPE; + } if (subs->hwptr + count > runtime->buffer_size) { /* err, the transferred area goes over buffer boundary. * copy the data to the temp buffer. @@ -888,6 +917,18 @@ static int snd_usx2y_pcm_open(struct snd_pcm_substream *substream) runtime->private_data = subs; subs->pcm_substream = substream; snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000); + /* + * Capture wraps the ring buffer once per packet and playback once + * per URB of nr_of_packs() packets of at most 50 frames (see + * usx2y_urb_play_prepare()). Playback needs the buffer to hold more + * than one URB and capture more than one packet; use the URB bound + * for both, and two URBs rather than one, because the position only + * advances when a URB is less than the whole buffer. The bound is in + * frames and does not depend on the altsetting, which is selected + * in prepare. + */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + 2 * nr_of_packs() * 50, UINT_MAX); return 0; } diff --git a/tools/testing/selftests/alsa/.gitignore b/tools/testing/selftests/alsa/.gitignore index 7b0e1e9ebf1b1e..a0762c3c52e6e9 100644 --- a/tools/testing/selftests/alsa/.gitignore +++ b/tools/testing/selftests/alsa/.gitignore @@ -2,5 +2,6 @@ aloop-test global-timer mixer-test pcm-test +seq-ump-test test-pcmtest-driver utimer-test diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index afd64a679dc1f6..92de58287e7786 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -16,7 +16,8 @@ LDLIBS+=-lpthread OVERRIDE_TARGETS = 1 -TEST_GEN_PROGS := aloop-test mixer-test pcm-test test-pcmtest-driver utimer-test +TEST_GEN_PROGS := aloop-test mixer-test pcm-test seq-ump-test test-pcmtest-driver \ + utimer-test TEST_GEN_PROGS_EXTENDED := libatest.so global-timer diff --git a/tools/testing/selftests/alsa/seq-ump-test.c b/tools/testing/selftests/alsa/seq-ump-test.c new file mode 100644 index 00000000000000..d1eca920cfc8e1 --- /dev/null +++ b/tools/testing/selftests/alsa/seq-ump-test.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test the conversion of MIDI 1.0 sequencer events to UMP. + * + * A legacy client sends CC and CONTROL14 events to a UMP client, and the + * test checks the packets that the UMP client receives. The converter keeps + * its RPN/NRPN state per destination port and channel, so each test uses + * new clients. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest_harness.h" + +#define MAX_PACKETS 8 +#define CAP_IN (SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE) +#define CAP_OUT (SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ) +#define READ_TIMEOUT_MS 100 + +/* first word of a MIDI 2.0 RPN/NRPN packet on group 0 */ +#define RPN(ch, bank, index) \ + (0x40200000u | ((ch) << 16) | ((bank) << 8) | (index)) +#define NRPN(ch, bank, index) \ + (0x40300000u | ((ch) << 16) | ((bank) << 8) | (index)) + +struct packet { + unsigned int w0; + unsigned int w1; +}; + +struct seq_client { + int fd; + int id; + int port; +}; + +/* the same scaling as the kernel's upscale_14_to_32bit() */ +static unsigned int data14(unsigned int msb, unsigned int lsb) +{ + unsigned int src = (msb << 7) | lsb; + unsigned int val = src << 18; + unsigned int repeat; + + if (src <= 0x2000) + return val; + repeat = src & 0x1fff; + return val | (repeat << 5) | (repeat >> 8); +} + +/* returns 0, or a negative errno with *skip set when UMP is unavailable */ +static int open_client(struct seq_client *c, int midi_version, + unsigned int cap, int *skip) +{ + struct snd_seq_client_info info; + struct snd_seq_port_info port; + int ver = SNDRV_SEQ_VERSION; + + c->fd = open("/dev/snd/seq", O_RDWR | O_NONBLOCK); + if (c->fd < 0) { + *skip = 1; + return -errno; + } + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_CLIENT_ID, &c->id) < 0) + return -errno; + + if (midi_version) { + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_USER_PVERSION, &ver) < 0) { + *skip = 1; + return -errno; + } + memset(&info, 0, sizeof(info)); + info.client = c->id; + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, &info) < 0) + return -errno; + info.midi_version = midi_version; + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &info) < 0) { + *skip = 1; + return -errno; + } + } + + memset(&port, 0, sizeof(port)); + port.addr.client = c->id; + strcpy(port.name, "seq-ump-test"); + port.capability = cap; + port.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_APPLICATION; + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_CREATE_PORT, &port) < 0) + return -errno; + c->port = port.addr.port; + return 0; +} + +static void close_client(struct seq_client *c) +{ + if (c->fd >= 0) + close(c->fd); + c->fd = -1; +} + +static int send_event(struct seq_client *src, struct seq_client *dst, + int type, int ch, int param, int value) +{ + struct snd_seq_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = type; + ev.flags = SNDRV_SEQ_TIME_STAMP_TICK | SNDRV_SEQ_EVENT_LENGTH_FIXED; + ev.queue = SNDRV_SEQ_QUEUE_DIRECT; + ev.source.client = src->id; + ev.source.port = src->port; + ev.dest.client = dst->id; + ev.dest.port = dst->port; + ev.data.control.channel = ch; + ev.data.control.param = param; + ev.data.control.value = value; + if (write(src->fd, &ev, sizeof(ev)) != sizeof(ev)) + return -errno; + return 0; +} + +/* + * Read what the UMP client received until it is quiet, and keep the + * packets of message type @mt. Returns the number of packets kept. + */ +static int read_packets(struct seq_client *dst, unsigned int mt, + struct packet *out) +{ + struct pollfd pfd = { .fd = dst->fd, .events = POLLIN }; + struct snd_seq_ump_event ev; + int n = 0; + + while (poll(&pfd, 1, READ_TIMEOUT_MS) > 0) { + if (read(dst->fd, &ev, sizeof(ev)) != sizeof(ev)) + break; + if (!(ev.flags & SNDRV_SEQ_EVENT_UMP) || ev.ump[0] >> 28 != mt) + continue; + if (n < MAX_PACKETS) { + out[n].w0 = ev.ump[0]; + out[n].w1 = ev.ump[1]; + } + n++; + } + return n; +} + +FIXTURE(seq_ump) { + struct seq_client src; + struct seq_client dst; +}; + +FIXTURE_SETUP(seq_ump) +{ + int skip = 0; + int err; + + self->src.fd = -1; + self->dst.fd = -1; + + err = open_client(&self->dst, SNDRV_SEQ_CLIENT_UMP_MIDI_2_0, + CAP_IN, &skip); + if (skip) + SKIP(return, "UMP sequencer client unavailable: %s", + strerror(-err)); + ASSERT_EQ(0, err); + + err = open_client(&self->src, 0, CAP_OUT, &skip); + ASSERT_EQ(0, err); +} + +FIXTURE_TEARDOWN(seq_ump) +{ + close_client(&self->src); + close_client(&self->dst); +} + +struct event { + int type; + int ch; + int param; + int value; +}; + +#define CC(ch, param, value) \ + { SNDRV_SEQ_EVENT_CONTROLLER, ch, param, value } +#define C14(ch, param, value) \ + { SNDRV_SEQ_EVENT_CONTROL14, ch, param, value } + +static int send_events(struct seq_client *src, struct seq_client *dst, + const struct event *ev, int n) +{ + int i, err; + + for (i = 0; i < n; i++) { + err = send_event(src, dst, ev[i].type, ev[i].ch, + ev[i].param, ev[i].value); + if (err) + return err; + } + return 0; +} + +#define SEND(...) do { \ + const struct event ev[] = { __VA_ARGS__ }; \ + \ + ASSERT_EQ(0, send_events(&self->src, &self->dst, ev, \ + ARRAY_SIZE(ev))); \ +} while (0) + +/* compare the RPN/NRPN packets received with the expected ones */ +#define EXPECT_PACKETS(...) do { \ + const struct packet want[] = { __VA_ARGS__ }; \ + struct packet got[MAX_PACKETS]; \ + int n = read_packets(&self->dst, 4, got); \ + int i; \ + \ + ASSERT_EQ((int)ARRAY_SIZE(want), n); \ + for (i = 0; i < n; i++) { \ + EXPECT_EQ(want[i].w0, got[i].w0); \ + EXPECT_EQ(want[i].w1, got[i].w1); \ + } \ +} while (0) + +#define EXPECT_NO_PACKETS() do { \ + struct packet got[MAX_PACKETS]; \ + \ + EXPECT_EQ(0, read_packets(&self->dst, 4, got)); \ +} while (0) + +/* RPN 0/1 with Data Entry */ +TEST_F(seq_ump, rpn) +{ + SEND(CC(0, 101, 0), CC(0, 100, 1), CC(0, 6, 0x40), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x40, 0) }); +} + +/* Data Entry after a Null RPN is not sent at the next selection */ +TEST_F(seq_ump, data_after_null_rpn) +{ + SEND(CC(0, 101, 0x7f), CC(0, 100, 0x7f), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 101, 0), CC(0, 100, 1)); + EXPECT_NO_PACKETS(); +} + +/* Data Entry before any selection is not sent at the next selection */ +TEST_F(seq_ump, data_before_selection) +{ + SEND(CC(0, 6, 0x40), CC(0, 38, 0), CC(0, 101, 0), CC(0, 100, 1)); + EXPECT_NO_PACKETS(); +} + +/* the same for an NRPN selected after a Null RPN */ +TEST_F(seq_ump, data_after_null_rpn_to_nrpn) +{ + SEND(CC(0, 101, 0x7f), CC(0, 100, 0x7f), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 99, 0), CC(0, 98, 5)); + EXPECT_NO_PACKETS(); +} + +/* a repeated Data Entry goes to the selected RPN */ +TEST_F(seq_ump, repeated_data) +{ + SEND(CC(0, 101, 0), CC(0, 100, 2), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 6, 0x10), CC(0, 38, 0), CC(0, 101, 0), CC(0, 100, 1)); + EXPECT_PACKETS({ RPN(0, 0, 2), data14(0x40, 0) }, + { RPN(0, 0, 2), data14(0x10, 0) }); +} + +/* two values for one RPN, then another RPN */ +TEST_F(seq_ump, two_rpns) +{ + SEND(CC(0, 101, 0), CC(0, 100, 0), CC(0, 6, 2), CC(0, 38, 0)); + SEND(CC(0, 6, 12), CC(0, 38, 0)); + SEND(CC(0, 101, 0), CC(0, 100, 1), CC(0, 6, 0x40), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 0, 0), data14(2, 0) }, + { RPN(0, 0, 0), data14(12, 0) }, + { RPN(0, 0, 1), data14(0x40, 0) }); +} + +/* the RPN number is held after a send, like the NRPN number */ +TEST_F(seq_ump, rpn_msb_held) +{ + SEND(CC(0, 101, 1), CC(0, 100, 1), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 100, 2), CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 1, 1), data14(0x40, 0) }, + { RPN(0, 1, 2), data14(0x10, 0) }); +} + +/* selecting an NRPN replaces an RPN selection, and the reverse */ +TEST_F(seq_ump, selector_switch) +{ + SEND(CC(0, 101, 0x11), CC(0, 100, 0x12), CC(0, 99, 0x21), + CC(0, 98, 0x22)); + SEND(CC(0, 6, 0x31), CC(0, 38, 0x32)); + SEND(CC(1, 99, 0x41), CC(1, 98, 0x42), CC(1, 101, 0x51), + CC(1, 100, 0x52)); + SEND(CC(1, 6, 0x61), CC(1, 38, 0x62)); + EXPECT_PACKETS({ NRPN(0, 0x21, 0x22), data14(0x31, 0x32) }, + { RPN(1, 0x51, 0x52), data14(0x61, 0x62) }); +} + +/* + * A pending Data Entry MSB is sent for the old selection when another + * one is made. Other channels keep their own selection. + */ +TEST_F(seq_ump, pending_flush_and_channels) +{ + SEND(CC(2, 101, 0x09), CC(2, 100, 0x08)); + SEND(CC(3, 99, 0x17), CC(3, 98, 0x18)); + SEND(CC(2, 6, 0x25), CC(2, 99, 0x29)); + SEND(CC(3, 6, 0x33), CC(3, 38, 0)); + EXPECT_PACKETS({ RPN(2, 0x09, 0x08), data14(0x25, 0) }, + { NRPN(3, 0x17, 0x18), data14(0x33, 0) }); +} + +/* a Null NRPN deselects, and its data is not sent */ +TEST_F(seq_ump, null_nrpn) +{ + SEND(CC(0, 99, 0x7f), CC(0, 98, 0x7f), CC(0, 6, 0x40), CC(0, 38, 0)); + EXPECT_NO_PACKETS(); +} + +/* a Null RPN also deselects an NRPN */ +TEST_F(seq_ump, null_rpn_after_nrpn) +{ + SEND(CC(0, 99, 1), CC(0, 98, 2), CC(0, 101, 0x7f), CC(0, 100, 0x7f)); + SEND(CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_NO_PACKETS(); +} + +/* a Null NRPN does not touch the RPN number */ +TEST_F(seq_ump, null_nrpn_keeps_rpn_number) +{ + SEND(CC(0, 100, 1), CC(0, 99, 0x7f), CC(0, 98, 0x7f), CC(0, 101, 0)); + SEND(CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x10, 0) }); +} + +/* a Null RPN sent around a Null NRPN still deselects */ +TEST_F(seq_ump, null_rpn_split_by_null_nrpn) +{ + SEND(CC(0, 100, 0x7f), CC(0, 99, 0x7f), CC(0, 98, 0x7f), + CC(0, 101, 0x7f)); + SEND(CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_NO_PACKETS(); +} + +/* a Null RPN after an RPN with LSB 127 does not select RPN 0/127 */ +TEST_F(seq_ump, null_rpn_after_lsb_127) +{ + SEND(CC(0, 101, 0), CC(0, 100, 0x7f), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 101, 0x7f), CC(0, 100, 0x7f), CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 0, 0x7f), data14(0x40, 0) }); +} + +/* CONTROL14: RPN 0/1 with Data Entry */ +TEST_F(seq_ump, c14_rpn) +{ + SEND(C14(0, 101, 1), C14(0, 6, 0x40 << 7)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x40, 0) }); +} + +/* CONTROL14: Data Entry before any selection is not sent */ +TEST_F(seq_ump, c14_data_before_selection) +{ + SEND(C14(0, 6, 0x40 << 7), C14(0, 101, 1), C14(0, 101, 2)); + EXPECT_NO_PACKETS(); +} + +/* CONTROL14: a repeated Data Entry goes to the selected RPN */ +TEST_F(seq_ump, c14_repeated_data) +{ + SEND(C14(0, 101, 1), C14(0, 6, 0x40 << 7), C14(0, 6, 0x10 << 7)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x40, 0) }, + { RPN(0, 0, 1), data14(0x10, 0) }); +} + +/* CONTROL14: selecting an NRPN replaces an RPN selection, and the reverse */ +TEST_F(seq_ump, c14_selector_switch) +{ + SEND(C14(4, 101, (1 << 7) | 2), C14(4, 99, (3 << 7) | 4)); + SEND(C14(4, 6, (5 << 7) | 6)); + SEND(C14(5, 99, (7 << 7) | 8), C14(5, 101, (9 << 7) | 10)); + SEND(C14(5, 6, (11 << 7) | 12)); + EXPECT_PACKETS({ NRPN(4, 3, 4), data14(5, 6) }, + { RPN(5, 9, 10), data14(11, 12) }); +} + +/* CONTROL14: a pending Data Entry is sent for the old selection */ +TEST_F(seq_ump, c14_pending_flush) +{ + SEND(C14(6, 99, (2 << 7) | 3), CC(6, 6, 0x35), + C14(6, 101, (4 << 7) | 5)); + EXPECT_PACKETS({ NRPN(6, 2, 3), data14(0x35, 0) }); +} + +/* CONTROL14: a Null RPN deselects an NRPN, and so does a Null NRPN */ +TEST_F(seq_ump, c14_null) +{ + SEND(C14(0, 99, (3 << 7) | 4), C14(0, 101, 0x3fff)); + SEND(C14(0, 6, 0x10 << 7)); + SEND(C14(1, 101, 1), C14(1, 99, 0x3fff), C14(1, 6, 0x10 << 7)); + EXPECT_NO_PACKETS(); +} + +/* CONTROL14: a Null NRPN does not touch the RPN number */ +TEST_F(seq_ump, c14_null_nrpn_keeps_rpn_number) +{ + SEND(CC(0, 100, 1), C14(0, 99, 0x3fff), CC(0, 101, 0)); + SEND(C14(0, 6, 0x10 << 7)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x10, 0) }); +} + +/* a UMP MIDI 1.0 client gets the CCs as they are, RPN selection included */ +TEST(midi1_passthrough) +{ + struct seq_client src = { .fd = -1 }, dst = { .fd = -1 }; + struct packet got[MAX_PACKETS]; + int skip = 0; + int err; + + err = open_client(&dst, SNDRV_SEQ_CLIENT_UMP_MIDI_1_0, + CAP_IN, &skip); + if (skip) { + close_client(&dst); + SKIP(return, "UMP sequencer client unavailable: %s", + strerror(-err)); + } + ASSERT_EQ(0, err); + ASSERT_EQ(0, open_client(&src, 0, CAP_OUT, &skip)); + + ASSERT_EQ(0, send_event(&src, &dst, SNDRV_SEQ_EVENT_CONTROLLER, + 9, 0x40, 0x22)); + ASSERT_EQ(0, send_event(&src, &dst, SNDRV_SEQ_EVENT_CONTROLLER, + 3, 101, 0x12)); + ASSERT_EQ(0, send_event(&src, &dst, SNDRV_SEQ_EVENT_CONTROLLER, + 3, 99, 0x34)); + ASSERT_EQ(3, read_packets(&dst, 2, got)); + EXPECT_EQ(0x20b94022u, got[0].w0); + EXPECT_EQ(0x20b36512u, got[1].w0); + EXPECT_EQ(0x20b36334u, got[2].w0); + + close_client(&src); + close_client(&dst); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c index 18ee72d5d4c963..b796a5613c6f9c 100644 --- a/tools/testing/selftests/alsa/test-pcmtest-driver.c +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -5,7 +5,9 @@ * * Copyright 2023 Ivan Orlov */ +#include #include +#include #include #include "kselftest_harness.h" @@ -58,6 +60,20 @@ static int read_patterns(void) return 0; } +static int set_param(const char *name, int value) +{ + char path[128]; + FILE *f; + + sprintf(path, "/sys/module/snd_pcmtest/parameters/%s", name); + f = fopen(path, "w"); + if (!f) + return -1; + fprintf(f, "%d", value); + + return fclose(f) ? -1 : 0; +} + static int get_test_results(char *debug_name) { int result; @@ -82,6 +98,25 @@ static size_t get_sec_buf_len(unsigned int rate, unsigned long channels, snd_pcm return rate * channels * snd_pcm_format_physical_width(format) / 8; } +/* + * Check interleaved capture data against the patterns. Return the offset of the first wrong + * byte, or -1 if all bytes match. + */ +static long find_capture_mismatch(const unsigned char *it, struct pcmtest_test_params *params) +{ + size_t i, cur_ch, pos_in_ch; + + for (i = 0; i < params->sec_buf_len * params->time; i++) { + cur_ch = (i / params->sample_size) % CH_NUM; + pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size + + (i % params->sample_size); + if (it[i] != patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]) + return i; + } + + return -1; +} + static int setup_handle(snd_pcm_t **handle, snd_pcm_sw_params_t *swparams, snd_pcm_hw_params_t *hwparams, struct pcmtest_test_params *params, int card, snd_pcm_stream_t stream) @@ -122,6 +157,10 @@ FIXTURE(pcmtest) { }; FIXTURE_TEARDOWN(pcmtest) { + set_param("inject_open_err", 0); + set_param("inject_hwpars_err", 0); + set_param("inject_prepare_err", 0); + set_param("inject_trigger_err", 0); } FIXTURE_SETUP(pcmtest) { @@ -207,9 +246,7 @@ TEST_F(pcmtest, playback) { */ TEST_F(pcmtest, capture) { snd_pcm_t *handle; - unsigned char *it; snd_pcm_sframes_t read_res; - int i, cur_ch, pos_in_ch; void *samples; struct pcmtest_test_params *params = &self->params; @@ -226,13 +263,7 @@ TEST_F(pcmtest, capture) { read_res = snd_pcm_readi(handle, samples, params->rate * params->time); ASSERT_EQ(read_res, params->rate * params->time); snd_pcm_close(handle); - it = (unsigned char *)samples; - for (i = 0; i < self->params.sec_buf_len * self->params.time; i++) { - cur_ch = (i / params->sample_size) % CH_NUM; - pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size - + (i % params->sample_size); - ASSERT_EQ(it[i], patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]); - } + ASSERT_EQ(find_capture_mismatch(samples, params), -1); free(samples); } @@ -332,4 +363,122 @@ TEST_F(pcmtest, reset_ioctl) { snd_pcm_close(handle); } +/* + * Here we pause a capture stream halfway. The core does not read the hardware pointer while + * the stream is paused, so a driver that keeps running only shows up after the release, as a + * jump of the pointer. The data must also continue where it stopped, so the whole buffer still + * matches the patterns. + */ +TEST_F(pcmtest, capture_pause) { + snd_pcm_t *handle; + snd_pcm_sframes_t read_res, paused_avail, avail, frames, half; + unsigned char *samples; + struct pcmtest_test_params *params = &self->params; + + samples = calloc(params->sec_buf_len * params->time, 1); + ASSERT_NE(samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, + self->card, SND_PCM_STREAM_CAPTURE), 0); + + frames = params->rate * params->time; + half = frames / 2; + read_res = snd_pcm_readi(handle, samples, half); + ASSERT_EQ(read_res, half); + + ASSERT_EQ(snd_pcm_pause(handle, 1), 0); + ASSERT_EQ(snd_pcm_state(handle), SND_PCM_STATE_PAUSED); + paused_avail = snd_pcm_avail(handle); + ASSERT_GE(paused_avail, 0); + usleep(500000); + + /* + * At 8000 Hz the pause is 4000 frames long. Allow a tenth of a second (800 frames) for + * the release. + */ + ASSERT_EQ(snd_pcm_pause(handle, 0), 0); + avail = snd_pcm_avail(handle); + ASSERT_GE(avail, 0); + ASSERT_LT(avail - paused_avail, (snd_pcm_sframes_t)params->rate / 10); + read_res = snd_pcm_readi(handle, samples + snd_pcm_frames_to_bytes(handle, half), + frames - half); + ASSERT_EQ(read_res, frames - half); + snd_pcm_close(handle); + + ASSERT_EQ(find_capture_mismatch(samples, params), -1); + free(samples); +} + +/* + * Here we check that an error from each PCM callback reaches user space. The driver returns + * the error while the matching 'inject_*_err' parameter is set, and works again once it is + * cleared. + */ +TEST_F(pcmtest, open_err) { + snd_pcm_t *handle; + char pcm_name[32]; + + sprintf(pcm_name, "hw:%d,0,0", self->card); + + ASSERT_EQ(set_param("inject_open_err", 1), 0); + ASSERT_EQ(snd_pcm_open(&handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0), -EBUSY); + + ASSERT_EQ(set_param("inject_open_err", 0), 0); + ASSERT_EQ(snd_pcm_open(&handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0), 0); + snd_pcm_close(handle); +} + +TEST_F(pcmtest, hw_params_err) { + snd_pcm_t *handle; + char pcm_name[32]; + + snd_pcm_hw_params_alloca(&self->hwparams); + sprintf(pcm_name, "hw:%d,0,0", self->card); + ASSERT_EQ(snd_pcm_open(&handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0), 0); + ASSERT_EQ(snd_pcm_hw_params_any(handle, self->hwparams), 0); + + ASSERT_EQ(set_param("inject_hwpars_err", 1), 0); + ASSERT_EQ(snd_pcm_hw_params(handle, self->hwparams), -EBUSY); + + ASSERT_EQ(set_param("inject_hwpars_err", 0), 0); + ASSERT_EQ(snd_pcm_hw_params(handle, self->hwparams), 0); + snd_pcm_close(handle); +} + +TEST_F(pcmtest, prepare_err) { + snd_pcm_t *handle; + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, &self->params, + self->card, SND_PCM_STREAM_PLAYBACK), 0); + + ASSERT_EQ(set_param("inject_prepare_err", 1), 0); + ASSERT_EQ(snd_pcm_prepare(handle), -EINVAL); + + ASSERT_EQ(set_param("inject_prepare_err", 0), 0); + ASSERT_EQ(snd_pcm_prepare(handle), 0); + snd_pcm_close(handle); +} + +TEST_F(pcmtest, trigger_err) { + snd_pcm_t *handle; + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, &self->params, + self->card, SND_PCM_STREAM_CAPTURE), 0); + + ASSERT_EQ(set_param("inject_trigger_err", 1), 0); + ASSERT_EQ(snd_pcm_start(handle), -EINVAL); + ASSERT_EQ(snd_pcm_state(handle), SND_PCM_STATE_PREPARED); + + ASSERT_EQ(set_param("inject_trigger_err", 0), 0); + ASSERT_EQ(snd_pcm_start(handle), 0); + ASSERT_EQ(snd_pcm_state(handle), SND_PCM_STATE_RUNNING); + snd_pcm_close(handle); +} + TEST_HARNESS_MAIN