diff --git a/Documentation/devicetree/bindings/sound/adi,adau1372.yaml b/Documentation/devicetree/bindings/sound/adi,adau1372.yaml index 9a7ff50a0a22..532550de62d7 100644 --- a/Documentation/devicetree/bindings/sound/adi,adau1372.yaml +++ b/Documentation/devicetree/bindings/sound/adi,adau1372.yaml @@ -52,11 +52,11 @@ examples: #address-cells = <1>; #size-cells = <0>; audio-codec@3c { - compatible = "adi,adau1372"; - reg = <0x3c>; - #sound-dai-cells = <0>; - clock-names = "mclk"; - clocks = <&adau1372z_xtal>; + compatible = "adi,adau1372"; + reg = <0x3c>; + #sound-dai-cells = <0>; + clock-names = "mclk"; + clocks = <&adau1372z_xtal>; }; }; diff --git a/Documentation/devicetree/bindings/sound/adi,adau7118.yaml b/Documentation/devicetree/bindings/sound/adi,adau7118.yaml index 11f59c29b575..8272cdaf66a8 100644 --- a/Documentation/devicetree/bindings/sound/adi,adau7118.yaml +++ b/Documentation/devicetree/bindings/sound/adi,adau7118.yaml @@ -68,20 +68,20 @@ examples: #address-cells = <1>; #size-cells = <0>; adau7118_codec: audio-codec@14 { - compatible = "adi,adau7118"; - reg = <0x14>; - #sound-dai-cells = <0>; - iovdd-supply = <&supply>; - dvdd-supply = <&supply>; - adi,pdm-clk-map = <1 1 0 0>; - adi,decimation-ratio = <16>; + compatible = "adi,adau7118"; + reg = <0x14>; + #sound-dai-cells = <0>; + iovdd-supply = <&supply>; + dvdd-supply = <&supply>; + adi,pdm-clk-map = <1 1 0 0>; + adi,decimation-ratio = <16>; }; }; /* example with hw standalone mode */ adau7118_codec_hw: adau7118-codec-hw { - compatible = "adi,adau7118"; - #sound-dai-cells = <0>; - iovdd-supply = <&supply>; - dvdd-supply = <&supply>; + compatible = "adi,adau7118"; + #sound-dai-cells = <0>; + iovdd-supply = <&supply>; + dvdd-supply = <&supply>; }; diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml new file mode 100644 index 000000000000..80d9e87f1470 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/airoha,an7581-afe.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha AFE PCM controller for AN7581 + +maintainers: + - Christian Marangi + +properties: + compatible: + const: airoha,an7581-afe + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + + afe@1fbe2200 { + compatible = "airoha,an7581-afe"; + reg = <0x1fbe2200 0x9000>; + + interrupts = ; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml new file mode 100644 index 000000000000..8886fbb6b43c --- /dev/null +++ b/Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/airoha,an7581-wm8960.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha AN7581 sound card with WM8960 codec + +maintainers: + - Christian Marangi + +allOf: + - $ref: sound-card-common.yaml# + +properties: + compatible: + const: airoha,an7581-wm8960-sound + + platform: + type: object + + additionalProperties: false + + properties: + sound-dai: + items: + - description: The phandle of AN7581 platform. + + required: + - sound-dai + + codec: + type: object + + additionalProperties: false + + properties: + sound-dai: + items: + - description: The phandle of WM8960 i2c codec. + + required: + - sound-dai + +required: + - compatible + - audio-routing + - platform + - codec + +unevaluatedProperties: false + +examples: + - | + sound { + compatible = "airoha,an7581-wm8960-sound"; + model = "an7581-wm8960"; + audio-routing = + "Headphone", "HP_L", + "Headphone", "HP_R", + "LINPUT1", "AMIC", + "RINPUT1", "AMIC"; + + platform { + sound-dai = <&afe>; + }; + + codec { + sound-dai = <&wm8960>; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml b/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml index 6fdf605ad59c..2be3b34e4f5f 100644 --- a/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml +++ b/Documentation/devicetree/bindings/sound/amlogic,gx-sound-card.yaml @@ -94,20 +94,20 @@ examples: audio-routing = "I2S ENCODER I2S IN", "I2S FIFO Playback"; dai-link-0 { - sound-dai = <&i2s_fifo>; + sound-dai = <&i2s_fifo>; }; dai-link-1 { - sound-dai = <&i2s_encoder>; - dai-format = "i2s"; - mclk-fs = <256>; + sound-dai = <&i2s_encoder>; + dai-format = "i2s"; + mclk-fs = <256>; - codec-0 { - sound-dai = <&codec0>; - }; + codec-0 { + sound-dai = <&codec0>; + }; - codec-1 { - sound-dai = <&codec1>; - }; + codec-1 { + sound-dai = <&codec1>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml index d412531ef9a2..7a7534493e6b 100644 --- a/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml +++ b/Documentation/devicetree/bindings/sound/asahi-kasei,ak4619.yaml @@ -34,6 +34,12 @@ properties: $ref: audio-graph-port.yaml# unevaluatedProperties: false + powerdown-gpios: + maxItems: 1 + description: + GPIO connected to the 'PDN' pin, if any. Needs to be set low to + power down the codec, hence the GPIO must be GPIO_ACTIVE_LOW. + required: - compatible - reg @@ -42,6 +48,8 @@ unevaluatedProperties: false examples: - | + #include + i2c { #address-cells = <1>; #size-cells = <0>; @@ -49,14 +57,16 @@ examples: compatible = "asahi-kasei,ak4619"; reg = <0x10>; + powerdown-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>; + clocks = <&rcar_sound>; clock-names = "mclk"; #sound-dai-cells = <0>; port { - ak4619_endpoint: endpoint { - remote-endpoint = <&rsnd_endpoint>; - }; + ak4619_endpoint: endpoint { + remote-endpoint = <&rsnd_endpoint>; + }; }; }; }; diff --git a/Documentation/devicetree/bindings/sound/audio-graph-port.yaml b/Documentation/devicetree/bindings/sound/audio-graph-port.yaml index d1cbfc5edd3a..565d723d0398 100644 --- a/Documentation/devicetree/bindings/sound/audio-graph-port.yaml +++ b/Documentation/devicetree/bindings/sound/audio-graph-port.yaml @@ -39,6 +39,7 @@ definitions: allOf: - $ref: /schemas/graph.yaml#/$defs/endpoint-base - $ref: /schemas/sound/dai-params.yaml# + - $ref: /schemas/sound/tdm-slot.yaml# properties: mclk-fs: $ref: simple-card.yaml#/definitions/mclk-fs @@ -48,6 +49,9 @@ definitions: bitclock-inversion: description: dai-link uses bit clock inversion $ref: /schemas/types.yaml#/definitions/flag + continuous-clock: + description: dai-link bit clock runs continuously rather than gated + $ref: /schemas/types.yaml#/definitions/flag frame-master: description: Indicates dai-link frame master. oneOf: @@ -82,12 +86,6 @@ definitions: - msb - lsb - dai-tdm-slot-num: - description: Number of slots in use. - $ref: /schemas/types.yaml#/definitions/uint32 - dai-tdm-slot-width: - description: Width in bits for each slot. - $ref: /schemas/types.yaml#/definitions/uint32 dai-tdm-slot-width-map: description: Mapping of sample widths to slot widths. For hardware that cannot support a fixed slot width or a slot width always diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml index 70f6c62aedca..e674811b22b2 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml @@ -53,6 +53,76 @@ properties: maximum: 3 default: 2 + cirrus,sync-lsw-txid: + description: + Transmitter ID to use for long software (LSW) data packets + on the SYNC bus. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 7 + default: 0 + + cirrus,sync-sw-txid: + description: + Transmitter ID to use for short software (SW) data packets + on the SYNC bus. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 7 + default: 0 + + cirrus,ldpm-group1-boost-select: + type: boolean + description: + When in a valid group 1 level dependent power management (LDPM) state + the integrated boost converter will enter the configured low power mode. + + cirrus,boost-low-power-mode: + description: + Configures the integrated boost converter reaction to a low power mode + request. + 0 = No reaction + 1 = Force boost DCM low power mode + 2 = Force boost static bypass + $ref: /schemas/types.yaml#/definitions/uint32 + default: 2 + enum: + - 0 + - 1 + - 2 + + cirrus,ldpm-group1-amp-select: + type: boolean + description: + When in a valid group 1 level dependent power management (LDPM) state + the amplifier will enter the configured low power mode. + + cirrus,amplifier-low-power-mode: + description: + Configures the amplifier output stage reaction to a low power mode + request. + 0 = Stop output switching (noise gate mode) + 1 = Noise floor reduction mode + $ref: /schemas/types.yaml#/definitions/uint32 + default: 1 + enum: + - 0 + - 1 + + cirrus,ldpm-group2-vmon-select: + type: boolean + description: + When in a valid group 2 level dependent power management (LDPM) state + the voltage monitor (VMON) circuit is put into a low power operational + state. + + cirrus,ldpm-group2-imon-select: + type: boolean + description: + When in a valid group 2 level dependent power management (LDPM) state + the current monitor (IMON) circuit is put into a low power operational + state. + patternProperties: "^cirrus,gpio-ctrl[1-3]$": description: @@ -121,6 +191,30 @@ patternProperties: minimum: 0 maximum: 7 default: 0 + + "^cirrus,ldpm-group[1-2]-pcm-threshold-db$": + description: + Amplifier level dependent power management (LDPM) threshold. Input + referred threshold of the PCM audio signal in which the level is low + enough for the LDPM group to engage after entry delay. A value of dB + -2147483648 is assumed to mean true zero data with an LSB dither. + $ref: /schemas/types.yaml#/definitions/int32-array + minItems: 1 + maxItems: 1 + items: + enum: [-2147483648, -102, -96, -90, -84, -78, -72, -66] + + "^cirrus,ldpm-group[1-2]-delay-ms$": + description: + Amplifier level dependent power management (LDPM) state entry delay. + Time that the incoming audio signal must be below the PCM threshold + before the LDPM group engages. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 1 + items: + enum: [5, 10, 25, 50, 100, 250, 500, 1000] + required: - compatible - reg @@ -135,25 +229,37 @@ examples: #address-cells = <1>; #size-cells = <0>; - cs35l45: cs35l45@2 { - #sound-dai-cells = <1>; - compatible = "cirrus,cs35l45"; - reg = <2>; - spi-max-frequency = <5000000>; - vdd-a-supply = <&dummy_vreg>; - vdd-batt-supply = <&dummy_vreg>; - reset-gpios = <&gpio 110 0>; - cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED | - CS35L45_ASP_TX_HIZ_DISABLED)>; - cirrus,gpio-ctrl1 { - gpio-ctrl = <0x2>; - }; - cirrus,gpio-ctrl2 { - gpio-ctrl = <0x2>; - }; - cirrus,gpio-ctrl3 { - gpio-ctrl = <0x1>; - gpio-dir = <0x1>; - }; + cs35l45: audio-codec@2 { + #sound-dai-cells = <1>; + compatible = "cirrus,cs35l45"; + reg = <2>; + spi-max-frequency = <5000000>; + vdd-a-supply = <&dummy_vreg>; + vdd-batt-supply = <&dummy_vreg>; + reset-gpios = <&gpio 110 0>; + cirrus,asp-sdout-hiz-ctrl = <(CS35L45_ASP_TX_HIZ_UNUSED | + CS35L45_ASP_TX_HIZ_DISABLED)>; + cirrus,sync-lsw-txid = <0x1>; + cirrus,sync-sw-txid = <0x1>; + cirrus,ldpm-group1-pcm-threshold-db = <(-66)>; + cirrus,ldpm-group1-delay-ms = <500>; + cirrus,ldpm-group1-boost-select; + cirrus,boost-low-power-mode = ; + cirrus,ldpm-group1-amp-select; + cirrus,amplifier-low-power-mode = ; + cirrus,ldpm-group2-pcm-threshold-db = <(-102)>; + cirrus,ldpm-group2-delay-ms = <1000>; + cirrus,ldpm-group2-vmon-select; + cirrus,ldpm-group2-imon-select; + cirrus,gpio-ctrl1 { + gpio-ctrl = <0x2>; + }; + cirrus,gpio-ctrl2 { + gpio-ctrl = <0x2>; + }; + cirrus,gpio-ctrl3 { + gpio-ctrl = <0x1>; + gpio-dir = <0x1>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml index 336e11773694..61106294a1d5 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs4270.yaml @@ -49,11 +49,11 @@ unevaluatedProperties: false examples: - | i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - codec@48 { - compatible = "cirrus,cs4270"; - reg = <0x48>; - }; + codec@48 { + compatible = "cirrus,cs4270"; + reg = <0x48>; + }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml index af599d8735e2..ab4c7d22dc0e 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42l42.yaml @@ -199,28 +199,28 @@ examples: - | #include i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - cs42l42: cs42l42@48 { - compatible = "cirrus,cs42l42"; - reg = <0x48>; - VA-supply = <&dummy_vreg>; - VP-supply = <&dummy_vreg>; - VCP-supply = <&dummy_vreg>; - VD_FILT-supply = <&dummy_vreg>; - VL-supply = <&dummy_vreg>; + audio-codec@48 { + compatible = "cirrus,cs42l42"; + reg = <0x48>; + VA-supply = <&dummy_vreg>; + VP-supply = <&dummy_vreg>; + VCP-supply = <&dummy_vreg>; + VD_FILT-supply = <&dummy_vreg>; + VL-supply = <&dummy_vreg>; - reset-gpios = <&axi_gpio_0 1 0>; - interrupt-parent = <&gpio0>; - interrupts = <55 8>; + reset-gpios = <&axi_gpio_0 1 0>; + interrupt-parent = <&gpio0>; + interrupts = <55 8>; - cirrus,ts-inv = ; - cirrus,ts-dbnc-rise = ; - cirrus,ts-dbnc-fall = ; - cirrus,btn-det-init-dbnce = <100>; - cirrus,btn-det-event-dbnce = <10>; - cirrus,bias-lvls = <0x0F 0x08 0x04 0x01>; - cirrus,hs-bias-ramp-rate = ; - }; + cirrus,ts-inv = ; + cirrus,ts-dbnc-rise = ; + cirrus,ts-dbnc-fall = ; + cirrus,btn-det-init-dbnce = <100>; + cirrus,btn-det-event-dbnce = <10>; + cirrus,bias-lvls = <0x0F 0x08 0x04 0x01>; + cirrus,hs-bias-ramp-rate = ; + }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml index 7f8338e8ae36..94062c1d9d45 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42l84.yaml @@ -43,14 +43,14 @@ examples: #include #include i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - jack_codec: codec@4b { - compatible = "cirrus,cs42l84"; - reg = <0x4b>; - reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_LOW>; - interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>; - #sound-dai-cells = <0>; - }; + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_LOW>; + interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>; + #sound-dai-cells = <0>; + }; }; diff --git a/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml b/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml index a1ae548c4b7b..b5cdc7e537f8 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs42xx8.yaml @@ -75,37 +75,37 @@ unevaluatedProperties: false examples: - | i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - codec@48 { - compatible = "cirrus,cs42888"; - reg = <0x48>; - clocks = <&codec_mclk 0>; - clock-names = "mclk"; - VA-supply = <®_audio>; - VD-supply = <®_audio>; - VLS-supply = <®_audio>; - VLC-supply = <®_audio>; - reset-gpios = <&gpio 1>; - }; + codec@48 { + compatible = "cirrus,cs42888"; + reg = <0x48>; + clocks = <&codec_mclk 0>; + clock-names = "mclk"; + VA-supply = <®_audio>; + VD-supply = <®_audio>; + VLS-supply = <®_audio>; + VLC-supply = <®_audio>; + reset-gpios = <&gpio 1>; + }; }; spi { - #address-cells = <1>; - #size-cells = <0>; - cs-gpios = <&gpio 8 0>; + #address-cells = <1>; + #size-cells = <0>; + cs-gpios = <&gpio 8 0>; - codec@0 { - compatible = "cirrus,cs42888"; - reg = <0>; - spi-max-frequency = <6000000>; - clocks = <&codec_mclk 0>; - clock-names = "mclk"; - VA-supply = <®_audio>; - VD-supply = <®_audio>; - VLS-supply = <®_audio>; - VLC-supply = <®_audio>; - reset-gpios = <&gpio 1>; - }; + codec@0 { + compatible = "cirrus,cs42888"; + reg = <0>; + spi-max-frequency = <6000000>; + clocks = <&codec_mclk 0>; + clock-names = "mclk"; + VA-supply = <®_audio>; + VD-supply = <®_audio>; + VLS-supply = <®_audio>; + VLC-supply = <®_audio>; + reset-gpios = <&gpio 1>; + }; }; diff --git a/Documentation/devicetree/bindings/sound/davinci-evm-audio.txt b/Documentation/devicetree/bindings/sound/davinci-evm-audio.txt deleted file mode 100644 index 963e100514c2..000000000000 --- a/Documentation/devicetree/bindings/sound/davinci-evm-audio.txt +++ /dev/null @@ -1,49 +0,0 @@ -* Texas Instruments SoC audio setups with TLV320AIC3X Codec - -Required properties: -- compatible : "ti,da830-evm-audio" : forDM365/DA8xx/OMAPL1x/AM33xx -- ti,model : The user-visible name of this sound complex. -- ti,audio-codec : The phandle of the TLV320AIC3x audio codec -- ti,mcasp-controller : The phandle of the McASP controller -- ti,audio-routing : A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the codec's pins, and the jacks on the board: - -Optional properties: -- ti,codec-clock-rate : The Codec Clock rate (in Hz) applied to the Codec. -- clocks : Reference to the master clock -- clock-names : The clock should be named "mclk" -- Either codec-clock-rate or the codec-clock reference has to be defined. If - the both are defined the driver attempts to set referenced clock to the - defined rate and takes the rate from the clock reference. - - Board connectors: - - * Headphone Jack - * Line Out - * Mic Jack - * Line In - - -Example: - -sound { - compatible = "ti,da830-evm-audio"; - ti,model = "DA830 EVM"; - ti,audio-codec = <&tlv320aic3x>; - ti,mcasp-controller = <&mcasp1>; - ti,codec-clock-rate = <12000000>; - ti,audio-routing = - "Headphone Jack", "HPLOUT", - "Headphone Jack", "HPROUT", - "Line Out", "LLOUT", - "Line Out", "RLOUT", - "MIC3L", "Mic Bias 2V", - "MIC3R", "Mic Bias 2V", - "Mic Bias 2V", "Mic Jack", - "LINE1L", "Line In", - "LINE2L", "Line In", - "LINE1R", "Line In", - "LINE2R", "Line In"; -}; diff --git a/Documentation/devicetree/bindings/sound/dialog,da7219.yaml b/Documentation/devicetree/bindings/sound/dialog,da7219.yaml index 19137abdba3e..852f27e9dffe 100644 --- a/Documentation/devicetree/bindings/sound/dialog,da7219.yaml +++ b/Documentation/devicetree/bindings/sound/dialog,da7219.yaml @@ -195,44 +195,44 @@ examples: - | #include i2c { - #address-cells = <1>; - #size-cells = <0>; + #address-cells = <1>; + #size-cells = <0>; - codec: da7219@1a { - compatible = "dlg,da7219"; - reg = <0x1a>; + codec: audio-codec@1a { + compatible = "dlg,da7219"; + reg = <0x1a>; - interrupt-parent = <&gpio6>; - interrupts = <11 IRQ_TYPE_LEVEL_LOW>; + interrupt-parent = <&gpio6>; + interrupts = <11 IRQ_TYPE_LEVEL_LOW>; - VDD-supply = <&vdd_reg>; - VDDMIC-supply = <&vddmic_reg>; - VDDIO-supply = <&vddio_reg>; + VDD-supply = <&vdd_reg>; + VDDMIC-supply = <&vddmic_reg>; + VDDIO-supply = <&vddio_reg>; - #clock-cells = <1>; - clock-output-names = "da7219-dai-wclk", "da7219-dai-bclk"; + #clock-cells = <1>; + clock-output-names = "da7219-dai-wclk", "da7219-dai-bclk"; - clocks = <&clks 201>; - clock-names = "mclk"; + clocks = <&clks 201>; + clock-names = "mclk"; - dlg,micbias-lvl = <2600>; - dlg,mic-amp-in-sel = "diff"; + dlg,micbias-lvl = <2600>; + dlg,mic-amp-in-sel = "diff"; - da7219_aad { - dlg,btn-cfg = <50>; - dlg,mic-det-thr = <500>; - dlg,jack-ins-deb = <20>; - dlg,jack-ins-det-pty = "low"; - dlg,jack-det-rate = "32_64"; - dlg,jack-rem-deb = <1>; + da7219_aad { + dlg,btn-cfg = <50>; + dlg,mic-det-thr = <500>; + dlg,jack-ins-deb = <20>; + dlg,jack-ins-det-pty = "low"; + dlg,jack-det-rate = "32_64"; + dlg,jack-rem-deb = <1>; - dlg,a-d-btn-thr = <0xa>; - dlg,d-b-btn-thr = <0x16>; - dlg,b-c-btn-thr = <0x21>; - dlg,c-mic-btn-thr = <0x3E>; + dlg,a-d-btn-thr = <0xa>; + dlg,d-b-btn-thr = <0x16>; + dlg,b-c-btn-thr = <0x21>; + dlg,c-mic-btn-thr = <0x3E>; - dlg,btn-avg = <4>; - dlg,adc-1bit-rpt = <1>; - }; - }; + dlg,btn-avg = <4>; + dlg,adc-1bit-rpt = <1>; + }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml b/Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml new file mode 100644 index 000000000000..15db30af4b6c --- /dev/null +++ b/Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/esstech,es9039q2m.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ESS Technology ES9039Q2M audio DAC + +maintainers: + - Karl Asseily + +description: + The ES9039Q2M is a 32-bit two-channel audio DAC supporting PCM to 768 kHz, + DSD64 to DSD1024, DoP and S/PDIF. It is controlled over I2C or SPI when the + MODE pin selects software mode; this binding covers the I2C interface. The + part contains an asynchronous sample rate converter, so the system clock need + not be synchronous with the audio bit or frame clocks. + + The four supplies are all nominally 3.3V and the datasheet requires a set + order - AVDD first, then VCCA about 200us later, then AVCC_DAC1 and + AVCC_DAC2, and the reverse on the way down. DVDD, the part's 1.2V rail, is + on-chip and needs only its decoupling capacitor - it is not a supply input + and has no property here. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: esstech,es9039q2m + + reg: + description: + I2C address, selected by the ADDR0 and ADDR1 pins - 0x48, 0x49, 0x4a or + 0x4b for GND/GND, GND/AVDD, AVDD/GND and AVDD/AVDD respectively. + enum: [ 0x48, 0x49, 0x4a, 0x4b ] + + clocks: + maxItems: 1 + + clock-names: + const: mclk + + avdd-supply: + description: + 3.3V supply for the part's internal digital regulator, pin AVDD. It is + the first rail up and the last down, per the ordering described above. + + vcca-supply: + description: 3.3V analogue supply, pin VCCA. + + avcc-dac1-supply: + description: + 3.3V reference supply for the channel 1 analogue output stage, pin + AVCC_DAC1. The datasheet allows this rail to move by up to 15% where a + calibrating regulator tracks the DAC's output impedance. + + avcc-dac2-supply: + description: + 3.3V reference supply for the channel 2 analogue output stage, pin + AVCC_DAC2, with the same 15% allowance as channel 1. + + "#sound-dai-cells": + const: 0 + +required: + - compatible + - reg + - avdd-supply + - vcca-supply + - avcc-dac1-supply + - avcc-dac2-supply + - "#sound-dai-cells" + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + audio-codec@48 { + compatible = "esstech,es9039q2m"; + reg = <0x48>; + #sound-dai-cells = <0>; + clocks = <&dac_mclk>; + clock-names = "mclk"; + avdd-supply = <&dac_3v3>; + vcca-supply = <&dac_3v3>; + avcc-dac1-supply = <&dac_avcc1>; + avcc-dac2-supply = <&dac_avcc2>; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/everest,es8316.yaml b/Documentation/devicetree/bindings/sound/everest,es8316.yaml index f4ff23120c5b..9060358a9080 100644 --- a/Documentation/devicetree/bindings/sound/everest,es8316.yaml +++ b/Documentation/devicetree/bindings/sound/everest,es8316.yaml @@ -84,6 +84,11 @@ properties: "#sound-dai-cells": const: 0 + everest,jack-detect-inverted: + $ref: /schemas/types.yaml#/definitions/flag + description: + Defined to invert the jack detection. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml b/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml index 4da735317e0f..17c938a565e5 100644 --- a/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml +++ b/Documentation/devicetree/bindings/sound/foursemi,fs2105s.yaml @@ -63,7 +63,7 @@ properties: a. Register initialization settings b. DSP effect parameters c. Multi-scene sound effect configurations(optional) - It's gernerated by FourSemi's tuning tool. + It's generated by FourSemi's tuning tool. required: - compatible diff --git a/Documentation/devicetree/bindings/sound/fsl,easrc.yaml b/Documentation/devicetree/bindings/sound/fsl,easrc.yaml index d5727f8bfb0b..a53ec4f6f1c0 100644 --- a/Documentation/devicetree/bindings/sound/fsl,easrc.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,easrc.yaml @@ -55,6 +55,14 @@ properties: - const: imx/easrc/easrc-imx8mn.bin description: The coefficient table for the filters + ports: + $ref: /schemas/graph.yaml#/properties/ports + patternProperties: + "^port@[0-3]$": + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: port for an independent conversion path + fsl,asrc-rate: $ref: /schemas/types.yaml#/definitions/uint32 minimum: 8000 @@ -106,4 +114,41 @@ examples: firmware-name = "imx/easrc/easrc-imx8mn.bin"; fsl,asrc-rate = <8000>; fsl,asrc-format = <2>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + playback-only; + endpoint { + remote-endpoint = <&fe00_ep>; + }; + }; + + port@1 { + reg = <1>; + playback-only; + endpoint { + remote-endpoint = <&fe01_ep>; + }; + }; + + port@2 { + reg = <2>; + capture-only; + endpoint { + remote-endpoint = <&fe02_ep>; + }; + }; + + port@3 { + reg = <3>; + capture-only; + endpoint { + remote-endpoint = <&fe03_ep>; + }; + }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml index 608defc93c1e..67d2826eef3f 100644 --- a/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,imx-asrc.yaml @@ -78,9 +78,19 @@ properties: power-domains: maxItems: 1 + ports: + $ref: /schemas/graph.yaml#/properties/ports + patternProperties: + "^port@[0-2]$": + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + description: port for an independent conversion path + port: $ref: audio-graph-port.yaml# unevaluatedProperties: false + deprecated: true + description: deprecated, use ports instead fsl,asrc-rate: $ref: /schemas/types.yaml#/definitions/uint32 @@ -125,6 +135,10 @@ required: - fsl,asrc-width allOf: + - not: + required: + - port + - ports - $ref: dai-common.yaml# - if: properties: @@ -180,11 +194,32 @@ examples: fsl,asrc-rate = <48000>; fsl,asrc-width = <16>; - port { - playback-only; + ports { + #address-cells = <1>; + #size-cells = <0>; - asrc_endpoint: endpoint { - remote-endpoint = <&fe00_ep>; + port@0 { + reg = <0>; + playback-only; + endpoint { + remote-endpoint = <&fe00_ep>; + }; }; - }; + + port@1 { + reg = <1>; + capture-only; + endpoint { + remote-endpoint = <&fe01_ep>; + }; + }; + + port@2 { + reg = <2>; + capture-only; + endpoint { + remote-endpoint = <&fe02_ep>; + }; + }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/fsl,sai.yaml b/Documentation/devicetree/bindings/sound/fsl,sai.yaml index ba65b3f3d066..eb0b9d8d907f 100644 --- a/Documentation/devicetree/bindings/sound/fsl,sai.yaml +++ b/Documentation/devicetree/bindings/sound/fsl,sai.yaml @@ -260,13 +260,13 @@ examples: playback-only; sai1_endpoint0: endpoint { - dai-tdm-slot-num = <8>; - dai-tdm-slot-width = <32>; - dai-tdm-slot-width-map = <32 8 32>; - dai-format = "dsp_a"; - bitclock-master; - frame-master; - remote-endpoint = <&mcodec01_ep>; + dai-tdm-slot-num = <8>; + dai-tdm-slot-width = <32>; + dai-tdm-slot-width-map = <32 8 32>; + dai-format = "dsp_a"; + bitclock-master; + frame-master; + remote-endpoint = <&mcodec01_ep>; }; }; diff --git a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml index 5171f984630b..b12698264724 100644 --- a/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml +++ b/Documentation/devicetree/bindings/sound/hisilicon,hi6210-i2s.yaml @@ -48,6 +48,10 @@ properties: The dai cell indexes reference the following interfaces: 0: S2 interface + ports: + $ref: audio-graph-port.yaml#/definitions/ports + unevaluatedProperties: false + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml index 950e3eab2942..27636466ba63 100644 --- a/Documentation/devicetree/bindings/sound/imx-audio-card.yaml +++ b/Documentation/devicetree/bindings/sound/imx-audio-card.yaml @@ -105,10 +105,10 @@ examples: format = "i2s"; fsl,mclk-equal-bclk; cpu { - sound-dai = <&sai1>; + sound-dai = <&sai1>; }; codec { - sound-dai = <&ak4458_1>, <&ak4458_2>; + sound-dai = <&ak4458_1>, <&ak4458_2>; }; }; fe-dai-link { diff --git a/Documentation/devicetree/bindings/sound/imx-audmux.yaml b/Documentation/devicetree/bindings/sound/imx-audmux.yaml index dab45c310670..501af1add3c1 100644 --- a/Documentation/devicetree/bindings/sound/imx-audmux.yaml +++ b/Documentation/devicetree/bindings/sound/imx-audmux.yaml @@ -101,10 +101,10 @@ examples: mux-ssi1 { fsl,audmux-port = <0>; fsl,port-config = < - IMX_AUDMUX_V2_PTCR_SYN 0 - IMX_AUDMUX_V2_PTCR_TFSEL(2) 0 - IMX_AUDMUX_V2_PTCR_TCSEL(2) 0 - IMX_AUDMUX_V2_PTCR_TFSDIR 0 + IMX_AUDMUX_V2_PTCR_SYN 0 + IMX_AUDMUX_V2_PTCR_TFSEL(2) 0 + IMX_AUDMUX_V2_PTCR_TCSEL(2) 0 + IMX_AUDMUX_V2_PTCR_TFSDIR 0 IMX_AUDMUX_V2_PTCR_TCLKDIR IMX_AUDMUX_V2_PDCR_RXDSEL(2) >; }; diff --git a/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml b/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml index 7bd984817aa9..054f80390b8d 100644 --- a/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml +++ b/Documentation/devicetree/bindings/sound/invensense,ics43432.yaml @@ -42,10 +42,10 @@ examples: compatible = "invensense,ics43432"; port { - endpoint { - remote-endpoint = <&i2s1_endpoint>; - dai-format = "i2s"; - }; + endpoint { + remote-endpoint = <&i2s1_endpoint>; + dai-format = "i2s"; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml index dc7f4afbb777..69ea611d4250 100644 --- a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml +++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml @@ -79,7 +79,7 @@ examples: sound-dai = <&i2s>; }; codec { - sound-dai = <&es8323>; + sound-dai = <&es8323>; }; }; @@ -104,6 +104,6 @@ examples: }; codec { - sound-dai = <&es8388>; + sound-dai = <&es8388>; }; }; diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml index 30f331366566..10df1e7c7eb9 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt2701-audio.yaml @@ -32,7 +32,7 @@ properties: maxItems: 1 clocks: - minItems: 34 + minItems: 33 items: - description: audio infra sys clock - description: top audio mux 1 @@ -74,7 +74,7 @@ properties: - description: audio APLL root pd clock-names: - minItems: 34 + minItems: 33 items: - const: infra_sys_audio_clk - const: top_audio_mux1_sel @@ -132,8 +132,10 @@ allOf: then: properties: clocks: - maxItems: 34 + minItems: 33 + maxItems: 33 clock-names: - maxItems: 34 + minItems: 33 + maxItems: 33 additionalProperties: false diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml index d8993b5d457a..4c66160e9e1b 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt8173-afe-pcm.yaml @@ -84,15 +84,15 @@ examples: <&topckgen CLK_TOP_I2S2_M_SEL>, <&topckgen CLK_TOP_I2S3_M_SEL>, <&topckgen CLK_TOP_I2S3_B_SEL>; - clock-names = "infra_sys_audio_clk", - "top_pdn_audio", - "top_pdn_aud_intbus", - "bck0", - "bck1", - "i2s0_m", - "i2s1_m", - "i2s2_m", - "i2s3_m", - "i2s3_b"; - memory-region = <&afe_dma_mem>; + clock-names = "infra_sys_audio_clk", + "top_pdn_audio", + "top_pdn_aud_intbus", + "bck0", + "bck1", + "i2s0_m", + "i2s1_m", + "i2s2_m", + "i2s3_m", + "i2s3_b"; + memory-region = <&afe_dma_mem>; }; diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml index 031b0fa7b4dc..261247ca2c87 100644 --- a/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml +++ b/Documentation/devicetree/bindings/sound/mediatek,mt8183-audio.yaml @@ -182,47 +182,47 @@ examples: <&topckgen CLK_TOP_APLL12_DIV4>, <&topckgen CLK_TOP_APLL12_DIVB>, <&clk26m>; - clock-names = "aud_afe_clk", - "aud_dac_clk", - "aud_dac_predis_clk", - "aud_adc_clk", - "aud_adc_adda6_clk", - "aud_apll22m_clk", - "aud_apll24m_clk", - "aud_apll1_tuner_clk", - "aud_apll2_tuner_clk", - "aud_i2s1_bclk_sw", - "aud_i2s2_bclk_sw", - "aud_i2s3_bclk_sw", - "aud_i2s4_bclk_sw", - "aud_tdm_clk", - "aud_tml_clk", - "aud_infra_clk", - "mtkaif_26m_clk", - "top_mux_audio", - "top_mux_aud_intbus", - "top_syspll_d2_d4", - "top_mux_aud_1", - "top_apll1_ck", - "top_mux_aud_2", - "top_apll2_ck", - "top_mux_aud_eng1", - "top_apll1_d8", - "top_mux_aud_eng2", - "top_apll2_d8", - "top_i2s0_m_sel", - "top_i2s1_m_sel", - "top_i2s2_m_sel", - "top_i2s3_m_sel", - "top_i2s4_m_sel", - "top_i2s5_m_sel", - "top_apll12_div0", - "top_apll12_div1", - "top_apll12_div2", - "top_apll12_div3", - "top_apll12_div4", - "top_apll12_divb", - "top_clk26m_clk"; + clock-names = "aud_afe_clk", + "aud_dac_clk", + "aud_dac_predis_clk", + "aud_adc_clk", + "aud_adc_adda6_clk", + "aud_apll22m_clk", + "aud_apll24m_clk", + "aud_apll1_tuner_clk", + "aud_apll2_tuner_clk", + "aud_i2s1_bclk_sw", + "aud_i2s2_bclk_sw", + "aud_i2s3_bclk_sw", + "aud_i2s4_bclk_sw", + "aud_tdm_clk", + "aud_tml_clk", + "aud_infra_clk", + "mtkaif_26m_clk", + "top_mux_audio", + "top_mux_aud_intbus", + "top_syspll_d2_d4", + "top_mux_aud_1", + "top_apll1_ck", + "top_mux_aud_2", + "top_apll2_ck", + "top_mux_aud_eng1", + "top_apll1_d8", + "top_mux_aud_eng2", + "top_apll2_d8", + "top_i2s0_m_sel", + "top_i2s1_m_sel", + "top_i2s2_m_sel", + "top_i2s3_m_sel", + "top_i2s4_m_sel", + "top_i2s5_m_sel", + "top_apll12_div0", + "top_apll12_div1", + "top_apll12_div2", + "top_apll12_div3", + "top_apll12_div4", + "top_apll12_divb", + "top_clk26m_clk"; }; ... diff --git a/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml b/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml index 037f21443ad1..d5b358838e2a 100644 --- a/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml +++ b/Documentation/devicetree/bindings/sound/mt8186-mt6366-da7219-max98357.yaml @@ -167,30 +167,30 @@ examples: "HDMI1", "TX"; hs-playback-dai-link { - link-name = "I2S0"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&da7219>; - }; + link-name = "I2S0"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&da7219>; + }; }; hs-capture-dai-link { - link-name = "I2S1"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&da7219>; - }; + link-name = "I2S1"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&da7219>; + }; }; spk-dp-playback-dai-link { - link-name = "I2S3"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&anx_bridge_dp>, <&max98357a>; - }; + link-name = "I2S3"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&anx_bridge_dp>, <&max98357a>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml b/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml index ed93f18ef985..7f553575c73d 100644 --- a/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml +++ b/Documentation/devicetree/bindings/sound/mt8186-mt6366-rt1019-rt5682s.yaml @@ -171,30 +171,30 @@ examples: "HDMI1", "TX"; hs-playback-dai-link { - link-name = "I2S0"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682s 0>; - }; + link-name = "I2S0"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682s 0>; + }; }; hs-capture-dai-link { - link-name = "I2S1"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682s 0>; - }; + link-name = "I2S1"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682s 0>; + }; }; spk-hdmi-playback-dai-link { - link-name = "I2S3"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&it6505dptx>, <&rt1019p>; - }; + link-name = "I2S3"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&it6505dptx>, <&rt1019p>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml b/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml index c4e68f31aaab..841aab0178e3 100644 --- a/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml +++ b/Documentation/devicetree/bindings/sound/mt8192-mt6359-rt1015-rt5682.yaml @@ -165,38 +165,38 @@ examples: "Speakers", "Speaker"; spk-playback-dai-link { - link-name = "I2S3"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt1015p>; - }; + link-name = "I2S3"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt1015p>; + }; }; hs-playback-dai-link { - link-name = "I2S8"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682 0>; - }; + link-name = "I2S8"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682 0>; + }; }; hs-capture-dai-link { - link-name = "I2S9"; - dai-format = "i2s"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&rt5682 0>; - }; + link-name = "I2S9"; + dai-format = "i2s"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&rt5682 0>; + }; }; displayport-dai-link { - link-name = "TDM"; - dai-format = "dsp_a"; - codec { - sound-dai = <&anx_bridge_dp>; - }; + link-name = "TDM"; + dai-format = "dsp_a"; + codec { + sound-dai = <&anx_bridge_dp>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml b/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml index 356e1feee962..c6bd286780c9 100644 --- a/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml +++ b/Documentation/devicetree/bindings/sound/mt8195-mt6359.yaml @@ -156,46 +156,46 @@ examples: "Ext Spk", "Speaker"; mm-dai-link { - link-name = "ETDM1_IN_BE"; - mediatek,clk-provider = "cpu"; + link-name = "ETDM1_IN_BE"; + mediatek,clk-provider = "cpu"; }; hs-playback-dai-link { - link-name = "ETDM1_OUT_BE"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&headset_codec>; - }; + link-name = "ETDM1_OUT_BE"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&headset_codec>; + }; }; hs-capture-dai-link { - link-name = "ETDM2_IN_BE"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&headset_codec>; - }; + link-name = "ETDM2_IN_BE"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&headset_codec>; + }; }; spk-playback-dai-link { - link-name = "ETDM2_OUT_BE"; - mediatek,clk-provider = "cpu"; - codec { - sound-dai = <&spk_amplifier>; - }; + link-name = "ETDM2_OUT_BE"; + mediatek,clk-provider = "cpu"; + codec { + sound-dai = <&spk_amplifier>; + }; }; hdmi-dai-link { - link-name = "ETDM3_OUT_BE"; - codec { - sound-dai = <&hdmi_tx>; - }; + link-name = "ETDM3_OUT_BE"; + codec { + sound-dai = <&hdmi_tx>; + }; }; displayport-dai-link { - link-name = "DPTX_BE"; - codec { - sound-dai = <&dp_tx>; - }; + link-name = "DPTX_BE"; + codec { + sound-dai = <&dp_tx>; + }; }; }; diff --git a/Documentation/devicetree/bindings/sound/nuvoton,nau8360.yaml b/Documentation/devicetree/bindings/sound/nuvoton,nau8360.yaml new file mode 100644 index 000000000000..3edab944a4e9 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nuvoton,nau8360.yaml @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nuvoton,nau8360.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Nuvoton NAU83G60 Stereo Class-D Amplifier with DSP + +description: + Stereo Class-D Amplifier with DSP and I/V-sense. + This device supports I2C. + +maintainers: + - Neo Chang + +properties: + compatible: + enum: + - nuvoton,nau8360 + + reg: + description: The I2C address is determined by the hardware pins. + enum: [0x1a, 0x1b, 0x4a, 0x4b] + + "#sound-dai-cells": + const: 0 + + clocks: + maxItems: 1 + + clock-names: + const: mclk + + firmware-name: + description: + List of DSP core firmware filenames. Both Left and Right DSP firmware + must be provided and loaded. In PBTL mode, the Left DSP must use the + same binary file as the Right DSP. + items: + - description: Left DSP core firmware filename. + - description: Right DSP core firmware filename. + + nuvoton,pbtl-enable: + type: boolean + description: NAU83G60 supports PBTL mode for mono output. + + nuvoton,dac-cur-enable: + type: boolean + description: + Adjust DAC output current to match speaker impedance and prevent + hardware damage. +3.2dB when present, 0dB by default. + + nuvoton,dsp-rx-slot-mapping: + description: + Configures the TDM slot routing for DSP RX functions. + Each integer assigns a specific DSP internal function to a TDM slot. + The indices of the array correspond to the following hardware functions + (0) DACL + (1) DACR + (2) ANCL + (3) ANCR + Assigned slots must be unique, except 255 which disables the function. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 4 + maxItems: 4 + items: + enum: [ 0, 1, 2, 3, 4, 5, 6, 7, 255 ] + + nuvoton,dsp-tx-slot-mapping: + description: + Configures the TDM slot routing for DSP TX functions. + Each integer assigns a specific DSP internal function to a TDM slot. + The indices of the array correspond to the following hardware functions + (0) AECL (Acoustic echo cancellation left) + (1) AECR (Acoustic echo cancellation right) + (2) ISNSL (Current sense left) + (3) ISNSR (Current sense right) + (4) VSNSL (Voltage sense left) + (5) VSNSR (Voltage sense right) + (6) TJ (Junction temperature) + (7) VBAT (Battery voltage) + Assigned slots must be unique, except 255 which disables the function. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 8 + maxItems: 8 + items: + enum: [ 0, 1, 2, 3, 4, 5, 6, 7, 255 ] + +required: + - compatible + - reg + - firmware-name + +allOf: + - $ref: dai-common.yaml# + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + codec@1a { + compatible = "nuvoton,nau8360"; + reg = <0x1a>; + #sound-dai-cells = <0>; + firmware-name = "NAU83G60.kcs.bin.l", "NAU83G60.kcs.bin.r"; + nuvoton,dac-cur-enable; + nuvoton,pbtl-enable; + nuvoton,dsp-rx-slot-mapping = <0 1 2 3>; + nuvoton,dsp-tx-slot-mapping = <0 1 2 3 4 5 6 7>; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml index 2588589ad62d..316806fc7346 100644 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml @@ -48,6 +48,11 @@ properties: $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 diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml new file mode 100644 index 000000000000..d2c317f831ce --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml @@ -0,0 +1,110 @@ +# 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,tegra30-ahub.txt b/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.txt deleted file mode 100644 index 0e9a1895d7fb..000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.txt +++ /dev/null @@ -1,88 +0,0 @@ -NVIDIA Tegra30 AHUB (Audio Hub) - -Required properties: -- compatible : For Tegra30, must contain "nvidia,tegra30-ahub". For Tegra114, - must contain "nvidia,tegra114-ahub". For Tegra124, must contain - "nvidia,tegra124-ahub". Otherwise, must contain "nvidia,-ahub", - plus at least one of the above, where is tegra132. -- reg : Should contain the register physical address and length for each of - the AHUB's register blocks. - - Tegra30 requires 2 entries, for the APBIF and AHUB/AUDIO register blocks. - - Tegra114 requires an additional entry, for the APBIF2 register block. -- interrupts : Should contain AHUB interrupt -- clocks : Must contain an entry for each entry in clock-names. - See ../clocks/clock-bindings.txt for details. -- clock-names : Must include the following entries: - - d_audio - - apbif -- resets : Must contain an entry for each entry in reset-names. - See ../reset/reset.txt for details. -- reset-names : Must include the following entries: - Tegra30 and later: - - d_audio - - apbif - - i2s0 - - i2s1 - - i2s2 - - i2s3 - - i2s4 - - dam0 - - dam1 - - dam2 - - spdif - Tegra114 and later additionally require: - - amx - - adx - Tegra124 and later additionally require: - - amx1 - - adx1 - - afc0 - - afc1 - - afc2 - - afc3 - - afc4 - - afc5 -- ranges : The bus address mapping for the configlink register bus. - Can be empty since the mapping is 1:1. -- dmas : Must contain an entry for each entry in clock-names. - See ../dma/dma.txt for details. -- dma-names : Must include the following entries: - - rx0 .. rx - - tx0 .. tx - ... where n is: - Tegra30: 3 - Tegra114, Tegra124: 9 -- #address-cells : For the configlink bus. Should be <1>; -- #size-cells : For the configlink bus. Should be <1>. - -AHUB client modules need to specify the IDs of their CIFs (Client InterFaces). -For RX CIFs, the numbers indicate the register number within AHUB routing -register space (APBIF 0..3 RX, I2S 0..5 RX, DAM 0..2 RX 0..1, SPDIF RX 0..1). -For TX CIFs, the numbers indicate the bit position within the AHUB routing -registers (APBIF 0..3 TX, I2S 0..5 TX, DAM 0..2 TX, SPDIF TX 0..1). - -Example: - -ahub@70080000 { - compatible = "nvidia,tegra30-ahub"; - reg = <0x70080000 0x200 0x70080200 0x100>; - interrupts = < 0 103 0x04 >; - nvidia,dma-request-selector = <&apbdma 1>; - clocks = <&tegra_car 106>, <&tegra_car 107>; - clock-names = "d_audio", "apbif"; - resets = <&tegra_car 106>, <&tegra_car 107>, <&tegra_car 30>, - <&tegra_car 11>, <&tegra_car 18>, <&tegra_car 101>, - <&tegra_car 102>, <&tegra_car 108>, <&tegra_car 109>, - <&tegra_car 110>, <&tegra_car 10>; - reset-names = "d_audio", "apbif", "i2s0", "i2s1", "i2s2", - "i2s3", "i2s4", "dam0", "dam1", "dam2", - "spdif"; - dmas = <&apbdma 1>, <&apbdma 1>; - <&apbdma 2>, <&apbdma 2>; - <&apbdma 3>, <&apbdma 3>; - <&apbdma 4>, <&apbdma 4>; - dma-names = "rx0", "tx0", "rx1", "tx1", "rx2", "tx2", "rx3", "tx3"; - ranges; - #address-cells = <1>; - #size-cells = <1>; -}; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml new file mode 100644 index 000000000000..26077c3ce198 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra30-ahub.yaml @@ -0,0 +1,289 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra30-ahub.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra30 AHUB (Audio Hub) + +maintainers: + - Thierry Reding + - Jon Hunter + +description: + AHUB client modules need to specify the IDs of their CIFs (Client + InterFaces). For RX CIFs, the numbers indicate the register number within + AHUB routing register space (APBIF 0..3 RX, I2S 0..5 RX, DAM 0..2 RX 0..1, + SPDIF RX 0..1). For TX CIFs, the numbers indicate the bit position within + the AHUB routing registers (APBIF 0..3 TX, I2S 0..5 TX, DAM 0..2 TX, SPDIF + TX 0..1). + +properties: + compatible: + oneOf: + - const: nvidia,tegra30-ahub + - const: nvidia,tegra114-ahub + - const: nvidia,tegra124-ahub + - items: + - const: nvidia,tegra132-ahub + - const: nvidia,tegra124-ahub + + reg: + minItems: 2 + maxItems: 3 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: digital audio clock + - description: module clock + + clock-names: + items: + - const: d_audio + - const: apbif + + resets: + minItems: 11 + maxItems: 21 + + reset-names: + minItems: 11 + maxItems: 21 + + ranges: true + + dmas: + minItems: 8 + maxItems: 20 + + dma-names: + minItems: 8 + maxItems: 20 + + "#address-cells": + enum: [1, 2] + + "#size-cells": + enum: [1, 2] + +patternProperties: + "^i2s@[0-9a-f]+$": + type: object + required: + - compatible + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - reset-names + - ranges + - dmas + - dma-names + - "#address-cells" + - "#size-cells" + +allOf: + - if: + properties: + compatible: + const: nvidia,tegra30-ahub + then: + properties: + reg: + items: + - description: APBIF registers + - description: AHUB/AUDIO registers + + resets: + maxItems: 11 + + reset-names: + items: + - const: d_audio + - const: apbif + - const: i2s0 + - const: i2s1 + - const: i2s2 + - const: i2s3 + - const: i2s4 + - const: dam0 + - const: dam1 + - const: dam2 + - const: spdif + + dmas: + maxItems: 8 + + dma-names: + items: + - const: rx0 + - const: tx0 + - const: rx1 + - const: tx1 + - const: rx2 + - const: tx2 + - const: rx3 + - const: tx3 + + - if: + properties: + compatible: + const: nvidia,tegra114-ahub + then: + properties: + reg: + items: + - description: APBIF registers + - description: AHUB/AUDIO registers + - description: APBIF2 registers + + resets: + minItems: 13 + maxItems: 13 + + reset-names: + items: + - const: d_audio + - const: apbif + - const: i2s0 + - const: i2s1 + - const: i2s2 + - const: i2s3 + - const: i2s4 + - const: dam0 + - const: dam1 + - const: dam2 + - const: spdif + - const: amx + - const: adx + + dmas: + minItems: 20 + + dma-names: + items: + - const: rx0 + - const: tx0 + - const: rx1 + - const: tx1 + - const: rx2 + - const: tx2 + - const: rx3 + - const: tx3 + - const: rx4 + - const: tx4 + - const: rx5 + - const: tx5 + - const: rx6 + - const: tx6 + - const: rx7 + - const: tx7 + - const: rx8 + - const: tx8 + - const: rx9 + - const: tx9 + + - if: + properties: + compatible: + contains: + const: nvidia,tegra124-ahub + then: + properties: + reg: + items: + - description: APBIF registers + - description: AHUB/AUDIO registers + - description: APBIF2 registers + + resets: + minItems: 21 + + reset-names: + items: + - const: d_audio + - const: apbif + - const: i2s0 + - const: i2s1 + - const: i2s2 + - const: i2s3 + - const: i2s4 + - const: dam0 + - const: dam1 + - const: dam2 + - const: spdif + - const: amx + - const: amx1 + - const: adx + - const: adx1 + - const: afc0 + - const: afc1 + - const: afc2 + - const: afc3 + - const: afc4 + - const: afc5 + + dmas: + minItems: 20 + + dma-names: + items: + - const: rx0 + - const: tx0 + - const: rx1 + - const: tx1 + - const: rx2 + - const: tx2 + - const: rx3 + - const: tx3 + - const: rx4 + - const: tx4 + - const: rx5 + - const: tx5 + - const: rx6 + - const: tx6 + - const: rx7 + - const: tx7 + - const: rx8 + - const: tx8 + - const: rx9 + - const: tx9 + +additionalProperties: false + +examples: + - | + #include + #include + + ahub@70080000 { + compatible = "nvidia,tegra30-ahub"; + reg = <0x70080000 0x200>, + <0x70080200 0x100>; + interrupts = ; + clocks = <&tegra_car TEGRA30_CLK_D_AUDIO>, + <&tegra_car TEGRA30_CLK_APBIF>; + clock-names = "d_audio", "apbif"; + resets = <&tegra_car 106>, <&tegra_car 107>, <&tegra_car 30>, + <&tegra_car 11>, <&tegra_car 18>, <&tegra_car 101>, + <&tegra_car 102>, <&tegra_car 108>, <&tegra_car 109>, + <&tegra_car 110>, <&tegra_car 10>; + reset-names = "d_audio", "apbif", "i2s0", "i2s1", "i2s2", + "i2s3", "i2s4", "dam0", "dam1", "dam2", + "spdif"; + dmas = <&apbdma 1>, <&apbdma 1>, + <&apbdma 2>, <&apbdma 2>, + <&apbdma 3>, <&apbdma 3>, + <&apbdma 4>, <&apbdma 4>; + dma-names = "rx0", "tx0", "rx1", "tx1", "rx2", "tx2", "rx3", "tx3"; + ranges; + #address-cells = <1>; + #size-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml b/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml index 33bc23b6176a..b603bb6eb9b7 100644 --- a/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,msm8916-wcd-digital-codec.yaml @@ -18,6 +18,7 @@ properties: - items: - enum: - qcom,sdm660-wcd-digital-codec + - qcom,sdm670-wcd-digital-codec - const: qcom,msm8916-wcd-digital-codec - const: qcom,msm8916-wcd-digital-codec diff --git a/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml index 9e5b30d9c6e6..c1f9fc9eab64 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml @@ -20,9 +20,24 @@ properties: minItems: 1 maxItems: 2 + memory-region: + minItems: 1 + items: + - description: + Control-path buffer (audio_heap_mem) for platforms where the DSP + runs in a stage-2 protected context (e.g. modem-DSP). + - description: + Data-path buffer pool from which per-stream DMA buffers are + allocated. + required: - compatible - - iommus + +oneOf: + - required: + - iommus + - required: + - memory-region additionalProperties: false diff --git a/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml b/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml index 4878c424ef03..78587139f155 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6apm-lpass-dais.yaml @@ -75,11 +75,11 @@ examples: #size-cells = <0>; dai@10 { - reg = ; - clocks = <&q6prmcc LPASS_CLK_ID_PRI_MI2S_IBIT - LPASS_CLK_ATTRIBUTE_COUPLE_NO>, - <&q6prmcc LPASS_CLK_ID_MCLK_1 - LPASS_CLK_ATTRIBUTE_COUPLE_NO>; - clock-names = "bclk", "mclk"; + reg = ; + clocks = <&q6prmcc LPASS_CLK_ID_PRI_MI2S_IBIT + LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_MCLK_1 + LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "bclk", "mclk"; }; }; diff --git a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml index 3b03e2acd67e..63f53837bd9f 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6dsp-lpass-ports.yaml @@ -108,10 +108,15 @@ patternProperties: properties: reg: contains: - # TDM DAI ID range from PRIMARY_TDM_RX_0 - QUINARY_TDM_TX_7 + # TDM DAI ID range from PRIMARY_TDM_RX_0 - QUINARY_TDM_TX_7 and + # AIF_TDM_RX_0 - AIF_TDM_TX_12 items: - minimum: 24 - maximum: 103 + oneOf: + - minimum: 24 + maximum: 103 + - minimum: 179 + maximum: 204 + then: required: - qcom,tdm-sync-mode @@ -127,7 +132,8 @@ patternProperties: contains: # MI2S DAI ID range PRIMARY_MI2S_RX - QUATERNARY_MI2S_TX and # QUINARY_MI2S_RX - QUINARY_MI2S_TX and - # LPI_MI2S_RX_0 - LPI_MI2S_TX_6 + # LPI_MI2S_RX_0 - LPI_MI2S_TX_6 and + # AIF_MI2S_RX_0 - AIF_MI2S_TX_12 items: oneOf: - minimum: 16 @@ -136,6 +142,8 @@ patternProperties: maximum: 128 - minimum: 137 maximum: 152 + - minimum: 153 + maximum: 178 then: required: - qcom,sd-lines diff --git a/Documentation/devicetree/bindings/sound/qcom,shikra-qaif-cpu.yaml b/Documentation/devicetree/bindings/sound/qcom,shikra-qaif-cpu.yaml new file mode 100644 index 000000000000..acca6f617751 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,shikra-qaif-cpu.yaml @@ -0,0 +1,290 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,shikra-qaif-cpu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Audio Interface (QAIF) CPU DAI Controller + +maintainers: + - Harendra Gautam + +description: | + The Qualcomm Audio Interface (QAIF) is a fully configurable DMA-based + audio subsystem controller. It serialises and deserialises PCM audio + between system memory and external serial audio peripherals (PCM, TDM, + I2S, MI2S) through the AIF path, and transfers parallel audio between + memory and an internal WCD codec through the CIF path. + + AIF (Audio Interface): up to 13 multi-lane Unified Audio Interfaces, + each supporting up to 8 independent data lanes. Each lane is individually + configurable as TX (output/speaker) or RX (input/mic). All lanes of an + interface share a single bit clock and frame sync. Supported modes are + PCM (short/long sync), TDM, and MI2S (stereo/mono). Per-interface + configuration includes sync source (master/slave), sync mode, sync delay, + sync inversion, slot width (8/16/24/32-bit), sample width, active slot + masks (up to 32 slots), bits-per-lane frame size, lane enable/direction + masks, loopback, output-enable control, and full-cycle path support for + long chip-to-chip connections. + + CIF (Codec Interface): up to 10 RDDMA (playback) and 10 WRDMA (capture) + channels connecting to an internal codec over a parallel bus. Each channel + supports active-channel enable mask (up to 16 channels), frame-sync + selection, frame-sync delay, frame-sync output gating, dynamic clock + gating, and 16-bit packing/unpacking. + + Note on RX/TX naming convention: in QAIF, TX refers to the output + (speaker/playback) direction and RX refers to the input (mic/capture) + direction. This applies to both AIF lane directions and CIF slot/mask + properties. + + DMA engine: RDDMA fetches audio from DDR/TCM/LPM into a shared SRAM + latency buffer (SHRAM) and drains it to the interface. WRDMA collects + data from the interface into SHRAM and writes it to memory. Each DMA + owns a private SHRAM region defined by start address and length registers. + Burst sizes of 1/2/4/8/16 beats (64-bit) are supported with up to 4 + outstanding transactions per DMA. Two QSB master ports (QXM0 for TCM, + QXM1 for DDR/LPM) provide the memory interface. + + Resources are partitioned among up to 5 Execution Engines (EEs) via + EE map registers. Each EE owns a set of DMAs, audio interfaces, and + interface groups, and receives its own independent interrupt output. + The interrupt hierarchy has a two-level structure: a summary register + identifies the event class (DMA period, underflow/overflow, error + response, audio interface underflow/overflow, group done, rate detector, + VFR), and per-resource status registers identify the specific channel. + + Interface grouping (bonding) allows up to 6 groups of audio and codec + interfaces to start synchronously and align their DMA period interrupts + within half a frame duration using the RDDMA padding feature. + + Two rate detector blocks measure the frequency of incoming frame sync or + word select signals and generate interrupts on rate change, undetected + rate, or sync timeout. + + Block diagram:: + + System Memory (DDR / LPM / TCM) + +---------------------------------+ + | Circular Buffers (ping-pong) | + +----------+----------+-----------+ + | ^ + 64-bit AXI 64-bit AXI + | | + +----------v----------+-----------+ + | QSB Master Ports | + | +----------+ +----------+ | + | | QXM0 | | QXM1 | | + | +----+-----+ +-----+----+ | + +-------|--------------|----------+ + | | + +-------v--------------v----------+ + | Shared RAM (SHRAM) | + | +------------+ +------------+ | + | | QXM0 Read | | QXM0 Write | | + | | SHRAM | | SHRAM | | + | +------------+ +------------+ | + | +------------+ +------------+ | + | | QXM1 Read | | QXM1 Write | | + | | SHRAM | | SHRAM | | + | +------------+ +------------+ | + +---+--------+--------+-------+---+ + | | | | + +---v--+ +--v---+ +--v---+ +-v----+ + |RDDMA | |RDDMA | |WRDMA | |WRDMA | + | AIF | | CIF | | AIF | | CIF | + |[0..n]| |[0..n]| |[0..n]| |[0..n]| + +--+---+ +--+---+ +--+---+ +-+----+ + | | ^ ^ + | TX | TX | RX | RX + v v | | + +--+--------------------+ +----+----------+ + | Unified Audio Intf | | Codec DMA | + | (AIF 0..12) | | Interface | + | | | (CIF) | + | AUD_INTFa block: | | | + | - Serializer (TX) | | RDDMA: DDR -> | + | - De-serializer (RX) | | internal | + | - Sync gen/detect | | codec | + | - Up to 8 data lanes | | WRDMA: codec | + | - PCM / TDM / MI2S | | -> DDR | + | - Near Pad Logic | | Up to 16 ch | + +--+--------------------+ +----+----------+ + | Lane 0..7 (TX/RX) | Parallel bus + | Bit clk + Frame sync | + Frame sync + v v + +--+--------+ +------+------+ + | External | | Internal | + | Serial | | Digital | + | Peripherals| | Codec | + | (PCM/TDM/ | | (Bolero/ | + | MI2S) | | WCD) | + +-----------+ +-------------+ + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: qcom,shikra-qaif-cpu + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + description: + QAIF summary interrupt for this Execution Engine. + + iommus: + maxItems: 1 + + dma-coherent: true + + clocks: + maxItems: 9 + + clock-names: + description: + Core clocks for register access and DMA, followed by one bit + clock per AIF interface. + items: + - const: lpass_config + - const: lpass_core_axim + - const: aud_dma + - const: aud_dma_mem + - const: bus + - const: aif_if0_ibit + - const: aif_if1_ibit + - const: aif_if2_ibit + - const: aif_if3_ibit + + '#sound-dai-cells': + const: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + '^aif@[0-9a-f]+$': + type: object + description: + QAIF AIF interface child node for PCM, TDM or MI2S configuration. + + allOf: + - $ref: /schemas/sound/tdm-slot.yaml# + + properties: + reg: + maxItems: 1 + description: + DAI ID of the AIF interface (QAIF_MI2S_AIF* or QAIF_TDM_AIF*). + + dai-format: + description: Serial audio interface format (MI2S, TDM or PCM). + enum: [i2s, dsp_a, dsp_b] + + dai-tdm-slot-width: + enum: [8, 16, 24, 32] + + dai-tdm-slot-num: + minimum: 1 + maximum: 32 + + dai-tdm-slot-rx-mask: + minItems: 1 + maxItems: 32 + + dai-tdm-slot-tx-mask: + minItems: 1 + maxItems: 32 + + qcom,qaif-aif-lane-map: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Per-lane direction map, one entry per lane starting from lane 0. + Use 0 for TX (output/speaker) and 1 for RX (input/mic). + minItems: 1 + maxItems: 8 + items: + enum: [0, 1] + + required: + - reg + - dai-format + - qcom,qaif-aif-lane-map + + if: + properties: + dai-format: + enum: [dsp_a, dsp_b] + then: + required: + - dai-tdm-slot-width + - dai-tdm-slot-num + - dai-tdm-slot-rx-mask + - dai-tdm-slot-tx-mask + + unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + - iommus + - clocks + - clock-names + - '#sound-dai-cells' + - '#address-cells' + - '#size-cells' + +unevaluatedProperties: false + +examples: + - | + #include + soc { + #address-cells = <2>; + #size-cells = <2>; + + qaif_cpu: audio-controller@a000000 { + compatible = "qcom,shikra-qaif-cpu"; + reg = <0x0 0x0a000000 0x0 0x20000>; + interrupts = ; + iommus = <&apps_smmu 0x1c0 0x0>; + clocks = <&gcc 213>, + <&gcc 214>, + <&audiocorecc 15>, + <&audiocorecc 17>, + <&audiocorecc 18>, + <&audiocorecc 5>, + <&audiocorecc 8>, + <&audiocorecc 11>, + <&audiocorecc 14>; + clock-names = "lpass_config", + "lpass_core_axim", + "aud_dma", + "aud_dma_mem", + "bus", + "aif_if0_ibit", + "aif_if1_ibit", + "aif_if2_ibit", + "aif_if3_ibit"; + #sound-dai-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + aif@b5 { + reg = <181>; + dai-format = "dsp_a"; + dai-tdm-slot-width = <32>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-rx-mask = <1 1>; + dai-tdm-slot-tx-mask = <1 1>; + qcom,qaif-aif-lane-map = <1 0>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index 1536fcd96d68..e2e3fc99e6d9 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -36,8 +36,10 @@ properties: - ayaneo,pocket-s2-sndcard - fairphone,fp4-sndcard - fairphone,fp5-sndcard + - motorola,dubai-sndcard - qcom,apq8096-sndcard - qcom,glymur-sndcard + - qcom,nord-sndcard - qcom,qcm6490-idp-sndcard - qcom,qcs615-sndcard - qcom,qcs6490-rb3gen2-sndcard @@ -54,6 +56,7 @@ properties: - qcom,sm8250-sndcard - qcom,sm8450-sndcard - qcom,x1e80100-sndcard + - thundercomm,qcs6490-rubikpi3-sndcard audio-routing: $ref: /schemas/types.yaml#/definitions/non-unique-string-array diff --git a/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml new file mode 100644 index 000000000000..62d85b1dca7f --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,wcd9378-sdw.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm WCD9378 (Tambora) SoundWire codec + +maintainers: + - Srinivas Kandagatla + +description: + Qualcomm WCD9378 (Tambora) SoundWire codec. The same silicon can be + fused for a mobile (non-SDCA) mode or an SDCA-compliant mode; the + qcom,sdca-compliant property selects the latter and is required by + this driver. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: sdw20217011000 + + reg: + maxItems: 1 + + qcom,sdca-compliant: + description: + Present when the codec is fused as SDCA-compliant. The mobile + (non-SDCA) variant carries the same class-ID compatible and is + handled by a different driver. + type: boolean + + qcom,port-mapping: + description: + Each entry maps a slave data port to a master data port. + Entries are in order starting from slave port 1; slave port 0 + is reserved and not represented. SDCA-compliant mode uses 8 + entries covering DP1..DP8. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 8 + maxItems: 8 + + reset-gpios: + description: GPIO used to release the codec from reset. + maxItems: 1 + + vdd-buck-supply: + description: 1.8 V analog buck supply. + + vdd-rxtx-supply: + description: 1.8 V RX/TX supply. + + vdd-io-supply: + description: Digital I/O supply. + + vdd-mic-bias-supply: + description: Mic bias supply. + + '#sound-dai-cells': + const: 1 + +required: + - compatible + - reg + - qcom,sdca-compliant + - qcom,port-mapping + - reset-gpios + - vdd-buck-supply + - vdd-rxtx-supply + - vdd-io-supply + - vdd-mic-bias-supply + - '#sound-dai-cells' + +unevaluatedProperties: false + +examples: + - | + #include + + soundwire@7630000 { + reg = <0x07630000 0x10000>; + #address-cells = <2>; + #size-cells = <0>; + + wcd9378c_sdw: audio-codec@0,3 { + compatible = "sdw20217011000"; + reg = <0 3>; + + qcom,sdca-compliant; + + qcom,port-mapping = <2 2 3 4 5 6 7 8>; + + reset-gpios = <&tlmm 191 GPIO_ACTIVE_LOW>; + + vdd-buck-supply = <&vreg_l15b_1p8>; + vdd-rxtx-supply = <&vreg_l15b_1p8>; + vdd-io-supply = <&vreg_l18b_1p2>; + vdd-mic-bias-supply = <&vreg_bob1>; + + #sound-dai-cells = <1>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml b/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml index ae27ae78b1b2..f76aad0d3016 100644 --- a/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml +++ b/Documentation/devicetree/bindings/sound/realtek,rt5677.yaml @@ -44,6 +44,14 @@ properties: '#gpio-cells': const: 2 + clocks: + items: + - description: phandle and clock specifier for the codec MCLK1 input. + + clock-names: + items: + - const: mclk1 + realtek,pow-ldo2-gpio: maxItems: 1 description: CODEC's POW_LDO2 pin. @@ -127,6 +135,8 @@ examples: interrupts = <3 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; #gpio-cells = <2>; + clocks = <&osc>; + clock-names = "mclk1"; realtek,pow-ldo2-gpio = <&gpio 3 GPIO_ACTIVE_HIGH>; realtek,reset-gpio = <&gpio 3 GPIO_ACTIVE_LOW>; realtek,in1-differential; diff --git a/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml b/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml index e8a2acb92646..d3516fb1b130 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml +++ b/Documentation/devicetree/bindings/sound/renesas,rsnd.yaml @@ -432,14 +432,14 @@ examples: "ssi.1", "ssi.0"; rcar_sound,dvc { - dvc0: dvc-0 { - dmas = <&audma0 0xbc>; - dma-names = "tx"; - }; - dvc1: dvc-1 { - dmas = <&audma0 0xbe>; - dma-names = "tx"; - }; + dvc0: dvc-0 { + dmas = <&audma0 0xbc>; + dma-names = "tx"; + }; + dvc1: dvc-1 { + dmas = <&audma0 0xbe>; + dma-names = "tx"; + }; }; rcar_sound,mix { diff --git a/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml b/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml index 1394f78281fc..d696fb33d9b8 100644 --- a/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml +++ b/Documentation/devicetree/bindings/sound/renesas,rz-ssi.yaml @@ -95,20 +95,20 @@ examples: ssi0: ssi@10049c00 { compatible = "renesas,r9a07g044-ssi", "renesas,rz-ssi"; - reg = <0x10049c00 0x400>; - interrupts = , - , - ; - interrupt-names = "int_req", "dma_rx", "dma_tx"; - clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>, - <&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>, - <&audio_clk1>, - <&audio_clk2>; - clock-names = "ssi", "ssi_sfr", "audio_clk1", "audio_clk2"; - power-domains = <&cpg>; - resets = <&cpg R9A07G044_SSI0_RST_M2_REG>; - dmas = <&dmac 0x2655>, - <&dmac 0x2656>; - dma-names = "tx", "rx"; - #sound-dai-cells = <0>; + reg = <0x10049c00 0x400>; + interrupts = , + , + ; + interrupt-names = "int_req", "dma_rx", "dma_tx"; + clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>, + <&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>, + <&audio_clk1>, + <&audio_clk2>; + clock-names = "ssi", "ssi_sfr", "audio_clk1", "audio_clk2"; + power-domains = <&cpg>; + resets = <&cpg R9A07G044_SSI0_RST_M2_REG>; + dmas = <&dmac 0x2655>, + <&dmac 0x2656>; + dma-names = "tx", "rx"; + #sound-dai-cells = <0>; }; diff --git a/Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml b/Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml new file mode 100644 index 000000000000..bdfcd5376cfe --- /dev/null +++ b/Documentation/devicetree/bindings/sound/stericsson,ux500-msp-i2s.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/stericsson,ux500-msp-i2s.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ST-Ericsson Ux500 Multichannel Serial Port + +maintainers: + - Linus Walleij + +description: + The Ux500 Multichannel Serial Port (MSP) is a synchronous serial audio + interface supporting I2S and PCM/TDM protocols. Data is transferred to and + from memory using the Ux500 DMA controller. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: stericsson,ux500-msp-i2s + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + v-ape-supply: + description: Analog power engine supply + + dmas: + minItems: 1 + maxItems: 2 + + dma-names: + minItems: 1 + maxItems: 2 + items: + enum: [ rx, tx ] + + clocks: + items: + - description: MSP functional clock + - description: MSP APB bus clock + + clock-names: + items: + - const: msp + - const: apb_pclk + + resets: + maxItems: 1 + + '#sound-dai-cells': + const: 0 + + port: + $ref: audio-graph-port.yaml# + unevaluatedProperties: false + +required: + - compatible + - reg + - interrupts + - v-ape-supply + - dmas + - dma-names + - clocks + - clock-names + - resets + - '#sound-dai-cells' + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + msp@80123000 { + compatible = "stericsson,ux500-msp-i2s"; + reg = <0x80123000 0x1000>; + interrupts = ; + v-ape-supply = <&db8500_vape_reg>; + dmas = <&dma 31 0 0x12>, <&dma 31 0 0x10>; + dma-names = "rx", "tx"; + clocks = <&prcc_kclk 1 3>, <&prcc_pclk 1 3>; + clock-names = "msp", "apb_pclk"; + resets = <&prcc_reset DB8500_PRCC_1 DB8500_PRCC_1_RESET_MSP0>; + #sound-dai-cells = <0>; + }; + +... diff --git a/Documentation/devicetree/bindings/sound/ti,da830-evm-audio.yaml b/Documentation/devicetree/bindings/sound/ti,da830-evm-audio.yaml new file mode 100644 index 000000000000..b4a1cf67582b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/ti,da830-evm-audio.yaml @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/ti,da830-evm-audio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments SoC audio setups with TLV320AIC3X Codec + +maintainers: + - Eduard Bostina + +properties: + compatible: + const: ti,da830-evm-audio + + ti,model: + $ref: /schemas/types.yaml#/definitions/string + description: The user-visible name of this sound complex. + + ti,audio-codec: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of the TLV320AIC3x audio codec + + ti,mcasp-controller: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of the McASP controller + + ti,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's pins, and the jacks on the board (Headphone Jack, + Line Out, Mic Jack, Line In). + + ti,codec-clock-rate: + $ref: /schemas/types.yaml#/definitions/uint32 + description: The Codec Clock rate (in Hz) applied to the Codec. + + clocks: + maxItems: 1 + description: Reference to the master clock + + clock-names: + const: mclk + +required: + - compatible + - ti,model + - ti,audio-codec + - ti,mcasp-controller + - ti,audio-routing + +anyOf: + - required: + - ti,codec-clock-rate + - required: + - clocks + +additionalProperties: false + +examples: + - | + sound { + compatible = "ti,da830-evm-audio"; + ti,model = "DA830 EVM"; + ti,audio-codec = <&tlv320aic3x>; + ti,mcasp-controller = <&mcasp1>; + ti,codec-clock-rate = <12000000>; + ti,audio-routing = + "Headphone Jack", "HPLOUT", + "Headphone Jack", "HPROUT", + "Line Out", "LLOUT", + "Line Out", "RLOUT", + "MIC3L", "Mic Bias 2V", + "MIC3R", "Mic Bias 2V", + "Mic Bias 2V", "Mic Jack", + "LINE1L", "Line In", + "LINE2L", "Line In", + "LINE1R", "Line In", + "LINE2R", "Line In"; + }; diff --git a/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml b/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml index 1f0e6787a746..fb09d1c39e3b 100644 --- a/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml +++ b/Documentation/devicetree/bindings/sound/ti,pcm1681.yaml @@ -21,6 +21,22 @@ properties: reg: maxItems: 1 + clocks: + maxItems: 1 + description: System clock (SCK) + + clock-names: + const: sck + + VCC1-supply: + description: 5V analog power supply + + VCC2-supply: + description: 5V analog power supply + + VDD-supply: + description: 3.3V digital power supply + "#sound-dai-cells": const: 0 diff --git a/Documentation/devicetree/bindings/sound/ti,tas2781.yaml b/Documentation/devicetree/bindings/sound/ti,tas2781.yaml index aa5a317b5a36..50c7792b24f8 100644 --- a/Documentation/devicetree/bindings/sound/ti,tas2781.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tas2781.yaml @@ -219,9 +219,7 @@ allOf: - if: properties: compatible: - contains: - enum: - - ti,tas2781 + const: ti,tas2781 then: properties: reg: diff --git a/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml b/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml index c2c2835a9e1d..f2ab274b02cd 100644 --- a/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tas5805m.yaml @@ -45,13 +45,13 @@ examples: #address-cells = <1>; #size-cells = <0>; tas5805m: tas5805m@2c { - reg = <0x2c>; - compatible = "ti,tas5805m"; + reg = <0x2c>; + compatible = "ti,tas5805m"; - pvdd-supply = <&audiopwr>; - pdn-gpios = <&tlmm 160 0>; + pvdd-supply = <&audiopwr>; + pdn-gpios = <&tlmm 160 0>; - ti,dsp-config-name = "mono_pbtl_48khz"; + ti,dsp-config-name = "mono_pbtl_48khz"; }; }; ... diff --git a/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml b/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml index 10299064cbc6..3bdaab1e5084 100644 --- a/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tlv320dac3100.yaml @@ -121,6 +121,6 @@ examples: AVDD-supply = <®ulator>; IOVDD-supply = <®ulator>; DVDD-supply = <®ulator>; - }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml b/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml index a42bf9bde694..5634b601ce65 100644 --- a/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml +++ b/Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml @@ -50,6 +50,6 @@ examples: reg = <0x60>; Vdd-supply = <&vmmc2>; power-gpio = <&gpio4 2 GPIO_ACTIVE_HIGH>; - }; + }; }; diff --git a/Documentation/devicetree/bindings/sound/ux500-mop500.txt b/Documentation/devicetree/bindings/sound/ux500-mop500.txt deleted file mode 100644 index 48e071c96b46..000000000000 --- a/Documentation/devicetree/bindings/sound/ux500-mop500.txt +++ /dev/null @@ -1,39 +0,0 @@ -* MOP500 Audio Machine Driver - -This node is responsible for linking together all ux500 Audio Driver components. - -Required properties: - - compatible : "stericsson,snd-soc-mop500" - -Non-standard properties: - - stericsson,cpu-dai : Phandle to the CPU-side DAI - - stericsson,audio-codec : Phandle to the Audio CODEC - - stericsson,card-name : Over-ride default card name - -Example: - - sound { - compatible = "stericsson,snd-soc-mop500"; - - stericsson,cpu-dai = <&msp1 &msp3>; - stericsson,audio-codec = <&codec>; - }; - - msp1: msp@80124000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80124000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - msp3: msp@80125000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80125000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - codec: ab8500-codec { - compatible = "stericsson,ab8500-codec"; - stericsson,earpeice-cmv = <950>; /* Units in mV. */ - }; diff --git a/Documentation/devicetree/bindings/sound/ux500-msp.txt b/Documentation/devicetree/bindings/sound/ux500-msp.txt deleted file mode 100644 index 7dd1b96160f5..000000000000 --- a/Documentation/devicetree/bindings/sound/ux500-msp.txt +++ /dev/null @@ -1,42 +0,0 @@ -* ux500 MSP (CPU-side Digital Audio Interface) - -Required properties: - - compatible :"stericsson,ux500-msp-i2s" - - reg : Physical base address and length of the device's registers. - -Optional properties: - - interrupts : The interrupt output from the device. - - -supply : Phandle to the regulator supply - -Example: - - sound { - compatible = "stericsson,snd-soc-mop500"; - - stericsson,platform-pcm-dma = <&pcm>; - stericsson,cpu-dai = <&msp1 &msp3>; - stericsson,audio-codec = <&codec>; - }; - - pcm: ux500-pcm { - compatible = "stericsson,ux500-pcm"; - }; - - msp1: msp@80124000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80124000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - msp3: msp@80125000 { - compatible = "stericsson,ux500-msp-i2s"; - reg = <0x80125000 0x1000>; - interrupts = <0 62 0x4>; - v-ape-supply = <&db8500_vape_reg>; - }; - - codec: ab8500-codec { - compatible = "stericsson,ab8500-codec"; - stericsson,earpeice-cmv = <950>; /* Units in mV. */ - }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 539aa1c89ce7..2d93308624ce 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -561,6 +561,8 @@ patternProperties: description: Seiko Epson Corp. "^esp,.*": description: Espressif Systems Co. Ltd. + "^esstech,.*": + description: ESS Technology, Inc. "^est,.*": description: ESTeem Wireless Modems "^eswin,.*": diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index e500ec84e3ef..689eccf6a592 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2407,6 +2407,12 @@ quirk_flags the requested rate. A device advertising a single rate is otherwise never sent the request at all, and some require it before streaming will start. + * bit 33: ``swap_rates`` + The clock source applies a requested rate to the other base-rate + family, i.e. asking for 44100 makes the device run at 48000 and vice + versa (same for 88200 <-> 96000 and 176400 <-> 192000). The driver + sends the partner rate in SET_CUR so the device ends up running at the + requested one. This module supports multiple devices, autoprobe and hotplugging. diff --git a/Documentation/sound/cards/hdspm.rst b/Documentation/sound/cards/hdspm.rst index 5373e51ed076..deba92267942 100644 --- a/Documentation/sound/cards/hdspm.rst +++ b/Documentation/sound/cards/hdspm.rst @@ -6,9 +6,11 @@ Software Interface ALSA-DSP MADI Driver 2004 - winfried ritsch +driver name - snd-hdspm + Full functionality has been added to the driver. Since some of -the Controls and startup-options are ALSA-Standard and only the +the Controls and startup options are ALSA-Standard, only the special Controls are described and discussed below. @@ -18,7 +20,7 @@ Hardware functionality Audio transmission ------------------ -* number of channels -- depends on transmission mode +* number of channels -- depends on transmission mode The number of channels chosen is from 1..Nmax. The reason to use for a lower number of channels is only resource allocation, @@ -29,29 +31,29 @@ Audio transmission * Single Speed -- 1..64 channels .. note:: - (Note: Choosing the 56channel mode for transmission or as + Choosing the 56-channel mode for transmission or as receiver, only 56 are transmitted/received over the MADI, but all 64 channels are available for the mixer, so channel count - for the driver) + for the driver. * Double Speed -- 1..32 channels .. note:: - Note: Choosing the 56-channel mode for - transmission/receive-mode , only 28 are transmitted/received + Choosing the 56-channel mode for + transmission/receive-mode, only 28 are transmitted/received over the MADI, but all 32 channels are available for the mixer, - so channel count for the driver + so channel count for the driver. * Quad Speed -- 1..16 channels .. note:: Choosing the 56-channel mode for - transmission/receive-mode , only 14 are transmitted/received + transmission/receive-mode, only 14 are transmitted/received over the MADI, but all 16 channels are available for the mixer, - so channel count for the driver + so channel count for the driver. -* Format -- signed 32 Bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE) +* Format -- signed 32-bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE) * Sample Rates -- @@ -67,30 +69,6 @@ Audio transmission * fragments -- 2 -* Hardware-pointer -- 2 Modi - - - The Card supports the readout of the actual Buffer-pointer, - where DMA reads/writes. Since of the bulk mode of PCI it is only - 64 Byte accurate. SO it is not really usable for the - ALSA-mid-level functions (here the buffer-ID gives a better - result), but if MMAP is used by the application. Therefore it - can be configured at load-time with the parameter - precise-pointer. - - -.. hint:: - (Hint: Experimenting I found that the pointer is maximum 64 to - large never to small. So if you subtract 64 you always have a - safe pointer for writing, which is used on this mode inside - ALSA. In theory now you can get now a latency as low as 16 - Samples, which is a quarter of the interrupt possibilities.) - - * Precise Pointer -- off - interrupt used for pointer-calculation - - * Precise Pointer -- on - hardware pointer used. Controller ---------- @@ -104,7 +82,7 @@ hwdep-interface. Also all 128+256 Peak and RMS-Meter can be accessed via the hwdep-interface. Since it could be a performance problem always copying and converting Peak and RMS-Levels even if you just need -one, I decided to export the hardware structure, so that of +one, I decided to export the hardware structure, so that if needed some driver-guru can implement a memory-mapping of mixer or peak-meters over ioctl, or also to do only copying and no conversion. A test-application shows the usage of the controller. @@ -112,9 +90,9 @@ conversion. A test-application shows the usage of the controller. * Latency Controls --- not implemented !!! .. note:: - Note: Within the windows-driver the latency is accessible of a - control-panel, but buffer-sizes are controlled with ALSA from - hwparams-calls and should not be changed in run-state, I did not + Within the Windows driver the latency is accessible via + Control Panel, but buffer sizes are controlled with ALSA from + hwparams calls and should not be changed in run-state. I did not implement it here. @@ -127,12 +105,12 @@ conversion. A test-application shows the usage of the controller. * Values -- "Master" "Slave" .. note:: - !!!! This is a hardware-function but is in conflict with the - Clock-source controller, which is a kind of ALSA-standard. I + !!!! This is a hardware function but is in conflict with the + Clock-source controller, which is a kind of ALSA-standard. It makes sense to set the card to a special mode (master at some - frequency or slave), since even not using an Audio-application + frequency or slave), since even not using an Audio application a studio should have working synchronisations setup. So use - Clock-source-controller instead !!!! + Clock-source controller instead !!!! * Clock Source @@ -145,11 +123,11 @@ conversion. A test-application shows the usage of the controller. "Internal 96.0 kHz" Choose between Master at a specific Frequency and so also the - Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref" + Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref". .. warning:: - !!!! This is no pure hardware function but was implemented by - ALSA by some ALSA-drivers before, so I use it also. !!! + !!!! This is not a pure hardware function but was implemented by + ALSA by some ALSA drivers before, so I use it also. !!! * Preferred Sync Ref @@ -165,7 +143,7 @@ conversion. A test-application shows the usage of the controller. chosen. If it is not available another is used if possible. .. note:: - Note: Since MADI has a much higher bit-rate than word-clock, the + Since MADI has a much higher bit-rate than word-clock, the card should synchronise better in MADI Mode. But since the RME-PLL is very good, there are almost no problems with word-clock too. I never found a difference. @@ -179,12 +157,12 @@ conversion. A test-application shows the usage of the controller. * Values -- 0 1 - Using 64-channel-modus (1) or 56-channel-modus for + Using 64-channel mode (1) or 56-channel mode for MADI-transmission (0). .. note:: - Note: This control is for output only. Input-mode is detected + This control is for output only. Input mode is detected automatically from hardware sending MADI. @@ -200,7 +178,7 @@ conversion. A test-application shows the usage of the controller. Don't use to lower 5 Audio-bits on AES as additional Bits. -* Safe Mode oder Auto Input +* Safe Mode or Auto Input * Name -- "Safe Mode" @@ -222,7 +200,7 @@ conversion. A test-application shows the usage of the controller. * Values -- optical coaxial - Choosing the Input, optical or coaxial. If Safe-mode is active, + Choosing the Input, optical or coaxial. If Safe mode is active, this is the preferred Input. Mixer @@ -240,7 +218,7 @@ Mixer Here as a first value the channel-index is taken to get/set the corresponding mixer channel, where 0-63 are the input to output fader and 64-127 the playback to outputs fader. Value 0 - is channel muted 0 and 32768 an amplification of 1. + is channel muted 0 and 32768 an amplification of 1. * Chn 1-64 @@ -250,14 +228,14 @@ Mixer * Line Out - * Name -- "Line Out" + * Name -- "Line Out" * Access -- Read Write * Values -- 0 1 Switching on and off the analog out, which has nothing to do - with mixing or routing. the analog outs reflects channel 63,64. + with mixing or routing. The analog outs reflects channel 63,64. Information (only read access) @@ -280,7 +258,7 @@ Information (only read access) Should be "Autosync Rate", but Name used is - ALSA-Scheme. External Sample frequency liked used on Autosync is + ALSA-Scheme. External Sample frequency like used on Autosync is reported. @@ -315,19 +293,19 @@ Information (only read access) Sync-Reference is either "WordClock", "MADI" or none. -* RX 64ch --- noch nicht implementiert +* RX 64ch --- not yet implemented - MADI-Receiver is in 64 channel mode oder 56 channel mode. + MADI-Receiver is in 64-channel mode or 56-channel mode. -* AB_inp --- not tested +* AB_inp --- not tested Used input for Auto-Input. * actual Buffer Position --- not implemented - !!! this is a ALSA internal function, so no control is used !!! + !!! This is an ALSA internal function, so no control is used !!! @@ -352,22 +330,15 @@ Calling Parameter note: ALSA-standard -* precise_ptr int array (min = 1, max = 8) - - Enable precise pointer, or disable. - -.. note:: - note: Use only when the application supports this (which is a special case). - * line_outs_monitor int array (min = 1, max = 8) Send playback streams to analog outs by default. .. note:: - note: each playback channel is mixed to the same numbered output + Each playback channel is mixed to the same numbered output channel (routed). This is against the ALSA-convention, where all channels have to be muted on after loading the driver, but was - used before on other cards, so i historically use it again) + used before on other cards, so I historically use it again. @@ -375,5 +346,5 @@ Calling Parameter Enable Analog Out on Channel 63/64 by default. -.. note :: - note: here the analog output is enabled (but not routed). +.. note:: + Here the analog output is enabled (but not routed). diff --git a/MAINTAINERS b/MAINTAINERS index b13b37402e4d..60734cdd3812 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -779,6 +779,14 @@ F: Documentation/devicetree/bindings/phy/airoha,en7581-pcie-phy.yaml F: drivers/phy/phy-airoha-pcie-regs.h F: drivers/phy/phy-airoha-pcie.c +AIROHA SOUND DRIVER +M: Christian Marangi +L: linux-sound@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/sound/airoha,an7581-afe.yaml +F: Documentation/devicetree/bindings/sound/airoha,an7581-wm8960.yaml +F: sound/soc/mediatek/an7581/* + AIROHA SPI SNFI DRIVER M: Lorenzo Bianconi M: Ray Liu @@ -4399,6 +4407,13 @@ F: drivers/net/ieee802154/at86rf230.h F: drivers/net/ieee802154/atusb.c F: drivers/net/ieee802154/atusb.h +AUDIENT EVO MIXER DRIVER +M: Christian Ruppert +L: linux-sound@vger.kernel.org +S: Maintained +F: sound/usb/mixer_evo.c +F: sound/usb/mixer_evo.h + AUDIT SUBSYSTEM M: Paul Moore M: Eric Paris @@ -4472,6 +4487,12 @@ L: linux-media@vger.kernel.org S: Maintained F: drivers/media/pci/hws/ +AWINIC AW88399 HDA SIDE CODEC DRIVER +M: Marco Giunta +L: linux-sound@vger.kernel.org +S: Maintained +F: sound/hda/codecs/side-codecs/aw88399* + AWINIC AW99706 WLED BACKLIGHT DRIVER M: Junjie Cao S: Maintained @@ -9874,6 +9895,13 @@ S: Maintained F: include/linux/errseq.h F: lib/errseq.c +ES9039Q2M AUDIO CODEC DRIVER +M: Karl Asseily +L: linux-sound@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml +F: sound/soc/codecs/es9039q2m.c + ESD CAN NETWORK DRIVERS M: Stefan Mätje R: socketcan@esd.eu @@ -22395,6 +22423,15 @@ F: sound/soc/codecs/wcd93*.* F: sound/soc/codecs/wsa88*.* F: sound/soc/qcom/ +QCOM AUDIO INTERFACE (QAIF) DRIVER +M: Harendra Gautam +M: Srinivas Kandagatla +L: linux-sound@vger.kernel.org +L: linux-arm-msm@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/sound/qcom,shikra-qaif-cpu.yaml +F: sound/soc/qcom/qaif* + QCOM EMBEDDED USB DEBUGGER (EUD) M: Souradeep Chowdhury L: linux-arm-msm@vger.kernel.org @@ -30630,9 +30667,12 @@ F: tools/testing/selftests/cgroup/test_zswap.c SENARYTECH AUDIO CODEC DRIVER M: bo liu +M: qianghua.wang S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git F: sound/hda/codecs/senarytech.c +F: sound/soc/codecs/sn624x* +F: sound/soc/sdw_utils/soc_sdw_senary* ZTE DINGHAI ETHERNET DRIVER M: Junyang Han diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 30bfef67cbaa..0b17a7e51956 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1763,6 +1763,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) /* Non-conforming _HID for Cirrus Logic already released */ {"CLSA0100", }, {"CLSA0101", }, + {"CLSA0102", }, /* * Some ACPI devs contain SerialBus resources even though they are not * attached to a serial bus at all. diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index bb93b24c659d..844c68036975 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -2960,6 +2960,8 @@ int cs_dsp_run(struct cs_dsp *dsp) return 0; err: + dsp->running = false; + if (dsp->ops->stop_core) dsp->ops->stop_core(dsp); if (dsp->ops->disable_core) @@ -3505,7 +3507,7 @@ EXPORT_SYMBOL_NS_GPL(cs_dsp_chunk_write, "FW_CS_DSP"); * * As cs_dsp_chunk_write only writes data when a whole DSP word is ready to * be written out it is possible that some data will remain in the cache, this - * function will pad that data with zeros upto a whole DSP word and write out. + * function will pad that data with zeros up to a whole DSP word and write out. * * Return: Zero for success, a negative number on error. */ diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c b/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c index e5a389808e5f..b6e141ec409f 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c @@ -36,6 +36,8 @@ struct cs_dsp_test_local { int num_post_stop; int num_watchdog_expired; + bool fail_post_run; + struct cs_dsp_coeff_ctl *passed_ctl[16]; struct cs_dsp *passed_dsp; }; @@ -106,6 +108,9 @@ static int cs_dsp_test_post_run_callback(struct cs_dsp *dsp) local->passed_dsp = dsp; local->num_post_run++; + if (local->fail_post_run) + return -EIO; + return 0; } @@ -198,6 +203,33 @@ static void cs_dsp_test_run_stop_callbacks(struct kunit *test) local->passed_dsp = NULL; } +static void cs_dsp_test_run_error_clears_running(struct kunit *test) +{ + struct cs_dsp_test *priv = test->priv; + struct cs_dsp_test_local *local = priv->local; + struct firmware *wmfw; + + wmfw = cs_dsp_mock_wmfw_get_firmware(local->wmfw_builder); + + KUNIT_EXPECT_EQ(test, + cs_dsp_power_up(priv->dsp, wmfw, "wmfw", NULL, NULL, "misc"), + 0); + + /* + * By the time the post_run hook runs, the core has been started. A + * failure there stops and disables the core again, so the DSP must not + * be left looking like it is running. + */ + local->fail_post_run = true; + KUNIT_EXPECT_EQ(test, cs_dsp_run(priv->dsp), -EIO); + KUNIT_EXPECT_FALSE(test, priv->dsp->running); + + /* and the next run must be able to start it again */ + local->fail_post_run = false; + KUNIT_EXPECT_EQ(test, cs_dsp_run(priv->dsp), 0); + KUNIT_EXPECT_TRUE(test, priv->dsp->running); +} + static void cs_dsp_test_ctl_v1_callbacks(struct kunit *test) { struct cs_dsp_test *priv = test->priv; @@ -609,6 +641,7 @@ KUNIT_ARRAY_PARAM(cs_dsp_no_callbacks, static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv1_test_cases[] = { KUNIT_CASE_PARAM(cs_dsp_test_run_stop_callbacks, cs_dsp_callbacks_ops_gen_params), + KUNIT_CASE_PARAM(cs_dsp_test_run_error_clears_running, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_ctl_v1_callbacks, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_no_callbacks, cs_dsp_no_callbacks_gen_params), @@ -617,6 +650,7 @@ static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv1_test_cases[] = { static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv2_test_cases[] = { KUNIT_CASE_PARAM(cs_dsp_test_run_stop_callbacks, cs_dsp_callbacks_ops_gen_params), + KUNIT_CASE_PARAM(cs_dsp_test_run_error_clears_running, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_ctl_v2_callbacks, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_no_callbacks, cs_dsp_no_callbacks_gen_params), @@ -625,6 +659,7 @@ static struct kunit_case cs_dsp_callbacks_adsp2_wmfwv2_test_cases[] = { static struct kunit_case cs_dsp_callbacks_halo_test_cases[] = { KUNIT_CASE_PARAM(cs_dsp_test_run_stop_callbacks, cs_dsp_callbacks_ops_gen_params), + KUNIT_CASE_PARAM(cs_dsp_test_run_error_clears_running, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_ctl_v2_callbacks, cs_dsp_callbacks_ops_gen_params), KUNIT_CASE_PARAM(cs_dsp_test_no_callbacks, cs_dsp_no_callbacks_gen_params), diff --git a/drivers/gpib/fmh_gpib/fmh_gpib.c b/drivers/gpib/fmh_gpib/fmh_gpib.c index 5e10e9353fed..4ab9b0a317fd 100644 --- a/drivers/gpib/fmh_gpib/fmh_gpib.c +++ b/drivers/gpib/fmh_gpib/fmh_gpib.c @@ -1458,11 +1458,15 @@ static int fmh_gpib_attach_impl(struct gpib_board *board, const struct gpib_boar e_priv->irq = irq; if (acquire_dma) { - e_priv->dma_channel = dma_request_slave_channel(board->dev, "rxtx"); - if (!e_priv->dma_channel) { + struct dma_chan *tmp_chan; + + tmp_chan = dma_request_chan(board->dev, "rxtx"); + retval = PTR_ERR_OR_ZERO(tmp_chan); + if (retval) { dev_err(board->dev, "failed to acquire dma channel \"rxtx\".\n"); - return -EIO; + return retval; } + e_priv->dma_channel = tmp_chan; } /* * in the future we might want to know the half-fifo size diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c index 3e89fcdd45f7..d535025a3f73 100644 --- a/drivers/platform/x86/serial-multi-instantiate.c +++ b/drivers/platform/x86/serial-multi-instantiate.c @@ -426,6 +426,7 @@ static const struct acpi_device_id smi_acpi_ids[] = { /* Non-conforming _HID for Cirrus Logic already released */ { "CLSA0100", (unsigned long)&cs35l41_hda }, { "CLSA0101", (unsigned long)&cs35l41_hda }, + { "CLSA0102", (unsigned long)&cs35l41_hda }, { } }; MODULE_DEVICE_TABLE(acpi, smi_acpi_ids); diff --git a/include/dt-bindings/sound/cs35l45.h b/include/dt-bindings/sound/cs35l45.h index 25386af18445..5cb77d48b0b8 100644 --- a/include/dt-bindings/sound/cs35l45.h +++ b/include/dt-bindings/sound/cs35l45.h @@ -17,6 +17,33 @@ #define CS35L45_ASP_TX_HIZ_UNUSED 0x1 #define CS35L45_ASP_TX_HIZ_DISABLED 0x2 +/* + * cirrus,boost-low-power-mode + * + * BST_LPMODE_NO_REACTION: No reaction + * BST_LPMODE_DCM_MODE: Force boost DCM low power mode + * BST_LPMODE_STATIC_BYPASS: Force boost static bypass + */ +#define CS35L45_BST_LPMODE_NO_REACTION 0x0 +#define CS35L45_BST_LPMODE_DCM_MODE 0x1 +#define CS35L45_BST_LPMODE_STATIC_BYPASS 0x2 + +/* + * cirrus,amplifier-low-power-mode + * + * AMP_LPMODE_NG_MODE: Noise gate mode + * AMP_LPMODE_NFR_MODE: Noise floor reduction mode + */ + #define CS35L45_AMP_LPMODE_NG_MODE 0x0 + #define CS35L45_AMP_LPMODE_NFR_MODE 0x1 + +/* + * cirrus,ldpm-groupX-pcm-threshold-db + * + * LDPM_PCM_THLD_TRUE_ZERO: + */ +#define CS35L45_LDPM_PCM_THLD_TRUE_ZERO -2147483648 + /* * Optional GPIOX Sub-nodes: * The cs35l45 node can have up to three "cirrus,gpio-ctrlX" ('X' = [1,2,3]) diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h index 3a99703dbc4a..e6c792d9312f 100644 --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h @@ -156,6 +156,58 @@ #define LPI_MI2S_TX_5 150 #define LPI_MI2S_RX_6 151 #define LPI_MI2S_TX_6 152 +#define AIF_MI2S_RX_0 153 +#define AIF_MI2S_TX_0 154 +#define AIF_MI2S_RX_1 155 +#define AIF_MI2S_TX_1 156 +#define AIF_MI2S_RX_2 157 +#define AIF_MI2S_TX_2 158 +#define AIF_MI2S_RX_3 159 +#define AIF_MI2S_TX_3 160 +#define AIF_MI2S_RX_4 161 +#define AIF_MI2S_TX_4 162 +#define AIF_MI2S_RX_5 163 +#define AIF_MI2S_TX_5 164 +#define AIF_MI2S_RX_6 165 +#define AIF_MI2S_TX_6 166 +#define AIF_MI2S_RX_7 167 +#define AIF_MI2S_TX_7 168 +#define AIF_MI2S_RX_8 169 +#define AIF_MI2S_TX_8 170 +#define AIF_MI2S_RX_9 171 +#define AIF_MI2S_TX_9 172 +#define AIF_MI2S_RX_10 173 +#define AIF_MI2S_TX_10 174 +#define AIF_MI2S_RX_11 175 +#define AIF_MI2S_TX_11 176 +#define AIF_MI2S_RX_12 177 +#define AIF_MI2S_TX_12 178 +#define AIF_TDM_RX_0 179 +#define AIF_TDM_TX_0 180 +#define AIF_TDM_RX_1 181 +#define AIF_TDM_TX_1 182 +#define AIF_TDM_RX_2 183 +#define AIF_TDM_TX_2 184 +#define AIF_TDM_RX_3 185 +#define AIF_TDM_TX_3 186 +#define AIF_TDM_RX_4 187 +#define AIF_TDM_TX_4 188 +#define AIF_TDM_RX_5 189 +#define AIF_TDM_TX_5 190 +#define AIF_TDM_RX_6 191 +#define AIF_TDM_TX_6 192 +#define AIF_TDM_RX_7 193 +#define AIF_TDM_TX_7 194 +#define AIF_TDM_RX_8 195 +#define AIF_TDM_TX_8 196 +#define AIF_TDM_RX_9 197 +#define AIF_TDM_TX_9 198 +#define AIF_TDM_RX_10 199 +#define AIF_TDM_TX_10 200 +#define AIF_TDM_RX_11 201 +#define AIF_TDM_TX_11 202 +#define AIF_TDM_RX_12 203 +#define AIF_TDM_TX_12 204 #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h index fe33a20abc61..f322e501ff62 100644 --- a/include/linux/dmaengine.h +++ b/include/linux/dmaengine.h @@ -1760,15 +1760,6 @@ void dma_run_dependencies(struct dma_async_tx_descriptor *tx); #define dma_request_channel(mask, x, y) \ __dma_request_channel(&(mask), x, y, NULL) -/* Deprecated, please use dma_request_chan() directly */ -static inline struct dma_chan * __deprecated -dma_request_slave_channel(struct device *dev, const char *name) -{ - struct dma_chan *ch = dma_request_chan(dev, name); - - return IS_ERR(ch) ? NULL : ch; -} - static inline struct dma_chan *dma_request_slave_channel_compat(const dma_cap_mask_t mask, dma_filter_fn fn, void *fn_param, diff --git a/include/linux/firmware/cirrus/cs_dsp.h b/include/linux/firmware/cirrus/cs_dsp.h index 4e3baa557068..6aa1e1b2b4a5 100644 --- a/include/linux/firmware/cirrus/cs_dsp.h +++ b/include/linux/firmware/cirrus/cs_dsp.h @@ -96,7 +96,7 @@ struct cs_dsp_alg_region { struct cs_dsp_coeff_ctl { struct list_head list; struct cs_dsp *dsp; - void *cache; + void *cache __counted_by_ptr(len); const char *fw_name; /* Subname is needed to match with firmware */ const char *subname; diff --git a/include/sound/aw88399.h b/include/sound/aw88399.h index a1e0de6be8ea..d72c491ee866 100644 --- a/include/sound/aw88399.h +++ b/include/sound/aw88399.h @@ -449,6 +449,11 @@ #define AW88399_NOISE_GATE_EN_MASK \ (~(((1< * - * Definitions of OSS compatible headers for Emu8000 device informations + * Definitions of OSS compatible headers for Emu8000 device information */ #include diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 1d05f991f2ce..c7f29ad64e02 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -28,6 +28,7 @@ struct hda_codec; struct hda_pcm; struct hda_pcm_stream; struct hda_codec_ops; +struct hda_jack_tbl; /* * codec bus @@ -274,7 +275,8 @@ struct hda_codec { struct hda_codec *codec, hda_nid_t nid); /* jack detection */ - struct snd_array jacktbl; + struct hda_jack_tbl *jacktbl, *jacktbl_last; /* linked list head/tail */ + int jacktbl_used; /* number of jacktbl elements */ unsigned long jackpoll_interval; /* In jiffies. Zero means no poll, rely on unsol events */ struct delayed_work jackpoll_work; @@ -519,7 +521,7 @@ void snd_hda_update_power_acct(struct hda_codec *codec); static inline bool hda_codec_need_resume(struct hda_codec *codec) { - return !codec->relaxed_resume && codec->jacktbl.used; + return !codec->relaxed_resume && codec->jacktbl_used; } /* diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index aa994d6e6d35..04fd4794bcee 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -96,7 +96,8 @@ struct hdac_device { bool lazy_cache:1; /* don't wake up for writes */ bool caps_overwriting:1; /* caps overwrite being in process */ bool cache_coef:1; /* cache COEF read/write too */ - unsigned int registered:1; /* codec was registered */ + bool registered; /* codec was registered */ + bool unsol_disabled; /* unsol events blocked; protected by bus->reg_lock */ }; /* device/driver type used for matching */ @@ -123,6 +124,7 @@ int snd_hdac_device_init(struct hdac_device *dev, struct hdac_bus *bus, const char *name, unsigned int addr); void snd_hdac_device_exit(struct hdac_device *dev); int snd_hdac_device_register(struct hdac_device *codec); +void snd_hdac_device_disable_unsol(struct hdac_device *codec); void snd_hdac_device_unregister(struct hdac_device *codec); int snd_hdac_device_set_chip_name(struct hdac_device *codec, const char *name); int snd_hdac_codec_modalias(const struct hdac_device *hdac, char *buf, size_t size); @@ -179,9 +181,9 @@ static inline int snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, #ifdef CONFIG_PM int snd_hdac_power_up(struct hdac_device *codec); -int snd_hdac_power_down(struct hdac_device *codec); +void snd_hdac_power_down(struct hdac_device *codec); int snd_hdac_power_up_pm(struct hdac_device *codec); -int snd_hdac_power_down_pm(struct hdac_device *codec); +void snd_hdac_power_down_pm(struct hdac_device *codec); int snd_hdac_keep_power_up(struct hdac_device *codec); /* call this at entering into suspend/resume callbacks in codec driver */ @@ -207,9 +209,9 @@ static inline bool snd_hdac_is_power_on(struct hdac_device *codec) } #else static inline int snd_hdac_power_up(struct hdac_device *codec) { return 0; } -static inline int snd_hdac_power_down(struct hdac_device *codec) { return 0; } +static inline void snd_hdac_power_down(struct hdac_device *codec) {} static inline int snd_hdac_power_up_pm(struct hdac_device *codec) { return 0; } -static inline int snd_hdac_power_down_pm(struct hdac_device *codec) { return 0; } +static inline void snd_hdac_power_down_pm(struct hdac_device *codec) {} static inline int snd_hdac_keep_power_up(struct hdac_device *codec) { return 0; } static inline void snd_hdac_enter_pm(struct hdac_device *codec) {} static inline void snd_hdac_leave_pm(struct hdac_device *codec) {} diff --git a/include/sound/jack.h b/include/sound/jack.h index 715b95983188..cf19c333e2eb 100644 --- a/include/sound/jack.h +++ b/include/sound/jack.h @@ -62,10 +62,10 @@ struct snd_jack { struct list_head kctl_list; struct snd_card *card; const char *id; + bool registered; #ifdef CONFIG_SND_JACK_INPUT_DEV struct input_dev *input_dev; struct mutex input_dev_lock; - int registered; int type; char name[100]; unsigned int key[6]; /* Keep in sync with definitions above */ diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 02d8689354c4..1cf511f8d880 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -8,6 +8,7 @@ * Abramo Bagnara */ +#include #include #include #include @@ -414,6 +415,7 @@ struct snd_pcm_runtime { snd_pcm_uframes_t twake; /* do transfer (!poll) wakeup if non-zero */ wait_queue_head_t sleep; /* poll sleep */ wait_queue_head_t tsleep; /* transfer sleep */ + struct snd_refcount refcnt; /* refcount for draining, etc */ struct snd_fasync *fasync; bool stop_operating; /* sync_stop will be called */ struct mutex buffer_mutex; /* protect for buffer changes */ diff --git a/include/sound/pcm_oss.h b/include/sound/pcm_oss.h index c96f23c7755b..43f765300031 100644 --- a/include/sound/pcm_oss.h +++ b/include/sound/pcm_oss.h @@ -70,6 +70,7 @@ struct snd_pcm_oss_stream { struct snd_pcm_oss { int reg; unsigned int reg_mask; + bool registered; }; #endif /* __SOUND_PCM_OSS_H */ diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 4154035af414..d812f70725bd 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -86,6 +86,7 @@ struct snd_rawmidi_substream { bool opened; /* open flag */ bool append; /* append flag (merge more streams) */ bool active_sensing; /* send active sensing when close */ + bool disconnected; /* already disconnected */ unsigned int framing; /* whether to frame input data */ unsigned int clock_type; /* clock source to use for input framing */ int use_count; /* use counter (for output) */ diff --git a/include/sound/sdca.h b/include/sound/sdca.h index 2bdf4e333e04..db2672b3aae2 100644 --- a/include/sound/sdca.h +++ b/include/sound/sdca.h @@ -60,20 +60,16 @@ enum sdca_quirk { SDCA_QUIRKS_SKIP_FUNC_TYPE_PATCHING, }; -#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA) +#if IS_ENABLED(CONFIG_SND_SOC_SDCA) -void sdca_lookup_functions(struct sdw_slave *slave); void sdca_lookup_swft(struct sdw_slave *slave); -void sdca_lookup_interface_revision(struct sdw_slave *slave); bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk); int sdca_dev_register_functions(struct sdw_slave *slave); void sdca_dev_unregister_functions(struct sdw_slave *slave); #else -static inline void sdca_lookup_functions(struct sdw_slave *slave) {} static inline void sdca_lookup_swft(struct sdw_slave *slave) {} -static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {} static inline bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk) { return false; @@ -88,4 +84,21 @@ static inline void sdca_dev_unregister_functions(struct sdw_slave *slave) {} #endif +/* + * Called from the SoundWire bus during peripheral enumeration; gated on + * ACPI to avoid a soundwire_bus <-> snd_soc_sdca module cycle on DT builds + * (where the bodies are stubs anyway). + */ +#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA) + +void sdca_lookup_functions(struct sdw_slave *slave); +void sdca_lookup_interface_revision(struct sdw_slave *slave); + +#else + +static inline void sdca_lookup_functions(struct sdw_slave *slave) {} +static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {} + +#endif + #endif diff --git a/include/sound/sdca_class.h b/include/sound/sdca_class.h new file mode 100644 index 000000000000..794a756472cd --- /dev/null +++ b/include/sound/sdca_class.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The MIPI SDCA specification is available for public downloads at + * https://www.mipi.org/mipi-sdca-v1-0-download + * + * Copyright (C) 2025 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef __SDCA_CLASS_H__ +#define __SDCA_CLASS_H__ + +#include +#include +#include + +struct device; +struct regmap; +struct sdw_slave; +struct sdca_function_data; + +/** + * struct sdca_class_ops - optional codec-provided class callbacks + * @populate_function: fill @function (entities, clusters, init_table, ...) + * from static tables in place of sdca_parse_function() on + * DT/non-DisCo platforms. Called once per SDCA function with + * @dev pointing at that function's device. Pure data source; + * performs no bus I/O. Return 0 on success or a negative errno. + */ +struct sdca_class_ops { + int (*populate_function)(struct device *dev, + struct sdca_function_data *function); +}; + +struct sdca_class_drv { + struct device *dev; + struct regmap *dev_regmap; + struct sdw_slave *sdw; + + struct sdca_interrupt_info *irq_info; + + const struct sdca_class_ops *ops; + + struct mutex regmap_lock; + /* Serialise function initialisations */ + struct mutex init_lock; + struct work_struct boot_work; +}; + +/* Library helpers used by codec-specific SDCA SoundWire drivers. */ +int sdca_class_probe(struct sdw_slave *sdw, + struct sdca_class_drv *drv, + const struct sdca_class_ops *ops); +void sdca_class_remove(struct sdca_class_drv *drv); + +/* + * PM helpers. Codec drivers embed sdca_class_drv in their own priv, + * own dev_set_drvdata(), and compose these into their own dev_pm_ops: + * + * static int wcd_runtime_suspend(struct device *dev) { + * struct wcd_priv *priv = dev_get_drvdata(dev); + * return sdca_class_runtime_suspend(&priv->class); + * } + */ +int sdca_class_runtime_suspend(struct sdca_class_drv *drv); +int sdca_class_runtime_resume(struct sdca_class_drv *drv); +int sdca_class_system_suspend(struct sdca_class_drv *drv); +int sdca_class_system_resume(struct sdca_class_drv *drv); + +#endif /* __SDCA_CLASS_H__ */ diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index bd8c3a033577..68faec56970e 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -219,12 +219,14 @@ static inline int simple_util_parse_aux_devs(struct simple_util_priv *priv, char int simple_util_init_jack(struct snd_soc_card *card, struct simple_util_jack *sjack, int is_hp, char *prefix, char *pin); +void simple_util_remove_jack(struct simple_util_jack *sjack); int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix); int simple_util_init_priv(struct simple_util_priv *priv, struct link_info *li); void simple_util_remove(struct platform_device *pdev); int graph_util_card_probe(struct snd_soc_card *card); +int graph_util_card_remove(struct snd_soc_card *card); int graph_util_is_ports0(struct device_node *port); int graph_util_parse_dai(struct simple_util_priv *priv, struct device_node *ep, struct snd_soc_dai_link_component *dlc, int *is_single_link); diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index c49b59630101..73d66c976694 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -154,6 +154,8 @@ struct snd_soc_component_driver { struct snd_pcm_substream *substream); snd_pcm_sframes_t (*delay)(struct snd_soc_component *component, struct snd_pcm_substream *substream); + int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params); const struct snd_compress_ops *compress_ops; @@ -194,12 +196,13 @@ struct snd_soc_component_driver { unsigned int endianness:1; unsigned int legacy_dai_naming:1; + /* use DAI link PCM ID as PCM device number */ + unsigned int use_dai_pcm_id:1; + /* this component uses topology and ignore machine driver FEs */ const char *ignore_machine; const char *topology_name_prefix; - int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd, - struct snd_pcm_hw_params *params); - bool use_dai_pcm_id; /* use DAI link PCM ID as PCM device number */ + int be_pcm_base; /* base device ID for all BE PCMs */ const char *debugfs_prefix; @@ -258,37 +261,24 @@ struct snd_soc_component { void *priv; }; -#define for_each_component_dais(component, dai)\ - list_for_each_entry(dai, &(component)->dai_list, list) -#define for_each_component_dais_safe(component, dai, _dai)\ - list_for_each_entry_safe(dai, _dai, &(component)->dai_list, list) +#define for_each_component_dais(component, dai) \ + for (dai = snd_soc_dai_from_list((component)->dai_list.next); \ + snd_soc_dai_to_list(dai) != &(component)->dai_list; \ + dai = snd_soc_dai_from_list(snd_soc_dai_to_list(dai)->next)) -/** - * snd_soc_component_to_dapm() - Returns the DAPM context associated with a - * component - * @component: The component for which to get the DAPM context - */ -static inline struct snd_soc_dapm_context *snd_soc_component_to_dapm( - struct snd_soc_component *component) -{ - return component->dapm; -} +#define for_each_component_dais_safe(component, dai, _dai) \ + for (dai = snd_soc_dai_from_list((component)->dai_list.next), \ + _dai = snd_soc_dai_from_list(snd_soc_dai_to_list(dai)->next); \ + snd_soc_dai_to_list(dai) != &(component)->dai_list; \ + dai = _dai, _dai = snd_soc_dai_from_list(snd_soc_dai_to_list(_dai)->next)) -/** - * snd_soc_component_cache_sync() - Sync the register cache with the hardware - * @component: COMPONENT to sync - * - * Note: This function will call regcache_sync() - */ -static inline int snd_soc_component_cache_sync( - struct snd_soc_component *component) -{ - return regcache_sync(component->regmap); -} +struct snd_soc_dapm_context *snd_soc_component_to_dapm(struct snd_soc_component *component); +struct snd_soc_component *snd_soc_component_new(struct device *dev, const char *name, + const struct snd_soc_component_driver *driver); +int snd_soc_component_register(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + int num_dai); -struct snd_soc_component *snd_soc_component_alloc(struct device *dev); - -void snd_soc_component_set_name(struct snd_soc_component *component, const char *name); const char *snd_soc_component_name(struct snd_soc_component *component); void snd_soc_component_set_priv(struct snd_soc_component *component, void *priv); @@ -299,6 +289,11 @@ void snd_soc_component_set_aux(struct snd_soc_component *component, int snd_soc_component_init(struct snd_soc_component *component); int snd_soc_component_is_dummy(struct snd_soc_component *component); +struct snd_soc_component *snd_soc_component_lookup_nolock(struct device *dev, + const char *driver_name); +struct snd_soc_component *snd_soc_component_lookup(struct device *dev, const char *driver_name); +struct snd_soc_component *snd_soc_component_lookup_by_name(const char *component_name); + /* component IO */ unsigned int snd_soc_component_read(struct snd_soc_component *component, unsigned int reg); @@ -310,7 +305,6 @@ int snd_soc_component_update_bits(struct snd_soc_component *component, int snd_soc_component_update_bits_async(struct snd_soc_component *component, unsigned int reg, unsigned int mask, unsigned int val); -void snd_soc_component_async_complete(struct snd_soc_component *component); int snd_soc_component_test_bits(struct snd_soc_component *component, unsigned int reg, unsigned int mask, unsigned int value); @@ -340,12 +334,18 @@ int snd_soc_component_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level); int snd_soc_component_regmap_val_bytes(struct snd_soc_component *component); +int snd_soc_component_regmap_cache_sync(struct snd_soc_component *component); +void snd_soc_component_regmap_async_complete(struct snd_soc_component *component); #ifdef CONFIG_REGMAP void snd_soc_component_init_regmap(struct snd_soc_component *component, struct regmap *regmap); void snd_soc_component_exit_regmap(struct snd_soc_component *component); #endif +int snd_soc_component_fixup_controls(struct snd_soc_component *component); +int snd_soc_component_add_controls(struct snd_soc_component *component, + const struct snd_kcontrol_new *controls, unsigned int num_controls); + #define snd_soc_component_module_get_when_probe(component)\ snd_soc_component_module_get(component, NULL, 0) #define snd_soc_component_module_get_when_open(component, substream) \ @@ -370,11 +370,7 @@ static inline void *snd_soc_component_get_drvdata(struct snd_soc_component *c) return dev_get_drvdata(c->dev); } -static inline unsigned int -snd_soc_component_active(struct snd_soc_component *component) -{ - return component->active; -} +unsigned int snd_soc_component_active(struct snd_soc_component *component); /* component controls */ struct snd_kcontrol *snd_soc_component_get_kcontrol(struct snd_soc_component *component, @@ -391,9 +387,6 @@ int snd_soc_component_close(struct snd_soc_component *component, void snd_soc_component_suspend(struct snd_soc_component *component); void snd_soc_component_resume(struct snd_soc_component *component); int snd_soc_component_is_suspended(struct snd_soc_component *component); -int snd_soc_component_probe(struct snd_soc_component *component); -int snd_soc_component_fixup_controls(struct snd_soc_component *component); -void snd_soc_component_remove(struct snd_soc_component *component); int snd_soc_component_of_xlate_dai_id(struct snd_soc_component *component, struct device_node *ep); int snd_soc_component_of_xlate_dai_name(struct snd_soc_component *component, @@ -423,7 +416,7 @@ int snd_soc_component_compr_set_metadata(struct snd_compr_stream *cstream, int snd_soc_component_compr_get_metadata(struct snd_compr_stream *cstream, struct snd_compr_metadata *metadata); -int snd_soc_pcm_component_pointer(struct snd_pcm_substream *substream); +snd_pcm_uframes_t snd_soc_pcm_component_pointer(struct snd_pcm_substream *substream); int snd_soc_pcm_component_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg); int snd_soc_pcm_component_sync_stop(struct snd_pcm_substream *substream); @@ -451,4 +444,11 @@ int snd_soc_pcm_component_ack(struct snd_pcm_substream *substream); void snd_soc_pcm_component_delay(struct snd_pcm_substream *substream, snd_pcm_sframes_t *cpu_delay, snd_pcm_sframes_t *codec_delay); +/* REMOVE ME */ +#define snd_soc_component_cache_sync snd_soc_component_regmap_cache_sync +#define snd_soc_component_async_complete snd_soc_component_regmap_async_complete +#define snd_soc_lookup_component_nolocked snd_soc_component_lookup_nolock +#define snd_soc_lookup_component snd_soc_component_lookup +#define snd_soc_lookup_component_by_name snd_soc_component_lookup_by_name + #endif /* __SOC_COMPONENT_H */ diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index ba3ae56c6b06..c044909ef10d 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -212,8 +212,11 @@ int snd_soc_dai_get_channel_map(const struct snd_soc_dai *dai, unsigned int *tx_num, unsigned int *tx_slot, unsigned int *rx_num, unsigned int *rx_slot); +struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai); +struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai); int snd_soc_dai_is_dummy(const struct snd_soc_dai *dai); - +int snd_soc_dai_add_controls(struct snd_soc_dai *dai, + const struct snd_kcontrol_new *controls, int num_controls); int snd_soc_dai_hw_params(struct snd_soc_dai *dai, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); @@ -228,18 +231,7 @@ void snd_soc_dai_suspend(struct snd_soc_dai *dai); void snd_soc_dai_resume(struct snd_soc_dai *dai); int snd_soc_dai_compress_new(struct snd_soc_dai *dai, struct snd_soc_pcm_runtime *rtd); bool snd_soc_dai_stream_valid(const struct snd_soc_dai *dai, int stream); -void snd_soc_dai_action(struct snd_soc_dai *dai, - int stream, int action); -static inline void snd_soc_dai_activate(struct snd_soc_dai *dai, - int stream) -{ - snd_soc_dai_action(dai, stream, 1); -} -static inline void snd_soc_dai_deactivate(struct snd_soc_dai *dai, - int stream) -{ - snd_soc_dai_action(dai, stream, -1); -} + int snd_soc_dai_active(const struct snd_soc_dai *dai); int snd_soc_pcm_dai_probe(struct snd_soc_pcm_runtime *rtd, int order); @@ -277,7 +269,12 @@ int snd_soc_dai_compr_get_metadata(struct snd_soc_dai *dai, struct snd_compr_stream *cstream, struct snd_compr_metadata *metadata); -const char *snd_soc_dai_name_get(const struct snd_soc_dai *dai); +int snd_soc_dai_matches_args(const struct snd_soc_dai *dai, + const struct of_phandle_args *args2); +int snd_soc_dai_matches_dlc(struct snd_soc_dai *dai, + const struct snd_soc_dai_link_component *dlc); +const char *snd_soc_dai_name(const struct snd_soc_dai *dai); +int snd_soc_dai_id(const struct snd_soc_dai *dai); struct snd_soc_dai_ops { /* DAI driver callbacks */ @@ -489,69 +486,51 @@ struct snd_soc_dai { void *priv; }; +const struct snd_soc_pcm_stream * +snd_soc_dai_pcm_stream_get_i(const struct snd_soc_dai *dai, int stream); static inline const struct snd_soc_pcm_stream * -snd_soc_dai_get_pcm_stream(const struct snd_soc_dai *dai, int stream) +snd_soc_dai_pcm_stream_get_s(const struct snd_soc_dai *dai, struct snd_pcm_substream *substream) { - return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? - &dai->driver->playback : &dai->driver->capture; + return snd_soc_dai_pcm_stream_get_i(dai, substream->stream); } +#define snd_soc_dai_pcm_stream_get(dai, x) _Generic((x), \ + int : snd_soc_dai_pcm_stream_get_i, \ + struct snd_pcm_substream * : snd_soc_dai_pcm_stream_get_s)(dai, x) -#define snd_soc_dai_get_widget_playback(dai) snd_soc_dai_get_widget(dai, SNDRV_PCM_STREAM_PLAYBACK) -#define snd_soc_dai_get_widget_capture(dai) snd_soc_dai_get_widget(dai, SNDRV_PCM_STREAM_CAPTURE) -static inline -struct snd_soc_dapm_widget *snd_soc_dai_get_widget(struct snd_soc_dai *dai, int stream) -{ - return dai->stream[stream].widget; -} +#define snd_soc_dai_stream_widget_get_playback(dai) snd_soc_dai_stream_widget_get(dai, SNDRV_PCM_STREAM_PLAYBACK) +#define snd_soc_dai_stream_widget_get_capture(dai) snd_soc_dai_stream_widget_get(dai, SNDRV_PCM_STREAM_CAPTURE) +struct snd_soc_dapm_widget *snd_soc_dai_stream_widget_get(struct snd_soc_dai *dai, int stream); -#define snd_soc_dai_set_widget_playback(dai, widget) snd_soc_dai_set_widget(dai, SNDRV_PCM_STREAM_PLAYBACK, widget) -#define snd_soc_dai_set_widget_capture(dai, widget) snd_soc_dai_set_widget(dai, SNDRV_PCM_STREAM_CAPTURE, widget) -static inline -void snd_soc_dai_set_widget(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget *widget) -{ - dai->stream[stream].widget = widget; -} +#define snd_soc_dai_stream_widget_set_playback(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_PLAYBACK, widget) +#define snd_soc_dai_stream_widget_set_capture(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_CAPTURE, widget) +void snd_soc_dai_stream_widget_set(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget *widget); -#define snd_soc_dai_dma_data_get_playback(dai) snd_soc_dai_dma_data_get(dai, SNDRV_PCM_STREAM_PLAYBACK) -#define snd_soc_dai_dma_data_get_capture(dai) snd_soc_dai_dma_data_get(dai, SNDRV_PCM_STREAM_CAPTURE) -#define snd_soc_dai_get_dma_data(dai, ss) snd_soc_dai_dma_data_get(dai, ss->stream) -static inline void *snd_soc_dai_dma_data_get(const struct snd_soc_dai *dai, int stream) +#define snd_soc_dai_stream_dma_data_get_playback(dai) snd_soc_dai_stream_dma_data_get(dai, SNDRV_PCM_STREAM_PLAYBACK) +#define snd_soc_dai_stream_dma_data_get_capture(dai) snd_soc_dai_stream_dma_data_get(dai, SNDRV_PCM_STREAM_CAPTURE) +void *snd_soc_dai_stream_dma_data_get_i(const struct snd_soc_dai *dai, int stream); +static inline void *snd_soc_dai_stream_dma_data_get_s(const struct snd_soc_dai *dai, struct snd_pcm_substream *substream) { - return dai->stream[stream].dma_data; + return snd_soc_dai_stream_dma_data_get_i(dai, substream->stream); } +#define snd_soc_dai_stream_dma_data_get(dai, x) _Generic((x), \ + int : snd_soc_dai_stream_dma_data_get_i, \ + struct snd_pcm_substream * : snd_soc_dai_stream_dma_data_get_s)(dai, x) -#define snd_soc_dai_dma_data_set_playback(dai, data) snd_soc_dai_dma_data_set(dai, SNDRV_PCM_STREAM_PLAYBACK, data) -#define snd_soc_dai_dma_data_set_capture(dai, data) snd_soc_dai_dma_data_set(dai, SNDRV_PCM_STREAM_CAPTURE, data) -#define snd_soc_dai_set_dma_data(dai, ss, data) snd_soc_dai_dma_data_set(dai, ss->stream, data) -static inline void snd_soc_dai_dma_data_set(struct snd_soc_dai *dai, int stream, void *data) +#define snd_soc_dai_stream_dma_data_set_playback(dai, data) snd_soc_dai_stream_dma_data_set(dai, SNDRV_PCM_STREAM_PLAYBACK, data) +#define snd_soc_dai_stream_dma_data_set_capture(dai, data) snd_soc_dai_stream_dma_data_set(dai, SNDRV_PCM_STREAM_CAPTURE, data) +void snd_soc_dai_stream_dma_data_set_i(struct snd_soc_dai *dai, int stream, void *data); +static inline void snd_soc_dai_stream_dma_data_set_s(struct snd_soc_dai *dai, struct snd_pcm_substream *substream, void *data) { - dai->stream[stream].dma_data = data; + snd_soc_dai_stream_dma_data_set_i(dai, substream->stream, data); } +#define snd_soc_dai_stream_dma_data_set(dai, x, data) _Generic((x), \ + int : snd_soc_dai_stream_dma_data_set_i, \ + struct snd_pcm_substream * : snd_soc_dai_stream_dma_data_set_s)(dai, x, data) -static inline void snd_soc_dai_init_dma_data(struct snd_soc_dai *dai, void *playback, void *capture) -{ - snd_soc_dai_dma_data_set_playback(dai, playback); - snd_soc_dai_dma_data_set_capture(dai, capture); -} +unsigned int snd_soc_dai_stream_tdm_mask_get(const struct snd_soc_dai *dai, int stream); +void snd_soc_dai_stream_tdm_mask_set(struct snd_soc_dai *dai, int stream, unsigned int tdm_mask); -static inline unsigned int snd_soc_dai_tdm_mask_get(const struct snd_soc_dai *dai, - int stream) -{ - return dai->stream[stream].tdm_mask; -} - -static inline void snd_soc_dai_tdm_mask_set(struct snd_soc_dai *dai, int stream, - unsigned int tdm_mask) -{ - dai->stream[stream].tdm_mask = tdm_mask; -} - -static inline unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, - int stream) -{ - /* see snd_soc_dai_action() for setup */ - return dai->stream[stream].active; -} +unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, int stream); static inline void snd_soc_dai_set_drvdata(struct snd_soc_dai *dai, void *data) @@ -564,45 +543,43 @@ static inline void *snd_soc_dai_get_drvdata(struct snd_soc_dai *dai) return dev_get_drvdata(dai->dev); } -/** - * snd_soc_dai_set_stream() - Configures a DAI for stream operation - * @dai: DAI - * @stream: STREAM (opaque structure depending on DAI type) - * @direction: Stream direction(Playback/Capture) - * Some subsystems, such as SoundWire, don't have a notion of direction and we reuse - * the ASoC stream direction to configure sink/source ports. - * Playback maps to source ports and Capture for sink ports. - * - * This should be invoked with NULL to clear the stream set previously. - * Returns 0 on success, a negative error code otherwise. - */ -static inline int snd_soc_dai_set_stream(struct snd_soc_dai *dai, - void *stream, int direction) -{ - if (dai->driver->ops->set_stream) - return dai->driver->ops->set_stream(dai, stream, direction); - else - return -ENOTSUPP; -} +int snd_soc_dai_set_stream(struct snd_soc_dai *dai, void *stream, int direction); +void *snd_soc_dai_get_stream(struct snd_soc_dai *dai, int direction); -/** - * snd_soc_dai_get_stream() - Retrieves stream from DAI - * @dai: DAI - * @direction: Stream direction(Playback/Capture) - * - * This routine only retrieves that was previously configured - * with snd_soc_dai_get_stream() - * - * Returns pointer to stream or an ERR_PTR value, e.g. - * ERR_PTR(-ENOTSUPP) if callback is not supported; - */ -static inline void *snd_soc_dai_get_stream(struct snd_soc_dai *dai, - int direction) +struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + bool legacy_dai_naming); +void snd_soc_dai_unregister(struct snd_soc_dai *dai); +struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list); +struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_symmetric_rate(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_symmetric_channels(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_symmetric_sample_bits(struct snd_soc_dai *dai); +void snd_soc_dai_set_priv(struct snd_soc_dai *dai, void *priv); +void *snd_soc_dai_to_priv(struct snd_soc_dai *dai); + +/* REMOVE ME */ +#define snd_soc_dai_get_pcm_stream snd_soc_dai_pcm_stream_get +#define snd_soc_dai_get_widget_playback(dai) snd_soc_dai_stream_widget_get(dai, SNDRV_PCM_STREAM_PLAYBACK) +#define snd_soc_dai_get_widget_capture(dai) snd_soc_dai_stream_widget_get(dai, SNDRV_PCM_STREAM_CAPTURE) +#define snd_soc_dai_get_widget snd_soc_dai_stream_widget_get +#define snd_soc_dai_set_widget_playback(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_PLAYBACK, widget) +#define snd_soc_dai_set_widget_capture(dai, widget) snd_soc_dai_stream_widget_set(dai, SNDRV_PCM_STREAM_CAPTURE, widget) +#define snd_soc_dai_set_widget snd_soc_dai_stream_widget_set +#define snd_soc_dai_dma_data_get_playback(dai) snd_soc_dai_stream_dma_data_get(dai, SNDRV_PCM_STREAM_PLAYBACK) +#define snd_soc_dai_dma_data_get_capture(dai) snd_soc_dai_stream_dma_data_get(dai, SNDRV_PCM_STREAM_CAPTURE) +#define snd_soc_dai_get_dma_data snd_soc_dai_stream_dma_data_get +#define snd_soc_dai_dma_data_get snd_soc_dai_stream_dma_data_get +#define snd_soc_dai_dma_data_set_playback(dai, data) snd_soc_dai_stream_dma_data_set(dai, SNDRV_PCM_STREAM_PLAYBACK, data) +#define snd_soc_dai_dma_data_set_capture(dai, data) snd_soc_dai_stream_dma_data_set(dai, SNDRV_PCM_STREAM_CAPTURE, data) +#define snd_soc_dai_set_dma_data snd_soc_dai_stream_dma_data_set +#define snd_soc_dai_dma_data_set snd_soc_dai_stream_dma_data_set +#define snd_soc_dai_tdm_mask_get snd_soc_dai_stream_tdm_mask_get +#define snd_soc_dai_tdm_mask_set snd_soc_dai_stream_tdm_mask_set +static inline void snd_soc_dai_init_dma_data(struct snd_soc_dai *dai, void *playback, void *capture) { - if (dai->driver->ops->get_stream) - return dai->driver->ops->get_stream(dai, direction); - else - return ERR_PTR(-ENOTSUPP); + snd_soc_dai_stream_dma_data_set_playback(dai, playback); + snd_soc_dai_stream_dma_data_set_capture(dai, capture); } #endif diff --git a/include/sound/soc.h b/include/sound/soc.h index 5afc34b147b5..f6b98324f346 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -447,15 +447,9 @@ static inline int snd_soc_resume(struct device *dev) } #endif int snd_soc_poweroff(struct device *dev); -int snd_soc_register_component_c(struct snd_soc_component *component, - const struct snd_soc_component_driver *component_driver, - struct snd_soc_dai_driver *dai_drv, int num_dai); -int snd_soc_register_component_d(struct device *dev, +int snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *component_driver, struct snd_soc_dai_driver *dai_drv, int num_dai); -#define snd_soc_register_component(x, ...) _Generic((x), \ -struct device * : snd_soc_register_component_d, \ -struct snd_soc_component * : snd_soc_register_component_c)(x, __VA_ARGS__) int devm_snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *component_driver, @@ -463,11 +457,6 @@ int devm_snd_soc_register_component(struct device *dev, #define snd_soc_unregister_component(dev) snd_soc_unregister_component_by_driver(dev, NULL) void snd_soc_unregister_component_by_driver(struct device *dev, const struct snd_soc_component_driver *component_driver); -struct snd_soc_component *snd_soc_lookup_component_nolocked(struct device *dev, - const char *driver_name); -struct snd_soc_component *snd_soc_lookup_component(struct device *dev, - const char *driver_name); -struct snd_soc_component *snd_soc_lookup_component_by_name(const char *component_name); int soc_new_pcm(struct snd_soc_pcm_runtime *rtd); #ifdef CONFIG_SND_SOC_COMPRESS @@ -551,12 +540,8 @@ 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_component_controls(struct snd_soc_component *component, - const struct snd_kcontrol_new *controls, unsigned int num_controls); int snd_soc_add_card_controls(struct snd_soc_card *soc_card, const struct snd_kcontrol_new *controls, int num_controls); -int snd_soc_add_dai_controls(struct snd_soc_dai *dai, - 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, @@ -1331,6 +1316,9 @@ int snd_soc_of_parse_tdm_slot(struct device_node *np, unsigned int *rx_mask, unsigned int *slots, unsigned int *slot_width); +int snd_soc_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name); void snd_soc_of_parse_node_prefix(struct device_node *np, struct snd_soc_codec_conf *codec_conf, struct device_node *of_node, @@ -1389,19 +1377,12 @@ void snd_soc_dlc_use_cpu_as_platform(struct snd_soc_dai_link_component *platform struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, const struct of_phandle_args *args); struct snd_soc_dai *snd_soc_get_dai_via_args(const struct of_phandle_args *dai_args); -struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component, - struct snd_soc_dai_driver *dai_drv, - bool legacy_dai_naming); -void snd_soc_unregister_dai(struct snd_soc_dai *dai); struct snd_soc_dai *snd_soc_find_dai( const struct snd_soc_dai_link_component *dlc); struct snd_soc_dai *snd_soc_find_dai_with_mutex( const struct snd_soc_dai_link_component *dlc); -void soc_pcm_set_dai_params(struct snd_soc_dai *dai, - struct snd_pcm_hw_params *params); - #include static inline @@ -1551,4 +1532,12 @@ static inline void _snd_soc_dpcm_mutex_assert_held_r(struct snd_soc_pcm_runtime #include #include +/* + * REMOVE ME + */ +#define snd_soc_add_dai_controls snd_soc_dai_add_controls +#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 + #endif diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h index 9fbb69b9052d..418466438ea1 100644 --- a/include/sound/soc_sdw_utils.h +++ b/include/sound/soc_sdw_utils.h @@ -292,4 +292,24 @@ int asoc_sdw_es9356_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai int asoc_sdw_es9356_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); int asoc_sdw_es9356_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai); +/* Senary SN624x SDCA */ +int asoc_sdw_senary_sdca_jack_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback); +int asoc_sdw_senary_sdca_jack_exit(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link); +int asoc_sdw_senary_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai); +int asoc_sdw_senary_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback); +int asoc_sdw_senary_amp_exit(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_link); +int asoc_sdw_senary_sdca_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai); +int asoc_sdw_senary_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_dai *dai); + #endif diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h index cea3d9453b84..c0216f4b14c2 100644 --- a/include/sound/sof/ipc4/header.h +++ b/include/sound/sof/ipc4/header.h @@ -436,12 +436,10 @@ enum sof_ipc4_fw_config_params { SOF_IPC4_FW_CFG_RESERVED, SOF_IPC4_FW_CFG_POWER_GATING_POLICY, SOF_IPC4_FW_CFG_ASSERT_MODE, - SOF_IPC4_FW_RESERVED1, - SOF_IPC4_FW_RESERVED2, - SOF_IPC4_FW_RESERVED3, - SOF_IPC4_FW_RESERVED4, - SOF_IPC4_FW_RESERVED5, - SOF_IPC4_FW_CONTEXT_SAVE + /* Reserved: 24 - 28 */ + SOF_IPC4_FW_CONTEXT_SAVE = 29, + /* Reserved: 30 - 34 */ + SOF_IPC4_FW_CFG_SOF_INFO = 35, }; struct sof_ipc4_fw_version { @@ -451,6 +449,14 @@ struct sof_ipc4_fw_version { uint16_t build; } __packed; +/* + * tuple based array for SOF specific information under SOF_IPC4_FW_CFG_SOF_INFO + * tuple of fw_config + */ +enum ipc4_fw_sof_info_params { + SOF_IPC4_SOF_CODEC_INFO, +}; + /* Payload data for SOF_IPC4_MOD_SET_DX */ struct sof_ipc4_dx_state_info { /* core(s) to apply the change */ @@ -641,9 +647,10 @@ struct sof_ipc4_notify_module_data { * The event_data contains the struct sof_ipc4_control_msg_payload of the control * which sent the notification. */ -#define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_MASK GENMASK(31, 16) +#define SOF_IPC4_NOTIFY_MODULE_EVENTID_SOF_MAGIC_MASK GENMASK(31, 16) #define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL 0xA15A0000 #define SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_PARAMID_MASK GENMASK(15, 0) +#define SOF_IPC4_NOTIFY_MODULE_EVENTID_COMPR_MAGIC_VAL 0xC0C00000 /* * Macros for creating struct sof_ipc4_module_init_ext_init payload @@ -706,7 +713,8 @@ struct sof_ipc4_module_init_ext_object { enum sof_ipc4_mod_init_ext_obj_id { SOF_IPC4_MOD_INIT_DATA_ID_INVALID = 0, SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, - SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA, + SOF_IPC4_MOD_INIT_DATA_ID_MODULE_DATA, + SOF_IPC4_MOD_INIT_DATA_ID_MAX = SOF_IPC4_MOD_INIT_DATA_ID_MODULE_DATA, }; /* DP module memory configuration data object for object array */ diff --git a/include/sound/tas2781-dsp.h b/include/sound/tas2781-dsp.h index e845687decf9..3380dfd526de 100644 --- a/include/sound/tas2781-dsp.h +++ b/include/sound/tas2781-dsp.h @@ -56,6 +56,10 @@ enum tasdevice_dsp_dev_idx { TASDEVICE_DSP_TAS_2781_QUAD, TASDEVICE_DSP_TAS_5825_MONO, TASDEVICE_DSP_TAS_5825_DUAL, + TASDEVICE_DSP_TAS_2573_MONO, + TASDEVICE_DSP_TAS_2573_DUAL, + TASDEVICE_DSP_TAS_2573_21, + TASDEVICE_DSP_TAS_2573_QUAD, TASDEVICE_DSP_TAS_MAX_DEVICE }; @@ -142,6 +146,7 @@ struct tasdevice_fw { struct tasdevice_calibration *calibrations; struct fct_param_address fct_par_addr; struct device *dev; + int calibration_config_id; }; enum tasdevice_fw_state { @@ -206,6 +211,14 @@ struct tasdevice_rca { * capture. */ int capture_profile_id; + /* + * Primarily designed for speaker calibration scenarios with special + * requirements. For regular use cases, when the default value -1 is + * set, calibration will directly reuse the current playback + * configuration; non-negative values can be customized for special + * calibration demands. + */ + int calibration_profile_id; /* * Since version 0x105, the keyword 'init' was introduced into the * profile, which is used for chip initialization, particularly to diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index b763da613769..f4275226366d 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -49,6 +49,13 @@ #define TASDEVICE_REG(book, page, reg) (((book * 256 * 128) + \ (page * 128)) + reg) +#define TAS_REG_ERR_LOG(dev, chn, reg, ret) \ + dev_err((dev), "%s, C%uBx%02xPx%02xRx%02x E=%d\n", \ + __func__, chn, TASDEVICE_BOOK_ID(reg), \ + TASDEVICE_PAGE_ID(reg), \ + TASDEVICE_PAGE_REG(reg), \ + (ret)) + /* Software Reset, compatble with new device (TAS5825). */ #define TASDEVICE_REG_SWRESET TASDEVICE_REG(0x0, 0x0, 0x01) #define TASDEVICE_REG_SWRESET_RESET BIT(0) @@ -91,6 +98,30 @@ /* prm_TE_1_Beta1 */ #define TAS2563_TE_DT_REG TASDEVICE_REG(0x00, 0x0f, 0x70) +#define TAS2573_FCT_INT_LATCH TASDEVICE_REG(0x00, 0x00, 0x67) +/* YM57 */ +#define TAS2573_FCT_OUTPUT_R0 TASDEVICE_REG(0x00, 0x65, 0x74) +/* YM58 */ +#define TAS2573_FCT_OUTPUT_R0_LOW TASDEVICE_REG(0x00, 0x65, 0x78) +/* YM59 */ +#define TAS2573_FCT_OUTPUT_INV_R0 TASDEVICE_REG(0x00, 0x65, 0x7C) +/* YM61 */ +#define TAS2573_FCT_OUTPUT_POWERTOT TASDEVICE_REG(0x00, 0x66, 0x0C) +/* YM64 */ +#define TAS2573_FCT_OUTPUT_F0 TASDEVICE_REG(0x00, 0x65, 0x18) +/* YM147 */ +#define TAS2573_FCT_STATUS_CTRL TASDEVICE_REG(0x00, 0x68, 0x74) +/* YM148 */ +#define TAS2573_FCT_STATUS_BINNING TASDEVICE_REG(0x00, 0x68, 0x78) +/* YM664 */ +#define TAS2573_RE_OUT TASDEVICE_REG(0x00, 0x7A, 0x18) +/* YM952 */ +#define TAS2573_SILENCE_DETECTED TASDEVICE_REG(0x00, 0x83, 0x60) +/* YM954 */ +#define TAS2573_OPEN_CIRCUIT TASDEVICE_REG(0x00, 0x83, 0x68) +/* YM955 */ +#define TAS2573_SHORTCKT TASDEVICE_REG(0x00, 0x83, 0x6C) + #define TAS2781_PRM_INT_MASK_REG TASDEVICE_REG(0x00, 0x00, 0x3b) #define TAS2781_PRM_CLK_CFG_REG TASDEVICE_REG(0x00, 0x00, 0x5c) #define TAS2781_PRM_RSVD_REG TASDEVICE_REG(0x00, 0x01, 0x19) diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index c11da9656e38..767c96273245 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -99,9 +99,10 @@ enum { SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */ SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */ + SNDRV_HWDEP_IFACE_CAIAQ, /* Native Instruments caiaq USB devices */ /* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_CAIAQ }; struct snd_hwdep_info { diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 7c397b1c9231..5e5e0f78f26d 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -42,6 +42,7 @@ #endif struct snd_compr_file { + struct list_head list; unsigned long caps; struct snd_compr_stream stream; }; @@ -75,11 +76,11 @@ static inline void snd_compr_task_free_all(struct snd_compr_stream *stream) { } static int snd_compr_open(struct inode *inode, struct file *f) { struct snd_compr *compr; - struct snd_compr_file *data; - struct snd_compr_runtime *runtime; + struct snd_compr_file *data = NULL; + struct snd_compr_runtime *runtime = NULL; enum snd_compr_direction dirn; int maj = imajor(inode); - int ret; + int ret = 0; if ((f->f_flags & O_ACCMODE) == O_WRONLY) dirn = SND_COMPRESS_PLAYBACK; @@ -101,17 +102,30 @@ static int snd_compr_open(struct inode *inode, struct file *f) return -ENODEV; } + ret = snd_card_file_add(compr->card, f); + if (ret < 0) { + snd_card_unref(compr->card); + return ret; + } + + if (!try_module_get(compr->card->module)) { + snd_card_file_remove(compr->card, f); + snd_card_unref(compr->card); + return -EFAULT; + } + if (dirn != compr->direction) { pr_err("this device doesn't support this direction\n"); - snd_card_unref(compr->card); - return -EINVAL; + ret = -EINVAL; + goto __error; } data = kzalloc_obj(*data); if (!data) { - snd_card_unref(compr->card); - return -ENOMEM; + ret = -ENOMEM; + goto __error; } + INIT_LIST_HEAD(&data->list); INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work); @@ -121,9 +135,8 @@ static int snd_compr_open(struct inode *inode, struct file *f) data->stream.device = compr; runtime = kzalloc_obj(*runtime); if (!runtime) { - kfree(data); - snd_card_unref(compr->card); - return -ENOMEM; + ret = -ENOMEM; + goto __error; } runtime->state = SNDRV_PCM_STATE_OPEN; init_waitqueue_head(&runtime->sleep); @@ -132,11 +145,18 @@ static int snd_compr_open(struct inode *inode, struct file *f) #endif data->stream.runtime = runtime; f->private_data = (void *)data; - scoped_guard(mutex, &compr->lock) + scoped_guard(mutex, &compr->lock) { ret = compr->ops->open(&data->stream); + if (!ret) + list_add_tail(&data->list, &compr->open_list); + } + +__error: if (ret) { kfree(runtime); kfree(data); + module_put(compr->card->module); + snd_card_file_remove(compr->card, f); } snd_card_unref(compr->card); return ret; @@ -146,17 +166,23 @@ static int snd_compr_free(struct inode *inode, struct file *f) { struct snd_compr_file *data = f->private_data; struct snd_compr_runtime *runtime = data->stream.runtime; + struct snd_compr *compr = data->stream.device; cancel_delayed_work_sync(&data->stream.error_work); - switch (runtime->state) { - case SNDRV_PCM_STATE_RUNNING: - case SNDRV_PCM_STATE_DRAINING: - case SNDRV_PCM_STATE_PAUSED: - data->stream.ops->trigger(&data->stream, SNDRV_PCM_TRIGGER_STOP); - break; - default: - break; + scoped_guard(mutex, &compr->lock) { + if (!list_empty(&data->list)) + list_del_init(&data->list); + + switch (runtime->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_PAUSED: + data->stream.ops->trigger(&data->stream, SNDRV_PCM_TRIGGER_STOP); + break; + default: + break; + } } snd_compr_task_free_all(&data->stream); @@ -164,8 +190,10 @@ static int snd_compr_free(struct inode *inode, struct file *f) data->stream.ops->free(&data->stream); if (!data->stream.runtime->dma_buffer_p) kfree(data->stream.runtime->buffer); + module_put(data->stream.device->card->module); kfree(data->stream.runtime); kfree(data); + snd_card_file_remove(compr->card, f); return 0; } @@ -669,13 +697,30 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) if (retval) return retval; - retval = snd_compr_allocate_buffer(stream, params); - if (retval) - return -ENOMEM; + if (stream->next_track) { + /* + * total_bytes_available/transferred still refer to the + * current buffer; its geometry must not change. + */ + if (params->buffer.fragment_size != stream->runtime->fragment_size || + params->buffer.fragments != stream->runtime->fragments) + return -EINVAL; + } else { + retval = snd_compr_allocate_buffer(stream, params); + if (retval) + return retval; + } retval = stream->ops->set_params(stream, params); - if (retval) + if (retval) { + /* the stream stays OPEN: drop the buffer of the rejected setup */ + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN && + !stream->runtime->dma_buffer_p) { + kfree(stream->runtime->buffer); + stream->runtime->buffer = NULL; + } return retval; + } if (stream->next_track) return retval; @@ -744,6 +789,9 @@ snd_compr_set_metadata(struct snd_compr_stream *stream, unsigned long arg) * we should allow parameter change only when stream has been * opened not in other cases */ + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata))) return -EFAULT; @@ -919,8 +967,10 @@ static int snd_compress_wait_for_drain(struct snd_compr_stream *stream) * It is expected that driver will notify the drain completion and then * stream will be moved to SETUP state, even if draining resulted in an * error. We can trigger next track after this. + * + * The state has been set to SNDRV_PCM_STATE_DRAINING by the caller, + * before the drain trigger was issued. */ - stream->runtime->state = SNDRV_PCM_STATE_DRAINING; mutex_unlock(&stream->device->lock); /* we wait for drain to complete here, drain can return when @@ -945,6 +995,7 @@ static int snd_compress_wait_for_drain(struct snd_compr_stream *stream) static int snd_compr_drain(struct snd_compr_stream *stream) { + snd_pcm_state_t state; int retval; switch (stream->runtime->state) { @@ -959,9 +1010,20 @@ static int snd_compr_drain(struct snd_compr_stream *stream) break; } + /* + * The state must be set to draining before the trigger is issued as the + * drain can be completed by the driver either from the trigger callback + * itself or from a different context as soon as the trigger is called. + * A snd_compr_drain_notify() arriving before the state is updated would + * be lost and the wait for the drain to finish would never be woken up. + */ + state = stream->runtime->state; + stream->runtime->state = SNDRV_PCM_STATE_DRAINING; + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_DRAIN); if (retval) { pr_debug("SND_COMPR_TRIGGER_DRAIN failed %d\n", retval); + stream->runtime->state = state; wake_up(&stream->runtime->sleep); return retval; } @@ -997,6 +1059,7 @@ static int snd_compr_next_track(struct snd_compr_stream *stream) static int snd_compr_partial_drain(struct snd_compr_stream *stream) { + snd_pcm_state_t state; int retval; switch (stream->runtime->state) { @@ -1020,9 +1083,16 @@ static int snd_compr_partial_drain(struct snd_compr_stream *stream) return -EPERM; stream->partial_drain = true; + + /* See the comment in snd_compr_drain() on the state handling */ + state = stream->runtime->state; + stream->runtime->state = SNDRV_PCM_STATE_DRAINING; + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_PARTIAL_DRAIN); if (retval) { pr_debug("Partial drain returned failure\n"); + stream->partial_drain = false; + stream->runtime->state = state; wake_up(&stream->runtime->sleep); return retval; } @@ -1136,7 +1206,9 @@ static int snd_compr_task_start_prepare(struct snd_compr_task_runtime *task, { if (task == NULL) return -EINVAL; - if (task->state >= SND_COMPRESS_TASK_STATE_FINISHED) + /* only an unqueued or already finished task may be (re)started */ + if (task->state != SND_COMPRESS_TASK_STATE_IDLE && + task->state != SND_COMPRESS_TASK_STATE_FINISHED) return -EBUSY; if (utask->input_size > task->input->size) return -EINVAL; @@ -1432,8 +1504,27 @@ static int snd_compress_dev_register(struct snd_device *device) static int snd_compress_dev_disconnect(struct snd_device *device) { struct snd_compr *compr; + struct snd_compr_file *data; compr = device->device_data; + scoped_guard(mutex, &compr->lock) { + list_for_each_entry(data, &compr->open_list, list) { + switch (data->stream.runtime->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_PAUSED: + data->stream.ops->trigger(&data->stream, + SNDRV_PCM_TRIGGER_STOP); + break; + default: + break; + } + + data->stream.runtime->state = SNDRV_PCM_STATE_DISCONNECTED; + wake_up(&data->stream.runtime->sleep); + } + } + snd_unregister_device(compr->dev); return 0; } @@ -1541,6 +1632,7 @@ int snd_compress_new(struct snd_card *card, int device, compr->device = device; compr->direction = dirn; mutex_init(&compr->lock); + INIT_LIST_HEAD(&compr->open_list); snd_compress_set_id(compr, id); diff --git a/sound/core/control.c b/sound/core/control.c index 4199342d4ffe..535ceba294ac 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -1802,6 +1802,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, alloc_size = compute_user_elem_size(private_size, count); guard(rwsem_write)(&card->controls_rwsem); + if (card->shutdown) + return -ENODEV; if (check_user_elem_overflow(card, alloc_size)) return -ENOMEM; diff --git a/sound/core/control_led.c b/sound/core/control_led.c index ec0e6c7ad657..501387287ae6 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -563,12 +563,43 @@ static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, si return count; } +/* + * snd_ctl_led_disconnect() removes these files with card->controls_rwsem + * held. so don't let the removal wait for a callback that takes it. + */ +static struct kernfs_node *snd_ctl_led_sysfs_enter(struct device *dev, + struct device_attribute *attr) +{ + struct kernfs_node *kn; + + if (!try_module_get(THIS_MODULE)) + return NULL; + kn = sysfs_break_active_protection(&dev->kobj, &attr->attr); + if (!kn) + module_put(THIS_MODULE); + return kn; +} + +static void snd_ctl_led_sysfs_exit(struct kernfs_node *kn) +{ + sysfs_unbreak_active_protection(kn); + module_put(THIS_MODULE); +} + static ssize_t attach_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); - return set_led_id(led_card, buf, count, true); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = set_led_id(led_card, buf, count, true); + snd_ctl_led_sysfs_exit(kn); + return ret; } static ssize_t detach_store(struct device *dev, @@ -576,7 +607,15 @@ static ssize_t detach_store(struct device *dev, const char *buf, size_t count) { struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); - return set_led_id(led_card, buf, count, false); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = set_led_id(led_card, buf, count, false); + snd_ctl_led_sysfs_exit(kn); + return ret; } static ssize_t reset_store(struct device *dev, @@ -594,10 +633,8 @@ static ssize_t reset_store(struct device *dev, return count; } -static ssize_t list_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t snd_ctl_led_list(struct snd_ctl_led_card *led_card, char *buf) { - struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); struct snd_ctl_led_ctl *lctl; size_t l = 0; struct snd_card *card __free(snd_card_unref) = @@ -620,6 +657,21 @@ static ssize_t list_show(struct device *dev, return l; } +static ssize_t list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = snd_ctl_led_list(led_card, buf); + snd_ctl_led_sysfs_exit(kn); + return ret; +} + static DEVICE_ATTR_WO(attach); static DEVICE_ATTR_WO(detach); static DEVICE_ATTR_WO(reset); diff --git a/sound/core/ctljack.c b/sound/core/ctljack.c index 6b6ab34fbde8..36700fda79f3 100644 --- a/sound/core/ctljack.c +++ b/sound/core/ctljack.c @@ -38,6 +38,7 @@ static int get_available_index(struct snd_card *card, const char *name) sid.iface = SNDRV_CTL_ELEM_IFACE_CARD; strscpy(sid.name, name, sizeof(sid.name)); + guard(rwsem_read)(&card->controls_rwsem); while (snd_ctl_find_id(card, &sid)) { sid.index++; /* reset numid; otherwise snd_ctl_find_id() hits this again */ diff --git a/sound/core/info.c b/sound/core/info.c index 3b8ccda04e1a..c4d57d0809ad 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -528,6 +528,8 @@ int snd_info_card_register(struct snd_card *card) void snd_info_card_id_change(struct snd_card *card) { guard(mutex)(&info_mutex); + if (card->shutdown) + return; if (card->proc_root_link) { proc_remove(card->proc_root_link); card->proc_root_link = NULL; @@ -555,7 +557,6 @@ void snd_info_card_disconnect(struct snd_card *card) if (card->proc_root) snd_info_clear_entries(card->proc_root); card->proc_root_link = NULL; - card->proc_root = NULL; } /* diff --git a/sound/core/init.c b/sound/core/init.c index 1bcb6a2e7550..d518cb838777 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -82,8 +82,24 @@ static int module_slot_match(struct module *module, int idx) } #if IS_ENABLED(CONFIG_SND_MIXER_OSS) -int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag); -EXPORT_SYMBOL(snd_mixer_oss_notify_callback); +static DEFINE_MUTEX(mixer_oss_notify_mutex); +static int (*mixer_oss_notify_callback)(struct snd_card *card, int cmd); + +void snd_mixer_oss_notify(struct snd_card *card, int cmd) +{ + guard(mutex)(&mixer_oss_notify_mutex); + if (mixer_oss_notify_callback) + mixer_oss_notify_callback(card, cmd); +} +EXPORT_SYMBOL(snd_mixer_oss_notify); + +void snd_mixer_oss_set_notify(int (*callback)(struct snd_card *, int)) +{ + /* Wait for callbacks, including a final module_put(), to return. */ + guard(mutex)(&mixer_oss_notify_mutex); + mixer_oss_notify_callback = callback; +} +EXPORT_SYMBOL(snd_mixer_oss_set_notify); #endif static int check_empty_slot(struct module *module, int slot) @@ -216,11 +232,13 @@ static void __snd_card_release(struct device *dev, void *data) * is added automatically. In that way, the resource disconnection is assured * at first, then released in the expected order. * - * If an error happens at the probe before snd_card_register() is called and - * there have been other devres resources, you'd need to free the card manually - * via snd_card_free() call in the error; otherwise it may lead to UAF due to - * devres call orders. You can use snd_card_free_on_error() helper for - * handling it more easily. + * Caveat: if an error happens at the probe before snd_card_register() is + * called and there have been other devres resources that are referred at the + * card-level destructor call, you'd still need to free the card manually; + * otherwise it may lead to UAF due to devres call orders. Instead of a manual + * snd_card_free() call, you can use the automatic clean-up for the card + * pointer via __free(snd_card_free), too. In this case, don't forget to do + * NULL-clear the card pointer before the successful return! * * Return: zero if successful, or a negative error code */ @@ -250,30 +268,6 @@ int snd_devm_card_new(struct device *parent, int idx, const char *xid, } EXPORT_SYMBOL_GPL(snd_devm_card_new); -/** - * snd_card_free_on_error - a small helper for handling devm probe errors - * @dev: the managed device object - * @ret: the return code from the probe callback - * - * This function handles the explicit snd_card_free() call at the error from - * the probe callback. It's just a small helper for simplifying the error - * handling with the managed devices. - * - * Return: zero if successful, or a negative error code - */ -int snd_card_free_on_error(struct device *dev, int ret) -{ - struct snd_card *card; - - if (!ret) - return 0; - card = devres_find(dev, __snd_card_release, NULL, NULL); - if (card) - snd_card_free(card); - return ret; -} -EXPORT_SYMBOL_GPL(snd_card_free_on_error); - static int snd_card_init(struct snd_card *card, struct device *parent, int idx, const char *xid, struct module *module, size_t extra_size) @@ -356,7 +350,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, err = snd_info_card_create(card); if (err < 0) { dev_err(parent, "unable to create card info\n"); - goto __error_ctl; + goto __error; } #ifdef CONFIG_SND_DEBUG @@ -365,16 +359,16 @@ static int snd_card_init(struct snd_card *card, struct device *parent, #endif #ifdef CONFIG_SND_CTL_DEBUG card->value_buf = kmalloc_obj(*card->value_buf); - if (!card->value_buf) - return -ENOMEM; + if (!card->value_buf) { + err = -ENOMEM; + goto __error; + } #endif return 0; - __error_ctl: - snd_device_free_all(card); __error: - put_device(&card->card_dev); - return err; + snd_card_free(card); + return err; } /** @@ -530,8 +524,7 @@ void snd_card_disconnect(struct snd_card *card) /* at this point, they cannot respond to any calls except release() */ #if IS_ENABLED(CONFIG_SND_MIXER_OSS) - if (snd_mixer_oss_notify_callback) - snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); #endif /* notify all devices that we are disconnected */ @@ -557,6 +550,8 @@ void snd_card_disconnect(struct snd_card *card) clear_bit(card->number, snd_cards_lock); } + /* order card->shutdown store against power_ref read */ + smp_mb(); snd_power_sync_ref(card); } EXPORT_SYMBOL(snd_card_disconnect); @@ -587,8 +582,7 @@ static int snd_card_do_free(struct snd_card *card) card->releasing = true; #if IS_ENABLED(CONFIG_SND_MIXER_OSS) - if (snd_mixer_oss_notify_callback) - snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_FREE); #endif snd_device_free_all(card); kfree(card->components); @@ -932,8 +926,7 @@ int snd_card_register(struct snd_card *card) return err; #if IS_ENABLED(CONFIG_SND_MIXER_OSS) - if (snd_mixer_oss_notify_callback) - snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_REGISTER); #endif return 0; } @@ -1174,12 +1167,14 @@ EXPORT_SYMBOL(snd_card_file_remove); int snd_power_ref_and_wait(struct snd_card *card) { snd_power_ref(card); - if (snd_power_get_state(card) == SNDRV_CTL_POWER_D0) - return 0; - wait_event_cmd(card->power_sleep, - card->shutdown || - snd_power_get_state(card) == SNDRV_CTL_POWER_D0, - snd_power_unref(card), snd_power_ref(card)); + /* order power_ref increment against card->shutdown read */ + smp_mb__after_atomic(); + if (snd_power_get_state(card) != SNDRV_CTL_POWER_D0) { + wait_event_cmd(card->power_sleep, + card->shutdown || + snd_power_get_state(card) == SNDRV_CTL_POWER_D0, + snd_power_unref(card), snd_power_ref(card)); + } if (card->shutdown) { snd_power_unref(card); return -ENODEV; diff --git a/sound/core/jack.c b/sound/core/jack.c index 0f04018181ac..af2dc2cfbe19 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -38,6 +38,7 @@ static const int jack_switch_types[SND_JACK_SWITCH_TYPES] = { }; #endif /* CONFIG_SND_JACK_INPUT_DEV */ +static void snd_jack_add_debugfs(struct snd_jack *jack); static void snd_jack_remove_debugfs(struct snd_jack *jack); static int snd_jack_dev_disconnect(struct snd_device *device) @@ -68,6 +69,9 @@ static int snd_jack_dev_free(struct snd_device *device) struct snd_card *card = device->card; struct snd_jack_kctl *jack_kctl, *tmp_jack_kctl; + /* Remove debugfs while the controls are still linked. */ + snd_jack_remove_debugfs(jack); + list_for_each_entry_safe(jack_kctl, tmp_jack_kctl, &jack->kctl_list, list) { list_del_init(&jack_kctl->list); snd_ctl_remove(card, jack_kctl->kctl); @@ -85,11 +89,10 @@ static int snd_jack_dev_free(struct snd_device *device) } #ifdef CONFIG_SND_JACK_INPUT_DEV -static int snd_jack_dev_register(struct snd_device *device) +static int snd_jack_input_dev_register(struct snd_card *card, + struct snd_jack *jack) { - struct snd_jack *jack = device->device_data; - struct snd_card *card = device->card; - int err, i; + int i; snprintf(jack->name, sizeof(jack->name), "%s %s", card->shortname, jack->id); @@ -117,14 +120,32 @@ static int snd_jack_dev_register(struct snd_device *device) input_set_capability(jack->input_dev, EV_KEY, jack->key[i]); } - err = input_register_device(jack->input_dev); - if (err == 0) - jack->registered = 1; - - return err; + return input_register_device(jack->input_dev); +} +#else +static inline int snd_jack_input_dev_register(struct snd_card *card, + struct snd_jack *jack) +{ + return 0; } #endif /* CONFIG_SND_JACK_INPUT_DEV */ +static int snd_jack_dev_register(struct snd_device *device) +{ + struct snd_card *card = device->card; + struct snd_jack *jack = device->device_data; + int err; + + err = snd_jack_input_dev_register(card, jack); + if (err < 0) + return err; + + snd_jack_add_debugfs(jack); + + jack->registered = true; + return 0; +} + #ifdef CONFIG_SND_JACK_INJECTION_DEBUG static void snd_jack_inject_report(struct snd_jack_kctl *jack_kctl, int status) { @@ -303,6 +324,7 @@ static ssize_t jack_type_read(struct file *file, } static const struct file_operations jack_type_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = jack_type_read, .llseek = default_llseek, @@ -310,6 +332,7 @@ static const struct file_operations jack_type_fops = { #endif static const struct file_operations sw_inject_enable_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = sw_inject_enable_read, .write = sw_inject_enable_write, @@ -317,24 +340,28 @@ static const struct file_operations sw_inject_enable_fops = { }; static const struct file_operations jackin_inject_fops = { + .owner = THIS_MODULE, .open = simple_open, .write = jackin_inject_write, .llseek = default_llseek, }; static const struct file_operations jack_kctl_id_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = jack_kctl_id_read, .llseek = default_llseek, }; static const struct file_operations jack_kctl_mask_bits_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = jack_kctl_mask_bits_read, .llseek = default_llseek, }; static const struct file_operations jack_kctl_status_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = jack_kctl_status_read, .llseek = default_llseek, @@ -346,6 +373,9 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, char *tname; int i; + if (jack_kctl->jack_debugfs_root) + return 0; + /* Don't create injection interface for Phantom jacks */ if (strstr(jack_kctl->kctl->id.name, "Phantom")) return 0; @@ -384,6 +414,14 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, return 0; } +static void snd_jack_add_debugfs(struct snd_jack *jack) +{ + struct snd_jack_kctl *jack_kctl; + + list_for_each_entry(jack_kctl, &jack->kctl_list, list) + snd_jack_debugfs_add_inject_node(jack, jack_kctl); +} + static void snd_jack_remove_debugfs(struct snd_jack *jack) { struct snd_jack_kctl *jack_kctl; @@ -400,6 +438,10 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, return 0; } +static void snd_jack_add_debugfs(struct snd_jack *jack) +{ +} + static void snd_jack_remove_debugfs(struct snd_jack *jack) { } @@ -420,7 +462,8 @@ static void snd_jack_kctl_add(struct snd_jack *jack, struct snd_jack_kctl *jack_ { jack_kctl->jack = jack; list_add_tail(&jack_kctl->list, &jack->kctl_list); - snd_jack_debugfs_add_inject_node(jack, jack_kctl); + if (jack->registered) + snd_jack_debugfs_add_inject_node(jack, jack_kctl); } static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const char *name, unsigned int mask) @@ -450,7 +493,7 @@ static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const cha return jack_kctl; error: - snd_ctl_free_one(kctl); + snd_ctl_remove(card, kctl); return NULL; } @@ -501,9 +544,7 @@ int snd_jack_new(struct snd_card *card, const char *id, int type, int err; static const struct snd_device_ops ops = { .dev_free = snd_jack_dev_free, -#ifdef CONFIG_SND_JACK_INPUT_DEV .dev_register = snd_jack_dev_register, -#endif /* CONFIG_SND_JACK_INPUT_DEV */ .dev_disconnect = snd_jack_dev_disconnect, }; diff --git a/sound/core/misc.c b/sound/core/misc.c index 066fb9ecdcdc..6fe199e76e83 100644 --- a/sound/core/misc.c +++ b/sound/core/misc.c @@ -183,11 +183,13 @@ void snd_refcount_init(struct snd_refcount *ref) { atomic_set(&ref->count, 0); init_waitqueue_head(&ref->waiter); + spin_lock_init(&ref->lock); } EXPORT_SYMBOL_GPL(snd_refcount_init); void snd_refcount_put(struct snd_refcount *ref) { + guard(spinlock_irqsave)(&ref->lock); if (atomic_dec_and_test(&ref->count)) wake_up(&ref->waiter); } @@ -195,6 +197,7 @@ EXPORT_SYMBOL_GPL(snd_refcount_put); void snd_refcount_sync(struct snd_refcount *ref) { - wait_event(ref->waiter, !atomic_read(&ref->count)); + guard(spinlock_irq)(&ref->lock); + wait_event_lock_irq(ref->waiter, !atomic_read(&ref->count), ref->lock); } EXPORT_SYMBOL_GPL(snd_refcount_sync); diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index c533d767c29a..a51bc68473e6 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -1272,6 +1272,7 @@ static void snd_mixer_oss_build(struct snd_mixer_oss *mixer) }; unsigned int idx; + guard(mutex)(&mixer->reg_mutex); for (idx = 0; idx < ARRAY_SIZE(table); idx++) snd_mixer_oss_build_input(mixer, &table[idx], 0, 0); if (mixer->mask_recsrc) { @@ -1308,13 +1309,22 @@ static int snd_mixer_oss_free1(void *private) static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) { struct snd_mixer_oss *mixer; + int err; if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) { - int idx, err; + int idx; + + /* Module loading and card registration can both notify us. */ + if (card->mixer_oss) + return 0; mixer = kzalloc_objs(*mixer, 2); if (mixer == NULL) return -ENOMEM; + if (!try_module_get(THIS_MODULE)) { + kfree(mixer); + return -ENODEV; + } mutex_init(&mixer->reg_mutex); err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, card, 0, @@ -1324,6 +1334,7 @@ static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) "unable to register OSS mixer device %i:%i\n", card->number, 0); kfree(mixer); + module_put(THIS_MODULE); return err; } mixer->oss_dev_alloc = 1; @@ -1357,7 +1368,10 @@ static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT) return 0; snd_mixer_oss_proc_done(mixer); - return snd_mixer_oss_free1(mixer); + err = snd_mixer_oss_free1(mixer); + if (!err) + module_put(THIS_MODULE); + return err; } return 0; } @@ -1366,12 +1380,12 @@ static int __init alsa_mixer_oss_init(void) { struct snd_card *card; int idx; - - snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler; + + snd_mixer_oss_set_notify(snd_mixer_oss_notify_handler); for (idx = 0; idx < SNDRV_CARDS; idx++) { card = snd_card_ref(idx); if (card) { - snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_REGISTER); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_REGISTER); snd_card_unref(card); } } @@ -1380,17 +1394,8 @@ static int __init alsa_mixer_oss_init(void) static void __exit alsa_mixer_oss_exit(void) { - struct snd_card *card; - int idx; - - snd_mixer_oss_notify_callback = NULL; - for (idx = 0; idx < SNDRV_CARDS; idx++) { - card = snd_card_ref(idx); - if (card) { - snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_FREE); - snd_card_unref(card); - } - } + /* Live adapters hold module references and prevent normal unload. */ + snd_mixer_oss_set_notify(NULL); } module_init(alsa_mixer_oss_init) diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index bd19328dca9e..e8df27d09b97 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -3151,6 +3151,12 @@ static void register_oss_dsp(struct snd_pcm *pcm, int index) static int snd_pcm_oss_register_minor(struct snd_pcm *pcm) { + if (pcm->oss.registered) + return 0; + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + pcm->oss.registered = true; + pcm->oss.reg = 0; if (dsp_map[pcm->card->number] == (int)pcm->device) { char name[128]; @@ -3205,8 +3211,14 @@ static int snd_pcm_oss_disconnect_minor(struct snd_pcm *pcm) static int snd_pcm_oss_unregister_minor(struct snd_pcm *pcm) { + /* BUILD-state and creation-error cleanup need not have registered us. */ + if (!pcm->oss.registered) + return 0; + snd_pcm_oss_disconnect_minor(pcm); snd_pcm_oss_proc_done(pcm); + pcm->oss.registered = false; + module_put(THIS_MODULE); return 0; } diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 41c2cab7a52c..1e4e0ecb3dbd 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -819,6 +819,9 @@ static void snd_pcm_free_stream(struct snd_pcm_str * pstr) /* free all proc files under the stream */ snd_pcm_stream_proc_done(pstr); + /* Drain channel-map callbacks before freeing the substreams. */ + free_chmap(pstr); + substream = pstr->substream; while (substream) { substream_next = substream->next; @@ -833,7 +836,6 @@ static void snd_pcm_free_stream(struct snd_pcm_str * pstr) kfree(setup); } #endif - free_chmap(pstr); if (pstr->substream_count) put_device(pstr->dev); } @@ -853,8 +855,10 @@ static int snd_pcm_free(struct snd_pcm *pcm) { if (!pcm) return 0; - if (!pcm->internal) - pcm_call_notify(pcm, n_unregister); + if (!pcm->internal) { + scoped_guard(mutex, ®ister_mutex) + pcm_call_notify(pcm, n_unregister); + } if (pcm->private_free) pcm->private_free(pcm); snd_pcm_lib_preallocate_free_for_all(pcm); @@ -957,6 +961,7 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, init_waitqueue_head(&runtime->sleep); init_waitqueue_head(&runtime->tsleep); + snd_refcount_init(&runtime->refcnt); __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_OPEN); mutex_init(&runtime->buffer_mutex); diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index 55ecf87586c4..c54cb07304ad 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -219,8 +219,8 @@ static int snd_pcm_status_user_compat64(struct snd_pcm_substream *substream, .audio_tstamp_data = status.audio_tstamp_data, .audio_tstamp_sec = status.audio_tstamp_sec, .audio_tstamp_nsec = status.audio_tstamp_nsec, - .driver_tstamp_sec = status.audio_tstamp_sec, - .driver_tstamp_nsec = status.audio_tstamp_nsec, + .driver_tstamp_sec = status.driver_tstamp_sec, + .driver_tstamp_nsec = status.driver_tstamp_nsec, .audio_tstamp_accuracy = status.audio_tstamp_accuracy, }; @@ -252,7 +252,7 @@ static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, return -EFAULT; if (refine) { - err = snd_pcm_hw_refine(substream, data); + err = snd_pcm_hw_refine_with_lock(substream, data); if (err < 0) return err; err = fixup_unreferenced_params(substream, data); diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 4d665b4148d7..bb16738bb591 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2009,8 +2009,8 @@ static int wait_for_avail(struct snd_pcm_substream *substream, case SNDRV_PCM_STATE_DRAINING: if (is_playback) err = -EPIPE; - else - avail = 0; /* indicate draining */ + else + avail = snd_pcm_avail(substream); goto _endloop; case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_SETUP: @@ -2208,13 +2208,17 @@ static int pcm_sanity_check(struct snd_pcm_substream *substream) return 0; } -static int pcm_accessible_state(struct snd_pcm_runtime *runtime) +static int pcm_accessible_state(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->state) { case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PAUSED: return 0; + case SNDRV_PCM_STATE_DRAINING: + return substream->stream == SNDRV_PCM_STREAM_CAPTURE ? 0 : -EBADFD; case SNDRV_PCM_STATE_XRUN: return -EPIPE; case SNDRV_PCM_STATE_SUSPENDED: @@ -2320,7 +2324,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, nonblock = !!(substream->f_flags & O_NONBLOCK); snd_pcm_stream_lock_irq(substream); - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; @@ -2348,7 +2352,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, if (!avail) { if (!is_playback && runtime->state == SNDRV_PCM_STATE_DRAINING) { - snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + snd_pcm_drain_done(substream); goto _end_unlock; } if (nonblock) { @@ -2388,7 +2392,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, atomic_dec(&runtime->buffer_accessing); if (err < 0) goto _end_unlock; - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; appl_ptr += frames; @@ -2496,6 +2500,7 @@ static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol, unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); struct snd_pcm_substream *substream; const struct snd_pcm_chmap_elem *map; + unsigned int channels; if (!info->chmap) return -EINVAL; @@ -2504,11 +2509,20 @@ static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol, return -ENODEV; memset(ucontrol->value.integer.value, 0, sizeof(long) * info->max_channels); - if (!substream->runtime) - return 0; /* no channels set */ + /* + * PCM disconnect takes open_mutex before removing controls. + * Use trylock to avoid a deadlock with controls_rwsem. + */ + scoped_cond_guard(mutex_try, return -EBUSY, + &info->pcm->open_mutex) { + if (!substream->runtime) + return 0; /* no channels set */ + channels = substream->runtime->channels; + } for (map = info->chmap; map->channels; map++) { int i; - if (map->channels == substream->runtime->channels && + + if (map->channels == channels && valid_chmap_channels(info, map->channels)) { for (i = 0; i < map->channels; i++) ucontrol->value.integer.value[i] = map->map[i]; diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 6d32c12fb79b..35152fae74ca 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -580,29 +580,6 @@ int snd_pcm_hw_refine(struct snd_pcm_substream *substream, } EXPORT_SYMBOL(snd_pcm_hw_refine); -static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params __user * _params) -{ - int err; - struct snd_pcm_hw_params *params __free(kfree) = - memdup_user(_params, sizeof(*params)); - - if (IS_ERR(params)) - return PTR_ERR(params); - - err = snd_pcm_hw_refine(substream, params); - if (err < 0) - return err; - - err = fixup_unreferenced_params(substream, params); - if (err < 0) - return err; - - if (copy_to_user(_params, params, sizeof(*params))) - return -EFAULT; - return 0; -} - static int period_to_usecs(struct snd_pcm_runtime *runtime) { int usecs; @@ -778,11 +755,48 @@ EXPORT_SYMBOL_GPL(snd_pcm_runtime_buffer_set_silence); #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)) + cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); +} + +static void pcm_qos_req_update(struct snd_pcm_substream *substream, int usecs) +{ + if (usecs < 0) { + pcm_qos_req_clear(substream); + return; + } + + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_update_request(&substream->latency_pm_qos_req, + usecs); + else + cpu_latency_qos_add_request(&substream->latency_pm_qos_req, + usecs); +} + +static int hw_params_state_check(struct snd_pcm_substream *substream) +{ + 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) && + atomic_read(&substream->mmap_count)) + return -EBADFD; + return 0; + default: + return -EBADFD; + } +} + static int snd_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime; - int err, usecs; + int err; unsigned int bits; snd_pcm_uframes_t frames; @@ -792,20 +806,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, err = snd_pcm_buffer_access_lock(runtime); if (err < 0) return err; - scoped_guard(pcm_stream_lock_irq, substream) { - switch (runtime->state) { - case SNDRV_PCM_STATE_OPEN: - case SNDRV_PCM_STATE_SETUP: - case SNDRV_PCM_STATE_PREPARED: - if (!is_oss_stream(substream) && - atomic_read(&substream->mmap_count)) - err = -EBADFD; - break; - default: - err = -EBADFD; - break; - } - } + err = hw_params_state_check(substream); if (err) goto unlock; @@ -889,12 +890,6 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, snd_pcm_timer_resolution_change(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP); - if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); - usecs = period_to_usecs(runtime); - if (usecs >= 0) - cpu_latency_qos_add_request(&substream->latency_pm_qos_req, - usecs); err = 0; _error: if (err) { @@ -932,6 +927,57 @@ static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream, return err; } +/* + * call snd_pcm_hw_refine() after buffer lock and sanity check; + * this one should be used for ioctls and other external paths instead of + * direct call of snd_pcm_hw_refine() + */ +static int snd_pcm_hw_refine_with_lock(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime; + int err; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + err = snd_pcm_buffer_access_lock(runtime); + if (err < 0) + return err; + err = hw_params_state_check(substream); + if (err) + goto unlock; + + err = snd_pcm_hw_refine(substream, params); + + unlock: + snd_pcm_buffer_access_unlock(runtime); + return err; +} + +static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params __user * _params) +{ + int err; + struct snd_pcm_hw_params *params __free(kfree) = + memdup_user(_params, sizeof(*params)); + + if (IS_ERR(params)) + return PTR_ERR(params); + + err = snd_pcm_hw_refine_with_lock(substream, params); + if (err < 0) + return err; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + return err; + + if (copy_to_user(_params, params, sizeof(*params))) + return -EFAULT; + return 0; +} + static int do_hw_free(struct snd_pcm_substream *substream) { int result = 0; @@ -971,7 +1017,7 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream) goto unlock; result = do_hw_free(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + pcm_qos_req_clear(substream); unlock: snd_pcm_buffer_access_unlock(runtime); return result; @@ -1191,8 +1237,8 @@ static int snd_pcm_status_user32(struct snd_pcm_substream *substream, .audio_tstamp_data = status64.audio_tstamp_data, .audio_tstamp_sec = status64.audio_tstamp_sec, .audio_tstamp_nsec = status64.audio_tstamp_nsec, - .driver_tstamp_sec = status64.audio_tstamp_sec, - .driver_tstamp_nsec = status64.audio_tstamp_nsec, + .driver_tstamp_sec = status64.driver_tstamp_sec, + .driver_tstamp_nsec = status64.driver_tstamp_nsec, .audio_tstamp_accuracy = status64.audio_tstamp_accuracy, }; @@ -2005,6 +2051,7 @@ static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, err = substream->ops->prepare(substream); if (err < 0) return err; + pcm_qos_req_update(substream, period_to_usecs(substream->runtime)); return snd_pcm_do_reset(substream, state); } @@ -2055,6 +2102,25 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream, (snd_pcm_state_t)f_flags); } +static int snd_pcm_update_linked_hw_ptr(struct snd_pcm_substream *substream) +{ + struct snd_pcm_substream *s; + + /* Keep group membership stable while locking each stream separately. */ + guard(rwsem_read)(&snd_pcm_link_rwsem); + snd_pcm_group_for_each_entry(s, substream) { + int err; + + guard(pcm_stream_lock_irq)(s); + if (!snd_pcm_running(s)) + continue; + err = snd_pcm_update_hw_ptr(s); + if (err < 0) + return err; + } + return 0; +} + /* * drain ioctl */ @@ -2164,7 +2230,13 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, /* resume pause */ if (runtime->state == SNDRV_PCM_STATE_PAUSED) snd_pcm_pause(substream, false); + snd_pcm_stream_unlock_irq(substream); + result = snd_pcm_update_linked_hw_ptr(substream); + if (result < 0) + return result; + + snd_pcm_stream_lock_irq(substream); /* pre-start/stop - all running streams are changed to DRAINING state */ result = snd_pcm_action(&snd_pcm_action_drain_init, substream, ACTION_ARG_IGNORE); @@ -2179,9 +2251,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, for (;;) { long tout; struct snd_pcm_runtime *to_check; - unsigned int drain_rate; - snd_pcm_uframes_t drain_bufsz; - bool drain_no_period_wakeup; if (signal_pending(current)) { result = -ERESTARTSYS; @@ -2196,45 +2265,35 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, runtime = s->runtime; if (runtime->state == SNDRV_PCM_STATE_DRAINING) { to_check = runtime; + /* pin the draining substream */ + snd_refcount_get(&to_check->refcnt); + + init_wait_entry(&wait, 0); + prepare_to_wait(&to_check->sleep, &wait, + TASK_INTERRUPTIBLE); + if (to_check->no_period_wakeup) { + tout = MAX_SCHEDULE_TIMEOUT; + } else { + tout = 100; + if (to_check->rate) { + long t = to_check->buffer_size * + 1100 / to_check->rate; + tout = max(t, tout); + } + tout = msecs_to_jiffies(tout); + } break; } } snd_pcm_group_unref(group, substream); if (!to_check) break; /* all drained */ - /* - * Cache the runtime fields needed after unlock. - * A concurrent close() on the linked stream may free - * its runtime via snd_pcm_detach_substream() once we - * release the stream lock below. - */ - drain_no_period_wakeup = to_check->no_period_wakeup; - drain_rate = to_check->rate; - drain_bufsz = to_check->buffer_size; - init_wait_entry(&wait, 0); - prepare_to_wait(&to_check->sleep, &wait, TASK_INTERRUPTIBLE); snd_pcm_stream_unlock_irq(substream); - if (drain_no_period_wakeup) - tout = MAX_SCHEDULE_TIMEOUT; - else { - tout = 100; - if (drain_rate) { - long t = drain_bufsz * 1100 / drain_rate; - tout = max(t, tout); - } - tout = msecs_to_jiffies(tout); - } tout = schedule_timeout(tout); - snd_pcm_stream_lock_irq(substream); - group = snd_pcm_stream_group_ref(substream); - snd_pcm_group_for_each_entry(s, substream) { - if (s->runtime == to_check) { - finish_wait(&to_check->sleep, &wait); - break; - } - } - snd_pcm_group_unref(group, substream); + finish_wait(&to_check->sleep, &wait); + /* unpin the draining substream again now */ + snd_refcount_put(&to_check->refcnt); if (card->shutdown) { result = -ENODEV; @@ -2267,23 +2326,26 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, static int snd_pcm_drop(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime; - int result = 0; + int result; if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; runtime = substream->runtime; - if (runtime->state == SNDRV_PCM_STATE_OPEN || - runtime->state == SNDRV_PCM_STATE_DISCONNECTED) - return -EBADFD; + scoped_guard(pcm_stream_lock_irq, substream) { + if (runtime->state == SNDRV_PCM_STATE_OPEN || + runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; - guard(pcm_stream_lock_irq)(substream); - /* resume pause */ - if (runtime->state == SNDRV_PCM_STATE_PAUSED) - snd_pcm_pause(substream, false); + /* resume pause */ + if (runtime->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, false); - snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); - /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + result = snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + } + + snd_pcm_sync_stop(substream, true); return result; } @@ -2776,27 +2838,23 @@ static int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream) return 0; } -static void pcm_release_private(struct snd_pcm_substream *substream) -{ - if (snd_pcm_stream_linked(substream)) - snd_pcm_unlink(substream); -} - void snd_pcm_release_substream(struct snd_pcm_substream *substream) { substream->ref_count--; if (substream->ref_count > 0) return; + snd_pcm_unlink(substream); + snd_pcm_drop(substream); if (substream->hw_opened) { + snd_refcount_sync(&substream->runtime->refcnt); if (substream->runtime->state != SNDRV_PCM_STATE_OPEN) do_hw_free(substream); substream->ops->close(substream); substream->hw_opened = 0; } - if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + pcm_qos_req_clear(substream); if (substream->pcm_release) { substream->pcm_release(substream); substream->pcm_release = NULL; @@ -2872,8 +2930,6 @@ static int snd_pcm_open_file(struct file *file, return -ENOMEM; } pcm_file->substream = substream; - if (substream->ref_count == 1) - substream->pcm_release = pcm_release_private; file->private_data = pcm_file; return 0; @@ -2930,7 +2986,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) mutex_lock(&pcm->open_mutex); while (1) { err = snd_pcm_open_file(file, pcm, stream); - if (err >= 0) + if (!err) break; if (err == -EAGAIN) { if (file->f_flags & O_NONBLOCK) { @@ -2999,7 +3055,7 @@ static int do_pcm_hwsync(struct snd_pcm_substream *substream) switch (substream->runtime->state) { case SNDRV_PCM_STATE_DRAINING: if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) - return -EBADFD; + return 0; fallthrough; case SNDRV_PCM_STATE_RUNNING: return snd_pcm_update_hw_ptr(substream); @@ -3063,11 +3119,15 @@ static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream, static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, snd_pcm_uframes_t frames) { + struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) return 0; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { ret = do_pcm_hwsync(substream); if (!ret) @@ -3076,17 +3136,22 @@ static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, } if (ret >= 0) snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + atomic_dec(&runtime->buffer_accessing); return ret; } static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, snd_pcm_uframes_t frames) { + struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) return 0; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { ret = do_pcm_hwsync(substream); if (!ret) @@ -3095,20 +3160,26 @@ static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, } if (ret >= 0) snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + atomic_dec(&runtime->buffer_accessing); return ret; } static int snd_pcm_delay(struct snd_pcm_substream *substream, snd_pcm_sframes_t *delay) { + struct snd_pcm_runtime *runtime = substream->runtime; int err; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { err = do_pcm_hwsync(substream); if (delay && !err) *delay = snd_pcm_calc_delay(substream); } snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); + atomic_dec(&runtime->buffer_accessing); return err; } @@ -4188,7 +4259,7 @@ static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, if (IS_ERR(oparams)) return PTR_ERR(oparams); snd_pcm_hw_convert_from_old_params(params, oparams); - err = snd_pcm_hw_refine(substream, params); + err = snd_pcm_hw_refine_with_lock(substream, params); if (err < 0) return err; diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 2617bb5b4faf..630d9fc8e9da 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -36,6 +36,13 @@ module_param_array(amidi_map, int, NULL, 0444); MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device."); #endif /* CONFIG_SND_OSSEMUL */ +/* upper bound for draining the output buffer */ +#define SNDRV_RAWMIDI_DRAIN_TIMEOUT (10 * HZ) +/* interval at which drain progress is re-checked */ +#define SNDRV_RAWMIDI_DRAIN_POLL (HZ / 5) +/* polls without progress before the drain is considered stalled */ +#define SNDRV_RAWMIDI_DRAIN_STALLS 5 + static int snd_rawmidi_dev_free(struct snd_device *device); static int snd_rawmidi_dev_register(struct snd_device *device); static int snd_rawmidi_dev_disconnect(struct snd_device *device); @@ -208,14 +215,14 @@ static int snd_rawmidi_runtime_free(struct snd_rawmidi_substream *substream) static inline void snd_rawmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) { - if (!substream->opened) + if (!substream->opened || substream->disconnected) return; substream->ops->trigger(substream, up); } static void snd_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) { - if (!substream->opened) + if (!substream->opened || substream->disconnected) return; substream->ops->trigger(substream, up); if (!up) @@ -246,31 +253,74 @@ int snd_rawmidi_drop_output(struct snd_rawmidi_substream *substream) } EXPORT_SYMBOL(snd_rawmidi_drop_output); +static bool output_drained(struct snd_rawmidi_runtime *runtime) +{ + return runtime->avail >= runtime->buffer_size; +} + int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) { - int err = 0; - long timeout; struct snd_rawmidi_runtime *runtime; + size_t avail, prev_avail; + unsigned int stalls = 0; + unsigned long deadline; + long timeout, wait; + bool done; + int err = 0; scoped_guard(spinlock_irq, &substream->lock) { runtime = substream->runtime; - if (!substream->opened || !runtime || !runtime->buffer) + if (!substream->opened || !runtime || !runtime->buffer || + substream->disconnected) return -EINVAL; snd_rawmidi_buffer_ref(runtime); runtime->drain = 1; + prev_avail = runtime->avail; } - timeout = wait_event_interruptible_timeout(runtime->sleep, - (runtime->avail >= runtime->buffer_size), - 10*HZ); + /* + * Wait for the device to consume the buffer. Rather than always + * sleeping for the whole timeout, sample the free space and stop + * early once it has not increased for a second. A substream that + * is still making progress is given as long as it needs, within the + * same overall limit as before. + */ + deadline = jiffies + SNDRV_RAWMIDI_DRAIN_TIMEOUT; + for (;;) { + /* signed difference, so this is safe across a jiffies wrap */ + wait = (long)(deadline - jiffies); + if (wait <= 0) { + timeout = 0; + break; + } + wait = min_t(long, SNDRV_RAWMIDI_DRAIN_POLL, wait); + timeout = wait_event_interruptible_timeout(runtime->sleep, + output_drained(runtime), wait); + scoped_guard(spinlock_irq, &substream->lock) { + avail = runtime->avail; + done = output_drained(runtime); + } + if (done || signal_pending(current)) + break; + if (avail == prev_avail) { + if (++stalls >= SNDRV_RAWMIDI_DRAIN_STALLS) { + timeout = 0; + break; + } + } else { + stalls = 0; + prev_avail = avail; + } + } scoped_guard(spinlock_irq, &substream->lock) { if (signal_pending(current)) err = -ERESTARTSYS; if (runtime->avail < runtime->buffer_size && !timeout) { - rmidi_warn(substream->rmidi, - "rawmidi drain error (avail = %li, buffer_size = %li)\n", - (long)runtime->avail, (long)runtime->buffer_size); + dev_warn_ratelimited(substream->rmidi->dev, + "rawmidi drain error (avail = %li, buffer_size = %li)\n", + (long)runtime->avail, + (long)runtime->buffer_size); err = -EIO; } runtime->drain = 0; @@ -537,7 +587,7 @@ static void close_substream(struct snd_rawmidi *rmidi, if (--substream->use_count) return; - if (cleanup) { + if (cleanup && !substream->disconnected) { if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) snd_rawmidi_input_trigger(substream, 0); else { @@ -557,6 +607,9 @@ static void close_substream(struct snd_rawmidi *rmidi, substream->opened = 0; substream->append = 0; } + /* Drain input work queued after the earlier cancellation. */ + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + cancel_work_sync(&substream->runtime->event_work); substream->ops->close(substream); if (substream->runtime->private_free) substream->runtime->private_free(substream); @@ -1151,7 +1204,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, struct snd_rawmidi_runtime *runtime; guard(spinlock_irqsave)(&substream->lock); - if (!substream->opened) + if (!substream->opened || substream->disconnected) return -EBADFD; runtime = substream->runtime; if (!runtime || !runtime->buffer) { @@ -2063,8 +2116,13 @@ static int snd_rawmidi_dev_disconnect(struct snd_device *device) struct snd_rawmidi_substream *s; list_for_each_entry(s, &rmidi->streams[dir].substreams, list) { - if (s->runtime) + scoped_guard(spinlock_irq, &s->lock) + s->disconnected = true; + if (s->runtime) { + s->ops->trigger(s, 0); + cancel_work_sync(&s->runtime->event_work); wake_up(&s->runtime->sleep); + } } } diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index e4f58cb985d4..0d606159103e 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -27,14 +27,17 @@ config SND_SEQ_DUMMY will be called snd-seq-dummy. config SND_SEQUENCER_OSS - tristate "OSS Sequencer API" + tristate "OSS Sequencer API (deprecated)" depends on SND_OSSEMUL select SND_SEQ_MIDI_EVENT help Say Y here to enable OSS sequencer emulation (both /dev/sequencer and /dev/music interfaces). - Many programs still use the OSS API, so say Y. + The OSS sequencer API is almost nowhere used, hence this option will + be dropped soon. + Use user-space wrapper (alsa-oss) or CUSE daemon (oss-seq-emu) + instead of the kernel-side emulation. To compile this driver as a module, choose M here: the module will be called snd-seq-oss. diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c index 2835576040ed..64a7e62029cc 100644 --- a/sound/core/seq/oss/seq_oss.c +++ b/sound/core/seq/oss/seq_oss.c @@ -67,6 +67,9 @@ static int __init alsa_seq_oss_init(void) { int rc; + pr_warn("seq-oss: kernel OSS sequencer emulation is deprecated\n"); + pr_warn("seq-oss: Use user-space solution (alsa-oss or oss-seq-emu) instead.\n"); + rc = register_device(); if (rc < 0) goto error; diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h index 935cf3df0b30..571186db4fa5 100644 --- a/sound/core/seq/oss/seq_oss_device.h +++ b/sound/core/seq/oss/seq_oss_device.h @@ -72,6 +72,7 @@ struct seq_oss_devinfo { int cseq; /* sequencer client number */ int port; /* sequencer port number */ int queue; /* sequencer queue number */ + bool initialized; struct snd_seq_addr addr; /* address of this device */ diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c index 122735862044..cf493f6c983b 100644 --- a/sound/core/seq/oss/seq_oss_event.c +++ b/sound/core/seq/oss/seq_oss_event.c @@ -425,6 +425,9 @@ snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data; union evrec *rec; + if (!dp || !smp_load_acquire(&dp->initialized)) + return 0; /* ignored */ + if (ev->type != SNDRV_SEQ_EVENT_ECHO) return snd_seq_oss_midi_input(ev, direct, private_data); @@ -433,8 +436,12 @@ snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data rec = (union evrec*)&ev->data; if (rec->s.code == SEQ_SYNCTIMER) { + struct seq_oss_writeq *writeq = READ_ONCE(dp->writeq); + + if (!writeq) + return 0; /* sync echo back */ - snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time); + snd_seq_oss_writeq_wakeup(writeq, rec->t.time); } else { /* echo back event */ diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c index 1aece46c8b06..6474a76a3cce 100644 --- a/sound/core/seq/oss/seq_oss_init.c +++ b/sound/core/seq/oss/seq_oss_init.c @@ -258,6 +258,7 @@ snd_seq_oss_open(struct file *file, int level) client_table[dp->index] = dp; num_clients++; + smp_store_release(&dp->initialized, true); return 0; @@ -376,7 +377,7 @@ delete_seq_queue(int queue) /* - * free device informations - private_free callback of port + * free device information - private_free callback of port */ static void free_devinfo(void *private) @@ -403,6 +404,7 @@ snd_seq_oss_release(struct seq_oss_devinfo *dp) client_table[dp->index] = NULL; num_clients--; + smp_store_release(&dp->initialized, false); snd_seq_oss_reset(dp); diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c index 70f94df65144..b456bd37d87b 100644 --- a/sound/core/seq/oss/seq_oss_midi.c +++ b/sound/core/seq/oss/seq_oss_midi.c @@ -211,6 +211,11 @@ snd_seq_oss_midi_check_exit_port(int client, int port) mdev = find_slot(client, port); if (mdev) { scoped_guard(spinlock_irqsave, ®ister_lock) { + /* Only one callback may release the device. */ + if (midi_devs[mdev->seq_device] != mdev) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } midi_devs[mdev->seq_device] = NULL; } snd_use_lock_free(&mdev->use_lock); diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 239809ce48d7..0711a9e23b5f 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -95,23 +95,18 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) return snd_seq_total_cells(client->pool) > 0; } -/* return pointer to client structure for specified id; call under RCU read-lock */ -static struct snd_seq_client *__clientptr(int clientid) +/* return pointer to client structure for specified id; + * the caller must guarantee the client's lifetime by itself, as neither RCU + * nor a use_lock reference is taken here. + */ +static struct snd_seq_client *clientptr(int clientid) { if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", clientid); return NULL; } - return rcu_dereference_check(clienttab[clientid], - lockdep_is_held(&clients_lock)); -} - -/* return pointer to client structure for specified id */ -static struct snd_seq_client *clientptr(int clientid) -{ - guard(rcu)(); - return __clientptr(clientid); + return rcu_dereference_protected(clienttab[clientid], true); } static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) @@ -124,7 +119,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) return NULL; } scoped_guard(rcu) { - client = __clientptr(clientid); + client = rcu_dereference(clienttab[clientid]); if (client) return snd_seq_client_ref(client); if (clienttablock[clientid]) @@ -159,7 +154,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) } } scoped_guard(rcu) { - client = __clientptr(clientid); + client = rcu_dereference(clienttab[clientid]); if (client) return snd_seq_client_ref(client); } @@ -485,11 +480,11 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, if (err < 0) { if (cell) snd_seq_fifo_cell_putback(fifo, cell); - if (err == -EAGAIN && result > 0) - err = 0; } - return (err < 0) ? err : result; + if (result > 0) + return result; + return err < 0 ? err : 0; } diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index 80110501da6f..57aaa39afb9d 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -25,7 +25,9 @@ struct snd_seq_port_info32 { u32 kernel; /* reserved for kernel use (must be NULL) */ u32 flags; /* misc. conditioning */ unsigned char time_queue; /* queue # for timestamping */ - char reserved[59]; /* for future use */ + unsigned char direction; /* port usage direction (r/w/bidir) */ + unsigned char ump_group; /* 0 = UMP EP (no conversion), 1-16 = UMP group number */ + char reserved[57]; /* for future use */ }; static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned int cmd, @@ -40,7 +42,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned if (copy_from_user(data, data32, sizeof(*data32)) || get_user(data->flags, &data32->flags) || - get_user(data->time_queue, &data32->time_queue)) + get_user(data->time_queue, &data32->time_queue) || + get_user(data->direction, &data32->direction) || + get_user(data->ump_group, &data32->ump_group)) return -EFAULT; data->kernel = NULL; @@ -50,9 +54,12 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned if (err < 0) return err; - if (copy_to_user(data32, data, sizeof(*data32)) || + if (copy_to_user(data32, data, + offsetof(struct snd_seq_port_info32, kernel)) || put_user(data->flags, &data32->flags) || - put_user(data->time_queue, &data32->time_queue)) + put_user(data->time_queue, &data32->time_queue) || + put_user(data->direction, &data32->direction) || + put_user(data->ump_group, &data32->ump_group)) return -EFAULT; return err; diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index eb67eb0eeb14..b582d034437f 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -248,17 +248,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, } /* delete port data */ -static int port_delete(struct snd_seq_client *client, - struct snd_seq_client_port *port) +static void port_free(struct snd_seq_client *client, + struct snd_seq_client_port *port) { - /* set closing flag and wait for all port access are gone */ - port->closing = 1; - /* the port has already been unlinked from the client's port list; - * wait for a grace period so that RCU readers still traversing the - * list can no longer take a new use_lock reference, then drain the - * outstanding references before freeing - */ - synchronize_rcu(); snd_use_lock_sync(&port->use_lock); /* clear subscribers info */ @@ -272,6 +264,20 @@ static int port_delete(struct snd_seq_client *client, snd_BUG_ON(port->c_dest.count != 0); kfree(port); +} + +static int port_delete(struct snd_seq_client *client, + struct snd_seq_client_port *port) +{ + /* set closing flag and wait for all port access are gone */ + port->closing = 1; + /* the port has already been unlinked from the client's port list; + * wait for a grace period so that RCU readers still traversing the + * list can no longer take a new use_lock reference, then drain the + * outstanding references before freeing + */ + synchronize_rcu(); + port_free(client, port); return 0; } @@ -302,18 +308,25 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) int snd_seq_delete_all_ports(struct snd_seq_client *client) { struct snd_seq_client_port *port, *tmp; + LIST_HEAD(deleted); - /* unlink and delete each port; port_delete() waits for an RCU grace - * period before draining the port, so concurrent lockless readers can - * no longer take a new use_lock reference on it + /* unlink all ports first and wait for a single RCU grace period + * before draining and freeing them, so concurrent lockless readers + * can no longer take a new use_lock reference on any of them */ guard(mutex)(&client->ports_mutex); list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { list_del_rcu(&port->list); client->num_ports--; + port->closing = 1; + list_add_tail(&port->delete_list, &deleted); snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); - port_delete(client, port); } + if (list_empty(&deleted)) + return 0; + synchronize_rcu(); + list_for_each_entry_safe(port, tmp, &deleted, delete_list) + port_free(client, port); return 0; } diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index 12ad86bf1489..ef1787d6c2dd 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -49,6 +49,7 @@ struct snd_seq_client_port { struct module *owner; /* owner of this port */ char name[64]; /* port name */ struct list_head list; /* port list */ + struct list_head delete_list; /* used by snd_seq_delete_all_ports() */ snd_use_lock_t use_lock; /* subscribers */ diff --git a/sound/core/timer.c b/sound/core/timer.c index 679b26435670..905176bf6948 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -2173,9 +2173,11 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon } static const struct file_operations snd_utimer_fops = { + .owner = THIS_MODULE, .llseek = noop_llseek, .release = snd_utimer_release, .unlocked_ioctl = snd_utimer_ioctl, + .compat_ioctl = snd_utimer_ioctl, }; static int snd_utimer_start(struct snd_timer *t) diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c index 25ee81c1668b..5bab00466de7 100644 --- a/sound/core/timer_compat.c +++ b/sound/core/timer_compat.c @@ -99,6 +99,7 @@ static long __snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, case SNDRV_TIMER_IOCTL_PAUSE: case SNDRV_TIMER_IOCTL_PAUSE_OLD: case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + case SNDRV_TIMER_IOCTL_CREATE: return __snd_timer_user_ioctl(file, cmd, (unsigned long)argp, true); case SNDRV_TIMER_IOCTL_GPARAMS32: return snd_timer_user_gparams_compat(file, argp); diff --git a/sound/core/ump.c b/sound/core/ump.c index 3d1a2ed3b476..f6482a9bfe61 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -66,7 +66,7 @@ static void safe_copy_string(void *dst, size_t max_dst_size, if (!max_dst_size--) return; - for (s = src; max_dst_size && *s && max_src_size--; s++) { + for (s = src; max_dst_size && max_src_size-- && *s; s++) { if (!isascii(*s) || !isprint(*s)) continue; *d++ = *s; diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index 85cc67de6330..0a7972d91ec5 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -323,7 +323,7 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc, cc->cc_data_lsb); reset_rpn(cc); - return 1; + return 8; } /* convert to a MIDI 1.0 Channel Voice message */ diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c index cd3dcaaf9f72..f43144ba2797 100644 --- a/sound/core/vmaster.c +++ b/sound/core/vmaster.c @@ -287,6 +287,8 @@ EXPORT_SYMBOL(_snd_ctl_add_follower); * * Adds the multiple follower kcontrols with the given names. * Returns 0 for success or a negative error code. + * + * Note that this function takes card->controls_rwsem lock internally. */ int snd_ctl_add_followers(struct snd_card *card, struct snd_kcontrol *master, const char * const *list) @@ -294,6 +296,7 @@ int snd_ctl_add_followers(struct snd_card *card, struct snd_kcontrol *master, struct snd_kcontrol *follower; int err; + guard(rwsem_write)(&card->controls_rwsem); for (; *list; list++) { follower = snd_ctl_find_id_mixer(card, *list); if (follower) { @@ -463,6 +466,7 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master); /** * snd_ctl_add_vmaster_hook - Add a hook to a vmaster control + * @card: the card instance * @kcontrol: vmaster kctl element * @hook: the hook function * @private_data: the private_data pointer to be saved @@ -470,13 +474,17 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master); * Adds the given hook to the vmaster control element so that it's called * at each time when the value is changed. * + * Takes the card's controls_rwsem lock internally. + * * Return: Zero. */ -int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol, +int snd_ctl_add_vmaster_hook(struct snd_card *card, struct snd_kcontrol *kcontrol, void (*hook)(void *private_data, int), void *private_data) { struct link_master *master = snd_kcontrol_chip(kcontrol); + + guard(rwsem_write)(&card->controls_rwsem); master->hook = hook; master->hook_private_data = private_data; return 0; diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 81cb1f59f703..824d320b665b 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -100,7 +100,7 @@ struct loopback_ops { struct loopback_cable { spinlock_t lock; struct loopback_pcm *streams[2]; - /* in-flight peer stops running outside cable->lock */ + /* in-flight PCM callbacks running outside cable->lock */ struct snd_refcount stop_count; struct snd_pcm_hardware hw; /* flags */ @@ -412,11 +412,14 @@ static int loopback_check_format(struct loopback_cable *cable, int stream) runtime->channels != cruntime->channels || is_access_interleaved(runtime->access) != is_access_interleaved(cruntime->access); - if (!check) - return 0; - if (stream == SNDRV_PCM_STREAM_CAPTURE) + /* + * Matching params also go on to the update below, so that + * the setup controls follow a playback that joins an open + * capture. + */ + if (check && stream == SNDRV_PCM_STREAM_CAPTURE) return -EIO; - else if (cruntime->state == SNDRV_PCM_STATE_RUNNING) { + else if (check && cruntime->state == SNDRV_PCM_STATE_RUNNING) { /* close must not free the peer runtime below */ snd_refcount_get(&cable->stop_count); stop_capture = true; @@ -873,6 +876,8 @@ static void loopback_snd_timer_period_elapsed(struct loopback_cable *cable, valid_runtime = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ? dpcm_play->substream->runtime : dpcm_capt->substream->runtime; + /* Keep runtimes alive through the PCM callbacks. */ + snd_refcount_get(&cable->stop_count); /* resolution is only valid for SNDRV_TIMER_EVENT_TICK events */ if (event == SNDRV_TIMER_EVENT_TICK) { @@ -908,13 +913,16 @@ static void loopback_snd_timer_period_elapsed(struct loopback_cable *cable, snd_pcm_stop_xrun(substream_play); if (substream_capt) snd_pcm_stop_xrun(substream_capt); - return; + goto exit; } if (substream_play) snd_pcm_period_elapsed(substream_play); if (substream_capt) snd_pcm_period_elapsed(substream_capt); + +exit: + snd_refcount_put(&cable->stop_count); } static void loopback_snd_timer_function(struct snd_timer_instance *timeri, @@ -1073,6 +1081,18 @@ static unsigned int get_cable_index(struct snd_pcm_substream *substream) return !substream->stream; } +/* + * In notify mode the playback side may change format, rate, channels and, + * with a sound timer, the period size. A running capture that no longer + * matches is stopped by loopback_check_format(). + * call in loopback->cable_lock + */ +static bool loopback_playback_unconstrained(struct loopback_pcm *dpcm) +{ + return dpcm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + get_notify(dpcm); +} + static int rule_format(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { @@ -1082,6 +1102,8 @@ static int rule_format(struct snd_pcm_hw_params *params, snd_mask_none(&m); scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; m.bits[0] = (u_int32_t)cable->hw.formats; m.bits[1] = (u_int32_t)(cable->hw.formats >> 32); } @@ -1096,6 +1118,8 @@ static int rule_rate(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.rate_min; t.max = cable->hw.rate_max; } @@ -1112,6 +1136,8 @@ static int rule_channels(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.channels_min; t.max = cable->hw.channels_max; } @@ -1128,6 +1154,8 @@ static int rule_period_bytes(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.period_bytes_min; t.max = cable->hw.period_bytes_max; } @@ -1154,7 +1182,7 @@ static void free_cable(struct snd_pcm_substream *substream) other_alive = cable->streams[!substream->stream]; } - /* Pair with the stop_count increment in loopback_check_format(). */ + /* Wait for PCM callbacks running outside cable->lock. */ snd_refcount_sync(&cable->stop_count); if (other_alive) return; @@ -1355,7 +1383,7 @@ static const struct loopback_ops loopback_snd_timer_ops = { .dpcm_info = loopback_snd_timer_dpcm_info, }; -static int loopback_open(struct snd_pcm_substream *substream) +static int __loopback_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct loopback *loopback = substream->private_data; @@ -1364,7 +1392,6 @@ static int loopback_open(struct snd_pcm_substream *substream) int err = 0; int dev = get_cable_index(substream); - guard(mutex)(&loopback->cable_lock); dpcm = kzalloc_obj(*dpcm); if (!dpcm) return -ENOMEM; @@ -1459,6 +1486,14 @@ static int loopback_open(struct snd_pcm_substream *substream) return err; } +static int loopback_open(struct snd_pcm_substream *substream) +{ + struct loopback *loopback = substream->private_data; + + guard(mutex)(&loopback->cable_lock); + return __loopback_open(substream); +} + static int loopback_close(struct snd_pcm_substream *substream) { struct loopback *loopback = substream->private_data; @@ -1870,13 +1905,10 @@ static int loopback_cable_proc_new(struct loopback *loopback, int cidx) static void loopback_set_timer_source(struct loopback *loopback, const char *value) { - if (loopback->timer_source) { - devm_kfree(loopback->card->dev, loopback->timer_source); - loopback->timer_source = NULL; - } + kfree(loopback->timer_source); + loopback->timer_source = NULL; if (value && *value) - loopback->timer_source = devm_kstrdup(loopback->card->dev, - value, GFP_KERNEL); + loopback->timer_source = kstrdup(value, GFP_KERNEL); } static void print_timer_source_info(struct snd_info_entry *entry, @@ -1907,6 +1939,13 @@ static int loopback_timer_source_proc_new(struct loopback *loopback) change_timer_source_info); } +static void loopback_private_free(struct snd_card *card) +{ + struct loopback *loopback = card->private_data; + + kfree(loopback->timer_source); +} + static int loopback_probe(struct platform_device *devptr) { struct snd_card *card; @@ -1932,6 +1971,7 @@ static int loopback_probe(struct platform_device *devptr) pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; loopback->card = card; + card->private_free = loopback_private_free; loopback_set_timer_source(loopback, timer_source[dev]); mutex_init(&loopback->cable_lock); diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index 3f6bfee29986..6b0a06e5c876 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -1063,11 +1063,13 @@ static int snd_dummy_probe(struct platform_device *devptr) break; } } + + if (pcm_substreams[dev] < 1) + pcm_substreams[dev] = 1; + if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) + pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; + for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) { - if (pcm_substreams[dev] < 1) - pcm_substreams[dev] = 1; - if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) - pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev]); if (err < 0) return err; diff --git a/sound/drivers/mpu401/mpu401_uart.c b/sound/drivers/mpu401/mpu401_uart.c index 6de03d72a908..fcf2b686f9f5 100644 --- a/sound/drivers/mpu401/mpu401_uart.c +++ b/sound/drivers/mpu401/mpu401_uart.c @@ -155,20 +155,20 @@ static void snd_mpu401_uart_timer(struct timer_list *t) scoped_guard(spinlock_irqsave, &mpu->timer_lock) { /*mpu->mode |= MPU401_MODE_TIMER;*/ - mod_timer(&mpu->timer, 1 + jiffies); + if (mpu->timer_invoked) + mod_timer(&mpu->timer, 1 + jiffies); } if (mpu->rmidi) _snd_mpu401_uart_interrupt(mpu); } /* - * initialize the timer callback if not programmed yet + * arm the timer if it is not already active */ static void snd_mpu401_uart_add_timer (struct snd_mpu401 *mpu, int input) { guard(spinlock_irqsave)(&mpu->timer_lock); if (mpu->timer_invoked == 0) { - timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); mod_timer(&mpu->timer, 1 + jiffies); } mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER : @@ -474,6 +474,7 @@ static void snd_mpu401_uart_free(struct snd_rawmidi *rmidi) struct snd_mpu401 *mpu = rmidi->private_data; if (mpu->irq >= 0) free_irq(mpu->irq, (void *) mpu); + timer_shutdown_sync(&mpu->timer); release_and_free_resource(mpu->res); kfree(mpu); } @@ -528,6 +529,7 @@ int snd_mpu401_uart_new(struct snd_card *card, int device, spin_lock_init(&mpu->input_lock); spin_lock_init(&mpu->output_lock); spin_lock_init(&mpu->timer_lock); + timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); mpu->hardware = hardware; mpu->irq = -1; mpu->rmidi = rmidi; diff --git a/sound/drivers/mtpav.c b/sound/drivers/mtpav.c index 1ed6d4f0cb62..288a873a27e6 100644 --- a/sound/drivers/mtpav.c +++ b/sound/drivers/mtpav.c @@ -443,7 +443,7 @@ static void snd_mtpav_output_trigger(struct snd_rawmidi_substream *substream, in scoped_guard(spinlock_irqsave, &mtp_card->spinlock) { if (up) { - if ((portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED)) { + if (!(portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED)) { if (mtp_card->istimer++ == 0) snd_mtpav_add_output_timer(mtp_card); portp->mode |= MTPAV_MODE_OUTPUT_TRIGGERED; diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c index 5d5281e4deb7..d1b0dff56cd9 100644 --- a/sound/drivers/pcmtest.c +++ b/sound/drivers/pcmtest.c @@ -280,39 +280,35 @@ static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_ru fill_block_pattern_n(v_iter, runtime); } +static void fill_random_block(char *buf, size_t buf_size, size_t pos, + size_t count) +{ + while (count) { + size_t chunk = min(count, buf_size - pos); + + get_random_bytes(buf + pos, chunk); + count -= chunk; + pos = 0; + } +} + static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) { unsigned int channels = runtime->channels; - // Remaining space in all channel buffers - size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; + size_t pos = v_iter->buf_pos / channels; unsigned int i; - for (i = 0; i < channels; i++) { - if (v_iter->b_rw <= bytes_remain) { - //b_rw - count of bytes must be written for all channels at each timer tick - get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), - v_iter->b_rw / channels); - } else { - // Write to the end of buffer and start from the beginning of it - get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), - bytes_remain / channels); - get_random_bytes(runtime->dma_area + v_iter->chan_block * i, - (v_iter->b_rw - bytes_remain) / channels); - } - } + for (i = 0; i < channels; i++) + fill_random_block(runtime->dma_area + v_iter->chan_block * i, + v_iter->chan_block, pos, + v_iter->b_rw / channels); inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); } static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) { - size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; - - if (v_iter->b_rw <= in_cur_block) { - get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw); - } else { - get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block); - get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block); - } + fill_random_block(runtime->dma_area, runtime->dma_bytes, + v_iter->buf_pos, v_iter->b_rw); inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); } @@ -349,6 +345,7 @@ static void timer_timeout(struct timer_list *data) v_iter = timer_container_of(v_iter, data, timer_instance); substream = v_iter->substream; + guard(pcm_stream_lock_irqsave)(substream); if (v_iter->suspend) return; @@ -362,7 +359,7 @@ static void timer_timeout(struct timer_list *data) v_iter->period_pos += v_iter->b_rw; if (v_iter->period_pos >= v_iter->period_bytes) { v_iter->period_pos %= v_iter->period_bytes; - snd_pcm_period_elapsed(substream); + snd_pcm_period_elapsed_under_stream_lock(substream); } if (!v_iter->suspend) @@ -449,24 +446,6 @@ static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substr return bytes_to_frames(substream->runtime, v_iter->buf_pos); } -static int snd_pcmtst_free(struct pcmtst *pcmtst) -{ - if (!pcmtst) - return 0; - kfree(pcmtst); - return 0; -} - -// These callbacks are required, but empty - all freeing occurs in pdev_remove -static int snd_pcmtst_dev_free(struct snd_device *device) -{ - return 0; -} - -static void pcmtst_pdev_release(struct device *dev) -{ -} - static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; @@ -566,40 +545,9 @@ static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) return err; } -static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, - struct pcmtst **r_pcmtst) -{ - struct pcmtst *pcmtst; - int err; - static const struct snd_device_ops ops = { - .dev_free = snd_pcmtst_dev_free, - }; - - pcmtst = kzalloc_obj(*pcmtst); - if (!pcmtst) - return -ENOMEM; - pcmtst->card = card; - pcmtst->pdev = pdev; - - err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops); - if (err < 0) - goto _err_free_chip; - - err = snd_pcmtst_new_pcm(pcmtst); - if (err < 0) - goto _err_free_chip; - - *r_pcmtst = pcmtst; - return 0; - -_err_free_chip: - snd_pcmtst_free(pcmtst); - return err; -} - static int pcmtst_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct pcmtst *pcmtst; int err; @@ -607,10 +555,16 @@ static int pcmtst_probe(struct platform_device *pdev) if (err) return err; - err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, + sizeof(*pcmtst), &card); if (err < 0) return err; - err = snd_pcmtst_create(card, pdev, &pcmtst); + + pcmtst = card->private_data; + pcmtst->card = card; + pcmtst->pdev = pdev; + + err = snd_pcmtst_new_pcm(pcmtst); if (err < 0) return err; @@ -623,27 +577,19 @@ static int pcmtst_probe(struct platform_device *pdev) return err; platform_set_drvdata(pdev, pcmtst); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void pdev_remove(struct platform_device *pdev) -{ - struct pcmtst *pcmtst = platform_get_drvdata(pdev); +#define SND_PCMTEST_DRIVER "pcmtest" - snd_pcmtst_free(pcmtst); -} - -static struct platform_device pcmtst_pdev = { - .name = "pcmtest", - .dev.release = pcmtst_pdev_release, -}; +static struct platform_device *pcmtst_pdev; static struct platform_driver pcmtst_pdrv = { .probe = pcmtst_probe, - .remove = pdev_remove, .driver = { - .name = "pcmtest", + .name = SND_PCMTEST_DRIVER, }, }; @@ -687,6 +633,7 @@ static ssize_t pattern_read(struct file *file, char __user *u_buff, size_t len, } static const struct file_operations fill_pattern_fops = { + .owner = THIS_MODULE, .read = pattern_read, .write = pattern_write, }; @@ -755,12 +702,14 @@ static int __init mod_init(void) err = init_debug_files(buf_allocated); if (err) goto err_free_patterns; - err = platform_device_register(&pcmtst_pdev); - if (err) + pcmtst_pdev = platform_device_register_simple(SND_PCMTEST_DRIVER, -1, NULL, 0); + if (IS_ERR(pcmtst_pdev)) { + err = PTR_ERR(pcmtst_pdev); goto err_clear_debug; + } err = platform_driver_register(&pcmtst_pdrv); if (err) { - platform_device_unregister(&pcmtst_pdev); + platform_device_unregister(pcmtst_pdev); goto err_clear_debug; } @@ -779,7 +728,7 @@ static void __exit mod_exit(void) free_pattern_buffers(); platform_driver_unregister(&pcmtst_pdrv); - platform_device_unregister(&pcmtst_pdev); + platform_device_unregister(pcmtst_pdev); } MODULE_DESCRIPTION("Virtual ALSA driver for PCM testing/fuzzing"); diff --git a/sound/drivers/serial-generic.c b/sound/drivers/serial-generic.c index 766206c6ca75..8ba5e35af4a1 100644 --- a/sound/drivers/serial-generic.c +++ b/sound/drivers/serial-generic.c @@ -183,10 +183,7 @@ static void snd_serial_generic_input_trigger(struct snd_rawmidi_substream *subst { struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; - if (up) - set_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); - else - clear_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); + assign_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode, up); } static int snd_serial_generic_output_open(struct snd_rawmidi_substream *substream) @@ -230,10 +227,7 @@ static void snd_serial_generic_output_trigger(struct snd_rawmidi_substream *subs { struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; - if (up) - set_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); - else - clear_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); + assign_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode, up); if (up) snd_serial_generic_tx_wakeup(drvdata); diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c index 216d1fceb6e7..1c523525fad2 100644 --- a/sound/firewire/bebob/bebob_hwdep.c +++ b/sound/firewire/bebob/bebob_hwdep.c @@ -8,7 +8,7 @@ /* * This codes give three functionality. * - * 1.get firewire node infomation + * 1.get firewire node information * 2.get notification about starting/stopping stream * 3.lock/unlock stream */ diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index 0f8fb62f021d..896091c1144f 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1060,6 +1061,8 @@ enum dsp_download_state { * CA0132 specific */ +#define AE5_STRIP_MAX_LEDS 100 + struct ca0132_spec { struct hda_gen_spec gen; @@ -1162,6 +1165,13 @@ struct ca0132_spec { * Renames PlayEnhancement and CrystalVoice too. */ bool use_alt_controls; + +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + /* AE-5 WS2812 strip control */ + struct mutex ae5_strip_mutex; + u32 ae5_strip_cur_colors[AE5_STRIP_MAX_LEDS]; + int ae5_strip_cur_num_leds; +#endif }; /* @@ -6846,6 +6856,15 @@ static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec) } +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) +static int ae5_add_strip_controls(struct hda_codec *codec); +#else +static inline int ae5_add_strip_controls(struct hda_codec *codec) +{ + return 0; +} +#endif + /* * Add headphone gain enumerated control for the AE-5. This switches between * three modes, low, medium, and high. When non-headphone outputs are selected, @@ -7149,6 +7168,10 @@ static int ca0132_build_controls(struct hda_codec *codec) switch (ca0132_quirk(spec)) { case QUIRK_AE5: + err = ae5_add_strip_controls(codec); + if (err < 0) + return err; + fallthrough; case QUIRK_AE7: err = ae5_add_headphone_gain_enum(codec); if (err < 0) @@ -8316,8 +8339,367 @@ static void sbz_setup_defaults(struct hda_codec *codec) } /* - * Setup default parameters for the Sound BlasterX AE-5 DSP. + * Sound BlasterX AE-5 External WS2812B RGB LED Strip Transport */ +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) +#define AE5_STRIP_DMA_SIZE 0x8000 +#define AE5_STRIP_TOTAL_WORDS (AE5_STRIP_DMA_SIZE / sizeof(u32)) +#define AE5_STRIP_PREAMBLE_WORDS 5 +#define AE5_STRIP_WORDS_PER_LED 4 +#define AE5_STRIP_RESET_GAP_WORDS 83 + +#define CA0113_SERIALIZER_BASE(spec, ch) ((spec)->mem_base + 0x42c + (ch) * 0x40) +#define CA0113_SERIALIZER_CLK(spec, ch) ((spec)->mem_base + 0x43c + (ch) * 0x40) +#define CA0113_SERIALIZER_OUT(spec, ch) ((spec)->mem_base + 0x454 + (ch) * 0x40) + +static void ae5_strip_encode_24(u32 u, u32 *words) +{ + /* + * 24-bit pixel -> 4 x 32-bit audio words (6 bits per word). + * Hardware audio serializer transmits MSB first (bits 31 down to 0). + * For word i (i8 = 23, 17, 11, 5), bit_idx 0..5 (MSB to LSB within the word) + * is placed at n = 29 - bit_idx * 4: + * Bit 0 -> 0b100 << n (375ns high, 1042ns low): BIT(n + 2) + * Bit 1 -> 0b111 << n (1083ns high, 333ns low): BIT(n + 2) | BIT(n + 1) | BIT(n) + */ + int i8 = 23; + int i, bit_idx; + + for (i = 0; i < 4; i++) { + u32 out = 0; + + for (bit_idx = 0; bit_idx < 6; bit_idx++) { + int bit = (u >> (i8 - bit_idx)) & 1; + int n = 29 - bit_idx * 4; + + out |= (bit ? (BIT(2) | BIT(1) | BIT(0)) : BIT(2)) << n; + } + words[i] = out; + i8 -= 6; + } +} + +static struct hdac_stream *ae5_strip_find_stream(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + struct hdac_bus *bus = &codec->bus->core; + struct hdac_stream *s, *dsp = NULL, *first = NULL; + + guard(spinlock_irqsave)(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + if (s->direction != SNDRV_PCM_STREAM_PLAYBACK) + continue; + if (s->opened || s->running || s->locked) + continue; + if (!first) + first = s; + /* The DSP DMA consumes the DSP-loader stream (tag in dsp_stream_id). */ + if (spec->dsp_stream_id && s->stream_tag == spec->dsp_stream_id) + dsp = s; + } + + s = dsp ? dsp : first; + if (s) + s->opened = 1; + + return s; +} + +/* + * Caller must hold spec->ae5_strip_mutex. + */ +static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, int num_leds) +{ + struct ca0132_spec *spec = codec->spec; + struct hdac_bus *bus = &codec->bus->core; + struct snd_dma_buffer dmab; + struct hdac_stream *hstr = NULL; + unsigned int format, stream_tag; + u32 enc_words[4]; + __le32 *dst; + int word_idx = 0; + int frame_words; + int led, w, i; + int retries = 20; /* ~100ms max */ + + if (!spec || !spec->mem_base) + return -ENODEV; + if (num_leds <= 0 || num_leds > AE5_STRIP_MAX_LEDS) + return -EINVAL; + + lockdep_assert_held(&spec->ae5_strip_mutex); + + CLASS(snd_hda_power_pm, pm)(codec); + if (pm.err < 0) + return pm.err; + + /* + * 44.1kHz, 24-bit, 2-channel format (0x4031) matches Windows CtxHda + * HDAUDIO_STREAM_FORMAT. + */ + format = snd_hdac_stream_format(2, 24, 44100); + + for (;;) { + hstr = ae5_strip_find_stream(codec); + if (!hstr) { + if (--retries <= 0) { + codec_warn(codec, "AE5 strip: no free azx stream after retries\n"); + return -EBUSY; + } + usleep_range(5000, 10000); + continue; + } + + stream_tag = snd_hdac_dsp_prepare(hstr, format, AE5_STRIP_DMA_SIZE, &dmab); + if ((int)stream_tag > 0) + break; + + /* Prepare failed (e.g. -EBUSY due to race with playback starting); retry */ + scoped_guard(spinlock_irq, &bus->reg_lock) + hstr->opened = 0; + + if (--retries <= 0) { + codec_warn(codec, "AE5 strip: snd_hdac_dsp_prepare failed %d\n", + (int)stream_tag); + return (int)stream_tag; + } + usleep_range(5000, 10000); + } + + /* 1. Populate the 32KB DMA buffer with repeating WS2812 frames + reset gaps */ + memset(dmab.area, 0, AE5_STRIP_DMA_SIZE); + dst = (__le32 *)dmab.area; + + /* Words per frame: preamble + (num_leds * words_per_led) + reset gap */ + frame_words = AE5_STRIP_PREAMBLE_WORDS + (num_leds * AE5_STRIP_WORDS_PER_LED) + + AE5_STRIP_RESET_GAP_WORDS; + + while (word_idx + frame_words <= AE5_STRIP_TOTAL_WORDS) { + /* Preamble per Windows CtxHda FUN_00042944 */ + for (w = 0; w < AE5_STRIP_PREAMBLE_WORDS; w++) { + *dst++ = 0; + word_idx++; + } + /* LEDs */ + for (led = 0; led < num_leds; led++) { + ae5_strip_encode_24(grb_colors[led], enc_words); + for (w = 0; w < AE5_STRIP_WORDS_PER_LED; w++) { + *dst++ = cpu_to_le32(enc_words[w]); + word_idx++; + } + } + /* Zero reset gap (> 1.8 ms; WS2812 requires > 50 us low) */ + for (w = 0; w < AE5_STRIP_RESET_GAP_WORDS; w++) { + *dst++ = 0; + word_idx++; + } + } + while (word_idx < AE5_STRIP_TOTAL_WORDS) { + *dst++ = 0; + word_idx++; + } + /* Ensure DMA buffer writes are committed before triggering the HDA stream */ + wmb(); + + /* 2. Configure CA0113 BAR2 hardware registers */ + /* Power up clocks (CtxHdb 0x141b7 / 0x14210) */ + writel(readl(spec->mem_base + 0xc04) | 0x7, spec->mem_base + 0xc04); + + /* Base serializer initialization (CtxHdb 0x14ba4) */ + writel(readl(spec->mem_base + 0x400) | 1, spec->mem_base + 0x400); + for (i = 0; i < 4; i++) { + writel(readl(CA0113_SERIALIZER_BASE(spec, i)) & ~1, + CA0113_SERIALIZER_BASE(spec, i)); + writel(0, CA0113_SERIALIZER_CLK(spec, i)); + } + writel(readl(spec->mem_base + 0x408) | 1, spec->mem_base + 0x408); + writel(readl(spec->mem_base + 0x40c) | 1, spec->mem_base + 0x40c); + writel((readl(spec->mem_base + 0x410) & ~0xb) | 0x14, spec->mem_base + 0x410); + + /* Configure serializers 0..3 clocking (CtxHdb 0x14a6c) */ + for (i = 0; i < 4; i++) + writel(readl(CA0113_SERIALIZER_CLK(spec, i)) | 0x33, + CA0113_SERIALIZER_CLK(spec, i)); + + /* BAR2 + 0x100 format & channel enables: + * - base 0x70f + * - bit 28 = 1 (24-bit audio format via CtxHdb 0x14f58) + * - bits 29..31 = 1 (44.1 kHz sample rate via CtxHdb 0x14fa4) + * - bits 4..7 = 0xf (stage 1 enables) + * - bits 12..15 = 0xf (stage 2 enables) + * => 0x3000ff8f + */ + writel((readl(spec->mem_base + 0x100) & ~0xe000ffff) | 0x3000ff8f, spec->mem_base + 0x100); + + /* Enable serializer output drivers for pins 0..3 (CtxHdb 0x14e94) */ + for (i = 0; i < 4; i++) + writel(readl(CA0113_SERIALIZER_OUT(spec, i)) | 1, + CA0113_SERIALIZER_OUT(spec, i)); + + /* Route all channels 0..3 to stream_tag (CtxHdb 0x1502c) */ + writel((stream_tag << 28) | (stream_tag << 20) | (stream_tag << 12) | (stream_tag << 4), + spec->mem_base + 0x104); + + /* Enable CA0113 MMIO GPIO 0 & 1 */ + ca0113_mmio_gpio_set(codec, 0, true); + ca0113_mmio_gpio_set(codec, 1, true); + + codec_dbg(codec, "AE5 strip: send_frame: num_leds=%d stream_tag=%u (0x%08x)\n", + num_leds, stream_tag, readl(spec->mem_base + 0x104)); + + /* 3. Trigger stream transmission on HDA link */ + snd_hdac_dsp_trigger(hstr, true); + /* + * WS2812 latches on the >50us reset gap after one frame. The 32KB buffer holds ~64 + * repeating frame+reset cycles; at ~1.4ms/frame a 15ms run transmits ~10 complete + * cycles - more than enough to latch reliably. Keep it short so rapid sysfs writes + * (OpenRGB effects/theme changes) are not serialized behind a 1s blocking sleep. + */ + usleep_range(15000, 20000); + 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); + + snd_hdac_dsp_cleanup(hstr, &dmab); + scoped_guard(spinlock_irq, &bus->reg_lock) + hstr->opened = 0; + + return 0; +} + +static int ae5_strip_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = AE5_STRIP_MAX_LEDS * 3; + return 0; +} + +static int ae5_strip_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int i; + + guard(mutex)(&spec->ae5_strip_mutex); + for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) { + u32 grb = spec->ae5_strip_cur_colors[i]; + + ucontrol->value.bytes.data[i * 3 + 0] = (grb >> 8) & 0xff; + ucontrol->value.bytes.data[i * 3 + 1] = (grb >> 16) & 0xff; + ucontrol->value.bytes.data[i * 3 + 2] = grb & 0xff; + } + return 0; +} + +static int ae5_strip_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + u32 colors[AE5_STRIP_MAX_LEDS]; + int i, num_leds, change, ret; + + guard(mutex)(&spec->ae5_strip_mutex); + for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) { + u8 r = ucontrol->value.bytes.data[i * 3 + 0]; + u8 g = ucontrol->value.bytes.data[i * 3 + 1]; + u8 b = ucontrol->value.bytes.data[i * 3 + 2]; + + colors[i] = ((u32)g << 16) | ((u32)r << 8) | (u32)b; + } + change = memcmp(spec->ae5_strip_cur_colors, colors, + sizeof(spec->ae5_strip_cur_colors)) != 0; + num_leds = spec->ae5_strip_cur_num_leds; + ret = ae5_strip_send_frame(codec, colors, num_leds); + if (ret < 0) + return ret; + if (change) + memcpy(spec->ae5_strip_cur_colors, colors, + sizeof(spec->ae5_strip_cur_colors)); + return change; +} + +static int ae5_strip_num_leds_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = AE5_STRIP_MAX_LEDS; + return 0; +} + +static int ae5_strip_num_leds_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + guard(mutex)(&spec->ae5_strip_mutex); + ucontrol->value.integer.value[0] = spec->ae5_strip_cur_num_leds; + return 0; +} + +static int ae5_strip_num_leds_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int val = ucontrol->value.integer.value[0]; + int ret; + + if (val < 1 || val > AE5_STRIP_MAX_LEDS) + return -EINVAL; + + guard(mutex)(&spec->ae5_strip_mutex); + if (spec->ae5_strip_cur_num_leds == val) + return 0; + ret = ae5_strip_send_frame(codec, spec->ae5_strip_cur_colors, val); + if (ret < 0) + return ret; + spec->ae5_strip_cur_num_leds = val; + return 1; +} + +static const struct snd_kcontrol_new ae5_strip_ctls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "AE-5 LED Strip", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = ae5_strip_ctl_info, + .get = ae5_strip_ctl_get, + .put = ae5_strip_ctl_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "AE-5 LED Strip Count", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = ae5_strip_num_leds_info, + .get = ae5_strip_num_leds_get, + .put = ae5_strip_num_leds_put, + }, +}; + +static int ae5_add_strip_controls(struct hda_codec *codec) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(ae5_strip_ctls); i++) { + err = snd_hda_ctl_add(codec, 0, + snd_ctl_new1(&ae5_strip_ctls[i], codec)); + if (err < 0) + return err; + } + return 0; +} +#endif /* CONFIG_SND_HDA_DSP_LOADER */ + static void ae5_setup_defaults(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; @@ -8741,6 +9123,10 @@ static void ca0132_init_chip(struct hda_codec *codec) unsigned int on; mutex_init(&spec->chipio_mutex); +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + mutex_init(&spec->ae5_strip_mutex); + spec->ae5_strip_cur_num_leds = 10; +#endif /* * The Windows driver always does this upon startup, which seems to @@ -9626,6 +10012,9 @@ static void ca0132_free(struct hda_codec *codec) #ifdef CONFIG_PCI if (spec->mem_base) pci_iounmap(codec->bus->pci, spec->mem_base); +#endif +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + mutex_destroy(&spec->ae5_strip_mutex); #endif kfree(spec->spec_init_verbs); kfree(codec->spec); diff --git a/sound/hda/codecs/cirrus/cs8409-tables.c b/sound/hda/codecs/cirrus/cs8409-tables.c index b9ec8fb8eab7..d0cef8492c99 100644 --- a/sound/hda/codecs/cirrus/cs8409-tables.c +++ b/sound/hda/codecs/cirrus/cs8409-tables.c @@ -594,7 +594,7 @@ const struct hda_quirk cs8409_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x0BA8, "Odin", CS8409_ODIN), SND_PCI_QUIRK(0x1028, 0x0BAA, "Odin", CS8409_ODIN), SND_PCI_QUIRK(0x1028, 0x0BAE, "Odin", CS8409_ODIN), - SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK_DELAYED_JD), SND_PCI_QUIRK(0x1028, 0x0BB3, "Warlock MLK", CS8409_WARLOCK_MLK), SND_PCI_QUIRK(0x1028, 0x0BB4, "Warlock MLK", CS8409_WARLOCK_MLK), SND_PCI_QUIRK(0x1028, 0x0BB5, "Warlock N3 15 TGL-U Nuvoton EC", CS8409_WARLOCK), @@ -625,6 +625,7 @@ const struct hda_model_fixup cs8409_models[] = { { .id = CS8409_BULLSEYE, .name = "bullseye" }, { .id = CS8409_WARLOCK, .name = "warlock" }, { .id = CS8409_WARLOCK_MLK, .name = "warlock mlk" }, + { .id = CS8409_WARLOCK_MLK_DELAYED_JD, .name = "warlock mlk delayed jd" }, { .id = CS8409_WARLOCK_MLK_DUAL_MIC, .name = "warlock mlk dual mic" }, { .id = CS8409_CYBORG, .name = "cyborg" }, { .id = CS8409_DOLPHIN, .name = "dolphin" }, @@ -652,6 +653,12 @@ const struct hda_fixup cs8409_fixups[] = { .chained = true, .chain_id = CS8409_FIXUPS, }, + [CS8409_WARLOCK_MLK_DELAYED_JD] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, [CS8409_WARLOCK_MLK_DUAL_MIC] = { .type = HDA_FIXUP_PINS, .v.pins = cs8409_cs42l42_pincfgs, diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c index c43ff3ef75b6..12ee71d7a35c 100644 --- a/sound/hda/codecs/cirrus/cs8409.c +++ b/sound/hda/codecs/cirrus/cs8409.c @@ -56,6 +56,9 @@ static int cs8409_parse_auto_config(struct hda_codec *codec) } static void cs8409_disable_i2c_clock_worker(struct work_struct *work); +static void cs8409_jack_detect_worker(struct work_struct *work); +static void cs42l42_run_jack_detect(struct sub_codec *cs42l42); +static int cs8409_i2c_read(struct sub_codec *scodec, unsigned int addr); static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) { @@ -69,6 +72,7 @@ static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) codec->power_save_node = 1; mutex_init(&spec->i2c_mux); INIT_DELAYED_WORK(&spec->i2c_clk_work, cs8409_disable_i2c_clock_worker); + INIT_DELAYED_WORK(&spec->jack_detect_work, cs8409_jack_detect_worker); snd_hda_gen_spec_init(&spec->gen); return spec; @@ -115,6 +119,28 @@ static void cs8409_disable_i2c_clock_worker(struct work_struct *work) cs8409_disable_i2c_clock(spec->codec); } +/* + * cs8409_jack_detect_worker - Perform initial jack detection once hardware is settled + * + * Defer initial jack detection. On ChromeOS and possibly other platforms this + * delay is required for jack detection to work as expected. + */ +static void cs8409_jack_detect_worker(struct work_struct *work) +{ + struct cs8409_spec *spec = container_of(work, struct cs8409_spec, jack_detect_work.work); + struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; + + if (spec->init_done && spec->build_ctrl_done && !cs42l42->hp_jack_in) { + int reg_ts_status = cs8409_i2c_read(cs42l42, CS42L42_TSRS_PLUG_STATUS); + + /* error case */ + if (reg_ts_status < 0) + return; + + cs42l42_run_jack_detect(cs42l42); + } +} + /* * cs8409_enable_i2c_clock - Enable I2C clocks * @codec: the codec instance @@ -951,6 +977,8 @@ static void cs8409_remove(struct hda_codec *codec) { struct cs8409_spec *spec = codec->spec; + /* Disable jack detect work */ + disable_delayed_work_sync(&spec->jack_detect_work); /* Cancel i2c clock disable timer, and disable clock if left enabled */ cancel_delayed_work_sync(&spec->i2c_clk_work); cs8409_disable_i2c_clock(codec); @@ -1020,6 +1048,9 @@ static int cs8409_cs42l42_suspend(struct hda_codec *codec) cs8409_enable_ur(codec, 0); + /* Cancel jack detect work first as it accesses CS42L42 over I2C */ + cancel_delayed_work_sync(&spec->jack_detect_work); + for (i = 0; i < spec->num_scodecs; i++) cs42l42_suspend(spec->scodecs[i]); @@ -1153,6 +1184,9 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; spec->speaker_pdn_gpio = CS8409_CYBORG_SPEAKER_PDN; break; + case CS8409_WARLOCK_MLK_DELAYED_JD: + spec->delay_jack_detect = 1; + fallthrough; case CS8409_WARLOCK_MLK: case CS8409_WARLOCK_MLK_DUAL_MIC: spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; @@ -1197,8 +1231,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, cs8409_cs42l42_hw_init(codec); spec->init_done = 1; if (spec->init_done && spec->build_ctrl_done - && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) - cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) { + if (spec->delay_jack_detect) + schedule_delayed_work(&spec->jack_detect_work, + msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS)); + else + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + } break; case HDA_FIXUP_ACT_BUILD: spec->build_ctrl_done = 1; @@ -1208,8 +1247,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, * Run immediately after init. */ if (spec->init_done && spec->build_ctrl_done - && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) - cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) { + if (spec->delay_jack_detect) + schedule_delayed_work(&spec->jack_detect_work, + msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS)); + else + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + } break; default: break; diff --git a/sound/hda/codecs/cirrus/cs8409.h b/sound/hda/codecs/cirrus/cs8409.h index be1714a84fff..dacc1083b9d4 100644 --- a/sound/hda/codecs/cirrus/cs8409.h +++ b/sound/hda/codecs/cirrus/cs8409.h @@ -247,6 +247,7 @@ enum cs8409_coefficient_index_registers { #define CS8409_CS42L42_AMIC_PIN_NID CS8409_PIN_ASP1_RECEIVER_A #define CS8409_CS42L42_DMIC_PIN_NID CS8409_PIN_DMIC1_IN #define CS8409_CS42L42_DMIC_ADC_PIN_NID CS8409_PIN_DMIC1 +#define CS8409_JACK_DETECT_DELAY_MS 100 /* Dolphin */ @@ -266,6 +267,7 @@ enum { CS8409_BULLSEYE, CS8409_WARLOCK, CS8409_WARLOCK_MLK, + CS8409_WARLOCK_MLK_DELAYED_JD, CS8409_WARLOCK_MLK_DUAL_MIC, CS8409_CYBORG, CS8409_FIXUPS, @@ -339,12 +341,14 @@ struct cs8409_spec { unsigned int i2c_clck_enabled; unsigned int dev_addr; struct delayed_work i2c_clk_work; + struct delayed_work jack_detect_work; unsigned int playback_started:1; unsigned int capture_started:1; unsigned int init_done:1; unsigned int build_ctrl_done:1; unsigned int speaker_muted:1; + unsigned int delay_jack_detect:1; /* verb exec op override */ int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, unsigned int flags, diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 9bfdbf5c9032..a141c94eee13 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -302,6 +302,7 @@ enum { CXT_FIXUP_HP_SPECTRE, CXT_FIXUP_HP_GATE_MIC, CXT_FIXUP_MUTE_LED_GPIO, + CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED, CXT_FIXUP_HP_ELITEONE_OUT_DIS, CXT_FIXUP_HP_ZBOOK_MUTE_LED, CXT_FIXUP_HEADSET_MIC, @@ -312,6 +313,7 @@ enum { CXT_FIXUP_HP_A_U, CXT_FIXUP_ACER_SWIFT_HP, CXT_FIXUP_HUAWEI_MATEBOOK_HP, + CXT_FIXUP_MATEBOOK14_SN6140, }; /* for hda_fixup_thinkpad_acpi() */ @@ -808,6 +810,13 @@ static void cxt_fixup_hp_zbook_mute_led(struct hda_codec *codec, cxt_setup_mute_led(codec, 0x10, 0x20); } +static void cxt_fixup_acer_aspire_go_mute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + cxt_setup_mute_led(codec, 0, 0x10); +} + static void cxt_fixup_hp_a_u(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -817,6 +826,67 @@ static void cxt_fixup_hp_a_u(struct hda_codec *codec, cxt_setup_gpio_unmute(codec, 0x2); } +/* Huawei MateBook 14 (SN6140): the speaker output follows the headphone + * pin's connection select in hardware. Both pins must be routed to the + * shared DAC 0x10, which carries the playback stream; the generic + * parser mutes that DAC when no headphone is plugged, silencing the + * speaker. Suppress the auto-mute, bind the speaker pin to the shared + * DAC, remove its mute switch and keep it unmuted; the auto-mute hook + * re-applies the routing after every output update. + */ +static void cxt_mb14_route_unmute(struct hda_codec *codec) +{ + /* route both pins to the shared DAC 0x10, enable the speaker + * EAPD and unmute the DAC at a moderate gain; the vmaster + * volume takes over once userspace starts + */ + snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_CONNECT_SEL, 0); + snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL, 0); + snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_EAPD_BTLENABLE, 0x02); + snd_hda_codec_amp_stereo(codec, 0x10, HDA_OUTPUT, 0, + HDA_AMP_VOLMASK | HDA_AMP_MUTE, 0x40); +} + +static void cxt_mb14_automute(struct hda_codec *codec) +{ + snd_hda_gen_update_outputs(codec); + cxt_mb14_route_unmute(codec); +} + +static void cxt_fixup_matebook14_sn6140(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + static const hda_nid_t dac_0x10[] = { 0x10 }; + + spec->gen.suppress_auto_mute = 1; + spec->gen.automute_hook = cxt_mb14_automute; + /* Bind the speaker pin to the shared DAC 0x10. + */ + snd_hda_override_conn_list(codec, 0x17, 1, dac_0x10); + return; + } + if (action == HDA_FIXUP_ACT_PROBE) { + unsigned int caps; + + /* Remove the mute switch on the shared DAC so that + * userspace (WirePlumber) cannot mute it when the + * headphone route becomes unavailable (e.g. after + * resume). Must be done here: the codec regmap is not + * available at pre-probe time. + */ + caps = query_amp_caps(codec, 0x10, HDA_OUTPUT); + snd_hda_override_amp_caps(codec, 0x10, HDA_OUTPUT, + caps & ~AC_AMPCAP_MUTE); + return; + } + if (action != HDA_FIXUP_ACT_INIT) + return; + cxt_mb14_route_unmute(codec); +} + /* ThinkPad X200 & co with cxt5051 */ static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = { { 0x16, 0x042140ff }, /* HP (seq# overridden) */ @@ -1007,6 +1077,10 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_mute_led_gpio, }, + [CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_acer_aspire_go_mute_led, + }, [CXT_FIXUP_HP_ELITEONE_OUT_DIS] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_update_pinctl, @@ -1062,6 +1136,10 @@ static const struct hda_fixup cxt_fixups[] = { { } }, }, + [CXT_FIXUP_MATEBOOK14_SN6140] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_matebook14_sn6140, + }, }; static const struct hda_quirk cxt5045_fixups[] = { @@ -1112,6 +1190,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x1025, 0x054c, "Acer Aspire 3830TG", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x054f, "Acer Aspire 4830T", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x136d, "Acer Swift SF314", CXT_FIXUP_ACER_SWIFT_HP), + 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, 0x80FD, "HP ProBook 640 G2", CXT_FIXUP_HP_DOCK), @@ -1164,6 +1243,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP), + SND_PCI_QUIRK(0x19e5, 0x329e, "Huawei MateBook 14", CXT_FIXUP_MATEBOOK14_SN6140), SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC), diff --git a/sound/hda/codecs/hdmi/eld.c b/sound/hda/codecs/hdmi/eld.c index 1464fd1c675b..fc6fe1b0873c 100644 --- a/sound/hda/codecs/hdmi/eld.c +++ b/sound/hda/codecs/hdmi/eld.c @@ -155,9 +155,11 @@ void snd_hdmi_write_eld_info(struct hdmi_eld *eld, e->aud_synch_delay = val; else if (!strcmp(name, "speakers")) e->spk_alloc = val; - else if (!strcmp(name, "sad_count")) + else if (!strcmp(name, "sad_count")) { + if (val > ELD_MAX_SAD) + continue; e->sad_count = val; - else if (!strncmp(name, "sad", 3)) { + } else if (!strncmp(name, "sad", 3)) { sname = name + 4; n = name[3] - '0'; if (name[4] >= '0' && name[4] <= '9') { diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index 1e7a05c87733..355fd3e73183 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -983,7 +983,8 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx) snd_hda_set_dev_select(codec, pin_nid, dev_id); if (spec->intel_hsw_fixup) { - conns = spec->num_cvts; + /* spec->cvt_nids[] only has ARRAY_SIZE(spec->cvt_nids) slots */ + conns = min_t(int, spec->num_cvts, ARRAY_SIZE(spec->cvt_nids)); memcpy(per_pin->mux_nids, spec->cvt_nids, sizeof(hda_nid_t) * conns); } else { @@ -1671,7 +1672,7 @@ int snd_hda_hdmi_generic_pcm_prepare(struct hda_pcm_stream *hinfo, * After S3, the audio driver restores pin:cvt selections * but this can happen before gfx is ready and such selection * is overlooked by HW. Thus multiple pins can share a same - * default convertor and mute control will affect each other, + * default converter and mute control will affect each other, * which can cause a resumed audio playback become silent * after S3. */ diff --git a/sound/hda/codecs/hdmi/tegrahdmi.c b/sound/hda/codecs/hdmi/tegrahdmi.c index ebb6410a4831..171d1ee154d9 100644 --- a/sound/hda/codecs/hdmi/tegrahdmi.c +++ b/sound/hda/codecs/hdmi/tegrahdmi.c @@ -227,6 +227,37 @@ static int nvhdmi_chmap_validate(struct hdac_chmap *chmap, return 0; } +static int tegrahdmi_pin_hbr_setup(struct hda_codec *codec, hda_nid_t pin_nid, + int dev_id, bool hbr) +{ + int pinctl, new_pinctl; + + snd_hda_set_dev_select(codec, pin_nid, dev_id); + pinctl = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + + if (pinctl < 0) + return hbr ? -EINVAL : 0; + + new_pinctl = pinctl & ~AC_PINCTL_EPT; + if (hbr) + new_pinctl |= AC_PINCTL_EPT_HBR; + else + new_pinctl |= AC_PINCTL_EPT_NATIVE; + + codec_dbg(codec, "%s: NID=0x%x, %spinctl=0x%x\n", + __func__, pin_nid, + pinctl == new_pinctl ? "" : "new-", + new_pinctl); + + if (pinctl != new_pinctl) + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + new_pinctl); + + return 0; +} + static int tegra_hdmi_init(struct hda_codec *codec) { struct hdmi_spec *spec = codec->spec; @@ -255,6 +286,10 @@ static int tegra_hdmi_init(struct hda_codec *codec) spec->chmap.ops.chmap_validate = nvhdmi_chmap_validate; spec->nv_dp_workaround = true; + // Tegra 3 does not support HBR + if (codec->core.vendor_id != 0x10de0020) + spec->ops.pin_hbr_setup = tegrahdmi_pin_hbr_setup; + return 0; } diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 1623cf926c71..23aa175faedd 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1642,6 +1642,14 @@ static void alc245_fixup_hp_mute_led_coefbit(struct hda_codec *codec, } } +/* mute LED via COEF bit and mic-mute LED via GPIO, called directly */ +static void alc245_fixup_cs35l41_mute_micmute(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc245_fixup_hp_mute_led_coefbit(codec, fix, action); + alc245_fixup_hp_gpio_led(codec, fix, action); +} + static void alc245_fixup_hp_mute_led_v1_coefbit(struct hda_codec *codec, const struct hda_fixup *fix, int action) @@ -1754,6 +1762,25 @@ static void alc295_fixup_hp_pavilion_mute_led_1b(struct hda_codec *codec, spec->mute_led_nid = 0x1b; } +/* + * HP 245 14 inch G10 Notebook PC (SSID 103c:8ba4): the speaker mute LED is + * controlled through the HP_Mute_LED DMI string (NID 0x1a), same as the + * ALC269_FIXUP_HP_MUTE_LED fallback, but the microphone mute LED is wired to + * COEF 0x35 bits 2-3 with an inverted sense. + */ +static void alc236_fixup_hp_mute_led_micmute_coef_inv(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->micmute_led_polarity = 1; + + alc269_fixup_hp_mute_led(codec, fix, action); + alc236_fixup_hp_coef_micmute_led(codec, fix, action); +} + static void alc233_fixup_lenovo_coef_micmute_led(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -1840,6 +1867,48 @@ struct alc298_samsung_amp_desc { unsigned short init_seq[2][2]; }; +/* + * The MSI GE66 Raider 11UE drives its speakers through a Realtek ALC1316 smart + * amplifier, reached over the ALC298 coefficient tunnel. Left unconfigured the + * amplifier's silence detection mutes the speaker path after about 0.2 s of + * quiet and takes about 0.4 s to reopen, so the start of every sound that + * follows a pause is lost. Headphones do not pass through the amplifier and are + * unaffected. + * + * Tunnel layout for ALC286/ALC298: coefficient 0x22 selects the amplifier, 0x23 + * carries the 16-bit register, 0x24 and 0x25 the value's high and low halves, + * and 0x26 commits. Bit 4 of the commit word is a self-clearing go bit. + */ +static void alc298_fixup_alc1316_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const unsigned short init_seq[][2] = { + { 0x0000, 0x01 }, { 0xc001, 0x14 }, { 0xc003, 0x00 }, { 0xc004, 0x11 }, + { 0xc005, 0x00 }, { 0xc006, 0x00 }, { 0xc007, 0x11 }, { 0xc008, 0x11 }, + { 0xc009, 0x00 }, { 0xc730, 0x06 }, { 0xc612, 0x16 }, { 0xc00f, 0xf7 }, + { 0xc00a, 0xd8 }, { 0xc00b, 0xb7 }, { 0xc60b, 0xff }, { 0xc60c, 0x37 }, + { 0xc605, 0xf0 }, { 0xc020, 0x00 }, { 0xc022, 0xd6 }, { 0xc023, 0x00 }, + { 0xc025, 0xd6 }, { 0xc602, 0x07 }, { 0xc603, 0x07 }, { 0xd101, 0x00 }, + { 0xc093, 0x80 }, { 0xc090, 0x87 }, { 0xc091, 0x11 }, { 0xc614, 0x20 }, + { 0xc615, 0x0a }, { 0xc616, 0x02 }, { 0xc617, 0x00 }, { 0xc050, 0x80 }, + { 0xc051, 0x7f }, { 0xc052, 0x00 }, { 0xc053, 0x70 }, { 0xc054, 0x00 }, + { 0xc0a2, 0x01 }, { 0xc09d, 0x80 }, { 0xc09c, 0x71 } + }; + int i; + + if (action != HDA_FIXUP_ACT_INIT) + return; + + alc_write_coef_idx(codec, 0x22, 0x0010); + + for (i = 0; i < ARRAY_SIZE(init_seq); i++) { + alc_write_coef_idx(codec, 0x23, init_seq[i][0]); + alc_write_coef_idx(codec, 0x24, 0x0000); + alc_write_coef_idx(codec, 0x25, init_seq[i][1]); + alc_write_coef_idx(codec, 0x26, 0xb031); + } +} + static void alc298_fixup_samsung_amp(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -2767,6 +2836,19 @@ static void alc275_fixup_gpio4_off(struct hda_codec *codec, } } +static void alc256_fixup_asus_aio_gpio2_low(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gpio_mask |= 0x04; + spec->gpio_dir |= 0x04; + spec->gpio_data &= ~0x04; + } +} + /* Quirk for Thinkpad X1 7th and 8th Gen * The following fixed routing needed * DAC1 (NID 0x02) -> Speaker (NID 0x14); some eq applied secretly @@ -3433,6 +3515,28 @@ static void alc287_fixup_legion_16ithg6_speakers(struct hda_codec *cdc, const st comp_generic_fixup(cdc, action, "i2c", "CLSA0101", "-%s:00-cs35l41-hda.%d", 2); } +static void alc287_fixup_yoga_slim7_carbon_speakers(struct hda_codec *cdc, + const struct hda_fixup *fix, int action) +{ + /* + * The bass speakers are driven by two CS35L41 amps fed over I2S. The codec only + * clocks the I2S bus while pin 0x17 is enabled, but the BIOS marks it unconnected. + * Keep the pin on DAC 0x02 with the other speakers: DAC 0x06 gets powered down + * with stereo streams, which stops the clock and the amps fail to power up. + */ + static const struct hda_pintbl pincfgs[] = { + { 0x17, 0x90170121 }, + { } + }; + static const hda_nid_t conn[] = { 0x02 }; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + snd_hda_apply_pincfgs(cdc, pincfgs); + snd_hda_override_conn_list(cdc, 0x17, ARRAY_SIZE(conn), conn); + } + comp_generic_fixup(cdc, action, "i2c", "CLSA0102", "-%s:00-cs35l41-hda.%d", 2); +} + static void alc285_fixup_asus_ga403u(struct hda_codec *cdc, const struct hda_fixup *fix, int action) { /* @@ -3537,6 +3641,27 @@ static void alc233_fixup_no_audio_jack(struct hda_codec *codec, alc_process_coef_fw(codec, alc233_fixup_no_audio_jack_coefs); } +static const struct coef_fw alc256_asus_speaker_coefs[] = { + WRITE_COEF(0x10, 0x7f20), WRITE_COEF(0x16, 0x0c50), WRITE_COEF(0x35, 0x8d6a), + WRITE_COEF(0x37, 0xfe06), WRITE_COEF(0x57, 0x7f7f), + {} +}; + +static void alc256_fixup_asus_speaker_coefs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* + * The internal speakers of the ASUS ExpertBook PM3606CHA stay silent + * with the default COEF values. These are the values set by the + * Windows driver. They are applied on every init so that they are + * restored after resume, too. + */ + if (action != HDA_FIXUP_ACT_INIT) + return; + alc_process_coef_fw(codec, alc256_asus_speaker_coefs); +} + static void alc256_fixup_mic_no_presence_and_resume(struct hda_codec *codec, const struct hda_fixup *fix, int action) @@ -3661,6 +3786,28 @@ static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec, } } +/* + * The Lenovo Xiaoxin 14 GT (SSID 17aa:3912) uses the same bass-speaker + * pin wiring as its 0x390d/0x3911/0x391a siblings, but on this board the + * codec's vendor processing coefficients (node 0x20) are left at their + * power-on default instead of being programmed the way the vendor + * (Windows) driver programs them. That leaves audible hiss/crackle on + * both the headphone jack and the internal speakers, independent of + * volume, regardless of which pin/DAC routing is used. + * + * This single coefficient write was captured from the vendor driver's + * own init sequence (via a VFIO pass-through of the physical codec to a + * Windows VM) and confirmed to eliminate the headphone jack noise. + */ +static void alc287_fixup_lenovo_xiaoxin_14gt_hp_noise(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + alc_write_coef_idx(codec, 0x36, 0x77d7); +} + static void alc_fixup_yoga_pro7_audio(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -4165,6 +4312,7 @@ enum { ALC256_FIXUP_ASUS_HEADSET_MODE, ALC256_FIXUP_ASUS_MIC, ALC256_FIXUP_ASUS_AIO_GPIO2, + ALC256_FIXUP_ASUS_AIO_GPIO2_LOW, ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, ALC233_FIXUP_LENOVO_MULTI_CODECS, @@ -4181,6 +4329,7 @@ enum { ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, ALC256_FIXUP_HUAWEI_MACH_WX9_PINS, + ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS, ALC298_FIXUP_HUAWEI_MBX_STEREO, ALC295_FIXUP_HP_X360, ALC295_FIXUP_HP_PAVILION_X360, @@ -4193,6 +4342,7 @@ enum { ALC294_FIXUP_ASUS_I2C_HEADSET_MIC, ALC294_FIXUP_ASUS_SPI_HEADSET_MIC, ALC294_FIXUP_ASUS_SPK, + ALC294_FIXUP_ASUS_Q423SA, ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE, ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE, ALC255_FIXUP_ACER_HEADSET_MIC, @@ -4237,10 +4387,12 @@ enum { ALC236_FIXUP_HP_MUTE_LED_COEFBIT2, ALC236_FIXUP_HP_GPIO_LED, ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV, ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, ALC236_FIXUP_HP_15_FD0XXX, ALC236_FIXUP_LENOVO_INV_DMIC, ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_MSI_GE66_ALC1316_AMP, ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, ALC298_FIXUP_LG_GRAM_STYLE_14, @@ -4285,11 +4437,13 @@ enum { ALC295_FIXUP_HP_OMEN, ALC285_FIXUP_HP_SPECTRE_X360, ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, + ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED, ALC623_FIXUP_LENOVO_THINKSTATION_P340, ALC255_FIXUP_ACER_HEADPHONE_AND_MIC, ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST, ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS, ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + ALC287_FIXUP_LEGION_S7_15IMH05_AMP, ALC287_FIXUP_YOGA7_14ITL_SPEAKERS, ALC298_FIXUP_LENOVO_C940_DUET7, ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, @@ -4317,9 +4471,11 @@ enum { ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE, ALC295_FIXUP_FRAMEWORK_LAPTOP_LIMIT_INT_MIC_BOOST, ALC287_FIXUP_LEGION_16ITHG6, + ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN, + ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE, ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS, ALC236_FIXUP_DELL_DUAL_CODECS, ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, @@ -4338,9 +4494,12 @@ enum { ALC245_FIXUP_HP_X360_MUTE_LEDS, ALC287_FIXUP_THINKPAD_I2S_SPK, ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI, + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD, ALC2XX_FIXUP_HEADSET_MIC, ALC289_FIXUP_DELL_CS35L41_SPI_2, ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED, ALC256_FIXUP_HEADPHONE_AMP_VOL, ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX, @@ -4375,8 +4534,10 @@ enum { ALC245_FIXUP_BASS_HP_DAC, ALC245_FIXUP_ACER_MICMUTE_LED, ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED, - ALC236_FIXUP_HP_DMIC, + ALC245_FIXUP_CS35L41_I2C_2_MUTE_MICMUTE_LED, ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, + ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS, + ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE, ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX, ALC287_FIXUP_ACER_MICMUTE_LED, ALC236_FIXUP_DELL_HP_POP_NOISE, @@ -4388,6 +4549,8 @@ enum { ALC285_LENOVO_DAC_RENAME, ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ALC256_FIXUP_IPASON_SMARTBOOK_S1, + ALC256_FIXUP_ASUS_SPEAKER_COEFS, + ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -4437,6 +4600,53 @@ static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); } +static const struct hda_verb legion_amp_verbs[] = { + /* left speaker */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + /* right speaker */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + {} +}; + +static void alc287_fixup_legion_s7_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + snd_hda_sequence_write(codec, legion_amp_verbs); + /* + * The amplifiers accept the setup only a few ms after the first + * write (8 ms is the measured minimum on the test machine), so + * keep a margin. + */ + msleep(20); + snd_hda_sequence_write(codec, legion_amp_verbs); +} + static const struct hda_fixup alc269_fixups[] = { [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { .type = HDA_FIXUP_PINS, @@ -4834,6 +5044,24 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC255_FIXUP_MIC_MUTE_LED }, + [ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x12, 0x90a60130}, + {0x13, 0x40000000}, + {0x14, 0x90170110}, + {0x18, 0x411111f0}, + {0x19, 0x04a11140}, /* use as headset mic, without its own jack detect */ + {0x1a, 0x411111f0}, + {0x1b, 0x90170112}, + {0x1d, 0x40759a05}, + {0x1e, 0x411111f0}, + {0x21, 0x04211020}, + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, [ALC298_FIXUP_HUAWEI_MBX_STEREO] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_huawei_mbx_stereo, @@ -5442,6 +5670,10 @@ static const struct hda_fixup alc269_fixups[] = { /* Set up GPIO2 for the speaker amp */ .v.func = alc_fixup_gpio4, }, + [ALC256_FIXUP_ASUS_AIO_GPIO2_LOW] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_asus_aio_gpio2_low, + }, [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -5669,6 +5901,17 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC }, + [ALC294_FIXUP_ASUS_Q423SA] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Restore bit 10 lost after reboot from Windows */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x1b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x4e4b }, + { } + }, + .chained = true, + .chain_id = ALC2XX_FIXUP_HEADSET_MIC, + }, [ALC295_FIXUP_CHROME_BOOK] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_chromebook, @@ -6033,6 +6276,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_vref, }, + [ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_coef_inv, + }, [ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_gpio, @@ -6055,6 +6302,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_hp_pavilion_mute_led_1b, }, + [ALC298_FIXUP_MSI_GE66_ALC1316_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_alc1316_amp, + }, [ALC298_FIXUP_SAMSUNG_AMP] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_samsung_amp, @@ -6435,6 +6686,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, + [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_ideapad_acpi, + .chained = true, + .chain_id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, + }, [ALC287_FIXUP_LENOVO_YOGA_PRO7] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_yoga_pro7_audio, @@ -6486,41 +6743,13 @@ static const struct hda_fixup alc269_fixups[] = { }, [ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS] = { .type = HDA_FIXUP_VERBS, - //.v.verbs = legion_15imhg05_coefs, - .v.verbs = (const struct hda_verb[]) { - // set left speaker Legion 7i. - { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - // set right speaker Legion 7i. - { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - {} - }, + .v.verbs = legion_amp_verbs, + .chained = true, + .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + }, + [ALC287_FIXUP_LEGION_S7_15IMH05_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_legion_s7_amp, .chained = true, .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, }, @@ -6713,6 +6942,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc287_fixup_legion_16ithg6_speakers, }, + [ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_yoga_slim7_carbon_speakers, + }, [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { @@ -6787,6 +7020,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2, }, + [ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_lenovo_xiaoxin_14gt_hp_noise, + .chained = true, + .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, + }, [ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_dell_inspiron_top_speakers, @@ -6886,6 +7125,18 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, }, + [ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_four, + .chained = true, + .chain_id = ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK, + }, + [ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_tb_vmaster_led, + .chained = true, + .chain_id = ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI, + }, [ALC2XX_FIXUP_HEADSET_MIC] = { .type = HDA_FIXUP_FUNC, .v.func = alc2xx_fixup_headset_mic, @@ -6900,6 +7151,13 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_fixup_acer_sfg16_micmute_led, }, + [ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_acer_sfg16_micmute_led, + .chained = true, + .chained_before = true, + .chain_id = ALC2XX_FIXUP_HEADSET_MIC, + }, [ALC256_FIXUP_HEADPHONE_AMP_VOL] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_decrease_headphone_amp_val, @@ -7084,12 +7342,11 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2, }, - [ALC236_FIXUP_HP_DMIC] = { - .type = HDA_FIXUP_PINS, - .v.pins = (const struct hda_pintbl[]) { - { 0x12, 0x90a60160 }, /* use as internal mic */ - { } - }, + [ALC245_FIXUP_CS35L41_I2C_2_MUTE_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc245_fixup_cs35l41_mute_micmute, + .chained = true, + .chain_id = ALC287_FIXUP_CS35L41_I2C_2, }, [ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO] = { .type = HDA_FIXUP_PINS, @@ -7101,6 +7358,20 @@ static const struct hda_fixup alc269_fixups[] = { { } } }, + [ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x90170111 }, /* tweeters (top speakers) */ + { 0x19, 0x03a1113c }, /* headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE + }, + [ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, + }, [ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX] = { .type = HDA_FIXUP_FUNC, .v.func = cs35l41_fixup_i2c_two, @@ -7174,6 +7445,21 @@ static const struct hda_fixup alc269_fixups[] = { { } }, }, + [ALC256_FIXUP_ASUS_SPEAKER_COEFS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_asus_speaker_coefs, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, + }, + [ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x04a19150 }, /* headset mic jack, no presence detect */ + { } + }, + .chained = true, + .chain_id = ALC245_FIXUP_BASS_HP_DAC + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -7230,7 +7516,13 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_COLDBOOT), SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + /* + * Shared SSID: A315-44P needs GPIO3 micmute LED; AG15-42P needs + * the headset pin remap (BIOS leaves 0x19 as 0x411111f0). One + * entry applies both. GPIO3 is unused on AG15. + */ + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P / AG15-42P", + ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), @@ -7491,6 +7783,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8812, "HP Spectre x360 15-eb1xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), SND_PCI_QUIRK(0x103c, 0x881d, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x881e, "HP Laptop 15s-du3xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8820, "HP ENVY Laptop 13-ba1xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8846, "HP EliteBook 850 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8847, "HP EliteBook x360 830 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x884b, "HP EliteBook 840 Aero G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), @@ -7502,6 +7795,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8870, "HP ZBook Fury 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8873, "HP ZBook Studio 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x887a, "HP Laptop 15s-eq2xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x89fc, "HP Laptop 15s-eq3xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), @@ -7550,6 +7844,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x89d3, "HP EliteBook 645 G9 (MB 89D2)", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x89da, "HP Spectre x360 14t-ea100", ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX), SND_PCI_QUIRK(0x103c, 0x89e7, "HP Elite x2 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x89f6, "HP Pavilion 15-eg2xxx", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8a05, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8a06, "HP Dragonfly Folio G3 2-in-1", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8a0e, "HP Pavilion Laptop 15-eh2xxx", ALC287_FIXUP_HP_GPIO_LED), @@ -7622,6 +7917,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ba4, "HP 245 14 inch G10 Notebook PC", + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV), SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), @@ -7640,10 +7937,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8be1, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be2, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be3, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8be5, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8be5, "HP Envy 16", ALC245_FIXUP_CS35L41_I2C_2_MUTE_MICMUTE_LED), SND_PCI_QUIRK(0x103c, 0x8be6, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be7, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8be9, "HP Envy x360 2-in-1 Laptop 15-fh0xxx", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8bfd, "HP Pavilion 15-eg3xxx", ALC287_FIXUP_HP_GPIO_LED), @@ -7722,12 +8019,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8da0, "HP 16 Clipper OmniBook 7(X360)", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da1, "HP 16 Clipper OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da7, "HP 14 Enstrom OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da8, "HP 16 Piston OmniBook X", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), - SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_DMIC), + SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8dd4, "HP EliteStudio 8 AIO", ALC274_FIXUP_HP_AIO_BIND_DACS), SND_PCI_QUIRK(0x103c, 0x8dd7, "HP Laptop 15-fd0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8de8, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2), @@ -7820,7 +8117,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), - SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS S5406SA/Q423SA", ALC294_FIXUP_ASUS_Q423SA), SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C), @@ -7867,7 +8164,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA), SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), SND_PCI_QUIRK(0x1043, 0x16d3, "ASUS UX5304VA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), @@ -7950,6 +8247,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x31e1, "ASUS B5605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x31f1, "ASUS B3605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3391, "ASUS PM3606CKA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3500, "ASUS E5402WHA", ALC256_FIXUP_ASUS_AIO_GPIO2_LOW), + SND_PCI_QUIRK(0x1043, 0x3501, "ASUS PM3606CHA", ALC256_FIXUP_ASUS_SPEAKER_COEFS), SND_PCI_QUIRK(0x1043, 0x3601, "ASUS PM5406CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3611, "ASUS PM5606CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3701, "ASUS P5406CCA", ALC245_FIXUP_CS35L41_SPI_2), @@ -8031,6 +8330,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, 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), @@ -8206,7 +8507,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x38d5, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C), HDA_CODEC_QUIRK(0x17aa, 0x38d6, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C), HDA_CODEC_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), - SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_S7_15IMH05_AMP), SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940 / Yoga Duet 7", ALC298_FIXUP_LENOVO_C940_DUET7), SND_PCI_QUIRK(0x17aa, 0x3819, "Lenovo 13s Gen2 ITL", ALC287_FIXUP_13S_GEN2_SPEAKERS), @@ -8216,6 +8517,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3827, "Ideapad S740", ALC285_FIXUP_IDEAPAD_S740_COEF), SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3842, "ThinkBook X G2 IAH", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD), + /* IdeaPad Pro 5 14IAH10 shares PCI SSID 17aa:3843 with Yoga Book 9i; + * use codec SSID to distinguish them + */ + HDA_CODEC_QUIRK(0x17aa, 0x3914, "Lenovo IdeaPad Pro 5 14IAH10", + ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED), SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; * use codec SSID to distinguish them @@ -8230,6 +8537,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), + HDA_CODEC_QUIRK(0x17aa, 0x3856, "Lenovo Yoga Slim 7 Carbon 14ACN6", ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), @@ -8243,6 +8551,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { 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), SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + /* Lenovo ThinkBook Plus G3 IAP shares PCI SSID 17aa:3891 with Lenovo Yoga + * Pro 7 14AHP9; use codec SSID to distinguish them. + */ + HDA_CODEC_QUIRK(0x17aa, 0x3875, "ThinkBook Plus G3 IAP", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x387d, "Yoga S780-16 pro Quad AAC", ALC287_FIXUP_TAS2781_I2C), @@ -8309,12 +8621,14 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET), SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), - SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE), SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), SND_PCI_QUIRK(0x17aa, 0x3929, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), SND_PCI_QUIRK(0x17aa, 0x392b, "Thinkbook 13x Gen 5", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD), + HDA_CODEC_QUIRK(0x17aa, 0x3934, "Legion R9000P AFR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), + HDA_CODEC_QUIRK(0x17aa, 0x3935, "Legion R9000P AFR10", ALC287_FIXUP_LENOVO_LEGION_AW88399), HDA_CODEC_QUIRK(0x17aa, 0x3936, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), HDA_CODEC_QUIRK(0x17aa, 0x3937, "Legion R9000P ADR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), HDA_CODEC_QUIRK(0x17aa, 0x3938, "Legion Pro 7 16AFR10H", ALC287_FIXUP_LENOVO_LEGION_AW88399), @@ -8359,6 +8673,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), 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), SND_PCI_QUIRK(0x1a58, 0x2023, "Razer Blade 16 (2025)", ALC298_FIXUP_RAZER_BLADE16_2025_PINS), SND_PCI_QUIRK(0x1a58, 0x300e, "Razer Blade 16 (2025)", @@ -8400,11 +8715,14 @@ 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(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), SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ALC245_FIXUP_MINISFORUM_JACK_DETECT), - SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f4c, 0xb022, "Minisforum AI X1", ALC245_FIXUP_MINISFORUM_JACK_DETECT), + SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", + ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), @@ -8419,9 +8737,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x2782, 0x1701, "Infinix Y4 Max", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x2782, 0x1718, "Acer Aspire AL16-31P", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), + SND_PCI_QUIRK(0x2782, 0xa248, "COLORFUL L1 Plus", ALC233_FIXUP_MEDION_MTL_SPK), SND_PCI_QUIRK(0x7017, 0x2014, "Star Labs StarFighter", ALC233_FIXUP_STARLABS_STARFIGHTER), SND_PCI_QUIRK(0x8086, 0x2073, "Intel NUC 8 Hades Canyon", ALC700_FIXUP_INTEL_HADES_CANYON), @@ -8616,6 +8936,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC298_FIXUP_HUAWEI_MBX_STEREO, .name = "huawei-mbx-stereo"}, {.id = ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE, .name = "alc256-medion-headset"}, {.id = ALC298_FIXUP_SAMSUNG_AMP, .name = "alc298-samsung-amp"}, + {.id = ALC298_FIXUP_MSI_GE66_ALC1316_AMP, .name = "alc298-alc1316-amp"}, {.id = ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, .name = "alc298-samsung-amp-v2-2-amps"}, {.id = ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, .name = "alc298-samsung-amp-v2-4-amps"}, {.id = ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, .name = "alc256-samsung-headphone"}, diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index eff7c83b9c59..c1c7277b6754 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -198,6 +198,24 @@ static void alc897_fixup_lenovo_headset_mode(struct hda_codec *codec, } } +static void alc897_fixup_limit_linein_boost(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PROBE) + return; + + snd_hda_override_amp_caps(codec, 0x1a, HDA_INPUT, + (0x00 << AC_AMPCAP_OFFSET_SHIFT) | + (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x2f << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + snd_hda_override_amp_caps(codec, 0x08, HDA_INPUT, + (0x00 << AC_AMPCAP_OFFSET_SHIFT) | + (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x02 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); +} + static const struct coef_fw alc668_coefs[] = { WRITE_COEF(0x01, 0xbebe), WRITE_COEF(0x02, 0xaaaa), WRITE_COEF(0x03, 0x0), WRITE_COEF(0x04, 0x0180), WRITE_COEF(0x06, 0x0), WRITE_COEF(0x07, 0x0f80), @@ -326,6 +344,7 @@ enum { ALC897_FIXUP_HEADSET_MIC_PIN3, ALC662_FIXUP_CSL_GPIO, ALC897_FIXUP_BEELINK_SER6_AMP, + ALC897_FIXUP_LIMIT_LINEIN_BOOST, }; static const struct hda_fixup alc662_fixups[] = { @@ -787,6 +806,10 @@ static const struct hda_fixup alc662_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_gpio4, }, + [ALC897_FIXUP_LIMIT_LINEIN_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc897_fixup_limit_linein_boost, + }, }; static const struct hda_quirk alc662_fixup_tbl[] = { @@ -857,6 +880,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1b35, 0x1234, "CZC ET26", ALC662_FIXUP_CZC_ET26), SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1e0b, 0x1297, "DECENTA MATX03", ALC897_FIXUP_LIMIT_LINEIN_BOOST), SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), diff --git a/sound/hda/codecs/realtek/alc882.c b/sound/hda/codecs/realtek/alc882.c index fd466b6985f0..800c3d6f5134 100644 --- a/sound/hda/codecs/realtek/alc882.c +++ b/sound/hda/codecs/realtek/alc882.c @@ -372,7 +372,7 @@ static const struct hda_fixup alc882_fixups[] = { [ALC883_FIXUP_ACER_EAPD] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { - /* eanable EAPD on Acer laptops */ + /* enable EAPD on Acer laptops */ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 }, { } diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig index f90f1dfcec68..b7e2a6fa4258 100644 --- a/sound/hda/codecs/side-codecs/Kconfig +++ b/sound/hda/codecs/side-codecs/Kconfig @@ -22,6 +22,7 @@ config SND_HDA_SCODEC_AW88399_I2C depends on I2C depends on ACPI depends on SND_SOC + imply SERIAL_MULTI_INSTANTIATE select SND_HDA_SCODEC_AW88399 select SND_SOC_AW88399_LIB help diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 439ad45557a4..1edb9b61df48 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "../generic.h" #include "hda_component.h" @@ -118,7 +119,6 @@ static int aw88399_hda_init(struct aw88399_hda *aw88399) mutex_init(&core->lock); core->reset_gpio = aw88399->reset_gpio; core->regmap = aw88399->regmap; - core->bsts_unreliable = aw88399->bsts_unreliable; aw88399_hw_reset(core); @@ -130,10 +130,17 @@ static int aw88399_hda_init(struct aw88399_hda *aw88399) if (core->aw_pa) aw88399_dev_set_channel(core, aw88399->channel); - ret = aw88399_request_firmware_file(core); + char *fw_name __free(kfree) = + kasprintf(GFP_KERNEL, "awinic/aw88399_acf_%s.bin", aw88399->acpi_subsystem_id); + if (!fw_name) + return -ENOMEM; + string_lower(fw_name, fw_name); + ret = aw88399_request_firmware_file(core, fw_name); if (ret) return ret; + dev_info(dev, "Loaded firmware %s\n", fw_name); + aw88399->core = core; aw88399->aw_dev = core->aw_pa; @@ -155,41 +162,22 @@ static int aw88399_swap_channels(struct aw88399_hda *aw88399) return 0; } -static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399) -{ - /* - * BSTS (boost-finished) status bit does not reliably report on - * some hardware. On certain Lenovo Legion laptops, both amps - * report BSTS=0 (boost not finished) during normal playback - * despite clean audio output. Skip BSTS in the startup status - * check to avoid false init failures. - */ - aw88399->bsts_unreliable = true; - dev_dbg(aw88399->dev, "BSTS status check disabled\n"); - return 0; -} - -static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399) -{ - aw88399_swap_channels(aw88399); - aw88399_skip_bsts_check(aw88399); - return 0; -} - struct aw88399_prop_model { const char *ssid; int (*apply_prop)(struct aw88399_hda *aw88399); }; static const struct aw88399_prop_model aw88399_prop_model_table[] = { - { "17AA3906", aw88399_apply_legion_quirks }, - { "17AA3907", aw88399_apply_legion_quirks }, - { "17AA3927", aw88399_apply_legion_quirks }, - { "17AA3928", aw88399_apply_legion_quirks }, - { "17AA3936", aw88399_apply_legion_quirks }, - { "17AA3937", aw88399_apply_legion_quirks }, - { "17AA3938", aw88399_apply_legion_quirks }, - { "17AA3939", aw88399_apply_legion_quirks }, + { "17AA3906", aw88399_swap_channels }, + { "17AA3907", aw88399_swap_channels }, + { "17AA3927", aw88399_swap_channels }, + { "17AA3928", aw88399_swap_channels }, + { "17AA3934", aw88399_swap_channels }, + { "17AA3935", aw88399_swap_channels }, + { "17AA3936", aw88399_swap_channels }, + { "17AA3937", aw88399_swap_channels }, + { "17AA3938", aw88399_swap_channels }, + { "17AA3939", aw88399_swap_channels }, { } }; @@ -215,8 +203,10 @@ static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) return -ENODEV; sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); - if (IS_ERR_OR_NULL(sub)) - return 0; + 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); @@ -369,4 +359,4 @@ MODULE_DESCRIPTION("AW88399 HDA driver"); MODULE_AUTHOR("Yakov Till "); MODULE_AUTHOR("Marco Giunta "); MODULE_LICENSE("GPL"); -MODULE_FIRMWARE("aw88399_acf.bin"); +MODULE_FIRMWARE("awinic/aw88399_acf_*.bin"); diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.h b/sound/hda/codecs/side-codecs/aw88399_hda.h index ca0aa5279298..af71083124bd 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.h +++ b/sound/hda/codecs/side-codecs/aw88399_hda.h @@ -19,7 +19,6 @@ struct aw88399_hda { struct gpio_desc *reset_gpio; struct aw_device *aw_dev; struct aw88399 *core; - bool bsts_unreliable; const char *acpi_subsystem_id; int index; diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c index 32abb71dd401..f879e3ffbb53 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c @@ -1907,7 +1907,6 @@ int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, hw_cfg->valid = false; hw_cfg->gpio1.valid = false; hw_cfg->gpio2.valid = false; - acpi_dev_put(cs35l41->dacpi); return ret; } @@ -1927,10 +1926,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i cs35l41->dacpi = adev; struct device *physdev __free(put_device) = acpi_bus_get_primary_device(adev); - if (!physdev) { - acpi_dev_put(adev); + if (!physdev) return -ENODEV; - } sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR(sub)) @@ -1985,8 +1982,10 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i dev_set_drvdata(dev, cs35l41); ret = cs35l41_hda_read_acpi(cs35l41, device_name, id); - if (ret) - return dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); + if (ret) { + dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); + goto err_acpi; + } if (IS_ERR(cs35l41->reset_gpio)) { ret = PTR_ERR(cs35l41->reset_gpio); @@ -2073,6 +2072,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i err: if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); +err_acpi: gpiod_put(cs35l41->reset_gpio); gpiod_put(cs35l41->cs_gpio); acpi_dev_put(cs35l41->dacpi); diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c index fdf406e92fca..59b82238f2b8 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c @@ -23,6 +23,8 @@ static int cs35l41_hda_i2c_probe(struct i2c_client *clt) device_name = "CLSA0100"; else if (strstr(dev_name(&clt->dev), "CLSA0101")) device_name = "CLSA0101"; + else if (strstr(dev_name(&clt->dev), "CLSA0102")) + device_name = "CLSA0102"; else if (strstr(dev_name(&clt->dev), "CSC3551")) device_name = "CSC3551"; else @@ -45,6 +47,7 @@ static const struct i2c_device_id cs35l41_hda_i2c_id[] = { static const struct acpi_device_id cs35l41_acpi_hda_match[] = { {"CLSA0100", 0 }, {"CLSA0101", 0 }, + {"CLSA0102", 0 }, {"CSC3551", 0 }, {} }; diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index 416d7bf3e289..c50c7e689533 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -85,6 +85,8 @@ 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 }, + { "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 }, { "10431433", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, { "10431463", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, @@ -126,6 +128,8 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "10433A60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 }, { "17AA3865", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3866", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, + { "17AA3845", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, + { "17AA3846", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA386E", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 2, -1, 0, 0, 0 }, { "17AA386F", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3874", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, @@ -439,6 +443,33 @@ static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *phy return 0; } +/* + * Device CLSA0102 (Lenovo Yoga Slim 7 Carbon 14ACN6) has the same ACPI layout as CLSA010(0/1): + * no _DSD, both amps in one node, reset on GPIO index 0. It has no speaker ID GPIO, the + * interrupt GPIO sits at index 2. The speakers use an external VSPK supply switched by GPIO1 + * (the Windows driver sets ExternalVspkControl and keeps the boost converter off). + */ +static int lenovo_yoga_slim7_carbon_no_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, + int id, const char *hid) +{ + struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; + + /* check I2C address to assign the index */ + cs35l41->index = id == 0x40 ? 0 : 1; + cs35l41->channel_index = 0; + cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); + cs35l41->speaker_id = -ENOENT; + hw_cfg->spk_pos = cs35l41->index; + hw_cfg->bst_type = CS35L41_EXT_BOOST; + hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; + hw_cfg->gpio1.valid = true; + hw_cfg->gpio2.func = CS35L41_INTERRUPT; + hw_cfg->gpio2.valid = true; + hw_cfg->valid = true; + + return 0; +} + static int missing_speaker_id_gpio2(struct cs35l41_hda *cs35l41, struct device *physdev, int id, const char *hid) { @@ -463,6 +494,7 @@ struct cs35l41_prop_model { static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CLSA0100", NULL, lenovo_legion_no_acpi }, { "CLSA0101", NULL, lenovo_legion_no_acpi }, + { "CLSA0102", NULL, lenovo_yoga_slim7_carbon_no_acpi }, { "CSC3551", "10251826", generic_dsd_config }, { "CSC3551", "1025182C", generic_dsd_config }, { "CSC3551", "10251844", generic_dsd_config }, @@ -511,6 +543,8 @@ 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", "103C8E60", generic_dsd_config }, + { "CSC3551", "103C8F07", generic_dsd_config }, { "CSC3551", "104312AF", generic_dsd_config }, { "CSC3551", "10431433", generic_dsd_config }, { "CSC3551", "10431463", generic_dsd_config }, @@ -551,6 +585,8 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "10433A40", generic_dsd_config }, { "CSC3551", "10433A50", generic_dsd_config }, { "CSC3551", "10433A60", generic_dsd_config }, + { "CSC3551", "17AA3845", generic_dsd_config }, + { "CSC3551", "17AA3846", generic_dsd_config }, { "CSC3551", "17AA3865", generic_dsd_config }, { "CSC3551", "17AA3866", generic_dsd_config }, { "CSC3551", "17AA386E", generic_dsd_config }, diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 161ca965c63e..0c8faa583baa 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -334,10 +334,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h) } cd->cali_dat_sz_per_dev = TAS2563_CAL_DATA_SIZE * TASDEV_CALIB_N; + cd->total_sz = 0; /* extra byte for each device is the device number */ - cd->total_sz = (cd->cali_dat_sz_per_dev + 1) * p->ndev; - data = cd->data = devm_kzalloc(p->dev, cd->total_sz, + data = cd->data = devm_kzalloc(p->dev, + (cd->cali_dat_sz_per_dev + 1) * p->ndev, GFP_KERNEL); if (!data) return -ENOMEM; @@ -351,7 +352,6 @@ static int tas2563_save_calibration(struct tas2781_hda *h) if (ret < 0 || ret >= sizeof(var8) - 1) { dev_err(p->dev, "%s: Read %s failed\n", __func__, var8); - cd->total_sz = 0; return -EINVAL; } /* @@ -369,7 +369,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h) dev_warn(p->dev, "Dev %d: Caldat[%d] read failed %ld\n", i, j, status); - cd->total_sz = 0; + return -EINVAL; + } + if (*(uint32_t *)&data[offset] == 0) { + dev_warn(p->dev, + "Dev %d: Caldat[%d] is zero\n", i, j); return -EINVAL; } bedata = cpu_to_be32(*(uint32_t *)&data[offset]); @@ -378,12 +382,7 @@ static int tas2563_save_calibration(struct tas2781_hda *h) } } - if (cd->total_sz != offset) { - dev_err(p->dev, "%s: tot_size(%lu) and offset(%u) mismatch\n", - __func__, cd->total_sz, offset); - cd->total_sz = 0; - return -EINVAL; - } + cd->total_sz = offset; r->r0_reg = TAS2563_CAL_R0; r->invr0_reg = TAS2563_CAL_INVR0; diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c index ee3bd21adc36..9a8ba43667f7 100644 --- a/sound/hda/codecs/sigmatel.c +++ b/sound/hda/codecs/sigmatel.c @@ -361,7 +361,7 @@ static unsigned int stac_vref_led_power_filter(struct hda_codec *codec, return snd_hda_gen_path_power_filter(codec, nid, power_state); } -/* update mute-LED accoring to the master switch */ +/* update mute-LED according to the master switch */ static void stac_update_led_status(struct hda_codec *codec, bool muted) { struct sigmatel_spec *spec = codec->spec; @@ -1710,7 +1710,7 @@ static const struct hda_pintbl ref92hd73xx_pin_configs[] = { { 0x11, 0x01014030 }, // CD in { 0x12, 0x02319040 }, - // Digial Mic ins + // Digital Mic ins { 0x13, 0x90a000f0 }, { 0x14, 0x90a000f0 }, // Digital outs @@ -3817,7 +3817,7 @@ static const struct hda_pintbl dell_3st_pin_configs[] = { static void stac927x_fixup_ref_no_jd(struct hda_codec *codec, const struct hda_fixup *fix, int action) { - /* no jack detecion for ref-no-jd model */ + /* no jack detection for ref-no-jd model */ if (action == HDA_FIXUP_ACT_PRE_PROBE) codec->no_jack_detect = 1; } diff --git a/sound/hda/codecs/via.c b/sound/hda/codecs/via.c index 807312cffd35..0641fc14b346 100644 --- a/sound/hda/codecs/via.c +++ b/sound/hda/codecs/via.c @@ -933,6 +933,7 @@ static const struct hda_fixup via_fixups[] = { static const struct hda_quirk vt2002p_fixups[] = { SND_PCI_QUIRK(0x1043, 0x13f7, "Asus B23E", VIA_FIXUP_POWER_SAVE), SND_PCI_QUIRK(0x1043, 0x1487, "Asus G75", VIA_FIXUP_ASUS_G75), + SND_PCI_QUIRK(0x1043, 0x1c33, "Asus X55A", VIA_FIXUP_POWER_SAVE), SND_PCI_QUIRK(0x1043, 0x8532, "Asus X202E", VIA_FIXUP_INTMIC_BOOST), SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", VIA_FIXUP_POWER_SAVE), {} diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c index 6a728a773556..957feace0f58 100644 --- a/sound/hda/common/bind.c +++ b/sound/hda/common/bind.c @@ -89,8 +89,12 @@ static int hda_codec_driver_probe(struct device *dev) struct hda_codec *codec = dev_to_hda_codec(dev); struct module *owner = dev->driver->owner; struct hda_codec_driver *driver = hda_codec_to_driver(codec); + bool probed = false; int err; + /* unsol events are still blocked until registered */ + codec->core.unsol_disabled = false; + if (codec->bus->core.ext_ops) { if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach)) return -EINVAL; @@ -119,7 +123,8 @@ static int hda_codec_driver_probe(struct device *dev) err = driver->ops->probe(codec, codec->preset); if (err < 0) - goto error_module_put; + goto error_module; + probed = true; err = snd_hda_codec_build_pcms(codec); if (err < 0) goto error_module; @@ -138,7 +143,9 @@ static int hda_codec_driver_probe(struct device *dev) return 0; error_module: - if (driver->ops->remove) + snd_hdac_device_disable_unsol(&codec->core); + cancel_delayed_work_sync(&codec->jackpoll_work); + if (probed && driver->ops->remove) driver->ops->remove(codec); error_module_put: module_put(owner); @@ -154,6 +161,10 @@ static int hda_codec_driver_remove(struct device *dev) struct hda_codec *codec = dev_to_hda_codec(dev); struct hda_codec_driver *driver = hda_codec_to_driver(codec); + /* stop asynchronous jack handling before freeing driver resources */ + snd_hdac_device_disable_unsol(&codec->core); + cancel_delayed_work_sync(&codec->jackpoll_work); + if (codec->bus->core.ext_ops) { if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach)) return -EINVAL; diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 5d9b53bd64ea..31e5fad78b0f 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -757,11 +757,15 @@ static void codec_release_pcms(struct hda_codec *codec) */ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec) { + struct hda_bus *bus = codec->bus; + if (codec->core.registered) { /* pm_runtime_put() is called in snd_hdac_device_exit() */ pm_runtime_get_noresume(hda_codec_dev(codec)); pm_runtime_disable(hda_codec_dev(codec)); - codec->core.registered = 0; + spin_lock_irq(&bus->core.reg_lock); + codec->core.registered = false; + spin_unlock_irq(&bus->core.reg_lock); } snd_hda_codec_disconnect_pcms(codec); @@ -808,6 +812,8 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable) */ void snd_hda_codec_register(struct hda_codec *codec) { + struct hda_bus *bus = codec->bus; + if (codec->core.registered) return; if (device_is_registered(hda_codec_dev(codec))) { @@ -815,7 +821,9 @@ void snd_hda_codec_register(struct hda_codec *codec) pm_runtime_enable(hda_codec_dev(codec)); /* it was powered up in snd_hda_codec_new(), now all done */ snd_hda_power_down(codec); - codec->core.registered = 1; + spin_lock_irq(&bus->core.reg_lock); + codec->core.registered = true; + spin_unlock_irq(&bus->core.reg_lock); } } EXPORT_SYMBOL_GPL(snd_hda_codec_register); @@ -921,7 +929,6 @@ snd_hda_codec_device_init(struct hda_bus *bus, unsigned int codec_addr, snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16); snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8); snd_array_init(&codec->spdif_out, sizeof(struct hda_spdif_out), 16); - snd_array_init(&codec->jacktbl, sizeof(struct hda_jack_tbl), 16); snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8); INIT_LIST_HEAD(&codec->conn_list); INIT_LIST_HEAD(&codec->pcm_list_head); @@ -2018,7 +2025,7 @@ int snd_hda_add_vmaster_hook(struct hda_codec *codec, if (!hook->hook || !hook->sw_kctl) return 0; hook->codec = codec; - snd_ctl_add_vmaster_hook(hook->sw_kctl, vmaster_hook, hook); + snd_ctl_add_vmaster_hook(codec->card, hook->sw_kctl, vmaster_hook, hook); return 0; } EXPORT_SYMBOL_GPL(snd_hda_add_vmaster_hook); @@ -3040,6 +3047,7 @@ void snd_hda_codec_shutdown(struct hda_codec *codec) codec->jackpoll_interval = 0; /* don't poll any longer */ cancel_delayed_work_sync(&codec->jackpoll_work); + snd_hdac_device_disable_unsol(&codec->core); list_for_each_entry(cpcm, &codec->pcm_list_head, list) snd_pcm_suspend_all(cpcm->pcm); @@ -3774,7 +3782,7 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open); /** - * snd_hda_multi_out_analog_prepare - Preapre the analog outputs. + * snd_hda_multi_out_analog_prepare - Prepare the analog outputs. * @codec: the HDA codec * @mout: hda_multi_out object * @stream_tag: stream tag to assign diff --git a/sound/hda/common/hda_jack.h b/sound/hda/common/hda_jack.h index e9b9970c59ed..a06b315e4b05 100644 --- a/sound/hda/common/hda_jack.h +++ b/sound/hda/common/hda_jack.h @@ -44,6 +44,7 @@ struct hda_jack_tbl { int type; int button_state; struct snd_jack *jack; + struct hda_jack_tbl *next; }; struct hda_jack_keymap { diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c index 1d6b0f0e6f27..6b8aa1a6c008 100644 --- a/sound/hda/common/jack.c +++ b/sound/hda/common/jack.c @@ -76,12 +76,11 @@ static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id) struct hda_jack_tbl * snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!nid || !jack) + if (!nid) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid == nid && jack->dev_id == dev_id) return jack; return NULL; @@ -98,12 +97,11 @@ struct hda_jack_tbl * snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag, int dev_id) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!tag || !jack) + if (!tag) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->tag == tag && jack->dev_id == dev_id) return jack; return NULL; @@ -113,12 +111,11 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag); static struct hda_jack_tbl * any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!nid || !jack) + if (!nid) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid == nid) return jack; return NULL; @@ -142,12 +139,19 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) if (jack) return jack; - jack = snd_array_new(&codec->jacktbl); + jack = kzalloc_obj(*jack); if (!jack) return NULL; jack->nid = nid; jack->dev_id = dev_id; jack->jack_dirty = 1; + if (!codec->jacktbl) + codec->jacktbl = jack; + else + codec->jacktbl_last->next = jack; + codec->jacktbl_last = jack; + codec->jacktbl_used++; + if (existing_nid_jack) { jack->tag = existing_nid_jack->tag; @@ -158,7 +162,7 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) */ jack->jack_detect = existing_nid_jack->jack_detect; } else { - jack->tag = codec->jacktbl.used; + jack->tag = codec->jacktbl_used; } return jack; @@ -166,10 +170,9 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) void snd_hda_jack_tbl_disconnect(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack->next) { if (!codec->bus->shutdown && jack->jack) snd_device_disconnect(codec->card, jack->jack); } @@ -177,22 +180,31 @@ void snd_hda_jack_tbl_disconnect(struct hda_codec *codec) void snd_hda_jack_tbl_clear(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack, *jack_next; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack_next) { struct hda_jack_callback *cb, *next; - /* free jack instances manually when clearing/reconfiguring */ - if (!codec->bus->shutdown && jack->jack) - snd_device_free(codec->card, jack->jack); + jack_next = jack->next; + + if (jack->jack) { + /* fingers away from stale data */ + jack->jack->private_data = NULL; + jack->jack->private_free = NULL; + /* free jack instances manually when clearing/reconfiguring */ + if (!codec->bus->shutdown) + snd_device_free(codec->card, jack->jack); + } for (cb = jack->callback; cb; cb = next) { next = cb->next; kfree(cb); } + kfree(jack); } - snd_array_free(&codec->jacktbl); + + codec->jacktbl = codec->jacktbl_last = NULL; + codec->jacktbl_used = 0; } #define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE) @@ -238,10 +250,9 @@ static void jack_detect_update(struct hda_codec *codec, */ void snd_hda_jack_set_dirty_all(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) jack->jack_dirty = 1; } @@ -389,12 +400,15 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable); int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid, hda_nid_t gating_nid) { - struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0); - struct hda_jack_tbl *gating = - snd_hda_jack_tbl_new(codec, gating_nid, 0); + struct hda_jack_tbl *gated, *gating; WARN_ON(codec->dp_mst); + if (!snd_hda_jack_tbl_new(codec, gated_nid, 0) || + !snd_hda_jack_tbl_new(codec, gating_nid, 0)) + return -ENOMEM; + gated = snd_hda_jack_tbl_get_mst(codec, gated_nid, 0); + gating = snd_hda_jack_tbl_get_mst(codec, gating_nid, 0); if (!gated || !gating) return -EINVAL; @@ -475,19 +489,17 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_set_button_state); void snd_hda_jack_report_sync(struct hda_codec *codec) { struct hda_jack_tbl *jack; - int i, state; + int state; /* update all jacks at first */ - jack = codec->jacktbl.list; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) jack_detect_update(codec, jack); /* report the updated jacks; it's done after updating all jacks * to make sure that all gating jacks properly have been set */ - jack = codec->jacktbl.list; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) { if (!jack->jack || jack->block_report) continue; @@ -755,10 +767,10 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event); */ void snd_hda_jack_poll_all(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i, changes = 0; + struct hda_jack_tbl *jack; + int changes = 0; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack->next) { unsigned int old_sense; if (!jack->nid || !jack->jack_dirty || jack->phantom_jack) continue; diff --git a/sound/hda/controllers/acpi.c b/sound/hda/controllers/acpi.c index 505cc97e0ee9..04b261dde22e 100644 --- a/sound/hda/controllers/acpi.c +++ b/sound/hda/controllers/acpi.c @@ -266,6 +266,38 @@ static void hda_acpi_shutdown(struct platform_device *pdev) azx_stop_chip(chip); } +static int hda_acpi_runtime_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + + if (chip && chip->running) { + /* enable controller wake up event */ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) | + STATESTS_INT_MASK); + + azx_stop_chip(chip); + azx_enter_link_reset(chip); + } + + return 0; +} + +static int hda_acpi_runtime_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + + if (chip && chip->running) { + azx_init_chip(chip, 1); + /* disable controller wake up event */ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) & + ~STATESTS_INT_MASK); + } + + return 0; +} + static int hda_acpi_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); @@ -274,6 +306,12 @@ static int hda_acpi_suspend(struct device *dev) rc = pm_runtime_force_suspend(dev); if (rc < 0) return rc; + /* + * Runtime PM is never enabled for this device, so the force_suspend + * above does not reach the runtime callback; stop the controller + * explicitly. + */ + hda_acpi_runtime_suspend(dev); snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); return 0; @@ -287,6 +325,8 @@ static int hda_acpi_resume(struct device *dev) rc = pm_runtime_force_resume(dev); if (rc < 0) return rc; + /* See hda_acpi_suspend(): re-init the controller explicitly. */ + hda_acpi_runtime_resume(dev); snd_power_change_state(card, SNDRV_CTL_POWER_D0); return 0; @@ -294,6 +334,7 @@ static int hda_acpi_resume(struct device *dev) static const struct dev_pm_ops hda_acpi_pm = { SYSTEM_SLEEP_PM_OPS(hda_acpi_suspend, hda_acpi_resume) + RUNTIME_PM_OPS(hda_acpi_runtime_suspend, hda_acpi_runtime_resume, NULL) }; static const struct hda_data nvidia_hda_data = { diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 24015c73a67f..6b80b59b8743 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -1184,7 +1184,7 @@ static const struct dev_pm_ops azx_pm = { }; -static int azx_probe_continue(struct azx *chip); +static int azx_probe_continue(struct azx *chip, bool free_on_error); #ifdef SUPPORT_VGA_SWITCHEROO static struct pci_dev *get_bound_vga(struct pci_dev *pci); @@ -1211,8 +1211,10 @@ static void azx_vs_set_state(struct pci_dev *pci, if (!disabled) { dev_info(chip->card->dev, "Start delayed initialization\n"); - if (azx_probe_continue(chip) < 0) + if (azx_probe_continue(chip, false) < 0) { + hda->init_failed = 1; dev_err(chip->card->dev, "initialization error\n"); + } } } else { dev_info(chip->card->dev, "%s via vga_switcheroo\n", @@ -1251,8 +1253,6 @@ static bool azx_vs_can_switch(struct pci_dev *pci) struct hda_intel *hda = container_of(chip, struct hda_intel, chip); wait_for_completion(&hda->probe_wait); - if (hda->init_failed) - return false; if (chip->disabled || !hda->probe_continued) return true; if (snd_hda_lock_devices(&chip->bus)) @@ -1380,7 +1380,6 @@ static void azx_free(struct azx *chip) * actually stop the chip) to allow GPU to suspend via * vga_switcheroo, and print a warning. */ - dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n"); pm_runtime_disable(&pci->dev); pm_runtime_set_suspended(&pci->dev); pm_runtime_enable(&pci->dev); @@ -1738,7 +1737,12 @@ static void azx_check_snoop_available(struct azx *chip) static void azx_probe_work(struct work_struct *work) { struct hda_intel *hda = container_of(work, struct hda_intel, probe_work.work); - azx_probe_continue(&hda->chip); + + if (azx_probe_continue(&hda->chip, false) < 0) { + hda->init_failed = 1; + complete_all(&hda->probe_wait); + snd_card_disconnect(hda->chip.card); + } } static int default_bdl_pos_adj(struct azx *chip) @@ -2356,7 +2360,7 @@ static const unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = { [AZX_DRIVER_TERA] = 1, }; -static int azx_probe_continue(struct azx *chip) +static int azx_probe_continue(struct azx *chip, bool free_on_error) { struct hda_intel *hda = container_of(chip, struct hda_intel, chip); struct hdac_bus *bus = azx_bus(chip); @@ -2447,8 +2451,12 @@ static int azx_probe_continue(struct azx *chip) out_free: if (err < 0) { - pci_set_drvdata(pci, NULL); - snd_card_free(chip->card); + if (hda->vga_switcheroo_registered) + dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n"); + if (free_on_error) { + pci_set_drvdata(pci, NULL); + snd_card_free(chip->card); + } return err; } @@ -2481,12 +2489,17 @@ static void azx_remove(struct pci_dev *pci) static void azx_shutdown(struct pci_dev *pci) { struct snd_card *card = pci_get_drvdata(pci); + struct hda_intel *hda; struct azx *chip; if (!card) return; chip = card->private_data; - if (chip && chip->running) + if (!chip) + return; + hda = container_of(chip, struct hda_intel, chip); + cancel_delayed_work_sync(&hda->probe_work); + if (chip->running) __azx_shutdown_chip(chip, true); } diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 81498f1e413e..02462bd035e7 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -21,7 +21,7 @@ static const struct hdac_bus_ops default_ops = { }; /** - * snd_hdac_bus_init - initialize a HD-audio bas bus + * snd_hdac_bus_init - initialize a HD-audio bus device * @bus: the pointer to bus object * @dev: device pointer * @ops: bus verb operators @@ -121,11 +121,15 @@ int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr, break; /* process pending verbs */ err = bus->ops->get_response(bus, addr, &tmp); - if (err) + if (err) { + snd_hdac_bus_reset_response_counter(bus, addr); break; + } } if (!err && res) { err = bus->ops->get_response(bus, addr, res); + if (err) + snd_hdac_bus_reset_response_counter(bus, addr); trace_hda_get_response(bus, addr, *res); } return err; @@ -180,17 +184,39 @@ static void snd_hdac_bus_process_unsol_events(struct work_struct *work) if (!(caddr & (1 << 4))) /* no unsolicited event? */ continue; codec = bus->caddr_tbl[caddr & 0x0f]; - if (!codec || !codec->registered) + if (!codec || !codec->registered || codec->unsol_disabled) + continue; + if (!codec->dev.driver) + continue; + drv = drv_to_hdac_driver(codec->dev.driver); + if (!drv->unsol_event) continue; spin_unlock_irq(&bus->reg_lock); - drv = drv_to_hdac_driver(codec->dev.driver); - if (drv->unsol_event) - drv->unsol_event(codec, res); + drv->unsol_event(codec, res); spin_lock_irq(&bus->reg_lock); } spin_unlock_irq(&bus->reg_lock); } +/** + * snd_hdac_device_disable_unsol - block and flush unsol events for the codec + * @codec: the HDA core device + * + * Stop dispatching the unsolicited events to the given codec, and wait for + * the pending unsol event handler to finish. Called at unbinding the codec + * driver before releasing the driver resources. + */ +void snd_hdac_device_disable_unsol(struct hdac_device *codec) +{ + struct hdac_bus *bus = codec->bus; + + spin_lock_irq(&bus->reg_lock); + codec->unsol_disabled = true; + spin_unlock_irq(&bus->reg_lock); + flush_work(&bus->unsol_work); +} +EXPORT_SYMBOL_GPL(snd_hdac_device_disable_unsol); + /** * snd_hdac_bus_add_device - Add a codec to bus * @bus: HDA core bus diff --git a/sound/hda/core/component.c b/sound/hda/core/component.c index 04755903880e..83b31186b027 100644 --- a/sound/hda/core/component.c +++ b/sound/hda/core/component.c @@ -70,10 +70,7 @@ void snd_hdac_display_power(struct hdac_bus *bus, unsigned int idx, bool enable) dev_dbg(bus->dev, "display power %s\n", str_enable_disable(enable)); guard(mutex)(&bus->lock); - if (enable) - set_bit(idx, &bus->display_power_status); - else - clear_bit(idx, &bus->display_power_status); + assign_bit(idx, &bus->display_power_status, enable); if (!acomp || !acomp->ops) return; diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index 78855ac357c6..a7acd3418ae8 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -396,6 +396,25 @@ int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr, } EXPORT_SYMBOL_GPL(snd_hdac_bus_get_response); +/** + * snd_hdac_bus_reset_response_counter - reset the pending response count + * @bus: HD-audio core bus + * @addr: codec address + * + * Drop the number of commands that are still waiting for a response from + * the given codec address. This is called when a verb is known to be dead, + * e.g. after a response timeout, so that a lost response won't leave the + * counter non-zero forever and block all later verbs to the same codec. + */ +void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus, unsigned int addr) +{ + if (addr >= HDA_MAX_CODECS) + return; + + guard(spinlock_irq)(&bus->reg_lock); + bus->rirb.cmds[addr] = 0; +} + #define HDAC_MAX_CAPS 10 /** * snd_hdac_bus_parse_capabilities - parse capability structure @@ -781,9 +800,6 @@ EXPORT_SYMBOL_GPL(snd_hdac_bus_free_stream_pages); */ void snd_hdac_bus_link_power(struct hdac_device *codec, bool enable) { - if (enable) - set_bit(codec->addr, &codec->bus->codec_powered); - else - clear_bit(codec->addr, &codec->bus->codec_powered); + assign_bit(codec->addr, &codec->bus->codec_powered, enable); } EXPORT_SYMBOL_GPL(snd_hdac_bus_link_power); diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c index a45f61e12c1a..6187ffaedbfa 100644 --- a/sound/hda/core/device.c +++ b/sound/hda/core/device.c @@ -572,14 +572,12 @@ EXPORT_SYMBOL_GPL(snd_hdac_power_up); /** * snd_hdac_power_down - power down the codec * @codec: the codec object - * - * Returns zero if successful, or a negative error code. */ -int snd_hdac_power_down(struct hdac_device *codec) +void snd_hdac_power_down(struct hdac_device *codec) { struct device *dev = &codec->dev; - return pm_runtime_put_autosuspend(dev); + pm_runtime_put_autosuspend(dev); } EXPORT_SYMBOL_GPL(snd_hdac_power_down); @@ -624,14 +622,11 @@ int snd_hdac_keep_power_up(struct hdac_device *codec) * * Like snd_hdac_power_up_pm(), this function is used in a recursive * code path like init code which may be called by PM suspend/resume again. - * - * Returns zero if successful, or a negative error code. */ -int snd_hdac_power_down_pm(struct hdac_device *codec) +void snd_hdac_power_down_pm(struct hdac_device *codec) { if (atomic_dec_if_positive(&codec->in_pm) < 0) - return snd_hdac_power_down(codec); - return 0; + snd_hdac_power_down(codec); } EXPORT_SYMBOL_GPL(snd_hdac_power_down_pm); #endif diff --git a/sound/hda/core/intel-nhlt.c b/sound/hda/core/intel-nhlt.c index 6d72a871bda0..af4d469dbd76 100644 --- a/sound/hda/core/intel-nhlt.c +++ b/sound/hda/core/intel-nhlt.c @@ -351,7 +351,7 @@ int intel_nhlt_ssp_device_type(struct device *dev, struct nhlt_acpi_table *nhlt, int i; if (!nhlt) { - dev_err(dev, "%s: NHLT table is missing (query for SSP%d)\n", + dev_dbg(dev, "%s: NHLT table is missing (query for SSP%d)\n", __func__, virtual_bus_id); return -EINVAL; } @@ -369,7 +369,7 @@ int intel_nhlt_ssp_device_type(struct device *dev, struct nhlt_acpi_table *nhlt, epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); } - dev_err(dev, "%s: No match for SSP%d in NHLT table\n", __func__, + dev_dbg(dev, "%s: No match for SSP%d in NHLT table\n", __func__, virtual_bus_id); dev_dbg(dev, "Available endpoints:\n"); diff --git a/sound/hda/core/local.h b/sound/hda/core/local.h index 5f03b203c416..b9e58395decb 100644 --- a/sound/hda/core/local.h +++ b/sound/hda/core/local.h @@ -16,6 +16,8 @@ int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec); void snd_hdac_bus_remove_device(struct hdac_bus *bus, struct hdac_device *codec); void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex); +void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus, + unsigned int addr); int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr, unsigned int cmd, unsigned int *res); diff --git a/sound/i2c/other/ak4113.c b/sound/i2c/other/ak4113.c index 6ef5fffda8d7..9405d985a87d 100644 --- a/sound/i2c/other/ak4113.c +++ b/sound/i2c/other/ak4113.c @@ -130,7 +130,7 @@ void snd_ak4113_reinit(struct ak4113 *chip) scoped_guard(mutex, &chip->reinit_mutex) { ak4113_init_regs(chip); } - /* bring up statistics / event queing */ + /* bring up statistics / event queuing */ if (atomic_dec_and_test(&chip->wq_processing)) schedule_delayed_work(&chip->work, HZ / 10); } diff --git a/sound/i2c/other/ak4114.c b/sound/i2c/other/ak4114.c index 71efb29b6c7c..4c3dbbf8c316 100644 --- a/sound/i2c/other/ak4114.c +++ b/sound/i2c/other/ak4114.c @@ -135,7 +135,7 @@ void snd_ak4114_reinit(struct ak4114 *chip) scoped_guard(mutex, &chip->reinit_mutex) { ak4114_init_regs(chip); } - /* bring up statistics / event queing */ + /* bring up statistics / event queuing */ if (atomic_dec_and_test(&chip->wq_processing)) schedule_delayed_work(&chip->work, HZ / 10); } diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index 9dd36b82a6ac..e7410f0f8660 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -147,7 +147,7 @@ EXPORT_SYMBOL(snd_akm4xxx_reset); * Volume conversion table for non-linear volumes * from -63.5dB (mute) to 0dB step 0.5dB * - * Used for AK4524/AK4620 input/ouput attenuation, AK4528, and + * Used for AK4524/AK4620 input/output attenuation, AK4528, and * AK5365 input attenuation */ static const unsigned char vol_cvt_datt[128] = { diff --git a/sound/isa/galaxy/galaxy.c b/sound/isa/galaxy/galaxy.c index b0f1562d0fc0..aa31744ed582 100644 --- a/sound/isa/galaxy/galaxy.c +++ b/sound/isa/galaxy/galaxy.c @@ -478,11 +478,11 @@ static void snd_galaxy_free(struct snd_card *card) galaxy_set_config(galaxy, galaxy->config); } -static int __snd_galaxy_probe(struct device *dev, unsigned int n) +static int snd_galaxy_probe(struct device *dev, unsigned int n) { struct snd_galaxy *galaxy; struct snd_wss *chip; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; u8 type; int err; @@ -595,14 +595,10 @@ static int __snd_galaxy_probe(struct device *dev, unsigned int n) return err; dev_set_drvdata(dev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_galaxy_probe(struct device *dev, unsigned int n) -{ - return snd_card_free_on_error(dev, __snd_galaxy_probe(dev, n)); -} - static struct isa_driver snd_galaxy_driver = { .match = snd_galaxy_match, .probe = snd_galaxy_probe, diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c index abaa3ed3ab5c..8a6db40ff893 100644 --- a/sound/isa/opti9xx/opti92x-ad1848.c +++ b/sound/isa/opti9xx/opti92x-ad1848.c @@ -378,7 +378,7 @@ static int snd_opti9xx_configure(struct snd_opti9xx *chip, */ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff); /* - * This bit sets OPTI931 to automaticaly select FM + * This bit sets OPTI931 to automatically select FM * or digital input signal. */ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01); diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index cd3a63c7c2a7..f75533fe03a4 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -572,14 +572,14 @@ static void snd_sc6000_free(struct snd_card *card) sc6000_setup_board(card->dev, sc6000->vport, 0); } -static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) +static int snd_sc6000_probe(struct device *devptr, unsigned int dev) { static const int possible_irqs[] = { 5, 7, 9, 10, 11, -1 }; static const int possible_dmas[] = { 1, 3, 0, -1 }; int err; int xirq = irq[dev]; int xdma = dma[dev]; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_sc6000 *sc6000; struct snd_wss *chip; struct snd_opl3 *opl3; @@ -696,14 +696,10 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) return err; dev_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_sc6000_probe(struct device *devptr, unsigned int dev) -{ - return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev)); -} - #ifdef CONFIG_PM static int snd_sc6000_suspend(struct device *devptr, unsigned int dev, pm_message_t state) diff --git a/sound/mips/hal2.c b/sound/mips/hal2.c index 2beb6a0dc3b7..6decbaa5800e 100644 --- a/sound/mips/hal2.c +++ b/sound/mips/hal2.c @@ -375,7 +375,7 @@ static void hal2_setup_dac(struct snd_hal2 *hal2) hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX); /* Setup the HAL2 for playback */ hal2_set_dac_rate(hal2); - /* Set endianess */ + /* Set endianness */ hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECTX); /* Set DMA bus */ hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); @@ -400,7 +400,7 @@ static void hal2_setup_adc(struct snd_hal2 *hal2) hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR); /* Setup the HAL2 for record */ hal2_set_adc_rate(hal2); - /* Set endianess */ + /* Set endianness */ hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECR); /* Set DMA bus */ hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); @@ -837,30 +837,24 @@ static int hal2_create(struct snd_card *card, struct snd_hal2 **rchip) static int hal2_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_hal2 *chip; int err; - err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = hal2_create(card, &chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = hal2_pcm_create(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = hal2_mixer_create(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } strscpy(card->driver, "SGI HAL2 Audio"); strscpy(card->shortname, "SGI HAL2 Audio"); @@ -869,24 +863,16 @@ static int hal2_probe(struct platform_device *pdev) SGI_HPCDMA_IRQ); err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } + platform_set_drvdata(pdev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void hal2_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - snd_card_free(card); -} - static struct platform_driver hal2_driver = { .probe = hal2_probe, - .remove = hal2_remove, .driver = { .name = "sgihal2", } diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c index 497d84cff12f..25c1871f72c1 100644 --- a/sound/mips/sgio2audio.c +++ b/sound/mips/sgio2audio.c @@ -742,7 +742,8 @@ static struct { /* ALSA driver */ -static int snd_sgio2audio_free(struct snd_sgio2audio *chip) +static int snd_sgio2audio_free(struct snd_sgio2audio *chip, + unsigned int irq_count) { int i; @@ -752,7 +753,7 @@ static int snd_sgio2audio_free(struct snd_sgio2audio *chip) writeq(0, &mace->perif.audio.control); /* release IRQ's */ - for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) + for (i = 0; i < irq_count; i++) free_irq(snd_sgio2_isr_table[i].irq, &chip->channel[snd_sgio2_isr_table[i].idx]); @@ -768,7 +769,7 @@ static int snd_sgio2audio_dev_free(struct snd_device *device) { struct snd_sgio2audio *chip = device->device_data; - return snd_sgio2audio_free(chip); + return snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); } static const struct snd_device_ops ops = { @@ -819,7 +820,7 @@ static int snd_sgio2audio_create(struct snd_card *card, 0, snd_sgio2_isr_table[i].desc, &chip->channel[snd_sgio2_isr_table[i].idx])) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, i); printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n", snd_sgio2_isr_table[i].irq); return -EBUSY; @@ -843,13 +844,13 @@ static int snd_sgio2audio_create(struct snd_card *card, /* initialize the AD1843 codec */ err = ad1843_init(&chip->ad1843); if (err < 0) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); return err; } err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); if (err < 0) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); return err; } *rchip = chip; @@ -858,30 +859,24 @@ static int snd_sgio2audio_create(struct snd_card *card, static int snd_sgio2audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_sgio2audio *chip; int err; - err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_sgio2audio_create(card, &chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = snd_sgio2audio_new_pcm(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = snd_sgio2audio_new_mixer(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } strscpy(card->driver, "SGI O2 Audio"); strscpy(card->shortname, "SGI O2 Audio"); @@ -891,24 +886,16 @@ static int snd_sgio2audio_probe(struct platform_device *pdev) MACEISA_AUDIO3_MERR_IRQ); err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } + platform_set_drvdata(pdev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void snd_sgio2audio_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - snd_card_free(card); -} - static struct platform_driver sgio2audio_driver = { .probe = snd_sgio2audio_probe, - .remove = snd_sgio2audio_remove, .driver = { .name = "sgio2audio", } diff --git a/sound/mips/snd-n64.c b/sound/mips/snd-n64.c index f17e63f2ff5a..da889570cebc 100644 --- a/sound/mips/snd-n64.c +++ b/sound/mips/snd-n64.c @@ -273,6 +273,15 @@ static const struct snd_pcm_ops n64audio_pcm_ops = { .close = n64audio_pcm_close, }; +static void n64audio_free(struct snd_card *card) +{ + struct n64audio *priv = card->private_data; + + if (priv->ring_base) + dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, + priv->ring_base_dma); +} + /* * The target device is embedded and RAM-constrained. We save RAM * by initializing in __init code that gets dropped late in boot. @@ -280,14 +289,14 @@ static const struct snd_pcm_ops n64audio_pcm_ops = { */ static int __init n64audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_pcm *pcm; struct n64audio *priv; int err, irq; - err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, - SNDRV_DEFAULT_STR1, - THIS_MODULE, sizeof(*priv), &card); + err = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, + THIS_MODULE, sizeof(*priv), &card); if (err < 0) return err; @@ -296,29 +305,24 @@ static int __init n64audio_probe(struct platform_device *pdev) spin_lock_init(&priv->chan.lock); priv->card = card; + card->private_free = n64audio_free; priv->ring_base = dma_alloc_coherent(card->dev, 32 * 1024, &priv->ring_base_dma, - GFP_DMA|GFP_KERNEL); - if (!priv->ring_base) { - err = -ENOMEM; - goto fail_card; - } + GFP_KERNEL); + if (!priv->ring_base) + return -ENOMEM; priv->mi_reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->mi_reg_base)) { - err = PTR_ERR(priv->mi_reg_base); - goto fail_dma_alloc; - } + if (IS_ERR(priv->mi_reg_base)) + return PTR_ERR(priv->mi_reg_base); priv->ai_reg_base = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(priv->ai_reg_base)) { - err = PTR_ERR(priv->ai_reg_base); - goto fail_dma_alloc; - } + if (IS_ERR(priv->ai_reg_base)) + return PTR_ERR(priv->ai_reg_base); err = snd_pcm_new(card, "N64 Audio", 0, 1, 0, &pcm); if (err < 0) - goto fail_dma_alloc; + return err; pcm->private_data = priv; strscpy(pcm->name, "N64 Audio"); @@ -331,28 +335,19 @@ static int __init n64audio_probe(struct platform_device *pdev) strscpy(card->longname, "N64 Audio"); irq = platform_get_irq(pdev, 0); - if (irq < 0) { - err = -EINVAL; - goto fail_dma_alloc; - } + if (irq < 0) + return -EINVAL; + if (devm_request_irq(&pdev->dev, irq, n64audio_isr, - IRQF_SHARED, "N64 Audio", priv)) { - err = -EBUSY; - goto fail_dma_alloc; - } + IRQF_SHARED, "N64 Audio", priv)) + return -EBUSY; err = snd_card_register(card); if (err < 0) - goto fail_dma_alloc; + return err; + card = NULL; /* probe succeeded, don't release as error */ return 0; - -fail_dma_alloc: - dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, priv->ring_base_dma); - -fail_card: - snd_card_free(card); - return err; } static struct platform_driver n64audio_driver = { diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c index e25a78dd1bf2..339311e5d807 100644 --- a/sound/oss/dmasound/dmasound_q40.c +++ b/sound/oss/dmasound/dmasound_q40.c @@ -49,9 +49,8 @@ static int Q40SetFormat(int format); static int Q40SetVolume(int volume); static void Q40PlayNextFrame(int index); static void Q40Play(void); -static irqreturn_t Q40StereoInterrupt(int irq, void *dummy); -static irqreturn_t Q40MonoInterrupt(int irq, void *dummy); -static void Q40Interrupt(void); +static irqreturn_t q40_audio_interrupt(int irq, void *dummy); +static void q40_frame_done(void); /*** Mid level stuff *********************************************************/ @@ -372,8 +371,8 @@ static void Q40Free(void *ptr, unsigned int size) static int __init Q40IrqInit(void) { /* Register interrupt handler. */ - if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, - "DMA sound", Q40Interrupt)) + if (request_irq(Q40_IRQ_SAMPLE, q40_audio_interrupt, 0, + "DMA sound", q40_frame_done)) return 0; return(1); @@ -384,7 +383,7 @@ static int __init Q40IrqInit(void) static void Q40IrqCleanUp(void) { master_outb(0,SAMPLE_ENABLE_REG); - free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); + free_irq(Q40_IRQ_SAMPLE, q40_frame_done); } #endif /* MODULE */ @@ -403,7 +402,6 @@ static void Q40PlayNextFrame(int index) u_char *start; u_long size; u_char speed; - int error; /* used by Q40Play() if all doubts whether there really is something * to be played are already wiped out. @@ -420,15 +418,6 @@ static void Q40PlayNextFrame(int index) speed=(dmasound.hard.speed==10000 ? 0 : 1); master_outb( 0,SAMPLE_ENABLE_REG); - free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); - if (dmasound.soft.stereo) - error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, - "Q40 sound", Q40Interrupt); - else - error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0, - "Q40 sound", Q40Interrupt); - if (error && printk_ratelimit()) - pr_err("Couldn't register sound interrupt\n"); master_outb( speed, SAMPLE_RATE_REG); master_outb( 1,SAMPLE_CLEAR_REG); @@ -456,31 +445,35 @@ static void Q40Play(void) spin_unlock_irqrestore(&dmasound.lock, flags); } -static irqreturn_t Q40StereoInterrupt(int irq, void *dummy) +static irqreturn_t q40_audio_interrupt(int irq, void *dummy) { + bool frame_done = false; + spin_lock(&dmasound.lock); - if (q40_sc>1){ - *DAC_LEFT=*q40_pp++; - *DAC_RIGHT=*q40_pp++; - q40_sc -=2; - master_outb(1,SAMPLE_CLEAR_REG); - }else Q40Interrupt(); + if (dmasound.hard.stereo) { + if (q40_sc > 1) { + *DAC_LEFT = *q40_pp++; + *DAC_RIGHT = *q40_pp++; + q40_sc -= 2; + master_outb(1, SAMPLE_CLEAR_REG); + } else { + frame_done = true; + } + } else if (q40_sc > 0) { + *DAC_LEFT = *q40_pp; + *DAC_RIGHT = *q40_pp++; + q40_sc--; + master_outb(1, SAMPLE_CLEAR_REG); + } else { + frame_done = true; + } spin_unlock(&dmasound.lock); + if (frame_done) + q40_frame_done(); return IRQ_HANDLED; } -static irqreturn_t Q40MonoInterrupt(int irq, void *dummy) -{ - spin_lock(&dmasound.lock); - if (q40_sc>0){ - *DAC_LEFT=*q40_pp; - *DAC_RIGHT=*q40_pp++; - q40_sc --; - master_outb(1,SAMPLE_CLEAR_REG); - }else Q40Interrupt(); - spin_unlock(&dmasound.lock); - return IRQ_HANDLED; -} -static void Q40Interrupt(void) + +static void q40_frame_done(void) { if (!write_sq.active) { /* playing was interrupted and sq_reset() has already cleared diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c index a9625aedf2a4..6cdd09f5855b 100644 --- a/sound/parisc/harmony.c +++ b/sound/parisc/harmony.c @@ -897,24 +897,24 @@ static int __init snd_harmony_probe(struct parisc_device *padev) { int err; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_harmony *h; - err = snd_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_harmony_create(card, padev, &h); if (err < 0) - goto free_and_ret; + return err; err = snd_harmony_pcm_init(h); if (err < 0) - goto free_and_ret; + return err; err = snd_harmony_mixer_init(h); if (err < 0) - goto free_and_ret; + return err; strscpy(card->driver, "harmony"); strscpy(card->shortname, "Harmony"); @@ -923,27 +923,17 @@ snd_harmony_probe(struct parisc_device *padev) err = snd_card_register(card); if (err < 0) - goto free_and_ret; + return err; parisc_set_drvdata(padev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -free_and_ret: - snd_card_free(card); - return err; -} - -static void __exit -snd_harmony_remove(struct parisc_device *padev) -{ - snd_card_free(parisc_get_drvdata(padev)); } static struct parisc_driver snd_harmony_driver __refdata = { .name = "harmony", .id_table = snd_harmony_devtable, .probe = snd_harmony_probe, - .remove = __exit_p(snd_harmony_remove), }; static int __init diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c index 0bb65be021d9..207ce083593f 100644 --- a/sound/pci/ac97/ac97_codec.c +++ b/sound/pci/ac97/ac97_codec.c @@ -2960,7 +2960,7 @@ int snd_ac97_tune_hardware(struct snd_ac97 *ac97, { int result; - /* quirk overriden? */ + /* quirk overridden? */ if (override && strcmp(override, "-1") && strcmp(override, "default")) { result = apply_quirk_str(ac97, override); if (result < 0) diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c index 64cc39dd2008..8577a2e0510c 100644 --- a/sound/pci/ac97/ac97_patch.c +++ b/sound/pci/ac97/ac97_patch.c @@ -430,7 +430,7 @@ static int patch_yamaha_ymf753(struct snd_ac97 * ac97) /* Patch for Yamaha YMF753, Copyright (c) by David Shust, dshust@shustring.com. This chip has nonstandard and extended behaviour with regard to its S/PDIF output. The AC'97 spec states that the S/PDIF signal is to be output at pin 48. - The YMF753 will ouput the S/PDIF signal to pin 43, 47 (EAPD), or 48. + The YMF753 will output the S/PDIF signal to pin 43, 47 (EAPD), or 48. By default, no output pin is selected, and the S/PDIF signal is not output. There is also a bit to mute S/PDIF output in a vendor-specific register. */ diff --git a/sound/pci/ad1889.c b/sound/pci/ad1889.c index f4ec404c0d15..42a8fa3f4fdc 100644 --- a/sound/pci/ad1889.c +++ b/sound/pci/ad1889.c @@ -837,12 +837,12 @@ snd_ad1889_create(struct snd_card *card, struct pci_dev *pci) } static int -__snd_ad1889_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +snd_ad1889_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { int err; static int devno; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ad1889 *chip; /* (1) */ @@ -892,17 +892,12 @@ __snd_ad1889_probe(struct pci_dev *pci, /* (7) */ pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ devno++; return 0; } -static int snd_ad1889_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ad1889_probe(pci, pci_id)); -} - static const struct pci_device_id snd_ad1889_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_ANALOG_DEVICES, PCI_DEVICE_ID_AD1889JS) }, { 0, }, diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index f07c5f4c2380..8295eee9b977 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -2086,10 +2086,10 @@ static int snd_ali_create(struct snd_card *card, return 0; } -static int __snd_ali_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_ali_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ali *codec; int err; @@ -2129,15 +2129,10 @@ static int __snd_ali_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_ali_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ali_probe(pci, pci_id)); -} - static struct pci_driver ali5451_driver = { .name = KBUILD_MODNAME, .id_table = snd_ali_ids, diff --git a/sound/pci/als300.c b/sound/pci/als300.c index 00f0720169c3..5832f4d38c4e 100644 --- a/sound/pci/als300.c +++ b/sound/pci/als300.c @@ -675,7 +675,7 @@ static int snd_als300_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_als300 *chip; int err, chip_type; @@ -696,7 +696,7 @@ static int snd_als300_probe(struct pci_dev *pci, err = snd_als300_create(card, pci, chip_type); if (err < 0) - goto error; + return err; strscpy(card->driver, "ALS300"); if (chip->chip_type == DEVICE_ALS300_PLUS) @@ -711,15 +711,12 @@ static int snd_als300_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver als300_driver = { diff --git a/sound/pci/als4000.c b/sound/pci/als4000.c index 6396aa6c3bf0..12bb868fb9ce 100644 --- a/sound/pci/als4000.c +++ b/sound/pci/als4000.c @@ -798,11 +798,11 @@ static void snd_card_als4000_free( struct snd_card *card ) snd_als4000_free_gameport(acard); } -static int __snd_card_als4000_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_als4000_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_card_als4000 *acard; unsigned long iobase; struct snd_sb *chip; @@ -918,16 +918,11 @@ static int __snd_card_als4000_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_als4000_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_als4000_probe(pci, pci_id)); -} - static int snd_als4000_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); diff --git a/sound/pci/atiixp.c b/sound/pci/atiixp.c index b738295b41e5..e1d36cd7945b 100644 --- a/sound/pci/atiixp.c +++ b/sound/pci/atiixp.c @@ -1550,10 +1550,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) } -static int __snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct atiixp *chip; int err; @@ -1598,15 +1598,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); -} - static struct pci_driver atiixp_driver = { .name = KBUILD_MODNAME, .id_table = snd_atiixp_ids, diff --git a/sound/pci/atiixp_modem.c b/sound/pci/atiixp_modem.c index 8aaeb197ce45..4984a345b4b2 100644 --- a/sound/pci/atiixp_modem.c +++ b/sound/pci/atiixp_modem.c @@ -1182,10 +1182,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) } -static int __snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct atiixp_modem *chip; int err; @@ -1225,15 +1225,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); -} - static struct pci_driver atiixp_modem_driver = { .name = KBUILD_MODNAME, .id_table = snd_atiixp_ids, diff --git a/sound/pci/au88x0/au88x0.c b/sound/pci/au88x0/au88x0.c index bb02945793f0..87a24f0c86e7 100644 --- a/sound/pci/au88x0/au88x0.c +++ b/sound/pci/au88x0/au88x0.c @@ -192,10 +192,10 @@ snd_vortex_create(struct snd_card *card, struct pci_dev *pci) // constructor -- see "Constructor" sub-section static int -__snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; vortex_t *chip; int err; @@ -303,18 +303,13 @@ __snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) return err; // (7) pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; vortex_connect_default(chip, 1); vortex_enable_int(chip); return 0; } -static int -snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_vortex_probe(pci, pci_id)); -} - // pci_driver definition static struct pci_driver vortex_driver = { .name = KBUILD_MODNAME, diff --git a/sound/pci/au88x0/au88x0.h b/sound/pci/au88x0/au88x0.h index 6cbb2bc4a048..f97a0303da3e 100644 --- a/sound/pci/au88x0/au88x0.h +++ b/sound/pci/au88x0/au88x0.h @@ -28,7 +28,7 @@ /* Vortex MPU401 defines. */ #define MIDI_CLOCK_DIV 0x61 -/* Standart MPU401 defines. */ +/* Standard MPU401 defines. */ #define MPU401_RESET 0xff #define MPU401_ENTER_UART 0x3f #define MPU401_ACK 0xfe diff --git a/sound/pci/au88x0/au88x0_mpu401.c b/sound/pci/au88x0/au88x0_mpu401.c index 164f6b7039ab..dff4bf80ab96 100644 --- a/sound/pci/au88x0/au88x0_mpu401.c +++ b/sound/pci/au88x0/au88x0_mpu401.c @@ -22,7 +22,7 @@ /* Vortex MPU401 defines. */ #define MIDI_CLOCK_DIV 0x61 -/* Standart MPU401 defines. */ +/* Standard MPU401 defines. */ #define MPU401_RESET 0xff #define MPU401_ENTER_UART 0x3f #define MPU401_ACK 0xfe diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c index 60a87322eae4..513249b9bb78 100644 --- a/sound/pci/aw2/aw2-alsa.c +++ b/sound/pci/aw2/aw2-alsa.c @@ -251,7 +251,7 @@ static int snd_aw2_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct aw2 *chip; int err; @@ -273,7 +273,7 @@ static int snd_aw2_probe(struct pci_dev *pci, /* (3) Create main component */ err = snd_aw2_create(card, pci); if (err < 0) - goto error; + return err; /* initialize mutex */ mutex_init(&chip->mtx); @@ -292,17 +292,14 @@ static int snd_aw2_probe(struct pci_dev *pci, /* (6) Register card instance */ err = snd_card_register(card); if (err < 0) - goto error; + return err; /* (7) Set PCI driver data */ pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } /* open callback */ diff --git a/sound/pci/aw2/aw2-saa7146.c b/sound/pci/aw2/aw2-saa7146.c index c84f1a45194f..6857a0ec2924 100644 --- a/sound/pci/aw2/aw2-saa7146.c +++ b/sound/pci/aw2/aw2-saa7146.c @@ -147,7 +147,7 @@ void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip, unsigned long dw_page, dw_limit; /* Configure DMA for substream - Configuration informations: ALSA has allocated continuous memory + Configuration information: ALSA has allocated continuous memory pages. So we don't need to use MMU of saa7146. */ @@ -204,7 +204,7 @@ void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip, unsigned long dw_page, dw_limit; /* Configure DMA for substream - Configuration informations: ALSA has allocated continuous memory + Configuration information: ALSA has allocated continuous memory pages. So we don't need to use MMU of saa7146. */ diff --git a/sound/pci/azt3328.c b/sound/pci/azt3328.c index ccca82417657..8cf94c41a54c 100644 --- a/sound/pci/azt3328.c +++ b/sound/pci/azt3328.c @@ -2403,10 +2403,10 @@ snd_azf3328_create(struct snd_card *card, } static int -__snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_azf3328 *chip; struct snd_opl3 *opl3; int err; @@ -2492,16 +2492,11 @@ __snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) snd_azf3328_gameport(chip, dev); pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_azf3328_probe(pci, pci_id)); -} - static inline void snd_azf3328_suspend_regs(const struct snd_azf3328 *chip, unsigned long io_addr, unsigned count, u32 *saved_regs) diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c index 383def1f2af7..db8f60261a0f 100644 --- a/sound/pci/bt87x.c +++ b/sound/pci/bt87x.c @@ -798,11 +798,11 @@ static int snd_bt87x_detect_card(struct pci_dev *pci) return SND_BT87X_BOARD_UNKNOWN; } -static int __snd_bt87x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_bt87x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_bt87x *chip; int err; enum snd_bt87x_boardid boardid; @@ -878,16 +878,11 @@ static int __snd_bt87x_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ ++dev; return 0; } -static int snd_bt87x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_bt87x_probe(pci, pci_id)); -} - /* default entries for all Bt87x cards - it's not exported */ /* driver_data is set to 0 to call detection */ static const struct pci_device_id snd_bt87x_default_ids[] = { diff --git a/sound/pci/ca0106/ca0106_main.c b/sound/pci/ca0106/ca0106_main.c index cf923f551211..6429c2bfc310 100644 --- a/sound/pci/ca0106/ca0106_main.c +++ b/sound/pci/ca0106/ca0106_main.c @@ -1710,11 +1710,11 @@ static int snd_ca0106_midi(struct snd_ca0106 *chip, unsigned int channel) } -static int __snd_ca0106_probe(struct pci_dev *pci, +static int snd_ca0106_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ca0106 *chip; int i, err; @@ -1767,16 +1767,11 @@ static int __snd_ca0106_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_ca0106_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ca0106_probe(pci, pci_id)); -} - #ifdef CONFIG_PM_SLEEP static int snd_ca0106_suspend(struct device *dev) { diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c index 9d9f784e3a8c..d120b7bbe024 100644 --- a/sound/pci/cmipci.c +++ b/sound/pci/cmipci.c @@ -3170,7 +3170,7 @@ static int snd_cmipci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -3201,19 +3201,16 @@ static int snd_cmipci_probe(struct pci_dev *pci, err = snd_cmipci_create(card, pci, dev); if (err < 0) - goto error; + return err; err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } /* diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index f51f4bb63766..b831f1d50750 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -324,7 +324,7 @@ MODULE_PARM_DESC(dual_codec, "Secondary Codec ID (0 = disabled)."); #define BA0_SSCR_XLPSRC (1<<8) /* External SRC Loopback Mode */ #define BA0_SSCR_LPSRC (1<<7) /* SRC Loopback Mode */ #define BA0_SSCR_CDTX (1<<5) /* CD Transfer Data */ -#define BA0_SSCR_HVC (1<<3) /* Harware Volume Control Enable */ +#define BA0_SSCR_HVC (1<<3) /* Hardware Volume Control Enable */ #define BA0_FMLVC 0x0754 /* FM Synthesis Left Volume Control */ #define BA0_FMRVC 0x0758 /* FM Synthesis Right Volume Control */ @@ -1811,11 +1811,11 @@ static void snd_cs4281_opl3_command(struct snd_opl3 *opl3, unsigned short cmd, udelay(30); } -static int __snd_cs4281_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_cs4281_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct cs4281 *chip; struct snd_opl3 *opl3; int err; @@ -1868,16 +1868,11 @@ static int __snd_cs4281_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_cs4281_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_cs4281_probe(pci, pci_id)); -} - /* * Power Management */ diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c index 0cb7c2a2929f..a4a0b36290d9 100644 --- a/sound/pci/cs46xx/cs46xx.c +++ b/sound/pci/cs46xx/cs46xx.c @@ -55,7 +55,7 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_cs46xx *chip; int err; @@ -74,36 +74,36 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, err = snd_cs46xx_create(card, pci, external_amp[dev], thinkpad[dev]); if (err < 0) - goto error; + return err; card->private_data = chip; chip->accept_valid = mmap_valid[dev]; err = snd_cs46xx_pcm(chip, 0); if (err < 0) - goto error; + return err; #ifdef CONFIG_SND_CS46XX_NEW_DSP err = snd_cs46xx_pcm_rear(chip, 1); if (err < 0) - goto error; + return err; err = snd_cs46xx_pcm_iec958(chip, 2); if (err < 0) - goto error; + return err; #endif err = snd_cs46xx_mixer(chip, 2); if (err < 0) - goto error; + return err; #ifdef CONFIG_SND_CS46XX_NEW_DSP if (chip->nr_ac97_codecs ==2) { err = snd_cs46xx_pcm_center_lfe(chip, 3); if (err < 0) - goto error; + return err; } #endif err = snd_cs46xx_midi(chip, 0); if (err < 0) - goto error; + return err; err = snd_cs46xx_start_dsp(chip); if (err < 0) - goto error; + return err; snd_cs46xx_gameport(chip); @@ -117,15 +117,12 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver cs46xx_driver = { diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c index f6db08b75649..797a5f09cade 100644 --- a/sound/pci/cs46xx/cs46xx_lib.c +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -315,7 +315,7 @@ int snd_cs46xx_download(struct snd_cs46xx *chip, dst = chip->region.idx[bank+1].remap_addr + offset; len /= sizeof(u32); - /* writel already converts 32-bit value to right endianess */ + /* writel already converts 32-bit value to right endianness */ while (len-- > 0) { writel(*src++, dst); dst += sizeof(u32); @@ -474,7 +474,7 @@ int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip, dst = chip->region.idx[bank+1].remap_addr + offset; len /= sizeof(u32); - /* writel already converts 32-bit value to right endianess */ + /* writel already converts 32-bit value to right endianness */ while (len-- > 0) { writel(0, dst); dst += sizeof(u32); @@ -3039,7 +3039,7 @@ static int snd_cs46xx_chip_init(struct snd_cs46xx *chip) #endif /* - * Assert the vaid frame signal so that we can start sending commands + * Assert the valid frame signal so that we can start sending commands * to the AC97 codec. */ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c index 6fd4f629d1d5..a77e6f5a14ad 100644 --- a/sound/pci/cs46xx/dsp_spos.c +++ b/sound/pci/cs46xx/dsp_spos.c @@ -393,7 +393,7 @@ int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * m dev_dbg(chip->card->dev, "dsp_spos: module got no code segment\n"); } else { - if (ins->code.offset + code->size > DSP_CODE_BYTE_SIZE) { + if ((u64)ins->code.offset + code->size > DSP_CODE_BYTE_SIZE / 4) { dev_err(chip->card->dev, "dsp_spos: no space available in DSP\n"); return -ENOMEM; diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c index fd19365026b4..71eee5cb478f 100644 --- a/sound/pci/cs46xx/dsp_spos_scb_lib.c +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -160,15 +160,15 @@ void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * { struct dsp_spos_instance * ins = chip->dsp_spos_instance; - /* check integrety */ + /* check integrity */ if (snd_BUG_ON(scb->index < 0 || scb->index >= ins->nscb || (ins->scbs + scb->index) != scb)) return; #if 0 - /* can't remove a SCB with childs before - removing childs first */ + /* can't remove a SCB with children before + removing children first */ if (snd_BUG_ON(scb->sub_list_ptr != ins->the_null_scb || scb->next_scb_ptr != ins->the_null_scb)) goto _end; @@ -1656,7 +1656,7 @@ int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip) return -EBUSY; } - /* check integrety */ + /* check integrity */ if (snd_BUG_ON(!ins->asynch_tx_scb)) return -EINVAL; if (snd_BUG_ON(!ins->spdif_pcm_input_scb)) diff --git a/sound/pci/cs5535audio/cs5535audio.c b/sound/pci/cs5535audio/cs5535audio.c index 0ebf6c02b1ef..e09f7bc9e23a 100644 --- a/sound/pci/cs5535audio/cs5535audio.c +++ b/sound/pci/cs5535audio/cs5535audio.c @@ -283,11 +283,11 @@ static int snd_cs5535audio_create(struct snd_card *card, return 0; } -static int __snd_cs5535audio_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_cs5535audio_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct cs5535audio *cs5535au; int err; @@ -329,16 +329,11 @@ static int __snd_cs5535audio_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_cs5535audio_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_cs5535audio_probe(pci, pci_id)); -} - static struct pci_driver cs5535audio_driver = { .name = KBUILD_MODNAME, .id_table = snd_cs5535audio_ids, diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c index d8dd84d41c87..99319d75e963 100644 --- a/sound/pci/ctxfi/xfi.c +++ b/sound/pci/ctxfi/xfi.c @@ -56,7 +56,7 @@ static int ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct ct_atc *atc; int err; @@ -67,8 +67,8 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) dev++; return -ENOENT; } - err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, - 0, &card); + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); if (err) return err; if ((reference_rate != 48000) && (reference_rate != 44100)) { @@ -89,14 +89,14 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) err = ct_atc_create(card, pci, reference_rate, multiple, pci_id->driver_data, subsystem[dev], &atc); if (err < 0) - goto error; + return err; card->private_data = atc; /* Create alsa devices supported by this card */ err = ct_atc_create_alsa_devs(atc); if (err < 0) - goto error; + return err; strscpy(card->driver, "SB-XFi"); strscpy(card->shortname, "Creative X-Fi"); @@ -105,21 +105,13 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - -error: - snd_card_free(card); - return err; -} - -static void ct_card_remove(struct pci_dev *pci) -{ - snd_card_free(pci_get_drvdata(pci)); } #ifdef CONFIG_PM_SLEEP @@ -149,7 +141,6 @@ static struct pci_driver ct_driver = { .name = KBUILD_MODNAME, .id_table = ct_pci_dev_ids, .probe = ct_card_probe, - .remove = ct_card_remove, .driver = { .pm = CT_CARD_PM_OPS, }, diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 214d68657707..4dcad05fd57e 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -270,7 +270,7 @@ static int pcm_open(struct snd_pcm_substream *substream, return -ENOMEM; pipe->index = -1; /* Not configured yet */ - /* Set up hw capabilities and contraints */ + /* Set up hw capabilities and constraints */ memcpy(&pipe->hw, &pcm_hardware_skel, sizeof(struct snd_pcm_hardware)); dev_dbg(chip->card->dev, "max_channels=%d\n", max_channels); pipe->constr.list = channels_list; @@ -1720,7 +1720,7 @@ static const struct snd_kcontrol_new snd_echo_vumeters = { -/*** Channels info - it exports informations about the number of channels ***/ +/*** Channels info - it exports information about the number of channels ***/ static int snd_echo_channels_info_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -1934,11 +1934,11 @@ static int snd_echo_create(struct snd_card *card, } /* constructor */ -static int __snd_echo_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_echo_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct echoaudio *chip; char *dsp; int err; @@ -2097,17 +2097,11 @@ static int __snd_echo_probe(struct pci_dev *pci, dev_info(card->dev, "Card registered: %s\n", card->longname); pci_set_drvdata(pci, chip); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_echo_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_echo_probe(pci, pci_id)); -} - - static int snd_echo_suspend(struct device *dev) { struct echoaudio *chip = dev_get_drvdata(dev); diff --git a/sound/pci/echoaudio/echoaudio.h b/sound/pci/echoaudio/echoaudio.h index 7a5744bf3155..ac4507b7cc78 100644 --- a/sound/pci/echoaudio/echoaudio.h +++ b/sound/pci/echoaudio/echoaudio.h @@ -394,8 +394,8 @@ struct echoaudio { u16 digital_modes; /* Bitmask of supported modes * (see ECHOCAPS_HAS_DIGITAL_MODE_*) */ - u16 input_clock_types; /* Suppoted input clock types */ - u16 output_clock_types; /* Suppoted output clock types - + u16 input_clock_types; /* Supported input clock types */ + u16 output_clock_types; /* Supported output clock types - * Layla20 only */ u16 device_id, subdevice_id; u16 *dsp_code; /* Current DSP code loaded, diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c index ed4630c8342b..03d277dd02d9 100644 --- a/sound/pci/emu10k1/emu10k1x.c +++ b/sound/pci/emu10k1/emu10k1x.c @@ -1447,11 +1447,11 @@ static int snd_emu10k1x_midi(struct emu10k1x *emu) return 0; } -static int __snd_emu10k1x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_emu10k1x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct emu10k1x *chip; int err; @@ -1506,16 +1506,11 @@ static int __snd_emu10k1x_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_emu10k1x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_emu10k1x_probe(pci, pci_id)); -} - // PCI IDs static const struct pci_device_id snd_emu10k1x_ids[] = { { PCI_VDEVICE(CREATIVE, 0x0006) }, /* Dell OEM version (EMU10K1) */ diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c index 0b9eb55a2121..c2af7eab633c 100644 --- a/sound/pci/ens1370.c +++ b/sound/pci/ens1370.c @@ -2267,11 +2267,11 @@ static irqreturn_t snd_audiopci_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static int __snd_audiopci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_audiopci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct ensoniq *ensoniq; int err; @@ -2328,16 +2328,11 @@ static int __snd_audiopci_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_audiopci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_audiopci_probe(pci, pci_id)); -} - static struct pci_driver ens137x_driver = { .name = KBUILD_MODNAME, .id_table = snd_audiopci_ids, diff --git a/sound/pci/es1938.c b/sound/pci/es1938.c index 217beb9376ac..a612368fd0b1 100644 --- a/sound/pci/es1938.c +++ b/sound/pci/es1938.c @@ -1683,11 +1683,11 @@ static int snd_es1938_mixer(struct es1938 *chip) } -static int __snd_es1938_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_es1938_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct es1938 *chip; struct snd_opl3 *opl3; int idx, err; @@ -1759,16 +1759,11 @@ static int __snd_es1938_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_es1938_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_es1938_probe(pci, pci_id)); -} - static struct pci_driver es1938_driver = { .name = KBUILD_MODNAME, .id_table = snd_es1938_ids, diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c index f04628c8cbb7..b0abbea2a992 100644 --- a/sound/pci/es1968.c +++ b/sound/pci/es1968.c @@ -2692,11 +2692,11 @@ static int snd_es1968_create(struct snd_card *card, /* */ -static int __snd_es1968_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_es1968_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct es1968 *chip; unsigned int i; int err; @@ -2795,16 +2795,11 @@ static int __snd_es1968_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_es1968_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_es1968_probe(pci, pci_id)); -} - static struct pci_driver es1968_driver = { .name = KBUILD_MODNAME, .id_table = snd_es1968_ids, diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c index 9cc96b807dd7..507ea0fe22a9 100644 --- a/sound/pci/fm801.c +++ b/sound/pci/fm801.c @@ -1264,11 +1264,11 @@ static int snd_fm801_create(struct snd_card *card, return 0; } -static int __snd_card_fm801_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_fm801_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct fm801 *chip; struct snd_opl3 *opl3; int err; @@ -1325,16 +1325,11 @@ static int __snd_card_fm801_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_fm801_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_fm801_probe(pci, pci_id)); -} - static const unsigned char saved_regs[] = { FM801_PCM_VOL, FM801_I2S_VOL, FM801_FM_VOL, FM801_REC_SRC, FM801_PLY_CTRL, FM801_PLY_COUNT, FM801_PLY_BUF1, FM801_PLY_BUF2, diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 859bb87393b4..651d92663567 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -2524,11 +2524,11 @@ static int snd_vt1724_create(struct snd_card *card, * */ -static int __snd_vt1724_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_vt1724_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ice1712 *ice; int pcm_dev = 0, err; const struct snd_ice1712_card_info *c; @@ -2663,16 +2663,11 @@ static int __snd_vt1724_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_vt1724_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_vt1724_probe(pci, pci_id)); -} - #ifdef CONFIG_PM_SLEEP static int snd_vt1724_suspend(struct device *dev) { diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c index e2ea9016a73e..339024182a26 100644 --- a/sound/pci/intel8x0.c +++ b/sound/pci/intel8x0.c @@ -3167,10 +3167,10 @@ static int check_default_spdif_aclink(struct pci_dev *pci) return 0; } -static int __snd_intel8x0_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_intel8x0_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct intel8x0 *chip; int err; struct shortname_table *name; @@ -3244,13 +3244,9 @@ static int __snd_intel8x0_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); - return 0; -} + card = NULL; /* probe succeeded, don't release as error */ -static int snd_intel8x0_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_intel8x0_probe(pci, pci_id)); + return 0; } static struct pci_driver intel8x0_driver = { diff --git a/sound/pci/intel8x0m.c b/sound/pci/intel8x0m.c index 27dbf6125427..38a15ebf94b2 100644 --- a/sound/pci/intel8x0m.c +++ b/sound/pci/intel8x0m.c @@ -1218,10 +1218,10 @@ static struct shortname_table { { 0 }, }; -static int __snd_intel8x0m_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_intel8x0m_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct intel8x0m *chip; int err; struct shortname_table *name; @@ -1262,15 +1262,10 @@ static int __snd_intel8x0m_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_intel8x0m_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_intel8x0m_probe(pci, pci_id)); -} - static struct pci_driver intel8x0m_driver = { .name = KBUILD_MODNAME, .id_table = snd_intel8x0m_ids, diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c index f4d8402f835f..bb442b556158 100644 --- a/sound/pci/korg1212/korg1212.c +++ b/sound/pci/korg1212/korg1212.c @@ -2216,7 +2216,7 @@ snd_korg1212_probe(struct pci_dev *pci, { static int dev; struct snd_korg1212 *korg1212; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) { @@ -2234,7 +2234,7 @@ snd_korg1212_probe(struct pci_dev *pci, err = snd_korg1212_create(card, pci); if (err < 0) - goto error; + return err; strscpy(card->driver, "korg1212"); strscpy(card->shortname, "korg1212"); @@ -2245,14 +2245,11 @@ snd_korg1212_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver korg1212_driver = { diff --git a/sound/pci/lola/lola.c b/sound/pci/lola/lola.c index 34a3ba17deb4..1f7ffca57fc9 100644 --- a/sound/pci/lola/lola.c +++ b/sound/pci/lola/lola.c @@ -641,11 +641,11 @@ static int lola_create(struct snd_card *card, struct pci_dev *pci, int dev) return 0; } -static int __lola_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int lola_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct lola *chip; int err; @@ -687,16 +687,11 @@ static int __lola_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int lola_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __lola_probe(pci, pci_id)); -} - /* PCI IDs */ static const struct pci_device_id lola_ids[] = { { PCI_VDEVICE(DIGIGRAM, 0x0001) }, diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c index 3b40ec071379..881f462fa929 100644 --- a/sound/pci/lx6464es/lx6464es.c +++ b/sound/pci/lx6464es/lx6464es.c @@ -981,7 +981,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct lx6464es *chip; int err; @@ -1003,7 +1003,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci, err = snd_lx6464es_create(card, pci); if (err < 0) { dev_err(card->dev, "error during snd_lx6464es_create\n"); - goto error; + return err; } strscpy(card->driver, "LX6464ES"); @@ -1020,16 +1020,13 @@ static int snd_lx6464es_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; dev_dbg(chip->card->dev, "initialization successful\n"); pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver lx6464es_driver = { diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c index 9909f1067871..ae265dd19333 100644 --- a/sound/pci/lx6464es/lx_core.c +++ b/sound/pci/lx6464es/lx_core.c @@ -364,7 +364,7 @@ int lx_dsp_get_mac(struct lx6464es *chip) macmsb = lx_dsp_reg_read(chip, eReg_ADMACESMSB) & 0x00FFFFFF; maclsb = lx_dsp_reg_read(chip, eReg_ADMACESLSB) & 0x00FFFFFF; - /* todo: endianess handling */ + /* todo: endianness handling */ chip->mac_address[5] = ((u8 *)(&maclsb))[0]; chip->mac_address[4] = ((u8 *)(&maclsb))[1]; chip->mac_address[3] = ((u8 *)(&maclsb))[2]; diff --git a/sound/pci/lx6464es/lx_defs.h b/sound/pci/lx6464es/lx_defs.h index eca5367ba561..89e433d3f7a2 100644 --- a/sound/pci/lx6464es/lx_defs.h +++ b/sound/pci/lx6464es/lx_defs.h @@ -315,7 +315,7 @@ enum stream_flags { #define ED_XILINX_ERROR (ED_RT | 0x07) #define ED_COBRANET_ITF_NOT_RESPONDING (ED_RT | 0x08) -/* Complete BOARD error code for the invaid standard object class */ +/* Complete BOARD error code for the invalid standard object class */ #define EB_ISO (ERROR_VALUE | E_SOURCE_BOARD | \ E_CLASS_INVALID_STD_OBJECT) #define EB_INVALID_EFFECT (EB_ISO | 0x00) diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c index dd45ffa171e1..2114f9f16a34 100644 --- a/sound/pci/maestro3.c +++ b/sound/pci/maestro3.c @@ -2629,10 +2629,10 @@ snd_m3_create(struct snd_card *card, struct pci_dev *pci, /* */ static int -__snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_m3 *chip; int err; @@ -2690,16 +2690,11 @@ __snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) #endif pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_m3_probe(pci, pci_id)); -} - static struct pci_driver m3_driver = { .name = KBUILD_MODNAME, .id_table = snd_m3_ids, diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c index 6b096d654f9f..587785d5451b 100644 --- a/sound/pci/oxygen/oxygen_lib.c +++ b/sound/pci/oxygen/oxygen_lib.c @@ -572,7 +572,7 @@ static void oxygen_card_free(struct snd_card *card) mutex_destroy(&chip->mutex); } -static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, +int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, struct module *owner, const struct pci_device_id *ids, int (*get_model)(struct oxygen *chip, @@ -580,7 +580,7 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, ) ) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct oxygen *chip; const struct pci_device_id *pci_id; int err; @@ -695,18 +695,9 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } - -int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, - struct module *owner, - const struct pci_device_id *ids, - int (*get_model)(struct oxygen *chip, - const struct pci_device_id *id)) -{ - return snd_card_free_on_error(&pci->dev, - __oxygen_pci_probe(pci, index, id, owner, ids, get_model)); -} EXPORT_SYMBOL(oxygen_pci_probe); static int oxygen_pci_suspend(struct device *dev) diff --git a/sound/pci/oxygen/oxygen_pcm.c b/sound/pci/oxygen/oxygen_pcm.c index b716356010b8..75efbf59a675 100644 --- a/sound/pci/oxygen/oxygen_pcm.c +++ b/sound/pci/oxygen/oxygen_pcm.c @@ -171,16 +171,14 @@ static int oxygen_open(struct snd_pcm_substream *substream, snd_pcm_set_sync(substream); chip->streams[channel] = substream; - guard(mutex)(&chip->mutex); - chip->pcm_active |= 1 << channel; - if (channel == PCM_SPDIF) { - chip->spdif_pcm_bits = chip->spdif_bits; - chip->controls[CONTROL_SPDIF_PCM]->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->controls[CONTROL_SPDIF_PCM]->id); + scoped_guard(mutex, &chip->mutex) { + chip->pcm_active |= 1 << channel; + if (channel == PCM_SPDIF) + chip->spdif_pcm_bits = chip->spdif_bits; } + if (channel == PCM_SPDIF) + snd_ctl_activate_id(chip->card, + &chip->controls[CONTROL_SPDIF_PCM]->id, 1); return 0; } @@ -220,19 +218,16 @@ static int oxygen_close(struct snd_pcm_substream *substream) struct oxygen *chip = snd_pcm_substream_chip(substream); unsigned int channel = oxygen_substream_channel(substream); - guard(mutex)(&chip->mutex); - chip->pcm_active &= ~(1 << channel); - if (channel == PCM_SPDIF) { - chip->controls[CONTROL_SPDIF_PCM]->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->controls[CONTROL_SPDIF_PCM]->id); - } - if (channel == PCM_SPDIF || channel == PCM_MULTICH) - oxygen_update_spdif_source(chip); + scoped_guard(mutex, &chip->mutex) { + chip->pcm_active &= ~(1 << channel); + if (channel == PCM_SPDIF || channel == PCM_MULTICH) + oxygen_update_spdif_source(chip); - chip->streams[channel] = NULL; + chip->streams[channel] = NULL; + } + if (channel == PCM_SPDIF) + snd_ctl_activate_id(chip->card, + &chip->controls[CONTROL_SPDIF_PCM]->id, 0); return 0; } diff --git a/sound/pci/pcxhr/pcxhr_core.c b/sound/pci/pcxhr/pcxhr_core.c index b3b9ab4f303e..f855d3bfad4b 100644 --- a/sound/pci/pcxhr/pcxhr_core.c +++ b/sound/pci/pcxhr/pcxhr_core.c @@ -802,7 +802,7 @@ static int pcxhr_prepair_pipe_start(struct pcxhr_mgr *mgr, err); return err; } - /* if the pipe couldn't be prepaired for start, + /* if the pipe couldn't be prepared for start, * retry it later */ if (rmh.stat[0] == 0) @@ -913,7 +913,7 @@ int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask, if (err) return err; if (state == 0) - break; /* success, all pipes prepaired */ + break; /* success, all pipes prepared */ mdelay(1); /* wait 1 millisecond and retry */ } } else { diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c index 99c00e46ce23..0d9110558ce4 100644 --- a/sound/pci/riptide/riptide.c +++ b/sound/pci/riptide/riptide.c @@ -264,7 +264,7 @@ MODULE_PARM_DESC(opl3_port, "OPL3 port # for Riptide driver."); #define SEND_SI2S(p,b) sendcmd(p,PARM,SI2S,WORD2(b),RET(0)) #define EOB_STATUS 0x80000000 /* status flags : block boundary */ -#define EOS_STATUS 0x40000000 /* : stoppped */ +#define EOS_STATUS 0x40000000 /* : stopped */ #define EOC_STATUS 0x20000000 /* : stream end */ #define ERR_STATUS 0x10000000 #define EMPTY_STATUS 0x08000000 @@ -453,7 +453,7 @@ struct snd_riptide { int in_suspend; }; -struct sgd { /* scatter gather desriptor */ +struct sgd { /* scatter gather descriptor */ __le32 dwNextLink; __le32 dwSegPtrPhys; __le32 dwSegLen; @@ -2020,10 +2020,10 @@ static void snd_riptide_joystick_remove(struct pci_dev *pci) #endif static int -__snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_riptide *chip; unsigned short val; int err; @@ -2117,16 +2117,11 @@ __snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_riptide_probe(pci, pci_id)); -} - static struct pci_driver driver = { .name = KBUILD_MODNAME, .id_table = snd_riptide_ids, diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c index 454a30a2c07e..7ea9a71aaab9 100644 --- a/sound/pci/rme32.c +++ b/sound/pci/rme32.c @@ -8,7 +8,7 @@ * Thanks to : Anders Torger , * Henk Hesselink * for writing the digi96-driver - * and RME for all informations. + * and RME for all information. * * **************************************************************************** * @@ -1810,11 +1810,11 @@ static void snd_rme32_card_free(struct snd_card *card) } static int -__snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; struct rme32 *rme32; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) { @@ -1858,16 +1858,11 @@ __snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_rme32_probe(pci, pci_id)); -} - static struct pci_driver rme32_driver = { .name = KBUILD_MODNAME, .id_table = snd_rme32_ids, diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c index 892fcc598557..926783f0410e 100644 --- a/sound/pci/rme96.c +++ b/sound/pci/rme96.c @@ -2356,12 +2356,12 @@ static void snd_rme96_card_free(struct snd_card *card) } static int -__snd_rme96_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +snd_rme96_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; struct rme96 *rme96; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; u8 val; @@ -2420,16 +2420,11 @@ __snd_rme96_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_rme96_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_rme96_probe(pci, pci_id)); -} - static struct pci_driver rme96_driver = { .name = KBUILD_MODNAME, .id_table = snd_rme96_ids, diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index 31cc2d91c8d2..7dce548b9f36 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -3664,7 +3664,7 @@ snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) snd_iprintf(buffer, "\n"); - /* Informations about H9632 specific controls */ + /* Information about H9632 specific controls */ if (hdsp->io_type == H9632) { char *tmp; @@ -5328,7 +5328,7 @@ static int snd_hdsp_probe(struct pci_dev *pci, { static int dev; struct hdsp *hdsp; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -5349,21 +5349,18 @@ static int snd_hdsp_probe(struct pci_dev *pci, hdsp->pci = pci; err = snd_hdsp_create(card, hdsp); if (err) - goto error; + return err; strscpy(card->shortname, "Hammerfall DSP"); sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name, hdsp->port, hdsp->irq); err = snd_card_register(card); if (err) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver hdsp_driver = { diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c index 95fd82bb1c6d..cc4cf43e5a5b 100644 --- a/sound/pci/rme9652/hdspm.c +++ b/sound/pci/rme9652/hdspm.c @@ -6796,7 +6796,7 @@ static int snd_hdspm_probe(struct pci_dev *pci, { static int dev; struct hdspm *hdspm; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -6818,7 +6818,7 @@ static int snd_hdspm_probe(struct pci_dev *pci, err = snd_hdspm_create(card, hdspm); if (err < 0) - goto error; + return err; if (hdspm->io_type != MADIface) { snprintf(card->shortname, sizeof(card->shortname), "%s_%x", @@ -6837,16 +6837,12 @@ static int snd_hdspm_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); - + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver hdspm_driver = { diff --git a/sound/pci/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c index 3be30cb57a2e..46ed6b7a2c53 100644 --- a/sound/pci/rme9652/rme9652.c +++ b/sound/pci/rme9652/rme9652.c @@ -2472,7 +2472,7 @@ static int snd_rme9652_probe(struct pci_dev *pci, { static int dev; struct snd_rme9652 *rme9652; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -2494,7 +2494,7 @@ static int snd_rme9652_probe(struct pci_dev *pci, rme9652->pci = pci; err = snd_rme9652_create(card, rme9652, precise_ptr[dev]); if (err) - goto error; + return err; strscpy(card->shortname, rme9652->card_name); @@ -2502,14 +2502,11 @@ static int snd_rme9652_probe(struct pci_dev *pci, card->shortname, rme9652->port, rme9652->irq); err = snd_card_register(card); if (err) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver rme9652_driver = { diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c index 55c1b11a2900..fe5bda5af6a5 100644 --- a/sound/pci/sis7019.c +++ b/sound/pci/sis7019.c @@ -1309,10 +1309,10 @@ static int sis_chip_create(struct snd_card *card, return 0; } -static int __snd_sis7019_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_sis7019_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct sis7019 *sis; int rc; @@ -1361,15 +1361,10 @@ static int __snd_sis7019_probe(struct pci_dev *pci, return rc; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_sis7019_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_sis7019_probe(pci, pci_id)); -} - static struct pci_driver sis7019_driver = { .name = KBUILD_MODNAME, .id_table = snd_sis7019_ids, diff --git a/sound/pci/sonicvibes.c b/sound/pci/sonicvibes.c index a885d544acd6..b5bb20440c6e 100644 --- a/sound/pci/sonicvibes.c +++ b/sound/pci/sonicvibes.c @@ -1362,11 +1362,11 @@ static int snd_sonicvibes_midi(struct sonicvibes *sonic, return 0; } -static int __snd_sonic_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_sonic_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct sonicvibes *sonic; struct snd_rawmidi *midi_uart; struct snd_opl3 *opl3; @@ -1430,16 +1430,11 @@ static int __snd_sonic_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_sonic_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_sonic_probe(pci, pci_id)); -} - static struct pci_driver sonicvibes_driver = { .name = KBUILD_MODNAME, .id_table = snd_sonic_ids, diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c index 81f6348191dc..4ed0e7d68322 100644 --- a/sound/pci/trident/trident_memory.c +++ b/sound/pci/trident/trident_memory.c @@ -27,7 +27,7 @@ #if PAGE_SIZE == 4096 /* page size == SNDRV_TRIDENT_PAGE_SIZE */ #define ALIGN_PAGE_SIZE PAGE_SIZE /* minimum page size for allocation */ -#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maxmium aligned pages */ +#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maximum aligned pages */ /* fill TLB entrie(s) corresponding to page with ptr */ #define set_tlb_bus(trident,page,addr) __set_tlb_bus(trident,page,addr) /* fill TLB entrie(s) corresponding to page with silence pointer */ diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 24ee1302f27d..c13fc554c0c8 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -1242,11 +1242,7 @@ static int snd_via8233_playback_open(struct snd_pcm_substream *substream) VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); chip->playback_volume[stream][1] = VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); - chip->dxs_controls[stream]->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->dxs_controls[stream]->id); + snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 1); } return 0; } @@ -1333,10 +1329,7 @@ static int snd_via8233_playback_close(struct snd_pcm_substream *substream) stream = viadev->reg_offset / 0x10; if (chip->dxs_controls[stream]) { - chip->dxs_controls[stream]->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, - &chip->dxs_controls[stream]->id); + snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 0); } return snd_via82xx_pcm_close(substream); } @@ -2435,10 +2428,10 @@ static int check_dxs_list(struct pci_dev *pci, int revision) return VIA_DXS_48K; }; -static int __snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct via82xx *chip; int chip_type = 0, card_type; unsigned int i; @@ -2543,15 +2536,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); -} - static struct pci_driver via82xx_driver = { .name = KBUILD_MODNAME, .id_table = snd_via82xx_ids, diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c index b32f84ac17cc..7934bc5d6463 100644 --- a/sound/pci/via82xx_modem.c +++ b/sound/pci/via82xx_modem.c @@ -1088,10 +1088,10 @@ static int snd_via82xx_create(struct snd_card *card, } -static int __snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct via82xx_modem *chip; int chip_type = 0, card_type; unsigned int i; @@ -1139,15 +1139,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); -} - static struct pci_driver via82xx_modem_driver = { .name = KBUILD_MODNAME, .id_table = snd_via82xx_modem_ids, diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c index b6459dbdb1b0..40f3716c478f 100644 --- a/sound/pci/vx222/vx222_ops.c +++ b/sound/pci/vx222/vx222_ops.c @@ -431,7 +431,7 @@ static int vx2_load_dsp(struct vx_core *vx, int index, const struct firmware *ds /* * vx_test_and_ack - test and acknowledge interrupt * - * called from irq hander, too + * called from irq handler, too * * spinlock held! */ diff --git a/sound/pci/ymfpci/ymfpci.c b/sound/pci/ymfpci/ymfpci.c index d3fb047c9a45..5e8c7e318138 100644 --- a/sound/pci/ymfpci/ymfpci.c +++ b/sound/pci/ymfpci/ymfpci.c @@ -152,11 +152,11 @@ static inline int snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev, i void snd_ymfpci_free_gameport(struct snd_ymfpci *chip) { } #endif /* SUPPORT_JOYSTICK */ -static int __snd_card_ymfpci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_ymfpci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct resource *fm_res = NULL; struct resource *mpu_res = NULL; struct snd_ymfpci *chip; @@ -344,16 +344,11 @@ static int __snd_card_ymfpci_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_ymfpci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_ymfpci_probe(pci, pci_id)); -} - static struct pci_driver ymfpci_driver = { .name = KBUILD_MODNAME, .id_table = snd_ymfpci_ids, diff --git a/sound/pcmcia/vx/vxp_ops.c b/sound/pcmcia/vx/vxp_ops.c index 4211e7239138..e52b61125629 100644 --- a/sound/pcmcia/vx/vxp_ops.c +++ b/sound/pcmcia/vx/vxp_ops.c @@ -265,7 +265,7 @@ static int vxp_load_dsp(struct vx_core *vx, int index, const struct firmware *fw /* * vx_test_and_ack - test and acknowledge interrupt * - * called from irq hander, too + * called from irq handler, too * * spinlock held! */ diff --git a/sound/ppc/powermac.c b/sound/ppc/powermac.c index e685d245883e..30f4cd1a6d32 100644 --- a/sound/ppc/powermac.c +++ b/sound/ppc/powermac.c @@ -39,18 +39,18 @@ static struct platform_device *device; static int snd_pmac_probe(struct platform_device *devptr) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_pmac *chip; char *name_ext; int err; - err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_pmac_new(card, &chip); if (err < 0) - goto __error; + return err; card->private_data = chip; switch (chip->model) { @@ -61,7 +61,7 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_burgundy_init(chip); if (err < 0) - goto __error; + return err; break; case PMAC_DACA: strscpy(card->driver, "PMac DACA"); @@ -70,7 +70,7 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_daca_init(chip); if (err < 0) - goto __error; + return err; break; case PMAC_TUMBLER: case PMAC_SNAPPER: @@ -81,10 +81,10 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_tumbler_init(chip); if (err < 0) - goto __error; + return err; err = snd_pmac_tumbler_post_init(); if (err < 0) - goto __error; + return err; break; case PMAC_AWACS: case PMAC_SCREAMER: @@ -101,17 +101,16 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, name_ext, chip->revision); err = snd_pmac_awacs_init(chip); if (err < 0) - goto __error; + return err; break; default: dev_err(&devptr->dev, "unsupported hardware %d\n", chip->model); - err = -EINVAL; - goto __error; + return -EINVAL; } err = snd_pmac_pcm_new(chip); if (err < 0) - goto __error; + return err; chip->initialized = 1; if (enable_beep) @@ -119,20 +118,11 @@ static int snd_pmac_probe(struct platform_device *devptr) err = snd_card_register(card); if (err < 0) - goto __error; + return err; platform_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -__error: - snd_card_free(card); - return err; -} - - -static void snd_pmac_remove(struct platform_device *devptr) -{ - snd_card_free(platform_get_drvdata(devptr)); } #ifdef CONFIG_PM_SLEEP @@ -160,7 +150,6 @@ static SIMPLE_DEV_PM_OPS(snd_pmac_pm, snd_pmac_driver_suspend, snd_pmac_driver_r static struct platform_driver snd_pmac_driver = { .probe = snd_pmac_probe, - .remove = snd_pmac_remove, .driver = { .name = SND_PMAC_DRIVER, .pm = SND_PMAC_PM_OPS, diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c index 139c7b25927a..7ba52e7a24d7 100644 --- a/sound/ppc/tumbler.c +++ b/sound/ppc/tumbler.c @@ -1134,7 +1134,7 @@ static long tumbler_find_device(const char *device, const char *platform, gp->active_val = 0x4; gp->inactive_val = 0x5; /* Here are some crude hacks to extract the GPIO polarity and - * open collector informations out of the do-platform script + * open collector information out of the do-platform script * as we don't yet have an interpreter for these things */ if (platform) diff --git a/sound/sh/sh_dac_audio.c b/sound/sh/sh_dac_audio.c index 2d9a2d5b824e..5896cce424af 100644 --- a/sound/sh/sh_dac_audio.c +++ b/sound/sh/sh_dac_audio.c @@ -236,12 +236,6 @@ static int snd_sh_dac_pcm(struct snd_sh_dac *chip, int device) /* END OF PCM INTERFACE */ -/* driver .remove -- destructor */ -static void snd_sh_dac_remove(struct platform_device *devptr) -{ - snd_card_free(platform_get_drvdata(devptr)); -} - /* free -- it has been defined by create */ static int snd_sh_dac_free(struct snd_sh_dac *chip) { @@ -341,10 +335,10 @@ static int snd_sh_dac_create(struct snd_card *card, static int snd_sh_dac_probe(struct platform_device *devptr) { struct snd_sh_dac *chip; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; - err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) { dev_err(&devptr->dev, "cannot allocate the card\n"); return err; @@ -352,11 +346,11 @@ static int snd_sh_dac_probe(struct platform_device *devptr) err = snd_sh_dac_create(card, devptr, &chip); if (err < 0) - goto probe_error; + return err; err = snd_sh_dac_pcm(chip, 0); if (err < 0) - goto probe_error; + return err; strscpy(card->driver, "snd_sh_dac"); strscpy(card->shortname, "SuperH DAC audio driver"); @@ -364,16 +358,13 @@ static int snd_sh_dac_probe(struct platform_device *devptr) err = snd_card_register(card); if (err < 0) - goto probe_error; + return err; dev_info(&devptr->dev, "ALSA driver for SuperH DAC audio\n"); platform_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -probe_error: - snd_card_free(card); - return err; } /* @@ -381,7 +372,6 @@ static int snd_sh_dac_probe(struct platform_device *devptr) */ static struct platform_driver sh_dac_driver = { .probe = snd_sh_dac_probe, - .remove = snd_sh_dac_remove, .driver = { .name = "dac_audio", }, diff --git a/sound/soc/amd/acp-config.c b/sound/soc/amd/acp-config.c index 3d48bb0f100f..2fb9789cac0a 100644 --- a/sound/soc/amd/acp-config.c +++ b/sound/soc/amd/acp-config.c @@ -98,6 +98,13 @@ static const struct dmi_system_id acp70_acpi_flag_override_table[] = { DMI_MATCH(DMI_BOARD_NAME, "8EA2"), }, }, + { + /* Dell 14S DS14265 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell 14S DS14265"), + }, + }, {} }; @@ -428,17 +435,29 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[] = { }; EXPORT_SYMBOL(snd_soc_acpi_amd_acp70_sof_machines); -struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7x_sof_machines[] = { +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[] = { { .id = "AMDI1010", - .drv_name = "acp7x-dsp", + .drv_name = "acp7f-dsp", .pdata = &acp_quirk_data, - .fw_filename = "sof-acp7x.ri", - .sof_tplg_filename = "sof-acp7x.tplg", + .fw_filename = "sof-acp7f.ri", + .sof_tplg_filename = "sof-acp7f.tplg", }, {}, }; -EXPORT_SYMBOL(snd_soc_acpi_amd_acp7x_sof_machines); +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7f_sof_machines); + +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[] = { + { + .id = "AMDI1010", + .drv_name = "acp7b-dsp", + .pdata = &acp_quirk_data, + .fw_filename = "sof-acp7b.ri", + .sof_tplg_filename = "sof-acp7b.tplg", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7b_sof_machines); MODULE_DESCRIPTION("AMD ACP Machine Configuration Module"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/amd/acp-es8336.c b/sound/soc/amd/acp-es8336.c index 34e953d1a3a7..0cb0ee76191d 100644 --- a/sound/soc/amd/acp-es8336.c +++ b/sound/soc/amd/acp-es8336.c @@ -30,7 +30,6 @@ static unsigned long acp2x_machine_id; static struct snd_soc_jack st_jack; -static struct device *codec_dev; static struct gpio_desc *gpio_pa; static int sof_es8316_speaker_power_event(struct snd_soc_dapm_widget *w, @@ -191,6 +190,7 @@ static const struct acpi_gpio_mapping acpi_es8336_gpios[] = { static int st_es8336_late_probe(struct snd_soc_card *card) { + struct device *codec_dev __free(put_device) = NULL; struct acpi_device *adev; int ret; @@ -213,7 +213,6 @@ static int st_es8336_late_probe(struct snd_soc_card *card) if (IS_ERR(gpio_pa)) { ret = dev_err_probe(card->dev, PTR_ERR(gpio_pa), "could not get pa-enable GPIO\n"); - put_device(codec_dev); return ret; } return 0; diff --git a/sound/soc/amd/acp/Kconfig b/sound/soc/amd/acp/Kconfig index e880e44b9763..a62bf0fb1efc 100644 --- a/sound/soc/amd/acp/Kconfig +++ b/sound/soc/amd/acp/Kconfig @@ -167,7 +167,7 @@ config SND_SOC_AMD_SOF_SDW_MACH config SND_SOC_AMD_LEGACY_SDW_MACH tristate "AMD Legacy(No DSP) Soundwire Machine Driver Support" - depends on X86 && PCI && ACPI + depends on X86 && PCI && ACPI && EFI depends on SOUNDWIRE select SND_SOC_AMD_SDW_MACH_COMMON select SND_SOC_SDW_UTILS @@ -181,7 +181,8 @@ config SND_SOC_AMD_LEGACY_SDW_MACH select SND_SOC_RT715_SDW select SND_SOC_RT715_SDCA_SDW select SND_SOC_RT722_SDCA_SDW - select SND_SOC_TAS2783_SDW + select SND_SOC_ES9356 if SND_SOC_SDCA + select SND_SOC_TAS2783_SDW if SND_SOC_SDCA && EFI help This option enables Legacy(No DSP) sound card support for SoundWire enabled AMD platforms along with ACP PDM controller. diff --git a/sound/soc/amd/acp/acp-mach-common.h b/sound/soc/amd/acp/acp-mach-common.h new file mode 100644 index 000000000000..c5f1bb41c826 --- /dev/null +++ b/sound/soc/amd/acp/acp-mach-common.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved + */ +/* + * acp-mach-common.h - Structures shared across all AMD ACP machine drivers + * (I2S, SoundWire, and SOF paths). + */ + +#ifndef __ACP_MACH_COMMON_H +#define __ACP_MACH_COMMON_H + +/* ACP7X PDM controller selection values for pdm_sel; 0 = not set */ +#define ACP7X_PDM_DMIC0 1 +#define ACP7X_PDM_DMIC1 2 + +/** + * struct amd_pdm_pdata - PDM controller platform data passed via mach->pdata + * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), + * non-zero when a PDM controller was identified via ACPI _DSD for + * ACP7.B/7.F platforms. + * + * Passed via mach->pdata by machine select logic to both I2S and SoundWire + * machine drivers so each can configure the correct DMIC DAI link. + */ +struct amd_pdm_pdata { + unsigned int pdm_sel; +}; + +#endif /* __ACP_MACH_COMMON_H */ diff --git a/sound/soc/amd/acp/acp-platform.c b/sound/soc/amd/acp/acp-platform.c index 6b1e18b31c1c..f2c8853775ad 100644 --- a/sound/soc/amd/acp/acp-platform.c +++ b/sound/soc/amd/acp/acp-platform.c @@ -248,7 +248,12 @@ static int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_subs list_add_tail(&stream->list, &chip->stream_list); spin_unlock_irq(&chip->acp_lock); - return ret; + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ + return 0; } static int acp_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index 1a05d4288a46..a8b62817b234 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -265,6 +265,14 @@ static int create_sdw_dailink(struct snd_soc_card *card, if (!soc_end->dai_info->direction[stream]) continue; + /* + * skip adding reference stream for amp type + * as legacy AMD doesn't use the DSP. + */ + if (soc_end->dai_info->dai_type == SOC_SDW_DAI_TYPE_AMP && + stream == SNDRV_PCM_STREAM_CAPTURE) + continue; + int link_num = ffs(soc_end->link_mask) - 1; cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, @@ -448,6 +456,30 @@ static int soc_card_dai_links_create(struct snd_soc_card *card) sdw_be_num = ret; + /* + * Legacy AMD has no DSP, so AMP reference capture streams are not + * supported. Subtract them from num_devs so create_sdw_dailink() + * uses the correct count directly, and decrement sdw_be_num when + * all capture endpoints on a DAI are AMP type. + */ + for (int i = 0; i < num_ends; i++) { + struct asoc_sdw_dailink *d = &soc_dais[i]; + struct asoc_sdw_endpoint *e; + + if (!d->initialised) + break; + if (!d->num_devs[SNDRV_PCM_STREAM_CAPTURE]) + continue; + + list_for_each_entry(e, &d->endpoints, list) { + if (e->dai_info->direction[SNDRV_PCM_STREAM_CAPTURE] && + e->dai_info->dai_type == SOC_SDW_DAI_TYPE_AMP) + d->num_devs[SNDRV_PCM_STREAM_CAPTURE]--; + } + if (!d->num_devs[SNDRV_PCM_STREAM_CAPTURE]) + sdw_be_num--; + } + /* enable dmic */ if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) dmic_num = 1; diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c index c522b421e1ec..eb0e9a6af6ef 100644 --- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,11 @@ struct acp3x_es83xx_private { struct snd_soc_dapm_route mic_map[2]; }; +static void acp3x_es83xx_put_codec_device(void *data) +{ + put_device(data); +} + static const unsigned int channels[] = { DUAL_CHANNEL, }; @@ -441,6 +447,12 @@ static int acp3x_es83xx_probe(struct snd_soc_card *card) return -ENOMEM; } + ret = devm_add_action_or_reset(dev, + acp3x_es83xx_put_codec_device, + codec_dev); + if (ret) + return ret; + priv->codec_dev = codec_dev; priv->quirk = (unsigned long)dmi_id->driver_data; acp_drvdata->mach_priv = priv; diff --git a/sound/soc/amd/acp/amd-acp63-acpi-match.c b/sound/soc/amd/acp/amd-acp63-acpi-match.c index 1dbbaba3c75b..4f448c711c2c 100644 --- a/sound/soc/amd/acp/amd-acp63-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp63-acpi-match.c @@ -117,6 +117,42 @@ static const struct snd_soc_acpi_adr_device rt722_0_single_adr[] = { } }; +static const struct snd_soc_acpi_endpoint es9356_endpoints[] = { + { /* Jack Playback Endpoint */ + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { /* DMIC Capture Endpoint */ + .num = 1, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { /* Jack Capture Endpoint */ + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { /* Speaker Playback Endpoint */ + .num = 3, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +static const struct snd_soc_acpi_adr_device es9356_0_adr[] = { + { + .adr = 0x00003004b3935601ull, + .num_endpoints = ARRAY_SIZE(es9356_endpoints), + .endpoints = es9356_endpoints, + .name_prefix = "es9356" + } +}; + static const struct snd_soc_acpi_endpoint cs42l43_endpoints[] = { { /* Jack Playback Endpoint */ .num = 0, @@ -465,6 +501,15 @@ static const struct snd_soc_acpi_link_adr acp63_rt722_only[] = { {} }; +static const struct snd_soc_acpi_link_adr acp63_es9356_only[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(es9356_0_adr), + .adr_d = es9356_0_adr, + }, + {} +}; + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[] = { { .link_mask = BIT(0) | BIT(1), @@ -478,6 +523,11 @@ struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[] = { EXPORT_SYMBOL(snd_soc_acpi_amd_acp63_sof_sdw_machines); struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sdw_machines[] = { + { + .link_mask = BIT(0), + .links = acp63_es9356_only, + .drv_name = "amd_sdw", + }, { .link_mask = BIT(0), .links = acp63_rt722_only, diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 3930cc46fa58..034c9ab6e4ea 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -13,6 +13,7 @@ #include #include #include +#include "acp-mach-common.h" #define ACP63_SDW_MAX_CPU_DAIS 8 #define ACP63_SDW_MAX_LINKS 2 diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h index b602a983feb7..02ce044bcbf7 100644 --- a/sound/soc/amd/mach-config.h +++ b/sound/soc/amd/mach-config.h @@ -28,7 +28,8 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp63_sof_sdw_machines[]; 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_acp7x_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[]; struct config_entry { u32 flags; diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 62cb6bef17ab..d0cfbd4dafc1 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -294,6 +294,7 @@ struct acp63_dev_data; * struct acp_hw_ops - ACP PCI driver platform specific ops * @acp_init: ACP initialization * @acp_deinit: ACP de-initialization + * @disable_interrupts: disable ACP interrupt sources * @acp_get_config: function to read the acp pin configuration * @acp_sdw_dma_irq_thread: ACP SoundWire DMA interrupt thread * acp_suspend: ACP system level suspend callback @@ -304,6 +305,7 @@ struct acp63_dev_data; struct acp_hw_ops { int (*acp_init)(void __iomem *acp_base, struct device *dev); int (*acp_deinit)(void __iomem *acp_base, struct device *dev); + void (*disable_interrupts)(void __iomem *acp_base); void (*acp_get_config)(struct pci_dev *pci, struct acp63_dev_data *acp_data); void (*acp_sdw_dma_irq_thread)(struct acp63_dev_data *acp_data); int (*acp_suspend)(struct device *dev); @@ -397,6 +399,12 @@ static inline int acp_hw_deinit(struct acp63_dev_data *adata, struct device *dev return -EOPNOTSUPP; } +static inline void acp_hw_disable_interrupts(struct acp63_dev_data *adata) +{ + if (adata && adata->hw_ops && adata->hw_ops->disable_interrupts) + ACP_HW_OPS(adata, disable_interrupts)(adata->acp63_base); +} + static inline void acp_hw_get_config(struct pci_dev *pci, struct acp63_dev_data *adata) { if (adata && adata->hw_ops && adata->hw_ops->acp_get_config) diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index 729f9aaba69e..b604db4942dd 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -738,6 +738,8 @@ static void snd_acp63_remove(struct pci_dev *pci) int ret; adata = pci_get_drvdata(pci); + acp_hw_disable_interrupts(adata); + devm_free_irq(&pci->dev, pci->irq, adata); if (adata->sdw) { amd_sdw_exit(adata); platform_device_unregister(adata->sdw_dma_dev); diff --git a/sound/soc/amd/ps/ps-common.c b/sound/soc/amd/ps/ps-common.c index 7b4966b75dc6..ea71cdf19c1e 100644 --- a/sound/soc/amd/ps/ps-common.c +++ b/sound/soc/amd/ps/ps-common.c @@ -248,6 +248,7 @@ void acp63_hw_init_ops(struct acp_hw_ops *hw_ops) { hw_ops->acp_init = acp63_init; hw_ops->acp_deinit = acp63_deinit; + hw_ops->disable_interrupts = acp63_disable_interrupts; hw_ops->acp_get_config = acp63_get_config; hw_ops->acp_sdw_dma_irq_thread = acp63_sdw_dma_irq_thread; hw_ops->acp_suspend = snd_acp63_suspend; @@ -484,6 +485,7 @@ void acp70_hw_init_ops(struct acp_hw_ops *hw_ops) { hw_ops->acp_init = acp70_init; hw_ops->acp_deinit = acp70_deinit; + hw_ops->disable_interrupts = acp70_disable_interrupts; hw_ops->acp_get_config = acp70_get_config; hw_ops->acp_sdw_dma_irq_thread = acp70_sdw_dma_irq_thread; hw_ops->acp_suspend = snd_acp70_suspend; diff --git a/sound/soc/amd/ps/ps-pdm-dma.c b/sound/soc/amd/ps/ps-pdm-dma.c index 04c014349347..12c341e89bce 100644 --- a/sound/soc/amd/ps/ps-pdm-dma.c +++ b/sound/soc/amd/ps/ps-pdm-dma.c @@ -211,7 +211,13 @@ static int acp63_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp63_base = adata->acp63_base; runtime->private_data = pdm_data; - return ret; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ + return 0; } static int acp63_pdm_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index f27ebbd21379..c2640985f54c 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -337,7 +337,13 @@ static int acp63_sdw_dma_open(struct snd_soc_component *component, stream->stream_id = cpu_dai->id; stream->instance = amd_manager->instance; runtime->private_data = stream; - return ret; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ + return 0; } static int acp63_sdw_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/raven/acp3x-pcm-dma.c b/sound/soc/amd/raven/acp3x-pcm-dma.c index 37ea5c572eb9..765057499021 100644 --- a/sound/soc/amd/raven/acp3x-pcm-dma.c +++ b/sound/soc/amd/raven/acp3x-pcm-dma.c @@ -237,7 +237,13 @@ static int acp3x_dma_open(struct snd_soc_component *component, i2s_data->acp3x_base = adata->acp3x_base; runtime->private_data = i2s_data; - return ret; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ + return 0; } diff --git a/sound/soc/amd/renoir/acp3x-pdm-dma.c b/sound/soc/amd/renoir/acp3x-pdm-dma.c index 3f59b753243d..89dc60849c06 100644 --- a/sound/soc/amd/renoir/acp3x-pdm-dma.c +++ b/sound/soc/amd/renoir/acp3x-pdm-dma.c @@ -233,7 +233,13 @@ static int acp_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp_base = adata->acp_base; runtime->private_data = pdm_data; - return ret; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ + return 0; } static int acp_pdm_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/vangogh/acp5x-pcm-dma.c b/sound/soc/amd/vangogh/acp5x-pcm-dma.c index 831e30e9b042..72050fa2de80 100644 --- a/sound/soc/amd/vangogh/acp5x-pcm-dma.c +++ b/sound/soc/amd/vangogh/acp5x-pcm-dma.c @@ -231,7 +231,13 @@ static int acp5x_dma_open(struct snd_soc_component *component, } i2s_data->acp5x_base = adata->acp5x_base; runtime->private_data = i2s_data; - return ret; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ + return 0; } static int acp5x_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 1be8504571ad..41f8c0885865 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -514,6 +514,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Bravo 15 B7ED"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_PRODUCT_NAME, "Crosshair A17 HX D8WFKG"), + } + }, { .driver_data = &acp6x_card, .matches = { @@ -528,6 +535,13 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Bravo 15 C7VF"), } }, + { + .driver_data = &acp6x_card, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_PRODUCT_NAME, "Alpha 17 C7VF"), + } + }, { .driver_data = &acp6x_card, .matches = { diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c index 40c4d833f4ed..cba2509faaa1 100644 --- a/sound/soc/amd/yc/acp6x-pdm-dma.c +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -209,7 +209,13 @@ static int acp6x_pdm_dma_open(struct snd_soc_component *component, pdm_data->acp6x_base = adata->acp6x_base; runtime->private_data = pdm_data; - return ret; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ + return 0; } static int acp6x_pdm_dma_hw_params(struct snd_soc_component *component, diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c index be702b2942a5..26022a42a95e 100644 --- a/sound/soc/apple/mca.c +++ b/sound/soc/apple/mca.c @@ -516,6 +516,7 @@ static int mca_fe_startup(struct snd_pcm_substream *substream, { struct mca_cluster *cl = mca_dai_to_cluster(dai); unsigned int mask, nchannels; + int ret; if (cl->tdm_slots) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) @@ -528,9 +529,18 @@ static int mca_fe_startup(struct snd_pcm_substream *substream, nchannels = 2; } - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, - 1, nchannels); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, nchannels); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ + return 0; } static int mca_fe_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c index d9ad262ec44d..e1d8af056234 100644 --- a/sound/soc/atmel/atmel-i2s.c +++ b/sound/soc/atmel/atmel-i2s.c @@ -466,11 +466,21 @@ static int atmel_i2s_switch_mck_generator(struct atmel_i2s_dev *dev, mr |= ATMEL_I2SC_MR_IMCKMODE_I2SMCK; ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr); if (ret) - return ret; + goto err_disable_gclk; /* Finally enable the I2S Master Clock generator. */ - return regmap_write(dev->regmap, ATMEL_I2SC_CR, - ATMEL_I2SC_CR_CKEN); + ret = regmap_write(dev->regmap, ATMEL_I2SC_CR, ATMEL_I2SC_CR_CKEN); + if (ret) + goto err_reset_mr; + + return 0; + +err_reset_mr: + regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, 0); +err_disable_gclk: + clk_disable_unprepare(dev->gclk); + + return ret; } static int atmel_i2s_trigger(struct snd_pcm_substream *substream, int cmd, @@ -579,21 +589,15 @@ static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev, struct device_node *np) { struct clk *muxclk; - int err; if (!dev->gclk) return 0; - /* muxclk is optional, so we return error for probe defer only */ - muxclk = devm_clk_get(dev->dev, "muxclk"); - if (IS_ERR(muxclk)) { - err = PTR_ERR(muxclk); - if (err == -EPROBE_DEFER) - return -EPROBE_DEFER; - dev_dbg(dev->dev, - "failed to get the I2S clock control: %d\n", err); - return 0; - } + /* muxclk is optional */ + muxclk = devm_clk_get_optional(dev->dev, "muxclk"); + if (IS_ERR(muxclk)) + return dev_err_probe(dev->dev, PTR_ERR(muxclk), + "failed to get the I2S clock control\n"); return clk_set_parent(muxclk, dev->gclk); } @@ -658,21 +662,15 @@ static int atmel_i2s_probe(struct platform_device *pdev) /* Get the peripheral clock. */ dev->pclk = devm_clk_get(&pdev->dev, "pclk"); - if (IS_ERR(dev->pclk)) { - err = PTR_ERR(dev->pclk); - dev_err(&pdev->dev, - "failed to get the peripheral clock: %d\n", err); - return err; - } + if (IS_ERR(dev->pclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(dev->pclk), + "failed to get the peripheral clock\n"); /* Get audio clock to generate the I2S Master Clock (I2S_MCK) */ - dev->gclk = devm_clk_get(&pdev->dev, "gclk"); - if (IS_ERR(dev->gclk)) { - if (PTR_ERR(dev->gclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - /* Master Mode not supported */ - dev->gclk = NULL; - } + dev->gclk = devm_clk_get_optional(&pdev->dev, "gclk"); + if (IS_ERR(dev->gclk)) + return PTR_ERR(dev->gclk); + dev->dev = &pdev->dev; dev->regmap = regmap; platform_set_drvdata(pdev, dev); @@ -701,7 +699,6 @@ static int atmel_i2s_probe(struct platform_device *pdev) &atmel_i2s_component, &atmel_i2s_dai, 1); if (err) { - dev_err(&pdev->dev, "failed to register DAI: %d\n", err); clk_disable_unprepare(dev->pclk); return err; } @@ -716,7 +713,6 @@ static int atmel_i2s_probe(struct platform_device *pdev) pcm_flags |= SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX; err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, pcm_flags); if (err) { - dev_err(&pdev->dev, "failed to register PCM: %d\n", err); clk_disable_unprepare(dev->pclk); return err; } diff --git a/sound/soc/atmel/atmel-pcm-dma.c b/sound/soc/atmel/atmel-pcm-dma.c index 7306e04da513..e4b73ae3df4f 100644 --- a/sound/soc/atmel/atmel-pcm-dma.c +++ b/sound/soc/atmel/atmel-pcm-dma.c @@ -47,7 +47,7 @@ static const struct snd_pcm_hardware atmel_pcm_dma_hardware = { * atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC * * We use DMAENGINE to send/receive data to/from SSC so this ISR is only to - * check if any overrun occured. + * check if any overrun occurred. */ static void atmel_pcm_dma_irq(u32 ssc_sr, struct snd_pcm_substream *substream) diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c index 374f93fa6e0b..4cedf2ad934d 100644 --- a/sound/soc/atmel/atmel-pcm-pdc.c +++ b/sound/soc/atmel/atmel-pcm-pdc.c @@ -295,6 +295,13 @@ static int atmel_pcm_open(struct snd_soc_component *component, } runtime->private_data = prtd; + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ + return 0; + out: return ret; } diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h index 4925e03c3c30..7848e15efbe1 100644 --- a/sound/soc/bcm/cygnus-ssp.h +++ b/sound/soc/bcm/cygnus-ssp.h @@ -27,7 +27,7 @@ struct ringbuf_regs { unsigned wraddr; unsigned baseaddr; unsigned endaddr; - unsigned fmark; /* freemark for play, fullmark for caputure */ + unsigned fmark; /* freemark for play, fullmark for capture */ unsigned period_bytes; unsigned buf_size; }; diff --git a/sound/soc/codecs/88pm860x-codec.c b/sound/soc/codecs/88pm860x-codec.c index b4f5b24cde45..7596227fbccf 100644 --- a/sound/soc/codecs/88pm860x-codec.c +++ b/sound/soc/codecs/88pm860x-codec.c @@ -1136,11 +1136,15 @@ static int pm860x_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 pm860x_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S; + static const struct snd_soc_dai_ops pm860x_pcm_dai_ops = { .mute_stream = pm860x_mute_stream, .hw_params = pm860x_pcm_hw_params, .set_fmt = pm860x_pcm_set_dai_fmt, .set_sysclk = pm860x_set_dai_sysclk, + .auto_selectable_formats = &pm860x_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1149,6 +1153,8 @@ static const struct snd_soc_dai_ops pm860x_i2s_dai_ops = { .hw_params = pm860x_i2s_hw_params, .set_fmt = pm860x_i2s_set_dai_fmt, .set_sysclk = pm860x_set_dai_sysclk, + .auto_selectable_formats = &pm860x_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 8e251e1d84b4..ba59dabeeccd 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -123,6 +123,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_ES8328_I2C imply SND_SOC_ES8375 imply SND_SOC_ES8389 + imply SND_SOC_ES9039Q2M imply SND_SOC_ES9356 imply SND_SOC_ES7134 imply SND_SOC_ES7241 @@ -173,6 +174,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_MT6660 imply SND_SOC_NAU8315 imply SND_SOC_NAU8325 + imply SND_SOC_NAU8360 imply SND_SOC_NAU8540 imply SND_SOC_NAU8810 imply SND_SOC_NAU8821 @@ -255,6 +257,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_SIMPLE_MUX imply SND_SOC_SMA1303 imply SND_SOC_SMA1307 + imply SND_SOC_SN624X_SDCA_SDW imply SND_SOC_SPDIF imply SND_SOC_SRC4XXX_I2C imply SND_SOC_SSM2305 @@ -1315,6 +1318,19 @@ config SND_SOC_ES8389 tristate "Everest Semi ES8389 CODEC" depends on I2C +config SND_SOC_ES9039Q2M + tristate "ESS Technology ES9039Q2M CODEC" + depends on I2C + select REGMAP_I2C + help + Enable support for the ESS Technology ES9039Q2M, a 32-bit + two-channel audio DAC with an asynchronous sample rate converter, + supporting PCM, DSD, DoP and S/PDIF input. This driver covers the + I2C control interface, which the MODE pin selects. + + To compile this driver as a module, choose M here: the module + will be called snd-soc-es9039q2m. + config SND_SOC_ES9356 tristate "Everest Semi ES9356 CODEC SDW" depends on SND_SOC_SDCA @@ -2097,6 +2113,18 @@ config SND_SOC_SMA1307 If you are using a system with an SMA1307 amplifier connected via I2C, enable this option. +config SND_SOC_SN624X_SDCA_SDW + tristate "Senary SN624x SDCA Codec - SDW" + depends on SND_SOC_SDCA + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + help + Enable support for Senary SN624x SDCA SoundWire codecs. + These multi-function devices provide headset jack, speaker + amplifier, and digital microphone endpoints on the SoundWire + bus. + config SND_SOC_SPDIF tristate "S/PDIF CODEC" @@ -2524,6 +2552,17 @@ config SND_SOC_WCD939X_SDW The WCD9390/9395 is a audio codec IC Integrated in Qualcomm SoCs like SM8650. +config SND_SOC_WCD9378_SDCA + tristate "Qualcomm Tambora (WCD9378) SDCA codec" + depends on SOUNDWIRE + depends on SND_SOC_SDCA_CLASS + help + This enables support for the Qualcomm Tambora (WCD9378) headset + codec when driven via the SDCA class driver on ARM platforms + without ACPI/DisCo tables. It provides the static SDCA topology + and SoundWire data port properties transcribed from the factory + ACPI tables. + config SND_SOC_WM0010 tristate depends on SPI_MASTER @@ -2901,6 +2940,16 @@ config SND_SOC_NAU8825 tristate depends on I2C +config SND_SOC_NAU8360 + tristate "Nuvoton Technology Corporation NAU83G60 Stereo Smart Amplifier" + depends on I2C + select FW_LOADER + help + The NAU83G60 is a stereo 30W+30W smart amplifier with integrated + low-latency Advanced Audio DSP running the KCS (Klippel Controlled + Sound) speaker control algorithm. KCS provides mechanical and + thermal speaker protection with voltage and current sensing. + config SND_SOC_NTPFW tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3d122ace75ad..3826f9e4fce8 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -140,6 +140,7 @@ snd-soc-es8328-i2c-y := es8328-i2c.o snd-soc-es8328-spi-y := es8328-spi.o snd-soc-es8375-y := es8375.o snd-soc-es8389-y := es8389.o +snd-soc-es9039q2m-y := es9039q2m.o snd-soc-es9356-y := es9356.o snd-soc-framer-y := framer-codec.o snd-soc-fs-amp-lib-y := fs-amp-lib.o @@ -198,6 +199,7 @@ snd-soc-mt6359-accdet-y := mt6359-accdet.o snd-soc-mt6660-y := mt6660.o snd-soc-nau8315-y := nau8315.o snd-soc-nau8325-y := nau8325.o +snd-soc-nau8360-y := nau8360.o nau8360-dsp.o snd-soc-nau8540-y := nau8540.o snd-soc-nau8810-y := nau8810.o snd-soc-nau8821-y := nau8821.o @@ -301,6 +303,7 @@ snd-soc-sigmadsp-regmap-y := sigmadsp-regmap.o snd-soc-si476x-y := si476x.o snd-soc-sma1303-y := sma1303.o snd-soc-sma1307-y := sma1307.o +snd-soc-sn624x-sdca-y := sn624x-sdca-sdw.o snd-soc-spdif-tx-y := spdif_transmitter.o snd-soc-spdif-rx-y := spdif_receiver.o snd-soc-src4xxx-y := src4xxx.o @@ -366,6 +369,7 @@ snd-soc-wcd938x-y := wcd938x.o snd-soc-wcd938x-sdw-y := wcd938x-sdw.o snd-soc-wcd939x-y := wcd939x.o snd-soc-wcd939x-sdw-y := wcd939x-sdw.o +snd-soc-wcd9378-y := wcd9378-sdw.o wcd9378-sdca.o snd-soc-wm-adsp-y := wm_adsp.o snd-soc-wm-adsp-test-y := wm_adsp_fw_find_test.o snd-soc-wm0010-y := wm0010.o @@ -584,6 +588,7 @@ obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o +obj-$(CONFIG_SND_SOC_ES9039Q2M) += snd-soc-es9039q2m.o obj-$(CONFIG_SND_SOC_ES9356) += snd-soc-es9356.o obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o @@ -637,6 +642,7 @@ obj-$(CONFIG_SND_SOC_MT6359_ACCDET) += mt6359-accdet.o obj-$(CONFIG_SND_SOC_MT6660) += snd-soc-mt6660.o obj-$(CONFIG_SND_SOC_NAU8315) += snd-soc-nau8315.o obj-$(CONFIG_SND_SOC_NAU8325) += snd-soc-nau8325.o +obj-$(CONFIG_SND_SOC_NAU8360) += snd-soc-nau8360.o obj-$(CONFIG_SND_SOC_NAU8540) += snd-soc-nau8540.o obj-$(CONFIG_SND_SOC_NAU8810) += snd-soc-nau8810.o obj-$(CONFIG_SND_SOC_NAU8821) += snd-soc-nau8821.o @@ -742,6 +748,7 @@ obj-$(CONFIG_SND_SOC_SIGMADSP_REGMAP) += snd-soc-sigmadsp-regmap.o obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o obj-$(CONFIG_SND_SOC_SMA1303) += snd-soc-sma1303.o obj-$(CONFIG_SND_SOC_SMA1307) += snd-soc-sma1307.o +obj-$(CONFIG_SND_SOC_SN624X_SDCA_SDW) += snd-soc-sn624x-sdca.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif-rx.o snd-soc-spdif-tx.o obj-$(CONFIG_SND_SOC_SRC4XXX) += snd-soc-src4xxx.o obj-$(CONFIG_SND_SOC_SRC4XXX_I2C) += snd-soc-src4xxx-i2c.o @@ -819,6 +826,7 @@ ifdef CONFIG_SND_SOC_WCD939X_SDW # avoid link failure by forcing sdw code built-in when needed obj-$(CONFIG_SND_SOC_WCD939X) += snd-soc-wcd939x-sdw.o endif +obj-$(CONFIG_SND_SOC_WCD9378_SDCA) += snd-soc-wcd9378.o obj-$(CONFIG_SND_SOC_WM0010) += snd-soc-wm0010.o obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o diff --git a/sound/soc/codecs/ab8500-codec.c b/sound/soc/codecs/ab8500-codec.c index e1a0e35836e6..3e3ff9bfe00d 100644 --- a/sound/soc/codecs/ab8500-codec.c +++ b/sound/soc/codecs/ab8500-codec.c @@ -299,7 +299,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_REGULATOR_SUPPLY("V-AUD", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC1", 0, 0), SND_SOC_DAPM_REGULATOR_SUPPLY("V-AMIC2", 0, 0), - SND_SOC_DAPM_REGULATOR_SUPPLY("V-DMIC", 0, 0), /* Power */ SND_SOC_DAPM_SUPPLY("Audio Power", @@ -324,7 +323,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_AIF_OUT("AD_OUT1", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT2", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT3", NULL, 0, SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_AIF_OUT("AD_OUT4", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT57", NULL, 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("AD_OUT68", NULL, 0, SND_SOC_NOPM, 0, 0), @@ -584,36 +582,6 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("AD5768 Enable", AB8500_ADPATHENA, AB8500_ADPATHENA_ENAD5768, 0, NULL, 0), - /* Digital Microphone path */ - - SND_SOC_DAPM_INPUT("DMic 1"), - SND_SOC_DAPM_INPUT("DMic 2"), - SND_SOC_DAPM_INPUT("DMic 3"), - SND_SOC_DAPM_INPUT("DMic 4"), - SND_SOC_DAPM_INPUT("DMic 5"), - SND_SOC_DAPM_INPUT("DMic 6"), - - SND_SOC_DAPM_MIXER("DMIC1", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC1, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC2", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC2, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC3", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC3, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC4", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC4, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC5", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC5, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("DMIC6", - AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC6, 0, - NULL, 0), - SND_SOC_DAPM_MIXER("AD4 Channel Volume", - SND_SOC_NOPM, 0, 0, - NULL, 0), /* Acoustical Noise Cancellation path */ SND_SOC_DAPM_MUX("ANC Source", @@ -648,6 +616,40 @@ static const struct snd_soc_dapm_widget ab8500_dapm_widgets[] = { NULL, 0), }; +static const struct snd_soc_dapm_widget ab8500_dmic_dapm_widgets[] = { + SND_SOC_DAPM_REGULATOR_SUPPLY("V-DMIC", 0, 0), + SND_SOC_DAPM_AIF_OUT("AD_OUT4", NULL, 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_INPUT("DMic 1"), + SND_SOC_DAPM_INPUT("DMic 2"), + SND_SOC_DAPM_INPUT("DMic 3"), + SND_SOC_DAPM_INPUT("DMic 4"), + SND_SOC_DAPM_INPUT("DMic 5"), + SND_SOC_DAPM_INPUT("DMic 6"), + + SND_SOC_DAPM_MIXER("DMIC1", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC1, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC2", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC2, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC3", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC3, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC4", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC4, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC5", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC5, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("DMIC6", + AB8500_DIGMICCONF, AB8500_DIGMICCONF_ENDMIC6, 0, + NULL, 0), + SND_SOC_DAPM_MIXER("AD4 Channel Volume", + SND_SOC_NOPM, 0, 0, + NULL, 0), +}; + /* * DAPM-routes */ @@ -853,35 +855,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"AD_OUT68", NULL, "Main Supply"}, {"AD_OUT68", NULL, "AD6 Channel Volume"}, - /* Digital Microphone path */ - - {"DMic 1", NULL, "V-DMIC"}, - {"DMic 2", NULL, "V-DMIC"}, - {"DMic 3", NULL, "V-DMIC"}, - {"DMic 4", NULL, "V-DMIC"}, - {"DMic 5", NULL, "V-DMIC"}, - {"DMic 6", NULL, "V-DMIC"}, - - {"DMIC1", NULL, "DMic 1"}, - {"DMIC2", NULL, "DMic 2"}, - {"DMIC3", NULL, "DMic 3"}, - {"DMIC4", NULL, "DMic 4"}, - {"DMIC5", NULL, "DMic 5"}, - {"DMIC6", NULL, "DMic 6"}, - - {"AD1 Source Select", "DMic 1", "DMIC1"}, - {"AD2 Source Select", "DMic 2", "DMIC2"}, - {"AD3 Source Select", "DMic 3", "DMIC3"}, - {"AD5 Source Select", "DMic 5", "DMIC5"}, - {"AD6 Source Select", "DMic 6", "DMIC6"}, - - {"AD4 Channel Volume", NULL, "DMIC4"}, - {"AD4 Channel Volume", NULL, "AD34 Enable"}, - - {"ab8500_0c", NULL, "AD_OUT4"}, - {"AD_OUT4", NULL, "Main Supply"}, - {"AD_OUT4", NULL, "AD4 Channel Volume"}, - /* LineIn Bypass path */ {"LINL to HSL Volume", NULL, "LINL Enable"}, @@ -911,7 +884,6 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"Sidetone Left Source", "Headset Left", "DA_IN1"}, {"Sidetone Right Source", "LineIn Right", "AD2 Channel Volume"}, {"Sidetone Right Source", "Mic 1", "AD3 Channel Volume"}, - {"Sidetone Right Source", "DMic 4", "AD4 Channel Volume"}, {"Sidetone Right Source", "Headset Right", "DA_IN2"}, {"STFIR1 Control", NULL, "Sidetone Left Source"}, @@ -924,6 +896,37 @@ static const struct snd_soc_dapm_route ab8500_dapm_routes[] = { {"DA2 Enable", NULL, "STFIR2 Volume"}, }; +static const struct snd_soc_dapm_route ab8500_dmic_dapm_routes[] = { + {"DMic 1", NULL, "V-DMIC"}, + {"DMic 2", NULL, "V-DMIC"}, + {"DMic 3", NULL, "V-DMIC"}, + {"DMic 4", NULL, "V-DMIC"}, + {"DMic 5", NULL, "V-DMIC"}, + {"DMic 6", NULL, "V-DMIC"}, + + {"DMIC1", NULL, "DMic 1"}, + {"DMIC2", NULL, "DMic 2"}, + {"DMIC3", NULL, "DMic 3"}, + {"DMIC4", NULL, "DMic 4"}, + {"DMIC5", NULL, "DMic 5"}, + {"DMIC6", NULL, "DMic 6"}, + + {"AD1 Source Select", "DMic 1", "DMIC1"}, + {"AD2 Source Select", "DMic 2", "DMIC2"}, + {"AD3 Source Select", "DMic 3", "DMIC3"}, + {"AD5 Source Select", "DMic 5", "DMIC5"}, + {"AD6 Source Select", "DMic 6", "DMIC6"}, + + {"AD4 Channel Volume", NULL, "DMIC4"}, + {"AD4 Channel Volume", NULL, "AD34 Enable"}, + + {"ab8500_0c", NULL, "AD_OUT4"}, + {"AD_OUT4", NULL, "Main Supply"}, + {"AD_OUT4", NULL, "AD4 Channel Volume"}, + + {"Sidetone Right Source", "DMic 4", "AD4 Channel Volume"}, +}; + static const struct snd_soc_dapm_route ab8500_dapm_routes_mic1a_vamicx[] = { {"MIC1A V-AMICx Enable", NULL, "V-AMIC1"}, {"MIC1A V-AMICx Enable", NULL, "V-AMIC2"}, @@ -1950,9 +1953,22 @@ static int ab8500_codec_set_dai_tdm_slot(struct snd_soc_dai *dai, return 0; } +static const u64 ab8500_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_GATED | + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ab8500_codec_ops = { .set_fmt = ab8500_codec_set_dai_fmt, .set_tdm_slot = ab8500_codec_set_dai_tdm_slot, + .auto_selectable_formats = &ab8500_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ab8500_codec_dai[] = { @@ -2043,13 +2059,30 @@ static int ab8500_codec_probe(struct snd_soc_component *component) { struct device *dev = component->dev; struct device_node *np = dev->of_node; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); struct ab8500_codec_platform_data codec_pdata; + struct snd_soc_dapm_context *dapm; int status; dev_dbg(dev, "%s: Enter.\n", __func__); ab8500_codec_of_probe(dev, np, &codec_pdata); + if (is_ab8500(ab8500)) { + dapm = snd_soc_component_to_dapm(component); + status = snd_soc_dapm_new_controls(dapm, + ab8500_dmic_dapm_widgets, + ARRAY_SIZE(ab8500_dmic_dapm_widgets)); + if (status) + return status; + + status = snd_soc_dapm_add_routes(dapm, + ab8500_dmic_dapm_routes, + ARRAY_SIZE(ab8500_dmic_dapm_routes)); + if (status) + return status; + } + status = ab8500_audio_init_audioblock(component); if (status < 0) { dev_err(dev, "%s: failed to init audio-block (%d)!\n", diff --git a/sound/soc/codecs/adau1372.c b/sound/soc/codecs/adau1372.c index c3d3be898ba6..25d7b38c28ad 100644 --- a/sound/soc/codecs/adau1372.c +++ b/sound/soc/codecs/adau1372.c @@ -880,12 +880,24 @@ static const struct snd_soc_component_driver adau1372_driver = { .endianness = 1, }; +static const u64 adau1372_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau1372_dai_ops = { .set_fmt = adau1372_set_dai_fmt, .set_tdm_slot = adau1372_set_tdm_slot, .set_tristate = adau1372_set_tristate, .hw_params = adau1372_hw_params, .startup = adau1372_startup, + .auto_selectable_formats = &adau1372_selectable_formats, + .num_auto_selectable_formats = 1, }; #define ADAU1372_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c index 5dd961b233ce..aa7d3a4b9f58 100644 --- a/sound/soc/codecs/adau1373.c +++ b/sound/soc/codecs/adau1373.c @@ -1196,10 +1196,22 @@ static int adau1373_set_dai_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 adau1373_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau1373_dai_ops = { .hw_params = adau1373_hw_params, .set_sysclk = adau1373_set_dai_sysclk, .set_fmt = adau1373_set_dai_fmt, + .auto_selectable_formats = &adau1373_selectable_formats, + .num_auto_selectable_formats = 1, }; #define ADAU1373_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c index 329ab01b62c0..4afbf46ffaa3 100644 --- a/sound/soc/codecs/adau1701.c +++ b/sound/soc/codecs/adau1701.c @@ -4,7 +4,7 @@ * * Copyright 2011 Analog Devices Inc. * Author: Lars-Peter Clausen - * based on an inital version by Cliff Cai + * based on an initial version by Cliff Cai */ #include @@ -627,11 +627,22 @@ static int adau1701_startup(struct snd_pcm_substream *substream, #define ADAU1701_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 adau1701_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau1701_dai_ops = { .set_fmt = adau1701_set_dai_fmt, .hw_params = adau1701_hw_params, .mute_stream = adau1701_mute_stream, .startup = adau1701_startup, + .auto_selectable_formats = &adau1701_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -688,7 +699,7 @@ static int adau1701_probe(struct snd_soc_component *component) */ adau1701->pll_clkdiv = ADAU1707_CLKDIV_UNSET; - /* initalize with pre-configured pll mode settings */ + /* initialize with pre-configured pll mode settings */ ret = adau1701_reset(component, adau1701->pll_clkdiv, 0); if (ret < 0) goto exit_regulators_disable; diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index 26d7eb437ad1..207189cfd3a6 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -776,6 +776,17 @@ static int adau17x1_startup(struct snd_pcm_substream *substream, return 0; } +static const u64 adau17x1_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + const struct snd_soc_dai_ops adau17x1_dai_ops = { .hw_params = adau17x1_hw_params, .set_sysclk = adau17x1_set_dai_sysclk, @@ -783,6 +794,8 @@ const struct snd_soc_dai_ops adau17x1_dai_ops = { .set_pll = adau17x1_set_dai_pll, .set_tdm_slot = adau17x1_set_dai_tdm_slot, .startup = adau17x1_startup, + .auto_selectable_formats = &adau17x1_selectable_formats, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(adau17x1_dai_ops); @@ -989,9 +1002,7 @@ int adau17x1_resume(struct snd_soc_component *component) if (adau->switch_mode) adau->switch_mode(component->dev); - regcache_sync(adau->regmap); - - return 0; + return regcache_sync(adau->regmap); } EXPORT_SYMBOL_GPL(adau17x1_resume); diff --git a/sound/soc/codecs/adau1977.c b/sound/soc/codecs/adau1977.c index b42f35b1a603..b9d985393421 100644 --- a/sound/soc/codecs/adau1977.c +++ b/sound/soc/codecs/adau1977.c @@ -741,6 +741,17 @@ static int adau1977_set_tristate(struct snd_soc_dai *dai, int tristate) ADAU1977_SAI_OVERTEMP_DRV_HIZ, val); } +static const u64 adau1977_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau1977_dai_ops = { .startup = adau1977_startup, .hw_params = adau1977_hw_params, @@ -748,6 +759,8 @@ static const struct snd_soc_dai_ops adau1977_dai_ops = { .set_fmt = adau1977_set_dai_fmt, .set_tdm_slot = adau1977_set_tdm_slot, .set_tristate = adau1977_set_tristate, + .auto_selectable_formats = &adau1977_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver adau1977_dai = { diff --git a/sound/soc/codecs/adau7118.c b/sound/soc/codecs/adau7118.c index 5ddea63a6612..9749299bc7f2 100644 --- a/sound/soc/codecs/adau7118.c +++ b/sound/soc/codecs/adau7118.c @@ -418,12 +418,24 @@ static int adau7118_component_probe(struct snd_soc_component *component) return ret; } +static const u64 adau7118_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adau7118_ops = { .hw_params = adau7118_hw_params, .set_channel_map = adau7118_set_channel_map, .set_fmt = adau7118_set_fmt, .set_tdm_slot = adau7118_set_tdm_slot, .set_tristate = adau7118_set_tristate, + .auto_selectable_formats = &adau7118_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver adau7118_dai = { diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c index 6ea7cb78cd44..5ebc3afccccf 100644 --- a/sound/soc/codecs/ak4104.c +++ b/sound/soc/codecs/ak4104.c @@ -147,9 +147,16 @@ static int ak4104_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 ak4101_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak4101_dai_ops = { .hw_params = ak4104_hw_params, .set_fmt = ak4104_set_dai_fmt, + .auto_selectable_formats = &ak4101_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak4104_dai = { diff --git a/sound/soc/codecs/ak4118.c b/sound/soc/codecs/ak4118.c index 03dadba0b038..2b96b82d4e40 100644 --- a/sound/soc/codecs/ak4118.c +++ b/sound/soc/codecs/ak4118.c @@ -236,9 +236,16 @@ static int ak4118_hw_params(struct snd_pcm_substream *substream, return 0; } +/* NOTE: Common formats only are auto detected */ +static const u64 ak4118_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak4118_dai_ops = { .hw_params = ak4118_hw_params, .set_fmt = ak4118_set_dai_fmt, + .auto_selectable_formats = &ak4118_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak4118_dai = { diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c index f81cd8cebdd8..a74be99e877d 100644 --- a/sound/soc/codecs/ak4458.c +++ b/sound/soc/codecs/ak4458.c @@ -591,12 +591,21 @@ static int ak4458_startup(struct snd_pcm_substream *substream, &ak4458_rate_constraints); } +static const u64 ak4458_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_PDM; + static const struct snd_soc_dai_ops ak4458_dai_ops = { .startup = ak4458_startup, .hw_params = ak4458_hw_params, .set_fmt = ak4458_set_dai_fmt, .mute_stream = ak4458_set_dai_mute, .set_tdm_slot = ak4458_set_tdm_slot, + .auto_selectable_formats = &ak4458_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 37555674cd15..6cabad4c9f74 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -346,11 +346,17 @@ static int ak4535_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +static const u64 ak4535_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak4535_dai_ops = { .hw_params = ak4535_hw_params, .set_fmt = ak4535_set_dai_fmt, .mute_stream = ak4535_mute, .set_sysclk = ak4535_set_dai_sysclk, + .auto_selectable_formats = &ak4535_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/ak4619.c b/sound/soc/codecs/ak4619.c index d9c9f6b20028..bae8e8b6a430 100644 --- a/sound/soc/codecs/ak4619.c +++ b/sound/soc/codecs/ak4619.c @@ -152,6 +152,7 @@ struct ak4619_priv { struct regmap *regmap; struct snd_pcm_hw_constraint_list constraint; + struct gpio_desc *pdn_gpio; int deemph_en; unsigned int playback_rate; unsigned int sysclk; @@ -799,7 +800,26 @@ static const struct snd_soc_dai_ops ak4619_dai_ops = { .num_auto_selectable_formats = ARRAY_SIZE(ak4619_dai_formats), }; +static int ak4619_suspend(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, true); + regcache_mark_dirty(regmap); + return 0; +} + +static int ak4619_resume(struct snd_soc_component *component) +{ + struct regmap *regmap = dev_get_regmap(component->dev, NULL); + + regcache_cache_only(regmap, false); + return regcache_sync(regmap); +} + static const struct snd_soc_component_driver soc_component_dev_ak4619 = { + .suspend = ak4619_suspend, + .resume = ak4619_resume, .set_bias_level = ak4619_set_bias_level, .controls = ak4619_snd_controls, .num_controls = ARRAY_SIZE(ak4619_snd_controls), @@ -875,10 +895,20 @@ static int ak4619_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, ak4619); + ak4619->pdn_gpio = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(ak4619->pdn_gpio)) + return dev_err_probe(dev, PTR_ERR(ak4619->pdn_gpio), + "powerdown GPIO request failed\n"); + if (ak4619->pdn_gpio) + msleep(10); + ak4619->regmap = devm_regmap_init_i2c(i2c, &ak4619_regmap_cfg); if (IS_ERR(ak4619->regmap)) { ret = PTR_ERR(ak4619->regmap); dev_err(dev, "regmap_init() failed: %d\n", ret); + if (ak4619->pdn_gpio) + gpiod_set_value_cansleep(ak4619->pdn_gpio, 1); return ret; } @@ -887,6 +917,8 @@ static int ak4619_i2c_probe(struct i2c_client *i2c) if (ret < 0) { dev_err(dev, "Failed to register ak4619 component: %d\n", ret); + if (ak4619->pdn_gpio) + gpiod_set_value_cansleep(ak4619->pdn_gpio, 1); return ret; } diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 08ec2035b270..a0de45660b00 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -493,12 +493,18 @@ static int ak4642_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 ak4642_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak4642_dai_ops = { .startup = ak4642_dai_startup, .shutdown = ak4642_dai_shutdown, .set_sysclk = ak4642_dai_set_sysclk, .set_fmt = ak4642_dai_set_fmt, .hw_params = ak4642_dai_hw_params, + .auto_selectable_formats = &ak4642_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak4642_dai = { @@ -533,8 +539,7 @@ static int ak4642_resume(struct snd_soc_component *component) struct regmap *regmap = dev_get_regmap(component->dev, NULL); regcache_cache_only(regmap, false); - regcache_sync(regmap); - return 0; + return regcache_sync(regmap); } static int ak4642_probe(struct snd_soc_component *component) { @@ -607,14 +612,13 @@ static struct clk *ak4642_of_parse_mcko(struct device *dev) struct device_node *np = dev->of_node; struct clk *clk; const char *clk_name = np->name; - const char *parent_clk_name = NULL; + const char *parent_clk_name; u32 rate; if (of_property_read_u32(np, "clock-frequency", &rate)) return NULL; - if (of_property_read_bool(np, "clocks")) - parent_clk_name = of_clk_get_parent_name(np, 0); + parent_clk_name = of_clk_get_parent_name(np, 0); of_property_read_string(np, "clock-output-names", &clk_name); diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c index aac54e9d54ee..1d804f03a21d 100644 --- a/sound/soc/codecs/ak4671.c +++ b/sound/soc/codecs/ak4671.c @@ -582,10 +582,17 @@ static int ak4671_set_bias_level(struct snd_soc_component *component, #define AK4671_FORMATS SNDRV_PCM_FMTBIT_S16_LE +static const u64 ak4671_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops ak4671_dai_ops = { .hw_params = ak4671_hw_params, .set_sysclk = ak4671_set_dai_sysclk, .set_fmt = ak4671_set_dai_fmt, + .auto_selectable_formats = &ak4671_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak4671_dai = { diff --git a/sound/soc/codecs/ak5386.c b/sound/soc/codecs/ak5386.c index 6525d50b7ab2..933ca90b7e28 100644 --- a/sound/soc/codecs/ak5386.c +++ b/sound/soc/codecs/ak5386.c @@ -128,10 +128,16 @@ static int ak5386_hw_free(struct snd_pcm_substream *substream, return 0; } +static const u64 ak5386_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ak5386_dai_ops = { .set_fmt = ak5386_set_dai_fmt, .hw_params = ak5386_hw_params, .hw_free = ak5386_hw_free, + .auto_selectable_formats = &ak5386_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak5386_dai = { diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c index 691db6565c20..0c9cdf9a53bb 100644 --- a/sound/soc/codecs/ak5558.c +++ b/sound/soc/codecs/ak5558.c @@ -285,12 +285,19 @@ static int ak5558_startup(struct snd_pcm_substream *substream, &ak5558_rate_constraints); } +static const u64 ak5558_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops ak5558_dai_ops = { .startup = ak5558_startup, .hw_params = ak5558_hw_params, .set_fmt = ak5558_set_dai_fmt, .set_tdm_slot = ak5558_set_tdm_slot, + .auto_selectable_formats = &ak5558_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ak5558_dai = { diff --git a/sound/soc/codecs/alc5623.c b/sound/soc/codecs/alc5623.c index f5a666c49361..96bb1d592772 100644 --- a/sound/soc/codecs/alc5623.c +++ b/sound/soc/codecs/alc5623.c @@ -822,12 +822,23 @@ static int alc5623_set_bias_level(struct snd_soc_component *component, | SNDRV_PCM_FMTBIT_S24_LE \ | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 alc5623_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops alc5623_dai_ops = { .hw_params = alc5623_pcm_hw_params, .mute_stream = alc5623_mute, .set_fmt = alc5623_set_dai_fmt, .set_sysclk = alc5623_set_dai_sysclk, .set_pll = alc5623_set_dai_pll, + .auto_selectable_formats = &alc5623_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/alc5632.c b/sound/soc/codecs/alc5632.c index 2fd3dd7ef8b3..a5e95f3fc247 100644 --- a/sound/soc/codecs/alc5632.c +++ b/sound/soc/codecs/alc5632.c @@ -1003,12 +1003,25 @@ static int alc5632_set_bias_level(struct snd_soc_component *component, | SNDRV_PCM_FMTBIT_S24_LE \ | SNDRV_PCM_FMTBIT_S32_LE) + +static const u64 alc5632_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops alc5632_dai_ops = { .hw_params = alc5632_pcm_hw_params, .mute_stream = alc5632_mute, .set_fmt = alc5632_set_dai_fmt, .set_sysclk = alc5632_set_dai_sysclk, .set_pll = alc5632_set_dai_pll, + .auto_selectable_formats = &alc5632_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/arizona-jack.c b/sound/soc/codecs/arizona-jack.c index 819d080b1188..6587d46a1867 100644 --- a/sound/soc/codecs/arizona-jack.c +++ b/sound/soc/codecs/arizona-jack.c @@ -699,6 +699,7 @@ static void arizona_start_hpdet_acc_id(struct arizona_priv *info) snd_soc_jack_report(info->jack, SND_JACK_HEADPHONE, SND_JACK_LINEOUT | SND_JACK_HEADPHONE); + pm_runtime_put_autosuspend(arizona->dev); info->hpdet_active = false; } diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 4411165c3734..7c296c5b24f7 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -2016,6 +2016,27 @@ static int arizona_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; } +static const u64 arizona_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* + * 2nd priority + * + * _DSP_B / _LEFT_J can't be clock consumer (= arizona_set_fmt()). + */ + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, +}; + const struct snd_soc_dai_ops arizona_dai_ops = { .startup = arizona_startup, .set_fmt = arizona_set_fmt, @@ -2023,6 +2044,8 @@ const struct snd_soc_dai_ops arizona_dai_ops = { .hw_params = arizona_hw_params, .set_sysclk = arizona_dai_set_sysclk, .set_tristate = arizona_set_tristate, + .auto_selectable_formats = arizona_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(arizona_selectable_formats), }; EXPORT_SYMBOL_GPL(arizona_dai_ops); diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index b8d06534bc8f..7303dc9eb4e0 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -853,13 +853,13 @@ static int aw88261_set_tdm_slot(struct snd_soc_dai *dai, return -EINVAL; aw88261->rxl_slotvld_mask = chan << AW88261_I2S_RXL_SLOTVLD_START_BIT; - } - if ((rx_mask & ~BIT(chan)) != 0) { - if ((chan = __ffs(rx_mask & ~BIT(chan))) > 16) - return -EINVAL; + if ((rx_mask & ~BIT(chan)) != 0) { + if ((chan = __ffs(rx_mask & ~BIT(chan))) > 16) + return -EINVAL; - aw88261->rxr_slotvld_mask = chan << AW88261_I2S_RXR_SLOTVLD_START_BIT; + aw88261->rxr_slotvld_mask = chan << AW88261_I2S_RXR_SLOTVLD_START_BIT; + } } return 0; @@ -1014,6 +1014,33 @@ static int aw88261_volume_set(struct snd_kcontrol *kcontrol, return 0; } +static int aw88261_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + + ucontrol->value.integer.value[0] = !aw88261->mute_st; + return 0; +} + +static int aw88261_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_kcontrol_chip(kcontrol); + struct aw88261 *aw88261 = snd_soc_component_get_drvdata(codec); + bool mute = !ucontrol->value.integer.value[0]; + + if (aw88261->mute_st == mute) + return 0; + + aw88261->mute_st = mute; + if (aw88261->aw_pa->status == AW88261_DEV_PW_ON) + aw88261_dev_mute(aw88261->aw_pa, mute); + + return 1; +} + /* * The field contains 4 bits in units of 6dB + 6 bits in units of 0.125dB * which is too precise for TLV (!) so we have to multiply the scale by 2. @@ -1026,6 +1053,8 @@ static const struct snd_kcontrol_new aw88261_controls[] = { SOC_SINGLE_EXT_TLV("PCM Playback Volume", AW88261_SYSCTRL2_REG, 6, AW88261_CTL_MAX_VOL, 1, aw88261_volume_get, aw88261_volume_set, volume_tlv), + SOC_SINGLE_EXT("Playback Switch", 0, 0, 1, 0, + aw88261_mute_get, aw88261_mute_put), AW88261_PROFILE_EXT("Profile Set", aw88261_profile_info, aw88261_profile_get, aw88261_profile_set), }; diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index 809c2faa0c0c..b4e8aae56805 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -167,9 +167,8 @@ int aw_dev_check_syspll(struct aw_device *aw_dev) } EXPORT_SYMBOL_GPL(aw_dev_check_syspll); -static int aw_dev_check_sysst(struct aw88399 *aw88399) +static int aw_dev_check_sysst(struct aw_device *aw_dev) { - struct aw_device *aw_dev = aw88399->aw_pa; unsigned int check_val; unsigned int reg_val; int ret, i; @@ -184,11 +183,16 @@ static int aw_dev_check_sysst(struct aw88399 *aw88399) check_val = AW88399_BIT_SYSST_SWS_CHECK; /* - * On some hardware the BSTS (boost-finished) status bit does not - * reliably assert even when audio output is working normally. - * Allow per-instance bypass when flagged by the side-codec driver. + * When an external power supply (EPS) is used to feed the amplifier + * (EN_HVBAT bit in BSTCTRL10 set), the boost converter is bypassed, + * and BSTS (boost finished) will never assert. Skip the BSTS check in + * this case, matching the vendor driver's behavior. */ - if (aw88399->bsts_unreliable) + ret = regmap_read(aw_dev->regmap, AW88399_BSTCTRL10_REG, ®_val); + if (ret) + return ret; + + if (reg_val & (~AW88399_EPS_EN_MASK)) check_val &= ~AW88399_BSTS_FINISHED_VALUE; for (i = 0; i < AW88399_DEV_SYSST_CHECK_MAX; i++) { @@ -719,7 +723,7 @@ static int aw88399_dev_start(struct aw88399 *aw88399) usleep_range(AW88399_1000_US, AW88399_1000_US + 50); /* check i2s status */ - ret = aw_dev_check_sysst(aw88399); + ret = aw_dev_check_sysst(aw_dev); if (ret) { dev_err(aw_dev->dev, "sysst check failed"); goto sysst_check_fail; @@ -1280,21 +1284,21 @@ static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg return 0; } -int aw88399_request_firmware_file(struct aw88399 *aw88399) +int aw88399_request_firmware_file(struct aw88399 *aw88399, const char *fw_name) { const struct firmware *cont __free(firmware) = NULL; int ret; aw88399->aw_pa->fw_status = AW88399_DEV_FW_FAILED; - ret = request_firmware(&cont, AW88399_ACF_FILE, aw88399->aw_pa->dev); + ret = request_firmware(&cont, fw_name, aw88399->aw_pa->dev); if (ret) { - dev_err(aw88399->aw_pa->dev, "request [%s] failed!", AW88399_ACF_FILE); + dev_err(aw88399->aw_pa->dev, "request [%s] failed!\n", fw_name); return ret; } dev_dbg(aw88399->aw_pa->dev, "loaded %s - size: %zu\n", - AW88399_ACF_FILE, cont ? cont->size : 0); + fw_name, cont ? cont->size : 0); aw88399->aw_cfg = devm_kzalloc(aw88399->aw_pa->dev, struct_size(aw88399->aw_cfg, data, cont->size), GFP_KERNEL); @@ -1306,7 +1310,7 @@ int aw88399_request_firmware_file(struct aw88399 *aw88399) ret = aw88395_dev_load_acf_check(aw88399->aw_pa, aw88399->aw_cfg); if (ret) { - dev_err(aw88399->aw_pa->dev, "load [%s] failed!", AW88399_ACF_FILE); + dev_err(aw88399->aw_pa->dev, "load [%s] failed!\n", fw_name); return ret; } diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c index e9dab5e7bae3..320dc1b87841 100644 --- a/sound/soc/codecs/aw88399.c +++ b/sound/soc/codecs/aw88399.c @@ -727,7 +727,7 @@ static int aw88399_codec_probe(struct snd_soc_component *component) INIT_DELAYED_WORK(&aw88399->start_work, aw88399_startup_work); - ret = aw88399_request_firmware_file(aw88399); + ret = aw88399_request_firmware_file(aw88399, AW88399_ACF_FILE); if (ret) dev_err(aw88399->aw_pa->dev, "%s failed\n", __func__); diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index fc41e1547bda..eaaf141dea57 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1244,11 +1244,24 @@ static int cpcap_hifi_set_mute(struct snd_soc_dai *dai, int mute, int direction) return regmap_update_bits(cpcap->regmap, reg, mask, val); } +static const u64 cpcap_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cpcap_dai_hifi_ops = { .hw_params = cpcap_hifi_hw_params, .set_sysclk = cpcap_hifi_set_dai_sysclk, .set_fmt = cpcap_hifi_set_dai_fmt, .mute_stream = cpcap_hifi_set_mute, + .auto_selectable_formats = &cpcap_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1406,6 +1419,8 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { .set_sysclk = cpcap_voice_set_dai_sysclk, .set_fmt = cpcap_voice_set_dai_fmt, .mute_stream = cpcap_voice_set_mute, + .auto_selectable_formats = &cpcap_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1607,21 +1622,11 @@ static int cpcap_soc_probe(struct snd_soc_component *component) { struct platform_device *pdev = to_platform_device(component->dev); struct snd_soc_card *card = component->card; - struct cpcap_audio *cpcap; + struct cpcap_audio *cpcap = dev_get_drvdata(component->dev); int err; - cpcap = devm_kzalloc(component->dev, sizeof(*cpcap), GFP_KERNEL); - if (!cpcap) - return -ENOMEM; - - snd_soc_component_set_drvdata(component, cpcap); cpcap->component = component; - cpcap->vaudio = devm_regulator_get(component->dev, "VAUDIO"); - if (IS_ERR(cpcap->vaudio)) - return dev_err_probe(component->dev, PTR_ERR(cpcap->vaudio), - "Cannot get VAUDIO regulator\n"); - err = snd_soc_card_jack_new(card, "Headphones", SND_JACK_HEADSET | SND_JACK_BTN_0, &cpcap->jack); @@ -1645,13 +1650,13 @@ static int cpcap_soc_probe(struct snd_soc_component *component) if (cpcap->hsirq < 0) return cpcap->hsirq; - err = devm_request_threaded_irq(component->dev, cpcap->hsirq, NULL, - cpcap_hs_irq_thread, - IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - "cpcap-codec-hs", - component); + err = request_threaded_irq(cpcap->hsirq, NULL, + cpcap_hs_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "cpcap-codec-hs", + component); if (err) { dev_warn(component->dev, "no HS irq%i: %i\n", cpcap->hsirq, err); @@ -1659,25 +1664,27 @@ static int cpcap_soc_probe(struct snd_soc_component *component) } cpcap->mb2irq = platform_get_irq_byname(pdev, "mb2"); - if (cpcap->mb2irq < 0) - return cpcap->mb2irq; + if (cpcap->mb2irq < 0) { + err = cpcap->mb2irq; + goto err_free_hsirq; + } - err = devm_request_threaded_irq(component->dev, cpcap->mb2irq, NULL, - cpcap_mb2_irq_thread, - IRQF_TRIGGER_RISING | - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - "cpcap-codec-mb2", - component); + err = request_threaded_irq(cpcap->mb2irq, NULL, + cpcap_mb2_irq_thread, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "cpcap-codec-mb2", + component); if (err) { dev_warn(component->dev, "no MB2 irq%i: %i\n", cpcap->mb2irq, err); - return err; + goto err_free_hsirq; } err = cpcap_audio_reset(component, false); if (err) - return err; + goto err_free_mb2irq; cpcap_hs_irq_thread(cpcap->hsirq, component); @@ -1685,6 +1692,13 @@ static int cpcap_soc_probe(struct snd_soc_component *component) enable_irq_wake(cpcap->mb2irq); return 0; + +err_free_mb2irq: + free_irq(cpcap->mb2irq, component); +err_free_hsirq: + free_irq(cpcap->hsirq, component); + + return err; } static void cpcap_soc_remove(struct snd_soc_component *component) @@ -1693,6 +1707,9 @@ static void cpcap_soc_remove(struct snd_soc_component *component) disable_irq_wake(cpcap->hsirq); disable_irq_wake(cpcap->mb2irq); + + free_irq(cpcap->mb2irq, component); + free_irq(cpcap->hsirq, component); } static int cpcap_set_bias_level(struct snd_soc_component *component, @@ -1739,11 +1756,24 @@ static int cpcap_codec_probe(struct platform_device *pdev) { struct device_node *codec_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; + cpcap = devm_kzalloc(&pdev->dev, sizeof(*cpcap), GFP_KERNEL); + if (!cpcap) + return -ENOMEM; + + cpcap->vaudio = devm_regulator_get(&pdev->dev, "VAUDIO"); + if (IS_ERR(cpcap->vaudio)) + return dev_err_probe(&pdev->dev, PTR_ERR(cpcap->vaudio), + "Cannot get VAUDIO regulator\n"); + + platform_set_drvdata(pdev, cpcap); + return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cpcap, cpcap_dai, ARRAY_SIZE(cpcap_dai)); } diff --git a/sound/soc/codecs/cs35l33.c b/sound/soc/codecs/cs35l33.c index e217da124549..b8c19f175701 100644 --- a/sound/soc/codecs/cs35l33.c +++ b/sound/soc/codecs/cs35l33.c @@ -578,7 +578,7 @@ static int cs35l33_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, | CS35L33_X_LOC); } - /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + /* disconnect {vp,vbst}_mon routes: enable later if set in tx_mask*/ snd_soc_dapm_del_routes(dapm, cs35l33_vp_vbst_mon_route, ARRAY_SIZE(cs35l33_vp_vbst_mon_route)); @@ -663,12 +663,18 @@ static int cs35l33_component_set_sysclk(struct snd_soc_component *component, return 0; } +static const u64 cs35l33_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops cs35l33_ops = { .startup = cs35l33_pcm_startup, .set_tristate = cs35l33_set_tristate, .set_fmt = cs35l33_set_dai_fmt, .hw_params = cs35l33_pcm_hw_params, .set_tdm_slot = cs35l33_set_tdm_slot, + .auto_selectable_formats = &cs35l33_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs35l33_dai = { diff --git a/sound/soc/codecs/cs35l34.c b/sound/soc/codecs/cs35l34.c index 3f265bd760f7..a72be629a31e 100644 --- a/sound/soc/codecs/cs35l34.c +++ b/sound/soc/codecs/cs35l34.c @@ -297,7 +297,7 @@ static int cs35l34_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, CS35L34_X_STATE | CS35L34_X_LOC, CS35L34_X_STATE | CS35L34_X_LOC); - /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + /* disconnect {vp,vbst}_mon routes: enable later if set in tx_mask*/ while (slot >= 0) { /* configure VMON_TX_LOC */ if (slot_num == 0) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index b2439ec3c19f..302d838f4c44 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -657,17 +657,24 @@ static int cs35l35_dai_set_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 cs35l35_selectable_formats_i2s = SND_SOC_POSSIBLE_DAIFMT_I2S; +static const u64 cs35l35_selectable_formats_pdm = SND_SOC_POSSIBLE_DAIFMT_PDM; + static const struct snd_soc_dai_ops cs35l35_ops = { .startup = cs35l35_pcm_startup, .set_fmt = cs35l35_set_dai_fmt, .hw_params = cs35l35_hw_params, .set_sysclk = cs35l35_dai_set_sysclk, + .auto_selectable_formats = &cs35l35_selectable_formats_i2s, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops cs35l35_pdm_ops = { .startup = cs35l35_pdm_startup, .set_fmt = cs35l35_set_dai_fmt, .hw_params = cs35l35_hw_params, + .auto_selectable_formats = &cs35l35_selectable_formats_pdm, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs35l35_dai[] = { diff --git a/sound/soc/codecs/cs35l36.c b/sound/soc/codecs/cs35l36.c index 89645327945f..5fc8ec292e3c 100644 --- a/sound/soc/codecs/cs35l36.c +++ b/sound/soc/codecs/cs35l36.c @@ -947,10 +947,66 @@ static const struct cs35l36_pll_config *cs35l36_get_clk_config( return NULL; } +static void cs35l36_mask_to_slots(struct cs35l36_private *cs35l36, + unsigned long mask, unsigned int base_reg, + unsigned int nchan) +{ + unsigned int chan = 0, shift; + int slot; + + /* Two 6-bit slot fields per register, at bits 0 and 16 */ + for_each_set_bit(slot, &mask, BITS_PER_TYPE(mask)) { + if (chan == nchan) { + dev_warn(cs35l36->dev, + "Too many slots in TDM mask: %lx\n", mask); + return; + } + + shift = (chan % 2) * CS35L36_ASP_TX2_SLOT_SHIFT; + regmap_update_bits(cs35l36->regmap, base_reg + (chan / 2) * 4, + CS35L36_ASP_RX1_SLOT_MASK << shift, + slot << shift); + chan++; + } +} + +static int cs35l36_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct cs35l36_private *cs35l36 = + snd_soc_component_get_drvdata(dai->component); + + /* Note: rx/tx is from point of view of the CPU end */ + if (!slots || !rx_mask) + rx_mask = BIT(0); /* ASPRX1 in slot 0 */ + + if (!slots || !tx_mask) + tx_mask = GENMASK(7, 0); /* ASPTX1..8 in slots 0..7 */ + + cs35l36_mask_to_slots(cs35l36, rx_mask, CS35L36_ASP_RX1_SLOT, 1); + cs35l36_mask_to_slots(cs35l36, tx_mask, CS35L36_ASP_TX1_TX2_SLOT, 8); + + return 0; +} + +static const u64 cs35l36_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_GATED | + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs35l36_ops = { .set_fmt = cs35l36_set_dai_fmt, .hw_params = cs35l36_pcm_hw_params, .set_sysclk = cs35l36_dai_set_sysclk, + .auto_selectable_formats = &cs35l36_selectable_formats, + .num_auto_selectable_formats = 1, + .set_tdm_slot = cs35l36_set_tdm_slot, }; #define CS35L36_RATES ( \ diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index b2a076706c79..d286025183f4 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -953,11 +953,21 @@ static void cs35l41_component_remove(struct snd_soc_component *component) wm_adsp2_component_remove(&cs35l41->dsp, component); } +static const u64 cs35l41_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs35l41_ops = { .set_fmt = cs35l41_set_dai_fmt, .hw_params = cs35l41_pcm_hw_params, .set_sysclk = cs35l41_dai_set_sysclk, .set_channel_map = cs35l41_set_channel_map, + .auto_selectable_formats = &cs35l41_selectable_formats, + .num_auto_selectable_formats = 1, }; #define CS35L41_RATES ( \ diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c index 764dbaa35042..554c8121a74f 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -51,6 +51,9 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_WKI2C_CTL, 0x00000030 }, { CS35L45_REFCLK_INPUT, 0x00000510 }, { CS35L45_GLOBAL_SAMPLE_RATE, 0x00000003 }, + { CS35L45_SYNC_TX_RX_ENABLES, 0x00000200 }, + { CS35L45_SYNC_SW_TX_ID, 0x00000000 }, + { CS35L45_BOOST_LPMODE_CFG, 0x00000002 }, { CS35L45_ASP_ENABLES1, 0x00000000 }, { CS35L45_ASP_CONTROL1, 0x00000028 }, { CS35L45_ASP_CONTROL2, 0x18180200 }, @@ -74,6 +77,7 @@ static const struct reg_default cs35l45_defaults[] = { { CS35L45_DSP1RX6_INPUT, 0x00000028 }, { CS35L45_DSP1RX7_INPUT, 0x0000003A }, { CS35L45_DSP1RX8_INPUT, 0x00000028 }, + { CS35L45_LDPM_CONFIG, 0x00003636 }, { CS35L45_AMP_PCM_CONTROL, 0x00100000 }, { CS35L45_AMP_GAIN, 0x00002300 }, { CS35L45_IRQ1_CFG, 0x00000000 }, @@ -134,6 +138,9 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_PWRMGT_STS: case CS35L45_REFCLK_INPUT: case CS35L45_GLOBAL_SAMPLE_RATE: + case CS35L45_SYNC_TX_RX_ENABLES: + case CS35L45_SYNC_SW_TX_ID: + case CS35L45_BOOST_LPMODE_CFG: case CS35L45_ASP_ENABLES1: case CS35L45_ASP_CONTROL1: case CS35L45_ASP_CONTROL2: @@ -158,6 +165,7 @@ static bool cs35l45_readable_reg(struct device *dev, unsigned int reg) case CS35L45_DSP1RX7_INPUT: case CS35L45_DSP1RX8_INPUT: case CS35L45_HVLV_CONFIG: + case CS35L45_LDPM_CONFIG: case CS35L45_AMP_PCM_CONTROL: case CS35L45_AMP_GAIN: case CS35L45_AMP_PCM_HPF_TST: @@ -330,3 +338,57 @@ int cs35l45_get_clk_freq_id(unsigned int freq) return -EINVAL; } EXPORT_SYMBOL_NS_GPL(cs35l45_get_clk_freq_id, "SND_SOC_CS35L45"); + +static const struct { + u8 cfg_id; + s32 pcm_thld_db; +} cs35l45_ldpm_pcm_thld[] = { + { 0x0, -66 }, + { 0x1, -72 }, + { 0x2, -78 }, + { 0x3, -84 }, + { 0x4, -90 }, + { 0x5, -96 }, + { 0x6, -102 }, + { 0x7, -2147483648 }, +}; + +int cs35l45_get_ldpm_pcm_thld(s32 pcm_thld_db) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l45_ldpm_pcm_thld); i++) { + if (cs35l45_ldpm_pcm_thld[i].pcm_thld_db == pcm_thld_db) + return cs35l45_ldpm_pcm_thld[i].cfg_id; + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(cs35l45_get_ldpm_pcm_thld, "SND_SOC_CS35L45"); + +static const struct { + u8 cfg_id; + u32 delay_ms; +} cs35l45_ldpm_delay[] = { + { 0x0, 5 }, + { 0x1, 10 }, + { 0x2, 25 }, + { 0x3, 50 }, + { 0x4, 100 }, + { 0x5, 250 }, + { 0x6, 500 }, + { 0x7, 1000 }, +}; + +int cs35l45_get_ldpm_delay(u32 delay_ms) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l45_ldpm_delay); i++) { + if (cs35l45_ldpm_delay[i].delay_ms == delay_ms) + return cs35l45_ldpm_delay[i].cfg_id; + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(cs35l45_get_ldpm_delay, "SND_SOC_CS35L45"); diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index a032bb23b4ac..064ee612f05e 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -6,6 +6,7 @@ // // Author: James Schulman +#include #include #include #include @@ -195,6 +196,44 @@ static int cs35l45_activate_ctl(struct snd_soc_component *component, return 0; } +static int cs35l45_sync_en_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs35l45_private *cs35l45 = + snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = cs35l45->sync_en; + + return 0; +} + +static int cs35l45_sync_en_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct cs35l45_private *cs35l45 = + snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = + snd_soc_component_to_dapm(component); + + snd_soc_dapm_mutex_lock(dapm); + + if ((bool)ucontrol->value.integer.value[0] == cs35l45->sync_en) { + snd_soc_dapm_mutex_unlock(dapm); + return 0; + } + + regmap_assign_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES2, CS35L45_SYNC_EN_MASK, + (bool)ucontrol->value.integer.value[0]); + + cs35l45->sync_en = (bool)ucontrol->value.integer.value[0]; + + snd_soc_dapm_mutex_unlock(dapm); + + return 1; +} + static int cs35l45_amplifier_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -591,6 +630,18 @@ static SOC_ENUM_SINGLE_DECL(amplifier_mode_enum, SND_SOC_NOPM, 0, static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 1000, 300, 0); static const DECLARE_TLV_DB_SCALE(cs35l45_dig_pcm_vol_tlv, -10225, 25, true); +static const struct snd_kcontrol_new cs35l45_sync_controls[] = { + SOC_SINGLE_BOOL_EXT("SYNC Switch", 0, cs35l45_sync_en_get, cs35l45_sync_en_put), + SOC_SINGLE("SYNC LSW RX Switch", CS35L45_SYNC_TX_RX_ENABLES, + CS35L45_SYNC_LSW_RX_EN_SHIFT, 1, 0), + SOC_SINGLE("SYNC LSW TX Switch", CS35L45_SYNC_TX_RX_ENABLES, + CS35L45_SYNC_LSW_TX_EN_SHIFT, 1, 0), + SOC_SINGLE("SYNC SW RX Switch", CS35L45_SYNC_TX_RX_ENABLES, + CS35L45_SYNC_SW_RX_EN_SHIFT, 1, 0), + SOC_SINGLE("SYNC SW TX Switch", CS35L45_SYNC_TX_RX_ENABLES, + CS35L45_SYNC_SW_TX_EN_SHIFT, 1, 0), +}; + static const struct snd_kcontrol_new cs35l45_controls[] = { SOC_ENUM_EXT("Amplifier Mode", amplifier_mode_enum, cs35l45_amplifier_mode_get, cs35l45_amplifier_mode_put), @@ -846,12 +897,22 @@ static int cs35l45_mute_stream(struct snd_soc_dai *dai, int mute, int stream) return 0; } +static const u64 cs35l45_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs35l45_asp_dai_ops = { .set_fmt = cs35l45_asp_set_fmt, .hw_params = cs35l45_asp_hw_params, .set_tdm_slot = cs35l45_asp_set_tdm_slot, .set_sysclk = cs35l45_asp_set_sysclk, .mute_stream = cs35l45_mute_stream, + .auto_selectable_formats = &cs35l45_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs35l45_dai[] = { @@ -880,6 +941,19 @@ static struct snd_soc_dai_driver cs35l45_dai[] = { static int cs35l45_component_probe(struct snd_soc_component *component) { struct cs35l45_private *cs35l45 = snd_soc_component_get_drvdata(component); + int ret; + + ret = snd_soc_add_component_controls(component, cs35l45_controls, + ARRAY_SIZE(cs35l45_controls)); + if (ret < 0) + return ret; + + if (cs35l45->sync_pin_set) { + ret = snd_soc_add_component_controls(component, cs35l45_sync_controls, + ARRAY_SIZE(cs35l45_sync_controls)); + if (ret < 0) + return ret; + } return wm_adsp2_component_probe(&cs35l45->dsp, component); } @@ -901,9 +975,6 @@ static const struct snd_soc_component_driver cs35l45_component = { .dapm_routes = cs35l45_dapm_routes, .num_dapm_routes = ARRAY_SIZE(cs35l45_dapm_routes), - .controls = cs35l45_controls, - .num_controls = ARRAY_SIZE(cs35l45_controls), - .name = "cs35l45", .endianness = 1, @@ -1064,6 +1135,95 @@ static int cs35l45_sys_resume(struct device *dev) return 0; } +static void cs35l45_apply_sync_property_config(struct cs35l45_private *cs35l45) +{ + unsigned int val; + + if (device_property_read_u32(cs35l45->dev, "cirrus,sync-lsw-txid", &val) == 0) + regmap_update_bits(cs35l45->regmap, CS35L45_SYNC_SW_TX_ID, + CS35L45_SYNC_LSW_TXID_MASK, val << CS35L45_SYNC_LSW_TXID_SHIFT); + + if (device_property_read_u32(cs35l45->dev, "cirrus,sync-sw-txid", &val) == 0) + regmap_update_bits(cs35l45->regmap, CS35L45_SYNC_SW_TX_ID, + CS35L45_SYNC_SW_TXID_MASK, val); +} + +static int cs35l45_apply_ldpm_config(struct cs35l45_private *cs35l45) +{ + char prop_name[CS35L45_LDPM_PROP_NAME_MAX]; + int i, ret; + u32 val; + + for (i = LDPM_GROUP1; i < NUM_LDPM_GROUP; i++) { + sprintf(prop_name, "cirrus,ldpm-group%d-pcm-threshold-db", i); + if (device_property_read_u32(cs35l45->dev, prop_name, &val) == 0) { + ret = cs35l45_get_ldpm_pcm_thld((s32)val); + if (ret == -EINVAL) + return -EINVAL; + + switch (i) { + case LDPM_GROUP1: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP1_PCM_THLD_MASK, + FIELD_PREP(CS35L45_LDPM_GP1_PCM_THLD_MASK, + ret)); + break; + case LDPM_GROUP2: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP2_PCM_THLD_MASK, + FIELD_PREP(CS35L45_LDPM_GP2_PCM_THLD_MASK, + ret)); + break; + default: + return -EINVAL; + } + } + + sprintf(prop_name, "cirrus,ldpm-group%d-delay-ms", i); + if (device_property_read_u32(cs35l45->dev, prop_name, &val) == 0) { + ret = cs35l45_get_ldpm_delay(val); + if (ret == -EINVAL) + return -EINVAL; + + switch (i) { + case LDPM_GROUP1: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP1_DELAY_MASK, + FIELD_PREP(CS35L45_LDPM_GP1_DELAY_MASK, ret)); + break; + case LDPM_GROUP2: + regmap_update_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, + CS35L45_LDPM_GP2_DELAY_MASK, + FIELD_PREP(CS35L45_LDPM_GP2_DELAY_MASK, ret)); + break; + default: + return -EINVAL; + } + } + } + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group1-boost-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP1_BOOST_SEL); + + if (device_property_read_u32(cs35l45->dev, "cirrus,boost-low-power-mode", &val) == 0) + regmap_write(cs35l45->regmap, CS35L45_BOOST_LPMODE_CFG, val); + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group1-amp-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP1_AMP_SEL); + + if ((device_property_read_u32(cs35l45->dev, "cirrus,amplifier-low-power-mode", &val) == 0) + && (val == 0)) + regmap_clear_bits(cs35l45->regmap, CS35L45_BLOCK_ENABLES, CS35L45_NFR_EN_MASK); + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group2-vmon-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP2_VMON_SEL); + + if (device_property_read_bool(cs35l45->dev, "cirrus,ldpm-group2-imon-select")) + regmap_set_bits(cs35l45->regmap, CS35L45_LDPM_CONFIG, CS35L45_LDPM_GP2_IMON_SEL); + + return 0; +} + static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) { struct device_node *node = cs35l45->dev->of_node; @@ -1110,10 +1270,15 @@ static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) val << CS35L45_GPIO_POL_SHIFT); ret = of_property_read_u32(child, "gpio-ctrl", &val); - if (!ret) + if (!ret) { + if ((i == 0) && (val == CS35L45_GP1_CTRL_MDSYNC)) { + cs35l45->sync_pin_set = true; + cs35l45_apply_sync_property_config(cs35l45); + } regmap_update_bits(cs35l45->regmap, pad_regs[i], CS35L45_GPIO_CTRL_MASK, val << CS35L45_GPIO_CTRL_SHIFT); + } ret = of_property_read_u32(child, "gpio-invert", &val); if (!ret) { @@ -1134,6 +1299,10 @@ static int cs35l45_apply_property_config(struct cs35l45_private *cs35l45) val << CS35L45_ASP_DOUT_HIZ_CTRL_SHIFT); } + ret = cs35l45_apply_ldpm_config(cs35l45); + if (ret < 0) + return ret; + return 0; } diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index 7a790d2acac7..5014c765adcb 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -35,7 +35,10 @@ #define CS35L45_PWRMGT_STS 0x0000290C #define CS35L45_REFCLK_INPUT 0x00002C04 #define CS35L45_GLOBAL_SAMPLE_RATE 0x00002C0C +#define CS35L45_SYNC_TX_RX_ENABLES 0x00003400 +#define CS35L45_SYNC_SW_TX_ID 0x00003408 #define CS35L45_BOOST_CCM_CFG 0x00003808 +#define CS35L45_BOOST_LPMODE_CFG 0x00003810 #define CS35L45_BOOST_DCM_CFG 0x0000380C #define CS35L45_BOOST_OV_CFG 0x0000382C #define CS35L45_ASP_ENABLES1 0x00004800 @@ -172,6 +175,8 @@ #define CS35L45_BST_EN_MASK GENMASK(5, 4) #define CS35L45_RCV_EN_SHIFT 2 #define CS35L45_RCV_EN_MASK BIT(2) +#define CS35L45_NFR_EN_SHIFT 1 +#define CS35L45_NFR_EN_MASK BIT(1) #define CS35L45_AMP_EN_SHIFT 0 #define CS35L45_AMP_EN_MASK BIT(0) @@ -183,6 +188,8 @@ #define CS35L45_ASP_EN_SHIFT 27 #define CS35L45_AMP_DRE_EN_SHIFT 20 #define CS35L45_AMP_DRE_EN_MASK BIT(20) +#define CS35L45_SYNC_EN_SHIFT 8 +#define CS35L45_SYNC_EN_MASK BIT(8) #define CS35L45_MEM_RDY_SHIFT 1 #define CS35L45_MEM_RDY_MASK BIT(1) @@ -220,6 +227,25 @@ #define CS35L45_44P100_KHZ 0x0B #define CS35L45_88P200_KHZ 0x0C +/* SYNC_TX_RX_ENABLES */ +#define CS35L45_SYNC_LSW_RX_EN_SHIFT 19 +#define CS35L45_SYNC_LSW_RX_EN_MASK BIT(19) +#define CS35L45_SYNC_LSW_TX_EN_SHIFT 18 +#define CS35L45_SYNC_LSW_TX_EN_MASK BIT(18) +#define CS35L45_SYNC_SW_RX_EN_SHIFT 17 +#define CS35L45_SYNC_SW_RX_EN_MASK BIT(17) +#define CS35L45_SYNC_SW_TX_EN_SHIFT 16 +#define CS35L45_SYNC_SW_TX_EN_MASK BIT(16) + +/* SYNC_SW_TX_ID */ +#define CS35L45_SYNC_LSW_TXID_SHIFT 8 +#define CS35L45_SYNC_LSW_TXID_MASK GENMASK(10, 8) +#define CS35L45_SYNC_SW_TXID_SHIFT 0 +#define CS35L45_SYNC_SW_TXID_MASK GENMASK(2, 0) + +/* BOOST_LPMODE_CFG */ +#define CS35L45_BST_LPMODE_SEL GENMASK(1, 0) + /* ASP_ENABLES_1 */ #define CS35L45_ASP_RX2_EN_SHIFT 17 #define CS35L45_ASP_RX1_EN_SHIFT 16 @@ -283,6 +309,20 @@ #define CS35L45_HVLV_MODE_SHIFT 0 #define CS35L45_HVLV_MODE_MASK GENMASK(1, 0) +/* LDPM_CONFIG */ +#define CS35L45_LDPM_GP1_BOOST_SEL BIT(15) +#define CS35L45_LDPM_GP1_AMP_SEL BIT(14) +#define CS35L45_LDPM_GP1_DELAY_SHIFT 11 +#define CS35L45_LDPM_GP1_DELAY_MASK GENMASK(13, 11) +#define CS35L45_LDPM_GP1_PCM_THLD_SHIFT 8 +#define CS35L45_LDPM_GP1_PCM_THLD_MASK GENMASK(10, 8) +#define CS35L45_LDPM_GP2_IMON_SEL BIT(7) +#define CS35L45_LDPM_GP2_VMON_SEL BIT(6) +#define CS35L45_LDPM_GP2_DELAY_SHIFT 3 +#define CS35L45_LDPM_GP2_DELAY_MASK GENMASK(5, 3) +#define CS35L45_LDPM_GP2_PCM_THLD_SHIFT 0 +#define CS35L45_LDPM_GP2_PCM_THLD_MASK GENMASK(2, 0) + /* AMP_PCM_CONTROL */ #define CS35L45_AMP_VOL_PCM_SHIFT 0 #define CS35L45_AMP_VOL_PCM_WIDTH 11 @@ -320,6 +360,7 @@ #define CS35L45_GPIO_CTRL_MASK GENMASK(22, 20) #define CS35L45_GPIO_INVERT_SHIFT 19 #define CS35L45_GPIO_INVERT_MASK BIT(19) +#define CS35L45_GP1_CTRL_MDSYNC 0x2 /* CS35L45_IRQ1_EINT_1 */ #define CS35L45_BST_UVP_ERR_SHIFT 7 @@ -427,6 +468,12 @@ enum amp_mode { AMP_MODE_RCV = 1, }; +enum ldpm_groups { + LDPM_GROUP1 = 1, + LDPM_GROUP2 = 2, + NUM_LDPM_GROUP, +}; + #define CS35L45_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_3LE| \ SNDRV_PCM_FMTBIT_S24_LE) @@ -436,6 +483,8 @@ enum amp_mode { SNDRV_PCM_RATE_88200 | \ SNDRV_PCM_RATE_96000) +#define CS35L45_LDPM_PROP_NAME_MAX 37 + /* * IRQs */ @@ -493,6 +542,8 @@ struct cs35l45_private { struct regulator *vdd_a; bool initialized; bool sysclk_set; + bool sync_en; + bool sync_pin_set; u8 slot_width; u8 slot_count; int amplifier_mode; @@ -508,6 +559,8 @@ extern const struct regmap_config cs35l45_i2c_regmap; extern const struct regmap_config cs35l45_spi_regmap; int cs35l45_apply_patch(struct cs35l45_private *cs35l45); int cs35l45_get_clk_freq_id(unsigned int freq); +int cs35l45_get_ldpm_pcm_thld(int pcm_thld_db); +int cs35l45_get_ldpm_delay(u32 delay_ms); int cs35l45_probe(struct cs35l45_private *cs35l45); void cs35l45_remove(struct cs35l45_private *cs35l45); diff --git a/sound/soc/codecs/cs35l56-shared-test.c b/sound/soc/codecs/cs35l56-shared-test.c index 4f52c8a192e5..34bb3d44b51f 100644 --- a/sound/soc/codecs/cs35l56-shared-test.c +++ b/sound/soc/codecs/cs35l56-shared-test.c @@ -29,6 +29,7 @@ struct cs35l56_shared_test_priv { struct faux_device *gpio_dev; struct cs35l56_shared_test_mock_gpio *gpio_priv; struct regmap *registers; + const struct regmap_config *regmap_config; unsigned int reg_offset; struct cs35l56_base *cs35l56_base; u8 applied_pad_pull_state[CS35L56_MAX_GPIO]; @@ -614,6 +615,55 @@ static void cs35l56_shared_test_get_speaker_id_from_host_gpio(struct kunit *test KUNIT_EXPECT_EQ(test, cs35l56_get_speaker_id(priv->cs35l56_base), param->spkid); } +static void cs35l56_shared_test_sorted_regmap_defaults(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + const struct reg_default *defaults; + int i; + + KUNIT_ASSERT_NOT_NULL(test, priv->regmap_config); + defaults = priv->regmap_config->reg_defaults; + KUNIT_ASSERT_NOT_NULL(test, defaults); + KUNIT_ASSERT_NE(test, priv->regmap_config->num_reg_defaults, 0); + + for (i = 1; i < priv->regmap_config->num_reg_defaults; i++) + KUNIT_EXPECT_LT(test, defaults[i - 1].reg, defaults[i].reg); +} + +static void cs35l56_shared_test_regmap_defaults_not_volatile(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + const struct regmap_config *config = priv->regmap_config; + int i; + + KUNIT_ASSERT_NOT_NULL(test, config); + KUNIT_ASSERT_NOT_NULL(test, config->volatile_reg); + + for (i = 0; i < config->num_reg_defaults; i++) { + KUNIT_EXPECT_FALSE(test, + config->volatile_reg(cs35l56_base->dev, + config->reg_defaults[i].reg)); + } +} + +static void cs35l56_shared_test_regmap_defaults_readable(struct kunit *test) +{ + struct cs35l56_shared_test_priv *priv = test->priv; + struct cs35l56_base *cs35l56_base = priv->cs35l56_base; + const struct regmap_config *config = priv->regmap_config; + int i; + + KUNIT_ASSERT_NOT_NULL(test, config); + KUNIT_ASSERT_NOT_NULL(test, config->readable_reg); + + for (i = 0; i < config->num_reg_defaults; i++) { + KUNIT_EXPECT_TRUE(test, + config->readable_reg(cs35l56_base->dev, + config->reg_defaults[i].reg)); + } +} + static int cs35l56_shared_test_case_regmap_init(struct kunit *test, const struct regmap_config *regmap_config) { @@ -645,6 +695,8 @@ static int cs35l56_shared_test_case_regmap_init(struct kunit *test, kunit_add_action_or_reset(test, regmap_exit_wrapper, cs35l56_base->regmap)); + priv->regmap_config = regmap_config; + return 0; } @@ -862,6 +914,10 @@ static struct kunit_case cs35l56_shared_test_cases[] = { cs35l56_shared_test_host_gpio_spkid_gen_params, { KUNIT_SPEED_SLOW }), + KUNIT_CASE(cs35l56_shared_test_sorted_regmap_defaults), + KUNIT_CASE(cs35l56_shared_test_regmap_defaults_not_volatile), + KUNIT_CASE(cs35l56_shared_test_regmap_defaults_readable), + { } }; diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 279db3f13f1e..dd264c310e1e 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -617,12 +617,22 @@ static int cs35l56_asp_dai_set_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 cs35l56_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs35l56_ops = { .probe = cs35l56_asp_dai_probe, .set_fmt = cs35l56_asp_dai_set_fmt, .set_tdm_slot = cs35l56_asp_dai_set_tdm_slot, .hw_params = cs35l56_asp_dai_hw_params, .set_sysclk = cs35l56_asp_dai_set_sysclk, + .auto_selectable_formats = &cs35l56_selectable_formats, + .num_auto_selectable_formats = 1, }; static void cs35l56_sdw_dai_shutdown(struct snd_pcm_substream *substream, diff --git a/sound/soc/codecs/cs40l50-codec.c b/sound/soc/codecs/cs40l50-codec.c index 676367163067..698aa59194f4 100644 --- a/sound/soc/codecs/cs40l50-codec.c +++ b/sound/soc/codecs/cs40l50-codec.c @@ -233,10 +233,19 @@ static int cs40l50_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int rati return 0; } +static const u64 cs40l50_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs40l50_dai_ops = { .set_fmt = cs40l50_set_dai_fmt, .set_bclk_ratio = cs40l50_set_dai_bclk_ratio, .hw_params = cs40l50_hw_params, + .auto_selectable_formats = &cs40l50_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs40l50_dai[] = { diff --git a/sound/soc/codecs/cs4234.c b/sound/soc/codecs/cs4234.c index a889fbd519a8..a6ead1ee3708 100644 --- a/sound/soc/codecs/cs4234.c +++ b/sound/soc/codecs/cs4234.c @@ -560,11 +560,20 @@ static int cs4234_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask return 0; } +static const u64 cs4234_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops cs4234_dai_ops = { .set_fmt = cs4234_dai_set_fmt, .hw_params = cs4234_dai_hw_params, .startup = cs4234_dai_startup, .set_tdm_slot = cs4234_dai_set_tdm_slot, + .auto_selectable_formats = &cs4234_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs4234_dai[] = { diff --git a/sound/soc/codecs/cs4265.c b/sound/soc/codecs/cs4265.c index 0b7d6826f4e9..97e32e6ccb84 100644 --- a/sound/soc/codecs/cs4265.c +++ b/sound/soc/codecs/cs4265.c @@ -495,11 +495,23 @@ static int cs4265_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE | \ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) +/* + * NOTE + * + * auto_select doesn't include _RIGHT_J, because capture can't use it + * (= cs4265_pcm_hw_params()). It can be selected via Card directly if needed. + */ +static const u64 cs4265_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs4265_ops = { .hw_params = cs4265_pcm_hw_params, .mute_stream = cs4265_mute, .set_fmt = cs4265_set_fmt, .set_sysclk = cs4265_set_sysclk, + .auto_selectable_formats = &cs4265_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 47b2f903a32c..aa3bbb951945 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -456,11 +456,17 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = { snd_soc_get_volsw, cs4270_soc_put_mute), }; +static const u64 cs4270_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs4270_dai_ops = { .hw_params = cs4270_hw_params, .set_sysclk = cs4270_set_dai_sysclk, .set_fmt = cs4270_set_dai_fmt, .mute_stream = cs4270_dai_mute, + .auto_selectable_formats = &cs4270_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c index d8cdd37e9112..398d8c24a9ce 100644 --- a/sound/soc/codecs/cs4271.c +++ b/sound/soc/codecs/cs4271.c @@ -455,11 +455,17 @@ static const struct snd_kcontrol_new cs4271_snd_controls[] = { 7, 1, 1), }; +static const u64 cs4271_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs4271_dai_ops = { .hw_params = cs4271_hw_params, .set_sysclk = cs4271_set_dai_sysclk, .set_fmt = cs4271_set_dai_fmt, .mute_stream = cs4271_mute_stream, + .auto_selectable_formats = &cs4271_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs4271_dai = { diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index fadf68860601..18d6cb9bfa2c 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -890,6 +890,7 @@ static int cs42l42_dai_startup(struct snd_pcm_substream *substream, struct snd_s { struct snd_soc_component *component = dai->component; struct cs42l42_private *cs42l42 = snd_soc_component_get_drvdata(component); + int ret; /* * Sample rates < 44.1 kHz would produce an out-of-range SCLK with @@ -900,9 +901,18 @@ static int cs42l42_dai_startup(struct snd_pcm_substream *substream, struct snd_s return 0; /* Machine driver has not set a SCLK, limit bottom end to 44.1 kHz */ - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 44100, 96000); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 44100, 96000); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ + return 0; } static int cs42l42_pcm_hw_params(struct snd_pcm_substream *substream, @@ -1122,6 +1132,13 @@ EXPORT_SYMBOL_NS_GPL(cs42l42_mute_stream, "SND_SOC_CS42L42_CORE"); SNDRV_PCM_FMTBIT_S24_LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 cs42l42_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs42l42_ops = { .startup = cs42l42_dai_startup, .hw_params = cs42l42_pcm_hw_params, @@ -1129,6 +1146,8 @@ static const struct snd_soc_dai_ops cs42l42_ops = { .set_sysclk = cs42l42_set_sysclk, .set_bclk_ratio = cs42l42_set_bclk_ratio, .mute_stream = cs42l42_mute_stream, + .auto_selectable_formats = &cs42l42_selectable_formats, + .num_auto_selectable_formats = 1, }; struct snd_soc_dai_driver cs42l42_dai = { @@ -1673,7 +1692,7 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data) if (cs42l42->suspended || !cs42l42->init_done) return IRQ_NONE; - /* Read sticky registers to clear interurpt */ + /* Read sticky registers to clear interrupt */ for (i = 0; i < ARRAY_SIZE(stickies); i++) { regmap_read(cs42l42->regmap, irq_params_table[i].status_addr, &(stickies[i])); diff --git a/sound/soc/codecs/cs42l43.c b/sound/soc/codecs/cs42l43.c index 1b817e1a6ea5..b6238f89c789 100644 --- a/sound/soc/codecs/cs42l43.c +++ b/sound/soc/codecs/cs42l43.c @@ -671,6 +671,16 @@ static int cs42l43_dai_remove(struct snd_soc_dai *dai) return 0; } +static const u64 cs42l43_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs42l43_asp_ops = { .probe = cs42l43_dai_probe, .remove = cs42l43_dai_remove, @@ -678,6 +688,8 @@ static const struct snd_soc_dai_ops cs42l43_asp_ops = { .hw_params = cs42l43_asp_hw_params, .set_fmt = cs42l43_asp_set_fmt, .set_tdm_slot = cs42l43_asp_set_tdm_slot, + .auto_selectable_formats = &cs42l43_selectable_formats, + .num_auto_selectable_formats = 1, }; static int cs42l43_sdw_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c index 0519affe0ed8..4b964c713398 100644 --- a/sound/soc/codecs/cs42l51.c +++ b/sound/soc/codecs/cs42l51.c @@ -533,11 +533,18 @@ static int cs42l51_of_xlate_dai_id(struct snd_soc_component *component, return 0; } +static const u64 cs42l51_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs42l51_dai_ops = { .hw_params = cs42l51_hw_params, .set_sysclk = cs42l51_set_dai_sysclk, .set_fmt = cs42l51_set_dai_fmt, .mute_stream = cs42l51_dai_mute, + .auto_selectable_formats = &cs42l51_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs42l52.c b/sound/soc/codecs/cs42l52.c index c55ae3532682..1d3334886402 100644 --- a/sound/soc/codecs/cs42l52.c +++ b/sound/soc/codecs/cs42l52.c @@ -883,11 +883,24 @@ static int cs42l52_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE) +static const u64 cs42l52_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs42l52_ops = { .hw_params = cs42l52_pcm_hw_params, .mute_stream = cs42l52_mute, .set_fmt = cs42l52_set_fmt, .set_sysclk = cs42l52_set_sysclk, + .auto_selectable_formats = &cs42l52_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1000,7 +1013,7 @@ static void cs42l52_init_beep(struct snd_soc_component *component) struct cs42l52_private *cs42l52 = snd_soc_component_get_drvdata(component); int ret; - cs42l52->beep = devm_input_allocate_device(component->dev); + cs42l52->beep = input_allocate_device(); if (!cs42l52->beep) { dev_err(component->dev, "Failed to allocate beep device\n"); return; @@ -1021,8 +1034,10 @@ static void cs42l52_init_beep(struct snd_soc_component *component) ret = input_register_device(cs42l52->beep); if (ret != 0) { + input_free_device(cs42l52->beep); cs42l52->beep = NULL; dev_err(component->dev, "Failed to register beep device\n"); + return; } ret = device_create_file(component->dev, &dev_attr_beep); @@ -1038,7 +1053,10 @@ static void cs42l52_free_beep(struct snd_soc_component *component) device_remove_file(component->dev, &dev_attr_beep); cancel_work_sync(&cs42l52->beep_work); - cs42l52->beep = NULL; + if (cs42l52->beep) { + input_unregister_device(cs42l52->beep); + cs42l52->beep = NULL; + } snd_soc_component_update_bits(component, CS42L52_BEEP_TONE_CTL, CS42L52_BEEP_EN_MASK, 0); diff --git a/sound/soc/codecs/cs42l56.c b/sound/soc/codecs/cs42l56.c index f0ea5309a6d4..54d23857b0ad 100644 --- a/sound/soc/codecs/cs42l56.c +++ b/sound/soc/codecs/cs42l56.c @@ -960,11 +960,19 @@ static int cs42l56_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S32_LE) +static const u64 cs42l56_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops cs42l56_ops = { .hw_params = cs42l56_pcm_hw_params, .mute_stream = cs42l56_mute, .set_fmt = cs42l56_set_dai_fmt, .set_sysclk = cs42l56_set_sysclk, + .auto_selectable_formats = &cs42l56_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1077,7 +1085,7 @@ static void cs42l56_init_beep(struct snd_soc_component *component) struct cs42l56_private *cs42l56 = snd_soc_component_get_drvdata(component); int ret; - cs42l56->beep = devm_input_allocate_device(component->dev); + cs42l56->beep = input_allocate_device(); if (!cs42l56->beep) { dev_err(component->dev, "Failed to allocate beep device\n"); return; @@ -1098,8 +1106,10 @@ static void cs42l56_init_beep(struct snd_soc_component *component) ret = input_register_device(cs42l56->beep); if (ret != 0) { + input_free_device(cs42l56->beep); cs42l56->beep = NULL; dev_err(component->dev, "Failed to register beep device\n"); + return; } ret = device_create_file(component->dev, &dev_attr_beep); @@ -1115,7 +1125,10 @@ static void cs42l56_free_beep(struct snd_soc_component *component) device_remove_file(component->dev, &dev_attr_beep); cancel_work_sync(&cs42l56->beep_work); - cs42l56->beep = NULL; + if (cs42l56->beep) { + input_unregister_device(cs42l56->beep); + cs42l56->beep = NULL; + } snd_soc_component_update_bits(component, CS42L56_BEEP_TONE_CFG, CS42L56_BEEP_EN_MASK, 0); diff --git a/sound/soc/codecs/cs42l73.c b/sound/soc/codecs/cs42l73.c index 6ddc6549492a..72c4213ed74b 100644 --- a/sound/soc/codecs/cs42l73.c +++ b/sound/soc/codecs/cs42l73.c @@ -761,7 +761,7 @@ static const struct snd_soc_dapm_route cs42l73_audio_map[] = { {"ASPOUTL", "ASP-IP Volume", "ASPL Output Mixer"}, {"ASPOUTR", "ASP-IP Volume", "ASPR Output Mixer"}, - /* Auxillary Capture */ + /* Auxiliary Capture */ {"XSPL Output Mixer", NULL, "Input Left Capture"}, {"XSPR Output Mixer", NULL, "Input Right Capture"}, @@ -1126,7 +1126,7 @@ static int cs42l73_set_bias_level(struct snd_soc_component *component, mdelay(cs42l73->shutdwn_delay); cs42l73->shutdwn_delay = 0; } else { - mdelay(15); /* Min amount of time requred to power + mdelay(15); /* Min amount of time required to power * down. */ } @@ -1163,12 +1163,19 @@ static int cs42l73_pcm_startup(struct snd_pcm_substream *substream, #define CS42L73_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 cs42l73_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops cs42l73_ops = { .startup = cs42l73_pcm_startup, .hw_params = cs42l73_pcm_hw_params, .set_fmt = cs42l73_set_dai_fmt, .set_sysclk = cs42l73_set_sysclk, .set_tristate = cs42l73_set_tristate, + .auto_selectable_formats = &cs42l73_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs42l73_dai[] = { diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c index a545730b7ed9..77015590368b 100644 --- a/sound/soc/codecs/cs42l84.c +++ b/sound/soc/codecs/cs42l84.c @@ -662,11 +662,17 @@ static int cs42l84_mute_stream(struct snd_soc_dai *dai, int mute, int stream) return 0; } +static const u64 cs42l84_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs42l84_ops = { .hw_params = cs42l84_pcm_hw_params, .set_fmt = cs42l84_set_dai_fmt, .set_sysclk = cs42l84_set_sysclk, .mute_stream = cs42l84_mute_stream, + .auto_selectable_formats = &cs42l84_selectable_formats, + .num_auto_selectable_formats = 1, }; #define CS42L84_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c index 5b689549c74e..fb9f4aff6bd8 100644 --- a/sound/soc/codecs/cs42xx8.c +++ b/sound/soc/codecs/cs42xx8.c @@ -393,12 +393,20 @@ static int cs42xx8_mute(struct snd_soc_dai *dai, int mute, int direction) return 0; } +static const u64 cs42xx8_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops cs42xx8_dai_ops = { .set_fmt = cs42xx8_set_dai_fmt, .set_sysclk = cs42xx8_set_dai_sysclk, .hw_params = cs42xx8_hw_params, .hw_free = cs42xx8_hw_free, .mute_stream = cs42xx8_mute, + .auto_selectable_formats = &cs42xx8_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c index 48d88dad4ceb..7bbb2e83a1ff 100644 --- a/sound/soc/codecs/cs43130.c +++ b/sound/soc/codecs/cs43130.c @@ -1609,12 +1609,24 @@ static int cs43130_set_sysclk(struct snd_soc_dai *codec_dai, return 0; } +static const u64 cs43130_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs43130_pcm_ops = { .startup = cs43130_pcm_startup, .hw_params = cs43130_hw_params, .hw_free = cs43130_hw_free, .set_sysclk = cs43130_set_sysclk, .set_fmt = cs43130_pcm_set_fmt, + .auto_selectable_formats = &cs43130_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops cs43130_dop_ops = { @@ -1623,6 +1635,8 @@ static const struct snd_soc_dai_ops cs43130_dop_ops = { .hw_free = cs43130_hw_free, .set_sysclk = cs43130_set_sysclk, .set_fmt = cs43130_pcm_set_fmt, + .auto_selectable_formats = &cs43130_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops cs43130_dsd_ops = { diff --git a/sound/soc/codecs/cs4341.c b/sound/soc/codecs/cs4341.c index a44e6b3c298e..74544c58a415 100644 --- a/sound/soc/codecs/cs4341.c +++ b/sound/soc/codecs/cs4341.c @@ -171,10 +171,18 @@ static const struct snd_kcontrol_new cs4341_controls[] = { SOC_SINGLE("Popguard Transient Switch", CS4341_REG_MODE2, 1, 1, 0), }; +static const u64 cs4341_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops cs4341_dai_ops = { .set_fmt = cs4341_set_fmt, .hw_params = cs4341_hw_params, .mute_stream = cs4341_mute, + .auto_selectable_formats = &cs4341_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs4349.c b/sound/soc/codecs/cs4349.c index 9a7f70175717..28320951e309 100644 --- a/sound/soc/codecs/cs4349.c +++ b/sound/soc/codecs/cs4349.c @@ -228,10 +228,22 @@ static const struct snd_soc_dapm_route cs4349_routes[] = { #define CS4349_PCM_RATES SNDRV_PCM_RATE_8000_192000 +/* + * NOTE + * + * auto_select doesn't include _RIGHT_J, because 32bit width can't use it + * (= cs4349_pcm_hw_params()). It can be selected via Card directly if needed. + */ +static const u64 cs4349_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops cs4349_dai_ops = { .hw_params = cs4349_pcm_hw_params, .set_fmt = cs4349_set_dai_fmt, .mute_stream = cs4349_mute, + .auto_selectable_formats = &cs4349_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/cs48l32.c b/sound/soc/codecs/cs48l32.c index a9967771124e..c11fc1a588c4 100644 --- a/sound/soc/codecs/cs48l32.c +++ b/sound/soc/codecs/cs48l32.c @@ -2361,6 +2361,27 @@ static int cs48l32_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; } +static const u64 cs48l32_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* + * 2nd priority + * + * _DSP_B / _LEFT_J can't be clock consumer (= cs48l32_set_fmt()). + */ + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, +}; + static const struct snd_soc_dai_ops cs48l32_dai_ops = { .probe = &cs48l32_asp_dai_probe, .startup = &cs48l32_startup, @@ -2368,6 +2389,8 @@ static const struct snd_soc_dai_ops cs48l32_dai_ops = { .set_tdm_slot = &cs48l32_set_tdm_slot, .hw_params = &cs48l32_hw_params, .set_sysclk = &cs48l32_dai_set_sysclk, + .auto_selectable_formats = cs48l32_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(cs48l32_selectable_formats), }; static int cs48l32_sysclk_ev(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/codecs/cs530x.c b/sound/soc/codecs/cs530x.c index 2c7e33135911..e0ea246edbd8 100644 --- a/sound/soc/codecs/cs530x.c +++ b/sound/soc/codecs/cs530x.c @@ -945,10 +945,19 @@ static int cs530x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, val << CS530X_ASP_TDM_SLOT_SHIFT); } +static const u64 cs530x_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs530x_dai_ops = { .set_fmt = cs530x_set_fmt, .hw_params = cs530x_hw_params, .set_tdm_slot = cs530x_set_tdm_slot, + .auto_selectable_formats = &cs530x_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver cs530x_dai = { diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c index 511453b4c375..c9c682a312c3 100644 --- a/sound/soc/codecs/cs53l30.c +++ b/sound/soc/codecs/cs53l30.c @@ -833,6 +833,12 @@ static int cs53l30_mute_stream(struct snd_soc_dai *dai, int mute, int stream) #define CS53L30_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 cs53l30_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops cs53l30_ops = { .hw_params = cs53l30_pcm_hw_params, .set_fmt = cs53l30_set_dai_fmt, @@ -840,6 +846,8 @@ static const struct snd_soc_dai_ops cs53l30_ops = { .set_tristate = cs53l30_set_tristate, .set_tdm_slot = cs53l30_set_dai_tdm_slot, .mute_stream = cs53l30_mute_stream, + .auto_selectable_formats = &cs53l30_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver cs53l30_dai = { diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index 0dea84cd8ef2..d3c7a9370220 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -200,7 +200,7 @@ static int cx20442_write(struct snd_soc_component *component, unsigned int reg, * Line discpline related code * * Any of the callback functions below can be used in two ways: - * 1) registerd by a machine driver as one of line discipline operations, + * 1) registered by a machine driver as one of line discipline operations, * 2) called from a machine's provided line discipline callback function * in case when extra machine specific code must be run as well. */ diff --git a/sound/soc/codecs/cx2072x.c b/sound/soc/codecs/cx2072x.c index d02ab74e549b..5b9e9520ae01 100644 --- a/sound/soc/codecs/cx2072x.c +++ b/sound/soc/codecs/cx2072x.c @@ -1521,6 +1521,16 @@ static const struct snd_soc_component_driver soc_codec_driver_cx2072x = { .endianness = 1, }; + +static const u64 cx2072x_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + /* * DAI ops */ @@ -1529,6 +1539,8 @@ static const struct snd_soc_dai_ops cx2072x_dai_ops = { .set_fmt = cx2072x_set_dai_fmt, .hw_params = cx2072x_hw_params, .set_bclk_ratio = cx2072x_set_dai_bclk_ratio, + .auto_selectable_formats = &cx2072x_selectable_formats, + .num_auto_selectable_formats = 1, }; static int cx2072x_dsp_dai_probe(struct snd_soc_dai *dai) diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c index 4df1a25e318b..40d759938edc 100644 --- a/sound/soc/codecs/da7210.c +++ b/sound/soc/codecs/da7210.c @@ -1030,6 +1030,11 @@ static int da7210_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, return -EINVAL; } +static const u64 da7210_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + /* DAI operations */ static const struct snd_soc_dai_ops da7210_dai_ops = { .hw_params = da7210_hw_params, @@ -1037,6 +1042,8 @@ static const struct snd_soc_dai_ops da7210_dai_ops = { .set_sysclk = da7210_set_dai_sysclk, .set_pll = da7210_set_dai_pll, .mute_stream = da7210_mute, + .auto_selectable_formats = &da7210_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1151,7 +1158,7 @@ static int da7210_probe(struct snd_soc_component *component) snd_soc_component_write(component, DA7210_PLL_DIV3, DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); - /* Diable PLL and bypass it */ + /* Disable PLL and bypass it */ snd_soc_component_write(component, DA7210_PLL, DA7210_PLL_FS_48000); /* Activate all enabled subsystem */ diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index 923b997efbc4..4990feaa8259 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -2091,16 +2091,6 @@ static int da7213_probe(struct snd_soc_component *component) pm_runtime_put_sync(component->dev); - /* Check if MCLK provided */ - da7213->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(da7213->mclk)) - return PTR_ERR(da7213->mclk); - if (da7213->mclk) - /* Do automatic PLL handling assuming fixed clock until - * set_pll() has been called. This makes the codec usable - * with the simple-audio-card driver. */ - da7213->fixed_clk_auto_pll = true; - /* Default infinite tone gen, start/stop by Kcontrol */ snd_soc_component_write(component, DA7213_TONE_GEN_CYCLES, DA7213_BEEP_CYCLES_MASK); @@ -2229,6 +2219,19 @@ static int da7213_i2c_probe(struct i2c_client *i2c) mutex_init(&da7213->ctrl_lock); + /* Check if MCLK provided */ + da7213->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(da7213->mclk)) + return dev_err_probe(&i2c->dev, PTR_ERR(da7213->mclk), + "failed to get mclk\n"); + if (da7213->mclk) + /* + * Do automatic PLL handling assuming fixed clock until + * set_pll() has been called. This makes the codec usable + * with the simple-audio-card driver. + */ + da7213->fixed_clk_auto_pll = true; + pm_runtime_set_autosuspend_delay(&i2c->dev, 100); pm_runtime_use_autosuspend(&i2c->dev); pm_runtime_set_active(&i2c->dev); diff --git a/sound/soc/codecs/da7218.c b/sound/soc/codecs/da7218.c index 93bd045ed870..a538b3dd1635 100644 --- a/sound/soc/codecs/da7218.c +++ b/sound/soc/codecs/da7218.c @@ -2166,12 +2166,24 @@ static int da7218_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 da7218_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops da7218_dai_ops = { .hw_params = da7218_hw_params, .set_sysclk = da7218_set_dai_sysclk, .set_pll = da7218_set_dai_pll, .set_fmt = da7218_set_dai_fmt, .set_tdm_slot = da7218_set_dai_tdm_slot, + .auto_selectable_formats = &da7218_selectable_formats, + .num_auto_selectable_formats = 1, }; #define DA7218_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ @@ -2286,7 +2298,7 @@ static const struct of_device_id da7218_of_match[] = { MODULE_DEVICE_TABLE(of, da7218_of_match); static enum da7218_micbias_voltage - da7218_of_micbias_lvl(struct snd_soc_component *component, u32 val) + da7218_of_micbias_lvl(struct device *dev, u32 val) { switch (val) { case 1200: @@ -2308,13 +2320,13 @@ static enum da7218_micbias_voltage case 3000: return DA7218_MICBIAS_3_0V; default: - dev_warn(component->dev, "Invalid micbias level"); + dev_warn(dev, "Invalid micbias level"); return DA7218_MICBIAS_1_6V; } } static enum da7218_mic_amp_in_sel - da7218_of_mic_amp_in_sel(struct snd_soc_component *component, const char *str) + da7218_of_mic_amp_in_sel(struct device *dev, const char *str) { if (!strcmp(str, "diff")) { return DA7218_MIC_AMP_IN_SEL_DIFF; @@ -2323,39 +2335,39 @@ static enum da7218_mic_amp_in_sel } else if (!strcmp(str, "se_n")) { return DA7218_MIC_AMP_IN_SEL_SE_N; } else { - dev_warn(component->dev, "Invalid mic input type selection"); + dev_warn(dev, "Invalid mic input type selection"); return DA7218_MIC_AMP_IN_SEL_DIFF; } } static enum da7218_dmic_data_sel - da7218_of_dmic_data_sel(struct snd_soc_component *component, const char *str) + da7218_of_dmic_data_sel(struct device *dev, const char *str) { if (!strcmp(str, "lrise_rfall")) { return DA7218_DMIC_DATA_LRISE_RFALL; } else if (!strcmp(str, "lfall_rrise")) { return DA7218_DMIC_DATA_LFALL_RRISE; } else { - dev_warn(component->dev, "Invalid DMIC data type selection"); + dev_warn(dev, "Invalid DMIC data type selection"); return DA7218_DMIC_DATA_LRISE_RFALL; } } static enum da7218_dmic_samplephase - da7218_of_dmic_samplephase(struct snd_soc_component *component, const char *str) + da7218_of_dmic_samplephase(struct device *dev, const char *str) { if (!strcmp(str, "on_clkedge")) { return DA7218_DMIC_SAMPLE_ON_CLKEDGE; } else if (!strcmp(str, "between_clkedge")) { return DA7218_DMIC_SAMPLE_BETWEEN_CLKEDGE; } else { - dev_warn(component->dev, "Invalid DMIC sample phase"); + dev_warn(dev, "Invalid DMIC sample phase"); return DA7218_DMIC_SAMPLE_ON_CLKEDGE; } } static enum da7218_dmic_clk_rate - da7218_of_dmic_clkrate(struct snd_soc_component *component, u32 val) + da7218_of_dmic_clkrate(struct device *dev, u32 val) { switch (val) { case 1500000: @@ -2363,13 +2375,13 @@ static enum da7218_dmic_clk_rate case 3000000: return DA7218_DMIC_CLK_3_0MHZ; default: - dev_warn(component->dev, "Invalid DMIC clock rate"); + dev_warn(dev, "Invalid DMIC clock rate"); return DA7218_DMIC_CLK_3_0MHZ; } } static enum da7218_hpldet_jack_rate - da7218_of_jack_rate(struct snd_soc_component *component, u32 val) + da7218_of_jack_rate(struct device *dev, u32 val) { switch (val) { case 5: @@ -2389,13 +2401,13 @@ static enum da7218_hpldet_jack_rate case 640: return DA7218_HPLDET_JACK_RATE_640US; default: - dev_warn(component->dev, "Invalid jack detect rate"); + dev_warn(dev, "Invalid jack detect rate"); return DA7218_HPLDET_JACK_RATE_40US; } } static enum da7218_hpldet_jack_debounce - da7218_of_jack_debounce(struct snd_soc_component *component, u32 val) + da7218_of_jack_debounce(struct device *dev, u32 val) { switch (val) { case 0: @@ -2407,13 +2419,13 @@ static enum da7218_hpldet_jack_debounce case 4: return DA7218_HPLDET_JACK_DEBOUNCE_4; default: - dev_warn(component->dev, "Invalid jack debounce"); + dev_warn(dev, "Invalid jack debounce"); return DA7218_HPLDET_JACK_DEBOUNCE_2; } } static enum da7218_hpldet_jack_thr - da7218_of_jack_thr(struct snd_soc_component *component, u32 val) + da7218_of_jack_thr(struct device *dev, u32 val) { switch (val) { case 84: @@ -2425,76 +2437,76 @@ static enum da7218_hpldet_jack_thr case 96: return DA7218_HPLDET_JACK_THR_96PCT; default: - dev_warn(component->dev, "Invalid jack threshold level"); + dev_warn(dev, "Invalid jack threshold level"); return DA7218_HPLDET_JACK_THR_84PCT; } } -static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *component) +static struct da7218_pdata *da7218_of_to_pdata(struct device *dev, + struct da7218_priv *da7218) { - struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); - struct device_node *np = component->dev->of_node; + struct device_node *np = dev->of_node; struct device_node *hpldet_np; struct da7218_pdata *pdata; struct da7218_hpldet_pdata *hpldet_pdata; const char *of_str; u32 of_val32; - pdata = devm_kzalloc(component->dev, sizeof(*pdata), GFP_KERNEL); + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return NULL; if (of_property_read_u32(np, "dlg,micbias1-lvl-millivolt", &of_val32) >= 0) - pdata->micbias1_lvl = da7218_of_micbias_lvl(component, of_val32); + pdata->micbias1_lvl = da7218_of_micbias_lvl(dev, of_val32); else pdata->micbias1_lvl = DA7218_MICBIAS_1_6V; if (of_property_read_u32(np, "dlg,micbias2-lvl-millivolt", &of_val32) >= 0) - pdata->micbias2_lvl = da7218_of_micbias_lvl(component, of_val32); + pdata->micbias2_lvl = da7218_of_micbias_lvl(dev, of_val32); else pdata->micbias2_lvl = DA7218_MICBIAS_1_6V; if (!of_property_read_string(np, "dlg,mic1-amp-in-sel", &of_str)) pdata->mic1_amp_in_sel = - da7218_of_mic_amp_in_sel(component, of_str); + da7218_of_mic_amp_in_sel(dev, of_str); else pdata->mic1_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF; if (!of_property_read_string(np, "dlg,mic2-amp-in-sel", &of_str)) pdata->mic2_amp_in_sel = - da7218_of_mic_amp_in_sel(component, of_str); + da7218_of_mic_amp_in_sel(dev, of_str); else pdata->mic2_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF; if (!of_property_read_string(np, "dlg,dmic1-data-sel", &of_str)) - pdata->dmic1_data_sel = da7218_of_dmic_data_sel(component, of_str); + pdata->dmic1_data_sel = da7218_of_dmic_data_sel(dev, of_str); else pdata->dmic1_data_sel = DA7218_DMIC_DATA_LRISE_RFALL; if (!of_property_read_string(np, "dlg,dmic1-samplephase", &of_str)) pdata->dmic1_samplephase = - da7218_of_dmic_samplephase(component, of_str); + da7218_of_dmic_samplephase(dev, of_str); else pdata->dmic1_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE; if (of_property_read_u32(np, "dlg,dmic1-clkrate-hz", &of_val32) >= 0) - pdata->dmic1_clk_rate = da7218_of_dmic_clkrate(component, of_val32); + pdata->dmic1_clk_rate = da7218_of_dmic_clkrate(dev, of_val32); else pdata->dmic1_clk_rate = DA7218_DMIC_CLK_3_0MHZ; if (!of_property_read_string(np, "dlg,dmic2-data-sel", &of_str)) - pdata->dmic2_data_sel = da7218_of_dmic_data_sel(component, of_str); + pdata->dmic2_data_sel = da7218_of_dmic_data_sel(dev, of_str); else pdata->dmic2_data_sel = DA7218_DMIC_DATA_LRISE_RFALL; if (!of_property_read_string(np, "dlg,dmic2-samplephase", &of_str)) pdata->dmic2_samplephase = - da7218_of_dmic_samplephase(component, of_str); + da7218_of_dmic_samplephase(dev, of_str); else pdata->dmic2_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE; if (of_property_read_u32(np, "dlg,dmic2-clkrate-hz", &of_val32) >= 0) - pdata->dmic2_clk_rate = da7218_of_dmic_clkrate(component, of_val32); + pdata->dmic2_clk_rate = da7218_of_dmic_clkrate(dev, of_val32); else pdata->dmic2_clk_rate = DA7218_DMIC_CLK_3_0MHZ; @@ -2508,7 +2520,7 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone if (!hpldet_np) return pdata; - hpldet_pdata = devm_kzalloc(component->dev, sizeof(*hpldet_pdata), + hpldet_pdata = devm_kzalloc(dev, sizeof(*hpldet_pdata), GFP_KERNEL); if (!hpldet_pdata) { of_node_put(hpldet_np); @@ -2519,14 +2531,14 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone if (of_property_read_u32(hpldet_np, "dlg,jack-rate-us", &of_val32) >= 0) hpldet_pdata->jack_rate = - da7218_of_jack_rate(component, of_val32); + da7218_of_jack_rate(dev, of_val32); else hpldet_pdata->jack_rate = DA7218_HPLDET_JACK_RATE_40US; if (of_property_read_u32(hpldet_np, "dlg,jack-debounce", &of_val32) >= 0) hpldet_pdata->jack_debounce = - da7218_of_jack_debounce(component, of_val32); + da7218_of_jack_debounce(dev, of_val32); else hpldet_pdata->jack_debounce = DA7218_HPLDET_JACK_DEBOUNCE_2; @@ -2534,7 +2546,7 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone if (of_property_read_u32(hpldet_np, "dlg,jack-threshold-pct", &of_val32) >= 0) hpldet_pdata->jack_thr = - da7218_of_jack_thr(component, of_val32); + da7218_of_jack_thr(dev, of_val32); else hpldet_pdata->jack_thr = DA7218_HPLDET_JACK_THR_84PCT; @@ -2626,18 +2638,7 @@ static int da7218_handle_supplies(struct snd_soc_component *component) struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); struct regulator *vddio; u8 io_voltage_lvl = DA7218_IO_VOLTAGE_LEVEL_2_5V_3_6V; - int i, ret; - - /* Get required supplies */ - for (i = 0; i < DA7218_NUM_SUPPLIES; ++i) - da7218->supplies[i].supply = da7218_supply_names[i]; - - ret = devm_regulator_bulk_get(component->dev, DA7218_NUM_SUPPLIES, - da7218->supplies); - if (ret) { - dev_err(component->dev, "Failed to get supplies\n"); - return ret; - } + int ret; /* Determine VDDIO voltage provided */ vddio = da7218->supplies[DA7218_SUPPLY_VDDIO].consumer; @@ -2875,21 +2876,8 @@ static int da7218_probe(struct snd_soc_component *component) if (ret) return ret; - /* Handle DT/Platform data */ - if (component->dev->of_node) - da7218->pdata = da7218_of_to_pdata(component); - else - da7218->pdata = dev_get_platdata(component->dev); - da7218_handle_pdata(component); - /* Check if MCLK provided, if not the clock is NULL */ - da7218->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(da7218->mclk)) { - ret = PTR_ERR(da7218->mclk); - goto err_disable_reg; - } - /* Default PC to free-running */ snd_soc_component_write(component, DA7218_PC_COUNT, DA7218_PC_FREERUN_MASK); @@ -2953,10 +2941,10 @@ static int da7218_probe(struct snd_soc_component *component) } if (da7218->irq) { - ret = devm_request_threaded_irq(component->dev, da7218->irq, NULL, - da7218_irq_thread, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - "da7218", component); + ret = request_threaded_irq(da7218->irq, NULL, + da7218_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "da7218", component); if (ret != 0) { dev_err(component->dev, "Failed to request IRQ %d: %d\n", da7218->irq, ret); @@ -2977,6 +2965,9 @@ static void da7218_remove(struct snd_soc_component *component) { struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component); + if (da7218->irq) + free_irq(da7218->irq, component); + regulator_bulk_disable(DA7218_NUM_SUPPLIES, da7218->supplies); } @@ -3247,7 +3238,7 @@ static const struct regmap_config da7218_regmap_config = { static int da7218_i2c_probe(struct i2c_client *i2c) { struct da7218_priv *da7218; - int ret; + int i, ret; da7218 = devm_kzalloc(&i2c->dev, sizeof(*da7218), GFP_KERNEL); if (!da7218) @@ -3272,6 +3263,28 @@ static int da7218_i2c_probe(struct i2c_client *i2c) return ret; } + /* Get required supplies */ + for (i = 0; i < DA7218_NUM_SUPPLIES; ++i) + da7218->supplies[i].supply = da7218_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, DA7218_NUM_SUPPLIES, + da7218->supplies); + if (ret) { + dev_err(&i2c->dev, "Failed to get supplies\n"); + return ret; + } + + /* Check if MCLK provided, if not the clock is NULL */ + da7218->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(da7218->mclk)) + return PTR_ERR(da7218->mclk); + + /* Handle DT/Platform data */ + if (i2c->dev.of_node) + da7218->pdata = da7218_of_to_pdata(&i2c->dev, da7218); + else + da7218->pdata = dev_get_platdata(&i2c->dev); + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_da7218, &da7218_dai, 1); if (ret < 0) { diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c index f6d371e93549..673285c3213b 100644 --- a/sound/soc/codecs/da7219.c +++ b/sound/soc/codecs/da7219.c @@ -1643,12 +1643,24 @@ static int da7219_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 da7219_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops da7219_dai_ops = { .hw_params = da7219_hw_params, .set_sysclk = da7219_set_dai_sysclk, .set_pll = da7219_set_dai_pll, .set_fmt = da7219_set_dai_fmt, .set_tdm_slot = da7219_set_dai_tdm_slot, + .auto_selectable_formats = &da7219_selectable_formats, + .num_auto_selectable_formats = 1, }; #define DA7219_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ diff --git a/sound/soc/codecs/da732x.c b/sound/soc/codecs/da732x.c index 12d1ac98461e..e417c7ada237 100644 --- a/sound/soc/codecs/da732x.c +++ b/sound/soc/codecs/da732x.c @@ -511,7 +511,7 @@ static const struct snd_kcontrol_new da732x_snd_controls[] = { SOC_SINGLE_TLV("ACD2 EQ Band 5 Volume", DA732X_REG_ADC2_EQ5, DA732X_EQ_BAND5_SHIFT, DA732X_EQ_VOL_VAL_MAX, DA732X_INVERT, eq_band_pga_tlv), - SOC_SINGLE_TLV("ADC2 EQ Overall Volume", DA732X_REG_ADC1_EQ5, + SOC_SINGLE_TLV("ADC2 EQ Overall Volume", DA732X_REG_ADC2_EQ5, DA732X_EQ_OVERALL_SHIFT, DA732X_EQ_OVERALL_VOL_VAL_MAX, DA732X_INVERT, eq_overall_tlv), @@ -1188,10 +1188,27 @@ static int da732x_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, #define DA732X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 da732x_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops da732x_dai_ops = { .hw_params = da732x_hw_params, .set_fmt = da732x_set_dai_fmt, .set_sysclk = da732x_set_dai_sysclk, + .auto_selectable_formats = da732x_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(da732x_selectable_formats), }; static struct snd_soc_dai_driver da732x_dai[] = { diff --git a/sound/soc/codecs/da9055.c b/sound/soc/codecs/da9055.c index a88c13533145..faff1535ab12 100644 --- a/sound/soc/codecs/da9055.c +++ b/sound/soc/codecs/da9055.c @@ -1317,6 +1317,12 @@ static int da9055_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, return -EINVAL; } +static const u64 da9055_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + /* DAI operations */ static const struct snd_soc_dai_ops da9055_dai_ops = { .hw_params = da9055_hw_params, @@ -1324,6 +1330,8 @@ static const struct snd_soc_dai_ops da9055_dai_ops = { .set_sysclk = da9055_set_dai_sysclk, .set_pll = da9055_set_dai_pll, .mute_stream = da9055_mute, + .auto_selectable_formats = &da9055_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/es7134.c b/sound/soc/codecs/es7134.c index 441df1523f1c..e3e6142ca028 100644 --- a/sound/soc/codecs/es7134.c +++ b/sound/soc/codecs/es7134.c @@ -130,10 +130,16 @@ static int es7134_component_probe(struct snd_soc_component *c) return 0; } +static const u64 es7134_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops es7134_dai_ops = { .set_fmt = es7134_set_fmt, .hw_params = es7134_hw_params, .set_sysclk = es7134_set_sysclk, + .auto_selectable_formats = &es7134_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver es7134_dai = { diff --git a/sound/soc/codecs/es7241.c b/sound/soc/codecs/es7241.c index 339553cfbb48..a0bd5386387d 100644 --- a/sound/soc/codecs/es7241.c +++ b/sound/soc/codecs/es7241.c @@ -152,10 +152,17 @@ static int es7241_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } +static const u64 es7241_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops es7241_dai_ops = { .set_fmt = es7241_set_fmt, .hw_params = es7241_hw_params, .set_sysclk = es7241_set_sysclk, + .auto_selectable_formats = &es7241_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver es7241_dai = { diff --git a/sound/soc/codecs/es8311.c b/sound/soc/codecs/es8311.c index 6fe3ae41afbd..ddbbfc2443b3 100644 --- a/sound/soc/codecs/es8311.c +++ b/sound/soc/codecs/es8311.c @@ -800,12 +800,29 @@ static int es8311_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 es8311_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops es8311_dai_ops = { .startup = es8311_startup, .hw_params = es8311_hw_params, .mute_stream = es8311_mute, .set_sysclk = es8311_set_sysclk, .set_fmt = es8311_set_dai_fmt, + .auto_selectable_formats = es8311_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(es8311_selectable_formats), .no_capture_mute = 1, }; @@ -889,12 +906,6 @@ static int es8311_component_probe(struct snd_soc_component *component) es8311 = snd_soc_component_get_drvdata(component); - es8311->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8311->mclk)) { - dev_err(component->dev, "invalid mclk\n"); - return PTR_ERR(es8311->mclk); - } - es8311->mclk_freq = clk_get_rate(es8311->mclk); if (es8311->mclk_freq > 0 && es8311->mclk_freq < ES8311_MCLK_MAX_FREQ) es8311_set_sysclk_constraints(es8311->mclk_freq, es8311); @@ -943,6 +954,10 @@ static int es8311_i2c_probe(struct i2c_client *i2c_client) if (es8311 == NULL) return -ENOMEM; + es8311->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(es8311->mclk)) + return dev_err_probe(dev, PTR_ERR(es8311->mclk), "invalid mclk\n"); + es8311->regmap = devm_regmap_init_i2c(i2c_client, &es8311_regmap_config); if (IS_ERR(es8311->regmap)) diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 24ec8b211cdb..ded36a43065a 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -566,12 +566,21 @@ static int es8316_mute(struct snd_soc_dai *dai, int mute, int direction) #define ES8316_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 es8316_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops es8316_ops = { .startup = es8316_pcm_startup, .hw_params = es8316_pcm_hw_params, .set_fmt = es8316_set_dai_fmt, .set_sysclk = es8316_set_dai_sysclk, .mute_stream = es8316_mute, + .auto_selectable_formats = &es8316_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -762,14 +771,6 @@ static int es8316_probe(struct snd_soc_component *component) es8316->component = component; - es8316->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8316->mclk)) { - dev_err(component->dev, "unable to get mclk\n"); - return PTR_ERR(es8316->mclk); - } - if (!es8316->mclk) - dev_warn(component->dev, "assuming static mclk\n"); - ret = clk_prepare_enable(es8316->mclk); if (ret) { dev_err(component->dev, "unable to enable mclk\n"); @@ -810,9 +811,7 @@ static int es8316_resume(struct snd_soc_component *component) struct es8316_priv *es8316 = snd_soc_component_get_drvdata(component); regcache_cache_only(es8316->regmap, false); - regcache_sync(es8316->regmap); - - return 0; + return regcache_sync(es8316->regmap); } static int es8316_suspend(struct snd_soc_component *component) @@ -874,6 +873,12 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client) i2c_set_clientdata(i2c_client, es8316); + es8316->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(es8316->mclk)) + return dev_err_probe(dev, PTR_ERR(es8316->mclk), "unable to get mclk\n"); + if (!es8316->mclk) + dev_warn(dev, "assuming static mclk\n"); + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names), es8316_supply_names); if (ret) diff --git a/sound/soc/codecs/es8323.c b/sound/soc/codecs/es8323.c index b926340256be..6a8d3902af23 100644 --- a/sound/soc/codecs/es8323.c +++ b/sound/soc/codecs/es8323.c @@ -622,12 +622,25 @@ static int es8323_mute_stream(struct snd_soc_dai *dai, int mute, int stream) mute ? ES8323_DACCONTROL3_DACMUTE : 0); } +static const u64 es8323_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops es8323_ops = { .startup = es8323_pcm_startup, .hw_params = es8323_pcm_hw_params, .set_fmt = es8323_set_dai_fmt, .set_sysclk = es8323_set_dai_sysclk, .mute_stream = es8323_mute_stream, + .auto_selectable_formats = &es8323_selectable_formats, + .num_auto_selectable_formats = 1, }; #define ES8323_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ @@ -658,15 +671,6 @@ static int es8323_probe(struct snd_soc_component *component) struct es8323_priv *es8323 = snd_soc_component_get_drvdata(component); int ret; - es8323->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8323->mclk)) { - dev_err(component->dev, "unable to get mclk\n"); - return PTR_ERR(es8323->mclk); - } - - if (!es8323->mclk) - dev_warn(component->dev, "assuming static mclk\n"); - ret = clk_prepare_enable(es8323->mclk); if (ret) { dev_err(component->dev, "unable to enable mclk\n"); @@ -733,9 +737,7 @@ static int es8323_resume(struct snd_soc_component *component) struct es8323_priv *es8323 = snd_soc_component_get_drvdata(component); regcache_cache_only(es8323->regmap, false); - regcache_sync(es8323->regmap); - - return 0; + return regcache_sync(es8323->regmap); } static const struct snd_soc_component_driver soc_component_dev_es8323 = { @@ -776,6 +778,12 @@ static int es8323_i2c_probe(struct i2c_client *i2c_client) i2c_set_clientdata(i2c_client, es8323); + es8323->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(es8323->mclk)) + return dev_err_probe(dev, PTR_ERR(es8323->mclk), "unable to get mclk\n"); + if (!es8323->mclk) + dev_warn(dev, "assuming static mclk\n"); + es8323->regmap = devm_regmap_init_i2c(i2c_client, &es8323_regmap); if (IS_ERR(es8323->regmap)) return PTR_ERR(es8323->regmap); diff --git a/sound/soc/codecs/es8326.c b/sound/soc/codecs/es8326.c index b6eadc2e9659..a95b3b8c75dd 100644 --- a/sound/soc/codecs/es8326.c +++ b/sound/soc/codecs/es8326.c @@ -33,11 +33,16 @@ struct es8326_priv { * while enabling or disabling or during an irq. */ struct mutex lock; + struct mutex hpf_lock; u8 jack_pol; u8 interrupt_src; u8 interrupt_clk; u8 hpl_vol; u8 hpr_vol; + u8 hpfl; + u8 hpfr; + u32 hpf_freq; + u32 capture_rate; bool jd_inverted; unsigned int sysclk; @@ -47,6 +52,48 @@ struct es8326_priv { int jack_remove_retry; }; +static const u32 hpf_table[10][10] = { + {1020, 754, 624, 559, 527, 511, 502, 498, 497, 496}, + {754, 495, 368, 306, 274, 259, 251, 247, 246, 244}, + {624, 368, 243, 182, 151, 136, 128, 124, 123, 121}, + {559, 306, 182, 120, 90, 75, 68, 63, 62, 60}, + {527, 274, 151, 90, 60, 45, 38, 33, 32, 31}, + {511, 259, 136, 75, 45, 30, 23, 19, 18, 17}, + {502, 251, 128, 68, 38, 23, 16, 13, 11, 11}, + {498, 247, 124, 63, 33, 19, 13, 10, 8, 8}, + {497, 246, 123, 62, 32, 18, 11, 8, 8, 0}, + {496, 244, 121, 60, 31, 17, 11, 8, 0, 0} +}; + +static bool find_best_hpf_freq(u32 target_hz, u8 *hpf1, u8 *hpf2) +{ + int best_row = -1, best_col = -1; + u32 min_diff = U32_MAX; + u32 f, diff; + int i, j; + + if (target_hz > 1020) + return false; + + for (i = 0; i < 10; i++) { + for (j = i; j < 10; j++) { + f = hpf_table[i][j]; + + diff = (target_hz > f) ? (target_hz - f) : (f - target_hz); + if (diff < min_diff) { + min_diff = diff; + best_row = i; + best_col = j; + } + } + } + + *hpf1 = best_col + ES8326_HPF_OFFSET; + *hpf2 = best_row + ES8326_HPF_OFFSET; + + return true; +} + static int es8326_crosstalk1_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -189,6 +236,48 @@ static int es8326_hprvol_set(struct snd_kcontrol *kcontrol, return 0; } +static int es8326_hpf_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = es8326->hpf_freq; + return 0; +} + +static int es8326_hpf_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + u32 freq; + bool hpf; + + if ((ucontrol->value.integer.value[0] > 1020) || (ucontrol->value.integer.value[0] < 0)) + return -EBUSY; + + if (es8326->hpf_freq == ucontrol->value.integer.value[0]) + return 0; + + if (es8326->capture_rate) { + guard(mutex)(&es8326->hpf_lock); + freq = (ucontrol->value.integer.value[0] * 48000) / es8326->capture_rate; + + hpf = find_best_hpf_freq(freq, &es8326->hpfl, &es8326->hpfr); + if (!hpf) + return -EBUSY; + + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS1, 0x0f, es8326->hpfl); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS2, 0x0f, es8326->hpfr); + } else { + dev_dbg_ratelimited(component->dev, "PCM_STREAM_CAPTURE is not active.retain the input frequency\n"); + } + es8326->hpf_freq = ucontrol->value.integer.value[0]; + + return 1; +} + static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(dac_vol_tlv, -9550, 50, 0); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(adc_vol_tlv, -9550, 50, 0); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(adc_analog_pga_tlv, 0, 300, 0); @@ -269,6 +358,8 @@ static const struct snd_kcontrol_new es8326_snd_controls[] = { SOC_ENUM("ALC Capture Winsize", alc_winsize), SOC_SINGLE_TLV("ALC Capture Target Level", ES8326_ALC_LEVEL, 0, 0x0f, 0, drc_target_tlv), + SOC_SINGLE_EXT("ADC HPF Freq Select", SND_SOC_NOPM, 0, 1020, 0, + es8326_hpf_get, es8326_hpf_set), SOC_SINGLE_EXT("CROSSTALK1", SND_SOC_NOPM, 0, 31, 0, es8326_crosstalk1_get, es8326_crosstalk1_set), @@ -353,8 +444,6 @@ static bool es8326_writeable_register(struct device *dev, unsigned int reg) case ES8326_BIAS_SW2: case ES8326_BIAS_SW3: case ES8326_BIAS_SW4: - case ES8326_ADC_HPFS1: - case ES8326_ADC_HPFS2: return false; default: return true; @@ -558,6 +647,8 @@ static int es8326_pcm_hw_params(struct snd_pcm_substream *substream, struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); u8 srate = 0; int coeff, array; + u32 freq; + bool hpf; if (es8326->version == 0) { coeff_div = coeff_div_v0; @@ -612,6 +703,28 @@ static int es8326_pcm_hw_params(struct snd_pcm_substream *substream, dev_warn(component->dev, "Clock coefficients do not match"); } + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + es8326->capture_rate = params_rate(params); + freq = (es8326->hpf_freq * 48000) / params_rate(params); + hpf = find_best_hpf_freq(freq, &es8326->hpfl, &es8326->hpfr); + if (!hpf) { + dev_err(component->dev, "The HPF frequency is invalid\n"); + return -EINVAL; + } + } + + return 0; +} + +static int es8326_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + es8326->capture_rate = 0; + return 0; } @@ -636,6 +749,8 @@ static int es8326_mute(struct snd_soc_dai *dai, int mute, int direction) regmap_update_bits(es8326->regmap, ES8326_VMIDSEL, 0x40, 0x40); regmap_update_bits(es8326->regmap, ES8326_ANA_MICBIAS, 0x70, 0x30); } + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS1, 0x0f, 0x04); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS2, 0x0f, 0x04); } } else { if (!es8326->calibrated) { @@ -705,7 +820,8 @@ static int es8326_set_bias_level(struct snd_soc_component *codec, case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: - regmap_write(es8326->regmap, ES8326_ANA_PDN, 0x3b); + regmap_write(es8326->regmap, ES8326_PGA_PDN, 0x58); + regmap_write(es8326->regmap, ES8326_ANA_PDN, 0x13); regmap_update_bits(es8326->regmap, ES8326_CLK_CTL, 0x20, 0x00); regmap_write(es8326->regmap, ES8326_SDINOUT1_IO, ES8326_IO_INPUT); if (es8326->version > ES8326_VERSION_B) { @@ -726,11 +842,21 @@ static int es8326_set_bias_level(struct snd_soc_component *codec, #define es8326_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 es8326_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops es8326_ops = { .hw_params = es8326_pcm_hw_params, + .hw_free = es8326_pcm_hw_free, .set_fmt = es8326_set_dai_fmt, .set_sysclk = es8326_set_dai_sysclk, .mute_stream = es8326_mute, + .auto_selectable_formats = &es8326_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 0, }; @@ -781,6 +907,8 @@ static void es8326_capture_pop_handler(struct work_struct *work) struct es8326_priv *es8326 = container_of(work, struct es8326_priv, capture_pop_work.work); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS1, 0x0f, es8326->hpfl); + regmap_update_bits(es8326->regmap, ES8326_ADC_HPFS2, 0x0f, es8326->hpfr); regmap_update_bits(es8326->regmap, ES8326_ADC_MUTE, 0x0F, 0x00); } @@ -865,7 +993,7 @@ static void es8326_jack_detect_handler(struct work_struct *work) struct es8326_priv *es8326 = container_of(work, struct es8326_priv, jack_detect_work.work); struct snd_soc_component *comp = es8326->component; - unsigned int iface; + unsigned int iface, pga_status; guard(mutex)(&es8326->lock); iface = snd_soc_component_read(comp, ES8326_HPDET_STA); @@ -961,12 +1089,19 @@ static void es8326_jack_detect_handler(struct work_struct *work) dev_dbg(comp->dev, "Headset detected\n"); snd_soc_jack_report(es8326->jack, SND_JACK_HEADSET, SND_JACK_HEADSET); - regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, - 0x08, 0x08); - regmap_write(es8326->regmap, ES8326_ADC1_SRC, 0x00); - regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x00); - regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, - 0x08, 0x00); + pga_status = snd_soc_component_read(comp, ES8326_PGA_PDN); + + if ((pga_status & 0x08) >> 3) { + regmap_write(es8326->regmap, ES8326_ADC1_SRC, 0x00); + regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x00); + } else { + regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, + 0x08, 0x08); + regmap_write(es8326->regmap, ES8326_ADC1_SRC, 0x00); + regmap_write(es8326->regmap, ES8326_ADC2_SRC, 0x00); + regmap_update_bits(es8326->regmap, ES8326_PGA_PDN, + 0x08, 0x00); + } usleep_range(10000, 15000); } } @@ -1130,18 +1265,28 @@ static int es8326_resume(struct snd_soc_component *component) { struct es8326_priv *es8326 = snd_soc_component_get_drvdata(component); unsigned int reg; + int ret; regcache_cache_only(es8326->regmap, false); regcache_cache_bypass(es8326->regmap, true); - regmap_read(es8326->regmap, ES8326_CLK_RESAMPLE, ®); + ret = regmap_read(es8326->regmap, ES8326_CLK_RESAMPLE, ®); regcache_cache_bypass(es8326->regmap, false); - /* reset internal clock state */ - if (reg == 0x05) - regmap_write(es8326->regmap, ES8326_CLK_CTL, ES8326_CLK_ON); - else - es8326_init(component); + if (ret) + return ret; - regcache_sync(es8326->regmap); + /* reset internal clock state */ + if (reg == 0x05) { + ret = regmap_write(es8326->regmap, ES8326_CLK_CTL, + ES8326_CLK_ON); + if (ret) + return ret; + } else { + es8326_init(component); + } + + ret = regcache_sync(es8326->regmap); + if (ret) + return ret; es8326_irq(es8326->irq, es8326); return 0; @@ -1202,6 +1347,7 @@ static int es8326_probe(struct snd_soc_component *component) } dev_dbg(component->dev, "interrupt-clk %x", es8326->interrupt_clk); + es8326->hpf_freq = ES8326_HPF_DEFAULT; es8326_init(component); return 0; } @@ -1288,6 +1434,7 @@ static int es8326_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, es8326); es8326->i2c = i2c; mutex_init(&es8326->lock); + mutex_init(&es8326->hpf_lock); es8326->regmap = devm_regmap_init_i2c(i2c, &es8326_regmap_config); if (IS_ERR(es8326->regmap)) { ret = PTR_ERR(es8326->regmap); diff --git a/sound/soc/codecs/es8326.h b/sound/soc/codecs/es8326.h index 1c5b3ec70a1e..c78d528e7dfe 100644 --- a/sound/soc/codecs/es8326.h +++ b/sound/soc/codecs/es8326.h @@ -149,6 +149,10 @@ #define ES8326_ADC_SRC_DMIC_SDIN3 6 #define ES8326_ADC_SRC_DMIC_SDIN3_INV 7 +/* HPF Settings */ +#define ES8326_HPF_DEFAULT 8 +#define ES8326_HPF_OFFSET 4 + #define ES8326_ADC_AMIC ((ES8326_ADC_SRC_ANA_INV_SW1 << ES8326_ADC2_SHIFT) \ | (ES8326_ADC_SRC_ANA_INV_SW1 << ES8326_ADC1_SHIFT)) #define ES8326_ADC_DMIC ((ES8326_ADC_SRC_DMIC_SDIN2 << ES8326_ADC2_SHIFT) \ diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c index aaa6646ad4c5..37fda2f42b0c 100644 --- a/sound/soc/codecs/es8328.c +++ b/sound/soc/codecs/es8328.c @@ -727,12 +727,19 @@ static int es8328_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 es8328_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops es8328_dai_ops = { .startup = es8328_startup, .hw_params = es8328_hw_params, .mute_stream = es8328_mute, .set_sysclk = es8328_set_sysclk, .set_fmt = es8328_set_dai_fmt, + .auto_selectable_formats = &es8328_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -822,14 +829,6 @@ static int es8328_component_probe(struct snd_soc_component *component) return ret; } - /* Setup clocks */ - es8328->clk = devm_clk_get(component->dev, NULL); - if (IS_ERR(es8328->clk)) { - dev_err(component->dev, "codec clock missing or invalid\n"); - ret = PTR_ERR(es8328->clk); - goto clk_fail; - } - ret = clk_prepare_enable(es8328->clk); if (ret) { dev_err(component->dev, "unable to prepare codec clk\n"); @@ -899,6 +898,11 @@ int es8328_probe(struct device *dev, struct regmap *regmap) es8328->regmap = regmap; + es8328->clk = devm_clk_get(dev, NULL); + if (IS_ERR(es8328->clk)) + return dev_err_probe(dev, PTR_ERR(es8328->clk), + "codec clock missing or invalid\n"); + for (i = 0; i < ARRAY_SIZE(es8328->supplies); i++) es8328->supplies[i].supply = supply_names[i]; diff --git a/sound/soc/codecs/es8375.c b/sound/soc/codecs/es8375.c index e8747bc24433..bed872eafc95 100644 --- a/sound/soc/codecs/es8375.c +++ b/sound/soc/codecs/es8375.c @@ -494,11 +494,23 @@ static int es8375_mute(struct snd_soc_dai *dai, int mute, int stream) #define es8375_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 es8375_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops es8375_ops = { .hw_params = es8375_hw_params, .mute_stream = es8375_mute, .set_sysclk = es8375_set_sysclk, .set_fmt = es8375_set_dai_fmt, + .auto_selectable_formats = &es8375_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver es8375_dai = { @@ -577,20 +589,24 @@ static int es8375_resume(struct snd_soc_component *component) { struct es8375_priv *es8375 = snd_soc_component_get_drvdata(component); unsigned int reg; + int ret; regcache_cache_only(es8375->regmap, false); regcache_cache_bypass(es8375->regmap, true); - regmap_read(es8375->regmap, ES8375_CLK_MGR2, ®); + ret = regmap_read(es8375->regmap, ES8375_CLK_MGR2, ®); regcache_cache_bypass(es8375->regmap, false); + if (ret) + return ret; - if (reg == 0x00) + if (reg == 0x00) { es8375_init(component); - else - es8375_set_bias_level(component, SND_SOC_BIAS_ON); + } else { + ret = es8375_set_bias_level(component, SND_SOC_BIAS_ON); + if (ret) + return ret; + } - regcache_sync(es8375->regmap); - - return 0; + return regcache_sync(es8375->regmap); } static int es8375_codec_probe(struct snd_soc_component *component) @@ -661,7 +677,7 @@ static int es8375_read_device_properities(struct device *dev, struct es8375_priv return ret; } - es8375->mclk = devm_clk_get(dev, "mclk"); + es8375->mclk = devm_clk_get_optional(dev, "mclk"); if (IS_ERR(es8375->mclk)) return dev_err_probe(dev, PTR_ERR(es8375->mclk), "unable to get mclk\n"); diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c index 2ea0c4052ce1..267806b72f87 100644 --- a/sound/soc/codecs/es8389.c +++ b/sound/soc/codecs/es8389.c @@ -910,6 +910,13 @@ static int es8389_mute(struct snd_soc_dai *dai, int mute, int direction) #define es8389_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 es8389_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops es8389_ops = { .hw_params = es8389_pcm_hw_params, .hw_free = es8389_pcm_hw_free, @@ -917,6 +924,8 @@ static const struct snd_soc_dai_ops es8389_ops = { .set_sysclk = es8389_set_dai_sysclk, .set_tdm_slot = es8389_set_tdm_slot, .mute_stream = es8389_mute, + .auto_selectable_formats = &es8389_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver es8389_dai = { @@ -1032,25 +1041,33 @@ static int es8389_resume(struct snd_soc_component *component) { struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); unsigned int regv; + int ret; regcache_cache_only(es8389->regmap, false); regcache_cache_bypass(es8389->regmap, true); - regmap_read(es8389->regmap, ES8389_RESET, ®v); + ret = regmap_read(es8389->regmap, ES8389_RESET, ®v); + if (ret) + goto disable_bypass; - if (regv == 0xff) + if (regv == 0xff) { es8389_init(component); - else - es8389_set_bias_level(component, SND_SOC_BIAS_ON); + } else { + ret = es8389_set_bias_level(component, SND_SOC_BIAS_ON); + if (ret) + goto disable_bypass; + } +disable_bypass: regcache_cache_bypass(es8389->regmap, false); - regcache_sync(es8389->regmap); + if (ret) + return ret; - return 0; + return regcache_sync(es8389->regmap); } static int es8389_probe(struct snd_soc_component *component) { - int ret, i; + int ret; struct es8389_private *es8389 = snd_soc_component_get_drvdata(component); ret = device_property_read_u8(component->dev, "everest,mclk-src", &es8389->mclk_src); @@ -1059,22 +1076,6 @@ static int es8389_probe(struct snd_soc_component *component) es8389->mclk_src = ES8389_MCLK_SOURCE; } - for (i = 0; i < ARRAY_SIZE(es8389_core_supplies); i++) - es8389->core_supply[i].supply = es8389_core_supplies[i]; - ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(es8389_core_supplies), es8389->core_supply); - if (ret) { - dev_err(component->dev, "Failed to request core supplies %d\n", ret); - return ret; - } - - es8389->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(es8389->mclk)) - return dev_err_probe(component->dev, PTR_ERR(es8389->mclk), - "ES8389 is unable to get mclk\n"); - - if (!es8389->mclk) - dev_err(component->dev, "%s, assuming static mclk\n", __func__); - ret = clk_prepare_enable(es8389->mclk); if (ret) { dev_err(component->dev, "%s, unable to enable mclk\n", __func__); @@ -1162,7 +1163,7 @@ static void es8389_i2c_shutdown(struct i2c_client *i2c) static int es8389_i2c_probe(struct i2c_client *i2c_client) { struct es8389_private *es8389; - int ret; + int ret, i; es8389 = devm_kzalloc(&i2c_client->dev, sizeof(*es8389), GFP_KERNEL); if (es8389 == NULL) @@ -1174,6 +1175,22 @@ static int es8389_i2c_probe(struct i2c_client *i2c_client) return dev_err_probe(&i2c_client->dev, PTR_ERR(es8389->regmap), "regmap_init() failed\n"); + for (i = 0; i < ARRAY_SIZE(es8389_core_supplies); i++) + es8389->core_supply[i].supply = es8389_core_supplies[i]; + ret = devm_regulator_bulk_get(&i2c_client->dev, ARRAY_SIZE(es8389_core_supplies), + es8389->core_supply); + if (ret) + return dev_err_probe(&i2c_client->dev, ret, + "Failed to request core supplies\n"); + + es8389->mclk = devm_clk_get_optional(&i2c_client->dev, "mclk"); + if (IS_ERR(es8389->mclk)) + return dev_err_probe(&i2c_client->dev, PTR_ERR(es8389->mclk), + "ES8389 is unable to get mclk\n"); + + if (!es8389->mclk) + dev_err(&i2c_client->dev, "%s, assuming static mclk\n", __func__); + ret = devm_snd_soc_register_component(&i2c_client->dev, &soc_codec_dev_es8389, &es8389_dai, diff --git a/sound/soc/codecs/es9039q2m.c b/sound/soc/codecs/es9039q2m.c new file mode 100644 index 000000000000..4e9f86cb7860 --- /dev/null +++ b/sound/soc/codecs/es9039q2m.c @@ -0,0 +1,2008 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// ESS Technology ES9039Q2M 32-bit 2-channel audio DAC +// +// Copyright (C) 2026 Karl Asseily +// +// Every register number, bit field and default in this file was taken from +// ES9039Q2M datasheet v0.2.3 and then verified by reading the defaults back off +// a live part over I2C. +// +// The part has two control personalities selected by the MODE pin: hardware +// mode (strapped by HW0/HW1/HW2, no bus at all) and software mode (I2C or SPI). +// This driver implements software mode over I2C, which MODE = GND selects. +// +// Three properties shape the driver: +// +// - There is an ASRC in front of the DAC, so MCLK need not be synchronous with +// BCLK or LRCK. A board may feed it a fixed oscillator, or may run it +// synchronously and let the part generate BCLK and WS; both are supported. +// +// - INPUT_SEL chooses PCM / DSD / DoP / S/PDIF. It does NOT choose I2S vs +// left-justified - that is TDM_LJ_MODE in register 60, and there is no +// right-justified mode to map onto at all. +// +// - Several registers have non-zero reserved defaults (register 88 reads +// 0xb8 at reset), so every write here is read-modify-write. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* ------------------------------------------------- read/write registers ---- */ + +#define ES9039_SYSTEM_CONFIG 0x00 /* reg 0 */ +#define ES9039_DAC_MODE BIT(1) /* CLEAR at reset - datapath off */ +#define ES9039_64FS_MODE BIT(6) + +/* + * Register 1 selects which DECODERS are running, and it is separate from + * INPUT_SEL in register 57, which only says which port to listen to. Both are + * needed: on reset only ENABLE_TDM_DECODE is set, so selecting DoP as the input + * while leaving bit 2 clear leaves the part hunting for a marker with the + * marker decoder switched off. It then finds no valid DoP and mutes - silence, + * DOP_VALID reading 0, and nothing anywhere saying why. Measured on hardware. + */ +#define ES9039_SYS_MODE 0x01 /* reg 1, reset 0xb1 */ +#define ES9039_ENABLE_TDM_DECODE BIT(0) /* set at reset */ +#define ES9039_ENABLE_DSD_DECODE BIT(1) +#define ES9039_ENABLE_DOP_DECODE BIT(2) +#define ES9039_ENABLE_SPDIF_DECODE BIT(3) +#define ES9039_SYNC_MODE BIT(6) /* 0 = ASYNC */ +#define ES9039_ENABLE_DAC_CLK BIT(7) /* set at reset */ +/* + * Every decoder the part has. hw_params() enables one of these and clears the + * rest, so leaving S/PDIF out of the mask would have left its decoder running + * alongside the selected one. + */ +#define ES9039_DECODE_MASK (ES9039_ENABLE_TDM_DECODE | \ + ES9039_ENABLE_DSD_DECODE | \ + ES9039_ENABLE_DOP_DECODE | \ + ES9039_ENABLE_SPDIF_DECODE) + +#define ES9039_AUTO_FS_DETECT 0x03 /* reg 3 */ +#define ES9039_AUTO_FS_DETECT_EN BIT(7) + +#define ES9039_CLOCK_CONFIG 0x04 /* reg 4, MASTER_BCK_DIV */ + +#define ES9039_INPUT_SEL 0x39 /* reg 57 */ +#define ES9039_AUTO_INPUT_SEL BIT(0) +#define ES9039_INPUT_SEL_MASK GENMASK(2, 1) +#define ES9039_INPUT_PCM 0x0 +#define ES9039_INPUT_DSD 0x1 +#define ES9039_INPUT_DOP 0x2 +#define ES9039_INPUT_SPDIF 0x3 +#define ES9039_PCM_MASTER_MODE BIT(4) +#define ES9039_DSD_MASTER_MODE BIT(5) +#define ES9039_DSD_FAULT_DETECT BIT(6) /* set at reset */ + +#define ES9039_MASTER_ENC 0x3a /* reg 58 */ +#define ES9039_BCK_INV BIT(6) + +#define ES9039_TDM_CH_NUM 0x3b /* reg 59, slots = value + 1 */ +#define ES9039_TDM_CH_NUM_MASK GENMASK(4, 0) + +#define ES9039_TDM_CONFIG1 0x3c /* reg 60 */ +#define ES9039_TDM_VALID_EDGE BIT(6) +#define ES9039_TDM_LJ_MODE BIT(7) /* 0 = standard I2S */ + +#define ES9039_TDM_CONFIG2 0x3d /* reg 61 */ +#define ES9039_TDM_BIT_WIDTH_MASK GENMASK(6, 5) +#define ES9039_WIDTH_32 0x0 +#define ES9039_WIDTH_24 0x1 +#define ES9039_WIDTH_16 0x2 + +#define ES9039_MONITOR_CFG 0x3e /* reg 62 */ +#define ES9039_DISABLE_PCM_DC BIT(3) +#define ES9039_ENABLE_BCK_MONITOR BIT(4) /* set at reset */ +#define ES9039_ENABLE_WS_MONITOR BIT(5) /* set at reset */ +#define ES9039_DISABLE_DSD_MUTE BIT(6) +#define ES9039_DISABLE_DSD_DC BIT(7) + +#define ES9039_VOLUME_CH1 0x4a /* reg 74, 0x00 = 0 dB */ +#define ES9039_VOLUME_CH2 0x4b /* reg 75, 0xff = -127.5 dB */ +#define ES9039_VOL_MAX 0xff + +#define ES9039_VOL_RATE_UP 0x52 /* reg 82 */ +#define ES9039_VOL_RATE_DOWN 0x53 /* reg 83 */ + +#define ES9039_DAC_MUTE 0x56 /* reg 86, 1 = muted */ +#define ES9039_MUTE_CH1 BIT(0) +#define ES9039_MUTE_CH2 BIT(1) +#define ES9039_MUTE_BOTH (ES9039_MUTE_CH1 | ES9039_MUTE_CH2) + +#define ES9039_DAC_INVERT 0x57 /* reg 87 */ + +#define ES9039_FILTER_SHAPE 0x58 /* reg 88, [7:3] reset to 10111 */ +#define ES9039_FILTER_SHAPE_MASK GENMASK(2, 0) +#define ES9039_FILTER_APODIZING 1 /* linear phase apodizing fast */ + +#define ES9039_IIR_SPDIF 0x59 /* reg 89 */ +#define ES9039_IIR_BW_MASK GENMASK(2, 0) +#define ES9039_VOLUME_HOLD BIT(3) +#define ES9039_SPDIF_SEL_MASK GENMASK(7, 4) + +#define ES9039_DAC_PATH 0x5a /* reg 90 */ +#define ES9039_BYPASS_FIR2X BIT(0) +#define ES9039_BYPASS_FIR4X BIT(1) +#define ES9039_BYPASS_IIR BIT(2) + +#define ES9039_THD_C2 0x5b /* regs 91-94: CH1 lo, CH2 hi */ +#define ES9039_THD_C3 0x6b /* regs 107-110 */ + +#define ES9039_AUTOMUTE_EN 0x7b /* reg 123, both set at reset */ +#define ES9039_AUTOMUTE_TIME 0x7c /* regs 124-125 */ +#define ES9039_AUTOMUTE_TIME_MASK GENMASK(10, 0) +#define ES9039_MUTE_RAMP_TO_GND BIT(11) /* set at reset */ +#define ES9039_AUTOMUTE_LEVEL 0x7e /* regs 126-127 */ +#define ES9039_AUTOMUTE_OFF_LEVEL 0x80 /* regs 128-129 */ + +#define ES9039_SOFT_RAMP 0x82 /* reg 130, valid 0..12 */ +#define ES9039_SOFT_RAMP_MASK GENMASK(4, 0) +#define ES9039_SOFT_RAMP_MAX 12 + +#define ES9039_NSMOD 0x83 /* reg 131 */ +#define ES9039_NSMOD_WIDE_BW_MASK GENMASK(4, 1) +#define ES9039_NSMOD_DEFAULT 0x4 +#define ES9039_NSMOD_WIDE 0xc + +#define ES9039_PROG_RAM_CTRL 0x87 /* reg 135 */ +#define ES9039_PROG_COEFF_EN BIT(0) +#define ES9039_PROG_COEFF_WE BIT(1) + +#define ES9039_PROG_RAM_ADDR 0x89 /* reg 137 */ +#define ES9039_PROG_ADDR_MASK GENMASK(6, 0) +#define ES9039_PROG_STAGE_4X BIT(7) + +#define ES9039_PROG_RAM_DATA 0x8a /* regs 138-140, 24-bit signed */ + +#define ES9039_LAST_RW 0x8e /* reg 145 */ + +/* ----------------------------------------------------- readback registers -- */ + +#define ES9039_READBACK_BASE 0xe0 /* reg 224 */ + +#define ES9039_CHIP_ID 0xe1 /* reg 225 */ +#define ES9039_CHIP_ID_ES9039Q2M 0x63 + +#define ES9039_IRQ_SOURCES 0xea /* regs 234-235, 16-bit */ +#define ES9039_SRC_VOL_MIN_MASK GENMASK(1, 0) +#define ES9039_SRC_AUTOMUTE_MASK GENMASK(3, 2) +#define ES9039_SRC_SS_RAMP_MASK GENMASK(5, 4) +#define ES9039_SRC_DOP_VALID BIT(6) +#define ES9039_SRC_BCK_WS_FAIL BIT(7) +#define ES9039_SRC_TDM_VALID BIT(11) + +#define ES9039_AUTO_FS_READ 0xef /* reg 239 */ +#define ES9039_FS_DIV_MASK GENMASK(5, 0) +#define ES9039_FS_HALF_DIV BIT(6) +#define ES9039_FS_DIV_VALID BIT(7) + +#define ES9039_AUTOMUTE_READ 0xf2 /* reg 242 */ + +#define ES9039_INPUT_STREAM_READ 0xf5 /* reg 245 */ +#define ES9039_RD_INPUT_SEL_MASK GENMASK(1, 0) +#define ES9039_RD_DOP_VALID BIT(2) +#define ES9039_RD_TDM_VALID BIT(3) +#define ES9039_RD_SPDIF_VALID BIT(4) + +#define ES9039_MAX_REGISTER 0xfb /* reg 251 */ + +/* Programmable oversampling FIR: 128 taps in the 2x stage, 32 in the 4x. */ +#define ES9039_FIR2X_TAPS 128 +#define ES9039_FIR4X_TAPS 32 +#define ES9039_COEFF_BYTES 3 +#define ES9039_FIR_STAGES 2 /* [0] = 2x stage, [1] = 4x */ + +/* In power-up order. Reversed on the way down. Datasheet figure 22. */ +#define ES9039_NUM_SUPPLIES 4 +#define ES9039_FIR_MAX_BYTES (ES9039_FIR2X_TAPS * ES9039_COEFF_BYTES) + +/* ------------------------------------------------------------------ private */ + +struct es9039q2m_priv { + struct regmap *regmap; + struct clk *mclk; + unsigned int mclk_rate; + unsigned int fmt; + unsigned int stream_rate; /* last rate from hw_params */ + unsigned int bclk_ratio; /* bit clocks per frame, 0 = unknown */ + + /* + * Serialises dop_auto against hw_params(), which reads it while + * deciding what to program. Without it a control write racing a + * stream start can leave the two DoP registers disagreeing. + */ + struct mutex lock; + bool dop_auto; /* let the part detect DoP itself */ + + /* + * ES9039_DAC_MUTE has two owners and neither can hold it alone: the + * "Master Playback Switch" control, and mute_stream() around stream + * start and stop. Both record what they want here and the register is + * written from the union, so a user unmute cannot un-mute a stopped + * stream and a stream start cannot override a user mute. + * + * mute_stream starts FALSE and only a real mute_stream(1) sets it. + * Starting it true seemed the safe choice - come up muted - but it is + * not: if a card never reaches the DAI mute callbacks, the flag never + * clears, and because the control now records intent rather than + * writing the register there is nothing the user can do about it. The + * DAC is then muted for ever and the mixer looks fine. Measured on + * hardware: silent with the switch on, off, stream or no stream. Pop + * suppression at probe comes from writing the register + * directly instead, which is what the driver did before this rework + * and is a state either owner may legitimately lift. + */ + unsigned int mute_user; /* ES9039_MUTE_CH* the user asked for */ + bool mute_stream; /* ASoC has the stream muted */ + + bool provider; /* part drives BCK and WS */ + bool mclk_fixed; /* MCLK rate cannot be changed, so ASRC it is */ + + /* + * The four supply inputs, held IN POWER-UP ORDER - the array order is + * the datasheet's sequence and the enable loop walks it forwards, the + * disable loop backwards. DVDD is not among them: the part's 1.2 V + * digital rail is generated on chip and that pin wants a decoupling + * capacitor, not a regulator. + */ + struct regulator_bulk_data supplies[ES9039_NUM_SUPPLIES]; + + /* + * Shadow of whatever was last uploaded to the programmable FIR RAM. + * + * That RAM cannot be read back - see the upload code below - so regmap + * cannot cache it and nothing else in the system knows its contents. + * A board that removes the part's supplies in system suspend therefore + * comes back with the RAM undefined while regcache_sync() restores the + * filter SELECTION from the cache, which would point the interpolator + * at whatever the RAM powered up holding. Keep a copy and re-upload. + */ + u8 fir_shadow[ES9039_FIR_STAGES][ES9039_FIR_MAX_BYTES]; + unsigned int fir_taps[ES9039_FIR_STAGES]; /* 0 = never uploaded */ +}; + +/* + * Multi-byte fields are little-endian across ascending register addresses: + * register N holds bits [7:0], N+1 holds [15:8], and so on. + */ +static int es9039_read_le(struct regmap *map, unsigned int reg, int n, u32 *out) +{ + u8 buf[4]; + int ret, i; + + ret = regmap_bulk_read(map, reg, buf, n); + if (ret) + return ret; + + *out = 0; + for (i = 0; i < n; i++) + *out |= (u32)buf[i] << (8 * i); + + return 0; +} + +static int es9039_write_le(struct regmap *map, unsigned int reg, int n, u32 val) +{ + u8 buf[4]; + int i; + + for (i = 0; i < n; i++) + buf[i] = (val >> (8 * i)) & 0xff; + + return regmap_bulk_write(map, reg, buf, n); +} + +/* ------------------------------------------------------------------ regmap */ + +static bool es9039q2m_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg <= ES9039_LAST_RW; +} + +static bool es9039q2m_readable_reg(struct device *dev, unsigned int reg) +{ + return reg <= ES9039_LAST_RW || reg >= ES9039_READBACK_BASE; +} + +static bool es9039q2m_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg >= ES9039_READBACK_BASE; +} + +static const struct regmap_config es9039q2m_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ES9039_MAX_REGISTER, + .writeable_reg = es9039q2m_writeable_reg, + .readable_reg = es9039q2m_readable_reg, + .volatile_reg = es9039q2m_volatile_reg, + .cache_type = REGCACHE_MAPLE, +}; + +/* --------------------------------------------------- signed 16-bit controls */ + +struct es9039_s16_ctl { + unsigned int reg; + unsigned int shift; /* 0 for CH1, 16 for CH2 within the 32-bit pair */ +}; + +static int es9039_s16_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = S16_MIN; + uinfo->value.integer.max = S16_MAX; + return 0; +} + +static int es9039_s16_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_s16_ctl *p = (void *)kcontrol->private_value; + u32 v; + int ret; + + ret = es9039_read_le(priv->regmap, p->reg + (p->shift / 8), 2, &v); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = (s16)v; + return 0; +} + +static int es9039_s16_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_s16_ctl *p = (void *)kcontrol->private_value; + long v = ucontrol->value.integer.value[0]; + u32 old; + int ret; + + if (v < S16_MIN || v > S16_MAX) + return -EINVAL; + + ret = es9039_read_le(priv->regmap, p->reg + (p->shift / 8), 2, &old); + if (ret) + return ret; + + if ((s16)old == (s16)v) + return 0; + + ret = es9039_write_le(priv->regmap, p->reg + (p->shift / 8), 2, + (u16)v); + if (ret) + return ret; + + return 1; +} + +#define ES9039_S16(xname, xreg, xshift) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = es9039_s16_info, \ + .get = es9039_s16_get, \ + .put = es9039_s16_put, \ + .private_value = (unsigned long)&(struct es9039_s16_ctl) \ + { .reg = xreg, .shift = xshift }, \ +} + +/* ------------------------------------------------- multi-register integers */ + +struct es9039_wide_ctl { + unsigned int reg; + unsigned int bytes; + unsigned int mask; + unsigned int max; +}; + +static int es9039_wide_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct es9039_wide_ctl *p = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = p->max; + return 0; +} + +static int es9039_wide_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_wide_ctl *p = (void *)kcontrol->private_value; + u32 v; + int ret; + + ret = es9039_read_le(priv->regmap, p->reg, p->bytes, &v); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = v & p->mask; + return 0; +} + +static int es9039_wide_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_wide_ctl *p = (void *)kcontrol->private_value; + long v = ucontrol->value.integer.value[0]; + u32 old; + int ret, i; + + if (v < 0 || v > p->max) + return -EINVAL; + + ret = es9039_read_le(priv->regmap, p->reg, p->bytes, &old); + if (ret) + return ret; + + if ((old & p->mask) == (u32)v) + return 0; + + /* + * Write one register at a time through regmap_update_bits() rather + * than reading the whole field and writing it back. + * + * The bits outside the field are not ours: ES9039_AUTOMUTE_TIME spans + * registers 124-125 while MUTE_RAMP_TO_GND is bit 3 of register 125 + * and has a control of its own. A read-modify-write over the pair + * leaves a window in which that other control can write between our + * read and our write, and its change is then lost - the ALSA put path + * holds card->controls_rwsem for READ, so two controls really can run + * at once. Per-register update_bits closes the window inside regmap's + * own lock and never touches a bit outside p->mask. + */ + for (i = 0; i < p->bytes; i++) { + u8 mask = (p->mask >> (8 * i)) & 0xff; + + if (!mask) + continue; + + ret = regmap_update_bits(priv->regmap, p->reg + i, mask, + ((u32)v >> (8 * i)) & mask); + if (ret) + return ret; + } + + return 1; +} + +#define ES9039_WIDE(xname, xreg, xbytes, xmask, xmax) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = es9039_wide_info, \ + .get = es9039_wide_get, \ + .put = es9039_wide_put, \ + .private_value = (unsigned long)&(struct es9039_wide_ctl) \ + { .reg = xreg, .bytes = xbytes, \ + .mask = xmask, .max = xmax }, \ +} + +/* ------------------------------------------------ programmable FIR upload -- + * + * Write-only, deliberately. The chip has a PROG_COEFF_OUT register (248-246) + * described only as "Programmable FIR coefficient readback", but it is not a + * RAM read port: it returns the LAST COEFFICIENT WRITTEN, whatever address is + * selected in PROG_COEFF_ADDR. Measured over raw I2C with this driver out of + * the path - two different coefficients written to + * addresses 0 and 1, then read back with five different sequences (plain, with + * a settle delay, with PROG_COEFF_EN set, with the address written twice, and + * with a WE pulse after the address). All ten reads returned the value written + * to address 1. + * + * A get() built on that register would return something with the shape of data + * and none of its meaning, so there is no get(). If ESS documents a real + * readback sequence, add one. + */ + +/* + * Per-control data rides in our own struct with the soc_bytes_ext EMBEDDED, + * recovered by container_of. Not in soc_bytes_ext.dobj: that field belongs to + * the topology subsystem and only exists under CONFIG_SND_SOC_TOPOLOGY, so a + * driver stashing its own data there fails to build on any config without it. + */ +struct es9039_fir_ctl { + struct soc_bytes_ext be; + unsigned int taps; + bool stage_4x; +}; + +/* + * Push one stage's coefficients into the RAM. Callers hold priv->lock: both + * FIR controls drive the same address, data and strobe registers, so two + * uploads at once would interleave into each other's RAM, and resume re-uploads + * through this same path. + * + * PROG_COEFF_WE is a per-coefficient strobe, not a gate held open across the + * upload. The datasheet's sequence is address, data, raise WE, lower WE, once + * per coefficient. Holding it high for the whole loop also appears to work on + * ES9039Q2M silicon, but "appears to work" is not a specification. + */ +static int es9039_fir_upload(struct es9039q2m_priv *priv, const u8 *data, + unsigned int taps, bool stage_4x) +{ + unsigned int i; + int ret = 0; + + for (i = 0; i < taps; i++) { + ret = regmap_write(priv->regmap, ES9039_PROG_RAM_ADDR, + (stage_4x ? ES9039_PROG_STAGE_4X : 0) | + FIELD_PREP(ES9039_PROG_ADDR_MASK, i)); + if (ret) + goto out; + + ret = regmap_bulk_write(priv->regmap, ES9039_PROG_RAM_DATA, + &data[i * ES9039_COEFF_BYTES], + ES9039_COEFF_BYTES); + if (ret) + goto out; + + ret = regmap_update_bits(priv->regmap, ES9039_PROG_RAM_CTRL, + ES9039_PROG_COEFF_WE, + ES9039_PROG_COEFF_WE); + if (ret) + goto out; + + ret = regmap_update_bits(priv->regmap, ES9039_PROG_RAM_CTRL, + ES9039_PROG_COEFF_WE, 0); + if (ret) + goto out; + } + +out: + regmap_update_bits(priv->regmap, ES9039_PROG_RAM_CTRL, + ES9039_PROG_COEFF_WE, 0); + return ret; +} + +static int es9039_fir_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *bytes, unsigned int size) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct soc_bytes_ext *be = (void *)kcontrol->private_value; + struct es9039_fir_ctl *p = container_of(be, struct es9039_fir_ctl, be); + unsigned int idx = p->stage_4x ? 1 : 0; + u8 *buf; + int ret; + + if (size != p->taps * ES9039_COEFF_BYTES) + return -EINVAL; + + buf = memdup_user(bytes, size); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + scoped_guard(mutex, &priv->lock) { + ret = es9039_fir_upload(priv, buf, p->taps, p->stage_4x); + if (!ret) { + /* + * Keep a copy. The RAM cannot be read back, so this is + * the only record of what is in it, and resume has + * nothing else to restore from. + */ + memcpy(priv->fir_shadow[idx], buf, size); + priv->fir_taps[idx] = p->taps; + } + } + + kfree(buf); + if (ret) + return ret; + + return 1; +} + +static struct es9039_fir_ctl es9039_fir2x = { + .be = { .max = ES9039_FIR2X_TAPS * ES9039_COEFF_BYTES, + .put = es9039_fir_put }, + .taps = ES9039_FIR2X_TAPS, +}; + +static struct es9039_fir_ctl es9039_fir4x = { + .be = { .max = ES9039_FIR4X_TAPS * ES9039_COEFF_BYTES, + .put = es9039_fir_put }, + .taps = ES9039_FIR4X_TAPS, + .stage_4x = true, +}; + +#define ES9039_FIR(xname, xctl) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = snd_soc_bytes_info_ext, \ + .tlv.c = snd_soc_bytes_tlv_callback, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_WRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .private_value = (unsigned long)&(xctl).be, \ +} + +/* ------------------------------------------------------- status (read-only) */ + +struct es9039_stat_ctl { + unsigned int reg; + unsigned int mask; + unsigned int max; +}; + +static int es9039_stat_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct es9039_stat_ctl *p = (void *)kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = p->max; + return 0; +} + +static int es9039_stat_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + struct es9039_stat_ctl *p = (void *)kcontrol->private_value; + unsigned int v; + int ret; + + ret = regmap_read(priv->regmap, p->reg, &v); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = + (v & p->mask) >> (ffs(p->mask) - 1); + return 0; +} + +#define ES9039_STAT(xname, xreg, xmask, xmax) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READ | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .info = es9039_stat_info, \ + .get = es9039_stat_get, \ + .private_value = (unsigned long)&(struct es9039_stat_ctl) \ + { .reg = xreg, .mask = xmask, .max = xmax }, \ +} + +/* + * Detected sample rate. + * + * When the part's own rate detector has a valid ratio, use it - it is measured + * from the incoming frame clock and is the ground truth: + * + * FS = Y * SYS_CLK / ((X + 1) * (128 >> 64FS_MODE)) + * + * with X = IDAC_DIV_AUTO and Y = 2 when IDAC_HALF_DIV_AUTO reports a + * half-integer multiple. + * + * That detector is UNAVAILABLE on any board running the DAC asynchronously - + * register 3[7] AUTO_FS_DETECT carries the note "Cannot be used in ASYNC mode". + * A board feeding a free-running oscillator and letting the ASRC absorb the + * difference is precisely that case, and it is the preferable design, so the + * detector reading 0 there is expected rather than a fault. Fall back to the + * rate the stream was opened at, which is what a front panel wants to show. + * Reports 0 only when nothing is playing and the chip has no lock either. + */ +static int es9039_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1536000; + return 0; +} + +static int es9039_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + unsigned int fsreg, sysreg, div, y, den; + int ret; + + ucontrol->value.integer.value[0] = priv->stream_rate; + + if (!priv->mclk_rate) + return 0; + + ret = regmap_read(priv->regmap, ES9039_AUTO_FS_READ, &fsreg); + if (ret) + return ret; + + if (!(fsreg & ES9039_FS_DIV_VALID)) + return 0; /* async mode: keep the stream rate set above */ + + ret = regmap_read(priv->regmap, ES9039_SYSTEM_CONFIG, &sysreg); + if (ret) + return ret; + + div = FIELD_GET(ES9039_FS_DIV_MASK, fsreg) + 1; + y = (fsreg & ES9039_FS_HALF_DIV) ? 2 : 1; + den = div * ((sysreg & ES9039_64FS_MODE) ? 64 : 128); + + ucontrol->value.integer.value[0] = + DIV_ROUND_CLOSEST(priv->mclk_rate * y, den); + return 0; +} + +static const char * const es9039_stream_texts[] = { + "PCM", "DSD", "DoP", "S/PDIF", +}; + +static int es9039_stream_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(es9039_stream_texts), + es9039_stream_texts); +} + +static int es9039_stream_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + unsigned int v; + int ret; + + ret = regmap_read(priv->regmap, ES9039_INPUT_STREAM_READ, &v); + if (ret) + return ret; + + ucontrol->value.enumerated.item[0] = + FIELD_GET(ES9039_RD_INPUT_SEL_MASK, v); + return 0; +} + +/* ----------------------------------------------------------------- controls */ + +static const DECLARE_TLV_DB_SCALE(es9039_vol_tlv, -12750, 50, 1); + +static const char * const es9039_filter_texts[] = { + "Minimum Phase", + "Linear Phase Apodizing Fast Roll-Off", + "Linear Phase Fast Roll-Off", + "Linear Phase Fast Roll-Off Low Ripple", + "Linear Phase Slow Roll-Off", + "Minimum Phase Fast Roll-Off", + "Minimum Phase Slow Roll-Off", + "Minimum Phase Slow Roll-Off Low Dispersion", +}; + +static SOC_ENUM_SINGLE_DECL(es9039_filter_enum, ES9039_FILTER_SHAPE, 0, + es9039_filter_texts); + +/* IIR_BW is a multiple of the datapath bandwidth, not a frequency. */ +static const char * const es9039_iir_texts[] = { + "Reserved", "BW x8", "BW x4", "BW x2", "BW", "BW /2", "BW /4", "BW /8", +}; + +static SOC_ENUM_SINGLE_DECL(es9039_iir_enum, ES9039_IIR_SPDIF, 0, + es9039_iir_texts); + +static const char * const es9039_nsmod_texts[] = { + "Default", "Wide Bandwidth", +}; + +static const unsigned int es9039_nsmod_values[] = { + ES9039_NSMOD_DEFAULT, ES9039_NSMOD_WIDE, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(es9039_nsmod_enum, ES9039_NSMOD, 1, + GENMASK(3, 0), es9039_nsmod_texts, + es9039_nsmod_values); + +static int es9039_dop_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + + ucontrol->value.integer.value[0] = priv->dop_auto; + return 0; +} + +/* + * Automatic DoP detection is two bits in two registers, and they are one + * setting: AUTO_INPUT_SEL lets the part choose a decoder from the data, and + * ENABLE_DOP_DECODE is what gives it a DoP decoder to choose. Enabling the + * first without the second leaves the part hunting for a marker it has no + * decoder for, whereupon it finds no valid DoP and mutes. + * + * Everything that changes the setting goes through here, so the pair can + * never be written apart. Callers hold priv->lock. + */ +static int es9039_apply_dop(struct es9039q2m_priv *priv, bool on) +{ + int ret; + + ret = regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_ENABLE_DOP_DECODE, + on ? ES9039_ENABLE_DOP_DECODE : 0); + if (ret) + return ret; + + return regmap_update_bits(priv->regmap, ES9039_INPUT_SEL, + ES9039_AUTO_INPUT_SEL, + on ? ES9039_AUTO_INPUT_SEL : 0); +} + +/* Caller holds priv->lock. */ +static int es9039_apply_mute(struct es9039q2m_priv *priv) +{ + unsigned int val = priv->mute_user; + + if (priv->mute_stream) + val = ES9039_MUTE_BOTH; + + return regmap_update_bits(priv->regmap, ES9039_DAC_MUTE, + ES9039_MUTE_BOTH, val); +} + +/* + * The control is inverted - 1 means playing - so a zero here is a mute + * request. It reports priv->mute_user rather than the register, because the + * register also carries the stream mute and the user did not ask for that. + */ +static int es9039_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + + guard(mutex)(&priv->lock); + + ucontrol->value.integer.value[0] = !(priv->mute_user & ES9039_MUTE_CH1); + ucontrol->value.integer.value[1] = !(priv->mute_user & ES9039_MUTE_CH2); + + return 0; +} + +static int es9039_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + unsigned int val = 0, old; + int ret; + + if (!ucontrol->value.integer.value[0]) + val |= ES9039_MUTE_CH1; + if (!ucontrol->value.integer.value[1]) + val |= ES9039_MUTE_CH2; + + guard(mutex)(&priv->lock); + + if (val == priv->mute_user) + return 0; + + /* + * Commit to the register first. If the write fails and the shadow has + * already moved, get() reports a mute the part is not in and the + * val == mute_user test above turns a retry with the same value into a + * no-op, leaving the control permanently wrong. + */ + old = priv->mute_user; + priv->mute_user = val; + + ret = es9039_apply_mute(priv); + if (ret) { + priv->mute_user = old; + return ret; + } + + return 1; +} + +static int es9039_dop_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + bool on = !!ucontrol->value.integer.value[0], old; + int ret; + + guard(mutex)(&priv->lock); + + if (on == priv->dop_auto) + return 0; + + /* + * hw_params() programs the decoder and INPUT_SEL from dop_auto, so + * changing it under a running stream would apply half the setting now + * and the rest at the next open. Refuse instead of half-applying. + */ + if (priv->stream_rate) + return -EBUSY; + + /* Same reasoning as es9039_mute_put(): the shadow follows the write. */ + old = priv->dop_auto; + priv->dop_auto = on; + + ret = es9039_apply_dop(priv, on); + if (ret) { + priv->dop_auto = old; + return ret; + } + + return 1; +} + +/* + * The two channel volumes live in separate registers, so a stereo change is + * two I2C writes and the channels are briefly at different levels in between. + * VOLUME_HOLD exists for exactly this: while it is set the part accepts writes + * to registers 74-75 without applying them, and clearing it applies both at + * once. Raise it, let the generic handler do the writes, drop it again. + * + * It is dropped unconditionally, including on the error path, because a stuck + * VOLUME_HOLD would silently freeze the volume control. + */ +static int es9039_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(c); + int ret; + + /* + * The hold has to cover the pair of channel writes, so it has to be + * serialised. Two callers interleaving would let the second write its + * volumes after the first had already dropped VOLUME_HOLD, which is + * exactly the torn stereo update the bit exists to prevent. + */ + guard(mutex)(&priv->lock); + + ret = regmap_update_bits(priv->regmap, ES9039_IIR_SPDIF, + ES9039_VOLUME_HOLD, ES9039_VOLUME_HOLD); + if (ret) + return ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + + regmap_update_bits(priv->regmap, ES9039_IIR_SPDIF, + ES9039_VOLUME_HOLD, 0); + + return ret; +} + +static const struct snd_kcontrol_new es9039q2m_controls[] = { + /* --- level --- */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_soc_info_volsw, + .get = snd_soc_get_volsw, + .put = es9039_vol_put, + .tlv.p = es9039_vol_tlv, + .private_value = SOC_DOUBLE_R_VALUE(ES9039_VOLUME_CH1, + ES9039_VOLUME_CH2, 0, 0, + ES9039_VOL_MAX, 1), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_soc_info_volsw, + .get = es9039_mute_get, + .put = es9039_mute_put, + .private_value = SOC_DOUBLE_VALUE(ES9039_DAC_MUTE, 0, 1, 0, 1, + 1, 0), + }, + SOC_DOUBLE("DAC Invert Switch", ES9039_DAC_INVERT, 0, 1, 1, 0), + SOC_SINGLE("Volume Ramp Up Rate", ES9039_VOL_RATE_UP, 0, 255, 0), + SOC_SINGLE("Volume Ramp Down Rate", ES9039_VOL_RATE_DOWN, 0, 255, 0), + SOC_SINGLE("Soft Ramp Time", ES9039_SOFT_RAMP, 0, + ES9039_SOFT_RAMP_MAX, 0), + + /* --- reconstruction filter --- */ + SOC_ENUM("Filter Shape", es9039_filter_enum), + SOC_ENUM("IIR Bandwidth", es9039_iir_enum), + SOC_ENUM("Modulator Bandwidth", es9039_nsmod_enum), + SOC_SINGLE("IIR Filter Bypass Switch", ES9039_DAC_PATH, 2, 1, 0), + SOC_SINGLE("FIR 2x Bypass Switch", ES9039_DAC_PATH, 0, 1, 0), + SOC_SINGLE("FIR 4x Bypass Switch", ES9039_DAC_PATH, 1, 1, 0), + SOC_SINGLE("Custom FIR Switch", ES9039_PROG_RAM_CTRL, 0, 1, 0), + ES9039_FIR("FIR 2x Coefficients", es9039_fir2x), + ES9039_FIR("FIR 4x Coefficients", es9039_fir4x), + + /* + * Not "... Volume", because these do not set a level. They are signed + * correction coefficients for the second and third harmonic: they + * cancel distortion the analogue stage adds, and turning one up does + * not make anything louder. Useful values come from measuring a given + * board's distortion on an analyser and solving for them; zero, the + * reset value, is the only honest default until someone has. + */ + /* --- distortion compensation --- */ + ES9039_S16("THD Compensation C2 CH1", ES9039_THD_C2, 0), + ES9039_S16("THD Compensation C2 CH2", ES9039_THD_C2, 16), + ES9039_S16("THD Compensation C3 CH1", ES9039_THD_C3, 0), + ES9039_S16("THD Compensation C3 CH2", ES9039_THD_C3, 16), + + /* --- automute --- */ + SOC_DOUBLE("Automute Switch", ES9039_AUTOMUTE_EN, 0, 1, 1, 0), + ES9039_WIDE("Automute Time", ES9039_AUTOMUTE_TIME, 2, + ES9039_AUTOMUTE_TIME_MASK, 2047), + ES9039_WIDE("Automute Level", ES9039_AUTOMUTE_LEVEL, 2, 0xffff, 65535), + ES9039_WIDE("Automute Off Level", ES9039_AUTOMUTE_OFF_LEVEL, 2, + 0xffff, 65535), + SOC_SINGLE("Mute Ramp To Ground Switch", ES9039_AUTOMUTE_TIME + 1, + 3, 1, 0), + SOC_SINGLE("DSD DC Automute Switch", ES9039_MONITOR_CFG, 7, 1, 1), + SOC_SINGLE("DSD Mute Pattern Switch", ES9039_MONITOR_CFG, 6, 1, 1), + SOC_SINGLE("PCM DC Automute Switch", ES9039_MONITOR_CFG, 3, 1, 1), + + /* --- stream --- */ + /* + * On by default. DoP is designed to be detected, not announced: the + * player just sends it and a DoP-aware DAC notices the marker, which + * is why a DAC that does not notice plays it as near-silence rather + * than noise. Players rely on that, and none of them can reach into + * ALSA to flip a mode first. + * + * Left switchable because automatic detection is a pattern match, and + * anyone worried about PCM material that happens to look like DoP can + * turn it off and get strictly PCM. + */ + SOC_SINGLE_BOOL_EXT("DoP Auto Detect Switch", 0, + es9039_dop_get, es9039_dop_put), + + /* --- status, read-only --- */ + ES9039_STAT("Automute Active CH1", ES9039_AUTOMUTE_READ, BIT(0), 1), + ES9039_STAT("Automute Active CH2", ES9039_AUTOMUTE_READ, BIT(1), 1), + ES9039_STAT("DoP Valid", ES9039_INPUT_STREAM_READ, + ES9039_RD_DOP_VALID, 1), + ES9039_STAT("TDM Data Valid", ES9039_INPUT_STREAM_READ, + ES9039_RD_TDM_VALID, 1), + ES9039_STAT("SPDIF Valid", ES9039_INPUT_STREAM_READ, + ES9039_RD_SPDIF_VALID, 1), + /* + * Register 235-234 is one 16-bit word and BCK_WS_FAIL_SOURCE is bit + * 7 of it, so it sits in the low byte at ES9039_IRQ_SOURCES. This + * read the byte above with the same mask, which is bit 15 - reserved + * - so the control could never have reported a fault. The named + * constant now carries the bit rather than a bare BIT(7). + * + * The flag is meaningful only with the BCK and WS monitors enabled, + * which they are at reset and the driver leaves alone. + */ + ES9039_STAT("Clock Fault", ES9039_IRQ_SOURCES, + ES9039_SRC_BCK_WS_FAIL, 1), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Detected Sample Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = es9039_rate_info, + .get = es9039_rate_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Detected Input Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = es9039_stream_info, + .get = es9039_stream_get, + }, +}; + +/* --------------------------------------------------------------------- DAPM */ + +static const struct snd_soc_dapm_widget es9039q2m_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("AOUTL"), + SND_SOC_DAPM_OUTPUT("AOUTR"), +}; + +static const struct snd_soc_dapm_route es9039q2m_routes[] = { + { "DAC", NULL, "Playback" }, + { "AOUTL", NULL, "DAC" }, + { "AOUTR", NULL, "DAC" }, +}; + +/* ---------------------------------------------------------------------- DAI */ + +/* + * MCLK limits, all from the datasheet: + * + * - 50 MHz absolute maximum ("Max MCLK Frequency", electrical specification). + * + * - Table 7 note 1: MCLK >= 128 * FS synchronous, MCLK > 130 * FS + * asynchronous. The gap is small but not academic - with a 24.576 MHz + * clock, 128 * FS puts 192 kHz exactly at the limit while 130 * FS puts the + * ceiling at 189 kHz. Note that the asynchronous bound is strict. + * + * - Register 0[6] ENABLE_64FS_MODE runs the interpolation path at 64FS and is + * "used only for PCM high sample rates such as 768kHz with a 49.152MHz or + * 384kHz with 24.576MHz clock" - both of which are MCLK = 64 * FS. Since + * 128 * 768000 is 98.304 MHz, well over the ceiling, 64FS mode is the only + * way 705.6 and 768 kHz are reachable at all. + */ +#define ES9039_MAX_MCLK 50000000 +#define ES9039_64FS_MCLK_FS 64 +#define ES9039_SYNC_MIN_MCLK_FS 128 +#define ES9039_ASYNC_MIN_MCLK_FS 130 + +/* + * MCLK/FS ratios to try on a settable clock, highest first. 256 leaves the + * part inside the window its automatic clock gearing aims for - register 5[2]: + * "MCLK will be geared down until 128FS <= SYS_CLK < 256FS" - and 64 is 64FS + * mode, which only becomes the choice when nothing larger fits under 50 MHz. + */ +static const unsigned int es9039_mclk_ratios[] = { 512, 256, 128, 64 }; + +/* + * Synchronous means the frame clock and MCLK come from the same place. That is + * true when the part generates BCK and WS itself, and it is true when the MCLK + * we were given can be set to a multiple of the sample rate. Only a fixed + * oscillator feeding a consumer-mode part is genuinely asynchronous, and only + * then does the ASRC have anything to do. + */ +static bool es9039_is_sync(struct es9039q2m_priv *priv) +{ + return priv->provider || !priv->mclk_fixed; +} + +/* + * 64FS mode is an exact MCLK/FS ratio, not a rate threshold, and it only makes + * sense synchronously: at 64 * FS an asynchronous part would sit far below its + * own 130 * FS floor, so a fixed oscillator that happens to land on 64 * FS is + * a coincidence rather than a mode. + */ +static bool es9039_is_64fs(struct es9039q2m_priv *priv, unsigned int mclk, + unsigned int rate) +{ + return es9039_is_sync(priv) && rate && + mclk == ES9039_64FS_MCLK_FS * rate; +} + +/* The lowest MCLK this rate may run at outside 64FS mode. */ +static unsigned int es9039_min_mclk(struct es9039q2m_priv *priv, + unsigned int rate) +{ + if (es9039_is_sync(priv)) + return ES9039_SYNC_MIN_MCLK_FS * rate; + + /* Asynchronous is strictly greater than 130 * FS, not equal to it. */ + return ES9039_ASYNC_MIN_MCLK_FS * rate + 1; +} + +static int es9039q2m_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int max_rate; + + /* + * A clock whose rate can be set imposes no ceiling here: hw_params() + * raises MCLK to suit the rate, and fails cleanly if it cannot. + * Constraining from the rate MCLK merely happens to be idling at would + * reject rates the board can carry perfectly well. + */ + if (!priv->mclk_rate || !priv->mclk_fixed) + return 0; + + if (!es9039_is_sync(priv)) { + max_rate = priv->mclk_rate / ES9039_ASYNC_MIN_MCLK_FS; + } else if (priv->mclk_rate % ES9039_64FS_MCLK_FS == 0) { + /* + * 64FS mode reaches exactly one rate above the ordinary + * ceiling - MCLK / 64 - so admit it here and leave + * hw_params() to reject anything in between, where neither + * 64 * FS nor 128 * FS is satisfied. + */ + max_rate = priv->mclk_rate / ES9039_64FS_MCLK_FS; + } else { + max_rate = priv->mclk_rate / ES9039_SYNC_MIN_MCLK_FS; + } + + return snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 8000, max_rate); +} + +/* + * Put the clocking where it needs to be for this rate, and tell the part which + * of its two timing worlds it is living in. + * + * With a settable MCLK the useful thing is to pull it to an exact multiple of + * the sample rate: the ASRC then has nothing to correct, which is the better + * arrangement whenever the board can manage it. With a fixed oscillator there + * is nothing to pull, and the ASRC earns its keep. + */ +static int es9039_setup_clocking(struct es9039q2m_priv *priv, + struct snd_soc_dai *dai, unsigned int rate) +{ + unsigned int ratio, min_mclk, div, i, target = 0; + bool sixtyfour; + int ret; + + if (!priv->mclk_rate) + return 0; + + ratio = priv->bclk_ratio ? priv->bclk_ratio : 64; + + if (!priv->mclk_fixed) { + /* + * Take the highest ratio that still fits under the 50 MHz + * ceiling rather than always asking for 256 * FS, which would + * put 384 kHz at 98.304 MHz and 768 kHz at 196.608 MHz. A + * settable clock is synchronous by definition here - see + * es9039_is_sync() - so only the synchronous floors apply. + */ + for (i = 0; i < ARRAY_SIZE(es9039_mclk_ratios); i++) { + unsigned int mult = es9039_mclk_ratios[i]; + + if (mult * rate > ES9039_MAX_MCLK) + continue; + if (mult < ES9039_SYNC_MIN_MCLK_FS && + mult != ES9039_64FS_MCLK_FS) + continue; + /* + * Master mode divides MCLK down to BCK by a whole + * number, so a ratio that is not a multiple of the + * frame size cannot produce the bit clock. + */ + if (priv->provider && mult % ratio) + continue; + + target = mult * rate; + break; + } + + if (!target) { + dev_err(dai->dev, + "no mclk ratio for %u Hz within %u Hz\n", + rate, ES9039_MAX_MCLK); + return -EINVAL; + } + + ret = clk_set_rate(priv->mclk, target); + if (ret) + return ret; + + /* Take what the clock settled on, not what was asked for. */ + priv->mclk_rate = clk_get_rate(priv->mclk); + } + + if (priv->mclk_rate > ES9039_MAX_MCLK) { + dev_err(dai->dev, "mclk %u Hz is above the %u Hz maximum\n", + priv->mclk_rate, ES9039_MAX_MCLK); + return -EINVAL; + } + + sixtyfour = es9039_is_64fs(priv, priv->mclk_rate, rate); + min_mclk = es9039_min_mclk(priv, rate); + + if (!sixtyfour && priv->mclk_rate < min_mclk) { + dev_err(dai->dev, + "mclk %u Hz is below the %u Hz %u Hz needs in this mode\n", + priv->mclk_rate, min_mclk, rate); + return -EINVAL; + } + + /* + * Register 0[6]. Decided from the MCLK actually in use rather than from + * the sample rate alone, because the part cares about the ratio: 384 kHz + * from 24.576 MHz needs 64FS mode exactly as 768 kHz from 49.152 MHz + * does. The mode also forces a minimum phase filter regardless of + * FILTER_SHAPE. + */ + ret = regmap_update_bits(priv->regmap, ES9039_SYSTEM_CONFIG, + ES9039_64FS_MODE, + sixtyfour ? ES9039_64FS_MODE : 0); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_SYNC_MODE, + es9039_is_sync(priv) ? ES9039_SYNC_MODE : 0); + if (ret) + return ret; + + if (!priv->provider) + return 0; + + /* + * Register 4: BCK = MCLK / (MASTER_BCK_DIV + 1), and WS follows from + * the frame length. An MCLK that is not a whole multiple of the bit + * clock cannot produce the requested rate at all, so say so rather + * than emitting something close. + */ + if (priv->mclk_rate % (ratio * rate)) { + dev_err(dai->dev, + "mclk %u Hz cannot produce %u * %u Hz bit clock\n", + priv->mclk_rate, ratio, rate); + return -EINVAL; + } + + div = priv->mclk_rate / (ratio * rate); + if (div < 1 || div > 256) { + dev_err(dai->dev, "master bck divider %u out of range\n", div); + return -EINVAL; + } + + return regmap_write(priv->regmap, ES9039_CLOCK_CONFIG, div - 1); +} + +static int es9039q2m_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int cfg1 = 0, enc = 0; + int ret; + + /* + * Both directions are useful and which is right is a board decision, + * not a driver one. Driving BCK and WS from a good MCLK is the + * conventional arrangement and keeps everything synchronous; taking + * them from a consumer-mode host and letting the ASRC absorb the + * mismatch is what a board with a fixed oscillator wants. The part can + * do either, so the driver offers either. + */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + priv->provider = false; + break; + case SND_SOC_DAIFMT_CBP_CFP: + if (!priv->mclk) + return -EINVAL; + priv->provider = true; + break; + default: + /* The part cannot split them: it drives both or neither. */ + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, ES9039_INPUT_SEL, + ES9039_PCM_MASTER_MODE, + priv->provider ? ES9039_PCM_MASTER_MODE : 0); + if (ret) + return ret; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + cfg1 |= ES9039_TDM_LJ_MODE; + break; + default: + /* Register 60 offers I2S or LJ. There is no RJ mode. */ + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + enc |= ES9039_BCK_INV; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(priv->regmap, ES9039_TDM_CONFIG1, + ES9039_TDM_LJ_MODE, cfg1); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_MASTER_ENC, + ES9039_BCK_INV, enc); + if (ret) + return ret; + + priv->fmt = fmt; + return 0; +} + +/* + * TDM_BIT_WIDTH describes the SLOT width on the wire, not the sample size. Those + * are routinely different: 16-bit samples are usually carried left-justified in + * 32-bit slots. Getting this wrong misaligns the channel boundaries and the + * result is one channel, or noise. + * + * ASoC does not hand the codec the bit clock ratio unless a machine driver sets + * it, so take it when offered and otherwise assume 32-bit slots - by far the + * most common arrangement, and what the RK3588 I2S does unconditionally + * (rockchip_i2s.c sets bclk_ratio = 64 at probe and never varies it with + * format). + */ +static int es9039q2m_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + + priv->bclk_ratio = ratio; + return 0; +} + +static int es9039q2m_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + unsigned int input_sel, decode, width, slot_bits, isel; + bool auto_sel; + int ret; + + /* + * Held for the whole call: dop_auto decides both INPUT_SEL and + * SYS_MODE below, and a kcontrol write landing between the two would + * leave automatic detection enabled with no DoP decoder behind it. + */ + guard(mutex)(&priv->lock); + + priv->stream_rate = params_rate(params); + + ret = es9039_setup_clocking(priv, dai, priv->stream_rate); + if (ret) + return ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + /* + * Forced, not auto-detected: the datasheet requires DSD data on + * DATA1 and DATA2 for AUTO_INPUT_SEL to identify it, which a + * two-channel I2S link does not provide. + */ + input_sel = ES9039_INPUT_DSD; + decode = ES9039_ENABLE_DSD_DECODE; + auto_sel = false; + break; + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S32_LE: + /* + * DoP arrives inside ordinary PCM frames and is indistinguishable + * from PCM until the part finds the marker, so both decoders run + * and AUTO_INPUT_SEL picks between them. INPUT_SEL is programmed + * anyway as the fallback the part uses when auto-detection is + * switched off. + */ + input_sel = ES9039_INPUT_PCM; + decode = ES9039_ENABLE_TDM_DECODE | + (priv->dop_auto ? ES9039_ENABLE_DOP_DECODE : 0); + auto_sel = priv->dop_auto; + break; + default: + return -EINVAL; + } + + slot_bits = priv->bclk_ratio ? + priv->bclk_ratio / params_channels(params) : 32; + + switch (slot_bits) { + case 16: + width = ES9039_WIDTH_16; + break; + case 24: + width = ES9039_WIDTH_24; + break; + case 32: + width = ES9039_WIDTH_32; + break; + default: + dev_err(dai->dev, "unsupported slot width %u\n", slot_bits); + return -EINVAL; + } + + /* + * AUTO_INPUT_SEL belongs in the value as well as the mask. It was in + * the mask alone, so every hw_params quietly cleared it and undid what + * the component probe had set - which is why enabling auto-detection + * by hand mid-stream worked while enabling it in probe() did not. + * INPUT_SEL is still programmed underneath: it is what the part falls + * back to when auto-detection is switched off. + */ + isel = FIELD_PREP(ES9039_INPUT_SEL_MASK, input_sel); + if (auto_sel) + isel |= ES9039_AUTO_INPUT_SEL; + + ret = regmap_update_bits(priv->regmap, ES9039_INPUT_SEL, + ES9039_AUTO_INPUT_SEL | ES9039_INPUT_SEL_MASK, + isel); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_TDM_CONFIG2, + ES9039_TDM_BIT_WIDTH_MASK, + FIELD_PREP(ES9039_TDM_BIT_WIDTH_MASK, width)); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_TDM_CH_NUM, + ES9039_TDM_CH_NUM_MASK, + params_channels(params) - 1); + if (ret) + return ret; + + /* Switch the right decoder on for this stream, and the others off. */ + return regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_DECODE_MASK, decode); +} + +static int es9039q2m_mute_stream(struct snd_soc_dai *dai, int mute, int dir) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + bool old; + int ret; + + guard(mutex)(&priv->lock); + + old = priv->mute_stream; + priv->mute_stream = mute; + + ret = es9039_apply_mute(priv); + if (ret) + priv->mute_stream = old; + + return ret; +} + +/* + * stream_rate doubles as "a stream is open", which es9039_dop_put() needs in + * order to refuse a change it could only half-apply. + */ +static void es9039q2m_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(dai->component); + + guard(mutex)(&priv->lock); + + priv->stream_rate = 0; +} + +static const struct snd_soc_dai_ops es9039q2m_dai_ops = { + .startup = es9039q2m_startup, + .shutdown = es9039q2m_shutdown, + .set_fmt = es9039q2m_set_fmt, + .set_bclk_ratio = es9039q2m_set_bclk_ratio, + .hw_params = es9039q2m_hw_params, + .mute_stream = es9039q2m_mute_stream, + .no_capture_mute = 1, +}; + +#define ES9039_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_DSD_U8 | \ + SNDRV_PCM_FMTBIT_DSD_U16_LE | \ + SNDRV_PCM_FMTBIT_DSD_U32_LE) + +static struct snd_soc_dai_driver es9039q2m_dai = { + .name = "es9039q2m-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_768000, + .formats = ES9039_FORMATS, + }, + .ops = &es9039q2m_dai_ops, +}; + +/* ---------------------------------------------------------------- component */ + +static int es9039q2m_component_probe(struct snd_soc_component *component) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(component); + int ret; + + /* + * Come up muted. The volume registers default to 0 dB, and an unmuted + * DAC arriving into an already-powered analogue stage is how you get a + * thump. ASoC unmutes via mute_stream() when a stream starts. + */ + priv->mute_user = 0; + priv->mute_stream = false; + ret = regmap_update_bits(priv->regmap, ES9039_DAC_MUTE, + ES9039_MUTE_BOTH, ES9039_MUTE_BOTH); + if (ret) + return ret; + + /* + * Enable the datapath. SYSTEM_CONFIG[1] is clear at reset, so in + * software mode nothing turns the DAC on at power-up: the analogue + * output stays dead until this bit is set. + * + * After the mute above and never before it. Bringing the datapath up + * into an unmuted part whose volume registers still sit at their 0 dB + * reset value is exactly the thump the mute is there to prevent. + * + * Note when testing that a warm reboot does not reset this part - + * registers and coefficient RAM both survive one, and only removing + * power clears them - so the previous boot's value can hide whether + * this write happened at all. + */ + ret = regmap_update_bits(priv->regmap, ES9039_SYSTEM_CONFIG, + ES9039_DAC_MODE, ES9039_DAC_MODE); + if (ret) + return ret; + + /* + * VOLUME_HOLD set means "do not apply volume register writes", so its + * resting state has to be clear or the volume control would appear + * dead. es9039_vol_put() raises it around the pair of writes and drops + * it again, which is how both channels are made to move together. + */ + ret = regmap_update_bits(priv->regmap, ES9039_IIR_SPDIF, + ES9039_VOLUME_HOLD, 0); + if (ret) + return ret; + + /* + * Let the part identify DoP for itself. Register 57[0] AUTO_INPUT_SEL + * makes it choose between PCM and DoP from the data, which is the only + * way DoP can work in practice: players send DoP-encoded PCM and expect + * the DAC to notice, and none of them can flip an ALSA control first. + * Without this the part is told "PCM", never looks for the marker, and + * renders a DoP stream as the near-silent hiss the DoP marker design + * deliberately degrades to. Verified on hardware by enabling this + * mid-stream and watching reg 245 flip from PCM to DoP with DOP_VALID + * set. + * + * The datasheet's "data must be provided on the DATA2 pin" applies to + * identifying DSD, whose two channels arrive on separate data lines. + * DoP is ordinary stereo I2S on one line and detects correctly without + * it, which the same measurement establishes. + */ + priv->dop_auto = true; + scoped_guard(mutex, &priv->lock) + ret = es9039_apply_dop(priv, true); + if (ret) + return ret; + + /* + * Board defaults, applied once at probe so the part is deterministic + * from cold instead of inheriting whatever its reset value happens to + * be. Both stay user-settable through their kcontrols; these are + * defaults, not policy. + * + * Reconstruction filter: linear phase apodizing fast roll-off. It keeps + * the sharp cut and flat passband of the plain fast linear-phase filter + * while suppressing pre-ringing, and an apodizing response also + * suppresses pre-ringing already baked into the source material by the + * recording chain - which none of the other seven addresses. The cost + * is a little stopband rejection right at the band edge, well above + * where it can matter. Chosen this way because a blind A/B listening + * test found no audible difference between any of the eight, + * so the tie is broken on theory rather than on preference. + * + * Modulator: wide bandwidth, which is ESS's own recommendation. It + * moves the modulator's noise further out of band and improves + * linearity at high frequencies. + */ + ret = regmap_update_bits(priv->regmap, ES9039_FILTER_SHAPE, + ES9039_FILTER_SHAPE_MASK, + ES9039_FILTER_APODIZING); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap, ES9039_NSMOD, + ES9039_NSMOD_WIDE_BW_MASK, + FIELD_PREP(ES9039_NSMOD_WIDE_BW_MASK, + ES9039_NSMOD_WIDE)); + if (ret) + return ret; + + /* Let the part work out the incoming rate; the ASRC does the rest. */ + return regmap_update_bits(priv->regmap, ES9039_AUTO_FS_DETECT, + ES9039_AUTO_FS_DETECT_EN, + ES9039_AUTO_FS_DETECT_EN); +} + +/* + * System suspend. + * + * Where the part is clocked from the SoC, system suspend stops its clock as a + * side effect and there is nothing for a driver to do. Where the board feeds + * it a free-running oscillator instead - which the ASRC in front of the DAC + * makes an attractive design, since MCLK then need not track BCLK or LRCK - + * nothing in the system can gate that clock, and the part stays fully clocked + * for the whole of suspend. + * + * Clearing ENABLE_DAC_CLK gates the part's internal clock tree, which is the + * only part of its consumption software can reach. The analogue supplies are + * the board's business, and not every board is able to switch them. + * + * The write deliberately bypasses the cache. The cached value must keep + * ENABLE_DAC_CLK set, so that regcache_sync() on resume restores whatever + * state userspace last left the part in without this code having to remember + * anything itself. + */ +static int es9039q2m_suspend(struct snd_soc_component *component) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(component); + int ret; + + guard(mutex)(&priv->lock); + + regcache_cache_bypass(priv->regmap, true); + ret = regmap_update_bits(priv->regmap, ES9039_SYS_MODE, + ES9039_ENABLE_DAC_CLK, 0); + regcache_cache_bypass(priv->regmap, false); + + if (ret) + dev_warn(component->dev, + "could not gate the DAC clock for suspend: %d\n", ret); + + regcache_mark_dirty(priv->regmap); + regcache_cache_only(priv->regmap, true); + + return 0; +} + +static int es9039q2m_resume(struct snd_soc_component *component) +{ + struct es9039q2m_priv *priv = snd_soc_component_get_drvdata(component); + unsigned int i; + int ret; + + guard(mutex)(&priv->lock); + + regcache_cache_only(priv->regmap, false); + + /* + * Mute the part before anything else, and write it straight to the + * hardware rather than through the cache. + * + * Where the board kept the supplies up this is a no-op - the part still + * holds the mute state it went to sleep with. Where the board removed + * them it is not: the part comes back at reset defaults, which are + * DAC_MODE clear, both channels UNMUTED, and both volume registers at + * 0x00, their 0 dB setting. regcache_sync() walks registers in ascending + * address order, so it would set DAC_MODE in register 0 - lighting the + * datapath up unmuted at full scale - and only restore the real mute in + * register 86, long after. That is the thump the probe path mutes to + * avoid, arriving by the other door. + * + * The cache is bypassed so the cached mute state is untouched; the sync + * below puts it back, after the volume registers at 74 and 75. + */ + regcache_cache_bypass(priv->regmap, true); + ret = regmap_update_bits(priv->regmap, ES9039_DAC_MUTE, + ES9039_MUTE_BOTH, ES9039_MUTE_BOTH); + regcache_cache_bypass(priv->regmap, false); + + if (ret) + dev_warn(component->dev, + "could not mute before resync: %d\n", ret); + + ret = regcache_sync(priv->regmap); + if (ret) + dev_err(component->dev, + "failed to restore registers on resume: %d\n", ret); + + /* + * Re-upload the programmable FIR RAM. regcache_sync() cannot restore + * it - it is write-only, so there is no cache of it to sync - and a + * board that removed the part's supplies comes back with that RAM + * undefined while the sync has just restored the filter SELECTION from + * the cache. Selecting the programmable filter would then point the + * interpolator at whatever the RAM powered up holding. After the sync, + * so the selection is already in place. Stages never uploaded have + * fir_taps == 0 and keep the part's own defaults. + */ + for (i = 0; i < ES9039_FIR_STAGES; i++) { + int err; + + if (!priv->fir_taps[i]) + continue; + + err = es9039_fir_upload(priv, priv->fir_shadow[i], + priv->fir_taps[i], i == 1); + if (err) + dev_err(component->dev, + "failed to restore FIR stage %u on resume: %d\n", + i, err); + } + + return ret; +} + +static const struct snd_soc_component_driver es9039q2m_component = { + .probe = es9039q2m_component_probe, + .suspend = es9039q2m_suspend, + .resume = es9039q2m_resume, + .controls = es9039q2m_controls, + .num_controls = ARRAY_SIZE(es9039q2m_controls), + .dapm_widgets = es9039q2m_widgets, + .num_dapm_widgets = ARRAY_SIZE(es9039q2m_widgets), + .dapm_routes = es9039q2m_routes, + .num_dapm_routes = ARRAY_SIZE(es9039q2m_routes), + .idle_bias_on = 1, + .endianness = 1, +}; + +/* ------------------------------------------------------------------- power */ + +/* + * The part has four supply inputs and the datasheet is specific about the + * order: figure 22 brings AVDD up first, VCCA about 200 us later, then the two + * output-stage references, and only then is the part enabled. Power-down is + * the exact reverse. + * + * That ordering is the whole reason these are enabled one at a time rather + * than with regulator_bulk_enable(), which makes no ordering guarantee at all. + * The bulk API is still used to GET them, where order is irrelevant. On a + * board that ties all four to one always-on rail the sequence costs nothing; + * on one that switches them separately, bringing an output stage up before its + * reference is precisely what the sequence exists to prevent. + * + * The datasheet gives no settling time between the last supply and the first + * register access, so none is invented here. + */ +static const char * const es9039_supply_names[ES9039_NUM_SUPPLIES] = { + "avdd", "vcca", "avcc-dac1", "avcc-dac2", +}; + +static int es9039_power_on(struct es9039q2m_priv *priv) +{ + int i, ret; + + for (i = 0; i < ES9039_NUM_SUPPLIES; i++) { + ret = regulator_enable(priv->supplies[i].consumer); + if (ret) + goto err; + + /* AVDD leads VCCA. It is the only interval the part specifies. */ + if (i == 0) + fsleep(200); + } + + return 0; + +err: + while (--i >= 0) + regulator_disable(priv->supplies[i].consumer); + + return ret; +} + +static void es9039_power_off(void *data) +{ + struct es9039q2m_priv *priv = data; + int i; + + for (i = ES9039_NUM_SUPPLIES - 1; i >= 0; i--) + regulator_disable(priv->supplies[i].consumer); +} + +/* --------------------------------------------------------------------- I2C */ + +static int es9039q2m_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct es9039q2m_priv *priv; + unsigned int id; + int i, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = devm_regmap_init_i2c(i2c, &es9039q2m_regmap); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "failed to init regmap\n"); + + for (i = 0; i < ES9039_NUM_SUPPLIES; i++) + priv->supplies[i].supply = es9039_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ES9039_NUM_SUPPLIES, priv->supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get the supplies\n"); + + ret = es9039_power_on(priv); + if (ret) + return dev_err_probe(dev, ret, "failed to enable the supplies\n"); + + ret = devm_add_action_or_reset(dev, es9039_power_off, priv); + if (ret) + return ret; + + priv->mclk = devm_clk_get_optional_enabled(dev, "mclk"); + if (IS_ERR(priv->mclk)) + return dev_err_probe(dev, PTR_ERR(priv->mclk), + "failed to get mclk\n"); + + if (priv->mclk) { + priv->mclk_rate = clk_get_rate(priv->mclk); + if (priv->mclk_rate > 50000000) + return dev_err_probe(dev, -EINVAL, + "mclk %u Hz exceeds the 50 MHz maximum\n", + priv->mclk_rate); + + /* + * A fixed-rate clock answers every rounding question with the + * one rate it has. That is the board saying "this is an + * oscillator, use the ASRC"; anything else can be pulled to + * suit the sample rate and run synchronously. + */ + priv->mclk_fixed = + clk_round_rate(priv->mclk, priv->mclk_rate / 2) == + priv->mclk_rate; + } + + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; + + i2c_set_clientdata(i2c, priv); + + ret = regmap_read(priv->regmap, ES9039_CHIP_ID, &id); + if (ret) + return dev_err_probe(dev, ret, "no response at 0x%02x\n", + i2c->addr); + + if (id != ES9039_CHIP_ID_ES9039Q2M) + return dev_err_probe(dev, -ENODEV, + "unexpected chip id 0x%02x, want 0x%02x\n", + id, ES9039_CHIP_ID_ES9039Q2M); + + dev_info(dev, "ES9039Q2M at 0x%02x, mclk %u Hz\n", + i2c->addr, priv->mclk_rate); + + return devm_snd_soc_register_component(dev, &es9039q2m_component, + &es9039q2m_dai, 1); +} + +static const struct of_device_id es9039q2m_of_match[] = { + { .compatible = "esstech,es9039q2m" }, + { } +}; +MODULE_DEVICE_TABLE(of, es9039q2m_of_match); + +static const struct i2c_device_id es9039q2m_i2c_id[] = { + { "es9039q2m" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, es9039q2m_i2c_id); + +static struct i2c_driver es9039q2m_i2c_driver = { + .driver = { + .name = "es9039q2m", + .of_match_table = es9039q2m_of_match, + }, + .probe = es9039q2m_i2c_probe, + .id_table = es9039q2m_i2c_id, +}; +module_i2c_driver(es9039q2m_i2c_driver); + +MODULE_DESCRIPTION("ASoC ES9039Q2M driver"); +MODULE_AUTHOR("Karl Asseily "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/es9356.c b/sound/soc/codecs/es9356.c index 80db0f2f4aef..b8b3ffc13fd5 100644 --- a/sound/soc/codecs/es9356.c +++ b/sound/soc/codecs/es9356.c @@ -26,6 +26,20 @@ #include #include "es9356.h" +/* + * The DAI stream capabilities were never filled in. snd_soc_runtime_calc_hw() + * intersects the capabilities of the CPU and codec DAIs to derive hw->rates + * and hw->formats, so an empty mask on the codec side leaves the PCM with no + * usable configuration and soc_hw_sanity_check() fails every open with -EINVAL + * ("No matching rates"). es9356_sdw_pcm_hw_params() already programs 16 kHz, + * 44.1 kHz, 48 kHz and 96 kHz, so advertise exactly that set, with the formats + * used by the other SDCA codecs in the tree. + */ +#define ES9356_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +#define ES9356_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + struct es9356_sdw_priv { struct sdw_slave *slave; struct device *dev; @@ -617,6 +631,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_DMIC, .capture = { .stream_name = "DP1 Capture", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, @@ -627,6 +643,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_JACK_IN, .capture = { .stream_name = "DP2 Capture", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, @@ -637,6 +655,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_AMP, .playback = { .stream_name = "DP3 Playback", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, @@ -647,6 +667,8 @@ static struct snd_soc_dai_driver es9356_sdw_dai[] = { .id = ES9356_JACK_OUT, .playback = { .stream_name = "DP4 Playback", + .rates = ES9356_RATES, + .formats = ES9356_FORMATS, .channels_min = 1, .channels_max = 2, }, diff --git a/sound/soc/codecs/fs210x.c b/sound/soc/codecs/fs210x.c index 510cbeea5b62..316f3fe80d93 100644 --- a/sound/soc/codecs/fs210x.c +++ b/sound/soc/codecs/fs210x.c @@ -610,8 +610,9 @@ static int fs210x_init_chip(struct fs210x_priv *fs210x) if (!ret) { regcache_cache_only(fs210x->regmap, false); regcache_mark_dirty(fs210x->regmap); - regcache_sync(fs210x->regmap); - fs210x->is_inited = true; + ret = regcache_sync(fs210x->regmap); + if (!ret) + fs210x->is_inited = true; } return ret; diff --git a/sound/soc/codecs/hda.c b/sound/soc/codecs/hda.c index 237b0b060457..c481ef2dd789 100644 --- a/sound/soc/codecs/hda.c +++ b/sound/soc/codecs/hda.c @@ -85,12 +85,11 @@ static int hda_codec_register_dais(struct hda_codec *codec, struct snd_soc_compo struct snd_soc_dai_driver *drvs = NULL; struct snd_soc_dapm_context *dapm; struct hda_pcm *pcm; - int ret, pcm_count = 0; + int ret, pcm_count; - if (list_empty(&codec->pcm_list_head)) + pcm_count = list_count_nodes(&codec->pcm_list_head); + if (!pcm_count) return -EINVAL; - list_for_each_entry(pcm, &codec->pcm_list_head, list) - pcm_count++; ret = hda_codec_create_dais(codec, pcm_count, &drvs); if (ret < 0) @@ -218,6 +217,9 @@ static int hda_codec_probe(struct snd_soc_component *component) goto err; } + /* unsol events are still blocked until registered */ + codec->core.unsol_disabled = false; + ret = driver->ops->probe(codec, codec->preset); if (ret < 0) { dev_err(&hdev->dev, "codec init failed: %d\n", ret); @@ -276,6 +278,10 @@ static void hda_codec_remove(struct snd_soc_component *component) /* Don't allow any more runtime suspends */ pm_runtime_forbid(&hdev->dev); + /* stop asynchronous jack handling before freeing driver resources */ + snd_hdac_device_disable_unsol(hdev); + cancel_delayed_work_sync(&codec->jackpoll_work); + hda_codec_unregister_dais(codec, component); if (driver->ops->remove) diff --git a/sound/soc/codecs/hdac_hda.c b/sound/soc/codecs/hdac_hda.c index 1c06fdbf0e71..7414be08d3d5 100644 --- a/sound/soc/codecs/hdac_hda.c +++ b/sound/soc/codecs/hdac_hda.c @@ -520,7 +520,7 @@ static int hdac_hda_codec_probe(struct snd_soc_component *component) /* * hdac_device core already sets the state to active and calls * get_noresume. So enable runtime and set the device to suspend. - * pm_runtime_enable is also called during codec registeration + * pm_runtime_enable is also called during codec registration */ pm_runtime_put(&hdev->dev); pm_runtime_suspend(&hdev->dev); diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index d9e6fa6d3e99..b3f677e7ddc9 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -1753,7 +1753,7 @@ static int hdmi_codec_resume(struct device *dev) /* * As the ELD notify callback request is not entertained while the * device is in suspend state. Need to manually check detection of - * all pins here. pin capablity change is not support, so use the + * all pins here. pin capability change is not support, so use the * already set pin caps. * * NOTE: this is safe to call even if the codec doesn't actually resume. diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 7aa50c5bd3df..0e1e532dfc63 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -475,8 +475,12 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, if (ret) return ret; - snd_parse_eld(dai->dev, &hcp->eld_parsed, - hcp->eld, sizeof(hcp->eld)); + /* An empty ELD means no sink is connected; nothing to parse. */ + if (hcp->eld[0]) + snd_parse_eld(dai->dev, &hcp->eld_parsed, + hcp->eld, sizeof(hcp->eld)); + else + memset(&hcp->eld_parsed, 0, sizeof(hcp->eld_parsed)); ret = snd_pcm_hw_constraint_eld(substream->runtime, hcp->eld); if (ret) diff --git a/sound/soc/codecs/inno_rk3036.c b/sound/soc/codecs/inno_rk3036.c index fdd19f8e8864..aaf03f228150 100644 --- a/sound/soc/codecs/inno_rk3036.c +++ b/sound/soc/codecs/inno_rk3036.c @@ -309,9 +309,21 @@ static int rk3036_codec_dai_hw_params(struct snd_pcm_substream *substream, SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 rk3036_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rk3036_codec_dai_ops = { .set_fmt = rk3036_codec_dai_set_fmt, .hw_params = rk3036_codec_dai_hw_params, + .auto_selectable_formats = &rk3036_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rk3036_codec_dai_driver[] = { diff --git a/sound/soc/codecs/isabelle.c b/sound/soc/codecs/isabelle.c index fa1c23dda4eb..7e8dde79b11a 100644 --- a/sound/soc/codecs/isabelle.c +++ b/sound/soc/codecs/isabelle.c @@ -1011,10 +1011,17 @@ static int isabelle_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) #define ISABELLE_FORMATS (SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 isabelle_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_PDM; + static const struct snd_soc_dai_ops isabelle_hs_dai_ops = { .hw_params = isabelle_hw_params, .set_fmt = isabelle_set_dai_fmt, .mute_stream = isabelle_hs_mute, + .auto_selectable_formats = &isabelle_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1022,6 +1029,8 @@ static const struct snd_soc_dai_ops isabelle_hf_dai_ops = { .hw_params = isabelle_hw_params, .set_fmt = isabelle_set_dai_fmt, .mute_stream = isabelle_hf_mute, + .auto_selectable_formats = &isabelle_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1029,12 +1038,16 @@ static const struct snd_soc_dai_ops isabelle_line_dai_ops = { .hw_params = isabelle_hw_params, .set_fmt = isabelle_set_dai_fmt, .mute_stream = isabelle_line_mute, + .auto_selectable_formats = &isabelle_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; static const struct snd_soc_dai_ops isabelle_ul_dai_ops = { .hw_params = isabelle_hw_params, .set_fmt = isabelle_set_dai_fmt, + .auto_selectable_formats = &isabelle_selectable_formats, + .num_auto_selectable_formats = 1, }; /* ISABELLE dai structure */ diff --git a/sound/soc/codecs/lm49453.c b/sound/soc/codecs/lm49453.c index 6e2e292d143b..991555bec712 100644 --- a/sound/soc/codecs/lm49453.c +++ b/sound/soc/codecs/lm49453.c @@ -1283,11 +1283,18 @@ static int lm49453_set_bias_level(struct snd_soc_component *component, #define LM49453_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 lm49453_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops lm49453_headset_dai_ops = { .hw_params = lm49453_hw_params, .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_hp_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1296,6 +1303,8 @@ static const struct snd_soc_dai_ops lm49453_speaker_dai_ops = { .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_ls_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1304,6 +1313,8 @@ static const struct snd_soc_dai_ops lm49453_haptic_dai_ops = { .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_ha_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1312,6 +1323,8 @@ static const struct snd_soc_dai_ops lm49453_ep_dai_ops = { .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_ep_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1320,6 +1333,8 @@ static const struct snd_soc_dai_ops lm49453_lineout_dai_ops = { .set_sysclk = lm49453_set_dai_sysclk, .set_fmt = lm49453_set_dai_fmt, .mute_stream = lm49453_lo_mute, + .auto_selectable_formats = &lm49453_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c index 282a6aaa9986..c41eb8de6160 100644 --- a/sound/soc/codecs/lpass-rx-macro.c +++ b/sound/soc/codecs/lpass-rx-macro.c @@ -1673,7 +1673,7 @@ static const struct regmap_config rx_regmap_config = { .reg_bits = 16, .val_bits = 32, /* 8 but with 32 bit read/write */ .reg_stride = 4, - .cache_type = REGCACHE_FLAT, + .cache_type = REGCACHE_MAPLE, .max_register = RX_MAX_OFFSET, .writeable_reg = rx_is_writeable_register, .volatile_reg = rx_is_volatile_register, @@ -3732,8 +3732,8 @@ static int rx_macro_register_mclk_output(struct rx_macro *rx) struct device *dev = rx->dev; const char *parent_clk_name = NULL; const char *clk_name = "lpass-rx-mclk"; + struct clk_init_data init = {}; struct clk_hw *hw; - struct clk_init_data init; int ret; if (rx->npl) @@ -3852,6 +3852,8 @@ static int rx_macro_probe(struct platform_device *pdev) return -EINVAL; } + regcache_sort_defaults(reg_defaults, def_count); + struct regmap_config *reg_config __free(kfree) = kmemdup(&rx_regmap_config, sizeof(*reg_config), GFP_KERNEL); diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c index a8e83eb60ba2..6deaaabe43af 100644 --- a/sound/soc/codecs/lpass-tx-macro.c +++ b/sound/soc/codecs/lpass-tx-macro.c @@ -602,7 +602,7 @@ static const struct regmap_config tx_regmap_config = { .reg_bits = 16, .val_bits = 32, .reg_stride = 4, - .cache_type = REGCACHE_FLAT, + .cache_type = REGCACHE_MAPLE, .max_register = TX_MAX_OFFSET, .reg_defaults = tx_defaults, .num_reg_defaults = ARRAY_SIZE(tx_defaults), @@ -2210,8 +2210,8 @@ static int tx_macro_register_mclk_output(struct tx_macro *tx) struct device *dev = tx->dev; const char *parent_clk_name = NULL; const char *clk_name = "lpass-tx-mclk"; + struct clk_init_data init = {}; struct clk_hw *hw; - struct clk_init_data init; int ret; if (tx->npl) diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c index dbc5795b9273..03d6434a4a12 100644 --- a/sound/soc/codecs/lpass-va-macro.c +++ b/sound/soc/codecs/lpass-va-macro.c @@ -454,7 +454,7 @@ static const struct regmap_config va_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .cache_type = REGCACHE_FLAT, + .cache_type = REGCACHE_MAPLE, .reg_defaults = va_defaults, .num_reg_defaults = ARRAY_SIZE(va_defaults), .max_register = VA_MAX_OFFSET, @@ -1414,7 +1414,7 @@ static int va_macro_register_fsgen_output(struct va_macro *va) struct device_node *np = dev->of_node; const char *parent_clk_name; const char *clk_name = "fsgen"; - struct clk_init_data init; + struct clk_init_data init = {}; int ret; if (va->has_npl_clk) diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index 38e37908baf9..899c8bee4645 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -1109,7 +1109,7 @@ static const struct regmap_config wsa_regmap_config = { .reg_bits = 16, .val_bits = 32, /* 8 but with 32 bit read/write */ .reg_stride = 4, - .cache_type = REGCACHE_FLAT, + .cache_type = REGCACHE_MAPLE, /* .reg_defaults and .num_reg_defaults set in probe() */ .max_register = WSA_MAX_OFFSET, .writeable_reg = wsa_is_writeable_register, @@ -1129,27 +1129,35 @@ static const struct regmap_config wsa_regmap_config = { int wsa_macro_set_spkr_mode(struct snd_soc_component *component, int mode) { struct wsa_macro *wsa = snd_soc_component_get_drvdata(component); + unsigned int comp1_ctl3, comp1_ctl7; + u8 comp_val, boost_val; wsa->spkr_mode = mode; - switch (mode) { - case WSA_MACRO_SPKR_MODE_1: - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL3, 0x80, 0x00); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL3, 0x80, 0x00); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL7, 0x01, 0x00); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL7, 0x01, 0x00); - snd_soc_component_update_bits(component, CDC_WSA_BOOST0_BOOST_CTL, 0x7C, 0x44); - snd_soc_component_update_bits(component, CDC_WSA_BOOST1_BOOST_CTL, 0x7C, 0x44); - break; - default: - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL3, 0x80, 0x80); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL3, 0x80, 0x80); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL7, 0x01, 0x01); - snd_soc_component_update_bits(component, CDC_WSA_COMPANDER1_CTL7, 0x01, 0x01); - snd_soc_component_update_bits(component, CDC_WSA_BOOST0_BOOST_CTL, 0x7C, 0x58); - snd_soc_component_update_bits(component, CDC_WSA_BOOST1_BOOST_CTL, 0x7C, 0x58); - break; + /* Compander1 CTL3/CTL7 moved on v2.5+ silicon. */ + if (wsa->codec_version >= LPASS_CODEC_VERSION_2_5) { + comp1_ctl3 = CDC_2_5_WSA_COMPANDER1_CTL3; + comp1_ctl7 = CDC_2_5_WSA_COMPANDER1_CTL7; + } else { + comp1_ctl3 = CDC_WSA_COMPANDER1_CTL3; + comp1_ctl7 = CDC_WSA_COMPANDER1_CTL7; } + + if (mode == WSA_MACRO_SPKR_MODE_1) { + comp_val = 0x00; + boost_val = 0x44; + } else { + comp_val = 0x80; + boost_val = 0x58; + } + + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL3, 0x80, comp_val); + snd_soc_component_update_bits(component, comp1_ctl3, 0x80, comp_val); + snd_soc_component_update_bits(component, CDC_WSA_COMPANDER0_CTL7, 0x01, comp_val ? 0x01 : 0x00); + snd_soc_component_update_bits(component, comp1_ctl7, 0x01, comp_val ? 0x01 : 0x00); + snd_soc_component_update_bits(component, CDC_WSA_BOOST0_BOOST_CTL, 0x7C, boost_val); + snd_soc_component_update_bits(component, CDC_WSA_BOOST1_BOOST_CTL, 0x7C, boost_val); + return 0; } EXPORT_SYMBOL(wsa_macro_set_spkr_mode); @@ -2641,10 +2649,10 @@ static const struct clk_ops swclk_gate_ops = { static int wsa_macro_register_mclk_output(struct wsa_macro *wsa) { + struct clk_init_data init = {}; struct device *dev = wsa->dev; const char *parent_clk_name; struct clk_hw *hw; - struct clk_init_data init; int ret; if (wsa->npl) @@ -2758,6 +2766,8 @@ static int wsa_macro_probe(struct platform_device *pdev) return -EINVAL; } + regcache_sort_defaults(reg_defaults, def_count); + struct regmap_config *reg_config __free(kfree) = kmemdup(&wsa_regmap_config, sizeof(*reg_config), GFP_KERNEL); diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c index df438baf05dc..1dd7881c84aa 100644 --- a/sound/soc/codecs/max98088.c +++ b/sound/soc/codecs/max98088.c @@ -1342,11 +1342,21 @@ static int max98088_set_bias_level(struct snd_soc_component *component, #define MAX98088_RATES SNDRV_PCM_RATE_8000_96000 #define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 max98088_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98088_dai1_ops = { .set_sysclk = max98088_dai_set_sysclk, .set_fmt = max98088_dai1_set_fmt, .hw_params = max98088_dai1_hw_params, .mute_stream = max98088_dai1_mute, + .auto_selectable_formats = &max98088_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1355,6 +1365,8 @@ static const struct snd_soc_dai_ops max98088_dai2_ops = { .set_fmt = max98088_dai2_set_fmt, .hw_params = max98088_dai2_hw_params, .mute_stream = max98088_dai2_mute, + .auto_selectable_formats = &max98088_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index 64dc442e9066..4bb5478f4642 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -2382,6 +2382,29 @@ static int max98090_set_jack(struct snd_soc_component *component, #define MAX98090_RATES SNDRV_PCM_RATE_8000_96000 #define MAX98090_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 max98090_selectable_formats[] = { + /* + * Non RIGHT_J formats doesn't have 24bit support. + * priority is RIGHT_J > I2S/LEFT_J/DSP_A + * see + * max98090_dai_startup() + */ + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, +}; + static const struct snd_soc_dai_ops max98090_dai_ops = { .startup = max98090_dai_startup, .set_sysclk = max98090_dai_set_sysclk, @@ -2390,6 +2413,8 @@ static const struct snd_soc_dai_ops max98090_dai_ops = { .hw_params = max98090_dai_hw_params, .mute_stream = max98090_dai_mute, .trigger = max98090_dai_trigger, + .auto_selectable_formats = max98090_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(max98090_selectable_formats), .no_capture_mute = 1, }; @@ -2423,11 +2448,6 @@ static int max98090_probe(struct snd_soc_component *component) dev_dbg(component->dev, "max98090_probe\n"); - max98090->mclk = devm_clk_get(component->dev, "mclk"); - if (IS_ERR(max98090->mclk)) - if (PTR_ERR(max98090->mclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - max98090->component = component; /* Reset the codec, the DSP core, and disable all interrupts */ @@ -2608,6 +2628,11 @@ static int max98090_i2c_probe(struct i2c_client *i2c) goto err_enable; } + max98090->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(max98090->mclk)) + if (PTR_ERR(max98090->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, max98090_interrupt, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "max98090_interrupt", max98090); diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c index d93436046849..c827fd47a5b0 100644 --- a/sound/soc/codecs/max98095.c +++ b/sound/soc/codecs/max98095.c @@ -1413,22 +1413,36 @@ static int max98095_set_bias_level(struct snd_soc_component *component, #define MAX98095_RATES SNDRV_PCM_RATE_8000_96000 #define MAX98095_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 max98095_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98095_dai1_ops = { .set_sysclk = max98095_dai_set_sysclk, .set_fmt = max98095_dai1_set_fmt, .hw_params = max98095_dai1_hw_params, + .auto_selectable_formats = &max98095_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops max98095_dai2_ops = { .set_sysclk = max98095_dai_set_sysclk, .set_fmt = max98095_dai2_set_fmt, .hw_params = max98095_dai2_hw_params, + .auto_selectable_formats = &max98095_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops max98095_dai3_ops = { .set_sysclk = max98095_dai_set_sysclk, .set_fmt = max98095_dai3_set_fmt, .hw_params = max98095_dai3_hw_params, + .auto_selectable_formats = &max98095_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max98095_dai[] = { @@ -1991,11 +2005,6 @@ static int max98095_probe(struct snd_soc_component *component) struct i2c_client *client; int ret = 0; - max98095->mclk = devm_clk_get(component->dev, "mclk"); - if (IS_ERR(max98095->mclk)) - if (PTR_ERR(max98095->mclk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - /* reset the codec, the DSP core, and disable all interrupts */ max98095_reset(component); @@ -2139,6 +2148,11 @@ static int max98095_i2c_probe(struct i2c_client *i2c) return ret; } + max98095->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(max98095->mclk)) + if (PTR_ERR(max98095->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + max98095->devtype = (uintptr_t)i2c_get_match_data(i2c); i2c_set_clientdata(i2c, max98095); max98095->pdata = i2c->dev.platform_data; diff --git a/sound/soc/codecs/max98371.c b/sound/soc/codecs/max98371.c index 324270cc5cb2..72bcdb0d1289 100644 --- a/sound/soc/codecs/max98371.c +++ b/sound/soc/codecs/max98371.c @@ -322,9 +322,16 @@ static const struct snd_soc_dapm_route max98371_audio_map[] = { #define MAX98371_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) +static const u64 max98371_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops max98371_dai_ops = { .set_fmt = max98371_dai_set_fmt, .hw_params = max98371_dai_hw_params, + .auto_selectable_formats = &max98371_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max98371_dai[] = { diff --git a/sound/soc/codecs/max98373-i2c.c b/sound/soc/codecs/max98373-i2c.c index cbd7ebffa6d6..4e52282ace7f 100644 --- a/sound/soc/codecs/max98373-i2c.c +++ b/sound/soc/codecs/max98373-i2c.c @@ -398,10 +398,20 @@ static int max98373_dai_tdm_slot(struct snd_soc_dai *dai, #define MAX98373_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98373_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98373_dai_ops = { .set_fmt = max98373_dai_set_fmt, .hw_params = max98373_dai_hw_params, .set_tdm_slot = max98373_dai_tdm_slot, + .auto_selectable_formats = &max98373_selectable_formats, + .num_auto_selectable_formats = 1, }; static bool max98373_readable_register(struct device *dev, unsigned int reg) diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c index d97dab41bf0f..a653e0a21899 100644 --- a/sound/soc/codecs/max98388.c +++ b/sound/soc/codecs/max98388.c @@ -776,10 +776,20 @@ static int max98388_dai_tdm_slot(struct snd_soc_dai *dai, #define MAX98388_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98388_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98388_dai_ops = { .set_fmt = max98388_dai_set_fmt, .hw_params = max98388_dai_hw_params, .set_tdm_slot = max98388_dai_tdm_slot, + .auto_selectable_formats = &max98388_selectable_formats, + .num_auto_selectable_formats = 1, }; static bool max98388_readable_register(struct device *dev, diff --git a/sound/soc/codecs/max98390.c b/sound/soc/codecs/max98390.c index 2295fc057c71..b87cca2a5b43 100644 --- a/sound/soc/codecs/max98390.c +++ b/sound/soc/codecs/max98390.c @@ -462,11 +462,21 @@ static int max98390_dai_set_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 max98390_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98390_dai_ops = { .set_sysclk = max98390_dai_set_sysclk, .set_fmt = max98390_dai_set_fmt, .hw_params = max98390_dai_hw_params, .set_tdm_slot = max98390_dai_tdm_slot, + .auto_selectable_formats = &max98390_selectable_formats, + .num_auto_selectable_formats = 1, }; static int max98390_dac_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/codecs/max98396.c b/sound/soc/codecs/max98396.c index e5a2fe824c03..935319fee41b 100644 --- a/sound/soc/codecs/max98396.c +++ b/sound/soc/codecs/max98396.c @@ -790,10 +790,22 @@ static int max98396_dai_tdm_slot(struct snd_soc_dai *dai, #define MAX98396_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98396_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98396_dai_ops = { .set_fmt = max98396_dai_set_fmt, .hw_params = max98396_dai_hw_params, .set_tdm_slot = max98396_dai_tdm_slot, + .auto_selectable_formats = &max98396_selectable_formats, + .num_auto_selectable_formats = 1, }; static int max98396_dac_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/codecs/max9850.c b/sound/soc/codecs/max9850.c index 9bd6a61212c1..c229b732121f 100644 --- a/sound/soc/codecs/max9850.c +++ b/sound/soc/codecs/max9850.c @@ -254,10 +254,21 @@ static int max9850_set_bias_level(struct snd_soc_component *component, #define MAX9850_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 max9850_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max9850_dai_ops = { .hw_params = max9850_hw_params, .set_sysclk = max9850_set_dai_sysclk, .set_fmt = max9850_set_dai_fmt, + .auto_selectable_formats = &max9850_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max9850_dai = { diff --git a/sound/soc/codecs/max98520.c b/sound/soc/codecs/max98520.c index 4fb98505db1a..57759d85907b 100644 --- a/sound/soc/codecs/max98520.c +++ b/sound/soc/codecs/max98520.c @@ -326,10 +326,20 @@ static int max98520_dai_tdm_slot(struct snd_soc_dai *dai, #define MAX98520_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98520_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98520_dai_ops = { .set_fmt = max98520_dai_set_fmt, .hw_params = max98520_dai_hw_params, .set_tdm_slot = max98520_dai_tdm_slot, + .auto_selectable_formats = &max98520_selectable_formats, + .num_auto_selectable_formats = 1, }; static int max98520_dac_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/codecs/max9860.c b/sound/soc/codecs/max9860.c index 20e288e38669..35265c2502fe 100644 --- a/sound/soc/codecs/max9860.c +++ b/sound/soc/codecs/max9860.c @@ -459,9 +459,32 @@ static int max9860_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) } } +static const u64 max9860_selectable_formats[] = { + /* + * DSP_A/B has some limitations. + * priority is I2S/LEFT_J > DSP_A/B + * see + * max9860_hw_params() + */ + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops max9860_dai_ops = { .hw_params = max9860_hw_params, .set_fmt = max9860_set_fmt, + .auto_selectable_formats = max9860_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(max9860_selectable_formats), }; static struct snd_soc_dai_driver max9860_dai = { diff --git a/sound/soc/codecs/max9867.c b/sound/soc/codecs/max9867.c index 07a53ec2a18a..f2214e3a19ce 100644 --- a/sound/soc/codecs/max9867.c +++ b/sound/soc/codecs/max9867.c @@ -522,12 +522,22 @@ static int max9867_dai_set_fmt(struct snd_soc_dai *codec_dai, return 0; } +static const u64 max9867_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max9867_dai_ops = { .set_sysclk = max9867_set_dai_sysclk, .set_fmt = max9867_dai_set_fmt, .mute_stream = max9867_mute, .startup = max9867_startup, .hw_params = max9867_dai_hw_params, + .auto_selectable_formats = &max9867_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/max98925.c b/sound/soc/codecs/max98925.c index 4302ab16a642..fe43ef99a0cb 100644 --- a/sound/soc/codecs/max98925.c +++ b/sound/soc/codecs/max98925.c @@ -484,10 +484,18 @@ static int max98925_dai_set_sysclk(struct snd_soc_dai *dai, #define MAX98925_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98925_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98925_dai_ops = { .set_sysclk = max98925_dai_set_sysclk, .set_fmt = max98925_dai_set_fmt, .hw_params = max98925_dai_hw_params, + .auto_selectable_formats = &max98925_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max98925_dai[] = { diff --git a/sound/soc/codecs/max98926.c b/sound/soc/codecs/max98926.c index 51a30c182167..5b590a1f00de 100644 --- a/sound/soc/codecs/max98926.c +++ b/sound/soc/codecs/max98926.c @@ -448,9 +448,17 @@ static int max98926_dai_hw_params(struct snd_pcm_substream *substream, #define MAX98926_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 max98926_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops max98926_dai_ops = { .set_fmt = max98926_dai_set_fmt, .hw_params = max98926_dai_hw_params, + .auto_selectable_formats = &max98926_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver max98926_dai[] = { diff --git a/sound/soc/codecs/max98927.c b/sound/soc/codecs/max98927.c index 524300d8fb0b..285c9d1ea721 100644 --- a/sound/soc/codecs/max98927.c +++ b/sound/soc/codecs/max98927.c @@ -463,11 +463,21 @@ static int max98927_dai_set_sysclk(struct snd_soc_dai *dai, return 0; } +static const u64 max98927_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops max98927_dai_ops = { .set_sysclk = max98927_dai_set_sysclk, .set_fmt = max98927_dai_set_fmt, .hw_params = max98927_dai_hw_params, .set_tdm_slot = max98927_dai_tdm_slot, + .auto_selectable_formats = &max98927_selectable_formats, + .num_auto_selectable_formats = 1, }; static int max98927_dac_event(struct snd_soc_dapm_widget *w, @@ -662,7 +672,7 @@ static int max98927_probe(struct snd_soc_component *component) 0x80); regmap_write(max98927->regmap, MAX98927_R0026_PCM_TO_SPK_MONOMIX_B, 0x1); - /* Set inital volume (+13dB) */ + /* Set initial volume (+13dB) */ regmap_write(max98927->regmap, MAX98927_R0036_AMP_VOL_CTRL, 0x38); regmap_write(max98927->regmap, MAX98927_R003C_SPK_GAIN, 0x05); /* Enable DC blocker */ @@ -860,7 +870,7 @@ static int max98927_i2c_probe(struct i2c_client *i2c) /* voltage/current slot configuration */ max98927_slot_config(i2c, max98927); - /* codec registeration */ + /* codec registration */ ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_max98927, max98927_dai, ARRAY_SIZE(max98927_dai)); diff --git a/sound/soc/codecs/mc13783.c b/sound/soc/codecs/mc13783.c index 086ac97e8386..a6231a12a9b0 100644 --- a/sound/soc/codecs/mc13783.c +++ b/sound/soc/codecs/mc13783.c @@ -637,11 +637,21 @@ static void mc13783_remove(struct snd_soc_component *component) #define MC13783_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 mc13783_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops mc13783_ops_dac = { .hw_params = mc13783_pcm_hw_params_dac, .set_fmt = mc13783_set_fmt_async, .set_sysclk = mc13783_set_sysclk_dac, .set_tdm_slot = mc13783_set_tdm_slot_dac, + .auto_selectable_formats = &mc13783_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops mc13783_ops_codec = { @@ -649,6 +659,8 @@ static const struct snd_soc_dai_ops mc13783_ops_codec = { .set_fmt = mc13783_set_fmt_async, .set_sysclk = mc13783_set_sysclk_codec, .set_tdm_slot = mc13783_set_tdm_slot_codec, + .auto_selectable_formats = &mc13783_selectable_formats, + .num_auto_selectable_formats = 1, }; /* diff --git a/sound/soc/codecs/ml26124.c b/sound/soc/codecs/ml26124.c index 7209b47c2029..a94a3f9b8f90 100644 --- a/sound/soc/codecs/ml26124.c +++ b/sound/soc/codecs/ml26124.c @@ -492,11 +492,17 @@ static int ml26124_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 ml26124_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops ml26124_dai_ops = { .hw_params = ml26124_hw_params, .mute_stream = ml26124_mute, .set_fmt = ml26124_set_dai_fmt, .set_sysclk = ml26124_set_dai_sysclk, + .auto_selectable_formats = &ml26124_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/mt6359-accdet.c b/sound/soc/codecs/mt6359-accdet.c index e1190c644021..54ec1c6bd64b 100644 --- a/sound/soc/codecs/mt6359-accdet.c +++ b/sound/soc/codecs/mt6359-accdet.c @@ -914,6 +914,14 @@ int mt6359_accdet_enable_jack_detect(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(mt6359_accdet_enable_jack_detect); +static void mt6359_accdet_destroy_workqueues(void *data) +{ + struct mt6359_accdet *priv = data; + + destroy_workqueue(priv->jd_workqueue); + destroy_workqueue(priv->accdet_workqueue); +} + static int mt6359_accdet_probe(struct platform_device *pdev) { struct mt6359_accdet *priv; @@ -955,6 +963,26 @@ static int mt6359_accdet_probe(struct platform_device *pdev) } mutex_init(&priv->res_lock); + priv->accdet_workqueue = create_singlethread_workqueue("accdet"); + INIT_WORK(&priv->accdet_work, mt6359_accdet_work); + if (!priv->accdet_workqueue) { + dev_err(&pdev->dev, "Failed to create accdet workqueue\n"); + return -ENOMEM; + } + + priv->jd_workqueue = create_singlethread_workqueue("mt6359_accdet_jd"); + INIT_WORK(&priv->jd_work, mt6359_accdet_jd_work); + if (!priv->jd_workqueue) { + dev_err(&pdev->dev, "Failed to create jack detect workqueue\n"); + destroy_workqueue(priv->accdet_workqueue); + return -ENOMEM; + } + + ret = devm_add_action_or_reset(&pdev->dev, + mt6359_accdet_destroy_workqueues, priv); + if (ret) + return ret; + priv->accdet_irq = platform_get_irq(pdev, 0); if (priv->accdet_irq >= 0) { ret = devm_request_threaded_irq(&pdev->dev, priv->accdet_irq, @@ -1000,22 +1028,6 @@ static int mt6359_accdet_probe(struct platform_device *pdev) } } - priv->accdet_workqueue = create_singlethread_workqueue("accdet"); - INIT_WORK(&priv->accdet_work, mt6359_accdet_work); - if (!priv->accdet_workqueue) { - dev_err(&pdev->dev, "Failed to create accdet workqueue\n"); - ret = -1; - goto err_accdet_wq; - } - - priv->jd_workqueue = create_singlethread_workqueue("mt6359_accdet_jd"); - INIT_WORK(&priv->jd_work, mt6359_accdet_jd_work); - if (!priv->jd_workqueue) { - dev_err(&pdev->dev, "Failed to create jack detect workqueue\n"); - ret = -1; - goto err_eint_wq; - } - platform_set_drvdata(pdev, priv); ret = devm_snd_soc_register_component(&pdev->dev, &mt6359_accdet_soc_driver, @@ -1034,12 +1046,6 @@ static int mt6359_accdet_probe(struct platform_device *pdev) mt6359_accdet_jack_report(priv); return 0; - -err_eint_wq: - destroy_workqueue(priv->accdet_workqueue); -err_accdet_wq: - dev_err(&pdev->dev, "%s error. now exit.!\n", __func__); - return ret; } static struct platform_driver mt6359_accdet_driver = { diff --git a/sound/soc/codecs/mt6359.c b/sound/soc/codecs/mt6359.c index b15bdb15dbb0..c51eae602ca0 100644 --- a/sound/soc/codecs/mt6359.c +++ b/sound/soc/codecs/mt6359.c @@ -82,7 +82,7 @@ static void mt6359_reset_capture_gpio(struct mt6359_priv *priv) 0x3 << 0, 0x0); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_dcxo(struct mt6359_priv *priv, bool enable) { regmap_update_bits(priv->regmap, MT6359_DCXO_CW12, @@ -90,7 +90,7 @@ static void mt6359_set_dcxo(struct mt6359_priv *priv, bool enable) (enable ? 1 : 0) << RG_XO_AUDIO_EN_M_SFT); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_clksq(struct mt6359_priv *priv, bool enable) { /* Enable/disable CLKSQ 26MHz */ @@ -99,7 +99,7 @@ static void mt6359_set_clksq(struct mt6359_priv *priv, bool enable) (enable ? 1 : 0) << RG_CLKSQ_EN_SFT); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_aud_global_bias(struct mt6359_priv *priv, bool enable) { regmap_update_bits(priv->regmap, MT6359_AUDDEC_ANA_CON13, @@ -107,7 +107,7 @@ static void mt6359_set_aud_global_bias(struct mt6359_priv *priv, bool enable) (enable ? 0 : 1) << RG_AUDGLB_PWRDN_VA32_SFT); } -/* use only when doing mtkaif calibraiton at the boot time */ +/* use only when doing mtkaif calibration at the boot time */ static void mt6359_set_topck(struct mt6359_priv *priv, bool enable) { regmap_update_bits(priv->regmap, MT6359_AUD_TOP_CKPDN_CON0, diff --git a/sound/soc/codecs/nau8325.c b/sound/soc/codecs/nau8325.c index 236d94c29c24..5ccf4c110567 100644 --- a/sound/soc/codecs/nau8325.c +++ b/sound/soc/codecs/nau8325.c @@ -489,14 +489,24 @@ static int nau8325_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8325 *nau8325 = snd_soc_component_get_drvdata(component); const struct nau8325_osr_attr *osr; + int ret; osr = nau8325_get_osr(nau8325); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ + return 0; } static int nau8325_hw_params(struct snd_pcm_substream *substream, @@ -628,10 +638,21 @@ static const struct snd_soc_component_driver nau8325_component_driver = { .num_dapm_routes = ARRAY_SIZE(nau8325_dapm_routes), }; +static const u64 nau8325_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8325_dai_ops = { .startup = nau8325_dai_startup, .hw_params = nau8325_hw_params, .set_fmt = nau8325_set_fmt, + .auto_selectable_formats = &nau8325_selectable_formats, + .num_auto_selectable_formats = 1, }; #define NAU8325_RATES SNDRV_PCM_RATE_8000_96000 diff --git a/sound/soc/codecs/nau8360-dsp.c b/sound/soc/codecs/nau8360-dsp.c new file mode 100644 index 000000000000..65ffec8b3a39 --- /dev/null +++ b/sound/soc/codecs/nau8360-dsp.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver. +// +// Copyright (C) 2026 Nuvoton Technology Corp. +// Author: David Lin +// Seven Lee +// John Hsu +// Neo Chang + +#include +#include +#include +#include +#include +#include +#include + +#include "nau8360-dsp.h" +#include "nau8360.h" + +#define NAU8360_DSP_IDLE_RETRY 10 +static const unsigned short nau8360_dsp_addr[NAU8360_DSP_FW_NUM] = { + NAU8360_RF000_DSP_COMM, NAU8360_RF002_DSP_COMM }; + +static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp, + const char *fw_name, int dsp_addr); + +#define NAU_DSP_CMD(_id, _msg, _setup, _reply) \ + [_id] = { \ + .cmd_id = _id, \ + .msg_param = _msg, \ + .setup_data = _setup, \ + .reply_data = _reply, \ + } + +#define NAU_DSP_CMD_ID(_id) \ + [_id] = { \ + .cmd_id = _id, \ + } + +/* + * Preamble Fragment Masks: + * FIELD | BITS | SHIFT | MASK | DETAILS + * ----------------|--------|-------|------|--------------------------------- + * length high | [31:24]| 24 | 0xff | Upper 8 bits of total length (LEN[9:2]) + * cmd_id | [23:18]| 18 | 0x3f | Command identifier (6 bits) + * length low | [17:16]| 16 | 0x03 | Lower 2 bits of total length (LEN[1:0]) + * preamble magic | [15:0] | 0 |0xffff| Preamble signature (0xB2A1) + */ +#define NAU8360_HOST_LEN_HIGH_MASK GENMASK(31, 24) +#define NAU8360_HOST_CMD_ID_MASK GENMASK(23, 18) +#define NAU8360_HOST_LEN_LOW_MASK GENMASK(17, 16) +#define NAU8360_HOST_PREAMBLE_MASK GENMASK(15, 0) + +/* + * Payload Fragment Masks: + * FIELD | BITS | SHIFT | MASK | DETAILS + * ----------------|--------|-------|------|--------------------------------- + * param_size | [31:16]| 16 |0xffff| Size of parameter data (16 bits) + * param_offset | [15:0] | 0 |0xffff| Starting offset (16 bits) + */ +#define NAU8360_HOST_PARAM_SIZE_MASK GENMASK(31, 16) +#define NAU8360_HOST_PARAM_OFFSET_MASK GENMASK(15, 0) + +/* + * Trailing Fragment Masks: + * FIELD | BITS | SHIFT | MASK | DETAILS + * ----------------|--------|-------|------|--------------------------------- + * length high | [15:14]| 14 | 0x03 | Upper 2 bits of total length + * padding | [13:12]| 12 | 0x03 | Padding bytes count + * length low | [7:0] | 0 | 0xff | Lower 8 bits of total length + * Note: Reassembled length = (length_high << 8) | length_low (Total 10 bits) + */ +#define NAU8360_TRAIL_LEN_LOW_MASK GENMASK(7, 0) +#define NAU8360_TRAIL_PAD_MASK GENMASK(13, 12) +#define NAU8360_TRAIL_LEN_HIGH_MASK GENMASK(15, 14) + +/* + * Reply Preamble Masks: + * FIELD | BITS | SHIFT | MASK | DETAILS + * ----------------|--------|-------|------|--------------------------------- + * length high | [31:24]| 24 | 0xff | Upper 8 bits of total length + * reply_id | [23:18]| 18 | 0x3f | Reply identifier (6 bits) + * length low | [17:16]| 16 | 0x03 | Lower 2 bits of total length + * Note: Reassembled length = (length_high << 2) | length_low (Total 10 bits) + */ +#define NAU8360_REPLY_LEN_HIGH_MASK GENMASK(31, 24) +#define NAU8360_REPLY_ID_MASK GENMASK(23, 18) +#define NAU8360_REPLY_LEN_LOW_MASK GENMASK(17, 16) + +static const struct nau8360_cmd_info nau8360_dsp_cmd_table[] = { + /* { cmd_id, msg_param, setup_data, reply_data} */ + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_COUNTER, 0, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_FRAME_STATUS, 0, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_REVISION, 0, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_KCS_RSLTS, 1, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_GET_KCS_SETUP, 1, 0, 1), + NAU_DSP_CMD(NAU8360_DSP_CMD_SET_KCS_SETUP, 1, 1, 0), + NAU_DSP_CMD_ID(NAU8360_DSP_CMD_CLK_STOP), + NAU_DSP_CMD_ID(NAU8360_DSP_CMD_CLK_RESTART), +}; + +static const char *const dsp_cmd_table[] = { + [NAU8360_DSP_CMD_GET_COUNTER] = "GET_COUNTER", + [NAU8360_DSP_CMD_GET_FRAME_STATUS] = "GET_FRAME_STATUS", + [NAU8360_DSP_CMD_GET_REVISION] = "GET_REVISION", + [NAU8360_DSP_CMD_GET_KCS_RSLTS] = "GET_KCS_RSLTS", + [NAU8360_DSP_CMD_GET_KCS_SETUP] = "GET_KCS_SETUP", + [NAU8360_DSP_CMD_SET_KCS_SETUP] = "SET_KCS_SETUP", + [NAU8360_DSP_CMD_CLK_STOP] = "CLK_STOP", + [NAU8360_DSP_CMD_CLK_RESTART] = "CLK_RESTART", +}; + +static int nau8360_dsp_idle(struct snd_soc_component *cp, unsigned short dsp_addr) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + unsigned int idle_pattern, timeout = NAU8360_DSP_IDLE_RETRY * USEC_PER_MSEC; + int ret = 0; + + ret = regmap_read_poll_timeout(nau8360->regmap, dsp_addr, idle_pattern, + idle_pattern == NAU8360_DSP_COMM_IDLE_WORD, USEC_PER_MSEC, timeout); + if (ret) + dev_err(nau8360->dev, "Timeout waiting for DSP idle state: %d", ret); + + return ret; +} + +/** + * nau8360_pack_preamble - Pack DSP preamble fragment + * @cmd_id: Command ID for the DSP message + * @frag_len: Total length of the message fragments + * + * Return: 32-bit packed payload in Little Endian format. + */ +static inline u32 nau8360_pack_preamble(u8 cmd_id, u16 frag_len) +{ + return FIELD_PREP(NAU8360_HOST_PREAMBLE_MASK, NAU8360_DSP_COMM_PREAMBLE) | + FIELD_PREP(NAU8360_HOST_CMD_ID_MASK, cmd_id) | + FIELD_PREP(NAU8360_HOST_LEN_LOW_MASK, frag_len) | + FIELD_PREP(NAU8360_HOST_LEN_HIGH_MASK, frag_len >> 2); +} + +/** + * nau8360_pack_param - Pack DSP parameter fragment + * @param_offset: Starting offset of the parameter data + * @param_size: Size of the parameter data in bytes + * + * Return: 32-bit packed payload in Little Endian format. + */ +static inline u32 nau8360_pack_param(u16 param_offset, u16 param_size) +{ + return FIELD_PREP(NAU8360_HOST_PARAM_OFFSET_MASK, param_offset) | + FIELD_PREP(NAU8360_HOST_PARAM_SIZE_MASK, param_size); +} + +/** + * nau8360_pack_trailing - Pack DSP trailing fragment + * @frag_cnt: Current fragment count + * @padding: Number of padding bytes added to the final data fragment + * + * Return: 32-bit packed payload in Little Endian format. + */ +static inline u32 nau8360_pack_trailing(u16 frag_cnt, u8 padding) +{ + return FIELD_PREP(NAU8360_TRAIL_LEN_LOW_MASK, frag_cnt) | + FIELD_PREP(NAU8360_TRAIL_PAD_MASK, padding) | + FIELD_PREP(NAU8360_TRAIL_LEN_HIGH_MASK, frag_cnt >> 8); +} + +static void nau8360_send_data_payload(struct snd_soc_component *cp, + unsigned short dsp_addr, const void *param_data, int param_size, + int *frag_cnt, int *padding) +{ + const u8 *data = (const u8 *)param_data; + u32 payload = 0; + int i, data_size = 0; + + for (i = 0; i < param_size; i++) { + payload |= data[i] << (data_size * 8); + data_size++; + + if (data_size == NAU8360_DSP_DATA_BYTE) { + snd_soc_component_write(cp, dsp_addr, payload); + data_size = 0; + payload = 0; + (*frag_cnt)++; + } + } + + if (data_size > 0) { + *padding = NAU8360_DSP_DATA_BYTE - data_size; + snd_soc_component_write(cp, dsp_addr, payload); + (*frag_cnt)++; + } +} + +static int nau8360_message_to_dsp(struct snd_soc_component *cp, + const struct nau8360_cmd_info *cmd_info, int frag_len, int param_offset, + int param_size, void *param_data, unsigned short dsp_addr) +{ + unsigned int payload; + int ret, padding = 0, frag_cnt = 0; + + ret = nau8360_dsp_idle(cp, dsp_addr); + if (ret) + return ret; + + /* sending preamble fragment */ + payload = nau8360_pack_preamble(cmd_info->cmd_id, frag_len); + snd_soc_component_write(cp, dsp_addr, payload); + + if (!cmd_info->msg_param) + return 0; + + /* sending payload + padding */ + payload = nau8360_pack_param(param_offset, param_size); + snd_soc_component_write(cp, dsp_addr, payload); + frag_cnt++; + + if (cmd_info->setup_data) + nau8360_send_data_payload(cp, dsp_addr, param_data, param_size, + &frag_cnt, &padding); + + /* sending trailing fragment */ + frag_cnt++; + payload = nau8360_pack_trailing(frag_cnt, padding); + snd_soc_component_write(cp, dsp_addr, payload); + + if (frag_cnt != frag_len) { + dev_err(cp->dev, "message error (CMD_ID 0x%x, LEN 0x%x) !!!", + cmd_info->cmd_id, frag_cnt); + return -EPROTO; + } + + return 0; +} + +static int nau8360_dsp_replied(struct nau8360 *nau8360, int *length, + unsigned short dsp_addr) +{ + unsigned int reply_preamble; + int ret, reply_id; + + ret = regmap_read_poll_timeout(nau8360->regmap, dsp_addr, reply_preamble, + (reply_preamble & 0xffff) == NAU8360_DSP_COMM_PREAMBLE, + USEC_PER_MSEC, NAU8360_DSP_IDLE_RETRY * USEC_PER_MSEC); + if (ret) { + dev_err(nau8360->dev, "timeout for reply preamble: %d", ret); + return ret; + } + + *length = FIELD_GET(NAU8360_REPLY_LEN_LOW_MASK, reply_preamble); + *length |= FIELD_GET(NAU8360_REPLY_LEN_HIGH_MASK, reply_preamble) << 2; + reply_id = FIELD_GET(NAU8360_REPLY_ID_MASK, reply_preamble); + + return (reply_id != NAU8360_DSP_REPLY_OK) ? -reply_id : 0; +} + +static int nau8360_validate_trailing(struct snd_soc_component *cp, + unsigned short dsp_addr, int frag_len, int pad_len_exp) +{ + int ret, len_pos, pad_len; + unsigned int payload; + struct device *dev = cp->dev; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + + /* reading trailing fragment */ + ret = regmap_read(nau8360->regmap, dsp_addr, &payload); + if (ret) { + dev_err(dev, "failed to read trailing fragment"); + return ret; + } + + len_pos = FIELD_GET(NAU8360_TRAIL_LEN_LOW_MASK, payload); + len_pos |= FIELD_GET(NAU8360_TRAIL_LEN_HIGH_MASK, payload) << 8; + + if (len_pos != frag_len) { + dev_err(dev, "LEN_POST %02X, expect %02X", len_pos, frag_len); + return -EPROTO; + } + + pad_len = FIELD_GET(NAU8360_TRAIL_PAD_MASK, payload); + if (pad_len != pad_len_exp) { + dev_err(dev, "PAD_LEN %02X, expect %02X", pad_len, pad_len_exp); + return -EPROTO; + } + + return 0; +} + +static int nau8360_read_data_payload(struct snd_soc_component *cp, + unsigned short dsp_addr, int frag_len, bool msg_param, + void *data, int data_size, int *data_count) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct device *dev = cp->dev; + unsigned int payload; + u32 *data_buf = data; + int i, j, ret; + + *data_count = (msg_param) ? data_size : 0; + for (i = 0; i < frag_len - 1; i++) { + ret = regmap_read(nau8360->regmap, dsp_addr, &payload); + if (ret) { + dev_err(dev, "failed to read payload of dsp"); + return ret; + } + + if (!msg_param) { + *data_buf++ = payload; + break; + } + + if (*data_count >= NAU8360_DSP_DATA_BYTE) { + *data_buf++ = payload; + *data_count -= NAU8360_DSP_DATA_BYTE; + } else if (*data_count > 0) { + for (j = 0; j < *data_count; j++) + ((u8 *)data_buf)[j] = (payload >> (j * 8)) & 0xff; + + *data_count = 0; + } + + if (*data_count == 0) + break; + } + + return 0; +} + +static int nau8360_reply_from_dsp(struct snd_soc_component *cp, + const struct nau8360_cmd_info *cmd_info, int data_size, + void *data, unsigned short dsp_addr) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct device *dev = cp->dev; + int ret, frag_len, pad_len_exp, data_count = 0; + + ret = nau8360_dsp_replied(nau8360, &frag_len, dsp_addr); + if (ret) + return ret; + if (!frag_len || !cmd_info->reply_data) + return 0; + if (!data || frag_len == 1) + return -EINVAL; + + ret = nau8360_read_data_payload(cp, dsp_addr, frag_len, + cmd_info->msg_param, data, data_size, &data_count); + if (ret) + return ret; + + /* check the reply length same as request */ + if (data_count && (cmd_info->cmd_id == NAU8360_DSP_CMD_GET_KCS_RSLTS || + cmd_info->cmd_id == NAU8360_DSP_CMD_GET_KCS_SETUP)) + dev_warn(dev, "payload_len %d, expected %d", + data_size - data_count, data_size); + + pad_len_exp = cmd_info->msg_param ? + (frag_len - 1) * NAU8360_DSP_DATA_BYTE - (data_size - data_count) : 0; + return nau8360_validate_trailing(cp, dsp_addr, frag_len, pad_len_exp); +} + +/** + * nau8360_send_dsp_command - Send command to DSP + * + * @cp: component to register + * @cmd_id: DSP supported command ID + * @kcs_setup: KCS setup structure + * @dsp_addr: DSP address + * + * The communication protocol is a Master-Slave type protocol + * where the host processor is the master and DSP is the slave. + * The Master initiates the communication and can either write or + * read back from the slave. + * Transactions from the Master are called "Messages", + * and read-back data from the Slave is called a "Reply". + * + * The function sends command to DSP according to the command ID. + * These commands include getting the information of DSP, + * getting or setting KCS configuration, or making DSP control. + */ +static int nau8360_send_dsp_command(struct snd_soc_component *cp, int cmd_id, + struct nau8360_kcs_setup *kcs_setup, unsigned short dsp_addr) +{ + const struct nau8360_cmd_info *cmd_info; + int ret = 0, frag_len = 0; + + cmd_info = &nau8360_dsp_cmd_table[cmd_id]; + if ((cmd_info->msg_param && !kcs_setup->set_len) || + (cmd_info->setup_data && !kcs_setup->set_kcs_data) || + (cmd_info->reply_data && !kcs_setup->get_data)) + return -EFAULT; + + /* Read up to 1kB data because the LEN field to request data is 10-bits + * long; and not beyond 3kB offset. + */ + if (cmd_id == NAU8360_DSP_CMD_GET_KCS_SETUP && + (kcs_setup->set_len > NAU8360_DSP_KCS_DAT_LEN_MAX || + kcs_setup->set_kcs_offset > NAU8360_DSP_KCS_OFFSET_MAX)) + return -ERANGE; + + /* one fragment for offset and size parameters + * one fragment for a postamble fragment + */ + if (cmd_info->msg_param) + frag_len += 2; + + /* fragments for KCS setup writen */ + if (cmd_info->setup_data) + frag_len += DIV_ROUND_UP(kcs_setup->set_len, NAU8360_DSP_DATA_BYTE); + + ret = nau8360_message_to_dsp(cp, cmd_info, frag_len, + kcs_setup->set_kcs_offset, kcs_setup->set_len, + kcs_setup->set_kcs_data, dsp_addr); + if (ret) + return ret; + + return nau8360_reply_from_dsp(cp, cmd_info, kcs_setup->get_len, + kcs_setup->get_data, dsp_addr); +} + +static inline int nau8360_dsp_exec_command(struct snd_soc_component *cp, int cmd_id, + int offset, int set_len, void *set_data, int get_len, void *get_data, + int dsp_addr) +{ + struct nau8360_kcs_setup kcs_setup = { + .set_kcs_offset = offset, + .set_len = set_len, + .set_kcs_data = set_data, + .get_len = get_len, + .get_data = get_data, + }; + + return nau8360_send_dsp_command(cp, cmd_id, &kcs_setup, dsp_addr); +} + +static inline int nau8360_send_dsp_broadcast(struct snd_soc_component *cp, int cmd_id) +{ + int i, ret; + + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { + ret = nau8360_dsp_exec_command(cp, cmd_id, 0, 0, NULL, 0, NULL, + nau8360_dsp_addr[i]); + if (ret) { + dev_err(cp->dev, "DSP %x fail (%d)", nau8360_dsp_addr[i], ret); + return ret; + } + } + + return 0; +} + +/** + * nau8360_dsp_kcs_setup - Send KCS setup command to DSP + * + * @cp: component to register + * @offset: address offset relative to KCS start + * @size: size of data writen to KCS + * @data: data writen to KCS setup + * @dsp_addr : DSP address + * + * The function sends KCS setup command to DSP for + * setting KCS configuration. The maximum size that you can transfer into + * the DSP is 96 bytes. Therefore, the driver has to split the data into + * 96 bytes chucks, if the setup configuration over the threshold. + */ +static int nau8360_dsp_kcs_setup(struct snd_soc_component *cp, int offset, int size, + const void *data, unsigned short dsp_addr) +{ + u8 *data_buf = (u8 *)data; + unsigned int kcs_rst = 0; + int retries = 0, ret, data_len, data_rem, addr_offset; + + /* Limit full load of KCS_SETUP data and not beyond 3kB offset. */ + if (!data || size > NAU8360_DSP_KCS_DAT_LEN_MAX || + offset > NAU8360_DSP_KCS_OFFSET_MAX) + return -EINVAL; + + /* sending fragments for KCS setup */ + addr_offset = offset; + data_rem = size; + + while (data_rem) { + data_len = min(data_rem, NAU8360_DSP_KCS_TX_MAX); + + ret = nau8360_dsp_exec_command(cp, NAU8360_DSP_CMD_SET_KCS_SETUP, + addr_offset, data_len, (void *)data_buf, 0, &kcs_rst, dsp_addr); + if (ret) { + if (retries++ < NAU8360_DSP_RETRY_MAX) + continue; + return ret; + } + + data_buf += (u8)data_len; + addr_offset += data_len; + data_rem -= data_len; + + /* checking KCS result */ + ret = nau8360_dsp_exec_command(cp, NAU8360_DSP_CMD_GET_KCS_RSLTS, + 0, NAU8360_DSP_DATA_BYTE, NULL, + NAU8360_DSP_DATA_BYTE, &kcs_rst, dsp_addr); + if (ret) + return ret; + if (kcs_rst != NAU8360_DSP_KCS_RSLTS_SUCCESS) + return -EINVAL; + } + + return 0; +} + +static int nau8360_dsp_get_cmd_put(struct snd_soc_component *cp, + int dsp_addr, int cmd, int *value) +{ + struct device *dev = cp->dev; + int ret; + + dev_dbg(dev, "send DSP %x command %s", dsp_addr, dsp_cmd_table[cmd]); + + ret = nau8360_dsp_exec_command(cp, cmd, 0, sizeof(int), NULL, + sizeof(int), value, dsp_addr); + if (ret) { + dev_err(dev, "do command fail (%d)", ret); + return ret; + } + + return 0; +} + +static int nau8360_dsp_clock_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int ret = 0; + + mutex_lock(&nau8360->lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = nau8360_send_dsp_broadcast(cp, NAU8360_DSP_CMD_CLK_RESTART); + else if (SND_SOC_DAPM_EVENT_OFF(event)) + ret = nau8360_send_dsp_broadcast(cp, NAU8360_DSP_CMD_CLK_STOP); + + mutex_unlock(&nau8360->lock); + + return ret; +} + +static const struct snd_soc_dapm_widget nau8360_dsp_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("DSP Clock", SND_SOC_NOPM, 0, 0, nau8360_dsp_clock_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route nau8360_dsp_dapm_routes[] = { + { "DSP", NULL, "HW3 Engine" }, + { "DSP", NULL, "DSP Clock" }, +}; + +static int nau8360_dsp_chan_kcs_setup(struct snd_soc_component *cp, + const char *fw_name, int dsp_addr) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + const struct firmware *fw; + int buf_off, buf_len; + int ret = 0, status = 0; + + mutex_lock(&nau8360->lock); + ret = nau8360_dsp_get_cmd_put(cp, dsp_addr, + NAU8360_DSP_CMD_GET_FRAME_STATUS, &status); + mutex_unlock(&nau8360->lock); + if (ret || !(status & NAU8360_DSP_ALGO_OK)) { + dev_err(cp->dev, "DSP %x is not ready", dsp_addr); + return -EIO; + } + + dev_info(cp->dev, "DSP %x is ready to load firmware %s, status %x", + dsp_addr, fw_name, status); + + ret = request_firmware(&fw, fw_name, cp->dev); + if (ret) { + dev_err(cp->dev, "failed to load firmware (%d)", ret); + return ret; + } + + buf_off = 0; + buf_len = nau8360->kcs_setup_size = fw->size; + mutex_lock(&nau8360->lock); + ret = nau8360_dsp_kcs_setup(cp, buf_off, buf_len, fw->data, dsp_addr); + mutex_unlock(&nau8360->lock); + if (ret) { + dev_err(cp->dev, "send DSP command %s fail (%d)", + dsp_cmd_table[NAU8360_DSP_CMD_SET_KCS_SETUP], ret); + } + release_firmware(fw); + + return ret; +} + +int nau8360_dsp_init(struct snd_soc_component *cp) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int i, ret; + + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { + ret = nau8360_dsp_chan_kcs_setup(cp, nau8360->dsp_firmware[i], nau8360_dsp_addr[i]); + if (ret) + return ret; + } + + return 0; +} + +int nau8360_dsp_setup_controls(struct snd_soc_component *cp) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct snd_soc_dapm_context *dapm = nau8360->dapm; + int ret; + + ret = snd_soc_dapm_new_controls(dapm, nau8360_dsp_dapm_widgets, + ARRAY_SIZE(nau8360_dsp_dapm_widgets)); + if (ret) { + dev_err(cp->dev, "add DSP widget fail (%d)", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, nau8360_dsp_dapm_routes, + ARRAY_SIZE(nau8360_dsp_dapm_routes)); + if (ret) { + dev_err(cp->dev, "add DSP route fail (%d)", ret); + return ret; + } + + return 0; +} diff --git a/sound/soc/codecs/nau8360-dsp.h b/sound/soc/codecs/nau8360-dsp.h new file mode 100644 index 000000000000..f8c2c97a25c4 --- /dev/null +++ b/sound/soc/codecs/nau8360-dsp.h @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver. +// +// Copyright (C) 2026 Nuvoton Technology Corp. +// Author: David Lin +// Seven Lee +// John Hsu +// Neo Chang + +#ifndef __NAU8360_DSP_H__ +#define __NAU8360_DSP_H__ + +#define NAU8360_DSP_COMM_IDLE_WORD 0xf4f3f2f1 +#define NAU8360_DSP_COMM_PREAMBLE 0xb2a1 +#define NAU8360_DSP_DATA_BYTE 4 +#define NAU8360_DSP_DATA_LEN (NAU8360_DSP_DATA_BYTE << 3) +/* max bytes of data to transfer into DSP each time during the KCS setup */ +#define NAU8360_DSP_KCS_TX_MAX 96 +#define NAU8360_DSP_RETRY_MAX 3 +#define NAU8360_DSP_KCS_DAT_LEN_MAX 1024 +#define NAU8360_DSP_KCS_OFFSET_MAX 3072 +#define NAU8360_DSP_KCS_RSLTS_SUCCESS 0x10 + +/* FRAME_STATUS (0x9) */ +#define NAU8360_DSP_SNS_OVF_SFT 31 +#define NAU8360_DSP_SNS_OVF (0x1 << NAU8360_DSP_SNS_OVF_SFT) +#define NAU8360_DSP_AUD_UVF_SFT 30 +#define NAU8360_DSP_AUD_UVF (0x1 << NAU8360_DSP_AUD_UVF_SFT) +#define NAU8360_DSP_AUD_OVF_SFT 29 +#define NAU8360_DSP_AUD_OVF (0x1 << NAU8360_DSP_AUD_OVF_SFT) +#define NAU8360_DSP_ALC_STS_SFT 28 +#define NAU8360_DSP_ALC_STS (0x1 << NAU8360_DSP_ALC_STS_SFT) +#define NAU8360_DSP_CLK_STOP_SFT 25 +#define NAU8360_DSP_CLK_STOP (0x1 << NAU8360_DSP_CLK_STOP_SFT) +#define NAU8360_DSP_OCP_OTP_SFT 24 +#define NAU8360_DSP_OCP_OTP (0x1 << NAU8360_DSP_OCP_OTP_SFT) +#define NAU8360_DSP_UVLO_SFT 22 +#define NAU8360_DSP_UVLO (0x1 << NAU8360_DSP_UVLO_SFT) +#define NAU8360_DSP_OVP_SFT 21 +#define NAU8360_DSP_OVP (0x1 << NAU8360_DSP_OVP_SFT) +#define NAU8360_DSP_APWR_DWN_SFT 20 +#define NAU8360_DSP_APWR_DWN (0x1 << NAU8360_DSP_APWR_DWN_SFT) +#define NAU8360_DSP_SNSR_RATE_SFT 12 +#define NAU8360_DSP_SNSR_RATE_MASK (0xff << NAU8360_DSP_SNSR_RATE_SFT) +#define NAU8360_DSP_AUD_RATE_SFT 4 +#define NAU8360_DSP_AUD_RATE_MASK (0xff << NAU8360_DSP_AUD_RATE_SFT) +#define NAU8360_DSP_FEED_TRU_SFT 1 +#define NAU8360_DSP_FEED_TRU (0x1 << NAU8360_DSP_FEED_TRU_SFT) +#define NAU8360_DSP_ALGO_OK 0x1 + +#define NAU8360_DSP_FIRMDIR "Nuvoton/" +#define NAU8360_DSP_FIRMWARE NAU8360_DSP_FIRMDIR"NAU83G60.kcs.bin" +#define NAU8360_DSP_FW_NUM NAU8360_DSP_CORE_NUM +#define NAU8360_DSP_FW_NAMELEN 64 +#define NAU8360_IS_DSP_REG(reg) \ + ((reg) == NAU8360_RF000_DSP_COMM || (reg) == NAU8360_RF002_DSP_COMM) + +enum { + NAU8360_DSP_REPLY_OK, + NAU8360_DSP_REPLY_MSG_INTEGRETY_ERR, + NAU8360_DSP_REPLY_EXECUTION_ERR, + NAU8360_DSP_REPLY_COMMAND_DOESNT_EXISTS_ERR, + NAU8360_DSP_REPLY_UNKNOWN_ERR, + NAU8360_DSP_REPLY_MSG_TOO_LONG, +}; + +enum { + NAU8360_DSP_CMD_GET_COUNTER = 0x1, + NAU8360_DSP_CMD_GET_FRAME_STATUS = 0x9, + NAU8360_DSP_CMD_GET_REVISION = 0xa, + NAU8360_DSP_CMD_GET_KCS_RSLTS = 0x4, + NAU8360_DSP_CMD_GET_KCS_SETUP = 0x6, + NAU8360_DSP_CMD_SET_KCS_SETUP = 0x7, + NAU8360_DSP_CMD_CLK_STOP = 0xb, + NAU8360_DSP_CMD_CLK_RESTART = 0xc, +}; + +/** + * struct nau8360_cmd_info - DSP command information structure + * @cmd_id: The command identification number + * @msg_param: Message parameters including offset, size, and data + * @setup_data: Setup data for write-only operations + * @reply_data: Reply data received from the DSP + */ +struct nau8360_cmd_info { + int cmd_id; + bool msg_param; + bool setup_data; + bool reply_data; +}; + +/** + * struct nau8360_kcs_setup - KCS setup configuration structure + * @set_len: Length of data written in bytes + * @set_kcs_offset: Address offset relative to the start of KCS + * @set_kcs_data: Pointer to the data written to KCS + * @get_len: Length of data from the reply in bytes + * @get_data: Pointer to the buffer for reply data + */ +struct nau8360_kcs_setup { + int set_len; + int set_kcs_offset; + void *set_kcs_data; + int get_len; + void *get_data; +}; + +/** + * struct nau8360_dsp_ctx - DSP context structure for firmware loading + * @cp: Pointer to the ALSA SoC component structure + * @dsp_addr: Register address of the DSP core (Left or Right channel) + */ +struct nau8360_dsp_ctx { + struct snd_soc_component *cp; + int dsp_addr; +}; + +int nau8360_dsp_init(struct snd_soc_component *component); +int nau8360_dsp_setup_controls(struct snd_soc_component *component); + +#endif /* __NAU8360_DSP_H__ */ diff --git a/sound/soc/codecs/nau8360.c b/sound/soc/codecs/nau8360.c new file mode 100644 index 000000000000..f0942739d7ab --- /dev/null +++ b/sound/soc/codecs/nau8360.c @@ -0,0 +1,2309 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver. +// +// Copyright (C) 2026 Nuvoton Technology Corp. +// Author: David Lin +// Seven Lee +// John Hsu +// Neo Chang + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nau8360-dsp.h" +#include "nau8360.h" + +/* range of Master Clock MCLK (Hz) */ +#define MASTER_CLK_MIN 11025000 +#define MASTER_CLK_MAX 24576000 +/* DSP Optimal Clock Range 120MHz~126M(Hz) */ +#define DSP_OP_CLK48 122880000 +#define DSP_OP_CLK44 112896000 +/* the maximum frequency of DAC and IV sense clock */ +#define CLK_DA_IVSNS_MAX 6144000 +#define ADSP_SR_48000 48000 +#define ADSP_SR_44100 44100 + +static const int ivsns_clk_div[] = { 1, 2, 4, 5, 8, 10 }; + +static const int dac_clk_div[] = { 1, 2, 4, 8 }; + +static const char * const nau8360_rx_func_names[] = { + [NAU8360_TDM_DACL] = "DAC_L", + [NAU8360_TDM_DACR] = "DAC_R", + [NAU8360_TDM_ANCL] = "ANC_L", + [NAU8360_TDM_ANCR] = "ANC_R", +}; + +static const char * const nau8360_tx_func_names[] = { + [NAU8360_TDM_AECL] = "AEC_L", + [NAU8360_TDM_AECR] = "AEC_R", + [NAU8360_TDM_ISNSL] = "ISNS_L", + [NAU8360_TDM_ISNSR] = "ISNS_R", + [NAU8360_TDM_VSNSL] = "VSNS_L", + [NAU8360_TDM_VSNSR] = "VSNS_R", + [NAU8360_TDM_TJ] = "TJ", + [NAU8360_TDM_VBAT] = "VBAT", +}; + +/* PLL threshold */ +#define PLL_FREQ_MIN 1000000 +#define PLL_FREQ_MAX 32000000 +#define PLL_FOUT_MIN 12500000 +#define PLL_FOUT_MAX 125000000 +#define PLL_FREF_MAX 8000000 +#define PLL_FVCO_MIN 50000000 +#define MSEL_MAX 32 +#define RSEL_MAX 4 + +static const struct reg_default nau8360_reg_defaults[] = { + { NAU8360_R02_I2C_ADDR, 0x0000 }, + { NAU8360_R03_CLK_CTRL0, 0x0000 }, + { NAU8360_R04_CLK_CTRL1, 0x0000 }, + { NAU8360_R05_INTERRUPT_CTRL, 0x40ff }, + { NAU8360_R07_GP_CTRL, 0xaa30 }, + { NAU8360_R08_GP_CTRL0, 0x1e1e }, + { NAU8360_R09_GP_CTRL1, 0x1e1e }, + { NAU8360_R0A_GP_CTRL2, 0x0000 }, + { NAU8360_R0B_I2S_PCM_CTRL1, 0x0702 }, + { NAU8360_R0C_I2S_PCM_CTRL2, 0x0a10 }, + { NAU8360_R0D_I2S_PCM_CTRL3, 0x1300 }, + { NAU8360_R0E_I2S_DATA_CTRL1, 0x0304 }, + { NAU8360_R0F_I2S_DATA_CTRL2, 0x080b }, + { NAU8360_R10_I2S_DATA_CTRL3, 0x0014 }, + { NAU8360_R11_I2S_DATA_CTRL4, 0x0014 }, + { NAU8360_R12_PATH_CTRL, 0x0400 }, + { NAU8360_R17_I2S0_DATA_CTRL5, 0x0014 }, + { NAU8360_R1A_DSP_CORE_CTRL2, 0x0010 }, + { NAU8360_R2C_ALC_CTRL1, 0x2000 }, + { NAU8360_R2D_ALC_CTRL2, 0x8400 }, + { NAU8360_R2E_ALC_CTRL3, 0x2083 }, + { NAU8360_R31_UVLOP_CTRL1, 0x0000 }, + { NAU8360_R32_UVLOP_CTRL2, 0x8400 }, + { NAU8360_R33_UVLOP_CTRL3, 0x0000 }, + { NAU8360_R40_CLK_DET_CTRL, 0xca60 }, + { NAU8360_R41_CLK_CTL2, 0xc400 }, + { NAU8360_R5D_SINC_CFG, 0x0010 }, + { NAU8360_R5F_ANA_TRIM_CFG1, 0x8400 }, + { NAU8360_R60_RST, 0x0010 }, + { NAU8360_R67_ANALOG_CONTROL_0, 0x0160 }, + { NAU8360_R68_ANALOG_CONTROL_1, 0x00d4 }, + { NAU8360_R6A_SARADC_CFG0, 0x5c09 }, + { NAU8360_R6B_SARADC_CFG1, 0x0008 }, + { NAU8360_R6C_IVSNS_CFG0, 0xf040 }, + { NAU8360_R6D_IVSNS_CFG1, 0x3555 }, + { NAU8360_R6E_DAC_CFG0, 0x0ed5 }, + { NAU8360_R71_CLK_DIV_CFG, 0x0211 }, + { NAU8360_R72_PLL_CFG0, 0xccc4 }, + { NAU8360_R73_PLL_CFG1, 0x0101 }, + { NAU8360_R74_PLL_CFG2, 0x0000 }, + { NAU8360_R7A_DAC_TRIM_CFG2, 0x0000 }, + { NAU8360_R7B_IVSNS_TRIM_CFG, 0x0000 }, + { NAU8360_R7C_MISC_TRIM_CFG, 0x72d5 }, + { NAU8360_R86_HW3_CTL0, 0x2000 }, + { NAU8360_R88_ALC_CTRL6, 0x0000 }, + { NAU8360_R8A_HW3_VL_CTL7, 0xc000 }, + { NAU8360_R8B_HW3_VR_CTL8, 0xc000 }, + { NAU8360_R8C_HW3_CTL6, 0x0000 }, + { NAU8360_R8D_HW3_IL_CTL7, 0xc000 }, + { NAU8360_R8E_HW3_IR_CTL8, 0xc000 }, + { NAU8360_R8F_HW3_CTL9, 0x0000 }, + { NAU8360_R90_HW2_CTL0, 0x2000 }, + { NAU8360_R96_HW2_CTL6, 0x0000 }, + { NAU8360_R97_HW2_CTL7, 0xc000 }, + { NAU8360_R98_HW2_CTL8, 0xc000 }, + { NAU8360_R99_HW2_CTL9, 0x0000 }, + { NAU8360_R9A_HW1_CTL0, 0xc000 }, + { NAU8360_R9B_HW1_CTL1, 0xc000 }, + { NAU8360_R9C_HW1_CTL2, 0x0800 }, + { NAU8360_R9D_PEQ_CTL, 0x0001 }, + { NAU8360_RA0_LEFT_XODRC_CTRL, 0x0000 }, + { NAU8360_RA2_RIGHT_XODRC_CTRL, 0x0000 }, + { NAU8360_RA4_ANA_REG_0, 0x7f86 }, + { NAU8360_RA5_ANA_REG_1, 0x276e }, +}; + +static bool nau8360_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8360_R00_SOFTWARE_RST ... NAU8360_R12_PATH_CTRL: + case NAU8360_R17_I2S0_DATA_CTRL5: + case NAU8360_R1A_DSP_CORE_CTRL2: + case NAU8360_R21_VBAT_READOUT ... NAU8360_R22_TEMP_READOUT: + case NAU8360_R2C_ALC_CTRL1 ... NAU8360_R2E_ALC_CTRL3: + case NAU8360_R31_UVLOP_CTRL1 ... NAU8360_R33_UVLOP_CTRL3: + case NAU8360_R40_CLK_DET_CTRL ... NAU8360_R41_CLK_CTL2: + case NAU8360_R46_I2C_DEVICE_ID: + case NAU8360_R5D_SINC_CFG: + case NAU8360_R5F_ANA_TRIM_CFG1 ... NAU8360_R60_RST: + case NAU8360_R67_ANALOG_CONTROL_0 ... NAU8360_R6E_DAC_CFG0: + case NAU8360_R71_CLK_DIV_CFG ... NAU8360_R74_PLL_CFG2: + case NAU8360_R77_SOFT_SD ... NAU8360_R7C_MISC_TRIM_CFG: + case NAU8360_R7E_CLK_GATED_EN: + case NAU8360_R86_HW3_CTL0: + case NAU8360_R88_ALC_CTRL6: + case NAU8360_R8A_HW3_VL_CTL7 ... NAU8360_R90_HW2_CTL0: + case NAU8360_R96_HW2_CTL6 ... NAU8360_R9D_PEQ_CTL: + case NAU8360_RA0_LEFT_XODRC_CTRL: + case NAU8360_RA2_RIGHT_XODRC_CTRL: + case NAU8360_RA4_ANA_REG_0 ... NAU8360_RA5_ANA_REG_1: + case NAU8360_R100_LEFT_BIQ0_COE ... (NAU8360_R100_LEFT_BIQ0_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R10C_LEFT_BIQ1_COE ... (NAU8360_R10C_LEFT_BIQ1_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R118_LEFT_BIQ2_COE ... (NAU8360_R118_LEFT_BIQ2_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R124_LEFT_BIQ3_COE ... (NAU8360_R124_LEFT_BIQ3_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R130_LEFT_BIQ4_COE ... (NAU8360_R130_LEFT_BIQ4_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R13C_LEFT_BIQ5_COE ... (NAU8360_R13C_LEFT_BIQ5_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R148_LEFT_BIQ6_COE ... (NAU8360_R148_LEFT_BIQ6_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R154_LEFT_BIQ7_COE ... (NAU8360_R154_LEFT_BIQ7_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R160_LEFT_BIQ8_COE ... (NAU8360_R160_LEFT_BIQ8_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R16C_LEFT_BIQ9_COE ... (NAU8360_R16C_LEFT_BIQ9_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R178_LEFT_BIQ10_COE ... (NAU8360_R178_LEFT_BIQ10_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R184_LEFT_BIQ11_COE ... (NAU8360_R184_LEFT_BIQ11_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R190_LEFT_BIQ12_COE ... (NAU8360_R190_LEFT_BIQ12_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R19C_LEFT_BIQ13_COE ... (NAU8360_R19C_LEFT_BIQ13_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R1A8_LEFT_BIQ14_COE ... (NAU8360_R1A8_LEFT_BIQ14_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R200_RIGHT_BIQ0_COE ... (NAU8360_R200_RIGHT_BIQ0_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R20C_RIGHT_BIQ1_COE ... (NAU8360_R20C_RIGHT_BIQ1_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R218_RIGHT_BIQ2_COE ... (NAU8360_R218_RIGHT_BIQ2_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R224_RIGHT_BIQ3_COE ... (NAU8360_R224_RIGHT_BIQ3_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R230_RIGHT_BIQ4_COE ... (NAU8360_R230_RIGHT_BIQ4_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R23C_RIGHT_BIQ5_COE ... (NAU8360_R23C_RIGHT_BIQ5_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R248_RIGHT_BIQ6_COE ... (NAU8360_R248_RIGHT_BIQ6_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R254_RIGHT_BIQ7_COE ... (NAU8360_R254_RIGHT_BIQ7_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R260_RIGHT_BIQ8_COE ... (NAU8360_R260_RIGHT_BIQ8_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R26C_RIGHT_BIQ9_COE ... (NAU8360_R26C_RIGHT_BIQ9_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R278_RIGHT_BIQ10_COE ... (NAU8360_R278_RIGHT_BIQ10_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R284_RIGHT_BIQ11_COE ... (NAU8360_R284_RIGHT_BIQ11_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R290_RIGHT_BIQ12_COE ... (NAU8360_R290_RIGHT_BIQ12_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R29C_RIGHT_BIQ13_COE ... (NAU8360_R29C_RIGHT_BIQ13_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R2A8_RIGHT_BIQ14_COE ... (NAU8360_R2A8_RIGHT_BIQ14_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_RF000_DSP_COMM: + case NAU8360_RF002_DSP_COMM: + return true; + default: + return false; + } + +} + +static bool nau8360_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8360_R00_SOFTWARE_RST ... NAU8360_R12_PATH_CTRL: + case NAU8360_R17_I2S0_DATA_CTRL5: + case NAU8360_R1A_DSP_CORE_CTRL2: + case NAU8360_R2C_ALC_CTRL1 ... NAU8360_R2E_ALC_CTRL3: + case NAU8360_R31_UVLOP_CTRL1 ... NAU8360_R33_UVLOP_CTRL3: + case NAU8360_R40_CLK_DET_CTRL ... NAU8360_R41_CLK_CTL2: + case NAU8360_R5D_SINC_CFG: + case NAU8360_R5F_ANA_TRIM_CFG1: + case NAU8360_R60_RST: + case NAU8360_R67_ANALOG_CONTROL_0 ... NAU8360_R68_ANALOG_CONTROL_1: + case NAU8360_R6A_SARADC_CFG0 ... NAU8360_R6E_DAC_CFG0: + case NAU8360_R71_CLK_DIV_CFG ... NAU8360_R74_PLL_CFG2: + case NAU8360_R77_SOFT_SD ... NAU8360_R7C_MISC_TRIM_CFG: + case NAU8360_R7E_CLK_GATED_EN: + case NAU8360_R86_HW3_CTL0: + case NAU8360_R88_ALC_CTRL6: + case NAU8360_R8A_HW3_VL_CTL7 ... NAU8360_R90_HW2_CTL0: + case NAU8360_R96_HW2_CTL6 ... NAU8360_R9D_PEQ_CTL: + case NAU8360_RA4_ANA_REG_0 ... NAU8360_RA5_ANA_REG_1: + case NAU8360_R100_LEFT_BIQ0_COE ... (NAU8360_R100_LEFT_BIQ0_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R10C_LEFT_BIQ1_COE ... (NAU8360_R10C_LEFT_BIQ1_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R118_LEFT_BIQ2_COE ... (NAU8360_R118_LEFT_BIQ2_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R124_LEFT_BIQ3_COE ... (NAU8360_R124_LEFT_BIQ3_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R130_LEFT_BIQ4_COE ... (NAU8360_R130_LEFT_BIQ4_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R13C_LEFT_BIQ5_COE ... (NAU8360_R13C_LEFT_BIQ5_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R148_LEFT_BIQ6_COE ... (NAU8360_R148_LEFT_BIQ6_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R154_LEFT_BIQ7_COE ... (NAU8360_R154_LEFT_BIQ7_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R160_LEFT_BIQ8_COE ... (NAU8360_R160_LEFT_BIQ8_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R16C_LEFT_BIQ9_COE ... (NAU8360_R16C_LEFT_BIQ9_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R178_LEFT_BIQ10_COE ... (NAU8360_R178_LEFT_BIQ10_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R184_LEFT_BIQ11_COE ... (NAU8360_R184_LEFT_BIQ11_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R190_LEFT_BIQ12_COE ... (NAU8360_R190_LEFT_BIQ12_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R19C_LEFT_BIQ13_COE ... (NAU8360_R19C_LEFT_BIQ13_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R1A8_LEFT_BIQ14_COE ... (NAU8360_R1A8_LEFT_BIQ14_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R200_RIGHT_BIQ0_COE ... (NAU8360_R200_RIGHT_BIQ0_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R20C_RIGHT_BIQ1_COE ... (NAU8360_R20C_RIGHT_BIQ1_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R218_RIGHT_BIQ2_COE ... (NAU8360_R218_RIGHT_BIQ2_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R224_RIGHT_BIQ3_COE ... (NAU8360_R224_RIGHT_BIQ3_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R230_RIGHT_BIQ4_COE ... (NAU8360_R230_RIGHT_BIQ4_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R23C_RIGHT_BIQ5_COE ... (NAU8360_R23C_RIGHT_BIQ5_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R248_RIGHT_BIQ6_COE ... (NAU8360_R248_RIGHT_BIQ6_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R254_RIGHT_BIQ7_COE ... (NAU8360_R254_RIGHT_BIQ7_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R260_RIGHT_BIQ8_COE ... (NAU8360_R260_RIGHT_BIQ8_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R26C_RIGHT_BIQ9_COE ... (NAU8360_R26C_RIGHT_BIQ9_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R278_RIGHT_BIQ10_COE ... (NAU8360_R278_RIGHT_BIQ10_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R284_RIGHT_BIQ11_COE ... (NAU8360_R284_RIGHT_BIQ11_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R290_RIGHT_BIQ12_COE ... (NAU8360_R290_RIGHT_BIQ12_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R29C_RIGHT_BIQ13_COE ... (NAU8360_R29C_RIGHT_BIQ13_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_R2A8_RIGHT_BIQ14_COE ... (NAU8360_R2A8_RIGHT_BIQ14_COE + + NAU8360_PEQ_REG_WIDTH): + case NAU8360_RF000_DSP_COMM: + case NAU8360_RF002_DSP_COMM: + return true; + default: + return false; + } +} + +static bool nau8360_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NAU8360_R00_SOFTWARE_RST ... NAU8360_R02_I2C_ADDR: + case NAU8360_R06_INT_CLR_STATUS: + case NAU8360_R21_VBAT_READOUT ... NAU8360_R22_TEMP_READOUT: + case NAU8360_R41_CLK_CTL2: + case NAU8360_R46_I2C_DEVICE_ID: + case NAU8360_R69_ANALOG_CONTROL_3: + case NAU8360_R77_SOFT_SD ... NAU8360_R79_EN_HIRC48M: + case NAU8360_R7E_CLK_GATED_EN: + case NAU8360_R9D_PEQ_CTL: + case NAU8360_RF000_DSP_COMM: + case NAU8360_RF002_DSP_COMM: + return true; + default: + return false; + } +} + +static int nau8360_get_tdm_chan_len(struct nau8360 *nau8360) +{ + int val = 0; + + regmap_read(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, &val); + val = (val & NAU8360_TDM_CLEN_MASK) >> NAU8360_TDM_CLEN_SFT; + + return (val << 3) + 16; +} + +static inline bool nau8360_dsp_active(struct snd_soc_component *comp) +{ + return (snd_soc_component_read(comp, NAU8360_R12_PATH_CTRL) & + NAU8360_DAC_SEL_DSP); +} + +static int nau8360_anc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int ret, value = NAU8360_PEQ_BAND_8; + + mutex_lock(&nau8360->lock); + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + /* update anc flag if return value 1 and register value changed */ + if (ret != 1) + goto unlock; + + nau8360->anc_enable = ucontrol->value.integer.value[0]; + if (nau8360_dsp_active(cp)) + value = nau8360->anc_enable ? NAU8360_PEQ_BAND_15 : NAU8360_PEQ_BAND_12; + regmap_update_bits(nau8360->regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, + value << NAU8360_PEQ_BAND_SFT); + +unlock: + mutex_unlock(&nau8360->lock); + + return ret; +} + +static inline void nau8360_peq_mem_enable(struct regmap *regmap, bool enable) +{ + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, + NAU8360_HW1_MEM_TEST, enable ? NAU8360_HW1_MEM_TEST : 0); +} + +static inline int nau8360_peq_regaddr(const char *id_name) +{ + int reg, band_num, dsp_addr = NAU8360_DSP_ADDR_BYNAME(id_name); + char *band = strstr(id_name, "BIQ"); + + if (!band || kstrtoint((band + 3), 10, &band_num)) + return -EINVAL; + reg = dsp_addr == NAU8360_RF000_DSP_COMM ? NAU8360_R100_LEFT_BIQ0_COE : + NAU8360_R200_RIGHT_BIQ0_COE; + reg += band_num * NAU8360_TOT_BAND_COE_RANGE; + + return reg; +} + +static int nau8360_peq_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(cp); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + int i, value, reg, ret = 0; + __be16 *val = (__be16 *)ucontrol->value.bytes.data; + + /* Use the DAPM lock to prevent race conditions during DAPM power-up + * state transitions, and check component active status to prohibit + * PEQ access during active audio streams (playback and capture). + */ + snd_soc_dapm_mutex_lock(dapm); + if (snd_soc_component_active(cp)) { + dev_dbg(nau8360->dev, + "PEQ coefficient access is ignored during audio is active"); + ret = -EBUSY; + goto unlock_dapm; + } + + reg = nau8360_peq_regaddr(kcontrol->id.name); + if (reg < 0) { + ret = reg; + goto unlock_dapm; + } + + mutex_lock(&nau8360->lock); + nau8360_peq_mem_enable(nau8360->regmap, true); + for (i = 0; i < params->max / sizeof(u16); i++) { + value = snd_soc_component_read(cp, reg + i); + *(val + i) = cpu_to_be16(value); + } + nau8360_peq_mem_enable(nau8360->regmap, false); + mutex_unlock(&nau8360->lock); + +unlock_dapm: + snd_soc_dapm_mutex_unlock(dapm); + + return ret; +} + +static int nau8360_peq_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(cp); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + int i, reg, ret = 0; + __be16 *data = NULL; + bool changed = false; + + /* Use the DAPM lock to prevent race conditions during DAPM power-up + * state transitions, and check component active status to prohibit + * PEQ access during active audio streams (playback and capture). + */ + snd_soc_dapm_mutex_lock(dapm); + if (snd_soc_component_active(cp)) { + dev_dbg(nau8360->dev, + "PEQ coefficient access is ignored during audio is active"); + ret = -EBUSY; + goto unlock_dapm; + } + + reg = nau8360_peq_regaddr(kcontrol->id.name); + if (reg < 0) { + ret = reg; + goto unlock_dapm; + } + + data = kmemdup(ucontrol->value.bytes.data, params->max, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto unlock_dapm; + } + + mutex_lock(&nau8360->lock); + nau8360_peq_mem_enable(nau8360->regmap, true); + for (i = 0; i < params->max / sizeof(u16); i++) { + if (snd_soc_component_read(cp, reg + i) != be16_to_cpu(*(data + i))) { + snd_soc_component_write(cp, reg + i, be16_to_cpu(*(data + i))); + changed = true; + } + } + nau8360_peq_mem_enable(nau8360->regmap, false); + mutex_unlock(&nau8360->lock); + + ret = changed ? 1 : 0; + +unlock_dapm: + snd_soc_dapm_mutex_unlock(dapm); + kfree(data); + + return ret; +} + +#define NAU8360_PEQ_COEF_BYTES_EXT(ch, band) \ + SND_SOC_BYTES_EXT(ch " PEQ Coefficients " band, 20, nau8360_peq_coeff_get, \ + nau8360_peq_coeff_put) + +static const struct snd_kcontrol_new nau8360_snd_controls[] = { + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ0"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ1"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ2"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ3"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ4"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ5"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ6"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ7"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ8"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ9"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ10"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ11"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ12"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ13"), + NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ14"), + + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ0"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ1"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ2"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ3"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ4"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ5"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ6"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ7"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ8"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ9"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ10"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ11"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ12"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ13"), + NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ14"), + SOC_SINGLE("Low Latency Switch", NAU8360_R96_HW2_CTL6, + NAU8360_HW2_LATENCY_SFT, 1, 0), + SOC_SINGLE_EXT("ANC Path Switch", NAU8360_R96_HW2_CTL6, NAU8360_HW1_ANC_EN_SFT, + 1, 0, snd_soc_get_volsw, nau8360_anc_put), +}; + +static int nau8360_adci_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) + snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_ISNS_R_PMD | NAU8360_PD_ISNS_L_PMD, 0); + else if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_ISNS_R_PMD | NAU8360_PD_ISNS_L_PMD, + NAU8360_PD_ISNS_R_PMD | NAU8360_PD_ISNS_L_PMD); + + return 0; +} + +static int nau8360_adcv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) + snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_VSNS_R_PMD | NAU8360_PD_VSNS_L_PMD, 0); + else if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_VSNS_R_PMD | NAU8360_PD_VSNS_L_PMD, + NAU8360_PD_VSNS_R_PMD | NAU8360_PD_VSNS_L_PMD); + + return 0; +} + +static int nau8360_aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_HV_EN, 0); + + return 0; +} + +static int nau8360_hw1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_ANA_MUTE, NAU8360_ANA_MUTE); + + return 0; +} + +static int nau8360_hw1_mux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_component_update_bits(component, NAU8360_R68_ANALOG_CONTROL_1, + NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL, 0); + snd_soc_component_update_bits(component, NAU8360_RA5_ANA_REG_1, + NAU8360_CLASSD_SHT_IN | NAU8360_HVEN_SYNC_SAW, + NAU8360_CLASSD_SHT_IN | NAU8360_HVEN_SYNC_SAW); + msleep(20); + } + + return 0; +} + +static int nau8360_hw2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + snd_soc_component_update_bits(component, NAU8360_R99_HW2_CTL9, + NAU8360_HW2_CH_MUTE, 0); + snd_soc_component_update_bits(component, NAU8360_R9C_HW1_CTL2, + NAU8360_HW1_CH_MUTE, 0); + } + + return 0; +} + +static int nau8360_dac_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm); + unsigned int mask = 1 << w->shift; + + if (SND_SOC_DAPM_EVENT_ON(event)) + snd_soc_component_update_bits(cp, NAU8360_R6E_DAC_CFG0, mask, 0); + else if (SND_SOC_DAPM_EVENT_OFF(event)) + snd_soc_component_update_bits(cp, NAU8360_R6E_DAC_CFG0, mask, mask); + + return 0; +} + +static int nau8360_hv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* enable Class D HV power after DAC power is stable */ + snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_HV_EN, NAU8360_HV_EN); + msleep(50); + /* Class D modulator input short setting for mute and de-pop purpose. + * Restore normal after initiation. Set segment driver as full driving + * strength. + */ + snd_soc_component_update_bits(component, NAU8360_RA5_ANA_REG_1, + NAU8360_CLASSD_SHT_IN | NAU8360_HVEN_SYNC_SAW, 0); + snd_soc_component_update_bits(component, NAU8360_R68_ANALOG_CONTROL_1, + NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL, + NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL); + + snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_ANA_MUTE, 0); + } else if (SND_SOC_DAPM_EVENT_OFF(event)) { + snd_soc_component_update_bits(component, NAU8360_R9C_HW1_CTL2, + NAU8360_HW1_CH_MUTE, NAU8360_HW1_CH_MUTE); + snd_soc_component_update_bits(component, NAU8360_R99_HW2_CTL9, + NAU8360_HW2_CH_MUTE, NAU8360_HW2_CH_MUTE); + } + + return 0; +} + +static inline void nau8360_dsp_enable(struct regmap *regmap, bool enable) +{ + regmap_update_bits(regmap, NAU8360_R86_HW3_CTL0, NAU8360_HW3_STALL, + enable ? 0 : NAU8360_HW3_STALL); + regmap_update_bits(regmap, NAU8360_R1A_DSP_CORE_CTRL2, NAU8360_DSP_RUNSTALL, + enable ? 0 : NAU8360_DSP_RUNSTALL); +} + +static void nau8360_dsp_switch(struct snd_soc_component *component, bool enable) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8360->regmap; + int value = NAU8360_PEQ_BAND_8; + + mutex_lock(&nau8360->lock); + + /* If DSP is enabled, unstall HW3 engine and DSP, loading DSP firmware, + * and configure PEQ after dsp reset. + */ + if (enable) { + value = nau8360->anc_enable ? NAU8360_PEQ_BAND_15 : NAU8360_PEQ_BAND_12; + nau8360_dsp_enable(regmap, true); + } else { + dev_dbg(nau8360->dev, "Bypass DSP path"); + nau8360_dsp_enable(regmap, false); + } + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, + value << NAU8360_PEQ_BAND_SFT); + + mutex_unlock(&nau8360->lock); +} + +static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol); + struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + int ret = 0; + + snd_soc_dapm_mutex_lock(dapm); + if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) { + dev_warn_ratelimited(nau8360->dev, "changing path is not allowed during playback"); + snd_soc_dapm_mutex_unlock(dapm); + return -EBUSY; + } + + if (item[0] == NAU8360_DAC_SRC_DSP && !nau8360->load_fw_done) { + dev_warn_ratelimited(nau8360->dev, "Cannot enable DSP: Firmware not ready or disabled\n"); + snd_soc_dapm_mutex_unlock(dapm); + return -EBUSY; + } + snd_soc_dapm_mutex_unlock(dapm); + + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + if (ret <= 0) + return ret; + + nau8360_dsp_switch(component, snd_soc_enum_item_to_val(e, item[0])); + + return ret; +} + +/* Select HW1 output source (enables PEQ bypass) */ +static const char *const nau8360_hw1out_src[] = { "Audio", "PEQ" }; + +static SOC_ENUM_SINGLE_DECL(nau8360_hw1out_enum, NAU8360_R12_PATH_CTRL, + NAU8360_SEL_HW1_SFT, nau8360_hw1out_src); + +static const struct snd_kcontrol_new nau8360_hw1out_mux = + SOC_DAPM_ENUM("HW1 Output Source", nau8360_hw1out_enum); + +/* Select DAC input source (enables DSP bypass) */ +static const char *const nau8360_dac_src[] = { "HW1", "DSP" }; + +static SOC_ENUM_SINGLE_DECL(nau8360_dac_enum, NAU8360_R12_PATH_CTRL, + NAU8360_DAC_SEL_SFT, nau8360_dac_src); + +static const struct snd_kcontrol_new nau8360_dac_mux = + SOC_DAPM_ENUM_EXT("DAC Source", nau8360_dac_enum, + snd_soc_dapm_get_enum_double, nau8360_dac_mux_put_enum); + +static const struct snd_soc_dapm_widget nau8360_dapm_widgets[] = { + SND_SOC_DAPM_SIGGEN("Sense"), + SND_SOC_DAPM_ADC_E("ADC_I", NULL, SND_SOC_NOPM, 0, 0, nau8360_adci_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC_V", NULL, SND_SOC_NOPM, 0, 0, nau8360_adcv_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA("DSP", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("HW3 Engine", NAU8360_R8C_HW3_CTL6, 3, 0, NULL, 0), + + SND_SOC_DAPM_AIF_IN_E("AIFRX", "Playback", 0, SND_SOC_NOPM, 0, 0, + nau8360_aif_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_S("HW1 Engine", 0, NAU8360_R9D_PEQ_CTL, 0, 1, + nau8360_hw1_event, SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX_E("HW1 Mux", SND_SOC_NOPM, 0, 0, &nau8360_hw1out_mux, + nau8360_hw1_mux_event, SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, &nau8360_dac_mux), + + SND_SOC_DAPM_PGA_S("HW2 Engine", 1, NAU8360_R96_HW2_CTL6, 5, 0, + nau8360_hw2_event, SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_SUPPLY("DAC Clock", NAU8360_R71_CLK_DIV_CFG, 9, 1, NULL, 0), + SND_SOC_DAPM_DAC_E("DACL", NULL, SND_SOC_NOPM, NAU8360_PD_DACL_SFT, 0, + nau8360_dac_power_event, SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("DACR", NULL, SND_SOC_NOPM, NAU8360_PD_DACR_SFT, 0, + nau8360_dac_power_event, SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_S("ADACL", 2, SND_SOC_NOPM, NAU8360_PD_DACL_SFT, 0, + nau8360_dac_power_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("ADACR", 2, SND_SOC_NOPM, NAU8360_PD_DACR_SFT, 0, + nau8360_dac_power_event, SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_S("Class D", 3, SND_SOC_NOPM, 0, 0, + nau8360_hv_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), +}; + +static const struct snd_soc_dapm_route nau8360_dapm_routes[] = { + { "ADC_I", NULL, "Sense" }, + { "ADC_V", NULL, "Sense" }, + { "HW3 Engine", NULL, "ADC_I" }, + { "HW3 Engine", NULL, "ADC_V" }, + { "Capture", NULL, "HW3 Engine" }, + + { "HW1 Engine", NULL, "AIFRX" }, + { "HW1 Mux", "Audio", "AIFRX" }, + { "HW1 Mux", "PEQ", "HW1 Engine" }, + + { "DSP", NULL, "HW1 Mux" }, + { "DAC Mux", "HW1", "HW1 Mux" }, + { "DAC Mux", "DSP", "DSP" }, + + { "HW2 Engine", NULL, "DAC Mux" }, + { "DACL", NULL, "HW2 Engine" }, + { "DACR", NULL, "HW2 Engine" }, + { "DACL", NULL, "DAC Clock" }, + { "DACR", NULL, "DAC Clock" }, + + { "ADACL", NULL, "DACL" }, + { "ADACR", NULL, "DACR" }, + { "Class D", NULL, "ADACL" }, + { "Class D", NULL, "ADACR" }, + + { "OUTL", NULL, "Class D" }, + { "OUTR", NULL, "Class D" }, +}; + +static int nau8360_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + unsigned int i2s_mask = NAU8360_FRAME_START_MASK | NAU8360_RX_OFFSET_MASK; + unsigned int i2s_fmt = NAU8360_FRAME_START_H2L | NAU8360_RX_OFFSET_I2S; + int val = 0; + + flush_work(&nau8360->load_fw_work); + + if (nau8360_dsp_active(component) && !nau8360->load_fw_done) { + dev_warn(nau8360->dev, "DSP firmware is not ready yet!"); + return -EBUSY; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_read(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, &val); + if ((val & i2s_mask) == i2s_fmt) + regmap_update_bits(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, + NAU8360_EN_TDM_RX, NAU8360_EN_TDM_RX); + + if (nau8360_dsp_active(component)) + snd_soc_dapm_enable_pin(nau8360->dapm, "Sense"); + } + + return 0; +} + +static void nau8360_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + unsigned int tdm_mask; + + tdm_mask = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + NAU8360_EN_TDM_RX : NAU8360_EN_TDM_TX; + regmap_update_bits(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, + tdm_mask, 0); + if (nau8360_dsp_active(component) && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dapm_disable_pin(nau8360->dapm, "Sense"); +} + +static int nau8360_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + unsigned int val_len, val_srate; + int dlen = params_width(params); + + if (dlen > nau8360_get_tdm_chan_len(nau8360)) { + dev_err(nau8360->dev, "Invalid data length"); + return -EINVAL; + } + + switch (dlen) { + case 16: + val_len = NAU8360_TDM_DLEN_16; + break; + case 20: + val_len = NAU8360_TDM_DLEN_20; + break; + case 24: + val_len = NAU8360_TDM_DLEN_24; + break; + case 32: + val_len = NAU8360_TDM_DLEN_32; + break; + default: + return -EINVAL; + } + + switch (params_rate(params)) { + case 16000: + val_srate = NAU8360_SRATE_16000; + break; + case 32000: + val_srate = NAU8360_SRATE_32000; + break; + case 44100: + case 48000: + val_srate = NAU8360_SRATE_48000; + break; + case 88200: + case 96000: + val_srate = NAU8360_SRATE_96000; + break; + case 176400: + case 192000: + val_srate = NAU8360_SRATE_192000; + break; + default: + return -EINVAL; + } + + regmap_update_bits(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, + NAU8360_TDM_DLEN_MASK, val_len); + regmap_update_bits(nau8360->regmap, NAU8360_R40_CLK_DET_CTRL, + NAU8360_SRATE_MASK, val_srate); + + return 0; +} + +static int nau8360_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + unsigned int ctrl_val, ctrl1_val; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl_val = NAU8360_FRAME_START_H2L | NAU8360_RX_OFFSET_I2S; + ctrl1_val = NAU8360_TX_OFFSET_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl_val = NAU8360_RX_OFFSET_LEFT | NAU8360_RX_LEFT_JUSTIFY; + ctrl1_val = NAU8360_TX_OFFSET_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl_val = NAU8360_RX_OFFSET_RIGHT | NAU8360_RX_RIGHT_JUSTIFY; + ctrl1_val = NAU8360_TX_OFFSET_RIGHT; + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl_val = NAU8360_RX_OFFSET_PCM_A; + ctrl1_val = NAU8360_TX_OFFSET_PCM_A; + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl_val = NAU8360_RX_OFFSET_PCM_B; + ctrl1_val = NAU8360_TX_OFFSET_PCM_B; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, NAU8360_R0B_I2S_PCM_CTRL1, + NAU8360_FRAME_START_MASK | NAU8360_RX_OFFSET_MASK | + NAU8360_RX_JUSTIFY_MASK, ctrl_val); + snd_soc_component_update_bits(component, NAU8360_R0D_I2S_PCM_CTRL3, + NAU8360_TX_OFFSET_MASK, ctrl1_val); + return 0; +} + +static void nau8360_set_tdm_rx_slot(struct snd_soc_component *cp, int type, int slot) +{ + switch (type) { + case NAU8360_TDM_DACL: + snd_soc_component_update_bits(cp, NAU8360_R0C_I2S_PCM_CTRL2, + NAU8360_RX_DACL_MASK, slot); + break; + + case NAU8360_TDM_DACR: + snd_soc_component_update_bits(cp, NAU8360_R0C_I2S_PCM_CTRL2, + NAU8360_RX_DACR_MASK, (slot << NAU8360_RX_DACR_SFT)); + break; + + case NAU8360_TDM_ANCL: + snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_RX_ANC_L_MASK, (slot << NAU8360_RX_ANC_L_SFT)); + break; + + case NAU8360_TDM_ANCR: + snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_RX_ANC_R_MASK, (slot << NAU8360_RX_ANC_R_SFT)); + break; + } +} + +static void nau8360_set_tdm_tx_slot(struct snd_soc_component *cp, int type, int slot) +{ + switch (type) { + case NAU8360_TDM_AECL: + snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, + NAU8360_AEC_L_SLOT_MASK, slot << NAU8360_AEC_L_SLOT_SFT); + break; + + case NAU8360_TDM_AECR: + snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, + NAU8360_AEC_R_SLOT_MASK, slot); + break; + + case NAU8360_TDM_ISNSL: + snd_soc_component_update_bits(cp, NAU8360_R0E_I2S_DATA_CTRL1, + NAU8360_ISNS_L_SLOT_MASK, slot << NAU8360_ISNS_L_SLOT_SFT); + break; + + case NAU8360_TDM_ISNSR: + snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_ISNS_R_SLOT_MASK, slot); + break; + + case NAU8360_TDM_VSNSL: + snd_soc_component_update_bits(cp, NAU8360_R0D_I2S_PCM_CTRL3, + NAU8360_VSNS_L_SLOT_MASK, slot); + break; + + case NAU8360_TDM_VSNSR: + snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_VSNS_R_SLOT_MASK, slot << NAU8360_VSNS_R_SLOT_SFT); + break; + + case NAU8360_TDM_TJ: + snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_TEMP_SLOT_MASK, slot); + break; + + case NAU8360_TDM_VBAT: + snd_soc_component_update_bits(cp, NAU8360_R0F_I2S_DATA_CTRL2, + NAU8360_VBAT_SLOT_MASK, slot << NAU8360_VBAT_SLOT_SFT); + break; + } +} + +static void nau8360_set_tdm_tx_func(struct snd_soc_component *cp, int type, bool enable) +{ + switch (type) { + case NAU8360_TDM_AECL: + snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, + NAU8360_AEC_L_EN, enable ? NAU8360_AEC_L_EN : 0); + break; + case NAU8360_TDM_AECR: + snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, + NAU8360_AEC_R_EN, enable ? NAU8360_AEC_R_EN : 0); + break; + case NAU8360_TDM_ISNSL: + snd_soc_component_update_bits(cp, NAU8360_R0E_I2S_DATA_CTRL1, + NAU8360_ISNS_L_TX_EN, enable ? NAU8360_ISNS_L_TX_EN : 0); + break; + case NAU8360_TDM_ISNSR: + snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_ISNS_R_TX_EN, enable ? NAU8360_ISNS_R_TX_EN : 0); + break; + case NAU8360_TDM_VSNSL: + snd_soc_component_update_bits(cp, NAU8360_R0D_I2S_PCM_CTRL3, + NAU8360_VSNS_L_TX_EN, enable ? NAU8360_VSNS_L_TX_EN : 0); + break; + case NAU8360_TDM_VSNSR: + snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_VSNS_R_TX_EN, enable ? NAU8360_VSNS_R_TX_EN : 0); + break; + case NAU8360_TDM_TJ: + snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_TEMP_TX_EN, enable ? NAU8360_TEMP_TX_EN : 0); + break; + case NAU8360_TDM_VBAT: + snd_soc_component_update_bits(cp, NAU8360_R0F_I2S_DATA_CTRL2, + NAU8360_VBAT_TX_EN, enable ? NAU8360_VBAT_TX_EN : 0); + break; + } +} + +static int nau8360_validate_tdm_slots(struct device *dev, unsigned int mask, + const u32 *func_slots, const char * const *func_names, + int num_funcs, const char *dir, + unsigned int *slot_used) +{ + int i; + unsigned int func_slot; + *slot_used = 0; + + if (!mask) + return 0; + + for (i = 0; i < num_funcs; i++) { + func_slot = func_slots[i]; + if (func_slot == TDM_SLOT_NONE) { + dev_warn(dev, "%s %s slot disabled", + dir, func_names[i]); + continue; + } + + if (!(mask & BIT(func_slot))) { + dev_warn(dev, "%s %s mapped to slot %d, but disabled by mask!", + dir, func_names[i], func_slot); + continue; + } + + if (*slot_used & BIT(func_slot)) { + dev_err(dev, "%s %s slot %d collision!", + dir, func_names[i], func_slot); + return -EINVAL; + } + + *slot_used |= BIT(func_slot); + } + + return 0; +} + +static void nau8360_enable_tdm_channels(struct snd_soc_component *cp, + int rx_slot_used, int tx_slot_used) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int i, slot; + unsigned int val = 0; + bool enable; + + for (i = 0; i < NAU8360_TDM_TXN; i++) { + slot = nau8360->tdm_tx_func_slot[i]; + enable = (slot != TDM_SLOT_NONE) && (tx_slot_used & BIT(slot)); + nau8360_set_tdm_tx_func(cp, i, enable); + } + + if (rx_slot_used) + val |= NAU8360_EN_TDM_RX; + if (tx_slot_used) + val |= NAU8360_EN_TDM_TX; + + snd_soc_component_update_bits(cp, NAU8360_R0B_I2S_PCM_CTRL1, + NAU8360_EN_TDM_RX | NAU8360_EN_TDM_TX, val); +} + +static void nau8360_tdm_apply(struct snd_soc_component *cp, int slot_width) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int i, chan_tx; + + for (i = 0; i < NAU8360_TDM_TXN; i++) { + if (nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE) + continue; + + /* compute the slot location in bytes according to slot/chan width */ + chan_tx = (slot_width >> 3) * nau8360->tdm_tx_func_slot[i]; + nau8360_set_tdm_tx_slot(cp, i, chan_tx); + } + + regmap_update_bits(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, + NAU8360_TDM_CLEN_MASK, + ((slot_width - 16) >> 3) << NAU8360_TDM_CLEN_SFT); +} + +static int nau8360_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_component *cp = dai->component; + struct device *dev = cp->dev; + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + unsigned int tx_slot_used = 0, rx_slot_used = 0; + int ret = 0; + + if (!slots || !slot_width) { + nau8360_enable_tdm_channels(cp, 0, 0); + return ret; + } + + if (slots > NAU8360_TDM_MAX_CHAN) { + dev_err(dev, "Invalid TDM slots: %d", slots); + return -EINVAL; + } + + if (slot_width != 16 && slot_width != 24 && slot_width != 32) { + dev_err(dev, "Invalid TDM channel length: %d", slot_width); + return -EINVAL; + } + + ret = nau8360_validate_tdm_slots(cp->dev, rx_mask, + nau8360->tdm_rx_func_slot, + nau8360_rx_func_names, + NAU8360_TDM_RXN, "RX", + &rx_slot_used); + if (ret < 0) + goto err; + + ret = nau8360_validate_tdm_slots(cp->dev, tx_mask, + nau8360->tdm_tx_func_slot, + nau8360_tx_func_names, + NAU8360_TDM_TXN, "TX", + &tx_slot_used); + if (ret < 0) + goto err; + + if (nau8360_get_tdm_chan_len(nau8360) != slot_width) + nau8360_tdm_apply(cp, slot_width); + + nau8360_enable_tdm_channels(cp, rx_slot_used, tx_slot_used); + + dev_dbg(dev, "TDM: tx_mask 0x%x, rx_mask 0x%x", tx_mask, rx_mask); +err: + return ret; +} + +static inline int dyn_clk_div(int div_max, int fin, int fout) +{ + int clk_div; + + if (!fin || !fout) + return -EINVAL; + + for (clk_div = 0; clk_div <= div_max; clk_div++) + if (fin / (clk_div + 1) <= fout) + break; + if (clk_div > div_max) + return -EINVAL; + + return clk_div; +} + +static inline int tab_clk_div(const int clk_div[], int num_div, int fin) +{ + int i; + + if (!fin) + return -EINVAL; + + for (i = 0; i < num_div; i++) + if ((fin / clk_div[i]) <= CLK_DA_IVSNS_MAX) + break; + if (i == num_div) + return -EINVAL; + + return i; +} + +static int nau8360_set_sysclk_output(struct nau8360 *nau8360, unsigned int freq) +{ + struct device *dev = nau8360->dev; + int ratio = 0, mclk_rate; + + if (freq < MASTER_CLK_MIN || freq > MASTER_CLK_MAX) { + dev_err(dev, "system clock %d Hz exceed range", freq); + return -EINVAL; + } + + if (freq % ADSP_SR_48000 == 0) + ratio = freq / ADSP_SR_48000; + else if (freq % ADSP_SR_44100 == 0) + ratio = freq / ADSP_SR_44100; + + switch (ratio) { + case NAU8360_MCLK_FS_RATIO_250: + mclk_rate = NAU8360_MCLK_RATE_12000; + break; + case NAU8360_MCLK_FS_RATIO_256: + mclk_rate = NAU8360_MCLK_RATE_12288; + break; + case NAU8360_MCLK_FS_RATIO_400: + mclk_rate = NAU8360_MCLK_RATE_19200; + break; + case NAU8360_MCLK_FS_RATIO_500: + mclk_rate = NAU8360_MCLK_RATE_24000; + break; + case NAU8360_MCLK_FS_RATIO_512: + mclk_rate = NAU8360_MCLK_RATE_24576; + break; + default: + dev_err(dev, "invalid sysclk %d", freq); + return -EINVAL; + } + + dev_dbg(dev, "sysclk %d Hz, ratio %d", freq, ratio); + + nau8360->sys_clk = freq; + regmap_update_bits(nau8360->regmap, NAU8360_R40_CLK_DET_CTRL, + NAU8360_MCLK_RATE_MASK, mclk_rate); + + return 0; +} + +static int nau8360_dig_sys_clk(struct nau8360 *nau8360, int source, unsigned int freq) +{ + struct device *dev = nau8360->dev; + struct regmap *regmap = nau8360->regmap; + int value, mclk_div; + + switch (source) { + case NAU8360_CLK_SRC_MCLK: + value = NAU8360_MCLK_SEL_MCLK; + break; + + case NAU8360_CLK_SRC_PLL: + value = NAU8360_MCLK_SEL_PLL; + break; + + default: + return -EINVAL; + } + + regmap_update_bits(regmap, NAU8360_R04_CLK_CTRL1, NAU8360_MCLK_SEL_MASK, value); + + mclk_div = dyn_clk_div(NAU8360_MCLK_DIV_MAX, freq, nau8360->sys_clk); + if (mclk_div < 0) { + dev_err(dev, "mclk_div (%d -> %d): error", freq, nau8360->sys_clk); + return -EINVAL; + } + regmap_update_bits(regmap, NAU8360_R03_CLK_CTRL0, NAU8360_MCLK_DIV_MASK, + mclk_div << NAU8360_MCLK_DIV_SFT); + + dev_dbg(dev, " mclk_div (%d -> %d): %d", freq, nau8360->sys_clk, mclk_div + 1); + + return 0; +} + +static int nau8360_ana_sys_clk(struct nau8360 *nau8360, int source, unsigned int freq) +{ + struct device *dev = nau8360->dev; + struct regmap *regmap = nau8360->regmap; + struct nau8360_pll *pll = &nau8360->pll; + int value, pclk_div, ivdiv_sel, ddiv_sel; + + switch (source) { + case NAU8360_CLK_SRC_PLL: + value = NAU8360_CLK_ANA_SEL_PLL; + break; + case NAU8360_CLK_SRC_MCLK: + value = NAU8360_CLK_ANA_SEL_MCLK; + break; + case NAU8360_CLK_SRC_BCLK: + value = NAU8360_CLK_ANA_SEL_BCLK; + break; + default: + return -EINVAL; + } + + if (source == NAU8360_CLK_SRC_PLL) { + freq = pll->output; + pclk_div = dyn_clk_div(NAU8360_PLLOUT_DIV_MAX, freq, nau8360->sys_clk); + if (pclk_div < 0) { + dev_err(dev, "pll_div (%d -> %d): error", freq, nau8360->sys_clk); + return -EINVAL; + } + + dev_dbg(dev, " pll_div (%d -> %d): %d", freq, nau8360->sys_clk, pclk_div + 1); + + regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, NAU8360_PLLOUT_DIV_MASK, + pclk_div << NAU8360_PLLOUT_DIV_SFT); + freq /= (pclk_div + 1); + } + + ivdiv_sel = tab_clk_div(ivsns_clk_div, ARRAY_SIZE(ivsns_clk_div), freq); + if (ivdiv_sel < 0) { + dev_err(dev, "iv_div (%d -> %d): error", freq, CLK_DA_IVSNS_MAX); + return -EINVAL; + } + value |= ivdiv_sel << NAU8360_IVSNS_CLK_DIV_SFT; + + ddiv_sel = tab_clk_div(dac_clk_div, ARRAY_SIZE(dac_clk_div), freq); + if (ddiv_sel < 0) { + dev_err(dev, "dac_div (%d -> %d): error", freq, CLK_DA_IVSNS_MAX); + return -EINVAL; + } + value |= ddiv_sel << NAU8360_DAC_CLK_DIV_SFT; + + dev_dbg(dev, " clk_div (%d -> %d): ivsns %d, dac %d", freq, CLK_DA_IVSNS_MAX, + ivsns_clk_div[ivdiv_sel], dac_clk_div[ddiv_sel]); + + regmap_update_bits(regmap, NAU8360_R71_CLK_DIV_CFG, NAU8360_CLK_ANA_SEL_MASK | + NAU8360_IVSNS_CLK_DIV_MASK | NAU8360_DAC_CLK_DIV_MASK, value); + + return 0; +} + +static int nau8360_dsp_hw_clk(struct nau8360 *nau8360, int source, unsigned int freq) +{ + struct device *dev = nau8360->dev; + struct regmap *regmap = nau8360->regmap; + int val_dsp, val_hw, clk_div, dsp_clk; + + if (freq % ADSP_SR_48000 == 0) + dsp_clk = DSP_OP_CLK48; + else if (freq % ADSP_SR_44100 == 0) + dsp_clk = DSP_OP_CLK44; + else + return -EINVAL; + clk_div = dyn_clk_div(NAU8360_DSP_CLK_DIV_MAX, freq, dsp_clk); + if (clk_div < 0) { + dev_err(dev, "dsp/hw clk_div (%d -> %d): error", freq, dsp_clk); + return -EINVAL; + } + val_dsp = clk_div << NAU8360_DSP_CLK_DIV_SFT; + val_hw = clk_div << NAU8360_HW_CLK_DIV_SFT; + if (source == NAU8360_CLK_SRC_MCLK) { + val_dsp |= NAU8360_DSP_CLK_SEL_MCLK; + val_hw |= NAU8360_HW_CLK_SEL_MCLK; + } else if (source == NAU8360_CLK_SRC_PLL) { + val_dsp |= NAU8360_DSP_CLK_SEL_PLL; + val_hw |= NAU8360_HW_CLK_SEL_PLL; + } else + return -EINVAL; + + dev_dbg(dev, " dsp/hw clk_div (%d -> %d): %d", freq, dsp_clk, clk_div + 1); + + regmap_update_bits(regmap, NAU8360_R03_CLK_CTRL0, NAU8360_DSP_CLK_SEL_MASK | + NAU8360_DSP_CLK_DIV_MASK, val_dsp); + regmap_update_bits(regmap, NAU8360_R04_CLK_CTRL1, NAU8360_HW_CLK_SEL_MASK | + NAU8360_HW_CLK_DIV_MASK, val_hw); + + return 0; +} + +static int nau8360_set_sysclk(struct snd_soc_component *cp, + int clk_id, int source, unsigned int freq, int dir) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct regmap *regmap = nau8360->regmap; + struct device *dev = nau8360->dev; + static const char * const idtab[] = { "DIG", "ANA", "Internal" }; + static const char * const srctab[] = { "MCLK", "PLL", "HIRC48M", "BCLK" }; + int ret; + + if (clk_id < 0 || clk_id >= ARRAY_SIZE(idtab)) + return -EINVAL; + + if (source < 0 || source >= ARRAY_SIZE(srctab)) + return -EINVAL; + + if (dir == SND_SOC_CLOCK_OUT) { + dev_dbg(dev, "sysclk: freq %d (out)", freq); + return nau8360_set_sysclk_output(nau8360, freq); + } + + switch (clk_id) { + case NAU8360_CLK_ID_INT: + source = NAU8360_CLK_SRC_ICLK; + dev_dbg(dev, "sysclk: id %d (%s), src %d (%s) (in)", + clk_id, idtab[clk_id], source, srctab[source]); + regmap_update_bits(regmap, NAU8360_R03_CLK_CTRL0, + NAU8360_DSP_CLK_SEL_MASK, NAU8360_DSP_CLK_SEL_HIRC48M); + regmap_update_bits(regmap, NAU8360_R04_CLK_CTRL1, + NAU8360_HW_CLK_SEL_MASK | NAU8360_MCLK_SEL_MASK, + NAU8360_HW_CLK_SEL_HIRC48M | NAU8360_MCLK_SEL_HIRC48M); + regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, + NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_DIS); + break; + + case NAU8360_CLK_ID_DIG: + dev_dbg(dev, "sysclk: id %d (%s), src %d (%s), freq %d (in)", + clk_id, idtab[clk_id], source, srctab[source], freq); + + if (source == NAU8360_CLK_SRC_BCLK) + return -EINVAL; + + if (source == NAU8360_CLK_SRC_MCLK) + regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, + NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_DIS); + + ret = nau8360_dig_sys_clk(nau8360, source, freq); + if (ret) + return -EINVAL; + ret = nau8360_dsp_hw_clk(nau8360, source, freq); + if (ret) + return -EINVAL; + break; + + case NAU8360_CLK_ID_ANA: + dev_dbg(dev, "sysclk: id %d (%s), src %d (%s), freq %d (in)", + clk_id, idtab[clk_id], source, srctab[source], freq); + + if (source == NAU8360_CLK_SRC_MCLK || source == NAU8360_CLK_SRC_BCLK) + regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, + NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_DIS); + + ret = nau8360_ana_sys_clk(nau8360, source, freq); + if (ret) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int nau8360_calc_pll(struct nau8360 *nau8360) +{ + struct nau8360_pll *pll = &nau8360->pll; + u64 fref = 0ULL, fvco = 0ULL, ratio; + int fr, fv; + + if (pll->src != NAU8360_PLL_INTERNAL && + (pll->input > PLL_FREQ_MAX || pll->input < PLL_FREQ_MIN)) + return -EINVAL; + + if (pll->output > PLL_FOUT_MAX || pll->output < PLL_FOUT_MIN) + return -EINVAL; + + for (pll->msel = 1; pll->msel <= MSEL_MAX; pll->msel++) { + fr = pll->input / pll->msel; + if (fr <= PLL_FREF_MAX) { + fref = fr; + break; + } + } + if (!fref) + return -ERANGE; + + for (pll->rsel = 1; pll->rsel <= RSEL_MAX; pll->rsel++) { + fv = pll->output * pll->rsel; + if (fv >= PLL_FVCO_MIN) { + fvco = fv; + break; + } + } + if (!fvco) + return -ERANGE; + + dev_dbg(nau8360->dev, "fref(1-8MHz): %lld, fvco(50-125MHz): %lld", fref, fvco); + + /* Calculate the PLL 8-bit integer input and 12-bit fractional */ + ratio = div_u64(fvco << 12, fref); + pll->nsel = (ratio >> 12) & 0xff; + pll->xsel = ratio & 0xfff; + + return 0; +} + +static int nau8360_set_pll(struct snd_soc_component *cp, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + struct device *dev = nau8360->dev; + struct nau8360_pll *pll = &nau8360->pll; + int ctrl_val, ret; + + switch (source) { + case NAU8360_PLL_MCLK: + ctrl_val = NAU8360_PLL_CLK_SEL_MCLK; + break; + case NAU8360_PLL_BCLK: + ctrl_val = NAU8360_PLL_CLK_SEL_BCLK; + break; + case NAU8360_PLL_INTERNAL: + ctrl_val = NAU8360_PLL_CLK_SEL_HIRC; + break; + default: + return -EINVAL; + } + pll->src = source; + pll->input = freq_in; + pll->output = freq_out; + ret = nau8360_calc_pll(nau8360); + if (ret) { + dev_err(dev, "clock error input %d output %d", freq_in, freq_out); + return ret; + } + dev_dbg(dev, "src:%d, input:%d, output:%d, M:%d, R:%d, N:%d, X:%d", pll->src, + pll->input, pll->output, pll->msel, pll->rsel, pll->nsel, pll->xsel); + + regmap_update_bits(nau8360->regmap, NAU8360_R72_PLL_CFG0, NAU8360_PD_PLL_MASK | + NAU8360_PLL_CLK_SEL_MASK, NAU8360_PD_PLL_EN | ctrl_val); + regmap_write_bits(nau8360->regmap, NAU8360_R73_PLL_CFG1, + NAU8360_RSEL_MASK | NAU8360_MSEL_MASK | NAU8360_NSEL_MASK, + (pll->rsel - 1) << NAU8360_RSEL_SFT | + (pll->msel - 1) << NAU8360_MSEL_SFT | (pll->nsel - 1)); + regmap_write_bits(nau8360->regmap, NAU8360_R74_PLL_CFG2, NAU8360_XSEL_MASK, + pll->xsel); + + return 0; +} + +static void nau8360_coeff_set_def(struct nau8360 *nau8360) +{ + struct regmap *regmap = nau8360->regmap; + int i; + + mutex_lock(&nau8360->lock); + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_HW1_MEM_CLEAR, + NAU8360_HW1_MEM_CLEAR); + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_HW1_MEM_CLEAR, 0); + nau8360_peq_mem_enable(regmap, true); + for (i = 0; i < NAU8360_TOT_BAND_PER_CH; i++) { + regmap_write(regmap, NAU8360_R100_LEFT_BIQ0_COE + 1 + + i * NAU8360_TOT_BAND_COE_RANGE, 0x20); + regmap_write(regmap, NAU8360_R200_RIGHT_BIQ0_COE + 1 + + i * NAU8360_TOT_BAND_COE_RANGE, 0x20); + } + nau8360_peq_mem_enable(regmap, false); + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, + NAU8360_PEQ_BAND_8 << NAU8360_PEQ_BAND_SFT); + mutex_unlock(&nau8360->lock); +} + +static inline int nau8360_vbat_level(struct regmap *regmap, int *vbat) +{ + struct device *dev = regmap_get_device(regmap); + int value = 0, ret; + + ret = regmap_read(regmap, NAU8360_R21_VBAT_READOUT, &value); + if (ret) + return ret; + + /* multiple 100 on value scale */ + *vbat = (value * 100 + NAU8360_VBAT_BASE) / NAU8360_VBAT_STEP; + if (*vbat < NAU8360_VBAT_MIN || *vbat > NAU8360_VBAT_MAX) { + dev_err(dev, "VBAT %dV is out of valid range (%dV-%dV)", + *vbat, NAU8360_VBAT_MIN, NAU8360_VBAT_MAX); + return -ERANGE; + } + + return 0; +} + +static inline void nau8360_sawtooth_params(int vbat, int *vsaw_level, int *vsaw_slope) +{ + if (vbat < NAU8360_VBAT_MID_THRES) { + *vsaw_level = 0x1; + *vsaw_slope = 0x0; + } else if (vbat < NAU8360_VBAT_HIGH_THRES) { + *vsaw_level = 0x2; + *vsaw_slope = 0x1; + } else { + *vsaw_level = 0x3; + *vsaw_slope = 0x2; + } +} + +static inline void nau8360_dsp_software_reset(struct snd_soc_component *component) +{ + /* Enable PLL for successful DSP reset. After DSP is alive, + * system clock switches to internal clock and disable PLL. + */ + snd_soc_component_update_bits(component, NAU8360_R72_PLL_CFG0, + NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_EN); + msleep(50); + snd_soc_component_write(component, NAU8360_R01_DSP_SOFTWARE_RST, 0x5a5a); + snd_soc_component_write(component, NAU8360_R01_DSP_SOFTWARE_RST, 0xa5a5); + snd_soc_component_set_sysclk(component, NAU8360_CLK_ID_INT, 0, 0, + SND_SOC_CLOCK_IN); +} + +static void nau8360_dsp_bootup(struct snd_soc_component *component) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8360->regmap; + + regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_STALL, 0); + nau8360_dsp_software_reset(component); + nau8360_dsp_enable(regmap, true); +} + +static void nau8360_tdm_function_config(struct snd_soc_component *cp) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); + int i, chan_tx, tdm_chan_len; + + for (i = 0; i < NAU8360_TDM_RXN; i++) { + if (nau8360->tdm_rx_func_slot[i] == TDM_SLOT_NONE) + continue; + + nau8360_set_tdm_rx_slot(cp, i, nau8360->tdm_rx_func_slot[i]); + } + + tdm_chan_len = nau8360_get_tdm_chan_len(nau8360) >> 3; + for (i = 0; i < NAU8360_TDM_TXN; i++) { + if (nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE) + continue; + + /* compute the slot location in bytes according to slot/chan width */ + chan_tx = (tdm_chan_len * nau8360->tdm_tx_func_slot[i]); + nau8360_set_tdm_tx_slot(cp, i, chan_tx); + } +} + +static void nau8360_dsp_fw_load(struct nau8360 *nau8360) +{ + nau8360->load_fw_done = false; + schedule_work(&nau8360->load_fw_work); +} + +static void nau8360_load_fw_work(struct work_struct *work) +{ + struct nau8360 *nau8360 = container_of(work, struct nau8360, load_fw_work); + struct snd_soc_component *cp = snd_soc_dapm_to_component(nau8360->dapm); + int ret; + + ret = nau8360_dsp_init(cp); + if (ret) { + dev_err(nau8360->dev, "Failed to initialize DSP: %d\n", ret); + nau8360_dsp_enable(nau8360->regmap, false); + return; + } + nau8360->load_fw_done = true; +} + +static int nau8360_codec_probe(struct snd_soc_component *component) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8360->regmap; + struct device *dev = nau8360->dev; + int ret, vbat, vsaw_level, vsaw_slope; + + nau8360->dapm = dapm; + nau8360_dsp_bootup(component); + nau8360_dsp_fw_load(nau8360); + ret = nau8360_dsp_setup_controls(component); + if (ret) { + nau8360_dsp_enable(regmap, false); + dev_err(dev, "DSP setup controls failed(%d)", ret); + goto err; + } + nau8360_tdm_function_config(component); + + /* default disable Sense signal after booting */ + snd_soc_dapm_disable_pin(nau8360->dapm, "Sense"); + snd_soc_dapm_sync(nau8360->dapm); + + nau8360_coeff_set_def(nau8360); + /* VBAT is sensed by chip. */ + ret = nau8360_vbat_level(regmap, &vbat); + if (ret) { + dev_err(dev, "Failed to get valid VBAT level: %d", ret); + goto err; + } + dev_dbg(dev, "VBAT %dV for nau8360", vbat); + + /* Config sawtooth clock according to VBAT. Class D modulator input short setting + * for mute and de-pop purpose. Restore normal after initiation. + */ + nau8360_sawtooth_params(vbat, &vsaw_level, &vsaw_slope); + + regmap_update_bits(regmap, NAU8360_RA5_ANA_REG_1, NAU8360_VSAW_LV_MASK | + NAU8360_KVCO_SAW_MASK, (vsaw_level << NAU8360_VSAW_LV_SFT) | + (vsaw_slope << NAU8360_KVCO_SAW_SFT)); + + return 0; + +err: + cancel_work_sync(&nau8360->load_fw_work); + return ret; +} + +static int __maybe_unused nau8360_suspend(struct snd_soc_component *component) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + + cancel_work_sync(&nau8360->load_fw_work); + + regmap_update_bits(nau8360->regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_STALL, + NAU8360_HW2_STALL); + nau8360_dsp_enable(nau8360->regmap, false); + + regcache_cache_only(nau8360->regmap, true); + regcache_mark_dirty(nau8360->regmap); + + return 0; +} + +static int __maybe_unused nau8360_resume(struct snd_soc_component *component) +{ + struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); + struct regmap *regmap = nau8360->regmap; + + regcache_cache_only(regmap, false); + + nau8360_dsp_bootup(component); + + nau8360_peq_mem_enable(regmap, true); + regcache_sync(regmap); + nau8360_peq_mem_enable(regmap, false); + + /* disable Sense at standby */ + snd_soc_dapm_disable_pin(nau8360->dapm, "Sense"); + snd_soc_dapm_sync(nau8360->dapm); + + nau8360_dsp_fw_load(nau8360); + + return 0; +} + +static const struct snd_soc_component_driver soc_comp_dev_nau8360 = { + .probe = nau8360_codec_probe, + .set_sysclk = nau8360_set_sysclk, + .set_pll = nau8360_set_pll, + .suspend = nau8360_suspend, + .resume = nau8360_resume, + .controls = nau8360_snd_controls, + .num_controls = ARRAY_SIZE(nau8360_snd_controls), + .dapm_widgets = nau8360_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(nau8360_dapm_widgets), + .dapm_routes = nau8360_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(nau8360_dapm_routes), + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +static const struct snd_soc_dai_ops nau8360_dai_ops = { + .startup = nau8360_startup, + .shutdown = nau8360_shutdown, + .hw_params = nau8360_hw_params, + .set_fmt = nau8360_set_fmt, + .set_tdm_slot = nau8360_set_tdm_slot, +}; + +#define NAU8360_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define NAU8360_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver nau8360_dai = { + .name = NAU8360_CODEC_DAI, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 4, + .rates = NAU8360_RATES, + .formats = NAU8360_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = NAU8360_RATES, + .formats = NAU8360_FORMATS, + }, + .ops = &nau8360_dai_ops, + .symmetric_rate = 1, +}; + +static int nau8360_reg_write(void *context, unsigned int reg, unsigned int value) +{ + struct i2c_client *client = context; + struct nau8360 *nau8360 = i2c_get_clientdata(client); + int ret, count = 0; + + put_unaligned_be16(reg, &nau8360->i2c_write_buf[count]); + count += 2; + + if (NAU8360_IS_DSP_REG(reg)) { + /* format for DSP, 4 bytes value and native */ + put_unaligned_le32(value, &nau8360->i2c_write_buf[count]); + count += 4; + } else { + /* format for Codec, 2 bytes value and endian big */ + put_unaligned_be16(value, &nau8360->i2c_write_buf[count]); + count += 2; + } + + ret = i2c_master_send(client, nau8360->i2c_write_buf, count); + + if (ret == count) + return 0; + if (ret < 0) + return ret; + + return -EIO; +} + +static int nau8360_reg_read(void *context, unsigned int reg, unsigned int *value) +{ + struct i2c_client *client = context; + struct nau8360 *nau8360 = i2c_get_clientdata(client); + struct i2c_msg xfer[2]; + int ret; + + put_unaligned_be16(reg, &nau8360->i2c_read_reg); + xfer[0].addr = client->addr; + xfer[0].len = 2; + xfer[0].buf = nau8360->i2c_read_reg; + xfer[0].flags = 0; + + xfer[1].addr = client->addr; + xfer[1].len = (NAU8360_IS_DSP_REG(reg)) ? 4 : 2; + xfer[1].buf = nau8360->i2c_read_buf; + xfer[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(xfer)) + return -EIO; + + if (NAU8360_IS_DSP_REG(reg)) + *value = get_unaligned_le32(nau8360->i2c_read_buf); + else + *value = get_unaligned_be16(nau8360->i2c_read_buf); + + return 0; +} + +static const struct regmap_config nau8360_regmap_config = { + .reg_bits = NAU8360_REG_ADDR_LEN, + .val_bits = NAU8360_REG_DATA_LEN, + + .max_register = NAU8360_REG_MAX, + .readable_reg = nau8360_readable_reg, + .writeable_reg = nau8360_writeable_reg, + .volatile_reg = nau8360_volatile_reg, + .reg_read = nau8360_reg_read, + .reg_write = nau8360_reg_write, + + .cache_type = REGCACHE_MAPLE, + .reg_defaults = nau8360_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(nau8360_reg_defaults), +}; + +static inline void nau8360_reset_chip(struct regmap *regmap) +{ + regmap_write(regmap, NAU8360_R00_SOFTWARE_RST, 0x5a5a); + regmap_write(regmap, NAU8360_R00_SOFTWARE_RST, 0xa5a5); +} + +static void nau8360_init_regs(struct nau8360 *nau8360) +{ + struct regmap *regmap = nau8360->regmap; + + /* Enable Digital LDO */ + regmap_write(regmap, NAU8360_R78_PD_SW_DLDO, NAU8360_PD_SW_DLDO_EN); + /* Enable Software Shutdown Mode */ + regmap_write(regmap, NAU8360_R77_SOFT_SD, NAU8360_SOFT_SD_EN); + /* Stall HW1 PEQ Engine and Clear DRAM to Zero */ + regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_STALL, + NAU8360_PEQ_STALL); + /* Stall HW2 engine */ + regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_STALL, + NAU8360_HW2_STALL); + /* Stall HW3 engine and DSP processor */ + nau8360_dsp_enable(regmap, false); + /* Enable Clock Gate */ + regmap_write(regmap, NAU8360_R7E_CLK_GATED_EN, NAU8360_CLK_GATED_EN); + /* Latch I2C LSB Value */ + regmap_write(regmap, NAU8360_R02_I2C_ADDR, 0x0001); + /* ANC Left/Right Channel as Slot 2/3 and switch */ + regmap_update_bits(regmap, NAU8360_R10_I2S_DATA_CTRL3, + NAU8360_RX_ANC_R_MASK | NAU8360_RX_ANC_L_MASK, + 0x3 << NAU8360_RX_ANC_R_SFT | 0x2 << NAU8360_RX_ANC_L_SFT); + /* Set DAC Clock Divider as 2 and Chopper Divider as 16 */ + regmap_update_bits(regmap, NAU8360_R71_CLK_DIV_CFG, + NAU8360_DAC_CLK_DIV_MASK | NAU8360_DAC_CHOP_CLK_DIV_MASK, + NAU8360_DAC_CLK_DIV_2 | NAU8360_DAC_CHOP_CLK_DIV_16); + /* Set DAC SINC OSR as 128 and IVSENSE BS OSR as 32 */ + regmap_update_bits(regmap, NAU8360_R5D_SINC_CFG, + NAU8360_DAC_SINC_OSR_MASK | NAU8360_IVSENSE_BS_OSR_MASK, + NAU8360_DAC_SINC_OSR_128 | NAU8360_IVSENSE_BS_OSR_32); + + /* Set Trim Bit Control of DLDO and GVDD to The Highest Voltage */ + regmap_write(regmap, NAU8360_R5F_ANA_TRIM_CFG1, 0xf407); + + /* Disable Data Det and Clock Detection Settings*/ + regmap_update_bits(regmap, NAU8360_R40_CLK_DET_CTRL, NAU8360_APWRUPEN | + NAU8360_CLKPWRUPEN | NAU8360_FS_MCLK_DET | NAU8360_FS_BCLK_DET, 0); + + /* Set HW3 Droop and Filter Sample as 192K */ + regmap_update_bits(regmap, NAU8360_R8C_HW3_CTL6, NAU8360_HW3_DROOP_MASK | + NAU8360_HW3_FS_MASK, NAU8360_HW3_DROOP_192K | NAU8360_HW3_FS_192K); + /* Set HW1 Mute, Mute Interval as 699ms and HW1 Zero THD as 0xff */ + regmap_update_bits(regmap, NAU8360_R9C_HW1_CTL2, NAU8360_MUTE_INTRVL_MASK | + NAU8360_HW1_CH_MUTE | NAU8360_HW1_ZERO_THD_MASK, + NAU8360_MUTE_INTRVL_699MS | NAU8360_HW1_CH_MUTE | 0xff); + /* set HW2 droop with 192K*/ + regmap_update_bits(regmap, NAU8360_R96_HW2_CTL6, NAU8360_HW2_DROOP_SEL_MASK | + NAU8360_HW2_DROOP_EN | NAU8360_HW2_FS_MASK, + NAU8360_HW2_DROOP_SEL_LARGE | NAU8360_HW2_DROOP_EN | NAU8360_HW2_FS_192K); + /* Set HW2 default volume */ + regmap_write(regmap, NAU8360_R97_HW2_CTL7, 0xbf66); + regmap_write(regmap, NAU8360_R98_HW2_CTL8, 0xbf66); + /* Set HW2 Mute and Threshold of Zero Crossing */ + regmap_update_bits(regmap, NAU8360_R99_HW2_CTL9, NAU8360_HW2_CH_MUTE | + NAU8360_HW2_ZERO_THD_MASK, NAU8360_HW2_CH_MUTE | 0xff); + /* According to DSP enabled or not, set stream path as + * HW1->DSP->HW2->SINC->HW3 or HW1->HW2->SINC->HW3 + */ + regmap_update_bits(regmap, NAU8360_R12_PATH_CTRL, NAU8360_SEL_HW1_MASK | + NAU8360_AUD_SEL_MASK | NAU8360_SEL_HW2_MASK | NAU8360_SEL_HW3_MASK | + NAU8360_DAC_SEL_MASK, NAU8360_SEL_HW1_OUT | NAU8360_AUD_SEL_SINCOUT | + NAU8360_SEL_HW2_OUT | NAU8360_SEL_HW3_OUT); + /* Set Input Status as Low and Input/Output Mode Disable for All GPIO */ + regmap_write(regmap, NAU8360_R07_GP_CTRL, 0x0000); + /* enable SARADC with extra average filter of VBAT */ + regmap_update_bits(regmap, NAU8360_R6A_SARADC_CFG0, NAU8360_SARADC_EN | + NAU8360_VBAT_AVG_EN, NAU8360_SARADC_EN | NAU8360_VBAT_AVG_EN); + /* Enable Temperature Sensor, VBATDIV and VRF. + * Set Average Filter Data as 512 for VTEMP and VBAT. + */ + regmap_update_bits(regmap, NAU8360_R6B_SARADC_CFG1, NAU8360_VTEMP_EN | + NAU8360_VBATDIV_EN | NAU8360_VREF_EN | NAU8360_PRELOAD_VREF_EN | + NAU8360_VTEMP_AVG_N_MASK | NAU8360_VBAT_AVG_N_MASK, NAU8360_VTEMP_EN | + NAU8360_VBATDIV_EN | NAU8360_VREF_EN | NAU8360_PRELOAD_VREF_EN | + NAU8360_VTEMP_AVG_N_512 | NAU8360_VBAT_AVG_N_512); + /* Enable IV sense internal LDO */ + regmap_update_bits(regmap, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PD_LDO_SDM_IVSNS_PMD, 0); + /* Enable Bias Current and Reference Voltage for IVSENSE. Set VSNS Gain + * as 1/12 and ISNS Gain as x16(24dB). Set PGA Current as 2'b11 and SDN + * Delay as 2'b00. + */ + regmap_write(regmap, NAU8360_R6D_IVSNS_CFG1, 0x0f5c); + /* sawtooth clock from ivsense clock, PWM frequency 432 Khz */ + regmap_update_bits(regmap, NAU8360_RA4_ANA_REG_0, NAU8360_SEL_STCLK_MASK | + NAU8360_NSEL_SAW_MASK, NAU8360_SEL_STCLK_IVCLK | 0x8); + /* sawtooth PLL APR */ + regmap_update_bits(regmap, NAU8360_RA5_ANA_REG_1, NAU8360_SAW_PLL_MASK, + NAU8360_SAW_PLL_NOR); + + /* Set DAC gain (0dB/+3.2dB) by current cell adjustment */ + regmap_update_bits(regmap, NAU8360_R6E_DAC_CFG0, NAU8360_DAC_CUR_MASK, + nau8360->dac_cur_enable ? NAU8360_DAC_CUR_3_2DB : NAU8360_DAC_CUR_0DB); + /* Config trim short current reference. Enable Non-overlap longer delay. + * Set segment driver as half driving strength. + */ + regmap_update_bits(regmap, NAU8360_R68_ANALOG_CONTROL_1, NAU8360_DTX_EN | + NAU8360_TRIMSCR_MASK | NAU8360_DRVCTL_SEGL_FULL | + NAU8360_DRVCTL_SEGR_FULL, NAU8360_TRIMSCR_MLOW | NAU8360_DTX_EN); + /* Enable SCP Clear Mode and Class D Modulator to Common Mode */ + regmap_update_bits(regmap, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_SCP_CLEAR_MODE_MASK | NAU8360_CM_COMP_EN, + NAU8360_SCP_CLEAR_MODE_AUTO | NAU8360_CM_COMP_EN); + /* Set Analog Mute and Class D Modulator Gain as 14dB */ + regmap_update_bits(regmap, NAU8360_R67_ANALOG_CONTROL_0, NAU8360_ANA_MUTE | + NAU8360_MOD_GAIN_MASK, NAU8360_ANA_MUTE | NAU8360_MOD_GAIN_14DB); + /* Set Stereo or PBTL Mode */ + regmap_update_bits(regmap, NAU8360_R67_ANALOG_CONTROL_0, + NAU8360_V_PBTL_EN, nau8360->pbtl_enable ? NAU8360_V_PBTL_EN : 0); + regmap_update_bits(regmap, NAU8360_R6C_IVSNS_CFG0, + NAU8360_PBTL_ISENE_LR_MASK, NAU8360_PBTL_ISENE_LR_ILR); + /* adjust HW3 default volume of voltage/current sense */ + regmap_write(regmap, NAU8360_R8A_HW3_VL_CTL7, 0xbe90); + regmap_write(regmap, NAU8360_R8B_HW3_VR_CTL8, 0xbe90); + regmap_write(regmap, NAU8360_R8D_HW3_IL_CTL7, nau8360->pbtl_enable ? 0xc3aa : 0xc24c); + regmap_write(regmap, NAU8360_R8E_HW3_IR_CTL8, nau8360->pbtl_enable ? 0xc3aa : 0xc24c); + /* Set HW3 Zero THD as 0xff */ + regmap_update_bits(regmap, NAU8360_R8F_HW3_CTL9, + NAU8360_HW3_ZERO_THD_MASK, 0xff); + /* Enable TX SDOUT and Data at BCLK Rising. */ + regmap_update_bits(regmap, NAU8360_R0D_I2S_PCM_CTRL3, NAU8360_TX_FILL_MASK, + NAU8360_TX_FILL_ZERO); + regmap_update_bits(regmap, NAU8360_R0D_I2S_PCM_CTRL3, + NAU8360_VSNS_L_SLEN_MASK, NAU8360_VSNS_L_SLEN_16); + regmap_update_bits(regmap, NAU8360_R0E_I2S_DATA_CTRL1, + NAU8360_ISNS_L_SLEN_MASK, NAU8360_ISNS_L_SLEN_16); + regmap_update_bits(regmap, NAU8360_R11_I2S_DATA_CTRL4, + NAU8360_VSNS_R_SLEN_MASK | NAU8360_ISNS_R_SLEN_MASK, + NAU8360_VSNS_R_SLEN_16 | NAU8360_ISNS_R_SLEN_16); + /* set DAC channel temperature trim code slope as 0x7D */ + regmap_update_bits(regmap, NAU8360_R7A_DAC_TRIM_CFG2, + NAU8360_DAC_TEMP_SLOPE_MASK, 0x7D << NAU8360_DAC_TEMP_SLOPE_SFT); + /* set ISNS temperature trim code slope as 0x1a */ + regmap_update_bits(regmap, NAU8360_R7B_IVSNS_TRIM_CFG, + NAU8360_ISNS_TEMP_SLOPE_MASK, 0x1a << NAU8360_ISNS_TEMP_SLOPE_SFT); + /* set misc trim config as 0x67F0 */ + regmap_update_bits(regmap, NAU8360_R7C_MISC_TRIM_CFG, NAU8360_DAC_GAIN_SB_MASK | + NAU8360_DAC_TEMP_SB_MASK | NAU8360_ISNS_TEMP_SB_MASK | + NAU8360_VSNS_TEMP_SB_MASK | NAU8360_ISNS_GAIN_SB_MASK | + NAU8360_VSNS_GAIN_SB_MASK, (0x3 << NAU8360_DAC_TEMP_SB_SFT) | + (0x7 << NAU8360_ISNS_TEMP_SB_SFT) | (0x3 << NAU8360_VSNS_TEMP_SB_SFT)); + /* Enable Efuse Initianllization */ + regmap_update_bits(regmap, NAU8360_R60_RST, NAU8360_EFUSE_CTRL_EN, + NAU8360_EFUSE_CTRL_EN); + /* Clear HW2 DRAM */ + regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, + NAU8360_HW2_DRAM_CLR, NAU8360_HW2_DRAM_CLR); + /* Finish HW DRAM Clear */ + regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_DRAM_CLR, 0); + /* Clear HW3 DRAM */ + regmap_update_bits(regmap, NAU8360_R86_HW3_CTL0, + NAU8360_HW3_DRAM_CLR, NAU8360_HW3_DRAM_CLR); + /* Finish HW DRAM Clear */ + regmap_update_bits(regmap, NAU8360_R86_HW3_CTL0, NAU8360_HW3_DRAM_CLR, 0); +} + +static void nau8360_print_device_properties(struct nau8360 *nau8360) +{ + int i; + + dev_dbg(nau8360->dev, "pbtl-enable: %d", nau8360->pbtl_enable); + dev_dbg(nau8360->dev, "dac-cur-enable: %d", nau8360->dac_cur_enable); + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) + dev_dbg(nau8360->dev, "firmware-name[%d]: %s", i, + nau8360->dsp_firmware[i]); + + for (i = 0; i < NAU8360_TDM_TXN; i++) + dev_dbg(nau8360->dev, "dsp-tx-slot[%d] (%s): %d%s", i, + nau8360_tx_func_names[i], nau8360->tdm_tx_func_slot[i], + nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE ? " (none)" : ""); + + for (i = 0; i < NAU8360_TDM_RXN; i++) + dev_dbg(nau8360->dev, "dsp-rx-slot[%d] (%s): %d%s", i, + nau8360_rx_func_names[i], nau8360->tdm_rx_func_slot[i], + nau8360->tdm_rx_func_slot[i] == TDM_SLOT_NONE ? " (none)" : ""); +} + +static int nau8360_read_device_properties(struct nau8360 *nau8360) +{ + const char *def_fws[NAU8360_DSP_FW_NUM] = { + NAU8360_DSP_FIRMWARE".l", NAU8360_DSP_FIRMWARE".r" + }; + struct device *dev = nau8360->dev; + const char *firmware_names[NAU8360_DSP_FW_NUM]; + int i, ret; + + ret = device_property_read_u32_array(dev, "nuvoton,dsp-tx-slot-mapping", + nau8360->tdm_tx_func_slot, NAU8360_TDM_TXN); + if (ret) { + for (i = 0; i < NAU8360_TDM_TXN; i++) + nau8360->tdm_tx_func_slot[i] = TDM_SLOT_NONE; + } else { + for (i = 0; i < NAU8360_TDM_TXN; i++) { + if (nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE) + continue; + if (nau8360->tdm_tx_func_slot[i] >= NAU8360_TDM_MAX_CHAN) { + dev_warn(dev, "Invalid TX slot %d, set to none\n", + nau8360->tdm_tx_func_slot[i]); + nau8360->tdm_tx_func_slot[i] = TDM_SLOT_NONE; + } + } + } + + ret = device_property_read_u32_array(dev, "nuvoton,dsp-rx-slot-mapping", + nau8360->tdm_rx_func_slot, NAU8360_TDM_RXN); + if (ret) { + nau8360->tdm_rx_func_slot[NAU8360_TDM_DACL] = 0; + nau8360->tdm_rx_func_slot[NAU8360_TDM_DACR] = 1; + nau8360->tdm_rx_func_slot[NAU8360_TDM_ANCL] = TDM_SLOT_NONE; + nau8360->tdm_rx_func_slot[NAU8360_TDM_ANCR] = TDM_SLOT_NONE; + } else { + for (i = 0; i < NAU8360_TDM_RXN; i++) { + if (nau8360->tdm_rx_func_slot[i] == TDM_SLOT_NONE) + continue; + if (nau8360->tdm_rx_func_slot[i] >= NAU8360_TDM_MAX_CHAN) { + dev_warn(dev, "Invalid RX slot %d, set to none\n", + nau8360->tdm_rx_func_slot[i]); + nau8360->tdm_rx_func_slot[i] = TDM_SLOT_NONE; + } + } + } + + nau8360->pbtl_enable = device_property_read_bool(dev, "nuvoton,pbtl-enable"); + nau8360->dac_cur_enable = device_property_read_bool(dev, "nuvoton,dac-cur-enable"); + + ret = device_property_read_string_array(dev, "firmware-name", + firmware_names, NAU8360_DSP_FW_NUM); + if (ret != NAU8360_DSP_FW_NUM) { + dev_warn(dev, "The firmware-name was not found, using default."); + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { + nau8360->dsp_firmware[i] = devm_kstrdup(dev, def_fws[i], GFP_KERNEL); + + if (!nau8360->dsp_firmware[i]) + return -ENOMEM; + } + } else { + for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { + nau8360->dsp_firmware[i] = devm_kasprintf(dev, GFP_KERNEL, + NAU8360_DSP_FIRMDIR "%s", firmware_names[i]); + + if (!nau8360->dsp_firmware[i]) + return -ENOMEM; + } + } + + return 0; +} + +static struct reg_default *nau8360_alloc_defaults(struct device *dev, int *total_regs) +{ + struct reg_default *dyn_defaults; + int reg_num = ARRAY_SIZE(nau8360_reg_defaults); + int total = reg_num + (2 * NAU8360_TOT_BAND_PER_CH * NAU8360_TOT_BAND_COE); + int i, j, idx; + + dyn_defaults = devm_kzalloc(dev, total * sizeof(*dyn_defaults), GFP_KERNEL); + if (!dyn_defaults) + return NULL; + + memcpy(dyn_defaults, nau8360_reg_defaults, sizeof(*dyn_defaults) * reg_num); + idx = reg_num; + + for (i = 0; i < NAU8360_TOT_BAND_PER_CH; i++) { + unsigned int range = i * NAU8360_TOT_BAND_COE_RANGE; + unsigned int l_base = NAU8360_R100_LEFT_BIQ0_COE + range; + unsigned int r_base = NAU8360_R200_RIGHT_BIQ0_COE + range; + + for (j = 0; j < NAU8360_TOT_BAND_COE; j++) { + dyn_defaults[idx++].reg = l_base + j; + dyn_defaults[idx++].reg = r_base + j; + } + } + + *total_regs = total; + + return dyn_defaults; +} + +static int nau8360_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct nau8360 *nau8360; + struct regmap_config regmap_cfg = nau8360_regmap_config; + struct reg_default *dyn_defaults; + int num_total_regs; + int ret, value; + + nau8360 = devm_kzalloc(dev, sizeof(*nau8360), GFP_KERNEL); + if (!nau8360) + return -ENOMEM; + + i2c_set_clientdata(i2c, nau8360); + mutex_init(&nau8360->lock); + INIT_WORK(&nau8360->load_fw_work, nau8360_load_fw_work); + + dyn_defaults = nau8360_alloc_defaults(dev, &num_total_regs); + if (!dyn_defaults) + return -ENOMEM; + + regmap_cfg.reg_defaults = dyn_defaults; + regmap_cfg.num_reg_defaults = num_total_regs; + + nau8360->regmap = devm_regmap_init(dev, NULL, i2c, ®map_cfg); + if (IS_ERR(nau8360->regmap)) + return PTR_ERR(nau8360->regmap); + nau8360->dev = dev; + + nau8360_reset_chip(nau8360->regmap); + ret = regmap_read(nau8360->regmap, NAU8360_R46_I2C_DEVICE_ID, &value); + if (ret) { + dev_err(dev, "Failed to read NAU83G60 device id %d", ret); + return ret; + } + + ret = nau8360_read_device_properties(nau8360); + if (ret) + return ret; + + nau8360_print_device_properties(nau8360); + nau8360_init_regs(nau8360); + + return devm_snd_soc_register_component(dev, &soc_comp_dev_nau8360, &nau8360_dai, 1); +} + +static void nau8360_i2c_remove(struct i2c_client *client) +{ + struct nau8360 *nau8360 = i2c_get_clientdata(client); + + cancel_work_sync(&nau8360->load_fw_work); +} + +static const struct i2c_device_id nau8360_i2c_ids[] = { + { .name = "nau8360" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, nau8360_i2c_ids); + +static const struct of_device_id nau8360_of_ids[] = { + { .compatible = "nuvoton,nau8360" }, + {} +}; +MODULE_DEVICE_TABLE(of, nau8360_of_ids); + +static const struct acpi_device_id nau8360_acpi_match[] = { + { .id = "NVTN2002" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, nau8360_acpi_match); + +static struct i2c_driver nau8360_i2c_driver = { + .driver = { + .name = "nau8360", + .of_match_table = of_match_ptr(nau8360_of_ids), + .acpi_match_table = ACPI_PTR(nau8360_acpi_match), + }, + .probe = nau8360_i2c_probe, + .remove = nau8360_i2c_remove, + .id_table = nau8360_i2c_ids, +}; +module_i2c_driver(nau8360_i2c_driver); + +MODULE_DESCRIPTION("ASoC NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver"); +MODULE_AUTHOR("David Lin "); +MODULE_AUTHOR("Seven Lee "); +MODULE_AUTHOR("John Hsu "); +MODULE_AUTHOR("Neo Chang "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/nau8360.h b/sound/soc/codecs/nau8360.h new file mode 100644 index 000000000000..71396747c0fa --- /dev/null +++ b/sound/soc/codecs/nau8360.h @@ -0,0 +1,917 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver. +// +// Copyright (C) 2026 Nuvoton Technology Corp. +// Author: David Lin +// Seven Lee +// John Hsu +// Neo Chang + +#ifndef __NAU8360_H__ +#define __NAU8360_H__ + +#define NAU8360_R00_SOFTWARE_RST 0x00 +#define NAU8360_R01_DSP_SOFTWARE_RST 0x01 +#define NAU8360_R02_I2C_ADDR 0x02 +#define NAU8360_R03_CLK_CTRL0 0x03 +#define NAU8360_R04_CLK_CTRL1 0x04 +#define NAU8360_R05_INTERRUPT_CTRL 0x05 +#define NAU8360_R06_INT_CLR_STATUS 0x06 +#define NAU8360_R07_GP_CTRL 0x07 +#define NAU8360_R08_GP_CTRL0 0x08 +#define NAU8360_R09_GP_CTRL1 0x09 +#define NAU8360_R0A_GP_CTRL2 0x0a +#define NAU8360_R0B_I2S_PCM_CTRL1 0x0b +#define NAU8360_R0C_I2S_PCM_CTRL2 0x0c +#define NAU8360_R0D_I2S_PCM_CTRL3 0x0d +#define NAU8360_R0E_I2S_DATA_CTRL1 0x0e +#define NAU8360_R0F_I2S_DATA_CTRL2 0x0f +#define NAU8360_R10_I2S_DATA_CTRL3 0x10 +#define NAU8360_R11_I2S_DATA_CTRL4 0x11 +#define NAU8360_R12_PATH_CTRL 0x12 +#define NAU8360_R16_NO_NAME 0x16 +#define NAU8360_R17_I2S0_DATA_CTRL5 0x17 +#define NAU8360_R1A_DSP_CORE_CTRL2 0x1a +#define NAU8360_R21_VBAT_READOUT 0x21 +#define NAU8360_R22_TEMP_READOUT 0x22 +#define NAU8360_R2C_ALC_CTRL1 0x2c +#define NAU8360_R2D_ALC_CTRL2 0x2d +#define NAU8360_R2E_ALC_CTRL3 0x2e +#define NAU8360_R31_UVLOP_CTRL1 0x31 +#define NAU8360_R32_UVLOP_CTRL2 0x32 +#define NAU8360_R33_UVLOP_CTRL3 0x33 +#define NAU8360_R40_CLK_DET_CTRL 0x40 +#define NAU8360_R41_CLK_CTL2 0x41 +#define NAU8360_R46_I2C_DEVICE_ID 0x46 +#define NAU8360_R5D_SINC_CFG 0x5d +#define NAU8360_R5F_ANA_TRIM_CFG1 0x5f +#define NAU8360_R60_RST 0x60 +#define NAU8360_R67_ANALOG_CONTROL_0 0x67 +#define NAU8360_R68_ANALOG_CONTROL_1 0x68 +#define NAU8360_R69_ANALOG_CONTROL_3 0x69 +#define NAU8360_R6A_SARADC_CFG0 0x6a +#define NAU8360_R6B_SARADC_CFG1 0x6b +#define NAU8360_R6C_IVSNS_CFG0 0x6c +#define NAU8360_R6D_IVSNS_CFG1 0x6d +#define NAU8360_R6E_DAC_CFG0 0x6e +#define NAU8360_R71_CLK_DIV_CFG 0x71 +#define NAU8360_R72_PLL_CFG0 0x72 +#define NAU8360_R73_PLL_CFG1 0x73 +#define NAU8360_R74_PLL_CFG2 0x74 +#define NAU8360_R77_SOFT_SD 0x77 +#define NAU8360_R78_PD_SW_DLDO 0x78 +#define NAU8360_R79_EN_HIRC48M 0x79 +#define NAU8360_R7A_DAC_TRIM_CFG2 0x7a +#define NAU8360_R7B_IVSNS_TRIM_CFG 0x7b +#define NAU8360_R7C_MISC_TRIM_CFG 0x7c +#define NAU8360_R7E_CLK_GATED_EN 0x7e +#define NAU8360_R86_HW3_CTL0 0x86 +#define NAU8360_R88_ALC_CTRL6 0x88 +#define NAU8360_R8A_HW3_VL_CTL7 0x8a +#define NAU8360_R8B_HW3_VR_CTL8 0x8b +#define NAU8360_R8C_HW3_CTL6 0x8c +#define NAU8360_R8D_HW3_IL_CTL7 0x8d +#define NAU8360_R8E_HW3_IR_CTL8 0x8e +#define NAU8360_R8F_HW3_CTL9 0x8f +#define NAU8360_R90_HW2_CTL0 0x90 +#define NAU8360_R96_HW2_CTL6 0x96 +#define NAU8360_R97_HW2_CTL7 0x97 +#define NAU8360_R98_HW2_CTL8 0x98 +#define NAU8360_R99_HW2_CTL9 0x99 +#define NAU8360_R9A_HW1_CTL0 0x9a +#define NAU8360_R9B_HW1_CTL1 0x9b +#define NAU8360_R9C_HW1_CTL2 0x9c +#define NAU8360_R9D_PEQ_CTL 0x9d +#define NAU8360_RA0_LEFT_XODRC_CTRL 0xa0 +#define NAU8360_RA2_RIGHT_XODRC_CTRL 0xa2 +#define NAU8360_RA4_ANA_REG_0 0xa4 +#define NAU8360_RA5_ANA_REG_1 0xa5 +/* Left PEQ includes 15 band, 10 coeff per band, from start addr 0x100 */ +#define NAU8360_R100_LEFT_BIQ0_COE 0x100 +#define NAU8360_R10C_LEFT_BIQ1_COE 0x10c +#define NAU8360_R118_LEFT_BIQ2_COE 0x118 +#define NAU8360_R124_LEFT_BIQ3_COE 0x124 +#define NAU8360_R130_LEFT_BIQ4_COE 0x130 +#define NAU8360_R13C_LEFT_BIQ5_COE 0x13c +#define NAU8360_R148_LEFT_BIQ6_COE 0x148 +#define NAU8360_R154_LEFT_BIQ7_COE 0x154 +#define NAU8360_R160_LEFT_BIQ8_COE 0x160 +#define NAU8360_R16C_LEFT_BIQ9_COE 0x16c +#define NAU8360_R178_LEFT_BIQ10_COE 0x178 +#define NAU8360_R184_LEFT_BIQ11_COE 0x184 +#define NAU8360_R190_LEFT_BIQ12_COE 0x190 +#define NAU8360_R19C_LEFT_BIQ13_COE 0x19c +#define NAU8360_R1A8_LEFT_BIQ14_COE 0x1a8 +/* Right PEQ includes 15 band, 10 coeff per band, from start addr 0x200 */ +#define NAU8360_R200_RIGHT_BIQ0_COE 0x200 +#define NAU8360_R20C_RIGHT_BIQ1_COE 0x20c +#define NAU8360_R218_RIGHT_BIQ2_COE 0x218 +#define NAU8360_R224_RIGHT_BIQ3_COE 0x224 +#define NAU8360_R230_RIGHT_BIQ4_COE 0x230 +#define NAU8360_R23C_RIGHT_BIQ5_COE 0x23c +#define NAU8360_R248_RIGHT_BIQ6_COE 0x248 +#define NAU8360_R254_RIGHT_BIQ7_COE 0x254 +#define NAU8360_R260_RIGHT_BIQ8_COE 0x260 +#define NAU8360_R26C_RIGHT_BIQ9_COE 0x26c +#define NAU8360_R278_RIGHT_BIQ10_COE 0x278 +#define NAU8360_R284_RIGHT_BIQ11_COE 0x284 +#define NAU8360_R290_RIGHT_BIQ12_COE 0x290 +#define NAU8360_R29C_RIGHT_BIQ13_COE 0x29c +#define NAU8360_R2A8_RIGHT_BIQ14_COE 0x2a8 +#define NAU8360_RF000_DSP_COMM 0xf000 +#define NAU8360_RF002_DSP_COMM 0xf002 +#define NAU8360_REG_MAX NAU8360_RF002_DSP_COMM + +/* 16-bit control register address, and 16-bits control register data */ +#define NAU8360_REG_ADDR_LEN 16 +#define NAU8360_REG_DATA_LEN 16 + +/* NAU8360_R03_CLK_CTRL0 (0x03) */ +#define NAU8360_MCLK_DIV_SFT 10 +#define NAU8360_MCLK_DIV_MAX 0x3f +#define NAU8360_MCLK_DIV_MASK (0x3f << NAU8360_MCLK_DIV_SFT) +#define NAU8360_DSP_CLK_DIV_SFT 7 +#define NAU8360_DSP_CLK_DIV_MAX 0x7 +#define NAU8360_DSP_CLK_DIV_MASK (0x7 << NAU8360_DSP_CLK_DIV_SFT) +#define NAU8360_DSP_CLK_SEL_SFT 5 +#define NAU8360_DSP_CLK_SEL_MASK (0x3 << NAU8360_DSP_CLK_SEL_SFT) +#define NAU8360_DSP_CLK_SEL_PLL (0x2 << NAU8360_DSP_CLK_SEL_SFT) +#define NAU8360_DSP_CLK_SEL_HIRC48M (0x1 << NAU8360_DSP_CLK_SEL_SFT) +#define NAU8360_DSP_CLK_SEL_MCLK (0x0 << NAU8360_DSP_CLK_SEL_SFT) + +/* NAU8360_R04_CLK_CTRL1 (0x4) */ +#define NAU8360_MCLK_SEL_SFT 14 +#define NAU8360_MCLK_SEL_MASK (0x3 << NAU8360_MCLK_SEL_SFT) +#define NAU8360_MCLK_SEL_PLL (0x2 << NAU8360_MCLK_SEL_SFT) +#define NAU8360_MCLK_SEL_HIRC48M (0x1 << NAU8360_MCLK_SEL_SFT) +#define NAU8360_MCLK_SEL_MCLK (0x0 << NAU8360_MCLK_SEL_SFT) +#define NAU8360_HW_CLK_SEL_SFT 10 +#define NAU8360_HW_CLK_SEL_MASK (0x3 << NAU8360_HW_CLK_SEL_SFT) +#define NAU8360_HW_CLK_SEL_PLL (0x2 << NAU8360_HW_CLK_SEL_SFT) +#define NAU8360_HW_CLK_SEL_HIRC48M (0x1 << NAU8360_HW_CLK_SEL_SFT) +#define NAU8360_HW_CLK_SEL_MCLK (0x0 << NAU8360_HW_CLK_SEL_SFT) +#define NAU8360_HW_CLK_DIV_SFT 7 +#define NAU8360_HW_CLK_DIV_MAX NAU8360_DSP_CLK_DIV_MAX +#define NAU8360_HW_CLK_DIV_MASK (0x7 << NAU8360_HW_CLK_DIV_SFT) + +/* NAU8360_R07_GP_CTRL (0x7) */ +#define NAU8360_GPIO1_IEOE_SFT 14 +#define NAU8360_GPIO1_IEOE_MASK (0x3 << NAU8360_GPIO1_IEOE_SFT) +#define NAU8360_GPIO2_IEOE_SFT 12 +#define NAU8360_GPIO2_IEOE_MASK (0x3 << NAU8360_GPIO2_IEOE_SFT) +#define NAU8360_GPIO3_IEOE_SFT 10 +#define NAU8360_GPIO3_IEOE_MASK (0x3 << NAU8360_GPIO3_IEOE_SFT) +#define NAU8360_GPIO4_IEOE_SFT 8 +#define NAU8360_GPIO4_IEOE_MASK (0x3 << NAU8360_GPIO4_IEOE_SFT) +#define NAU8360_GPIO1_IN_STAT_SFT 7 +#define NAU8360_GPIO1_IN_STAT_MASK (0x1 << NAU8360_GPIO1_IN_STAT_SFT) +#define NAU8360_GPIO2_IN_STAT_SFT 6 +#define NAU8360_GPIO2_IN_STAT_MASK (0x1 << NAU8360_GPIO2_IN_STAT_SFT) +#define NAU8360_GPIO3_IN_STAT_SFT 5 +#define NAU8360_GPIO3_IN_STAT_MASK (0x1 << NAU8360_GPIO3_IN_STAT_SFT) +#define NAU8360_GPIO4_IN_STAT_SFT 4 +#define NAU8360_GPIO4_IN_STAT_MASK (0x1 << NAU8360_GPIO4_IN_STAT_SFT) + +/* NAU8360_R08_GP_CTRL0 (0x8) */ +#define NAU8360_GPIO2_MUX_SFT 14 +#define NAU8360_GPIO2_MUX_MASK (0x3 << NAU8360_GPIO2_MUX_SFT) +#define NAU8360_GPIO2_SR_SFT 13 +#define NAU8360_GPIO2_SR_MASK (0x1 << NAU8360_GPIO2_SR_SFT) +#define NAU8360_GPIO2_CDS_SFT 11 +#define NAU8360_GPIO2_CDS_MASK (0x3 << NAU8360_GPIO2_CDS_SFT) +#define NAU8360_GPIO2_PS_SFT 10 +#define NAU8360_GPIO2_PS_MASK (0x1 << NAU8360_GPIO2_PS_SFT) +#define NAU8360_GPIO2_PE_SFT 9 +#define NAU8360_GPIO2_PE_EN (0x1 << NAU8360_GPIO2_PE_SFT) +#define NAU8360_GPIO1_MUX_SFT 6 +#define NAU8360_GPIO1_MUX_MASK (0x3 << NAU8360_GPIO1_MUX_SFT) +#define NAU8360_GPIO1_SR_SFT 5 +#define NAU8360_GPIO1_SR_MASK (0x1 << NAU8360_GPIO1_SR_SFT) +#define NAU8360_GPIO1_CDS_SFT 3 +#define NAU8360_GPIO1_CDS_MASK (0x3 << NAU8360_GPIO1_CDS_SFT) +#define NAU8360_GPIO1_PS_SFT 2 +#define NAU8360_GPIO1_PS_MASK (0x1 << NAU8360_GPIO1_PS_SFT) +#define NAU8360_GPIO1_PE_SFT 1 +#define NAU8360_GPIO1_PE_EN (0x1 << NAU8360_GPIO1_PE_SFT) + +/* NAU8360_R09_GP_CTRL1 (0x9) */ +#define NAU8360_GPIO4_MUX_SFT 14 +#define NAU8360_GPIO4_MUX_MASK (0x3 << NAU8360_GPIO4_MUX_SFT) +#define NAU8360_GPIO4_SR_SFT 13 +#define NAU8360_GPIO4_SR_MASK (0x1 << NAU8360_GPIO4_SR_SFT) +#define NAU8360_GPIO4_CDS_SFT 11 +#define NAU8360_GPIO4_CDS_MASK (0x3 << NAU8360_GPIO4_CDS_SFT) +#define NAU8360_GPIO4_PS_SFT 10 +#define NAU8360_GPIO4_PS_MASK (0x1 << NAU8360_GPIO4_PS_SFT) +#define NAU8360_GPIO4_PE_SFT 9 +#define NAU8360_GPIO4_PE_EN (0x1 << NAU8360_GPIO4_PE_SFT) +#define NAU8360_GPIO3_MUX_SFT 6 +#define NAU8360_GPIO3_MUX_MASK (0x3 << NAU8360_GPIO3_MUX_SFT) +#define NAU8360_GPIO3_SR_SFT 5 +#define NAU8360_GPIO3_SR_MASK (0x1 << NAU8360_GPIO3_SR_SFT) +#define NAU8360_GPIO3_CDS_SFT 3 +#define NAU8360_GPIO3_CDS_MASK (0x3 << NAU8360_GPIO3_CDS_SFT) +#define NAU8360_GPIO3_PS_SFT 2 +#define NAU8360_GPIO3_PS_MASK (0x1 << NAU8360_GPIO3_PS_SFT) +#define NAU8360_GPIO3_PE_SFT 1 +#define NAU8360_GPIO3_PE_EN (0x1 << NAU8360_GPIO3_PE_SFT) + +/* NAU8360_R0B_I2S_PCM_CTRL1 (0x0b) */ +#define NAU8360_EN_TDM_TX_SFT 15 +#define NAU8360_EN_TDM_TX (0x1 << NAU8360_EN_TDM_TX_SFT) +#define NAU8360_EN_TDM_RX_SFT 14 +#define NAU8360_EN_TDM_RX (0x1 << NAU8360_EN_TDM_RX_SFT) +#define NAU8360_FRAME_START_SFT 8 +#define NAU8360_FRAME_START_MASK (0x1 << NAU8360_FRAME_START_SFT) +#define NAU8360_FRAME_START_H2L (0x1 << NAU8360_FRAME_START_SFT) +#define NAU8360_FRAME_START_L2H (0x0 << NAU8360_FRAME_START_SFT) +#define NAU8360_RX_JUSTIFY_SFT 7 +#define NAU8360_RX_JUSTIFY_MASK (0x1 << NAU8360_RX_JUSTIFY_SFT) +#define NAU8360_RX_RIGHT_JUSTIFY (0x1 << NAU8360_RX_JUSTIFY_SFT) +#define NAU8360_RX_LEFT_JUSTIFY (0x0 << NAU8360_RX_JUSTIFY_SFT) +#define NAU8360_RX_OFFSET_SFT 2 +#define NAU8360_RX_OFFSET_MASK (0x1f << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_I2S (0x1 << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_LEFT (0x0 << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_RIGHT (0x0 << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_PCM_A (0x1 << NAU8360_RX_OFFSET_SFT) +#define NAU8360_RX_OFFSET_PCM_B (0x0 << NAU8360_RX_OFFSET_SFT) + +/* NAU8360_R0C_I2S_PCM_CTRL2 (0xc) */ +#define NAU8360_TDM_DLEN_SFT 10 +#define NAU8360_TDM_DLEN_MASK (0x3 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_DLEN_32 (0x3 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_DLEN_24 (0x2 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_DLEN_20 (0x1 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_DLEN_16 (0x0 << NAU8360_TDM_DLEN_SFT) +#define NAU8360_TDM_CLEN_SFT 8 +#define NAU8360_TDM_CLEN_MASK (0x3 << NAU8360_TDM_CLEN_SFT) +#define NAU8360_TDM_CLEN_32 (0x2 << NAU8360_TDM_CLEN_SFT) +#define NAU8360_TDM_CLEN_24 (0x1 << NAU8360_TDM_CLEN_SFT) +#define NAU8360_TDM_CLEN_16 (0x0 << NAU8360_TDM_CLEN_SFT) +#define NAU8360_RX_DACR_SFT 4 +#define NAU8360_RX_DACR_MASK (0xf << NAU8360_RX_DACR_SFT) +#define NAU8360_RX_DACL_MASK 0xf + +/* NAU8360_R0D_I2S_PCM_CTRL3 (0x0d) */ +#define NAU8360_TX_FILL_SFT 12 +#define NAU8360_TX_FILL_MASK (0x1 << NAU8360_TX_FILL_SFT) +#define NAU8360_TX_FILL_HIGHZ (0x1 << NAU8360_TX_FILL_SFT) +#define NAU8360_TX_FILL_ZERO (0x0 << NAU8360_TX_FILL_SFT) +#define NAU8360_TX_OFFSET_SFT 9 +#define NAU8360_TX_OFFSET_MASK (0x7 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_I2S (0x1 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_LEFT (0x0 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_RIGHT (0x0 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_PCM_A (0x1 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_TX_OFFSET_PCM_B (0x0 << NAU8360_TX_OFFSET_SFT) +#define NAU8360_VSNS_L_SLEN_SFT 7 +#define NAU8360_VSNS_L_SLEN_MASK (0x1 << NAU8360_VSNS_L_SLEN_SFT) +#define NAU8360_VSNS_L_SLEN_16 (0x1 << NAU8360_VSNS_L_SLEN_SFT) +#define NAU8360_VSNS_L_SLEN_8 (0x0 << NAU8360_VSNS_L_SLEN_SFT) +#define NAU8360_VSNS_L_TX_EN_SFT 6 +#define NAU8360_VSNS_L_TX_EN (0x1 << NAU8360_VSNS_L_TX_EN_SFT) +#define NAU8360_VSNS_L_SLOT_MASK 0x3f + +/* NAU8360_R0E_I2S_DATA_CTRL1 (0x0e) */ +#define NAU8360_ISNS_L_SLEN_SFT 15 +#define NAU8360_ISNS_L_SLEN_MASK (0x1 << NAU8360_ISNS_L_SLEN_SFT) +#define NAU8360_ISNS_L_SLEN_16 (0x1 << NAU8360_ISNS_L_SLEN_SFT) +#define NAU8360_ISNS_L_SLEN_8 (0x0 << NAU8360_ISNS_L_SLEN_SFT) +#define NAU8360_ISNS_L_TX_EN_SFT 14 +#define NAU8360_ISNS_L_TX_EN (0x1 << NAU8360_ISNS_L_TX_EN_SFT) +#define NAU8360_ISNS_L_SLOT_SFT 8 +#define NAU8360_ISNS_L_SLOT_MASK (0x3f << NAU8360_ISNS_L_SLOT_SFT) +#define NAU8360_PDM_L_SLEN_SFT 7 +#define NAU8360_PDM_L_SLEN_MASK (0x1 << NAU8360_PDM_L_SLEN_SFT) +#define NAU8360_PDM_L_SLEN_24 (0x1 << NAU8360_PDM_L_SLEN_SFT) +#define NAU8360_PDM_L_SLEN_16 (0x0 << NAU8360_PDM_L_SLEN_SFT) +#define NAU8360_PDM_L_TX_EN_SFT 6 +#define NAU8360_PDM_L_TX_EN (0x1 << NAU8360_PDM_L_TX_EN_SFT) +#define NAU8360_PDM_L_SLOT_MASK 0x3f + +/* NAU8360_R0F_I2S_DATA_CTRL2 (0xf) */ +#define NAU8360_VBAT_TX_EN_SFT 14 +#define NAU8360_VBAT_TX_EN (0x1 << NAU8360_VBAT_TX_EN_SFT) +#define NAU8360_VBAT_SLOT_SFT 8 +#define NAU8360_VBAT_SLOT_MASK (0x3f << NAU8360_VBAT_SLOT_SFT) +#define NAU8360_PDM_R_SLEN_SFT 7 +#define NAU8360_PDM_R_SLEN_MASK (0x1 << NAU8360_PDM_R_SLEN_SFT) +#define NAU8360_PDM_R_SLEN_24 (0x1 << NAU8360_PDM_R_SLEN_SFT) +#define NAU8360_PDM_R_SLEN_16 (0x0 << NAU8360_PDM_R_SLEN_SFT) +#define NAU8360_PDM_R_TX_EN_SFT 6 +#define NAU8360_PDM_R_TX_EN (0x1 << NAU8360_PDM_R_TX_EN_SFT) +#define NAU8360_PDM_R_SLOT_MASK 0x3f + +/* NAU8360_R10_I2S_DATA_CTRL3 (0x10) */ +#define NAU8360_RX_ANC_R_SFT 12 +#define NAU8360_RX_ANC_R_MASK (0xf << NAU8360_RX_ANC_R_SFT) +#define NAU8360_RX_ANC_L_SFT 8 +#define NAU8360_RX_ANC_L_MASK (0xf << NAU8360_RX_ANC_L_SFT) +#define NAU8360_TDM_LOOPBACK_SFT 7 +#define NAU8360_TDM_LOOPBACK (0x1 << NAU8360_TDM_LOOPBACK_SFT) +#define NAU8360_TEMP_TX_EN_SFT 6 +#define NAU8360_TEMP_TX_EN (0x1 << NAU8360_TEMP_TX_EN_SFT) +#define NAU8360_TEMP_SLOT_MASK 0x3f + +/* NAU8360_R11_I2S_DATA_CTRL4 (0x11) */ +#define NAU8360_VSNS_R_SLEN_SFT 15 +#define NAU8360_VSNS_R_SLEN_MASK (0x1 << NAU8360_VSNS_R_SLEN_SFT) +#define NAU8360_VSNS_R_SLEN_16 (0x1 << NAU8360_VSNS_R_SLEN_SFT) +#define NAU8360_VSNS_R_SLEN_8 (0x0 << NAU8360_VSNS_R_SLEN_SFT) +#define NAU8360_VSNS_R_TX_EN_SFT 14 +#define NAU8360_VSNS_R_TX_EN (0x1 << NAU8360_VSNS_R_TX_EN_SFT) +#define NAU8360_VSNS_R_SLOT_SFT 8 +#define NAU8360_VSNS_R_SLOT_MASK (0x3f << NAU8360_VSNS_R_SLOT_SFT) +#define NAU8360_ISNS_R_SLEN_SFT 7 +#define NAU8360_ISNS_R_SLEN_MASK (0x1 << NAU8360_ISNS_R_SLEN_SFT) +#define NAU8360_ISNS_R_SLEN_16 (0x1 << NAU8360_ISNS_R_SLEN_SFT) +#define NAU8360_ISNS_R_SLEN_8 (0x0 << NAU8360_ISNS_R_SLEN_SFT) +#define NAU8360_ISNS_R_TX_EN_SFT 6 +#define NAU8360_ISNS_R_TX_EN (0x1 << NAU8360_ISNS_R_TX_EN_SFT) +#define NAU8360_ISNS_R_SLOT_MASK 0x3f + +/* NAU8360_R12_PATH_CTRL (0x12) */ +#define NAU8360_SEL_HW1_SFT 9 +#define NAU8360_SEL_HW1_MASK (0x1 << NAU8360_SEL_HW1_SFT) +#define NAU8360_SEL_HW1_OUT (0x1 << NAU8360_SEL_HW1_SFT) +#define NAU8360_SEL_HW1_BYP (0x0 << NAU8360_SEL_HW1_SFT) +#define NAU8360_AUD_SEL_SFT 7 +#define NAU8360_AUD_SEL_MASK (0x1 << NAU8360_AUD_SEL_SFT) +#define NAU8360_AUD_SEL_SINCOUT (0x1 << NAU8360_AUD_SEL_SFT) +#define NAU8360_AUD_SEL_SINCBYP (0x0 << NAU8360_AUD_SEL_SFT) +#define NAU8360_SEL_HW2_SFT 6 +#define NAU8360_SEL_HW2_MASK (0x1 << NAU8360_SEL_HW2_SFT) +#define NAU8360_SEL_HW2_OUT (0x1 << NAU8360_SEL_HW2_SFT) +#define NAU8360_SEL_HW2_BYP (0x0 << NAU8360_SEL_HW2_SFT) +#define NAU8360_DAC_SEL_SFT 5 +#define NAU8360_DAC_SEL_MASK (0x1 << NAU8360_DAC_SEL_SFT) +#define NAU8360_DAC_SEL_DSP (0x1 << NAU8360_DAC_SEL_SFT) +#define NAU8360_DAC_SEL_BYP (0x0 << NAU8360_DAC_SEL_SFT) +#define NAU8360_SEL_HW3_SFT 4 +#define NAU8360_SEL_HW3_MASK (0x1 << NAU8360_SEL_HW3_SFT) +#define NAU8360_SEL_HW3_OUT (0x1 << NAU8360_SEL_HW3_SFT) +#define NAU8360_SEL_HW3_BYP (0x0 << NAU8360_SEL_HW3_SFT) + +/* NAU8360_R17_I2S0_DATA_CTRL5 (0x17)*/ +#define NAU8360_AEC_L_SLEN_SFT 15 +#define NAU8360_AEC_L_SLEN_MASK (0x1 << NAU8360_AEC_L_SLEN_SFT) +#define NAU8360_AEC_L_SLEN_32 (0x1 << NAU8360_AEC_L_SLEN_SFT) +#define NAU8360_AEC_L_SLEN_16 (0x0 << NAU8360_AEC_L_SLEN_SFT) +#define NAU8360_AEC_L_EN_SFT 14 +#define NAU8360_AEC_L_EN (0x1 << NAU8360_AEC_L_EN_SFT) +#define NAU8360_AEC_L_SLOT_SFT 8 +#define NAU8360_AEC_L_SLOT_MASK (0x3f << NAU8360_AEC_L_SLOT_SFT) +#define NAU8360_AEC_R_SLEN_SFT 7 +#define NAU8360_AEC_R_SLEN_MASK (0x1 << NAU8360_AEC_R_SLEN_SFT) +#define NAU8360_AEC_R_SLEN_32 (0x1 << NAU8360_AEC_R_SLEN_SFT) +#define NAU8360_AEC_R_SLEN_16 (0x0 << NAU8360_AEC_R_SLEN_SFT) +#define NAU8360_AEC_R_EN_SFT 6 +#define NAU8360_AEC_R_EN (0x1 << NAU8360_AEC_R_EN_SFT) +#define NAU8360_AEC_R_SLOT_MASK 0x3f + +/* NAU8360_R1A_DSP_CORE_CTRL2 (0x1a) */ +#define NAU8360_DSP_RUNSTALL_SFT 4 +#define NAU8360_DSP_RUNSTALL (0x1 << NAU8360_DSP_RUNSTALL_SFT) + +/* NAU8360_R21_VBAT_READOUT (0x21) */ +#define NAU8360_VBAT_BASE 1108 +#define NAU8360_VBAT_STEP 12353 + +/* NAU8360_R40_CLK_DET_CTRL (0x40) */ +#define NAU8360_APWRUPEN_SFT 15 +#define NAU8360_APWRUPEN (0x1 << NAU8360_APWRUPEN_SFT) +#define NAU8360_CLKPWRUPEN_SFT 14 +#define NAU8360_CLKPWRUPEN (0x1 << NAU8360_CLKPWRUPEN_SFT) +#define NAU8360_SRATE_SFT 10 +#define NAU8360_SRATE_MASK (0x7 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_192000 (0x4 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_96000 (0x3 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_48000 (0x2 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_32000 (0x1 << NAU8360_SRATE_SFT) +#define NAU8360_SRATE_16000 (0x0 << NAU8360_SRATE_SFT) +#define NAU8360_MCLK_RATE_SFT 7 +#define NAU8360_MCLK_RATE_MASK (0x7 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_24576 (0x4 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_24000 (0x3 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_19200 (0x2 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_12288 (0x1 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_MCLK_RATE_12000 (0x0 << NAU8360_MCLK_RATE_SFT) +#define NAU8360_FS_MCLK_DET_SFT 6 +#define NAU8360_FS_MCLK_DET (0x1 << NAU8360_FS_MCLK_DET_SFT) +#define NAU8360_FS_BCLK_DET_SFT 5 +#define NAU8360_FS_BCLK_DET (0x1 << NAU8360_FS_BCLK_DET_SFT) + +/* NAU8360_R41_CLK_CTL2 (0x41) */ +#define NAU8360_NOISE_FILTER_THD_SFT 12 +#define NAU8360_NOISE_FILTER_THDT_MASK (0xf << NAU8360_NOISE_FILTER_THD_SFT) +#define NAU8360_SEL_BLOCK_SIZE_SFT 6 +#define NAU8360_SEL_BLOCK_SIZE_MASK (0x7 << NAU8360_SEL_BLOCK_SIZE_SFT) +#define NAU8360_EN_ABCLKDET_SFT 3 +#define NAU8360_EN_ABCLKDET (0x1 << NAU8360_EN_ABCLKDET_SFT) +#define NAU8360_EN_DBCLKDET_SFT 2 +#define NAU8360_EN_DBCLKDET (0x1 << NAU8360_EN_DBCLKDET_SFT) +#define NAU8360_EN_AMCLKDET_SFT 1 +#define NAU8360_EN_AMCLKDET (0x1 << NAU8360_EN_AMCLKDET_SFT) +#define NAU8360_EN_DMCLKDET 0x1 + +/* NAU8360_R46_I2C_DEVICE_ID (0x46) */ +#define NAU8360_REG_SI_REV_MASK 0xff +#define NAU8360_REG_SI_REV_B 0xf2 +#define NAU8360_REG_SI_REV_C 0xf5 + +/* NAU8360_R5D_SINC_CFG (0x5d) */ +#define NAU8360_DAC_SINC_OSR_SFT 4 +#define NAU8360_DAC_SINC_OSR_MASK (0x3 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_DAC_SINC_OSR_250 (0x3 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_DAC_SINC_OSR_200 (0x2 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_DAC_SINC_OSR_128 (0x1 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_DAC_SINC_OSR_0 (0x0 << NAU8360_DAC_SINC_OSR_SFT) +#define NAU8360_IVSENSE_BS_OSR_MASK 0x3 +#define NAU8360_IVSENSE_BS_OSR_25 0x3 +#define NAU8360_IVSENSE_BS_OSR_64 0x2 +#define NAU8360_IVSENSE_BS_OSR_32 0x1 +#define NAU8360_IVSENSE_BS_OSR_16 0x0 + +/* NAU8360_R60_RST (0x60) */ +#define NAU8360_PD_POR5_SFT 4 +#define NAU8360_PD_POR5_PWD (0x1 << NAU8360_PD_POR5_SFT) +#define NAU8360_EFUSE_CTRL_SFT 3 +#define NAU8360_EFUSE_CTRL_EN (0x1 << NAU8360_EFUSE_CTRL_SFT) + +/* NAU8360_R67_ANALOG_CONTROL_0 (0x67) */ +#define NAU8360_SCP_CLEAR_MODE_SFT 15 +#define NAU8360_SCP_CLEAR_MODE_MASK (0x1 << NAU8360_SCP_CLEAR_MODE_SFT) +#define NAU8360_SCP_CLEAR_MODE_AUTO (0x1 << NAU8360_SCP_CLEAR_MODE_SFT) +#define NAU8360_SCP_CLEAR_MODE_MANU (0x0 << NAU8360_SCP_CLEAR_MODE_SFT) +#define NAU8360_SCP_CLEAR_SFT 14 +#define NAU8360_SCP_CLEAR (0x1 << NAU8360_SCP_CLEAR_SFT) +#define NAU8360_HV_EN_SFT 13 +#define NAU8360_HV_EN (0x1 << NAU8360_HV_EN_SFT) +#define NAU8360_ANA_MUTE_SFT 8 +#define NAU8360_ANA_MUTE (0x1 << NAU8360_ANA_MUTE_SFT) +#define NAU8360_MOD_GAIN_SFT 5 +#define NAU8360_MOD_GAIN_MASK (0x7 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_20DB (0x7 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_18DB (0x6 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_16DB (0x5 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_14DB (0x4 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_12DB (0x3 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_10DB (0x2 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_8DB (0x1 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_MOD_GAIN_6DB (0x0 << NAU8360_MOD_GAIN_SFT) +#define NAU8360_CM_COMP_SFT 4 +#define NAU8360_CM_COMP_EN (0x1 << NAU8360_CM_COMP_SFT) +#define NAU8360_V_PBTL_SFT 3 +#define NAU8360_V_PBTL_EN (0x1 << NAU8360_V_PBTL_SFT) + +/* NAU8360_R68_ANALOG_CONTROL_1 (0x68) */ +#define NAU8360_TRIMSCR_SFT 6 +#define NAU8360_TRIMSCR_MASK (0x3 << NAU8360_TRIMSCR_SFT) +#define NAU8360_TRIMSCR_HIGH (0x3 << NAU8360_TRIMSCR_SFT) +#define NAU8360_TRIMSCR_MHIGH (0x2 << NAU8360_TRIMSCR_SFT) +#define NAU8360_TRIMSCR_MLOW (0x1 << NAU8360_TRIMSCR_SFT) +#define NAU8360_TRIMSCR_LOW (0x0 << NAU8360_TRIMSCR_SFT) +#define NAU8360_DTX_SFT 5 +#define NAU8360_DTX_EN (0x1 << NAU8360_DTX_SFT) +#define NAU8360_DRVCTL_SEGL_SFT 4 +#define NAU8360_DRVCTL_SEGL_FULL (0x1 << NAU8360_DRVCTL_SEGL_SFT) +#define NAU8360_DRVCTL_SEGR_SFT 2 +#define NAU8360_DRVCTL_SEGR_FULL (0x1 << NAU8360_DRVCTL_SEGR_SFT) +#define NAU8360_SCP_TEST_SFT 1 +#define NAU8360_SCP_TEST (0x1 << NAU8360_SCP_TEST_SFT) +#define NAU8360_SEG_AUTO_CTRL_EN 0x1 + +/* NAU8360_R69_ANALOG_CONTROL_3 (0x69) */ +#define NAU8360_ANA_STATUS_OTP_SFT 8 +#define NAU8360_ANA_STATUS_OTP_MASK (0x1 << NAU8360_ANA_STATUS_OTP_SFT) +#define NAU8360_ANA_STATUS_OVLO_SFT 7 +#define NAU8360_ANA_STATUS_OVLO_MASK (0x1 << NAU8360_ANA_STATUS_OVLO_SFT) +#define NAU8360_ANA_STATUS_UVLO_SFT 6 +#define NAU8360_ANA_STATUS_UVLO_MASK (0x1 << NAU8360_ANA_STATUS_UVLO_SFT) +#define NAU8360_ANA_STATUS_SCP_SFT 4 +#define NAU8360_ANA_STATUS_SCP_MASK (0x1 << NAU8360_ANA_STATUS_SCP_SFT) +#define NAU8360_ANA_STATUS_EN_V2I_SFT 3 +#define NAU8360_ANA_STATUS_EN_V2I_MASK (0x1 << NAU8360_ANA_STATUS_EN_V2I_SFT) +#define NAU8360_ANA_STATUS_FAULT_SFT 2 +#define NAU8360_ANA_STATUS_FAULT_MASK (0x1 << NAU8360_ANA_STATUS_FAULT_SFT) +#define NAU8360_ANA_STATUS_DC_DET_SFT 1 +#define NAU8360_ANA_STATUS_DC_DET_MASK (0x1 << NAU8360_ANA_STATUS_DC_DET_SFT) +#define NAU8360_ANA_STATUS_EN_ALL_MASK 0x1 + +/* NAU8360_R6A_SARADC_CFG0 (0x6a) */ +#define NAU8360_SARADC_SFT 15 +#define NAU8360_SARADC_EN (0x1 << NAU8360_SARADC_SFT) +#define NAU8360_RESETB_SARADC_SFT 14 +#define NAU8360_RESETB_SARADC_MASK (0x1 << NAU8360_RESETB_SARADC_SFT) +#define NAU8360_RESETB_SARADC_NOR (0x1 << NAU8360_RESETB_SARADC_SFT) +#define NAU8360_RESETB_SARADC_RST (0x0 << NAU8360_RESETB_SARADC_SFT) +#define NAU8360_SARADC_CAL_SFT 13 +#define NAU8360_SARADC_CAL (0x1 << NAU8360_SARADC_CAL_SFT) +#define NAU8360_SARADC_DIV_SFT 12 +#define NAU8360_SARADC_DIV (0x1 << NAU8360_SARADC_DIV_SFT) +#define NAU8360_SARADC_CALSEL_SFT 9 +#define NAU8360_SARADC_CALSEL_MASK (0x7 << NAU8360_SARADC_CALSEL_SFT) +#define NAU8360_TRIMSMPL_SFT 3 +#define NAU8360_TRIMSMPL_MASK (0x7 << NAU8360_TRIMSMPL_SFT) +#define NAU8360_SAR_INPUT_CH_SEL_SFT 2 +#define NAU8360_SAR_INPUT_CH_SEL_MASK (0x1 << NAU8360_SAR_INPUT_CH_SEL_SFT) +#define NAU8360_VBAT_AVG_SFT 1 +#define NAU8360_VBAT_AVG_EN (0x1 << NAU8360_VBAT_AVG_SFT) +#define NAU8360_VTEMP_AVG_EN 0x1 + +/* NAU8360_R6B_SARADC_CFG1 (0x6b) */ +#define NAU8360_VTEMP_SFT 15 +#define NAU8360_VTEMP_EN (0x1 << NAU8360_VTEMP_SFT) +#define NAU8360_VBATDIV_SFT 14 +#define NAU8360_VBATDIV_EN (0x1 << NAU8360_VBATDIV_SFT) +#define NAU8360_VREF_SFT 10 +#define NAU8360_VREF_EN (0x1 << NAU8360_VREF_SFT) +#define NAU8360_PRELOAD_VREF_SFT 9 +#define NAU8360_PRELOAD_VREF_EN (0x1 << NAU8360_PRELOAD_VREF_SFT) +#define NAU8360_VTEMP_AVG_N_SFT 2 +#define NAU8360_VTEMP_AVG_N_MASK (0x3 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VTEMP_AVG_N_512 (0x3 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VTEMP_AVG_N_128 (0x2 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VTEMP_AVG_N_32 (0x1 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VTEMP_AVG_N_1 (0x0 << NAU8360_VTEMP_AVG_N_SFT) +#define NAU8360_VBAT_AVG_N_MASK 0x3 +#define NAU8360_VBAT_AVG_N_512 0x3 +#define NAU8360_VBAT_AVG_N_128 0x2 +#define NAU8360_VBAT_AVG_N_32 0x1 +#define NAU8360_VBAT_AVG_N_1 0 + +/* NAU8360_R6C_IVSNS_CFG0 (0x6c) */ +#define NAU8360_PD_VSNS_R_SFT 15 +#define NAU8360_PD_VSNS_R_PMD (0x1 << NAU8360_PD_VSNS_R_SFT) +#define NAU8360_PD_VSNS_L_SFT 14 +#define NAU8360_PD_VSNS_L_PMD (0x1 << NAU8360_PD_VSNS_L_SFT) +#define NAU8360_PD_ISNS_R_SFT 13 +#define NAU8360_PD_ISNS_R_PMD (0x1 << NAU8360_PD_ISNS_R_SFT) +#define NAU8360_PD_ISNS_L_SFT 12 +#define NAU8360_PD_ISNS_L_PMD (0x1 << NAU8360_PD_ISNS_L_SFT) +#define NAU8360_RESET_SC_VADC_R_SFT 11 +#define NAU8360_RESET_SC_VADC_R_RST (0x1 << NAU8360_RESET_SC_VADC_R_SFT) +#define NAU8360_RESET_SC_VADC_L_SFT 10 +#define NAU8360_RESET_SC_VADC_L_RST (0x1 << NAU8360_RESET_SC_VADC_L_SFT) +#define NAU8360_RESET_SC_IADC_R_SFT 9 +#define NAU8360_RESET_SC_IADC_R_RST (0x1 << NAU8360_RESET_SC_IADC_R_SFT) +#define NAU8360_RESET_SC_IADC_L_SFT 8 +#define NAU8360_RESET_SC_IADC_L_RST (0x1 << NAU8360_RESET_SC_IADC_L_SFT) +#define NAU8360_PBTL_ISENE_LR_SFT 7 +#define NAU8360_PBTL_ISENE_LR_MASK (0x1 << NAU8360_PBTL_ISENE_LR_SFT) +#define NAU8360_PBTL_ISENE_LR_ILR (0x1 << NAU8360_PBTL_ISENE_LR_SFT) +#define NAU8360_PBTL_ISENE_LR_I2R (0x0 << NAU8360_PBTL_ISENE_LR_SFT) +#define NAU8360_PD_LDO_SDM_IVSNS_SFT 6 +#define NAU8360_PD_LDO_SDM_IVSNS_PMD (0x1 << NAU8360_PD_LDO_SDM_IVSNS_SFT) + +/* NAU8360_R6D_IVSNS_CFG1 (0x6d) */ +#define NAU8360_PD_V2I_IVSNS_SFT 13 +#define NAU8360_PD_V2I_IVSNS_MASK (0x1 << NAU8360_PD_V2I_IVSNS_SFT) +#define NAU8360_PD_VREF_SDM_IVSNS_SFT 12 +#define NAU8360_PD_VREF_SDM_IVSNS_MASK (0x1 << NAU8360_PD_VREF_SDM_IVSNS_SFT) +#define NAU8360_VSNS_PGA_CUR_SET_SFT 10 +#define NAU8360_VSNS_PGA_CUR_SET_MASK (0x3 << NAU8360_VSNS_PGA_CUR_SET_SFT) +#define NAU8360_VSNS_GAIN_SFT 8 +#define NAU8360_VSNS_GAIN_MASK (0x3 << NAU8360_VSNS_GAIN_SFT) +#define NAU8360_VREF_CUR_SFT 6 +#define NAU8360_VREF_CUR_MASK (0x3 << NAU8360_VREF_CUR_SFT) +#define NAU8360_ISNS_PGA_CUR_SFT 4 +#define NAU8360_ISNS_PGA_CUR_MASK (0x3 << NAU8360_ISNS_PGA_CUR_SFT) +#define NAU8360_ISNS_GAIN_SFT 2 +#define NAU8360_ISNS_GAIN_MASK (0x3 << NAU8360_ISNS_GAIN_SFT) +#define NAU8360_SDM_DELAY_TRIM_MASK 0x3 + +/* NAU8360_R6E_DAC_CFG0 (0x6e) */ +#define NAU8360_PD_DACL_SFT 11 +#define NAU8360_PD_DACL_DIS (0x1 << NAU8360_PD_DACL_SFT) +#define NAU8360_PD_DACR_SFT 10 +#define NAU8360_PD_DACR_DIS (0x1 << NAU8360_PD_DACR_SFT) +#define NAU8360_PD_CHOP_DAC_SFT 9 +#define NAU8360_PD_CHOP_DAC_MASK (0x1 << NAU8360_PD_CHOP_DAC_SFT) +#define NAU8360_DAC_RES_SFT 7 +#define NAU8360_DAC_RES_MASK (0x1 << NAU8360_DAC_RES_SFT) +#define NAU8360_DAC_CUR_SFT 6 +#define NAU8360_DAC_CUR_MASK (0x1 << NAU8360_DAC_CUR_SFT) +#define NAU8360_DAC_CUR_3_2DB (0x1 << NAU8360_DAC_CUR_SFT) +#define NAU8360_DAC_CUR_0DB (0x0 << NAU8360_DAC_CUR_SFT) +#define NAU8360_DAC_BUFOPCUR_SFT 4 +#define NAU8360_DAC_BUFOPCUR_MASK (0x3 << NAU8360_DAC_BUFOPCUR_SFT) +#define NAU8360_DAC_IVOPCUR_SFT 2 +#define NAU8360_DAC_IVOPCUR_MASK (0x3 << NAU8360_DAC_IVOPCUR_SFT) +#define NAU8360_DAC_LPFOP_CUR_MASK 0x3 + +/* NAU8360_R71_CLK_DIV_CFG (0x71) */ +#define NAU8360_IVSNS_CLK_INV_SEL_SFT 10 +#define NAU8360_IVSNS_CLK_INV_SEL_MASK (0x1 << NAU8360_IVSNS_CLK_INV_SEL_SFT) +#define NAU8360_CLK_ANA_SEL_SFT 7 +#define NAU8360_CLK_ANA_SEL_MASK (0x3 << NAU8360_CLK_ANA_SEL_SFT) +#define NAU8360_CLK_ANA_SEL_PLL (0x2 << NAU8360_CLK_ANA_SEL_SFT) +#define NAU8360_CLK_ANA_SEL_BCLK (0x1 << NAU8360_CLK_ANA_SEL_SFT) +#define NAU8360_CLK_ANA_SEL_MCLK (0x0 << NAU8360_CLK_ANA_SEL_SFT) +#define NAU8360_IVSNS_CLK_DIV_SFT 4 +#define NAU8360_IVSNS_CLK_DIV_MASK (0x7 << NAU8360_IVSNS_CLK_DIV_SFT) +#define NAU8360_DAC_OUTPUT_EDGE_SFT 3 +#define NAU8360_DAC_OUTPUT_EDGE_MASK (0x1 << NAU8360_DAC_OUTPUT_EDGE_SFT) +#define NAU8360_DAC_CLK_DIV_SFT 1 +#define NAU8360_DAC_CLK_DIV_MASK (0x3 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CLK_DIV_8 (0x3 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CLK_DIV_4 (0x2 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CLK_DIV_2 (0x1 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CLK_DIV_1 (0x0 << NAU8360_DAC_CLK_DIV_SFT) +#define NAU8360_DAC_CHOP_CLK_DIV_MASK 0x1 +#define NAU8360_DAC_CHOP_CLK_DIV_32 0x1 +#define NAU8360_DAC_CHOP_CLK_DIV_16 0x0 + +/* NAU8360_R72_PLL_CFG0 (0x72) */ +#define NAU8360_PLLOUT_DIV_SFT 10 +#define NAU8360_PLLOUT_DIV_MAX 0x1f +#define NAU8360_PLLOUT_DIV_MASK (0x1f << NAU8360_PLLOUT_DIV_SFT) +#define NAU8360_PLLOUT_DIV_BYPASS (0x0 << NAU8360_PLLOUT_DIV_SFT) +#define NAU8360_PLL_CLK_SEL_SFT 8 +#define NAU8360_PLL_CLK_SEL_MASK (0x3 << NAU8360_PLL_CLK_SEL_SFT) +#define NAU8360_PLL_CLK_SEL_HIRC (0x3 << NAU8360_PLL_CLK_SEL_SFT) +#define NAU8360_PLL_CLK_SEL_BCLK (0x1 << NAU8360_PLL_CLK_SEL_SFT) +#define NAU8360_PLL_CLK_SEL_MCLK (0x0 << NAU8360_PLL_CLK_SEL_SFT) +#define NAU8360_PD_PLL_SFT 7 +#define NAU8360_PD_PLL_MASK (0x1 << NAU8360_PD_PLL_SFT) +#define NAU8360_PD_PLL_DIS (0x1 << NAU8360_PD_PLL_SFT) +#define NAU8360_PD_PLL_EN (0x0 << NAU8360_PD_PLL_SFT) + +/* NAU8360_R73_PLL_CFG1 (0x73) */ +#define NAU8360_RSEL_SFT 13 +#define NAU8360_RSEL_MASK (0x3 << NAU8360_RSEL_SFT) +#define NAU8360_MSEL_SFT 8 +#define NAU8360_MSEL_MASK (0x1f << NAU8360_MSEL_SFT) +#define NAU8360_NSEL_MASK 0xff + +/* NAU8360_R74_PLL_CFG2 (0x74) */ +#define NAU8360_XSEL_MASK 0xfff + +/* NAU8360_R77_SOFT_SD (0x77) */ +#define NAU8360_SOFT_SD 0x55AA +#define NAU8360_SOFT_SD_EN 0 + +/* NAU8360_R78_PD_SW_DLDO (0x78) */ +#define NAU8360_PD_SW_DLDO 0xAA55 +#define NAU8360_PD_SW_DLDO_EN 0 + +/* NAU8360_R7A_DAC_TRIM_CFG2 (0x7a) */ +#define NAU8360_DAC_TEMP_SLOPE_SFT 8 +#define NAU8360_DAC_TEMP_SLOPE_MASK (0x7f << NAU8360_DAC_TEMP_SLOPE_SFT) + +/* NAU8360_R7B_IVSNS_TRIM_CFG (0x7b) */ +#define NAU8360_ISNS_TEMP_SLOPE_SFT 8 +#define NAU8360_ISNS_TEMP_SLOPE_MASK (0x7f << NAU8360_ISNS_TEMP_SLOPE_SFT) +#define NAU8360_VSNS_TEMP_SLOPE_MASK 0x7f + +/* NAU8360_R7C_MISC_TRIM_CFG (0x7c) */ +#define NAU8360_DAC_OS_SB_SFT 13 +#define NAU8360_DAC_OS_SB_SFT_MASK (0x7 << NAU8360_DAC_OS_SB_SFT) +#define NAU8360_DAC_GAIN_SB_SFT 11 +#define NAU8360_DAC_GAIN_SB_MASK (0x3 << NAU8360_DAC_GAIN_SB_SFT) +#define NAU8360_DAC_TEMP_SB_SFT 9 +#define NAU8360_DAC_TEMP_SB_MASK (0x3 << NAU8360_DAC_TEMP_SB_SFT) +#define NAU8360_ISNS_TEMP_SB_SFT 6 +#define NAU8360_ISNS_TEMP_SB_MASK (0x7 << NAU8360_ISNS_TEMP_SB_SFT) +#define NAU8360_VSNS_TEMP_SB_SFT 4 +#define NAU8360_VSNS_TEMP_SB_MASK (0x3 << NAU8360_VSNS_TEMP_SB_SFT) +#define NAU8360_ISNS_GAIN_SB_SFT 2 +#define NAU8360_ISNS_GAIN_SB_MASK (0x3 << NAU8360_ISNS_GAIN_SB_SFT) +#define NAU8360_VSNS_GAIN_SB_MASK 0x3 + +/* NAU8360_R7E_CLK_GATED_EN (0x7e)*/ +#define NAU8360_CLK_GATED_EN 0xa5a5 +#define NAU8360_CLK_GATED_DIS 0 + +/* NAU8360_R86_HW3_CTL0 (0x86) */ +#define NAU8360_HW3_DRAM_CLR_SFT 14 +#define NAU8360_HW3_DRAM_CLR (0x1 << NAU8360_HW3_DRAM_CLR_SFT) +#define NAU8360_HW3_STALL_SFT 13 +#define NAU8360_HW3_STALL (0x1 << NAU8360_HW3_STALL_SFT) + +/* NAU8360_R8C_HW3_CTL6 (0x8c)*/ +#define NAU8360_HW3_DROOP_SFT 2 +#define NAU8360_HW3_DROOP_MASK (0x1 << NAU8360_HW3_DROOP_SFT) +#define NAU8360_HW3_DROOP_192K (0x1 << NAU8360_HW3_DROOP_SFT) +#define NAU8360_HW3_DROOP_96K (0x0 << NAU8360_HW3_DROOP_SFT) +#define NAU8360_HW3_FS_MASK 0x1 +#define NAU8360_HW3_FS_192K 0x1 +#define NAU8360_HW3_FS_96K 0x0 + +/* NAU8360_R8F_HW3_CTL9 (0x8f)*/ +#define NAU8360_HW3_CH_MUTE_SFT 8 +#define NAU8360_HW3_CH_MUTE (0x1 << NAU8360_HW3_CH_MUTE_SFT) +#define NAU8360_HW3_ZERO_THD_MASK 0xff + +/* NAU8360_R90_HW2_CTL0 (0x90) */ +#define NAU8360_HW2_DRAM_CLR_SFT 14 +#define NAU8360_HW2_DRAM_CLR (0x1 << NAU8360_HW2_DRAM_CLR_SFT) +#define NAU8360_HW2_STALL_SFT 13 +#define NAU8360_HW2_STALL (0x1 << NAU8360_HW2_STALL_SFT) + +/* NAU8360_R96_HW2_CTL6 (0x96) */ +#define NAU8360_HW1_ANC_EN_SFT 15 +#define NAU8360_HW1_ANC_EN (0x1 << NAU8360_HW1_ANC_EN_SFT) +#define NAU8360_HW2_DROOP_SEL_SFT 3 +#define NAU8360_HW2_DROOP_SEL_MASK (0x1 << NAU8360_HW2_DROOP_SEL_SFT) +#define NAU8360_HW2_DROOP_SEL_LARGE (0x1 << NAU8360_HW2_DROOP_SEL_SFT) +#define NAU8360_HW2_DROOP_SEL_SMALL (0x0 << NAU8360_HW2_DROOP_SEL_SFT) +#define NAU8360_HW2_DROOP_EN_SFT 2 +#define NAU8360_HW2_DROOP_EN (0x1 << NAU8360_HW2_DROOP_EN_SFT) +#define NAU8360_HW2_LATENCY_SFT 1 +#define NAU8360_HW2_LATENCY_MASK (0x1 << NAU8360_HW2_LATENCY_SFT) +#define NAU8360_HW2_LATENCY_LOW (0x1 << NAU8360_HW2_LATENCY_SFT) +#define NAU8360_HW2_LATENCY_NOR (0x0 << NAU8360_HW2_LATENCY_SFT) +#define NAU8360_HW2_FS_MASK 0x1 +#define NAU8360_HW2_FS_192K 0x1 +#define NAU8360_HW2_FS_96K 0x0 + +/* NAU8360_R99_HW2_CTL9 (0x99) */ +#define NAU8360_HW2_VSR_SFT 9 +#define NAU8360_HW2_VSR_MASK (0x3 << NAU8360_HW2_VSR_SFT) +#define NAU8360_HW2_CH_MUTE_SFT 8 +#define NAU8360_HW2_CH_MUTE (0x1 << NAU8360_HW2_CH_MUTE_SFT) +#define NAU8360_HW2_ZERO_THD_MASK 0xff + +/* NAU8360_R9C_HW1_CTL2 (0x9c) */ +#define NAU8360_MUTE_INTRVL_SFT 11 +#define NAU8360_MUTE_INTRVL_MASK (0x3 << NAU8360_MUTE_INTRVL_SFT) +#define NAU8360_MUTE_INTRVL_699MS (0x3 << NAU8360_MUTE_INTRVL_SFT) +#define NAU8360_MUTE_INTRVL_466MS (0x2 << NAU8360_MUTE_INTRVL_SFT) +#define NAU8360_MUTE_INTRVL_233MS (0x1 << NAU8360_MUTE_INTRVL_SFT) +#define NAU8360_HW1_CH_MUTE_SFT 8 +#define NAU8360_HW1_CH_MUTE (0x1 << NAU8360_HW1_CH_MUTE_SFT) +#define NAU8360_HW1_ZERO_THD_MASK 0xff + +/* NAU8360_R9D_PEQ_CTL (0x9d) */ +#define NAU8360_PEQ_BAND_SFT 12 +#define NAU8360_PEQ_BAND_MASK (0xf << NAU8360_PEQ_BAND_SFT) +#define NAU8360_HW1_MEM_TEST_SFT 2 +#define NAU8360_HW1_MEM_TEST (0x1 << NAU8360_HW1_MEM_TEST_SFT) +#define NAU8360_HW1_MEM_CLEAR_SFT 1 +#define NAU8360_HW1_MEM_CLEAR (0x1 << NAU8360_HW1_MEM_CLEAR_SFT) +#define NAU8360_PEQ_STALL 0x1 + +/* NAU8360_RA4_ANA_REG_0 (0xa4) */ +#define NAU8360_MSEL_SAW_SFT 8 +#define NAU8360_MSEL_SAW_MASK (0xff << NAU8360_MSEL_SAW_SFT) +#define NAU8360_FTRIM_PWM_SFT 6 +#define NAU8360_FTRIM_PWM_MASK (0x3 << NAU8360_MSEL_SAW_SFT) +#define NAU8360_SEL_STCLK_SFT 5 +#define NAU8360_SEL_STCLK_MASK (0x1 << NAU8360_SEL_STCLK_SFT) +#define NAU8360_SEL_STCLK_IVCLK (0x1 << NAU8360_SEL_STCLK_SFT) +#define NAU8360_SEL_STCLK_FREERUN (0x0 << NAU8360_SEL_STCLK_SFT) +#define NAU8360_NSEL_SAW_MASK 0x1f + +/* NAU8360_RA5_ANA_REG_1 (0xa5) */ +#define NAU8360_ICP_SAW_SET_SFT 14 +#define NAU8360_ICP_SAW_SET_MASK (0x3 << NAU8360_ICP_SAW_SET_SFT) +#define NAU8360_VSAW_LV_SFT 12 +#define NAU8360_VSAW_LV_MASK (0x3 << NAU8360_VSAW_LV_SFT) +#define NAU8360_KVCO_SAW_SFT 10 +#define NAU8360_KVCO_SAW_MASK (0x3 << NAU8360_KVCO_SAW_SFT) +#define NAU8360_TRIM_LPF_SFT 8 +#define NAU8360_TRIM_LPF_MASK (0x3 << NAU8360_TRIM_LPF_SFT) +#define NAU8360_SAW_PLL_SFT 7 +#define NAU8360_SAW_PLL_MASK (0x1 << NAU8360_SAW_PLL_SFT) +#define NAU8360_SAW_PLL_NOR (0x1 << NAU8360_SAW_PLL_SFT) +#define NAU8360_SAW_PLL_RST (0x0 << NAU8360_SAW_PLL_SFT) +#define NAU8360_CLASSD_SHT_SFT 6 +#define NAU8360_CLASSD_SHT_IN (0x1 << NAU8360_CLASSD_SHT_SFT) +#define NAU8360_HVEN_SYNC_SFT 5 +#define NAU8360_HVEN_SYNC_SAW (0x1 << NAU8360_HVEN_SYNC_SFT) +#define NAU8360_SYNC_SAW_CLK_SFT 4 +#define NAU8360_SYNC_SAW_CLK_INVT (0x1 << NAU8360_SYNC_SAW_CLK_SFT) +#define NAU8360_DEPOP_CAP_SFT 2 +#define NAU8360_DEPOP_CAP_MASK (0x3 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_DEPOP_CAP_48PF (0x3 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_DEPOP_CAP_36PF (0x2 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_DEPOP_CAP_24PF (0x1 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_DEPOP_CAP_12PF (0x0 << NAU8360_DEPOP_CAP_SFT) +#define NAU8360_SW_BG_IO_SFT 1 +#define NAU8360_SW_BG_IO_CONN (0x1 << NAU8360_SW_BG_IO_SFT) +#define NAU8360_SW_VPTC_IO_EN 0x1 + +/* NAU8360 VBAT Range Thresholds */ +#define NAU8360_VBAT_MIN 8 +#define NAU8360_VBAT_MID_THRES 13 +#define NAU8360_VBAT_HIGH_THRES 19 +#define NAU8360_VBAT_MAX 24 + +/* NAU8360_R100_LEFT_BIQ0_COE (0x100) + * NAU8360_R200_RIGHT_BIQ0_COE (0x200) + */ +#define NAU8360_TOT_BAND_PER_CH 15 +#define NAU8360_TOT_BAND_COE 10 +#define NAU8360_TOT_BAND_COE_RANGE (NAU8360_TOT_BAND_COE + 2) +#define NAU8360_PEQ_REG_WIDTH (NAU8360_TOT_BAND_COE - 1) + +/* DSP stereo core */ +#define NAU8360_DSP_CORE_NUM 2 +#define NAU8360_DSP_ADDR_BYNAME(x) \ + (strstr((x), "Left") ? NAU8360_RF000_DSP_COMM : NAU8360_RF002_DSP_COMM) + +#define NAU8360_I2C_REG_LEN 2 +#define NAU8360_I2C_RXBUF_LEN 4 +#define NAU8360_I2C_TXBUF_LEN 6 +#define NAU8360_CODEC_DAI "nau8360-hifi" + +/* clock source */ +enum { + NAU8360_CLK_SRC_MCLK, + NAU8360_CLK_SRC_PLL, + NAU8360_CLK_SRC_ICLK, + NAU8360_CLK_SRC_BCLK, +}; + +/* clock target */ +enum { + NAU8360_CLK_ID_DIG, /* DIG_SYS + DSP + HW */ + NAU8360_CLK_ID_ANA, /* IV + DAC */ + NAU8360_CLK_ID_INT, /* HIRC48M standby */ +}; + +enum { + NAU8360_MCLK_FS_RATIO_250 = 250, + NAU8360_MCLK_FS_RATIO_256 = 256, + NAU8360_MCLK_FS_RATIO_400 = 400, + NAU8360_MCLK_FS_RATIO_500 = 500, + NAU8360_MCLK_FS_RATIO_512 = 512, +}; + +enum { + NAU8360_PEQ_BAND_8 = 8, + NAU8360_PEQ_BAND_12 = 12, + NAU8360_PEQ_BAND_15 = 15, +}; + +#define NAU8360_TDM_MAX_CHAN 8 +#define TDM_SLOT_NONE 255 + +enum { + NAU8360_TDM_DACL, + NAU8360_TDM_DACR, + NAU8360_TDM_ANCL, + NAU8360_TDM_ANCR, + NAU8360_TDM_RXN, +}; + +enum { + NAU8360_TDM_AECL, + NAU8360_TDM_AECR, + NAU8360_TDM_ISNSL, + NAU8360_TDM_ISNSR, + NAU8360_TDM_VSNSL, + NAU8360_TDM_VSNSR, + NAU8360_TDM_TJ, + NAU8360_TDM_VBAT, + NAU8360_TDM_TXN, +}; + +/* DAC Source Path*/ +enum { + NAU8360_DAC_SRC_HW1 = 0, + NAU8360_DAC_SRC_DSP, +}; + +/* PLL Source */ +enum { + NAU8360_PLL_MCLK, + NAU8360_PLL_BCLK, + NAU8360_PLL_INTERNAL, +}; + +struct nau8360_pll { + int src; + int input; + int output; + int msel; + int rsel; + int nsel; + int xsel; +}; + +struct nau8360 { + struct device *dev; + struct regmap *regmap; + struct mutex lock; + struct snd_soc_dapm_context *dapm; + int sys_clk; + int anc_enable; + int pbtl_enable; + int dac_cur_enable; + int kcs_setup_size; + struct nau8360_pll pll; + u32 tdm_tx_func_slot[NAU8360_TDM_TXN]; + u32 tdm_rx_func_slot[NAU8360_TDM_RXN]; + u8 i2c_read_reg[NAU8360_I2C_REG_LEN] ____cacheline_aligned; + u8 i2c_read_buf[NAU8360_I2C_RXBUF_LEN] ____cacheline_aligned; + u8 i2c_write_buf[NAU8360_I2C_TXBUF_LEN] ____cacheline_aligned; + struct work_struct load_fw_work; + const char *dsp_firmware[NAU8360_DSP_CORE_NUM]; + bool load_fw_done; +}; + +#endif /* __NAU8360_H__ */ diff --git a/sound/soc/codecs/nau8540.c b/sound/soc/codecs/nau8540.c index fefbd5722c00..09e7f31a904d 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -419,14 +419,24 @@ static int nau8540_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); const struct nau8540_osr_attr *osr; + int ret; osr = nau8540_get_osr(nau8540); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_ADC_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_ADC_MAX / osr->osr); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ + return 0; } static int nau8540_hw_params(struct snd_pcm_substream *substream, @@ -623,12 +633,23 @@ static int nau8540_dai_trigger(struct snd_pcm_substream *substream, return ret; } +static const u64 nau8540_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8540_dai_ops = { .startup = nau8540_dai_startup, .hw_params = nau8540_hw_params, .set_fmt = nau8540_set_fmt, .set_tdm_slot = nau8540_set_tdm_slot, .trigger = nau8540_dai_trigger, + .auto_selectable_formats = &nau8540_selectable_formats, + .num_auto_selectable_formats = 1, }; #define NAU8540_RATES SNDRV_PCM_RATE_8000_48000 @@ -897,9 +918,7 @@ static int __maybe_unused nau8540_resume(struct snd_soc_component *component) struct nau8540 *nau8540 = snd_soc_component_get_drvdata(component); regcache_cache_only(nau8540->regmap, false); - regcache_sync(nau8540->regmap); - - return 0; + return regcache_sync(nau8540->regmap); } static const struct snd_soc_component_driver nau8540_component_driver = { diff --git a/sound/soc/codecs/nau8810.c b/sound/soc/codecs/nau8810.c index a050387dd485..eb3e93380678 100644 --- a/sound/soc/codecs/nau8810.c +++ b/sound/soc/codecs/nau8810.c @@ -817,11 +817,23 @@ static int nau8810_set_bias_level(struct snd_soc_component *component, #define NAU8810_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 nau8810_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops nau8810_ops = { .hw_params = nau8810_pcm_hw_params, .set_fmt = nau8810_set_dai_fmt, .set_sysclk = nau8810_set_sysclk, .set_pll = nau8810_set_pll, + .auto_selectable_formats = &nau8810_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver nau8810_dai = { diff --git a/sound/soc/codecs/nau8821.c b/sound/soc/codecs/nau8821.c index c45c5b864ea4..7b88ee3159d1 100644 --- a/sound/soc/codecs/nau8821.c +++ b/sound/soc/codecs/nau8821.c @@ -829,14 +829,24 @@ static int nau8821_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8821 *nau8821 = snd_soc_component_get_drvdata(component); const struct nau8821_osr_attr *osr; + int ret; osr = nau8821_get_osr(nau8821, substream->stream); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ + return 0; } static int nau8821_hw_params(struct snd_pcm_substream *substream, @@ -981,11 +991,22 @@ static int nau8821_digital_mute(struct snd_soc_dai *dai, int mute, NAU8821_R31_MUTE_CTRL, NAU8821_DAC_SOFT_MUTE, val); } +static const u64 nau8821_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8821_dai_ops = { .startup = nau8821_dai_startup, .hw_params = nau8821_hw_params, .set_fmt = nau8821_set_dai_fmt, .mute_stream = nau8821_digital_mute, + .auto_selectable_formats = &nau8821_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/nau8822.c b/sound/soc/codecs/nau8822.c index b4ae269873c4..d91a0bed2025 100644 --- a/sound/soc/codecs/nau8822.c +++ b/sound/soc/codecs/nau8822.c @@ -1026,12 +1026,24 @@ static int nau8822_set_bias_level(struct snd_soc_component *component, #define NAU8822_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 nau8822_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops nau8822_dai_ops = { .hw_params = nau8822_hw_params, .mute_stream = nau8822_mute, .set_fmt = nau8822_set_dai_fmt, .set_sysclk = nau8822_set_dai_sysclk, .set_pll = nau8822_set_pll, + .auto_selectable_formats = &nau8822_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1059,10 +1071,14 @@ static int nau8822_suspend(struct snd_soc_component *component) { struct nau8822 *nau8822 = snd_soc_component_get_drvdata(component); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int ret; snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_OFF); - regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + ret = regulator_bulk_disable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + if (ret) + return ret; + regcache_cache_only(nau8822->regmap, true); regcache_mark_dirty(nau8822->regmap); return 0; @@ -1072,7 +1088,9 @@ static int nau8822_resume(struct snd_soc_component *component) { struct nau8822 *nau8822 = snd_soc_component_get_drvdata(component); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); - int ret = regulator_bulk_enable(NAU8822_NUM_SUPPLIES, nau8822->supplies); + int ret; + + ret = regulator_bulk_enable(NAU8822_NUM_SUPPLIES, nau8822->supplies); if (ret) { dev_err(component->dev, @@ -1082,7 +1100,10 @@ static int nau8822_resume(struct snd_soc_component *component) fsleep(100); - regcache_sync(nau8822->regmap); + regcache_cache_only(nau8822->regmap, false); + ret = regcache_sync(nau8822->regmap); + if (ret) + return ret; snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_STANDBY); diff --git a/sound/soc/codecs/nau8824.c b/sound/soc/codecs/nau8824.c index 426a488ff2a3..046cce9192b7 100644 --- a/sound/soc/codecs/nau8824.c +++ b/sound/soc/codecs/nau8824.c @@ -1061,14 +1061,24 @@ static int nau8824_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8824 *nau8824 = snd_soc_component_get_drvdata(component); const struct nau8824_osr_attr *osr; + int ret; osr = nau8824_get_osr(nau8824, substream->stream); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ + return 0; } static int nau8824_hw_params(struct snd_pcm_substream *substream, @@ -1580,11 +1590,22 @@ static const struct snd_soc_component_driver nau8824_component_driver = { .endianness = 1, }; +static const u64 nau8824_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8824_dai_ops = { .startup = nau8824_dai_startup, .hw_params = nau8824_hw_params, .set_fmt = nau8824_set_fmt, .set_tdm_slot = nau8824_set_tdm_slot, + .auto_selectable_formats = &nau8824_selectable_formats, + .num_auto_selectable_formats = 1, }; #define NAU8824_RATES SNDRV_PCM_RATE_8000_192000 diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index ae83b778bf93..af2a49c893d3 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -345,10 +345,10 @@ static u32 nau8825_intlog10_dec3(u32 value) /** * nau8825_xtalk_sidetone - computes cross talk suppression sidetone gain. * - * @sig_org: orignal signal level + * @sig_org: original signal level * @sig_cros: cross talk signal level * - * The orignal and cross talk signal vlues need to be characterized. + * The original and cross talk signal values need to be characterized. * Once these values have been characterized, this sidetone value * can be converted to decibel with the equation below. * sidetone = 20 * log (original signal level / crosstalk signal level) @@ -671,11 +671,11 @@ static void nau8825_xtalk_imm_stop(struct nau8825 *nau8825) * Thus, the measurement function has four states to complete whole sequence. * 1. Prepare state : Prepare the resource for detection and transfer to HPR * IMM stat to make JKR1(HPR) impedance measure. - * 2. HPR IMM state : Read out orignal signal level of JKR1(HPR) and transfer + * 2. HPR IMM state : Read out original signal level of JKR1(HPR) and transfer * to HPL IMM state to make JKTIP(HPL) impedance measure. * 3. HPL IMM state : Read out cross talk signal level of JKTIP(HPL) and * transfer to IMM state to determine suppression sidetone gain. - * 4. IMM state : Computes cross talk suppression sidetone gain with orignal + * 4. IMM state : Computes cross talk suppression sidetone gain with original * and cross talk signal level. Apply this gain and then restore codec * configuration. Then transfer to Done state for ending. */ @@ -724,8 +724,8 @@ static void nau8825_xtalk_measure(struct nau8825 *nau8825) nau8825->xtalk_state = NAU8825_XTALK_IMM; break; case NAU8825_XTALK_IMM: - /* In impedance measure state, the orignal and cross talk - * signal level vlues are ready. The side tone gain is deter- + /* In impedance measure state, the original and cross talk + * signal level values are ready. The side tone gain is deter- * mined with these signal level. After all, restore codec * configuration. */ @@ -771,7 +771,7 @@ static void nau8825_xtalk_work(struct work_struct *work) static void nau8825_xtalk_cancel(struct nau8825 *nau8825) { - /* If the crosstalk is eanbled and the process is on going, + /* If the crosstalk is enabled and the process is on going, * the driver forces to cancel the crosstalk task and * restores the configuration to original status. */ @@ -1261,14 +1261,24 @@ static int nau8825_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_component *component = dai->component; struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); const struct nau8825_osr_attr *osr; + int ret; osr = nau8825_get_osr(nau8825, substream->stream); if (!osr || !osr->osr) return -EINVAL; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - 0, CLK_DA_AD_MAX / osr->osr); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, CLK_DA_AD_MAX / osr->osr); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ + return 0; } static int nau8825_hw_params(struct snd_pcm_substream *substream, @@ -1303,7 +1313,7 @@ static int nau8825_hw_params(struct snd_pcm_substream *substream, NAU8825_CLK_ADC_SRC_MASK, osr->clk_src << NAU8825_CLK_ADC_SRC_SFT); - /* make BCLK and LRC divde configuration if the codec as master. */ + /* make BCLK and LRC divide configuration if the codec as master. */ regmap_read(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, &ctrl_val); if (ctrl_val & NAU8825_I2S_MS_MASTER) { /* get the bclk and fs ratio */ @@ -1507,11 +1517,22 @@ static int nau8825_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; } +static const u64 nau8825_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops nau8825_dai_ops = { .startup = nau8825_dai_startup, .hw_params = nau8825_hw_params, .set_fmt = nau8825_set_dai_fmt, .set_tdm_slot = nau8825_set_tdm_slot, + .auto_selectable_formats = &nau8825_selectable_formats, + .num_auto_selectable_formats = 1, }; #define NAU8825_RATES SNDRV_PCM_RATE_8000_192000 @@ -2886,7 +2907,7 @@ static int nau8825_i2c_probe(struct i2c_client *i2c) nau8825->dev = dev; nau8825->irq = i2c->irq; /* Initiate parameters, semaphore and work queue which are needed in - * cross talk suppression measurment function. + * cross talk suppression measurement function. */ nau8825->xtalk_state = NAU8825_XTALK_DONE; nau8825->xtalk_protect = false; diff --git a/sound/soc/codecs/ntp8835.c b/sound/soc/codecs/ntp8835.c index 5837b9379fee..39620b84e214 100644 --- a/sound/soc/codecs/ntp8835.c +++ b/sound/soc/codecs/ntp8835.c @@ -385,9 +385,16 @@ static int ntp8835_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; }; +static const u64 ntp8835_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ntp8835_dai_ops = { .hw_params = ntp8835_hw_params, .set_fmt = ntp8835_set_fmt, + .auto_selectable_formats = &ntp8835_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ntp8835_dai = { diff --git a/sound/soc/codecs/ntp8918.c b/sound/soc/codecs/ntp8918.c index a18d79aa80aa..eca34fafc4e6 100644 --- a/sound/soc/codecs/ntp8918.c +++ b/sound/soc/codecs/ntp8918.c @@ -306,10 +306,17 @@ static int ntp8918_digital_mute(struct snd_soc_dai *dai, int mute, int stream) mute_mask, mute ? mute_mask : 0); } +static const u64 ntp8918_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops ntp8918_dai_ops = { .hw_params = ntp8918_hw_params, .set_fmt = ntp8918_set_fmt, .mute_stream = ntp8918_digital_mute, + .auto_selectable_formats = &ntp8918_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ntp8918_dai = { diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c index 60fdbe5c4e05..6002da3a5666 100644 --- a/sound/soc/codecs/pcm1681.c +++ b/sound/soc/codecs/pcm1681.c @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -36,6 +39,12 @@ #define PCM1681_DEEMPH_CONTROL 0x0a /* De-emphasis control */ #define PCM1681_ZERO_DETECT_STATUS 0x0e /* Zero detect status reg */ +/* + * The DAC requires 65536 system clock cycles after the clock is supplied to + * complete its internal reset sequence before it is ready to be used. + */ +#define PCM1681_SCK_SETTLE_CYCLES 65536 + static const struct reg_default pcm1681_reg_defaults[] = { { 0x01, 0xff }, { 0x02, 0xff }, @@ -45,7 +54,7 @@ static const struct reg_default pcm1681_reg_defaults[] = { { 0x06, 0xff }, { 0x07, 0x00 }, { 0x08, 0x00 }, - { 0x09, 0x06 }, + { 0x09, 0x05 }, { 0x0A, 0x00 }, { 0x0B, 0xff }, { 0x0C, 0x0f }, @@ -67,6 +76,12 @@ static bool pcm1681_writeable_reg(struct device *dev, unsigned int reg) (reg != PCM1681_ZERO_DETECT_STATUS); } +static bool pcm1681_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Status is updated by the hardware and must never be cached */ + return reg == PCM1681_ZERO_DETECT_STATUS; +} + struct pcm1681_private { struct regmap *regmap; unsigned int format; @@ -74,8 +89,24 @@ struct pcm1681_private { unsigned int deemph; /* Current rate for deemphasis control */ unsigned int rate; + struct clk *sck; }; +/* Wait for the DAC to come out of its internal reset after sck is enabled */ +static void pcm1681_sck_settle(struct pcm1681_private *priv) +{ + unsigned long rate = clk_get_rate(priv->sck); + + /* + * sck is optional, so there may be no clock to wait on, and its rate + * is not always discoverable. Only wait when the cycle count can be + * converted into a delay. + */ + if (rate) + fsleep(DIV_ROUND_UP_ULL(PCM1681_SCK_SETTLE_CYCLES * + (u64)USEC_PER_SEC, rate)); +} + static const int pcm1681_deemph[] = { 44100, 48000, 32000 }; static int pcm1681_set_deemph(struct snd_soc_component *component) @@ -187,6 +218,9 @@ static int pcm1681_hw_params(struct snd_pcm_substream *substream, case SND_SOC_DAIFMT_LEFT_J: val = 0x05; break; + case SND_SOC_DAIFMT_DSP_A: + val = 0x06; + break; default: dev_err(component->dev, "Invalid DAI format\n"); return -EINVAL; @@ -202,7 +236,8 @@ static int pcm1681_hw_params(struct snd_pcm_substream *substream, static const u64 pcm1681_selectable_formats = SND_SOC_POSSIBLE_DAIFMT_I2S | SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | - SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; static const struct snd_soc_dai_ops pcm1681_dai_ops = { .set_fmt = pcm1681_set_dai_fmt, @@ -282,6 +317,8 @@ static const struct regmap_config pcm1681_regmap = { .num_reg_defaults = ARRAY_SIZE(pcm1681_reg_defaults), .writeable_reg = pcm1681_writeable_reg, .readable_reg = pcm1681_accessible_reg, + .volatile_reg = pcm1681_volatile_reg, + .cache_type = REGCACHE_MAPLE, }; static const struct snd_soc_component_driver soc_component_dev_pcm1681 = { @@ -291,7 +328,6 @@ static const struct snd_soc_component_driver soc_component_dev_pcm1681 = { .num_dapm_widgets = ARRAY_SIZE(pcm1681_dapm_widgets), .dapm_routes = pcm1681_dapm_routes, .num_dapm_routes = ARRAY_SIZE(pcm1681_dapm_routes), - .idle_bias_on = 1, .use_pmdown_time = 1, .endianness = 1, }; @@ -302,36 +338,141 @@ static const struct i2c_device_id pcm1681_i2c_id[] = { }; MODULE_DEVICE_TABLE(i2c, pcm1681_i2c_id); +static const char * const pcm1681_supply_names[] = { + "VCC1", "VCC2", "VDD", +}; + static int pcm1681_i2c_probe(struct i2c_client *client) { + struct device *dev = &client->dev; int ret; struct pcm1681_private *priv; - priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(pcm1681_supply_names), + pcm1681_supply_names); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable supplies\n"); + + priv->sck = devm_clk_get_optional(dev, "sck"); + if (IS_ERR(priv->sck)) + return dev_err_probe(dev, PTR_ERR(priv->sck), + "Failed to get sck\n"); + priv->regmap = devm_regmap_init_i2c(client, &pcm1681_regmap); if (IS_ERR(priv->regmap)) { ret = PTR_ERR(priv->regmap); - dev_err(&client->dev, "Failed to create regmap: %d\n", ret); + dev_err(dev, "Failed to create regmap: %d\n", ret); return ret; } + /* Must be set before runtime PM is enabled, the callbacks use it */ i2c_set_clientdata(client, priv); - return devm_snd_soc_register_component(&client->dev, - &soc_component_dev_pcm1681, - &pcm1681_dai, 1); + ret = clk_prepare_enable(priv->sck); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable sck\n"); + + pcm1681_sck_settle(priv); + + /* The clock is on, so hand the now-active device over to runtime PM */ + pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = devm_snd_soc_register_component(dev, + &soc_component_dev_pcm1681, + &pcm1681_dai, 1); + if (ret) { + dev_err(dev, "Failed to register component: %d\n", ret); + goto err_pm; + } + + return 0; + +err_pm: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + if (!pm_runtime_status_suspended(dev)) + clk_disable_unprepare(priv->sck); + pm_runtime_set_suspended(dev); + + return ret; } +static void pcm1681_i2c_remove(struct i2c_client *client) +{ + struct pcm1681_private *priv = i2c_get_clientdata(client); + struct device *dev = &client->dev; + + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + /* Runtime PM may already have gated the clock */ + if (!pm_runtime_status_suspended(dev)) + clk_disable_unprepare(priv->sck); + pm_runtime_set_suspended(dev); +} + +static int pcm1681_runtime_suspend(struct device *dev) +{ + struct pcm1681_private *priv = dev_get_drvdata(dev); + + /* + * Registers lose their contents once the clock is gated, so serve + * further access from the cache and replay it on resume. + */ + regcache_cache_only(priv->regmap, true); + regcache_mark_dirty(priv->regmap); + + clk_disable_unprepare(priv->sck); + + return 0; +} + +static int pcm1681_runtime_resume(struct device *dev) +{ + struct pcm1681_private *priv = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(priv->sck); + if (ret) { + dev_err(dev, "Failed to enable sck: %d\n", ret); + return ret; + } + + pcm1681_sck_settle(priv); + + regcache_cache_only(priv->regmap, false); + ret = regcache_sync(priv->regmap); + if (ret) { + dev_err(dev, "Failed to sync regcache: %d\n", ret); + regcache_cache_only(priv->regmap, true); + clk_disable_unprepare(priv->sck); + return ret; + } + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(pcm1681_pm_ops, pcm1681_runtime_suspend, + pcm1681_runtime_resume, NULL); + static struct i2c_driver pcm1681_i2c_driver = { .driver = { .name = "pcm1681", .of_match_table = of_match_ptr(pcm1681_dt_ids), + .pm = pm_ptr(&pcm1681_pm_ops), }, .id_table = pcm1681_i2c_id, .probe = pcm1681_i2c_probe, + .remove = pcm1681_i2c_remove, }; module_i2c_driver(pcm1681_i2c_driver); diff --git a/sound/soc/codecs/pcm5102a.c b/sound/soc/codecs/pcm5102a.c index 9bca53de2475..ed1e29e54ef8 100644 --- a/sound/soc/codecs/pcm5102a.c +++ b/sound/soc/codecs/pcm5102a.c @@ -6,12 +6,17 @@ * Copyright 2013 */ +#include #include #include #include #include +struct pcm5102a_priv { + struct gpio_desc *gpio_reset; +}; + static struct snd_soc_dai_driver pcm5102a_dai = { .name = "pcm5102a-hifi", .playback = { @@ -32,10 +37,32 @@ static const struct snd_soc_component_driver soc_component_dev_pcm5102a = { static int pcm5102a_probe(struct platform_device *pdev) { - return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm5102a, + struct device *dev = &pdev->dev; + struct pcm5102a_priv *pcm5102a; + + pcm5102a = devm_kzalloc(dev, sizeof(*pcm5102a), GFP_KERNEL); + if (!pcm5102a) + return -ENOMEM; + + pcm5102a->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + + if (IS_ERR(pcm5102a->gpio_reset)) + return dev_err_probe(dev, PTR_ERR(pcm5102a->gpio_reset), + "failed to acquire reset gpio\n"); + + platform_set_drvdata(pdev, pcm5102a); + + return devm_snd_soc_register_component(dev, &soc_component_dev_pcm5102a, &pcm5102a_dai, 1); } +static void pcm5102a_remove(struct platform_device *pdev) +{ + struct pcm5102a_priv *priv = platform_get_drvdata(pdev); + + gpiod_set_value_cansleep(priv->gpio_reset, 1); +} + static const struct of_device_id pcm5102a_of_match[] = { { .compatible = "ti,pcm5102a", }, { } @@ -44,6 +71,7 @@ MODULE_DEVICE_TABLE(of, pcm5102a_of_match); static struct platform_driver pcm5102a_codec_driver = { .probe = pcm5102a_probe, + .remove = pcm5102a_remove, .driver = { .name = "pcm5102a-codec", .of_match_table = pcm5102a_of_match, diff --git a/sound/soc/codecs/pcm6240.c b/sound/soc/codecs/pcm6240.c index db85ae2f8aed..db70a33d14b8 100644 --- a/sound/soc/codecs/pcm6240.c +++ b/sound/soc/codecs/pcm6240.c @@ -620,12 +620,12 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, /* Set to wide-range mode, before using vol ctrl. */ if (!val && vol_ctrl_type == PCMDEV_PCM1690_VOL_CTRL) { ucontrol->value.integer.value[0] = -25500; - return ret; + return 0; } /* Set to fine mode, before using fine vol ctrl. */ if (val && vol_ctrl_type == PCMDEV_PCM1690_FINE_VOL_CTRL) { ucontrol->value.integer.value[0] = -12750; - return ret; + return 0; } } @@ -641,7 +641,7 @@ static int pcmdev_get_volsw(struct snd_kcontrol *kcontrol, val = mc->invert ? max - val : val; ucontrol->value.integer.value[0] = val; - return ret; + return 0; } static int pcmdevice_get_volsw(struct snd_kcontrol *kcontrol, @@ -2019,8 +2019,6 @@ static const struct regmap_config pcmdevice_i2c_regmap = { static void pcmdevice_remove(struct pcmdevice_priv *pcm_dev) { - if (pcm_dev->irq) - free_irq(pcm_dev->irq, pcm_dev); mutex_destroy(&pcm_dev->codec_lock); } diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c index 60befe9d37f0..a3d41cd8150d 100644 --- a/sound/soc/codecs/rk3308_codec.c +++ b/sound/soc/codecs/rk3308_codec.c @@ -192,6 +192,90 @@ static int rk3308_codec_pop_sound_set(struct snd_soc_dapm_widget *w, return 0; } +static int rk3308_codec_dac_output_vcm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component); + unsigned int val; + + if (rk3308->codec_ver != ACODEC_VERSION_B) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Keep the DAC outputs at VCM while their reference is charged. */ + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK | + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK, + RK3308_DAC_L_SEL_DC_FROM_VCM | + RK3308_DAC_R_SEL_DC_FROM_VCM); + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_CURRENT_CHARGE_MSK, 1); + regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_VCM_LINEOUT_EN); + for (val = 1; val <= 0xf; val++) { + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_CURRENT_CHARGE_MSK, val); + usleep_range(200, 400); + } + msleep(20); + break; + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK | + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK, + RK3308_DAC_L_SEL_DC_FROM_INTERNAL | + RK3308_DAC_R_SEL_DC_FROM_INTERNAL); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, + RK3308_DAC_LINEOUT_POP_SOUND_L_MSK | + RK3308_DAC_LINEOUT_POP_SOUND_R_MSK, + RK3308_DAC_L_SEL_DC_FROM_VCM | + RK3308_DAC_R_SEL_DC_FROM_VCM); + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_CURRENT_CHARGE_MSK, 1); + regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON14, + RK3308_DAC_VCM_LINEOUT_EN); + break; + default: + break; + } + + return 0; +} + +static int rk3308_codec_lineout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component); + unsigned int mask = RK3308_DAC_LINEOUT_POP_SOUND_L_MSK << w->shift; + unsigned int val; + + if (rk3308->codec_ver != ACODEC_VERSION_B) + return 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = RK3308_DAC_L_SEL_LINEOUT_FROM_INTERNAL << w->shift; + break; + case SND_SOC_DAPM_POST_PMD: + val = RK3308_DAC_L_SEL_DC_FROM_INTERNAL << w->shift; + break; + default: + return 0; + } + + regmap_update_bits(rk3308->regmap, RK3308_DAC_ANA_CON15, mask, val); + usleep_range(20, 40); + + return 0; +} + static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = { SND_SOC_DAPM_INPUT("MIC1"), SND_SOC_DAPM_INPUT("MIC2"), @@ -322,6 +406,10 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = { SND_SOC_DAPM_OUT_DRV_E("HPOUT_POP_SOUND_R", SND_SOC_NOPM, 4, 0, NULL, 0, rk3308_codec_pop_sound_set, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("DAC_OUTPUT_VCM", SND_SOC_NOPM, 0, 0, + rk3308_codec_dac_output_vcm_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_OUT_DRV("L_HPOUT_EN", RK3308_DAC_ANA_CON03, 1, 0, NULL, 0), SND_SOC_DAPM_OUT_DRV("R_HPOUT_EN", RK3308_DAC_ANA_CON03, 5, 0, NULL, 0), SND_SOC_DAPM_OUT_DRV("L_HPOUT_WORK", RK3308_DAC_ANA_CON03, 2, 0, NULL, 0), @@ -329,8 +417,12 @@ static const struct snd_soc_dapm_widget rk3308_codec_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HPOUT_L"), SND_SOC_DAPM_OUTPUT("HPOUT_R"), - SND_SOC_DAPM_OUT_DRV("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0, NULL, 0), - SND_SOC_DAPM_OUT_DRV("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0, NULL, 0), + SND_SOC_DAPM_OUT_DRV_E("L_LINEOUT_EN", RK3308_DAC_ANA_CON04, 0, 0, + NULL, 0, rk3308_codec_lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("R_LINEOUT_EN", RK3308_DAC_ANA_CON04, 4, 0, + NULL, 0, rk3308_codec_lineout_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_OUTPUT("LINEOUT_L"), SND_SOC_DAPM_OUTPUT("LINEOUT_R"), }; @@ -466,6 +558,8 @@ static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = { { "HPOUT_R", NULL, "DAC_BUF_REF_R" }, { "L_HPOUT_EN", NULL, "DAC_L_HPMIX_SEL" }, { "R_HPOUT_EN", NULL, "DAC_R_HPMIX_SEL" }, + { "L_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" }, + { "R_HPOUT_EN", NULL, "DAC_OUTPUT_VCM" }, { "L_HPOUT_WORK", NULL, "L_HPOUT_EN" }, { "R_HPOUT_WORK", NULL, "R_HPOUT_EN" }, { "HPOUT_POP_SOUND_L", NULL, "L_HPOUT_WORK" }, @@ -475,6 +569,8 @@ static const struct snd_soc_dapm_route rk3308_codec_dapm_routes[] = { { "L_LINEOUT_EN", NULL, "DAC_L_HPMIX_SEL" }, { "R_LINEOUT_EN", NULL, "DAC_R_HPMIX_SEL" }, + { "L_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" }, + { "R_LINEOUT_EN", NULL, "DAC_OUTPUT_VCM" }, { "LINEOUT_L", NULL, "L_LINEOUT_EN" }, { "LINEOUT_R", NULL, "R_LINEOUT_EN" }, }; @@ -679,9 +775,20 @@ static int rk3308_codec_hw_params(struct snd_pcm_substream *substream, rk3308_codec_adc_dig_config(rk3308, params); } +static const u64 rk3308_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rk3308_codec_dai_ops = { .hw_params = rk3308_codec_hw_params, .set_fmt = rk3308_codec_set_dai_fmt, + .auto_selectable_formats = &rk3308_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rk3308_codec_dai_driver = { @@ -855,7 +962,7 @@ static int rk3308_codec_get_version(struct rk3308_codec_priv *rk3308) break; case 0x3308: rk3308->codec_ver = ACODEC_VERSION_B; - return dev_err_probe(rk3308->dev, -EINVAL, "Chip version B not supported\n"); + break; case 0x3308c: rk3308->codec_ver = ACODEC_VERSION_C; break; diff --git a/sound/soc/codecs/rk3328_codec.c b/sound/soc/codecs/rk3328_codec.c index 5871b5a81975..c2798ee94059 100644 --- a/sound/soc/codecs/rk3328_codec.c +++ b/sound/soc/codecs/rk3328_codec.c @@ -312,12 +312,21 @@ static void rk3328_pcm_shutdown(struct snd_pcm_substream *substream, rk3328_codec_close_playback(rk3328); } +static const u64 rk3328_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rk3328_dai_ops = { .hw_params = rk3328_hw_params, .set_fmt = rk3328_set_dai_fmt, .mute_stream = rk3328_mute_stream, .startup = rk3328_pcm_startup, .shutdown = rk3328_pcm_shutdown, + .auto_selectable_formats = &rk3328_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c index 15488a059283..dda38e6e56b6 100644 --- a/sound/soc/codecs/rt1011.c +++ b/sound/soc/codecs/rt1011.c @@ -2053,29 +2053,45 @@ static int rt1011_probe(struct snd_soc_component *component) schedule_work(&rt1011->cali_work); rt1011->i2s_ref = 0; - rt1011->bq_drc_params = devm_kcalloc(component->dev, - RT1011_ADVMODE_NUM, sizeof(struct rt1011_bq_drc_params *), - GFP_KERNEL); + rt1011->bq_drc_params = kcalloc(RT1011_ADVMODE_NUM, + sizeof(struct rt1011_bq_drc_params *), + GFP_KERNEL); if (!rt1011->bq_drc_params) return -ENOMEM; for (i = 0; i < RT1011_ADVMODE_NUM; i++) { - rt1011->bq_drc_params[i] = devm_kcalloc(component->dev, - RT1011_BQ_DRC_NUM, sizeof(struct rt1011_bq_drc_params), - GFP_KERNEL); + rt1011->bq_drc_params[i] = kcalloc(RT1011_BQ_DRC_NUM, + sizeof(struct rt1011_bq_drc_params), + GFP_KERNEL); if (!rt1011->bq_drc_params[i]) - return -ENOMEM; + goto err; } return 0; + +err: + while (i--) + kfree(rt1011->bq_drc_params[i]); + kfree(rt1011->bq_drc_params); + rt1011->bq_drc_params = NULL; + + return -ENOMEM; } static void rt1011_remove(struct snd_soc_component *component) { struct rt1011_priv *rt1011 = snd_soc_component_get_drvdata(component); + int i; cancel_work_sync(&rt1011->cali_work); rt1011_reset(rt1011->regmap); + + if (rt1011->bq_drc_params) { + for (i = 0; i < RT1011_ADVMODE_NUM; i++) + kfree(rt1011->bq_drc_params[i]); + kfree(rt1011->bq_drc_params); + rt1011->bq_drc_params = NULL; + } } #ifdef CONFIG_PM @@ -2094,9 +2110,7 @@ static int rt1011_resume(struct snd_soc_component *component) struct rt1011_priv *rt1011 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1011->regmap, false); - regcache_sync(rt1011->regmap); - - return 0; + return regcache_sync(rt1011->regmap); } #else #define rt1011_suspend NULL @@ -2133,10 +2147,20 @@ static int rt1011_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 rt1011_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1011_aif_dai_ops = { .hw_params = rt1011_hw_params, .set_fmt = rt1011_set_dai_fmt, .set_tdm_slot = rt1011_set_tdm_slot, + .auto_selectable_formats = &rt1011_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1011_dai[] = { diff --git a/sound/soc/codecs/rt1015.c b/sound/soc/codecs/rt1015.c index ff0c08ff610e..5d6be7cc67b4 100644 --- a/sound/soc/codecs/rt1015.c +++ b/sound/soc/codecs/rt1015.c @@ -488,35 +488,72 @@ static int rt1015_bypass_boost_get(struct snd_kcontrol *kcontrol, return 0; } -static void rt1015_calibrate(struct rt1015_priv *rt1015) +static int rt1015_calibrate(struct rt1015_priv *rt1015) { struct snd_soc_component *component = rt1015->component; struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct regmap *regmap = rt1015->regmap; + int ret, sync_ret; snd_soc_dapm_mutex_lock(dapm); regcache_cache_bypass(regmap, true); - regmap_write(regmap, RT1015_CLK_DET, 0x0000); - regmap_write(regmap, RT1015_PWR4, 0x00B2); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0009); + ret = regmap_write(regmap, RT1015_CLK_DET, 0x0000); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_PWR4, 0x00B2); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0009); + if (ret) + goto restore_cache; + msleep(100); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000A); + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000A); + if (ret) + goto restore_cache; + msleep(100); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000C); + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000C); + if (ret) + goto restore_cache; + msleep(100); - regmap_write(regmap, RT1015_CLSD_INTERNAL8, 0x2028); - regmap_write(regmap, RT1015_CLSD_INTERNAL9, 0x0140); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000D); + ret = regmap_write(regmap, RT1015_CLSD_INTERNAL8, 0x2028); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_CLSD_INTERNAL9, 0x0140); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x000D); + if (ret) + goto restore_cache; + msleep(300); - regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0008); - regmap_write(regmap, RT1015_SYS_RST1, 0x05F5); - regmap_write(regmap, RT1015_CLK_DET, 0x8000); + ret = regmap_write(regmap, RT1015_PWR_STATE_CTRL, 0x0008); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_SYS_RST1, 0x05F5); + if (ret) + goto restore_cache; + + ret = regmap_write(regmap, RT1015_CLK_DET, 0x8000); + +restore_cache: regcache_cache_bypass(regmap, false); regcache_mark_dirty(regmap); - regcache_sync(regmap); + sync_ret = regcache_sync(regmap); + if (!ret) + ret = sync_ret; snd_soc_dapm_mutex_unlock(dapm); + + return ret; } static int rt1015_bypass_boost_put(struct snd_kcontrol *kcontrol, @@ -525,6 +562,8 @@ static int rt1015_bypass_boost_put(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct rt1015_priv *rt1015 = snd_soc_component_get_drvdata(component); + int bypass_boost = rt1015->bypass_boost; + int ret; if (rt1015->dac_is_used) { dev_err(component->dev, "DAC is being used!\n"); @@ -534,13 +573,23 @@ static int rt1015_bypass_boost_put(struct snd_kcontrol *kcontrol, rt1015->bypass_boost = ucontrol->value.integer.value[0]; if (rt1015->bypass_boost == RT1015_Bypass_Boost && !rt1015->cali_done) { - rt1015_calibrate(rt1015); - rt1015->cali_done = 1; + ret = rt1015_calibrate(rt1015); + if (ret) + goto restore_boost; - regmap_write(rt1015->regmap, RT1015_MONO_DYNA_CTRL, 0x0010); + ret = regmap_write(rt1015->regmap, RT1015_MONO_DYNA_CTRL, + 0x0010); + if (ret) + goto restore_boost; + + rt1015->cali_done = 1; } return 0; + +restore_boost: + rt1015->bypass_boost = bypass_boost; + return ret; } static const char * const rt1015_dac_output_vol_select[] = { @@ -1016,10 +1065,20 @@ static void rt1015_remove(struct snd_soc_component *component) #define RT1015_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt1015_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1015_aif_dai_ops = { .hw_params = rt1015_hw_params, .set_fmt = rt1015_set_dai_fmt, .set_tdm_slot = rt1015_set_tdm_slot, + .auto_selectable_formats = &rt1015_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1015_dai[] = { diff --git a/sound/soc/codecs/rt1016.c b/sound/soc/codecs/rt1016.c index 11c8c45574a1..1e97d9ffa352 100644 --- a/sound/soc/codecs/rt1016.c +++ b/sound/soc/codecs/rt1016.c @@ -534,9 +534,19 @@ static void rt1016_remove(struct snd_soc_component *component) #define RT1016_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt1016_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1016_aif_dai_ops = { .hw_params = rt1016_hw_params, .set_fmt = rt1016_set_dai_fmt, + .auto_selectable_formats = &rt1016_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1016_dai[] = { @@ -570,9 +580,7 @@ static int rt1016_resume(struct snd_soc_component *component) struct rt1016_priv *rt1016 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1016->regmap, false); - regcache_sync(rt1016->regmap); - - return 0; + return regcache_sync(rt1016->regmap); } #else #define rt1016_suspend NULL diff --git a/sound/soc/codecs/rt1019.c b/sound/soc/codecs/rt1019.c index 370521149ac8..3998abccd93a 100644 --- a/sound/soc/codecs/rt1019.c +++ b/sound/soc/codecs/rt1019.c @@ -492,12 +492,22 @@ static int rt1019_probe(struct snd_soc_component *component) #define RT1019_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt1019_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1019_aif_dai_ops = { .hw_params = rt1019_hw_params, .set_fmt = rt1019_set_dai_fmt, .set_sysclk = rt1019_set_dai_sysclk, .set_pll = rt1019_set_dai_pll, .set_tdm_slot = rt1019_set_tdm_slot, + .auto_selectable_formats = &rt1019_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1019_dai[] = { diff --git a/sound/soc/codecs/rt1305.c b/sound/soc/codecs/rt1305.c index 2d5fad76cf52..0130ff9f050d 100644 --- a/sound/soc/codecs/rt1305.c +++ b/sound/soc/codecs/rt1305.c @@ -896,9 +896,7 @@ static int rt1305_resume(struct snd_soc_component *component) struct rt1305_priv *rt1305 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1305->regmap, false); - regcache_sync(rt1305->regmap); - - return 0; + return regcache_sync(rt1305->regmap); } #else #define rt1305_suspend NULL @@ -910,9 +908,19 @@ static int rt1305_resume(struct snd_soc_component *component) SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 rt1305_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1305_aif_dai_ops = { .hw_params = rt1305_hw_params, .set_fmt = rt1305_set_dai_fmt, + .auto_selectable_formats = &rt1305_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1305_dai[] = { diff --git a/sound/soc/codecs/rt1308.c b/sound/soc/codecs/rt1308.c index 630946cd7cdf..3d4aadb91206 100644 --- a/sound/soc/codecs/rt1308.c +++ b/sound/soc/codecs/rt1308.c @@ -715,9 +715,7 @@ static int rt1308_resume(struct snd_soc_component *component) struct rt1308_priv *rt1308 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1308->regmap, false); - regcache_sync(rt1308->regmap); - - return 0; + return regcache_sync(rt1308->regmap); } #else #define rt1308_suspend NULL @@ -729,9 +727,19 @@ static int rt1308_resume(struct snd_soc_component *component) SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 rt1308_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1308_aif_dai_ops = { .hw_params = rt1308_hw_params, .set_fmt = rt1308_set_dai_fmt, + .auto_selectable_formats = &rt1308_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1308_dai[] = { diff --git a/sound/soc/codecs/rt1318.c b/sound/soc/codecs/rt1318.c index 55607f1b9a1e..5c80957b0f01 100644 --- a/sound/soc/codecs/rt1318.c +++ b/sound/soc/codecs/rt1318.c @@ -1075,8 +1075,7 @@ static int rt1318_resume(struct snd_soc_component *component) struct rt1318_priv *rt1318 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt1318->regmap, false); - regcache_sync(rt1318->regmap); - return 0; + return regcache_sync(rt1318->regmap); } #else #define rt1318_suspend NULL @@ -1087,12 +1086,22 @@ static int rt1318_resume(struct snd_soc_component *component) #define RT1318_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt1318_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt1318_aif_dai_ops = { .hw_params = rt1318_hw_params, .set_fmt = rt1318_set_dai_fmt, .set_sysclk = rt1318_set_dai_sysclk, .set_pll = rt1318_set_dai_pll, .set_tdm_slot = rt1318_set_tdm_slot, + .auto_selectable_formats = &rt1318_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt1318_dai[] = { diff --git a/sound/soc/codecs/rt1320-sdw.c b/sound/soc/codecs/rt1320-sdw.c index 72af413306fa..fa89db096651 100644 --- a/sound/soc/codecs/rt1320-sdw.c +++ b/sound/soc/codecs/rt1320-sdw.c @@ -890,8 +890,9 @@ static bool rt1320_readable_register(struct device *dev, unsigned int reg) case 0xf01e: case 0xf717 ... 0xf719: case 0xf720 ... 0xf723: - case 0x1000cd91 ... 0x1000cd96: - case RT1321_PATCH_MAIN_VER ... RT1321_PATCH_BETA_VER: + case 0x10000000 ... 0x10008fff: + case 0x1000c000 ... 0x1000cfff: + case 0x1000d800 ... 0x1000dfff: case 0x1000f008: case 0x1000f021: case 0x20003000 ... 0x2000300f: @@ -996,7 +997,8 @@ static bool rt1320_volatile_register(struct device *dev, unsigned int reg) case 0xf717 ... 0xf719: case 0xf720 ... 0xf723: case 0x10000000 ... 0x10008fff: - case 0x1000c000 ... 0x1000dfff: + case 0x1000c000 ... 0x1000cfff: + case 0x1000d800 ... 0x1000dfff: case 0x1000f008: case 0x1000f021: case 0x2000300e ... 0x2000300f: @@ -2084,7 +2086,7 @@ static int rt1320_rae_load(struct rt1320_sdw_priv *rt1320) regmap_update_bits(rt1320->regmap, 0x20005818, 0x80, 0x80); /* RAE run */ regmap_update_bits(rt1320->regmap, 0x2000301c, 0x01, 0x01); - /* Phase sync eanble */ + /* Phase sync enable */ regmap_update_bits(rt1320->regmap, 0xc047, 0x80, 0x80); break; } @@ -2470,7 +2472,7 @@ static int rt1320_io_init(struct device *dev, struct sdw_slave *slave) dev_dbg(dev, "%s amp func_status=0x%x\n", __func__, amp_func_status); /* initialization write */ - if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION) || !rt1320->first_hw_init) { + if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION)) { switch (rt1320->dev_id) { case RT1320_DEV_ID: if (rt1320->version_id < RT1320_VC) diff --git a/sound/soc/codecs/rt274.c b/sound/soc/codecs/rt274.c index 3c47f1ae8db9..5c67b1d210b2 100644 --- a/sound/soc/codecs/rt274.c +++ b/sound/soc/codecs/rt274.c @@ -1029,6 +1029,12 @@ static int rt274_resume(struct snd_soc_component *component) #define RT274_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt274_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rt274_aif_dai_ops = { .hw_params = rt274_hw_params, .set_fmt = rt274_set_dai_fmt, @@ -1036,6 +1042,8 @@ static const struct snd_soc_dai_ops rt274_aif_dai_ops = { .set_pll = rt274_set_dai_pll, .set_bclk_ratio = rt274_set_bclk_ratio, .set_tdm_slot = rt274_set_tdm_slot, + .auto_selectable_formats = &rt274_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt274_dai[] = { diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index bcd9f5abcda8..c60e81ba87f3 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -995,11 +995,19 @@ static int rt286_resume(struct snd_soc_component *component) #define RT286_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt286_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rt286_aif_dai_ops = { .hw_params = rt286_hw_params, .set_fmt = rt286_set_dai_fmt, .set_sysclk = rt286_set_dai_sysclk, .set_bclk_ratio = rt286_set_bclk_ratio, + .auto_selectable_formats = &rt286_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt286_dai[] = { diff --git a/sound/soc/codecs/rt298.c b/sound/soc/codecs/rt298.c index 09aed08b5b79..72407fec0f34 100644 --- a/sound/soc/codecs/rt298.c +++ b/sound/soc/codecs/rt298.c @@ -1056,11 +1056,19 @@ static int rt298_resume(struct snd_soc_component *component) #define RT298_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt298_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rt298_aif_dai_ops = { .hw_params = rt298_hw_params, .set_fmt = rt298_set_dai_fmt, .set_sysclk = rt298_set_dai_sysclk, .set_bclk_ratio = rt298_set_bclk_ratio, + .auto_selectable_formats = &rt298_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt298_dai[] = { diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c index 91290bfe8daa..bcc2ff28b125 100644 --- a/sound/soc/codecs/rt5514-spi.c +++ b/sound/soc/codecs/rt5514-spi.c @@ -43,6 +43,7 @@ struct rt5514_dsp { struct snd_pcm_substream *substream; unsigned int buf_base, buf_limit, buf_rp; size_t buf_size, get_size, dma_offset; + int irq; }; static const struct snd_pcm_hardware rt5514_spi_pcm_hardware = { @@ -257,8 +258,7 @@ static int rt5514_spi_pcm_probe(struct snd_soc_component *component) struct rt5514_dsp *rt5514_dsp; int ret; - rt5514_dsp = devm_kzalloc(component->dev, sizeof(*rt5514_dsp), - GFP_KERNEL); + rt5514_dsp = kzalloc_obj(*rt5514_dsp); if (!rt5514_dsp) return -ENOMEM; @@ -268,21 +268,41 @@ static int rt5514_spi_pcm_probe(struct snd_soc_component *component) snd_soc_component_set_drvdata(component, rt5514_dsp); if (rt5514_spi->irq) { - ret = devm_request_threaded_irq(&rt5514_spi->dev, - rt5514_spi->irq, NULL, rt5514_spi_irq, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rt5514-spi", - rt5514_dsp); - if (ret) + ret = request_threaded_irq(rt5514_spi->irq, NULL, + rt5514_spi_irq, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "rt5514-spi", rt5514_dsp); + if (ret) { dev_err(&rt5514_spi->dev, "%s Failed to request IRQ: %d\n", __func__, ret); - else + } else { + rt5514_dsp->irq = rt5514_spi->irq; device_init_wakeup(rt5514_dsp->dev, true); + } } return 0; } +static void rt5514_spi_pcm_remove(struct snd_soc_component *component) +{ + struct rt5514_dsp *rt5514_dsp = + snd_soc_component_get_drvdata(component); + + snd_soc_component_set_drvdata(component, NULL); + + if (rt5514_dsp->irq) { + free_irq(rt5514_dsp->irq, rt5514_dsp); + device_init_wakeup(rt5514_dsp->dev, false); + } + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + + mutex_destroy(&rt5514_dsp->dma_lock); + kfree(rt5514_dsp); +} + static int rt5514_spi_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { @@ -294,6 +314,7 @@ static int rt5514_spi_pcm_new(struct snd_soc_component *component, static const struct snd_soc_component_driver rt5514_spi_component = { .name = DRV_NAME, .probe = rt5514_spi_pcm_probe, + .remove = rt5514_spi_pcm_remove, .open = rt5514_spi_pcm_open, .hw_params = rt5514_spi_hw_params, .hw_free = rt5514_spi_hw_free, diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 58aecd4c83a6..a33fbc9fb047 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -1099,18 +1099,6 @@ static int rt5514_set_bias_level(struct snd_soc_component *component, static int rt5514_probe(struct snd_soc_component *component) { struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); - struct platform_device *pdev = to_platform_device(component->dev); - - rt5514->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5514->mclk)) - return PTR_ERR(rt5514->mclk); - - if (rt5514->pdata.dsp_calib_clk_name) { - rt5514->dsp_calib_clk = devm_clk_get(&pdev->dev, - rt5514->pdata.dsp_calib_clk_name); - if (PTR_ERR(rt5514->dsp_calib_clk) == -EPROBE_DEFER) - return -EPROBE_DEFER; - } rt5514->component = component; rt5514->pll3_cal_value = 0x0078b000; @@ -1142,12 +1130,24 @@ static int rt5514_i2c_write(void *context, unsigned int reg, unsigned int val) #define RT5514_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5514_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rt5514_aif_dai_ops = { .hw_params = rt5514_hw_params, .set_fmt = rt5514_set_dai_fmt, .set_sysclk = rt5514_set_dai_sysclk, .set_pll = rt5514_set_dai_pll, .set_tdm_slot = rt5514_set_tdm_slot, + .auto_selectable_formats = &rt5514_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5514_dai[] = { @@ -1273,6 +1273,17 @@ static int rt5514_i2c_probe(struct i2c_client *i2c) else rt5514_parse_dp(rt5514, &i2c->dev); + rt5514->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5514->mclk)) + return PTR_ERR(rt5514->mclk); + + if (rt5514->pdata.dsp_calib_clk_name) { + rt5514->dsp_calib_clk = devm_clk_get(&i2c->dev, + rt5514->pdata.dsp_calib_clk_name); + if (PTR_ERR(rt5514->dsp_calib_clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } + rt5514->i2c_regmap = devm_regmap_init_i2c(i2c, &rt5514_i2c_regmap); if (IS_ERR(rt5514->i2c_regmap)) { ret = PTR_ERR(rt5514->i2c_regmap); @@ -1320,7 +1331,7 @@ static int rt5514_i2c_probe(struct i2c_client *i2c) rt5514_dai, ARRAY_SIZE(rt5514_dai)); } -static const struct dev_pm_ops rt5514_i2_pm_ops = { +static const struct dev_pm_ops rt5514_i2c_pm_ops = { SYSTEM_SLEEP_PM_OPS(NULL, rt5514_i2c_resume) }; @@ -1329,7 +1340,7 @@ static struct i2c_driver rt5514_i2c_driver = { .name = "rt5514", .acpi_match_table = ACPI_PTR(rt5514_acpi_match), .of_match_table = of_match_ptr(rt5514_of_match), - .pm = pm_ptr(&rt5514_i2_pm_ops), + .pm = pm_ptr(&rt5514_i2c_pm_ops), }, .probe = rt5514_i2c_probe, .id_table = rt5514_i2c_id, diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c index 46ee412dc81d..af6299baea7c 100644 --- a/sound/soc/codecs/rt5616.c +++ b/sound/soc/codecs/rt5616.c @@ -1222,11 +1222,6 @@ static int rt5616_probe(struct snd_soc_component *component) { struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); - /* Check if MCLK provided */ - rt5616->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5616->mclk)) - return PTR_ERR(rt5616->mclk); - rt5616->component = component; return 0; @@ -1248,8 +1243,7 @@ static int rt5616_resume(struct snd_soc_component *component) struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt5616->regmap, false); - regcache_sync(rt5616->regmap); - return 0; + return regcache_sync(rt5616->regmap); } #else #define rt5616_suspend NULL @@ -1260,11 +1254,21 @@ static int rt5616_resume(struct snd_soc_component *component) #define RT5616_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5616_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5616_aif_dai_ops = { .hw_params = rt5616_hw_params, .set_fmt = rt5616_set_dai_fmt, .set_sysclk = rt5616_set_dai_sysclk, .set_pll = rt5616_set_dai_pll, + .auto_selectable_formats = &rt5616_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5616_dai[] = { @@ -1347,6 +1351,11 @@ static int rt5616_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, rt5616); + /* Check if MCLK provided */ + rt5616->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5616->mclk)) + return PTR_ERR(rt5616->mclk); + rt5616->regmap = devm_regmap_init_i2c(i2c, &rt5616_regmap); if (IS_ERR(rt5616->regmap)) { ret = PTR_ERR(rt5616->regmap); diff --git a/sound/soc/codecs/rt5631.c b/sound/soc/codecs/rt5631.c index ed1233973eb8..f97fd24e4229 100644 --- a/sound/soc/codecs/rt5631.c +++ b/sound/soc/codecs/rt5631.c @@ -1626,11 +1626,21 @@ static int rt5631_probe(struct snd_soc_component *component) SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S8) +static const u64 rt5631_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5631_ops = { .hw_params = rt5631_hifi_pcm_params, .set_fmt = rt5631_hifi_codec_set_dai_fmt, .set_sysclk = rt5631_hifi_codec_set_dai_sysclk, .set_pll = rt5631_codec_set_dai_pll, + .auto_selectable_formats = &rt5631_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5631_dai[] = { diff --git a/sound/soc/codecs/rt5631.h b/sound/soc/codecs/rt5631.h index 8a6b99a48c7c..00734207ac9f 100644 --- a/sound/soc/codecs/rt5631.h +++ b/sound/soc/codecs/rt5631.h @@ -415,7 +415,7 @@ #define RT5631_ADDA_FILTER_CLK_SEL_256FS (0 << 7) /* 256FS */ #define RT5631_ADDA_FILTER_CLK_SEL_384FS (1 << 7) /* 384FS */ -/* Power managment addition 1 (0x3A) */ +/* Power management addition 1 (0x3A) */ #define RT5631_PWR_MAIN_I2S_EN (0x1 << 15) #define RT5631_PWR_MAIN_I2S_BIT 15 #define RT5631_PWR_CLASS_D (0x1 << 12) @@ -435,7 +435,7 @@ #define RT5631_PWR_DAC_R_TO_MIXER (0x1 << 5) #define RT5631_PWR_DAC_R_TO_MIXER_BIT 5 -/* Power managment addition 2 (0x3B) */ +/* Power management addition 2 (0x3B) */ #define RT5631_PWR_OUTMIXER_L (0x1 << 15) #define RT5631_PWR_OUTMIXER_L_BIT 15 #define RT5631_PWR_OUTMIXER_R (0x1 << 14) @@ -461,7 +461,7 @@ #define RT5631_PWR_PLL2 (0x1 << 0) #define RT5631_PWR_PLL2_BIT 0 -/* Power managment addition 3(0x3C) */ +/* Power management addition 3(0x3C) */ #define RT5631_PWR_VREF (0x1 << 15) #define RT5631_PWR_VREF_BIT 15 #define RT5631_PWR_FAST_VREF_CTRL (0x1 << 14) @@ -489,7 +489,7 @@ #define RT5631_PWR_HP_AMP_DRIVING (0x1 << 0) #define RT5631_PWR_HP_AMP_DRIVING_BIT 0 -/* Power managment addition 4(0x3E) */ +/* Power management addition 4(0x3E) */ #define RT5631_PWR_SPK_L_VOL (0x1 << 15) #define RT5631_PWR_SPK_L_VOL_BIT 15 #define RT5631_PWR_SPK_R_VOL (0x1 << 14) diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index 74fd05176eff..d6068bb5cb4c 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -1216,7 +1216,7 @@ static const struct snd_soc_dapm_widget rt5640_dapm_widgets[] = { 0, rt5640_spk_l_mix, ARRAY_SIZE(rt5640_spk_l_mix)), SND_SOC_DAPM_MIXER("SPK MIXR", RT5640_PWR_MIXER, RT5640_PWR_SM_R_BIT, 0, rt5640_spk_r_mix, ARRAY_SIZE(rt5640_spk_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_PGA("SPKVOL L", RT5640_PWR_VOL, RT5640_PWR_SV_L_BIT, 0, NULL, 0), SND_SOC_DAPM_PGA("SPKVOL R", RT5640_PWR_VOL, @@ -2667,11 +2667,6 @@ static int rt5640_probe(struct snd_soc_component *component) bool dmic_en = false; u32 val; - /* Check if MCLK provided */ - rt5640->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5640->mclk)) - return PTR_ERR(rt5640->mclk); - rt5640->component = component; snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_OFF); @@ -2877,11 +2872,21 @@ static int rt5640_resume(struct snd_soc_component *component) #define RT5640_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5640_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5640_aif_dai_ops = { .hw_params = rt5640_hw_params, .set_fmt = rt5640_set_dai_fmt, .set_sysclk = rt5640_set_dai_sysclk, .set_pll = rt5640_set_dai_pll, + .auto_selectable_formats = &rt5640_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5640_dai[] = { @@ -3002,6 +3007,11 @@ static int rt5640_i2c_probe(struct i2c_client *i2c) return -ENOMEM; i2c_set_clientdata(i2c, rt5640); + /* Check if MCLK provided */ + rt5640->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5640->mclk)) + return PTR_ERR(rt5640->mclk); + rt5640->ldo1_en = devm_gpiod_get_optional(&i2c->dev, "realtek,ldo1-en", GPIOD_OUT_HIGH); diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 7313ea71c958..5e0fc69576c0 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -2209,7 +2210,7 @@ static const struct snd_soc_dapm_widget rt5645_dapm_widgets[] = { 0, rt5645_out_l_mix, ARRAY_SIZE(rt5645_out_l_mix)), SND_SOC_DAPM_MIXER("OUT MIXR", RT5645_PWR_MIXER, RT5645_PWR_OM_R_BIT, 0, rt5645_out_r_mix, ARRAY_SIZE(rt5645_out_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_SWITCH("SPKVOL L", RT5645_PWR_VOL, RT5645_PWR_SV_L_BIT, 0, &spk_l_vol_control), SND_SOC_DAPM_SWITCH("SPKVOL R", RT5645_PWR_VOL, RT5645_PWR_SV_R_BIT, 0, @@ -3487,10 +3488,8 @@ static int rt5645_probe(struct snd_soc_component *component) if (rt5645->pdata.long_name) component->card->long_name = rt5645->pdata.long_name; - rt5645->eq_param = devm_kcalloc(component->dev, - RT5645_HWEQ_NUM, sizeof(struct rt5645_eq_param_s), - GFP_KERNEL); - + rt5645->eq_param = kcalloc(RT5645_HWEQ_NUM, + sizeof(struct rt5645_eq_param_s), GFP_KERNEL); if (!rt5645->eq_param) return -ENOMEM; @@ -3503,7 +3502,12 @@ static int rt5645_probe(struct snd_soc_component *component) static void rt5645_remove(struct snd_soc_component *component) { + struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component); + rt5645_reset(component); + + kfree(rt5645->eq_param); + rt5645->eq_param = NULL; } #ifdef CONFIG_PM @@ -3541,12 +3545,22 @@ static int rt5645_resume(struct snd_soc_component *component) #define RT5645_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5645_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5645_aif_dai_ops = { .hw_params = rt5645_hw_params, .set_fmt = rt5645_set_dai_fmt, .set_sysclk = rt5645_set_dai_sysclk, .set_tdm_slot = rt5645_set_tdm_slot, .set_pll = rt5645_set_dai_pll, + .auto_selectable_formats = &rt5645_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5645_dai[] = { @@ -4032,26 +4046,16 @@ static int rt5645_i2c_probe(struct i2c_client *i2c) rt5645->gpiod_hp_det = devm_gpiod_get_optional(&i2c->dev, "hp-detect", GPIOD_IN); - if (IS_ERR(rt5645->gpiod_hp_det)) { - dev_info(&i2c->dev, "failed to initialize gpiod\n"); - ret = PTR_ERR(rt5645->gpiod_hp_det); - /* - * Continue if optional gpiod is missing, bail for all other - * errors, including -EPROBE_DEFER - */ - if (ret != -ENOENT) - return ret; - } + if (IS_ERR(rt5645->gpiod_hp_det)) + return dev_err_probe(&i2c->dev, PTR_ERR(rt5645->gpiod_hp_det), + "failed to get hp-detect gpio\n"); rt5645->gpiod_cbj_sleeve = devm_gpiod_get_optional(&i2c->dev, "cbj-sleeve", GPIOD_OUT_LOW); - if (IS_ERR(rt5645->gpiod_cbj_sleeve)) { - ret = PTR_ERR(rt5645->gpiod_cbj_sleeve); - dev_info(&i2c->dev, "failed to initialize gpiod, ret=%d\n", ret); - if (ret != -ENOENT) - return ret; - } + if (IS_ERR(rt5645->gpiod_cbj_sleeve)) + return dev_err_probe(&i2c->dev, PTR_ERR(rt5645->gpiod_cbj_sleeve), + "failed to get cbj-sleeve gpio\n"); for (i = 0; i < ARRAY_SIZE(rt5645->supplies); i++) rt5645->supplies[i].supply = rt5645_supply_names[i]; @@ -4292,10 +4296,10 @@ static void rt5645_i2c_remove(struct i2c_client *i2c) free_irq(i2c->irq, rt5645); /* - * Since the rt5645_btn_check_callback() can queue jack_detect_work, - * the timer need to be delted first + * The timer and jack_detect_work can rearm each other. Shut down the + * timer before cancelling the work. */ - timer_delete_sync(&rt5645->btn_check_timer); + timer_shutdown_sync(&rt5645->btn_check_timer); cancel_delayed_work_sync(&rt5645->jack_detect_work); cancel_delayed_work_sync(&rt5645->rcclock_work); diff --git a/sound/soc/codecs/rt5651.c b/sound/soc/codecs/rt5651.c index 5c729135f71b..b911757376f5 100644 --- a/sound/soc/codecs/rt5651.c +++ b/sound/soc/codecs/rt5651.c @@ -1024,7 +1024,7 @@ static const struct snd_soc_dapm_widget rt5651_dapm_widgets[] = { 0, rt5651_out_l_mix, ARRAY_SIZE(rt5651_out_l_mix)), SND_SOC_DAPM_MIXER("OUT MIXR", RT5651_PWR_MIXER, RT5651_PWR_OM_R_BIT, 0, rt5651_out_r_mix, ARRAY_SIZE(rt5651_out_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_SWITCH("OUTVOL L", RT5651_PWR_VOL, RT5651_PWR_OV_L_BIT, 0, &outvol_l_control), SND_SOC_DAPM_SWITCH("OUTVOL R", RT5651_PWR_VOL, @@ -2102,11 +2102,21 @@ static int rt5651_resume(struct snd_soc_component *component) #define RT5651_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5651_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5651_aif_dai_ops = { .hw_params = rt5651_hw_params, .set_fmt = rt5651_set_dai_fmt, .set_sysclk = rt5651_set_dai_sysclk, .set_pll = rt5651_set_dai_pll, + .auto_selectable_formats = &rt5651_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5651_dai[] = { diff --git a/sound/soc/codecs/rt5659.c b/sound/soc/codecs/rt5659.c index 3097ee6d4e89..af8051fa69f8 100644 --- a/sound/soc/codecs/rt5659.c +++ b/sound/soc/codecs/rt5659.c @@ -3703,9 +3703,7 @@ static int rt5659_resume(struct snd_soc_component *component) struct rt5659_priv *rt5659 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt5659->regmap, false); - regcache_sync(rt5659->regmap); - - return 0; + return regcache_sync(rt5659->regmap); } #else #define rt5659_suspend NULL @@ -3716,11 +3714,21 @@ static int rt5659_resume(struct snd_soc_component *component) #define RT5659_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5659_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5659_aif_dai_ops = { .hw_params = rt5659_hw_params, .set_fmt = rt5659_set_dai_fmt, .set_tdm_slot = rt5659_set_tdm_slot, .set_bclk_ratio = rt5659_set_bclk_ratio, + .auto_selectable_formats = &rt5659_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5659_dai[] = { @@ -4113,10 +4121,14 @@ static int rt5659_i2c_probe(struct i2c_client *i2c) rt5659->gpiod_ldo1_en = devm_gpiod_get_optional(&i2c->dev, "ldo1-en", GPIOD_OUT_HIGH); if (IS_ERR(rt5659->gpiod_ldo1_en)) - dev_warn(&i2c->dev, "Request ldo1-en GPIO failed\n"); + return dev_err_probe(&i2c->dev, PTR_ERR(rt5659->gpiod_ldo1_en), + "failed to get ldo1-en GPIO\n"); rt5659->gpiod_reset = devm_gpiod_get_optional(&i2c->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(rt5659->gpiod_reset)) + return dev_err_probe(&i2c->dev, PTR_ERR(rt5659->gpiod_reset), + "failed to get reset GPIO\n"); /* Sleep for 300 ms minimum */ msleep(300); diff --git a/sound/soc/codecs/rt5660.c b/sound/soc/codecs/rt5660.c index edf2e3e346e4..2a3ecac12c07 100644 --- a/sound/soc/codecs/rt5660.c +++ b/sound/soc/codecs/rt5660.c @@ -1148,9 +1148,7 @@ static int rt5660_resume(struct snd_soc_component *component) msleep(350); regcache_cache_only(rt5660->regmap, false); - regcache_sync(rt5660->regmap); - - return 0; + return regcache_sync(rt5660->regmap); } #else #define rt5660_suspend NULL @@ -1161,11 +1159,21 @@ static int rt5660_resume(struct snd_soc_component *component) #define RT5660_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5660_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5660_aif_dai_ops = { .hw_params = rt5660_hw_params, .set_fmt = rt5660_set_dai_fmt, .set_sysclk = rt5660_set_dai_sysclk, .set_pll = rt5660_set_dai_pll, + .auto_selectable_formats = &rt5660_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5660_dai[] = { diff --git a/sound/soc/codecs/rt5663.c b/sound/soc/codecs/rt5663.c index 262d3bba1f3d..ec46e6180cc0 100644 --- a/sound/soc/codecs/rt5663.c +++ b/sound/soc/codecs/rt5663.c @@ -3222,6 +3222,14 @@ static int rt5663_resume(struct snd_soc_component *component) #define RT5663_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5663_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5663_aif_dai_ops = { .hw_params = rt5663_hw_params, .set_fmt = rt5663_set_dai_fmt, @@ -3229,6 +3237,8 @@ static const struct snd_soc_dai_ops rt5663_aif_dai_ops = { .set_pll = rt5663_set_dai_pll, .set_tdm_slot = rt5663_set_tdm_slot, .set_bclk_ratio = rt5663_set_bclk_ratio, + .auto_selectable_formats = &rt5663_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5663_dai[] = { diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c index 1d987472ba42..62b21092e352 100644 --- a/sound/soc/codecs/rt5665.c +++ b/sound/soc/codecs/rt5665.c @@ -4388,9 +4388,7 @@ static int rt5665_resume(struct snd_soc_component *component) struct rt5665_priv *rt5665 = snd_soc_component_get_drvdata(component); regcache_cache_only(rt5665->regmap, false); - regcache_sync(rt5665->regmap); - - return 0; + return regcache_sync(rt5665->regmap); } #else #define rt5665_suspend NULL @@ -4401,11 +4399,21 @@ static int rt5665_resume(struct snd_soc_component *component) #define RT5665_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5665_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5665_aif_dai_ops = { .hw_params = rt5665_hw_params, .set_fmt = rt5665_set_dai_fmt, .set_tdm_slot = rt5665_set_tdm_slot, .set_bclk_ratio = rt5665_set_bclk_ratio, + .auto_selectable_formats = &rt5665_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5665_dai[] = { diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c index 4154bad27610..cd6245d6b91a 100644 --- a/sound/soc/codecs/rt5668.c +++ b/sound/soc/codecs/rt5668.c @@ -2254,16 +2254,38 @@ static int rt5668_resume(struct snd_soc_component *component) #define RT5668_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5668_selectable_formats_aif1 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + +static const u64 rt5668_selectable_formats_aif2 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5668_aif1_dai_ops = { .hw_params = rt5668_hw_params, .set_fmt = rt5668_set_dai_fmt, .set_tdm_slot = rt5668_set_tdm_slot, + .auto_selectable_formats = &rt5668_selectable_formats_aif1, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops rt5668_aif2_dai_ops = { .hw_params = rt5668_hw_params, .set_fmt = rt5668_set_dai_fmt, .set_bclk_ratio = rt5668_set_bclk_ratio, + .auto_selectable_formats = &rt5668_selectable_formats_aif2, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5668_dai[] = { diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 1d120b5dc2be..62d4e7cea5af 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -1901,7 +1901,7 @@ static const struct snd_soc_dapm_widget rt5670_dapm_widgets[] = { 0, rt5670_out_l_mix, ARRAY_SIZE(rt5670_out_l_mix)), SND_SOC_DAPM_MIXER("OUT MIXR", RT5670_PWR_MIXER, RT5670_PWR_OM_R_BIT, 0, rt5670_out_r_mix, ARRAY_SIZE(rt5670_out_r_mix)), - /* Ouput Volume */ + /* Output Volume */ SND_SOC_DAPM_MIXER("HPOVOL MIXL", RT5670_PWR_VOL, RT5670_PWR_HV_L_BIT, 0, rt5670_hpvoll_mix, ARRAY_SIZE(rt5670_hpvoll_mix)), @@ -2791,12 +2791,22 @@ static int rt5670_resume(struct snd_soc_component *component) #define RT5670_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5670_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5670_aif_dai_ops = { .hw_params = rt5670_hw_params, .set_fmt = rt5670_set_dai_fmt, .set_tdm_slot = rt5670_set_tdm_slot, .set_pll = rt5670_set_dai_pll, .set_bclk_ratio = rt5670_set_bclk_ratio, + .auto_selectable_formats = &rt5670_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt5670_dai[] = { diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c index ebc527115ea5..0b2166082437 100644 --- a/sound/soc/codecs/rt5677-spi.c +++ b/sound/soc/codecs/rt5677-spi.c @@ -58,6 +58,8 @@ static struct spi_device *g_spi; static DEFINE_MUTEX(spi_mutex); +/* Serializes access to the DSP context against the exported hotword callback */ +static DEFINE_MUTEX(dsp_lock); struct rt5677_dsp { struct device *dev; @@ -380,8 +382,7 @@ static int rt5677_spi_pcm_probe(struct snd_soc_component *component) { struct rt5677_dsp *rt5677_dsp; - rt5677_dsp = devm_kzalloc(component->dev, sizeof(*rt5677_dsp), - GFP_KERNEL); + rt5677_dsp = kzalloc_obj(*rt5677_dsp); if (!rt5677_dsp) return -ENOMEM; rt5677_dsp->dev = &g_spi->dev; @@ -392,9 +393,23 @@ static int rt5677_spi_pcm_probe(struct snd_soc_component *component) return 0; } +static void rt5677_spi_pcm_remove(struct snd_soc_component *component) +{ + struct rt5677_dsp *rt5677_dsp = + snd_soc_component_get_drvdata(component); + + scoped_guard(mutex, &dsp_lock) + snd_soc_component_set_drvdata(component, NULL); + + cancel_delayed_work_sync(&rt5677_dsp->copy_work); + mutex_destroy(&rt5677_dsp->dma_lock); + kfree(rt5677_dsp); +} + static const struct snd_soc_component_driver rt5677_spi_dai_component = { .name = DRV_NAME, .probe = rt5677_spi_pcm_probe, + .remove = rt5677_spi_pcm_remove, .open = rt5677_spi_pcm_open, .close = rt5677_spi_pcm_close, .hw_params = rt5677_spi_hw_params, @@ -579,6 +594,8 @@ void rt5677_spi_hotword_detected(void) if (!g_spi) return; + guard(mutex)(&dsp_lock); + rt5677_dsp = dev_get_drvdata(&g_spi->dev); if (!rt5677_dsp) { dev_err(&g_spi->dev, "Can't get rt5677_dsp\n"); diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 4757017cc83d..7b80922236e5 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5031,12 +5031,22 @@ static int rt5677_write(void *context, unsigned int reg, unsigned int val) #define RT5677_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) +static const u64 rt5677_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5677_aif_dai_ops = { .hw_params = rt5677_hw_params, .set_fmt = rt5677_set_dai_fmt, .set_sysclk = rt5677_set_dai_sysclk, .set_pll = rt5677_set_dai_pll, .set_tdm_slot = rt5677_set_tdm_slot, + .auto_selectable_formats = &rt5677_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops rt5677_dsp_dai_ops = { diff --git a/sound/soc/codecs/rt5682-sdw.c b/sound/soc/codecs/rt5682-sdw.c index 23c7d2a9dbee..5f2897c58ad7 100644 --- a/sound/soc/codecs/rt5682-sdw.c +++ b/sound/soc/codecs/rt5682-sdw.c @@ -704,8 +704,7 @@ static void rt5682_sdw_remove(struct sdw_slave *slave) { struct rt5682_priv *rt5682 = dev_get_drvdata(&slave->dev); - if (rt5682->hw_init) - cancel_delayed_work_sync(&rt5682->jack_detect_work); + cancel_delayed_work_sync(&rt5682->jack_detect_work); pm_runtime_disable(&slave->dev); } diff --git a/sound/soc/codecs/rt5682.c b/sound/soc/codecs/rt5682.c index 6f7e68c20d13..1aceb08b280d 100644 --- a/sound/soc/codecs/rt5682.c +++ b/sound/soc/codecs/rt5682.c @@ -3043,11 +3043,31 @@ static int rt5682_resume(struct snd_soc_component *component) #define rt5682_resume NULL #endif +static const u64 rt5682_selectable_formats_aif1 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + +static const u64 rt5682_selectable_formats_aif2 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + const struct snd_soc_dai_ops rt5682_aif1_dai_ops = { .hw_params = rt5682_hw_params, .set_fmt = rt5682_set_dai_fmt, .set_tdm_slot = rt5682_set_tdm_slot, .set_bclk_ratio = rt5682_set_bclk1_ratio, + .auto_selectable_formats = &rt5682_selectable_formats_aif1, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(rt5682_aif1_dai_ops); @@ -3055,6 +3075,8 @@ const struct snd_soc_dai_ops rt5682_aif2_dai_ops = { .hw_params = rt5682_hw_params, .set_fmt = rt5682_set_dai_fmt, .set_bclk_ratio = rt5682_set_bclk2_ratio, + .auto_selectable_formats = &rt5682_selectable_formats_aif2, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(rt5682_aif2_dai_ops); diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c index 34997cfb465b..49ed7793d3a2 100644 --- a/sound/soc/codecs/rt5682s.c +++ b/sound/soc/codecs/rt5682s.c @@ -2846,11 +2846,6 @@ static int rt5682s_dai_probe_clks(struct snd_soc_component *component) struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component); int ret; - /* Check if MCLK provided */ - rt5682s->mclk = devm_clk_get_optional(component->dev, "mclk"); - if (IS_ERR(rt5682s->mclk)) - return PTR_ERR(rt5682s->mclk); - /* Register CCF DAI clock control */ ret = rt5682s_register_dai_clks(component); if (ret) @@ -2926,17 +2921,39 @@ static int rt5682s_resume(struct snd_soc_component *component) #define rt5682s_resume NULL #endif +static const u64 rt5682s_selectable_formats_aif1 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + +static const u64 rt5682s_selectable_formats_aif2 = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rt5682s_aif1_dai_ops = { .hw_params = rt5682s_hw_params, .set_fmt = rt5682s_set_dai_fmt, .set_tdm_slot = rt5682s_set_tdm_slot, .set_bclk_ratio = rt5682s_set_bclk1_ratio, + .auto_selectable_formats = &rt5682s_selectable_formats_aif1, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops rt5682s_aif2_dai_ops = { .hw_params = rt5682s_hw_params, .set_fmt = rt5682s_set_dai_fmt, .set_bclk_ratio = rt5682s_set_bclk2_ratio, + .auto_selectable_formats = &rt5682s_selectable_formats_aif2, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_component_driver rt5682s_soc_component_dev = { @@ -3130,6 +3147,13 @@ static int rt5682s_i2c_probe(struct i2c_client *i2c) return ret; } +#ifdef CONFIG_COMMON_CLK + /* Check if MCLK provided */ + rt5682s->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5682s->mclk)) + return PTR_ERR(rt5682s->mclk); +#endif + for (i = 0; i < ARRAY_SIZE(rt5682s->supplies); i++) rt5682s->supplies[i].supply = rt5682s_supply_names[i]; @@ -3294,8 +3318,8 @@ static void rt5682s_i2c_shutdown(struct i2c_client *client) struct rt5682s_priv *rt5682s = i2c_get_clientdata(client); disable_irq(client->irq); - cancel_delayed_work_sync(&rt5682s->jack_detect_work); - cancel_delayed_work_sync(&rt5682s->jd_check_work); + disable_delayed_work_sync(&rt5682s->jack_detect_work); + disable_delayed_work_sync(&rt5682s->jd_check_work); rt5682s_reset(rt5682s); } diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c index e292b28b029e..f74b193c720d 100644 --- a/sound/soc/codecs/rt711-sdca-sdw.c +++ b/sound/soc/codecs/rt711-sdca-sdw.c @@ -369,10 +369,8 @@ static void rt711_sdca_sdw_remove(struct sdw_slave *slave) { struct rt711_sdca_priv *rt711 = dev_get_drvdata(&slave->dev); - if (rt711->hw_init) { - cancel_delayed_work_sync(&rt711->jack_detect_work); - cancel_delayed_work_sync(&rt711->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt711->jack_detect_work); + cancel_delayed_work_sync(&rt711->jack_btn_check_work); pm_runtime_disable(&slave->dev); diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c index 4ad2b1da1954..798d181d5c90 100644 --- a/sound/soc/codecs/rt711-sdw.c +++ b/sound/soc/codecs/rt711-sdw.c @@ -461,11 +461,9 @@ static void rt711_sdw_remove(struct sdw_slave *slave) { struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev); - if (rt711->hw_init) { - cancel_delayed_work_sync(&rt711->jack_detect_work); - cancel_delayed_work_sync(&rt711->jack_btn_check_work); - cancel_work_sync(&rt711->calibration_work); - } + cancel_delayed_work_sync(&rt711->jack_detect_work); + cancel_delayed_work_sync(&rt711->jack_btn_check_work); + cancel_work_sync(&rt711->calibration_work); pm_runtime_disable(&slave->dev); diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c index a9f3aa4e143a..882390890738 100644 --- a/sound/soc/codecs/rt712-sdca-dmic.c +++ b/sound/soc/codecs/rt712-sdca-dmic.c @@ -246,7 +246,7 @@ static int rt712_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); - struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); + struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component); struct rt712_sdca_dmic_kctrl_priv *p = (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value; unsigned int regvalue, ctl, i; @@ -277,7 +277,7 @@ static int rt712_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct rt712_sdca_dmic_kctrl_priv *p = (struct rt712_sdca_dmic_kctrl_priv *)kcontrol->private_value; - struct rt712_sdca_priv *rt712 = snd_soc_component_get_drvdata(component); + struct rt712_sdca_dmic_priv *rt712 = snd_soc_component_get_drvdata(component); unsigned int gain_val[4]; unsigned int i, adc_vol_flag = 0, changed = 0; unsigned int regvalue[4]; diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c index edba0367d9ce..0f0447d1a05b 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -386,15 +386,14 @@ static void rt712_sdca_sdw_remove(struct sdw_slave *slave) { struct rt712_sdca_priv *rt712 = dev_get_drvdata(&slave->dev); - if (rt712->hw_init) { - cancel_delayed_work_sync(&rt712->jack_detect_work); - cancel_delayed_work_sync(&rt712->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt712->jack_detect_work); + cancel_delayed_work_sync(&rt712->jack_btn_check_work); pm_runtime_disable(&slave->dev); mutex_destroy(&rt712->calibrate_mutex); mutex_destroy(&rt712->disable_irq_lock); + mutex_destroy(&rt712->gc_lock); } static const struct sdw_device_id rt712_sdca_id[] = { diff --git a/sound/soc/codecs/rt712-sdca-sdw.h b/sound/soc/codecs/rt712-sdca-sdw.h index 7ad25cc27f62..162d62234b68 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.h +++ b/sound/soc/codecs/rt712-sdca-sdw.h @@ -46,6 +46,9 @@ static const struct reg_default rt712_sdca_mbq_defaults[] = { { 0x5b00029, 0x3fff }, { 0x5b0002a, 0xf000 }, { 0x6100000, 0x04e4 }, + { 0x6100009, 0x0000 }, + { 0x610000a, 0x0000 }, + { 0x610000b, 0x0020 }, { 0x610000e, 0x0007 }, { 0x6100045, 0x0860 }, { 0x6100046, 0x0029 }, diff --git a/sound/soc/codecs/rt712-sdca.c b/sound/soc/codecs/rt712-sdca.c index 38052cb19790..20961e5760c6 100644 --- a/sound/soc/codecs/rt712-sdca.c +++ b/sound/soc/codecs/rt712-sdca.c @@ -113,6 +113,138 @@ static void rt712_sdca_clk_patch2(struct rt712_sdca_priv *rt712) rt712_sdca_index_write(rt712, RT712_VENDOR_REG, 0x65, 0x0000); } +static int rt712_sdca_gpio_request(struct gpio_chip *chip, unsigned int offset) +{ + struct rt712_sdca_priv *rt712 = gpiochip_get_data(chip); + struct device *dev = &rt712->slave->dev; + unsigned int gpio_pin = offset + 3; + int ret; + + dev_dbg(&rt712->slave->dev, "%s: gpio_pin=%d\n", __func__, gpio_pin); + + mutex_lock(&rt712->gc_lock); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) + goto io_error; + + /* + * Only support GPIO3 and GPIO4 for now + */ + switch (gpio_pin) { + case 3: + break; + case 4: + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_LEGACY_CONFIG_CTL0, 0x000c, 0x0004); + if (ret < 0) + goto io_error; + break; + default: + ret = -EINVAL; + goto io_error; + } + + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_EN_CTL, (1 << gpio_pin), (1 << gpio_pin)); + if (ret < 0) + goto io_error; + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_DIRECTION_CTL, (1 << gpio_pin), (1 << gpio_pin)); + +io_error: + mutex_unlock(&rt712->gc_lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int rt712_sdca_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct rt712_sdca_priv *rt712 = gpiochip_get_data(chip); + struct device *dev = &rt712->slave->dev; + unsigned int gpio_pin = offset + 3; + int ret; + + dev_dbg(&rt712->slave->dev, "%s: gpio_pin=%d, value=%d\n", __func__, gpio_pin, value); + + mutex_lock(&rt712->gc_lock); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) + goto io_error; + + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_SET_CTL, (1 << gpio_pin), (!!value << gpio_pin)); + +io_error: + mutex_unlock(&rt712->gc_lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int rt712_sdca_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct rt712_sdca_priv *rt712 = gpiochip_get_data(chip); + struct device *dev = &rt712->slave->dev; + unsigned int gpio_pin = offset + 3; + int ret; + + dev_dbg(&rt712->slave->dev, "%s: gpio_pin=%d, value=%d\n", __func__, gpio_pin, value); + + mutex_lock(&rt712->gc_lock); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) + goto io_error; + + switch (gpio_pin) { + case 3: + case 4: + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_DIRECTION_CTL, (1 << gpio_pin), (1 << gpio_pin)); + if (ret < 0) + goto io_error; + ret = rt712_sdca_index_update_bits(rt712, RT712_VENDOR_HDA_CTL, + RT712_HDA_GPIO_SET_CTL, (1 << gpio_pin), (!!value << gpio_pin)); + break; + default: + ret = -EINVAL; + goto io_error; + } + +io_error: + mutex_unlock(&rt712->gc_lock); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + return ret; +} + +static const struct gpio_chip rt712_sdca_template_chip = { + .label = "rt712-sdca", + .owner = THIS_MODULE, + .request = rt712_sdca_gpio_request, + .direction_output = rt712_sdca_gpio_direction_out, + .set = rt712_sdca_gpio_set, + .ngpio = 2, + .can_sleep = true, + .base = -1, +}; + +static void rt712_sdca_gpio_init(struct rt712_sdca_priv *rt712) +{ + int ret; + + mutex_init(&rt712->gc_lock); + rt712->gpio_chip = rt712_sdca_template_chip; + rt712->gpio_chip.parent = &rt712->slave->dev; + rt712->gpio_chip.fwnode = dev_fwnode(&rt712->slave->dev); + + ret = devm_gpiochip_add_data(&rt712->slave->dev, &rt712->gpio_chip, rt712); + if (ret != 0) + dev_err(&rt712->slave->dev, "Failed to add GPIOs: %d\n", ret); +} + static int rt712_sdca_calibration(struct rt712_sdca_priv *rt712) { unsigned int val, loop_rc = 0, loop_dc = 0; @@ -1724,6 +1856,10 @@ int rt712_sdca_init(struct device *dev, struct regmap *regmap, INIT_DELAYED_WORK(&rt712->jack_detect_work, rt712_sdca_jack_detect_handler); INIT_DELAYED_WORK(&rt712->jack_btn_check_work, rt712_sdca_btn_check_handler); + /* initialize GPIOs */ + if (IS_ENABLED(CONFIG_GPIOLIB)) + rt712_sdca_gpio_init(rt712); + /* * Mark hw_init to false * HW init will be performed when device reports present @@ -1952,7 +2088,8 @@ int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave) pm_runtime_get_noresume(&slave->dev); - rt712_sdca_reset(rt712); + if (!rt712->first_hw_init) + rt712_sdca_reset(rt712); rt712_sdca_index_read(rt712, RT712_VENDOR_REG, RT712_JD_PRODUCT_NUM, &val); rt712->hw_id = (val & 0xf000) >> 12; @@ -1981,7 +2118,7 @@ int rt712_sdca_io_init(struct device *dev, struct sdw_slave *slave) * if set_jack callback occurred early than io_init, * we set up the jack detection function now */ - if (rt712->hs_jack) + if (rt712->hs_jack && (!rt712->first_hw_init)) rt712_sdca_jack_init(rt712); rt712_sdca_index_write(rt712, RT712_VENDOR_REG, RT712_SW_CONFIG1, 0x0001); diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h index 6229fe341bb5..7e6ee21c8a17 100644 --- a/sound/soc/codecs/rt712-sdca.h +++ b/sound/soc/codecs/rt712-sdca.h @@ -14,6 +14,7 @@ #include #include #include +#include struct rt712_sdca_priv { struct regmap *regmap; @@ -45,6 +46,8 @@ struct rt712_sdca_priv { bool fu05_dapm_mute; bool fu05_mixer_l_mute; bool fu05_mixer_r_mute; + struct gpio_chip gpio_chip; + struct mutex gc_lock; }; struct rt712_dmic_kctrl_priv { @@ -114,6 +117,9 @@ struct rt712_dmic_kctrl_priv { #define RT712_HDA_LEGACY_MUX_CTL0 0x00 #define RT712_HDA_LEGACY_CONFIG_CTL0 0x06 #define RT712_HDA_LEGACY_RESET_CTL 0x08 +#define RT712_HDA_GPIO_EN_CTL 0x09 +#define RT712_HDA_GPIO_DIRECTION_CTL 0x0a +#define RT712_HDA_GPIO_SET_CTL 0x0b #define RT712_HDA_LEGACY_GPIO_WAKE_EN_CTL 0x0e #define RT712_DMIC_ENT_FLOAT_CTL 0x10 #define RT712_DMIC_GAIN_ENT_FLOAT_CTL0 0x11 diff --git a/sound/soc/codecs/rt721-sdca-sdw.c b/sound/soc/codecs/rt721-sdca-sdw.c index 910583162d3e..fc34a0537d5f 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.c +++ b/sound/soc/codecs/rt721-sdca-sdw.c @@ -25,16 +25,22 @@ static bool rt721_sdca_readable_register(struct device *dev, unsigned int reg) case 0x2f50: case 0x2f51: case 0x2f58 ... 0x2f5d: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_XUV, RT721_SDCA_CTL_XUV, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_GE49, RT721_SDCA_CTL_SELECTED_MODE, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_GE49, RT721_SDCA_CTL_DETECTED_MODE, 0): + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): case RT721_BUF_ADDR_HID1 ... RT721_BUF_ADDR_HID2: + case SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): return true; default: return false; @@ -46,14 +52,20 @@ static bool rt721_sdca_volatile_register(struct device *dev, unsigned int reg) switch (reg) { case 0x2f01: case 0x2f51: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_GE49, RT721_SDCA_CTL_DETECTED_MODE, 0): case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_XUV, RT721_SDCA_CTL_XUV, 0): + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): case SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... SDW_SDCA_CTL(FUNC_NUM_HID, RT721_SDCA_ENT_HID01, RT721_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): case RT721_BUF_ADDR_HID1 ... RT721_BUF_ADDR_HID2: + case SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0): return true; default: return false; @@ -64,9 +76,16 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re { switch (reg) { case 0x0900004 ... 0x0900009: + case 0x0910000: + case 0x0910001: + case 0x0910002: + case 0x0910003: + case 0x0910202: case 0x0a00005: case 0x0c00005: case 0x0d00014: + case 0x0f00004: + case 0x0f00005: case 0x0310100: case 0x2000000 ... 0x2000003: case 0x2000013: @@ -75,17 +94,25 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re case 0x200003c: case 0x2000046: case 0x5810000: + case 0x5810033: case 0x5810036: case 0x5810037: case 0x5810038: case 0x5810039: - case 0x5b10018: - case 0x5b10019: + case 0x581003a: + case 0x581003b: + case 0x5810100: + case 0x5b10000 ... 0x5b10026: + case 0x5b10100 ... 0x5b10103: + case 0x5f00010: + case 0x5f00043: case 0x5f00045: case 0x5f00048: case 0x6100000: + case 0x6100002: case 0x6100005: case 0x6100006: + case 0x6100007: case 0x610000d: case 0x6100010: case 0x6100011: @@ -139,6 +166,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x0a00005: case 0x0c00005: case 0x0d00014: + case 0x0f00004: case 0x2000000: case 0x200000d: case 0x2000019: @@ -155,8 +183,11 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x5810037: case 0x5810038: case 0x5810039: - case 0x5b10018: - case 0x5b10019: + case 0x581003a: + case 0x581003b: + case 0x5810100: + case 0x5b10000 ... 0x5b10016: + case 0x5b10018 ... 0x5b10026: case 0x6100006: return true; default: @@ -304,6 +335,11 @@ static int rt721_sdca_interrupt_callback(struct sdw_slave *slave, int count = 0, retry = 3; unsigned int sdca_cascade, scp_sdca_stat1, scp_sdca_stat2 = 0; + if (rt721->imp_sensing_ongoing && rt721->wf_id == RT721_S) + dev_info(&slave->dev, + "%s: IRQ arrived DURING impedance sensing\n", + __func__); + if (cancel_delayed_work_sync(&rt721->jack_detect_work)) { dev_warn(&slave->dev, "%s the pending delayed_work was cancelled", __func__); /* avoid the HID owner doesn't change to device */ @@ -381,9 +417,15 @@ static int rt721_sdca_interrupt_callback(struct sdw_slave *slave, ret = sdw_read_no_pm(rt721->slave, SDW_SCP_SDCA_INT1); ret = sdw_read_no_pm(rt721->slave, SDW_SCP_SDCA_INT2); - if (status->sdca_cascade && !rt721->disable_irq) + if (status->sdca_cascade && !rt721->disable_irq) { + unsigned int delay_ms = 280; + + if (rt721->imp_sensing_ongoing && rt721->wf_id == RT721_S) + delay_ms = 800; + mod_delayed_work(system_power_efficient_wq, - &rt721->jack_detect_work, msecs_to_jiffies(280)); + &rt721->jack_detect_work, msecs_to_jiffies(delay_ms)); + } mutex_unlock(&rt721->disable_irq_lock); @@ -422,10 +464,8 @@ static void rt721_sdca_sdw_remove(struct sdw_slave *slave) { struct rt721_sdca_priv *rt721 = dev_get_drvdata(&slave->dev); - if (rt721->hw_init) { - cancel_delayed_work_sync(&rt721->jack_detect_work); - cancel_delayed_work_sync(&rt721->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt721->jack_detect_work); + cancel_delayed_work_sync(&rt721->jack_btn_check_work); if (rt721->first_hw_init) pm_runtime_disable(&slave->dev); @@ -436,6 +476,7 @@ static void rt721_sdca_sdw_remove(struct sdw_slave *slave) static const struct sdw_device_id rt721_sdca_id[] = { SDW_SLAVE_ENTRY_EXT(0x025d, 0x721, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x718, 0x3, 0x1, 0), {}, }; MODULE_DEVICE_TABLE(sdw, rt721_sdca_id); diff --git a/sound/soc/codecs/rt721-sdca-sdw.h b/sound/soc/codecs/rt721-sdca-sdw.h index 13d6227f3392..fe04a3726334 100644 --- a/sound/soc/codecs/rt721-sdca-sdw.h +++ b/sound/soc/codecs/rt721-sdca-sdw.h @@ -80,18 +80,25 @@ static const struct reg_default rt721_sdca_reg_defaults[] = { }; static const struct reg_default rt721_sdca_mbq_defaults[] = { + { 0x0900006, 0xe000 }, { 0x0900007, 0xc004 }, + { 0x0910000, 0x00a1 }, + { 0x0910001, 0x1256 }, + { 0x0910002, 0x0000 }, + { 0x0910003, 0x0000 }, { 0x2000001, 0x0000 }, { 0x2000002, 0x0000 }, { 0x2000003, 0x0000 }, { 0x2000013, 0x8001 }, { 0x200003c, 0x0000 }, { 0x2000046, 0x3400 }, + { 0x5b10017, 0x1b07 }, { 0x5f00044, 0x6040 }, { 0x5f00045, 0x3333 }, { 0x5f00048, 0x0000 }, { 0x6100005, 0x0005 }, { 0x6100006, 0x0000 }, + { 0x6100007, 0x0007 }, { 0x610000d, 0x0051 }, { 0x6100010, 0x0180 }, { 0x6100011, 0x0000 }, diff --git a/sound/soc/codecs/rt721-sdca.c b/sound/soc/codecs/rt721-sdca.c index 738644018fb9..9f567caecb70 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -27,11 +27,86 @@ #include "rt721-sdca.h" #include "rt-sdw-common.h" +static void rt721_uaj_power_on(struct rt721_sdca_priv *rt721) +{ + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL10, 0xfcb5, 0xfcb5); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON12, 0xc000, 0x4000); +} + +static void rt721_impedance_sensing(struct rt721_sdca_priv *rt721) +{ + unsigned int read_imp; + int i, loop_cnt = 25; + unsigned long cal_T; + bool spk = false; + + rt721->imp_sensing_ongoing = true; + + rt721_uaj_power_on(rt721); + + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_UAJ_CTL, 0x0048, 0x0000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0x0003, 0x0003); + + cal_T = jiffies + msecs_to_jiffies(200); + + for (i = 0; i < loop_cnt; i++) { + regmap_read(rt721->mbq_regmap, RT721_IMP_SEN_CTRL13, &read_imp); + read_imp = (read_imp >> 12) & 0xf; + dev_dbg(&rt721->slave->dev, "%s, loop:%d, read_imp=0x%x\n", + __func__, i, read_imp); + + if (read_imp == 0x6 || read_imp == 0x7 || + read_imp == 0x8 || read_imp == 0x9) { + spk = true; + break; + } + + if (read_imp != 0 || i == loop_cnt - 1 || + time_after(jiffies, cal_T)) + break; + + usleep_range(20000, 21000); + } + + if (spk) { + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8c00, 0x8c00); + usleep_range(50000, 51000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8000, 0x0000); + } else { + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8c00, 0x8000); + usleep_range(50000, 51000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x8000, 0x0000); + } + + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_UAJ_CTL, 0x0048, 0x0048); + rt721->int_cnt = 1; +} + +static void rt721_sdca_jack_reset_state(struct rt721_sdca_priv *rt721) +{ + rt721->jack_type = 0; + rt721->imp_sensing_done = false; + rt721->last_real_JD = 0; + rt721->int_cnt = 0; +} + static void rt721_sdca_jack_detect_handler(struct work_struct *work) { struct rt721_sdca_priv *rt721 = container_of(work, struct rt721_sdca_priv, jack_detect_work.work); + unsigned int sta_mode = 0; int btn_type = 0; + int retry, retry_loop = 40; + int real_JD; if (!rt721->hs_jack) return; @@ -39,12 +114,132 @@ static void rt721_sdca_jack_detect_handler(struct work_struct *work) if (!rt721->component->card || !rt721->component->card->instantiated) return; - /* SDW_SCP_SDCA_INT_SDCA_6 is used for jack detection */ - if (rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) { - rt721->jack_type = rt_sdca_headset_detect(rt721->regmap, - RT721_SDCA_ENT_GE49); - if (rt721->jack_type < 0) + if (rt721->wf_id == RT721_S) { + if (rt721->imp_sensing_ongoing) { + dev_dbg(&rt721->slave->dev, + "SKIP, imp_sensing_ongoing\n"); return; + } + + rt_sdca_index_read(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_STA1, &real_JD); + real_JD &= 0x1; + if (real_JD == 0x0) + rt721_sdca_jack_reset_state(rt721); + + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + !(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) && + real_JD == 0x1 && rt721->int_cnt == 0 && + !rt721->imp_sensing_done && rt721->jack_type != 0) { + unsigned int cur_mode = 0; + int cur_type = -1; + + for (retry = 0; retry < retry_loop; retry++) { + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, + RT721_SDCA_ENT_GE49, + RT721_SDCA_CTL_DETECTED_MODE, 0), + &cur_mode); + if (cur_mode) + break; + usleep_range(20000, 21000); + } + + switch (cur_mode) { + case 0x03: + cur_type = SND_JACK_HEADPHONE; + break; + case 0x05: + cur_type = SND_JACK_HEADSET; + break; + default: + cur_type = -1; + break; + } + + dev_info(&rt721->slave->dev, + "%s: resume check, cur_mode=0x%x cur_type=%d retained jack_type=%d\n", + __func__, cur_mode, cur_type, rt721->jack_type); + + if (cur_mode != 0 && cur_type == rt721->jack_type) { + dev_info(&rt721->slave->dev, + "%s: resume with same jack, reuse jack_type=%d\n", + __func__, rt721->jack_type); + rt721->imp_sensing_done = true; + rt721->last_real_JD = real_JD; + snd_soc_jack_report(rt721->hs_jack, rt721->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + return; + } + + dev_info(&rt721->slave->dev, + "%s: resume jack changed/unsettled, redetect\n", __func__); + rt721->jack_type = 0; + } + + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + !(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) && + real_JD == rt721->last_real_JD && real_JD == 0x1 && + rt721->int_cnt == 0 && rt721->imp_sensing_done && + rt721->jack_type != 0) { + dev_info(&rt721->slave->dev, + "%s: state settled (JD=%d), skip redetect\n", + __func__, real_JD); + rt721->last_real_JD = real_JD; + return; + } + rt721->last_real_JD = real_JD; + + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + !(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) && + rt721->int_cnt == 0 && real_JD == 0x1 && + !rt721->imp_sensing_done) { + dev_info(&rt721->slave->dev, + "%s: BRANCH impedance_sensing (SDCA_0 set, fresh pass)\n", + __func__); + rt721->imp_sensing_done = true; + rt721_impedance_sensing(rt721); + rt721->imp_sensing_ongoing = false; + return; + } + + /* SDW_SCP_SDCA_INT_SDCA_0 is used for jack detection */ + if ((rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) && + (rt721->int_cnt == 1 || real_JD == 0x0)) { + if (real_JD == 0x1) { + for (retry = 0; retry < retry_loop; retry++) { + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, + RT721_SDCA_ENT_GE49, + RT721_SDCA_CTL_DETECTED_MODE, 0), + &sta_mode); + if (sta_mode) + break; + usleep_range(20000, 21000); + } + dev_dbg(&rt721->slave->dev, + "%s: detected_mode settled=0x%x, %d retries\n", + __func__, sta_mode, retry); + } + rt721->jack_type = rt_sdca_headset_detect(rt721->regmap, + RT721_SDCA_ENT_GE49); + rt721->int_cnt = 0; + if (rt721->jack_type < 0) + return; + } + dev_info(&rt721->slave->dev, + "%s: handler run stat1=0x%x stat2=0x%x (SDCA_8=%d)\n", + __func__, rt721->scp_sdca_stat1, rt721->scp_sdca_stat2, + !!(rt721->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8)); + } else { + if (rt721->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) { + rt721->jack_type = rt_sdca_headset_detect(rt721->regmap, + RT721_SDCA_ENT_GE49); + if (rt721->jack_type < 0) + return; + } } /* SDW_SCP_SDCA_INT_SDCA_8 is used for button detection */ @@ -114,8 +309,12 @@ static void rt721_sdca_btn_check_handler(struct work_struct *work) /* Report ID for HID1 */ if (buf[0] == 0x11) btn_type = rt_sdca_btn_type(&buf[1]); - } else - rt721->jack_type = 0; + } else { + if (rt721->wf_id == RT721_S) + rt721_sdca_jack_reset_state(rt721); + else + rt721->jack_type = 0; + } dev_dbg(&rt721->slave->dev, "%s, btn_type=0x%x\n", __func__, btn_type); snd_soc_jack_report(rt721->hs_jack, rt721->jack_type | btn_type, @@ -142,151 +341,396 @@ static void rt721_sdca_btn_check_handler(struct work_struct *work) static void rt721_sdca_dmic_preset(struct rt721_sdca_priv *rt721) { - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF1_HV_CTRL1, 0xe000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8007); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL9, 0x2a2a); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL10, 0x2a00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL6, 0x2a2a); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL5, 0x2626); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL8, 0x1e00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL7, 0x1515); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL3, 0x0304); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL4, 0x0304); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_HDA_LEGACY_CTL1, 0x0000); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT_IT26, - RT721_SDCA_CTL_VENDOR_DEF, 0), 0x01); - regmap_write(rt721->mbq_regmap, 0x5910009, 0x2e01); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b40); - regmap_write(rt721->regmap, 0x2f5c, 0x25); + unsigned int mic_func_status; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &mic_func_status); + dev_dbg(dev, "%s mic func_status=0x%x\n", __func__, mic_func_status); + + if ((mic_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL9, 0x2a2a); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL10, 0x2a00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL6, 0x2a2a); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL5, 0x2626); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL8, 0x1e00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL7, 0x1515); + if (rt721->wf_id == RT721_S) + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL3, 0x0304); + else + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL5, 0x0304); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL4, 0x0304); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_CTL1, 0x0000); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT_IT26, + RT721_SDCA_CTL_VENDOR_DEF, 0), 0x01); + regmap_write(rt721->mbq_regmap, 0x5910009, 0x2e01); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b40); + if (rt721->wf_id == RT721_S) { + regmap_write(rt721->regmap, 0x2f5c, 0x25); + } else { + regmap_write(rt721->regmap, 0x2f5c, 0x01); + regmap_write(rt721->regmap, 0x2f52, 0x00); + } + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + } } static void rt721_sdca_amp_preset(struct rt721_sdca_priv *rt721) { - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF1_HV_CTRL1, 0xe000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8007); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6420); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x6421); - regmap_write(rt721->mbq_regmap, 0x5810000, 0xe421); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_CH_FLOAT_CTL6, 0x5561); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, - RT721_GPIO_PAD_CTRL5, 0x8003); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_OT23, - RT721_SDCA_CTL_VENDOR_DEF, 0), 0x04); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, - RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, - RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, - RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); - regmap_write(rt721->regmap, - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, - RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); - regmap_write(rt721->regmap, 0x2f5d, 0x1); + unsigned int amp_func_status; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &_func_status); + dev_dbg(dev, "%s amp func_status=0x%x\n", __func__, amp_func_status); + + if ((amp_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_CH_FLOAT_CTL6, 0x5561); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_REG, + RT721_GPIO_PAD_CTRL5, 0x8003); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_OT23, + RT721_SDCA_CTL_VENDOR_DEF, 0), 0x04); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, + RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_PDE23, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_01), 0x00); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_FU55, + RT721_SDCA_CTL_FU_MUTE, CH_02), 0x00); + regmap_write(rt721->regmap, 0x2f5d, 0x1); + if (rt721->wf_id == RT721_U) + regmap_write(rt721->regmap, 0x2f54, 0x00); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + FUNCTION_NEEDS_INITIALIZATION); + } } static void rt721_sdca_jack_preset(struct rt721_sdca_priv *rt721) { - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF1_HV_CTRL1, 0xe000); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_MISC_POWER_CTL31, 0x8007); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_GE_REL_CTRL1, 0x8011); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL3, 0xcf00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL4, 0x000f); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL1, 0x1100); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_UMP_HID_CTRL5, 0x0c12); - rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, - RT721_JD_1PIN_GAT_CTRL2, 0xc002); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, - RT721_RC_CALIB_CTRL0, 0x0b40); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3333); - regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); - regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL1, 0x0140); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); - regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL18, 0x5522); - regmap_write(rt721->mbq_regmap, 0x5b10007, 0x2000); - regmap_write(rt721->mbq_regmap, 0x5B10017, 0x1b0f); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, - RT721_CBJ_A0_GAT_CTRL1, 0x2205); - rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, - RT721_HP_AMP_2CH_CAL4, 0xa105); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3b33); - regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON14, 0x3f33); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON13, 0x6048); - regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); - regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); - regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); - regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); - regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, - RT721_UAJ_TOP_TCON17, 0x0008); - rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, - RT721_DAC_2CH_CTRL3, 0x55ff); - rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, - RT721_DAC_2CH_CTRL4, 0xcc00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_MBIAS_LV_CTRL2, 0x6677); - rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, - RT721_VREF2_LV_CTRL1, 0x7600); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL2, 0x1234); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL3, 0x3512); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL1, 0x4040); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_ENT_FLOAT_CTL4, 0x1201); - rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, - RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); - regmap_write(rt721->regmap, 0x2f58, 0x07); + unsigned int calib_val, calib_val2, calib_tmp = 0, calib_tmp2 = 0; + unsigned int i, calib_loop = 10; + unsigned int wait_calib_loop = 10; + unsigned int jack_func_status; + struct device *dev = &rt721->slave->dev; - regmap_write(rt721->regmap, 0x2f51, 0x00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_MISC_CTL, 0x0004); + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &jack_func_status); + dev_dbg(dev, "%s jack func_status=0x%x\n", __func__, jack_func_status); + + if ((jack_func_status & FUNCTION_NEEDS_INITIALIZATION) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0xae05); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x8011); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL3, 0xcf00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL4, 0x000f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL1, 0x1100); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL5, 0x0c12); + rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL2, 0xc004); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b40); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x333b); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL18, 0x5522); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x4540); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0xa107); + regmap_write(rt721->mbq_regmap, 0x5B10017, 0x180f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL27, 0x4400); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3b3b); + regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3f3b); + regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); + regmap_write(rt721->mbq_regmap, 0x310300, 0x000c); + regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON17, 0x0008); + rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, + RT721_DAC_2CH_CTRL3, 0x55ff); + rt_sdca_index_write(rt721->mbq_regmap, RT721_DAC_CTRL, + RT721_DAC_2CH_CTRL4, 0xcc00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_MBIAS_LV_CTRL2, 0x6677); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF2_LV_CTRL1, 0x7600); + regmap_write(rt721->mbq_regmap, 0x0910000, 0x00a1); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); + rt_sdca_index_write(rt721->mbq_regmap, RT721_BOOST_CTRL, + RT721_BST_4CH_TOP_GATING_CTRL1, 0x002a); + + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL2, 0x1234); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL3, 0x3512); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL1, 0x4040); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL4, 0x1201); + regmap_write(rt721->regmap, 0x2f58, 0x01); + regmap_write(rt721->regmap, 0x2f59, 0x01); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6040); + regmap_write(rt721->mbq_regmap, 0x0910001, 0x1256); + regmap_write(rt721->mbq_regmap, 0x0910002, 0x0000); + regmap_write(rt721->mbq_regmap, 0x0910003, 0x0000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_TOP_CTRL3, 0x7788); + regmap_update_bits(rt721->mbq_regmap, RT721_ADC_BIAS_CTRL3, + 0x0004, 0x0004); + rt_sdca_index_write(rt721->mbq_regmap, RT721_MIC_BIAS_CTRL, + RT721_MIC_BIAS_TOP_CTRL2, 0xcc04); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CHARGE_PUMP_CTRL, + RT721_CHGPMP_GAT_CTRL1, 0x0000); + for (i = 0; i < wait_calib_loop; i++) { + regmap_read(rt721->mbq_regmap, 0x0581003a, &calib_val); + regmap_read(rt721->mbq_regmap, 0x0581003b, &calib_val2); + calib_val &= 0x1f; + calib_val2 &= 0xffff; + if (calib_val == 0 || calib_val2 == 0 || calib_val != calib_tmp || + calib_val2 != calib_tmp2) { + dev_dbg(&rt721->slave->dev, "%s: Keep calibration\n", __func__); + usleep_range(10000, 11000); + } else { + dev_dbg(&rt721->slave->dev, "%s: Calibration done\n", __func__); + break; + } + calib_tmp = calib_val; + calib_tmp2 = calib_val2; + } + + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL2, 0xd000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON_ANY, 0xc820); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x0540); + regmap_write(rt721->mbq_regmap, 0x5810030, 0xee00); + regmap_write(rt721->mbq_regmap, 0x5810033, 0x6000); + regmap_write(rt721->mbq_regmap, 0x5810035, 0x0036); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x0020); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x0021); + regmap_write(rt721->mbq_regmap, 0x5810000, 0x8021); + for (i = 0; i < calib_loop; i++) { + regmap_read(rt721->mbq_regmap, 0x0581003a, &calib_val); + regmap_read(rt721->mbq_regmap, 0x0581003b, &calib_val2); + calib_val &= 0x1f; + calib_val2 &= 0xffff; + if (calib_val == 0 || calib_val2 == 0 || calib_val != calib_tmp || + calib_val2 != calib_tmp2) { + dev_dbg(&rt721->slave->dev, "%s: Keep calibration\n", __func__); + usleep_range(10000, 11000); + } else { + dev_dbg(&rt721->slave->dev, "%s: Calibration done\n", __func__); + break; + } + calib_tmp = calib_val; + calib_tmp2 = calib_val2; + } + + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x4540); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2e05); + regmap_write(rt721->regmap, 0x40400188, 0x0); + regmap_write(rt721->regmap, 0x40400688, 0x0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(6), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(5), 0); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_MISC_CTL, BIT(1), 0); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_MISC_CTL, BIT(0), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(4), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(3), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(2), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(1), 0); + regmap_update_bits(rt721->regmap, 0x2f51, BIT(0), 0); + + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_HDA_LEGACY_UAJ_CTL, 0x1c00, 0x0000); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL1, 0xc000, 0xc000); + regmap_update_bits(rt721->mbq_regmap, RT721_IMP_SEN_CTRL11, + 0x001c, 0x0004); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL2, 0x0007, 0x0004); + rt_sdca_index_update_bits(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x000f, 0x0003); + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + msleep(500); + } +} + +static void rt721u_sdca_jack_preset(struct rt721_sdca_priv *rt721) +{ + unsigned int jack_func_status, calibration_status, i; + int ret; + struct device *dev = &rt721->slave->dev; + + regmap_read(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, RT721_SDCA_CTL_FUNC_STATUS, 0), + &jack_func_status); + dev_dbg(dev, "%s jack func_status=0x%x\n", __func__, jack_func_status); + + if ((jack_func_status & BIT(5)) || (!rt721->first_hw_init)) { + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_ANA_POW_PART, + RT721_VREF1_HV_CTRL1, 0xe000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_MISC_POWER_CTL31, 0x8007); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + 0x10, 0xffb7); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + 0x20, 0xffb7); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + 0x30, 0xffb7); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6048); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_GE_REL_CTRL1, 0x8011); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL3, 0xcf00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL4, 0x000f); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL1, 0x1100); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_UMP_HID_CTRL5, 0x0c12); + rt_sdca_index_write(rt721->mbq_regmap, RT721_JD_CTRL, + RT721_JD_1PIN_GAT_CTRL2, 0xc004); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_RC_CALIB_CTRL, + RT721_RC_CALIB_CTRL0, 0x0b40); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x333b); + regmap_write(rt721->mbq_regmap, 0x5810135, 0x8036); + regmap_write(rt721->mbq_regmap, 0x5810130, 0xee03); + regmap_write(rt721->mbq_regmap, 0x5810133, 0x6000); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL1, 0x4140); + regmap_write(rt721->mbq_regmap, 0x5810100, 0x0006); + regmap_write(rt721->mbq_regmap, 0x5810100, 0x8006); + for (i = 0; i < 5; i++) { + ret = regmap_read(rt721->mbq_regmap, 0x5810100, + &calibration_status); + if (ret < 0) + dev_dbg(dev, "calibration failed!, ret=%d\n", + ret); + if (!(calibration_status & 0x8000)) + break; + } + if (rt721->hw_vid == RT721_U_VB) + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2205); + else + rt_sdca_index_write(rt721->mbq_regmap, RT721_CBJ_CTRL, + RT721_CBJ_A0_GAT_CTRL1, 0x2e05); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3b3b); + regmap_write(rt721->mbq_regmap, 0x310400, 0x3043); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON14, 0x3f33); + regmap_write(rt721->mbq_regmap, 0x310401, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310402, 0x1b00); + regmap_write(rt721->mbq_regmap, 0x310300, 0x000f); + regmap_write(rt721->mbq_regmap, 0x310301, 0x3000); + regmap_write(rt721->mbq_regmap, 0x310302, 0x1b00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON17, 0x0008); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL2, 0x1234); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL3, 0x3512); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL1, 0x4040); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_ENT_FLOAT_CTL4, 0x1201); + regmap_write(rt721->regmap, 0x2f58, 0x01); + regmap_write(rt721->regmap, 0x2f59, 0x01); + regmap_write(rt721->regmap, 0x2f51, 0x00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_VENDOR_ANA_CTL, + RT721_UAJ_TOP_TCON13, 0x6040); + regmap_write(rt721->mbq_regmap, 0x910001, 0x3256); + regmap_write(rt721->mbq_regmap, 0x910002, 0x0000); + regmap_write(rt721->mbq_regmap, 0x910003, 0x0000); + regmap_write(rt721->mbq_regmap, 0x900004, 0x7778); + regmap_write(rt721->mbq_regmap, 0x910202, 0x02e4); + regmap_write(rt721->mbq_regmap, 0x800002, 0xcc04); + rt_sdca_index_write(rt721->mbq_regmap, RT721_CAP_PORT_CTRL, + RT721_HP_AMP_2CH_CAL4, 0xa105); + rt_sdca_index_write(rt721->mbq_regmap, 0x0b, 0x01, 0x0000); + /* clear flag */ + regmap_write(rt721->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT0, + RT721_SDCA_CTL_FUNC_STATUS, 0), FUNCTION_NEEDS_INITIALIZATION); + } } static void rt721_sdca_jack_init(struct rt721_sdca_priv *rt721) @@ -692,19 +1136,47 @@ static int rt721_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, return changed; } -static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(out_vol_tlv_s, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(out_vol_tlv_u, -9225, 75, 0); static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0); static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, 0, 1000, 0); static const DECLARE_TLV_DB_SCALE(mic2_boost_vol_tlv, -200, 200, 0); -static const struct snd_kcontrol_new rt721_sdca_controls[] = { +static const struct snd_kcontrol_new rt721s_sdca_controls[] = { /* Headphone playback settings */ SOC_DOUBLE_R_EXT_TLV("FU05 Playback Volume", SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, RT721_SDCA_CTL_FU_VOLUME, CH_L), SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, - rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv), + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_s), + /* AMP playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_s), +}; + +static const struct snd_kcontrol_new rt721u_sdca_controls[] = { + /* Headphone playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU05 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, + RT721_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_USER_FU05, + RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x7b, 0, + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_u), + /* AMP playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, + RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x7b, 0, + rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv_u), +}; + +static const struct snd_kcontrol_new rt721_sdca_controls[] = { /* Headset mic capture settings */ SOC_DOUBLE_EXT("FU0F Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0, rt721_sdca_fu0f_capture_get, rt721_sdca_fu0f_capture_put), @@ -720,13 +1192,6 @@ static const struct snd_kcontrol_new rt721_sdca_controls[] = { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT721_SDCA_ENT_PLATFORM_FU44, RT721_SDCA_CTL_FU_CH_GAIN, CH_R), 1, 0x15, 0, rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, mic2_boost_vol_tlv), - /* AMP playback settings */ - SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, - RT721_SDCA_CTL_FU_VOLUME, CH_L), - SDW_SDCA_CTL(FUNC_NUM_AMP, RT721_SDCA_ENT_USER_FU06, - RT721_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, - rt721_sdca_set_gain_get, rt721_sdca_set_gain_put, out_vol_tlv), /* DMIC capture settings */ RT_SDCA_FU_CTRL("FU1E Capture Switch", SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT721_SDCA_ENT_USER_FU1E, @@ -1235,6 +1700,15 @@ static int rt721_sdca_probe(struct snd_soc_component *component) if (ret < 0 && ret != -EACCES) return ret; + if (rt721->wf_id == RT721_S) + snd_soc_add_component_controls(component, + rt721s_sdca_controls, + ARRAY_SIZE(rt721s_sdca_controls)); + else + snd_soc_add_component_controls(component, + rt721u_sdca_controls, + ARRAY_SIZE(rt721u_sdca_controls)); + return 0; } @@ -1492,6 +1966,7 @@ int rt721_sdca_init(struct device *dev, struct regmap *regmap, rt721->fu0f_mixer_l_mute = rt721->fu0f_mixer_r_mute = true; rt721->fu1e_mixer_mute[0] = rt721->fu1e_mixer_mute[1] = rt721->fu1e_mixer_mute[2] = rt721->fu1e_mixer_mute[3] = true; + rt721->wf_id = -1; return devm_snd_soc_register_component(dev, &soc_sdca_dev_rt721, rt721_sdca_dai, ARRAY_SIZE(rt721_sdca_dai)); @@ -1509,8 +1984,11 @@ static void rt721_sdca_reset(struct rt721_sdca_priv *rt721) int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) { struct rt721_sdca_priv *rt721 = dev_get_drvdata(dev); + int val; rt721->disable_irq = false; + rt721->imp_sensing_done = false; + rt721->last_real_JD = -1; if (rt721->hw_init) return 0; @@ -1540,12 +2018,23 @@ int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave) pm_runtime_get_noresume(&slave->dev); + if (rt721->wf_id < 0) { + rt_sdca_index_read(rt721->mbq_regmap, RT721_VENDOR_REG, + RT721_JD_PRODUCT_NUM, &val); + rt721->wf_id = (val >> 4) & 0xf; + rt721->hw_vid = val & 0xf; + dev_dbg(&slave->dev, "wf_id = %d hw_vid = %d\n", rt721->wf_id, rt721->hw_vid); + } + if (!rt721->first_hw_init) rt721_sdca_reset(rt721); rt721_sdca_dmic_preset(rt721); rt721_sdca_amp_preset(rt721); - rt721_sdca_jack_preset(rt721); + if (rt721->wf_id == RT721_S) + rt721_sdca_jack_preset(rt721); + else + rt721u_sdca_jack_preset(rt721); if (rt721->hs_jack && (!rt721->first_hw_init)) rt721_sdca_jack_init(rt721); diff --git a/sound/soc/codecs/rt721-sdca.h b/sound/soc/codecs/rt721-sdca.h index 24ce188562ba..cc3bce0ba517 100644 --- a/sound/soc/codecs/rt721-sdca.h +++ b/sound/soc/codecs/rt721-sdca.h @@ -29,6 +29,10 @@ struct rt721_sdca_priv { /* For Headset jack & Headphone */ unsigned int scp_sdca_stat1; unsigned int scp_sdca_stat2; + unsigned int int_cnt; + bool imp_sensing_ongoing; + bool imp_sensing_done; + int last_real_JD; struct snd_soc_jack *hs_jack; struct delayed_work jack_detect_work; struct delayed_work jack_btn_check_work; @@ -40,6 +44,8 @@ struct rt721_sdca_priv { /* For DMIC */ bool fu1e_dapm_mute; bool fu1e_mixer_mute[4]; + int wf_id; + int hw_vid; }; struct rt721_sdca_dmic_kctrl_priv { @@ -52,8 +58,10 @@ struct rt721_sdca_dmic_kctrl_priv { /* NID */ #define RT721_ANA_POW_PART 0x01 #define RT721_DAC_CTRL 0x04 +#define RT721_MIC_BIAS_CTRL 0x08 #define RT721_JD_CTRL 0x09 #define RT721_CBJ_CTRL 0x0a +#define RT721_CHARGE_PUMP_CTRL 0x0b #define RT721_CAP_PORT_CTRL 0x0c #define RT721_CLASD_AMP_CTRL 0x0d #define RT721_BOOST_CTRL 0x0f @@ -79,17 +87,28 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_DAC_2CH_CTRL3 0x02 #define RT721_DAC_2CH_CTRL4 0x03 +/* Index (NID:08h) */ +#define RT721_MIC_BIAS_TOP_CTRL2 0x02 + /* Index (NID:09h) */ +#define RT721_JD_1PIN_TOP_CTRL3 0x04 +#define RT721_JD_1PIN_GAT_CTRL1 0x06 #define RT721_JD_1PIN_GAT_CTRL2 0x07 +#define RT721_JD_1PIN_GAT_STA1 0x09 /* Index (NID:0ah) */ #define RT721_CBJ_A0_GAT_CTRL1 0x04 #define RT721_CBJ_A0_GAT_CTRL2 0x05 +/* Index (NID:0bh) */ +#define RT721_CHGPMP_GAT_CTRL1 0x01 + /* Index (NID:0Ch) */ #define RT721_HP_AMP_2CH_CAL1 0x05 +#define RT721_HP_AMP_2CH_CAL2 0x06 #define RT721_HP_AMP_2CH_CAL4 0x08 #define RT721_HP_AMP_2CH_CAL18 0x1b +#define RT721_HP_AMP_2CH_CAL27 0x24 /* Index (NID:0dh) */ #define RT721_CLASD_AMP_2CH_CAL 0x14 @@ -127,10 +146,13 @@ struct rt721_sdca_dmic_kctrl_priv { /* Index (NID:5fh) */ #define RT721_MISC_POWER_CTL0 0x00 +#define RT721_MISC_POWER_CTL10 0x10 #define RT721_MISC_POWER_CTL31 0x31 +#define RT721_UAJ_TOP_TCON12 0x43 #define RT721_UAJ_TOP_TCON13 0x44 #define RT721_UAJ_TOP_TCON14 0x45 #define RT721_UAJ_TOP_TCON17 0x48 +#define RT721_UAJ_TOP_TCON_ANY 0x70 /* Index (NID:61h) */ #define RT721_HDA_LEGACY_MUX_CTL0 0x00 @@ -179,6 +201,13 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_BUF_ADDR_HID1 0x44030000 #define RT721_BUF_ADDR_HID2 0x44030020 +/* Impedance Sensing Control */ +#define RT721_IMP_SEN_CTRL11 0x5b10017 +#define RT721_IMP_SEN_CTRL13 0x5b1001b + +/* ADC Bias Control 3 */ +#define RT721_ADC_BIAS_CTRL3 0x0910202 + /* RT721 SDCA Control - function number */ #define FUNC_NUM_JACK_CODEC 0x01 #define FUNC_NUM_MIC_ARRAY 0x02 @@ -214,6 +243,7 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_SDCA_ENT_XU03 0x03 #define RT721_SDCA_ENT_XU0D 0x0d #define RT721_SDCA_ENT_FU55 0x55 +#define RT721_SDCA_ENT0 0x00 /* RT721 SDCA control */ #define RT721_SDCA_CTL_SAMPLE_FREQ_INDEX 0x10 @@ -229,6 +259,7 @@ struct rt721_sdca_dmic_kctrl_priv { #define RT721_SDCA_CTL_VENDOR_DEF 0x30 #define RT721_SDCA_CTL_XUV 0x34 #define RT721_SDCA_CTL_FU_CH_GAIN 0x0b +#define RT721_SDCA_CTL_FUNC_STATUS 0x10 /* RT721 SDCA channel */ #define CH_L 0x01 @@ -261,6 +292,9 @@ struct rt721_sdca_dmic_kctrl_priv { /* RT721 HID ID */ #define RT721_SDCA_HID_ID 0x11 +/* Function_Status */ +#define FUNCTION_NEEDS_INITIALIZATION BIT(5) + enum { RT721_AIF1, /* For headset mic and headphone */ RT721_AIF2, /* For speaker */ @@ -268,6 +302,16 @@ enum { RT721_AIFS, }; +enum rt721_wf_id { + RT721_S, + RT721_U, +}; + +enum rt721u_hw_ver { + RT721_U_VB = 1, + RT721_U_VD = 3, +}; + int rt721_sdca_io_init(struct device *dev, struct sdw_slave *slave); int rt721_sdca_init(struct device *dev, struct regmap *regmap, struct regmap *mbq_regmap, struct sdw_slave *slave); diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c index 5bc9aed58d14..078255fbe1fc 100644 --- a/sound/soc/codecs/rt722-sdca-sdw.c +++ b/sound/soc/codecs/rt722-sdca-sdw.c @@ -471,10 +471,8 @@ static void rt722_sdca_sdw_remove(struct sdw_slave *slave) { struct rt722_sdca_priv *rt722 = dev_get_drvdata(&slave->dev); - if (rt722->hw_init) { - cancel_delayed_work_sync(&rt722->jack_detect_work); - cancel_delayed_work_sync(&rt722->jack_btn_check_work); - } + cancel_delayed_work_sync(&rt722->jack_detect_work); + cancel_delayed_work_sync(&rt722->jack_btn_check_work); if (rt722->first_hw_init) pm_runtime_disable(&slave->dev); diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c index 5a1c1e10e6d7..2e891b99dc27 100644 --- a/sound/soc/codecs/rt766-sdca.c +++ b/sound/soc/codecs/rt766-sdca.c @@ -176,8 +176,11 @@ static irqreturn_t rt766_sdca_irq_jd_handler(int irq, void *data) if (!rt766->hs_jack) return IRQ_HANDLED; - if (!rt766->component->card || !rt766->component->card->instantiated) + if (!rt766->component->card || !rt766->component->card->instantiated) { + /* trigger again if card is not instantiated */ + regmap_write(rt766->regmap, RT766_GE_DET_IRQ_MNL, 0x01); return IRQ_HANDLED; + } mutex_lock(&rt766->disable_irq_lock); if (!rt766->disable_irq) @@ -304,6 +307,9 @@ static int rt766_sdca_set_jack_detect(struct snd_soc_component *component, if (rt766->hid_func_data) rt766_sdca_irq_ctl(rt766, rt766->hid_func_data, rt766->component, rt766->irq_info, false); + } else { + /* trigger GE interrupt */ + regmap_write(rt766->regmap, RT766_GE_DET_IRQ_MNL, 0x01); } pm_runtime_put_autosuspend(component->dev); @@ -1297,7 +1303,7 @@ static int rt766_func_initialize(struct rt766_sdca_priv *rt766, struct sdca_func regmap_read(rt766->regmap, func_status_reg, &func_status); dev_dbg(dev, "%s, %s func_status=0x%x\n", __func__, func_data->desc->name, func_status); - if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) || (!rt766->first_hw_init)) { + if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION)) { ret = sdca_regmap_write_init(dev, rt766->regmap, func_data); if (ret) { dev_err(dev, "%s initialization table update failed\n", func_data->desc->name); diff --git a/sound/soc/codecs/rt766-sdca.h b/sound/soc/codecs/rt766-sdca.h index de4064007eb9..61af5e9c077a 100644 --- a/sound/soc/codecs/rt766-sdca.h +++ b/sound/soc/codecs/rt766-sdca.h @@ -50,6 +50,7 @@ struct rt766_sdca_priv { #define RT766_DEV_ID1 0xc405 #define RT766_DEV_ID0 0xc406 #define RT766_BOND_LATCH_ID 0xc407 +#define RT766_GE_DET_IRQ_MNL 0xd203 #define RT766_HP_POWER_STATE 0x1000004 #define RT766_HP_FSM_CTL2_1 0x100000d diff --git a/sound/soc/codecs/rt9120.c b/sound/soc/codecs/rt9120.c index e51cd4a31894..1445e823c4ff 100644 --- a/sound/soc/codecs/rt9120.c +++ b/sound/soc/codecs/rt9120.c @@ -289,9 +289,18 @@ static int rt9120_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 rt9120_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rt9120_dai_ops = { .set_fmt = rt9120_set_fmt, .hw_params = rt9120_hw_params, + .auto_selectable_formats = &rt9120_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt9120_dai = { diff --git a/sound/soc/codecs/rt9123.c b/sound/soc/codecs/rt9123.c index 07eaf275c9e9..251bad450ec8 100644 --- a/sound/soc/codecs/rt9123.c +++ b/sound/soc/codecs/rt9123.c @@ -299,10 +299,23 @@ static int rt9123_dai_hw_params(struct snd_pcm_substream *substream, return 0; } +/* + * NOTE + * + * auto_select doesn't include _DSP_A / _DSP_B, because it has limitaion for TDM + * (= rt9123_dai_hw_params()). It can be selected via Card directly if needed. + */ +static const u64 rt9123_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops rt9123_dai_ops = { .set_fmt = rt9123_dai_set_format, .set_tdm_slot = rt9123_dai_set_tdm_slot, .hw_params = rt9123_dai_hw_params, + .auto_selectable_formats = &rt9123_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rt9123_dai_driver = { diff --git a/sound/soc/codecs/rtq9124.c b/sound/soc/codecs/rtq9124.c index 2a041894bc0c..cc1c68752fde 100644 --- a/sound/soc/codecs/rtq9124.c +++ b/sound/soc/codecs/rtq9124.c @@ -286,10 +286,19 @@ static int rtq9124_dai_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 rtq9124_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rtq9124_dai_ops = { .set_fmt = rtq9124_dai_set_format, .set_tdm_slot = rtq9124_dai_set_tdm_slot, .hw_params = rtq9124_dai_hw_params, + .auto_selectable_formats = &rtq9124_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rtq9124_dai_driver = { diff --git a/sound/soc/codecs/rtq9128.c b/sound/soc/codecs/rtq9128.c index 573200e5062f..9567e90c0b62 100644 --- a/sound/soc/codecs/rtq9128.c +++ b/sound/soc/codecs/rtq9128.c @@ -727,11 +727,20 @@ static int rtq9128_dai_mute_stream(struct snd_soc_dai *dai, int mute, int stream return ret < 0 ? ret : 0; } +static const u64 rtq9128_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops rtq9128_dai_ops = { .set_fmt = rtq9128_dai_set_fmt, .set_tdm_slot = rtq9128_dai_set_tdm_slot, .hw_params = rtq9128_dai_hw_params, .mute_stream = rtq9128_dai_mute_stream, + .auto_selectable_formats = &rtq9128_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 35df1a8c4c8c..97bea75ed24f 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -1158,12 +1158,23 @@ static int sgtl5000_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S24_LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 sgtl5000_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops sgtl5000_ops = { .hw_params = sgtl5000_pcm_hw_params, .mute_stream = sgtl5000_mute_stream, .set_fmt = sgtl5000_set_dai_fmt, .set_sysclk = sgtl5000_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &sgtl5000_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sgtl5000_dai = { diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c index d87141ba8438..9b904450ab8a 100644 --- a/sound/soc/codecs/si476x.c +++ b/sound/soc/codecs/si476x.c @@ -199,9 +199,27 @@ static int si476x_codec_hw_params(struct snd_pcm_substream *substream, return err; } +static const u64 si476x_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops si476x_dai_ops = { .hw_params = si476x_codec_hw_params, .set_fmt = si476x_codec_set_dai_fmt, + .auto_selectable_formats = si476x_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(si476x_selectable_formats), }; static struct snd_soc_dai_driver si476x_dai = { diff --git a/sound/soc/codecs/sma1303.c b/sound/soc/codecs/sma1303.c index 15758eb13f8c..0bff32559714 100644 --- a/sound/soc/codecs/sma1303.c +++ b/sound/soc/codecs/sma1303.c @@ -1412,12 +1412,25 @@ static int sma1303_dai_set_tdm_slot(struct snd_soc_dai *dai, return 0; } +static const u64 sma1303_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops sma1303_dai_ops_amp = { .set_sysclk = sma1303_dai_set_sysclk_amp, .set_fmt = sma1303_dai_set_fmt_amp, .hw_params = sma1303_dai_hw_params_amp, .mute_stream = sma1303_dai_mute, .set_tdm_slot = sma1303_dai_set_tdm_slot, + .auto_selectable_formats = &sma1303_selectable_formats, + .num_auto_selectable_formats = 1, }; #define SMA1303_RATES SNDRV_PCM_RATE_8000_192000 diff --git a/sound/soc/codecs/sma1307.c b/sound/soc/codecs/sma1307.c index adb369a29b9d..57f34f2c5efb 100644 --- a/sound/soc/codecs/sma1307.c +++ b/sound/soc/codecs/sma1307.c @@ -1552,12 +1552,25 @@ static int sma1307_dai_mute_stream(struct snd_soc_dai *dai, int mute, return 0; } +static const u64 sma1307_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops sma1307_dai_ops_amp = { .hw_params = sma1307_dai_hw_params_amp, .set_fmt = sma1307_dai_set_fmt_amp, .set_sysclk = sma1307_dai_set_sysclk_amp, .set_tdm_slot = sma1307_dai_set_tdm_slot, .mute_stream = sma1307_dai_mute_stream, + .auto_selectable_formats = &sma1307_selectable_formats, + .num_auto_selectable_formats = 1, }; #define SMA1307_RATES_PLAYBACK SNDRV_PCM_RATE_8000_96000 @@ -1701,7 +1714,9 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil __func__, setting_file, ERR_PTR(ret)); sma1307->set.status = false; return; - } else if ((fw->size) < SMA1307_SETTING_HEADER_SIZE) { + } else if (fw->size % sizeof(int) || + fw->size < (SMA1307_SETTING_HEADER_SIZE + + SMA1307_SETTING_DEFAULT_SIZE) * sizeof(int)) { dev_err(sma1307->dev, "%s: Invalid file\n", __func__); sma1307->set.status = false; return; @@ -1720,6 +1735,10 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil sma1307->set.checksum = data[sma1307->set.header_size - 2]; sma1307->set.num_mode = data[sma1307->set.header_size - 1]; num_mode = sma1307->set.num_mode; + if (num_mode < 0 || num_mode > ARRAY_SIZE(sma1307->set.mode_set)) { + sma1307->set.status = false; + return; + } sma1307->set.header = devm_kmalloc_array(sma1307->dev, sma1307->set.header_size, sizeof(int), @@ -1755,7 +1774,11 @@ static void sma1307_setting_loaded(struct sma1307_priv *sma1307, const char *fil /* MODE */ offset = sma1307->set.header_size + sma1307->set.def_size; - sma1307->set.mode_size = DIV_ROUND_CLOSEST(size - offset, num_mode + 1); + if ((size - offset) % (num_mode + 1)) { + sma1307->set.status = false; + return; + } + sma1307->set.mode_size = (size - offset) / (num_mode + 1); for (int i = 0; i < num_mode; i++) { sma1307->set.mode_set[i] = devm_kzalloc(sma1307->dev, diff --git a/sound/soc/codecs/sn624x-sdca-sdw.c b/sound/soc/codecs/sn624x-sdca-sdw.c new file mode 100644 index 000000000000..c9e690482e8e --- /dev/null +++ b/sound/soc/codecs/sn624x-sdca-sdw.c @@ -0,0 +1,1888 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// sn624x-sdw-sdca.c -- SN624X SDCA ALSA SoC SoundWire audio driver +// +// Copyright(c) 2025 Senary Semiconductor Corp. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sn624x-sdca.h" + +/* Resume wait after bus re-enumeration (rt722-style probe timeout) */ +#define SN624X_PROBE_TIMEOUT_MS 5000U +#define SN624X_PWR_D0 SDCA_PDE_PS0 +#define SN624X_PWR_D3 SDCA_PDE_PS3 +#define SN624X_MUTE_ON 1 +/* + * Budget for ACTUAL_PS poll via sdca_asoc_pde_poll_actual_ps() (100 polls). + * Matches previous ~2s hand-rolled loop for slow PDE transitions. + */ +#define SN624X_PDE_PS_POLL_BUDGET_US 2000000U + +/* + * Prefer ACPI mipi-sdca-function-initialization-table via + * sdca_regmap_write_init(). If absent or DisCo parse is incomplete, + * a temporary vendor-window OEM fallback is used until firmware tables land. + * + * Jack detection is poll-driven (UAJ pulse + GE35); SDCA INTMASK bits stay + * masked so interrupt storms cannot schedule extra work. + */ + +struct sn624x_sdca_priv; + +static void sn624x_sdca_sdca_irq_mask_all(struct sn624x_sdca_priv *sn624x); +static int sn624x_uaj_ge35_apply_detected_mode(struct device *dev, + struct sn624x_sdca_priv *sn624x); +#define SN624X_DBG(dev, fmt, ...) dev_dbg(dev, "sn624x: " fmt, ##__VA_ARGS__) + +/* + * SDCA control addresses via SDW_SDCA_CTL(fun, ent, ctl, ch). + * Pass ch=0 for single-value controls (power/rate/status); use CH_L/CH_R + * only for multi-channel FU mute/volume. CH_ENABLE_* are not SDCA Control + * Prefix addresses — keep as raw windows. + */ +/* Speaker amp (function 4) — PDE/FU power/mute/vol; rate on CS01 */ +#define SN624X_REG_PWR_STATE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_ACTUAL_PS, 0) +#define SN624X_REG_PWR_STATE_WRITE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_REQUESTED_PS, 0) +#define SN624X_REG_RATE_SEL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_CS01, \ + SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0) +#define SN624X_REG_CH_ENABLE_A 0x00000220u +#define SN624X_REG_CH_ENABLE_B 0x00000230u +#define SN624X_REG_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L) +#define SN624X_REG_CHR_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R) +#define SN624X_REG_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L) +#define SN624X_REG_RIGHT_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_R) + +/* Jack playback (function 1) */ +#define SN624X_REG_JACK_OUT_PWR_STATE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_ACTUAL_PS, 0) +#define SN624X_REG_JACK_OUT_PWR_STATE_WRITE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_REQUESTED_PS, 0) +#define SN624X_REG_JACK_OUT_RATE_SEL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_CS01, \ + SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0) +#define SN624X_REG_JACK_OUT_CH_ENABLE_A 0x00000120u +#define SN624X_REG_JACK_OUT_CH_ENABLE_B 0x00000130u +#define SN624X_REG_JACK_OUT_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L) +#define SN624X_REG_JACK_OUT_CHR_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R) +#define SN624X_REG_JACK_OUT_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L) +#define SN624X_REG_JACK_OUT_CLUSTER_INDEX \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_CRU05, \ + SN624X_SDCA_CTL_CLUSTER_INDEX, 0) + +/* Jack headset capture (function 1) */ +#define SN624X_REG_JACK_CAP_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU1E, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L) +#define SN624X_REG_JACK_CAP_CHR_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU1E, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R) +#define SN624X_REG_JACK_CAP_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU1E, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L) +#define SN624X_REG_JACK_CAP_PWR_STATE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE04, \ + SDCA_CTL_PDE_ACTUAL_PS, 0) +#define SN624X_REG_JACK_CAP_PWR_STATE_WRITE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE04, \ + SDCA_CTL_PDE_REQUESTED_PS, 0) +#define SN624X_REG_JACK_CAP_RATE_SEL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_CS02, \ + SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0) +#define SN624X_REG_JACK_CAP_CH_ENABLE_A 0x00000420u /* low bits: 0x3=both */ +#define SN624X_REG_JACK_CAP_CH_ENABLE_B 0x00000430u + +#define SN624X_REG_JACK_FUN_STATUS_CTL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_ENTITY0, \ + SN624X_SDCA_CTL_FUNCTION_STATUS, 0) +#define SN624X_JACK_FUN_NEEDS_INIT BIT(5) +#define SN624X_JACK_FUN_HAS_BEEN_RESET BIT(6) + +/* DMIC capture (function 2) */ +#define SN624X_REG_DMIC_CAP_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L) +#define SN624X_REG_DMIC_CAP_CHR_VOL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R) +/* Q7.8 channel volume: integer in bits 15:8, fraction in bits 7:0 */ +#define SN624X_VOL_Q78(int_part, frac_part) \ + ((((unsigned int)(int_part) & 0xff) << 8) | ((unsigned int)(frac_part) & 0xff)) +#define SN624X_REG_DMIC_CAP_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L) +#define SN624X_REG_DMIC_CAP_CHR_MUTE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \ + SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_R) +#define SN624X_REG_DMIC_CAP_RATE_SEL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_CS02, \ + SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0) +/* OEM sheet used ctl=0; SDCA Actual PS is 0x10 — poll the standard selector */ +#define SN624X_REG_DMIC_CAP_PWR_STATE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_ACTUAL_PS, 0) +#define SN624X_REG_DMIC_CAP_PWR_STATE_WRITE \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_PDE03, \ + SDCA_CTL_PDE_REQUESTED_PS, 0) +#define SN624X_REG_DMIC_CAP_CH_ENABLE_A 0x00000520u /* low bits: 0x3=both */ +#define SN624X_REG_DMIC_CAP_CH_ENABLE_B 0x00000530u + +#define SN624X_REG_DMIC_FUN_STATUS_CTL \ + SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_ENTITY0, \ + SN624X_SDCA_CTL_FUNCTION_STATUS, 0) + +/* + * Write REQUESTED_PS then poll ACTUAL_PS with the shared SDCA helper + * (sdca_asoc_pde_poll_actual_ps). Caller must not hand-roll ACTUAL_PS waits. + */ +static int sn624x_pde_request_ps(struct device *dev, struct regmap *regmap, + unsigned int fun, unsigned int ent, + unsigned int ps); + +/* + * DAPM PDE supplies (rt711/rt712 style): write REQUESTED_PS then poll ACTUAL_PS. + * Mute-before-D3 is handled by FU widget PRE_PMD, which runs before SUPPLY PRE_PMD + * when the FU lists the PDE as a dependency. + */ +static int sn624x_dapm_pde_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event, + unsigned int fun, unsigned int ent) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct sn624x_sdca_priv *sn624x = + snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + int ret; + + if (!sn624x || !sn624x->regmap) + return -ENODEV; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + ret = sn624x_pde_request_ps(dev, sn624x->regmap, fun, ent, + SN624X_PWR_D0); + if (ret < 0) + dev_warn(dev, + "sn624x: DAPM %s PDE fun=%u ent=0x%x -> PS0 failed (%d)\n", + w->name, fun, ent, ret); + break; + case SND_SOC_DAPM_PRE_PMD: + ret = sn624x_pde_request_ps(dev, sn624x->regmap, fun, ent, + SN624X_PWR_D3); + if (ret < 0) + dev_warn(dev, + "sn624x: DAPM %s PDE fun=%u ent=0x%x -> PS3 failed (%d)\n", + w->name, fun, ent, ret); + break; + default: + return 0; + } + return ret; +} + +static int sn624x_pde_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_dapm_pde_event(w, kcontrol, event, + SN624X_FUNC_NUM_JACK_CODEC, + SN624X_SDCA_ENT_PDE03); +} + +static int sn624x_pde_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_dapm_pde_event(w, kcontrol, event, + SN624X_FUNC_NUM_SPEAKER_AMP, + SN624X_SDCA_ENT_PDE03); +} + +static int sn624x_pde_mic2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_dapm_pde_event(w, kcontrol, event, + SN624X_FUNC_NUM_JACK_CODEC, + SN624X_SDCA_ENT_PDE04); +} + +static int sn624x_pde_dmic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_dapm_pde_event(w, kcontrol, event, + SN624X_FUNC_NUM_MIC_ARRAY, + SN624X_SDCA_ENT_PDE03); +} + +static int sn624x_fu_mute_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event, + unsigned int mute_l, unsigned int mute_r, + unsigned int vol_l, unsigned int vol_r, + unsigned int vol_val) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct sn624x_sdca_priv *sn624x = + snd_soc_component_get_drvdata(component); + struct device *dev = component->dev; + int ret; + + if (!sn624x || !sn624x->regmap) + return -ENODEV; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (vol_l) { + ret = regmap_write(sn624x->regmap, vol_l, vol_val); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s vol(L) failed (%d)\n", + w->name, ret); + } + if (vol_r) { + ret = regmap_write(sn624x->regmap, vol_r, vol_val); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s vol(R) failed (%d)\n", + w->name, ret); + } + ret = regmap_write(sn624x->regmap, mute_l, 0); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s unmute(L) failed (%d)\n", + w->name, ret); + if (mute_r) { + ret = regmap_write(sn624x->regmap, mute_r, 0); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s unmute(R) failed (%d)\n", + w->name, ret); + } + break; + case SND_SOC_DAPM_PRE_PMD: + /* Mute and settle before PDE SUPPLY PRE_PMD takes the domain to PS3 */ + ret = regmap_write(sn624x->regmap, mute_l, SN624X_MUTE_ON); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s mute(L) failed (%d)\n", + w->name, ret); + if (mute_r) { + ret = regmap_write(sn624x->regmap, mute_r, SN624X_MUTE_ON); + if (ret < 0) + dev_warn(dev, "sn624x: DAPM %s mute(R) failed (%d)\n", + w->name, ret); + } + usleep_range(SN624X_ROUTE_AFTER_MUTE_US_MIN, + SN624X_ROUTE_AFTER_MUTE_US_MAX); + break; + default: + break; + } + return 0; +} + +static int sn624x_fu_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_fu_mute_event(w, kcontrol, event, + SN624X_REG_JACK_OUT_MUTE, 0, + SN624X_REG_JACK_OUT_VOL, + SN624X_REG_JACK_OUT_CHR_VOL, 0); +} + +static int sn624x_fu_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_fu_mute_event(w, kcontrol, event, + SN624X_REG_MUTE, SN624X_REG_RIGHT_MUTE, + SN624X_REG_VOL, SN624X_REG_CHR_VOL, 0); +} + +static int sn624x_fu_mic2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct sn624x_sdca_priv *sn624x = + snd_soc_component_get_drvdata(component); + int ret; + + if (event == SND_SOC_DAPM_POST_PMU && sn624x) + sn624x_uaj_ge35_apply_detected_mode(component->dev, sn624x); + + ret = sn624x_fu_mute_event(w, kcontrol, event, + SN624X_REG_JACK_CAP_MUTE, 0, + SN624X_REG_JACK_CAP_VOL, + SN624X_REG_JACK_CAP_CHR_VOL, + SN624X_VOL_Q78(6, 0)); + return ret; +} + +static int sn624x_fu_dmic_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + return sn624x_fu_mute_event(w, kcontrol, event, + SN624X_REG_DMIC_CAP_MUTE, + SN624X_REG_DMIC_CAP_CHR_MUTE, + SN624X_REG_DMIC_CAP_VOL, + SN624X_REG_DMIC_CAP_CHR_VOL, + SN624X_VOL_Q78(6, 0)); +} + +static int sn624x_pde_request_ps(struct device *dev, struct regmap *regmap, + unsigned int fun, unsigned int ent, + unsigned int ps) +{ + static const struct sdca_pde_delay delays[] = { + { .from_ps = SDCA_PDE_PS0, .to_ps = SDCA_PDE_PS3, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS3, .to_ps = SDCA_PDE_PS0, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS1, .to_ps = SDCA_PDE_PS0, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS2, .to_ps = SDCA_PDE_PS0, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS0, .to_ps = SDCA_PDE_PS0, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + { .from_ps = SDCA_PDE_PS3, .to_ps = SDCA_PDE_PS3, + .us = SN624X_PDE_PS_POLL_BUDGET_US }, + }; + unsigned int req = SDW_SDCA_CTL(fun, ent, SDCA_CTL_PDE_REQUESTED_PS, 0); + unsigned int act = SDW_SDCA_CTL(fun, ent, SDCA_CTL_PDE_ACTUAL_PS, 0); + unsigned int from_ps = SDCA_PDE_PS3; + int ret; + + ret = regmap_read(regmap, act, &from_ps); + if (ret < 0) { + dev_warn(dev, + "sn624x: PDE fun=%u ent=0x%x ACTUAL_PS read failed (%d)\n", + fun, ent, ret); + from_ps = (ps == SN624X_PWR_D0) ? SN624X_PWR_D3 : SN624X_PWR_D0; + } else { + from_ps &= 0xffu; + } + + ret = regmap_write(regmap, req, ps); + if (ret < 0) { + dev_warn(dev, + "sn624x: PDE fun=%u ent=0x%x REQUESTED_PS=%u write failed (%d)\n", + fun, ent, ps, ret); + return ret; + } + + ret = sdca_asoc_pde_poll_actual_ps(regmap, fun, ent, + from_ps, ps, + delays, ARRAY_SIZE(delays)); + if (ret < 0) + dev_warn(dev, + "sn624x: PDE fun=%u ent=0x%x ACTUAL_PS poll failed (%d; want %u)\n", + fun, ent, ret, ps); + return ret; +} + +static void sn624x_jack_type_from_det_mode(struct sn624x_sdca_priv *sn624x, + unsigned int det_mode) +{ + switch (det_mode) { + case 0x00: + sn624x->jack_type = 0; + break; + case 0x03: + sn624x->jack_type = SND_JACK_HEADPHONE; + break; + case 0x05: + sn624x->jack_type = SND_JACK_HEADSET; + break; + default: + SN624X_DBG(&sn624x->slave->dev, + "unknown GE35 detected_mode 0x%x\n", det_mode); + sn624x->jack_type = 0; + break; + } +} + +static int sn624x_uaj_ge35_select_mode(struct device *dev, + struct sn624x_sdca_priv *sn624x, + unsigned int mode) +{ + int ret; + + ret = regmap_write(sn624x->regmap, + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, + SN624X_SDCA_ENT_GE35, + SN624X_SDCA_CTL_SELECTED_MODE, 0), + mode); + if (ret < 0) + dev_warn(dev, + "sn624x: jack_cap: GE35 SELECTED_MODE=0x%x failed (%d)\n", + mode, ret); + else + dev_dbg(dev, + "sn624x: jack_cap: GE35 SELECTED_MODE=0x%x\n", + mode); + return ret; +} + +/* + * Follow DETECTED_MODE → SELECTED_MODE (rt711/rt722 pattern). Never force + * headset: mic path is only valid when the jack hardware reports one. + * Also updates sn624x->jack_type from DETECTED_MODE. + */ +static int sn624x_uaj_ge35_apply_detected_mode(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + unsigned int det_mode = 0; + int ret; + + ret = regmap_read(sn624x->regmap, + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, + SN624X_SDCA_ENT_GE35, + SN624X_SDCA_CTL_DETECTED_MODE, 0), + &det_mode); + if (ret < 0) { + dev_warn(dev, + "sn624x: jack_cap: GE35 DETECTED_MODE read failed (%d)\n", + ret); + return ret; + } + + dev_dbg(dev, "sn624x: jack_cap: GE35 DETECTED_MODE=0x%x\n", det_mode); + sn624x_jack_type_from_det_mode(sn624x, det_mode); + + if (!det_mode) { + dev_dbg(dev, + "sn624x: jack_cap: no jack detected — skip SELECTED_MODE\n"); + return 0; + } + + return sn624x_uaj_ge35_select_mode(dev, sn624x, det_mode); +} + +static const char *sn624x_status_str(enum sdw_slave_status status) +{ + switch (status) { + case SDW_SLAVE_UNATTACHED: + return "UNATTACHED"; + case SDW_SLAVE_ATTACHED: + return "ATTACHED"; + case SDW_SLAVE_ALERT: + return "ALERT"; + case SDW_SLAVE_RESERVED: + return "RESERVED"; + default: + return "UNKNOWN"; + } +} + +static int sn624x_sdca_mbq_size(struct device *dev, unsigned int reg) +{ + if (!SDW_SDCA_VALID_CTL(reg)) + return 1; + + /* + * FU_VOLUME and GE35 DETECTED_MODE share control selector 0x02. + * Only list known FU volume addresses as 16-bit MBQ — matching + * DETECTED_MODE by csel alone makes GE35 use SDW_SDCA_MBQ_CTL and + * fails with -ENODATA (-61), which breaks jack plug/unplug. + */ + switch (reg) { + case SN624X_REG_VOL: + case SN624X_REG_CHR_VOL: + case SN624X_REG_JACK_OUT_VOL: + case SN624X_REG_JACK_OUT_CHR_VOL: + case SN624X_REG_JACK_CAP_VOL: + case SN624X_REG_JACK_CAP_CHR_VOL: + case SN624X_REG_DMIC_CAP_VOL: + case SN624X_REG_DMIC_CAP_CHR_VOL: + return 2; + default: + return 1; + } +} + +static bool sn624x_sdca_volatile_register(struct device *dev, unsigned int reg) +{ + if (!SDW_SDCA_VALID_CTL(reg)) + return true; + + switch (reg) { + case SN624X_REG_PWR_STATE: + case SN624X_REG_JACK_OUT_PWR_STATE: + case SN624X_REG_JACK_CAP_PWR_STATE: + case SN624X_REG_DMIC_CAP_PWR_STATE: + case SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_GE35, + SN624X_SDCA_CTL_DETECTED_MODE, 0): + return true; + default: + if (SDW_SDCA_CTL_CSEL(reg) == SDCA_CTL_PDE_ACTUAL_PS) + return true; + return false; + } +} + +static const struct regmap_sdw_mbq_cfg sn624x_sdca_mbq_cfg = { + .mbq_size = sn624x_sdca_mbq_size, +}; + +static const struct regmap_config sn624x_sdca_regmap = { + .reg_bits = 32, + .val_bits = 16, + .volatile_reg = sn624x_sdca_volatile_register, + .max_register = 0xffffffff, + .cache_type = REGCACHE_MAPLE, + .use_single_read = true, + .use_single_write = true, +}; + +/* + * Prefer ACPI mipi-sdca-function-initialization-table (sdca_regmap_write_init). + * sdca_parse_function() loads that table before entity parsing, so a later + * DisCo parse failure can still leave fn->num_init_table usable. + */ +static int sn624x_write_sdca_init_table(struct device *dev, + struct sn624x_sdca_priv *sn624x, + struct sdca_function_data *fn, + const char *label) +{ + int ret; + + if (!fn || !fn->num_init_table) + return -ENOENT; + + ret = sdca_regmap_write_init(dev, sn624x->regmap, fn); + if (ret < 0) { + dev_warn(dev, + "sn624x: %s function-initialization-table write failed (%d)\n", + label, ret); + return ret; + } + + dev_info(dev, + "sn624x: applied %s function-initialization-table (%d writes)\n", + label, fn->num_init_table); + return 0; +} + +/* + * Temporary vendor-window fallback when ACPI has no init table. + * Validated on current platforms; remove once DisCo init tables are complete. + */ +static int sn624x_jack_oem_fallback_init(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + static const struct reg_sequence jack_oem_table[] = { + REG_SEQ0(SN624X_UAJ_CTL_IT33_MIC_BIAS, 0x06), + REG_SEQ0(SN624X_UAJ_CTL_IT31_MIC_BIAS, 0x06), + REG_SEQ0(SN624X_UAJ_CTL_FU41_CH1_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU41_CH2_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU31_CH0_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU32_CH0_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU33_CH0_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU36_CH1_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU36_CH2_MUTE, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU31_CH0_GAIN_HIGH, 0x12), + REG_SEQ0(SN624X_UAJ_CTL_FU31_CH0_GAIN_LOW, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU32_CH0_GAIN_HIGH, 0x0C), + REG_SEQ0(SN624X_UAJ_CTL_FU32_CH0_GAIN_LOW, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_FU33_CH0_GAIN_HIGH, 0x12), + REG_SEQ0(SN624X_UAJ_CTL_FU33_CH0_GAIN_LOW, 0x00), + REG_SEQ0(SN624X_UAJ_CTL_INPUT_DELAY_TIME, 0x0F), + REG_SEQ0(SN624X_UAJ_CTL_PORTA_CHARGE_PUMP, 0x0B), + }; + int ret; + + dev_warn_once(dev, + "sn624x: using temporary jack OEM init fallback (no ACPI init table)\n"); + ret = regmap_multi_reg_write_bypassed(sn624x->regmap, jack_oem_table, + ARRAY_SIZE(jack_oem_table)); + if (ret < 0) { + dev_warn(dev, "sn624x: jack OEM fallback failed (%d)\n", ret); + return ret; + } + usleep_range(1000, 1500); + return 0; +} + +static int sn624x_dmic_oem_fallback_init(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + static const struct reg_sequence dmic_oem_table[] = { + REG_SEQ0(0x00002041, 0x00), + REG_SEQ0(SN624X_UAJ_DMIC_PPU11, 0x30), + REG_SEQ0(0x00002105, 0x66), + REG_SEQ0(0x00002107, 0x26), + REG_SEQ0(0x00002109, 0x62), + REG_SEQ0(0x40800a09, 0x00), + REG_SEQ0(0x40800a0a, 0x00), + REG_SEQ0(0x40802a11, 0x00), + REG_SEQ0(0x40800a11, 0x00), + REG_SEQ0(0x40802a12, 0x00), + REG_SEQ0(0x40800a12, 0x00), + REG_SEQ0(0x40802a13, 0x00), + REG_SEQ0(0x40800a13, 0x00), + REG_SEQ0(0x40802a14, 0x00), + REG_SEQ0(0x40800a14, 0x00), + REG_SEQ0(0x408029d8, 0x0c), + REG_SEQ0(0x408009d8, 0x00), + REG_SEQ0(0x408029d9, 0x0c), + REG_SEQ0(0x408009d9, 0x00), + REG_SEQ0(0x00002140, 0x18), + REG_SEQ0(0x00002045, 0x01), + }; + int ret; + + dev_warn_once(dev, + "sn624x: using temporary dmic OEM init fallback (no ACPI init table)\n"); + ret = regmap_multi_reg_write_bypassed(sn624x->regmap, dmic_oem_table, + ARRAY_SIZE(dmic_oem_table)); + if (ret < 0) { + dev_warn(dev, "sn624x: dmic OEM fallback failed (%d)\n", ret); + return ret; + } + usleep_range(1000, 1500); + return 0; +} + +static void sn624x_uaj_apply_io_defaults(struct sn624x_sdca_priv *sn624x) +{ + struct device *dev = &sn624x->slave->dev; + int ret; + + ret = sn624x_write_sdca_init_table(dev, sn624x, sn624x->jack_func, + "jack"); + if (ret) + sn624x_jack_oem_fallback_init(dev, sn624x); +} + +static int sn624x_parse_sdca_functions(struct sn624x_sdca_priv *sn624x) +{ + struct sdw_slave *slave = sn624x->slave; + struct device *dev = &slave->dev; + int i, ret; + + if (!slave->sdca_data.num_functions) { + dev_dbg(dev, "sn624x: no SDCA function descriptors from DisCo\n"); + return 0; + } + + for (i = 0; i < slave->sdca_data.num_functions; i++) { + struct sdca_function_desc *desc = &slave->sdca_data.function[i]; + struct sdca_function_data *fn; + bool is_jack = false, is_mic = false, is_amp = false; + + switch (desc->type) { + case SDCA_FUNCTION_TYPE_UAJ: + case SDCA_FUNCTION_TYPE_SIMPLE_JACK: + case SDCA_FUNCTION_TYPE_RJ: + is_jack = true; + break; + case SDCA_FUNCTION_TYPE_SMART_MIC: + case SDCA_FUNCTION_TYPE_SIMPLE_MIC: + is_mic = true; + break; + case SDCA_FUNCTION_TYPE_SMART_AMP: + case SDCA_FUNCTION_TYPE_SIMPLE_AMP: + case SDCA_FUNCTION_TYPE_SPEAKER_MIC: + is_amp = true; + break; + default: + break; + } + + if (desc->adr == SN624X_FUNC_NUM_JACK_CODEC) + is_jack = true; + else if (desc->adr == SN624X_FUNC_NUM_MIC_ARRAY) + is_mic = true; + else if (desc->adr == SN624X_FUNC_NUM_SPEAKER_AMP) + is_amp = true; + + if (!is_jack && !is_mic && !is_amp) + continue; + + fn = devm_kzalloc(dev, sizeof(*fn), GFP_KERNEL); + if (!fn) + return -ENOMEM; + + fn->desc = desc; + ret = sdca_parse_function(dev, fn); + if (ret) { + /* + * sdca_parse_function() reads the ACPI + * mipi-sdca-function-initialization-table first, + * then parses entities/controls. On some platforms + * DisCo entity data is incomplete so the later + * steps fail (-EINVAL), but fn->init_table is + * already filled. Keep that partial function so + * io_init can still apply the ACPI table; if the + * table was never loaded, drop this function and + * let the OEM register fallback run instead. + */ + dev_warn(dev, + "sn624x: sdca_parse_function(%s adr=%u) failed (%d)%s\n", + desc->name ? desc->name : "?", desc->adr, ret, + fn->num_init_table ? + ", keeping ACPI init table" : ""); + if (!fn->num_init_table) + continue; + } else { + dev_dbg(dev, + "sn624x: parsed SDCA function %s type=%u adr=%u init_writes=%d\n", + desc->name ? desc->name : "?", desc->type, + desc->adr, fn->num_init_table); + } + + if (is_jack && !sn624x->jack_func) + sn624x->jack_func = fn; + else if (is_mic && !sn624x->mic_func) + sn624x->mic_func = fn; + else if (is_amp && !sn624x->amp_func) + sn624x->amp_func = fn; + } + + return 0; +} + +/* + * Poll-driven jack detect: pulse UAJ jack-detect, then apply GE35 + * DETECTED_MODE → SELECTED_MODE via sn624x_uaj_ge35_apply_detected_mode(). + */ +static int sn624x_sdca_headset_detect(struct sn624x_sdca_priv *sn624x) +{ + struct device *dev = &sn624x->slave->dev; + int ret; + + SN624X_DBG(dev, "headset_detect: pulse UAJ jack-detect, read GE35\n"); + ret = regmap_write(sn624x->regmap, + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, + SN624X_UAJ_ENT_BLOCK, + SN624X_UAJ_CTL_JACK_DETECT, 0), + 0x08); + if (ret < 0) + goto io_error; + + usleep_range(5000, 5500); + + ret = sn624x_uaj_ge35_apply_detected_mode(dev, sn624x); + + /* Clear the detect pulse even if GE35 apply failed. */ + regmap_write(sn624x->regmap, + SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_UAJ_ENT_BLOCK, + SN624X_UAJ_CTL_JACK_DETECT, 0), + 0x00); + + if (ret < 0) + goto io_error; + + SN624X_DBG(dev, "headset_detect: jack_type=0x%x\n", sn624x->jack_type); + return 0; + +io_error: + if (ret == -ENODATA) + SN624X_DBG(dev, "%s: UAJ/GE35 access ignored (-ENODATA)\n", + __func__); + else + dev_err_ratelimited(dev, "%s failed (%d)\n", __func__, ret); + return ret; +} + +static void sn624x_sdca_sdca_irq_mask_all(struct sn624x_sdca_priv *sn624x) +{ + if (!sn624x->slave) + return; + + sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK1, 0); + sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK2, 0); + sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK3, 0); + sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK4, 0); + SN624X_DBG(&sn624x->slave->dev, + "SDCA INTMASK1-4 = 0 (all SDCA sources masked)\n"); +} + +static void sn624x_sdca_jack_irq_unmask(struct sn624x_sdca_priv *sn624x) +{ + /* Jack detection is poll-driven; SDCA jack IRQs remain masked. */ + (void)sn624x; +} + +static void sn624x_sdca_jack_irq_mask(struct sn624x_sdca_priv *sn624x) +{ + if (!sn624x->slave) + return; + + sn624x_sdca_sdca_irq_mask_all(sn624x); +} + +/* + * Hold a runtime PM reference for the lifetime of jack detection so the + * device cannot autosuspend while the poll worker needs register access. + */ +static int sn624x_jack_rpm_get(struct sn624x_sdca_priv *sn624x) +{ + struct device *dev; + int ret; + + if (!sn624x) + return -EINVAL; + if (sn624x->jack_rpm) + return 0; + if (!sn624x->component) + return -ENODEV; + + dev = sn624x->component->dev; + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + if (ret != -EACCES) { + dev_err(dev, "sn624x: jack rpm get failed (%d)\n", ret); + return ret; + } + /* pm_runtime not enabled yet (before first ATTACHED io_init) */ + return 0; + } + + sn624x->jack_rpm = true; + return 0; +} + +static void sn624x_jack_rpm_put(struct sn624x_sdca_priv *sn624x) +{ + if (!sn624x || !sn624x->jack_rpm || !sn624x->component) + return; + + pm_runtime_mark_last_busy(sn624x->component->dev); + pm_runtime_put_autosuspend(sn624x->component->dev); + sn624x->jack_rpm = false; +} + +static void sn624x_jack_schedule_poll(struct sn624x_sdca_priv *sn624x) +{ + if (!sn624x || !sn624x->hs_jack || !sn624x->slave) + return; + + mod_delayed_work(system_power_efficient_wq, &sn624x->jack_detect_work, + msecs_to_jiffies(SN624X_JACK_POLL_MS)); +} + +static void sn624x_sdca_jack_poll_and_report(struct sn624x_sdca_priv *sn624x) +{ + struct device *dev; + + if (!sn624x->hs_jack || !sn624x->slave) + return; + + dev = &sn624x->slave->dev; + if (sn624x_sdca_headset_detect(sn624x) < 0) + return; + + snd_soc_jack_report(sn624x->hs_jack, sn624x->jack_type, + SND_JACK_HEADSET | SND_JACK_HEADPHONE); + sn624x->jack_type_last = sn624x->jack_type; + dev_dbg(dev, "sn624x: jack polled: detected_mode -> jack_type=0x%x\n", + sn624x->jack_type); +} + +static void sn624x_sdca_jack_detect_handler(struct work_struct *work) +{ + struct sn624x_sdca_priv *sn624x = + container_of(work, struct sn624x_sdca_priv, jack_detect_work.work); + struct device *dev; + + if (!sn624x->slave) { + sn624x_jack_schedule_poll(sn624x); + return; + } + + dev = &sn624x->slave->dev; + + if (!sn624x->hs_jack) { + SN624X_DBG(dev, "jack_work: skip (no hs_jack)\n"); + goto out_reschedule; + } + + /* Registers are only valid after ATTACHED → io_init (hw_init). */ + if (!sn624x->hw_init) { + SN624X_DBG(dev, "jack_work: skip (hw_init not ready)\n"); + goto out_reschedule; + } + + /* Poll GE35 every interval; do not rely on SDCA_0 IRQ or PCM activity */ + sn624x_sdca_headset_detect(sn624x); + + if (sn624x->jack_type != sn624x->jack_type_last) { + dev_dbg(dev, + "sn624x: jack event: %s (jack_type=0x%x; HP=0x%x HS=0x%x)\n", + sn624x->jack_type ? "plug" : "unplug", + sn624x->jack_type, SND_JACK_HEADPHONE, SND_JACK_HEADSET); + sn624x->jack_type_last = sn624x->jack_type; + + snd_soc_jack_report(sn624x->hs_jack, sn624x->jack_type, + SND_JACK_HEADSET | SND_JACK_HEADPHONE); + } + +out_reschedule: + sn624x_jack_schedule_poll(sn624x); +} + +static void sn624x_sdca_jack_init(struct sn624x_sdca_priv *sn624x) +{ + /* No machine set_jack() yet — not "jack unsupported". */ + if (!sn624x->hs_jack) + return; + + sn624x_sdca_jack_irq_unmask(sn624x); + sn624x_sdca_jack_poll_and_report(sn624x); + sn624x_jack_schedule_poll(sn624x); + + dev_dbg(&sn624x->slave->dev, + "sn624x: jack_init: poll scheduled every %ums\n", SN624X_JACK_POLL_MS); +} + +static int sn624x_sdca_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(component); + int ret; + + dev_dbg(component->dev, + "sn624x: set_jack: hs_jack=%p first_hw_init=%d hw_init=%d\n", + hs_jack, sn624x->first_hw_init, sn624x->hw_init); + + if (!hs_jack) { + cancel_delayed_work_sync(&sn624x->jack_detect_work); + sn624x->hs_jack = NULL; + sn624x_jack_rpm_put(sn624x); + return 0; + } + + /* + * Component registration happens before runtime PM is enabled (RPM is + * enabled from io_init on first ATTACHED). Do not touch runtime PM + * until then, or get/put counts can go out of sync. + */ + if (!sn624x->first_hw_init) { + sn624x->hs_jack = hs_jack; + sn624x->jack_type_last = -1; + sn624x_jack_schedule_poll(sn624x); + return 0; + } + + /* + * Take the runtime PM reference before publishing hs_jack, so a + * failed get cannot leave jack detection marked as enabled. + */ + ret = sn624x_jack_rpm_get(sn624x); + if (ret < 0) + return ret; + + sn624x->hs_jack = hs_jack; + sn624x->jack_type_last = -1; + sn624x_sdca_jack_init(sn624x); + return 0; +} + +/* Prefer ACPI init table; OEM vendor-window fallback only if absent. */ +static int sn624x_jack_apply_defaults(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + int ret; + + if (!sn624x || !sn624x->regmap || !sn624x->slave) + return -ENODEV; + + ret = sn624x_write_sdca_init_table(dev, sn624x, sn624x->jack_func, + "jack"); + if (ret == 0) { + usleep_range(1000, 1500); + return 0; + } + + return sn624x_jack_oem_fallback_init(dev, sn624x); +} + +static int sn624x_dmic_apply_defaults(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + int ret; + + if (!sn624x || !sn624x->regmap || !sn624x->slave) + return -ENODEV; + + ret = sn624x_write_sdca_init_table(dev, sn624x, sn624x->mic_func, + "dmic"); + if (ret == 0) { + usleep_range(1000, 1500); + return 0; + } + + return sn624x_dmic_oem_fallback_init(dev, sn624x); +} + +static int sn624x_sdca_jack_function_init(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + unsigned int jack_func_status; + const struct reg_sequence clear_status = + REG_SEQ0(SN624X_REG_JACK_FUN_STATUS_CTL, 0x61); + int ret; + + ret = regmap_read_bypassed(sn624x->regmap, + SN624X_REG_JACK_FUN_STATUS_CTL, + &jack_func_status); + if (ret < 0) + return ret; + + if (!(jack_func_status & (SN624X_JACK_FUN_NEEDS_INIT | + SN624X_JACK_FUN_HAS_BEEN_RESET))) + return 0; + + ret = sn624x_jack_apply_defaults(dev, sn624x); + if (ret < 0) + return ret; + + ret = regmap_multi_reg_write_bypassed(sn624x->regmap, &clear_status, 1); + if (ret < 0) + return ret; + + usleep_range(1000, 1500); + return 0; +} + +static int sn624x_sdca_dmic_function_init(struct device *dev, + struct sn624x_sdca_priv *sn624x) +{ + unsigned int dmic_func_status; + const struct reg_sequence clear_status = + REG_SEQ0(SN624X_REG_DMIC_FUN_STATUS_CTL, 0x61); + int ret; + + ret = regmap_read_bypassed(sn624x->regmap, + SN624X_REG_DMIC_FUN_STATUS_CTL, + &dmic_func_status); + if (ret < 0) + return ret; + + if (!(dmic_func_status & (SN624X_JACK_FUN_NEEDS_INIT | + SN624X_JACK_FUN_HAS_BEEN_RESET))) + return 0; + + ret = sn624x_dmic_apply_defaults(dev, sn624x); + if (ret < 0) + return ret; + + ret = regmap_multi_reg_write_bypassed(sn624x->regmap, &clear_status, 1); + if (ret < 0) + return ret; + + usleep_range(1000, 1500); + return 0; +} + +static int sn624x_sdca_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + int ret; + + if (sn624x->hw_init) { + SN624X_DBG(dev, "io_init: skip (hw_init already true)\n"); + return 0; + } + + SN624X_DBG(dev, "io_init: start (first hardware bring-up)\n"); + sn624x->disable_irq = false; + + sn624x_sdca_sdca_irq_mask_all(sn624x); + + regcache_cache_only(sn624x->regmap, false); + if (!sn624x->first_hw_init) { + /* + * PM runtime is only enabled when a Slave reports as Attached + * (same pattern as rt722). + */ + pm_runtime_set_autosuspend_delay(dev, 3000); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_enable(dev); + } + + pm_runtime_get_noresume(dev); + + sn624x_uaj_apply_io_defaults(sn624x); + + /* + * set_jack() may have run before RPM was enabled (-EACCES). Take the + * long-lived jack PM ref now that runtime PM is active. + */ + if (sn624x->hs_jack) { + sn624x_jack_rpm_get(sn624x); + sn624x_sdca_jack_init(sn624x); + } + + ret = sn624x_sdca_jack_function_init(dev, sn624x); + if (ret < 0) { + dev_err(dev, "sn624x: jack function init failed (%d)\n", ret); + goto err; + } + + ret = sn624x_sdca_dmic_function_init(dev, sn624x); + if (ret < 0) { + dev_err(dev, "sn624x: dmic function init failed (%d)\n", ret); + goto err; + } + + /* + * Re-attach after bus reset: force a full cache write-back on the + * following resume/sync path (same idea as rt722). + */ + if (sn624x->first_hw_init) + regcache_mark_dirty(sn624x->regmap); + + sn624x->hw_init = true; + sn624x->first_hw_init = true; + + pm_runtime_put_autosuspend(dev); + + SN624X_DBG(dev, "io_init: complete (hw_init set), jack %s\n", + sn624x->hs_jack ? "registered" : "not registered yet"); + return 0; + +err: + pm_runtime_put_noidle(dev); + return ret; +} + +/* + * Bus lifecycle (same as rt722): enumerate → assign dev_num → ATTACHED → + * io_init(). Do not poll enumeration/init around each register access; after + * unattach, resume uses sdw_slave_wait_for_init() then regcache_sync. + */ +static int sn624x_sdca_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev); + bool need_io_init; + int ret; + + SN624X_DBG(&slave->dev, "update_status: %s hw_init=%d dev_num=%u\n", + sn624x_status_str(status), sn624x->hw_init, slave->dev_num); + + if (status == SDW_SLAVE_UNATTACHED) { + cancel_delayed_work_sync(&sn624x->jack_detect_work); + sn624x->hw_init = false; + SN624X_DBG(&slave->dev, + "slave UNATTACHED: cleared hw_init, jack work cancelled\n"); + } + + if (status != SDW_SLAVE_ATTACHED) + return 0; + + sn624x_sdca_sdca_irq_mask_all(sn624x); + + need_io_init = !sn624x->hw_init; + if (need_io_init) { + dev_notice(&slave->dev, "sn624x: update_status ATTACHED -> io_init()\n"); + ret = sn624x_sdca_io_init(&slave->dev, slave); + if (ret < 0) + return ret; + } else { + dev_dbg(&slave->dev, + "sn624x: update_status ATTACHED: io_init already done\n"); + } + + /* Re-ATTACHED without io_init: restore jack detect after bus is up. */ + if (sn624x->hs_jack && sn624x->hw_init && !need_io_init) { + sn624x_sdca_jack_irq_unmask(sn624x); + sn624x_sdca_jack_poll_and_report(sn624x); + sn624x_jack_schedule_poll(sn624x); + } + + return 0; +} + +static int sn624x_sdca_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + struct sdw_dpn_prop *dpn; + unsigned long addr; + unsigned int bit; + int nval; + int i, j; + + sdw_slave_read_lane_mapping(slave); + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + prop->paging_support = true; + prop->use_domain_irq = true; + prop->wake_capable = true; + + prop->source_ports = BIT(SN624X_PORT_JACK_CAPTURE) | + BIT(SN624X_PORT_DMIC_CAPTURE); + prop->sink_ports = BIT(SN624X_PORT_JACK_PLAYBACK) | + BIT(SN624X_PORT_SPEAKER_PLAYBACK); + + nval = hweight32(prop->source_ports); + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + i = 0; + dpn = prop->src_dpn_prop; + addr = prop->source_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].type = SDW_DPN_FULL; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + nval = hweight32(prop->sink_ports); + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + j = 0; + dpn = prop->sink_dpn_prop; + addr = prop->sink_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[j].num = bit; + dpn[j].type = SDW_DPN_FULL; + dpn[j].simple_ch_prep_sm = true; + dpn[j].ch_prep_timeout = 10; + j++; + } + prop->clk_stop_timeout = 900; + prop->simple_clk_stop_capable = true; + prop->lane_control_support = true; + SN624X_DBG(&slave->dev, + "read_prop: src_ports=0x%x sink_ports=0x%x lane irq paging\n", + prop->source_ports, prop->sink_ports); + + return 0; +} + +/* + * Clear SDCA INT1/2 only. DP0_INT standard fields (PORT_READY, etc.) are + * owned by the SoundWire bus core; cascade clears when SDCA status is clear. + * Pending SDCA IRQs can block clock-stop prepare (CLK_STP_NF -> -ETIMEDOUT). + */ +static int sn624x_sdca_flush_pending_irqs(struct sn624x_sdca_priv *sn624x, + struct device *dev) +{ + int ret, stat = 0; + int count = 0; + const int retry = 3; + unsigned int scp_sdca_stat1, scp_sdca_stat2 = 0; + + if (!sn624x || !sn624x->slave) + return 0; + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1); + if (ret < 0) + return ret; + sn624x->scp_sdca_stat1 = ret; + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2); + if (ret < 0) + return ret; + sn624x->scp_sdca_stat2 = ret; + + do { + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1); + if (ret < 0) + return ret; + if (ret & SDW_SCP_SDCA_INTMASK_SDCA_0) { + ret = sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1, + SDW_SCP_SDCA_INTMASK_SDCA_0); + if (ret < 0) + return ret; + } + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2); + if (ret < 0) + return ret; + if (ret & SDW_SCP_SDCA_INTMASK_SDCA_8) { + ret = sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2, + SDW_SCP_SDCA_INTMASK_SDCA_8); + if (ret < 0) + return ret; + } + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1); + if (ret < 0) + return ret; + scp_sdca_stat1 = ret & SDW_SCP_SDCA_INTMASK_SDCA_0; + + ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2); + if (ret < 0) + return ret; + scp_sdca_stat2 = ret & SDW_SCP_SDCA_INTMASK_SDCA_8; + + stat = scp_sdca_stat1 || scp_sdca_stat2; + count++; + } while (stat != 0 && count < retry); + + if (stat && dev) + dev_warn(dev, + "sn624x: SDCA IRQ flush incomplete (stat=%u)\n", stat); + + return stat ? -EBUSY : 0; +} + +static int sn624x_sdca_interrupt_callback(struct sdw_slave *slave, + struct sdw_slave_intr_status *status) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev); + int ret; + + if (!sn624x) + return 0; + + /* + * Bus expects the codec to clear SCP SDCA INT1/2 when cascade is set. + * Leaving them set can stall alert handling. + */ + + SN624X_DBG(&slave->dev, + "irq: control_port=%#x sdca_cascade=%u disable_irq=%d\n", + status->control_port, status->sdca_cascade, sn624x->disable_irq); + + cancel_delayed_work_sync(&sn624x->jack_detect_work); + + mutex_lock(&sn624x->disable_irq_lock); + + ret = sn624x_sdca_flush_pending_irqs(sn624x, &slave->dev); + if (ret < 0 && ret != -EBUSY) + goto io_error; + + SN624X_DBG(&slave->dev, "irq: INT1=0x%x INT2=0x%x\n", + sn624x->scp_sdca_stat1, sn624x->scp_sdca_stat2); + + /* + * Always restart the jack poll after IRQ handling so a cancel above + * cannot stop detection. SDCA INTMASK stays masked; any residual SCP + * IRQ still triggers an early poll. The poll itself does the UAJ + * pulse + GE35 read. + */ + if (!sn624x->disable_irq) + sn624x_jack_schedule_poll(sn624x); + + mutex_unlock(&sn624x->disable_irq_lock); + return 0; + +io_error: + mutex_unlock(&sn624x->disable_irq_lock); + dev_err_ratelimited(&slave->dev, "%s: IO error (%d)\n", __func__, ret); + return ret; +} + +static const struct sdw_slave_ops sn624x_sdca_slave_ops = { + .read_prop = sn624x_sdca_read_prop, + .interrupt_callback = sn624x_sdca_interrupt_callback, + .update_status = sn624x_sdca_update_status, +}; + +static int sn624x_sdca_probe(struct snd_soc_component *component) +{ + struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(component); + int ret; + + sn624x->component = component; + + if (!sn624x->first_hw_init) + return 0; + + ret = pm_runtime_resume(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + return 0; +} + +static int sn624x_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config = {}; + struct sdw_port_config port_config = {}; + struct sdw_stream_runtime *sdw_stream; + unsigned int sampling_rate; + int port; + int ret; + + dev_dbg(dai->dev, + "sn624x: hw_params: entered dai=%s id=%d stream=%s\n", + dai->name, dai->id, snd_pcm_stream_str(substream)); + + sdw_stream = snd_soc_dai_get_dma_data(dai, substream); + if (!sdw_stream) { + dev_warn(dai->dev, + "sn624x: hw_params: no SDW stream (set_stream not run yet?)\n"); + return -EINVAL; + } + if (!sn624x->slave) { + dev_warn(dai->dev, "sn624x: hw_params: slave NULL\n"); + return -EINVAL; + } + + ret = pm_runtime_resume(component->dev); + if (ret < 0 && ret != -EACCES) { + dev_err(dai->dev, + "sn624x: hw_params: pm_runtime_resume failed (%d)\n", ret); + return ret; + } + + snd_sdw_params_to_config(substream, params, &stream_config, &port_config); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (dai->id == SN624X_DAI_JACK) + port = SN624X_PORT_JACK_PLAYBACK; + else if (dai->id == SN624X_DAI_SPEAKER) + port = SN624X_PORT_SPEAKER_PLAYBACK; + else + return -EINVAL; + } else { + if (dai->id == SN624X_DAI_JACK) + port = SN624X_PORT_JACK_CAPTURE; + else if (dai->id == SN624X_DAI_DMIC) + port = SN624X_PORT_DMIC_CAPTURE; + else + return -EINVAL; + } + port_config.num = port; + + ret = sdw_stream_add_slave(sn624x->slave, &stream_config, + &port_config, 1, sdw_stream); + if (ret) { + dev_err(dai->dev, "%s: sdw_stream_add_slave port %d failed: %d\n", + __func__, port, ret); + return ret; + } + + /* SDCA SampleFreqIndex (same encoding as other SDCA codecs) */ + switch (params_rate(params)) { + case 44100: + sampling_rate = SN624X_SDCA_RATE_44100HZ; + break; + case 48000: + sampling_rate = SN624X_SDCA_RATE_48000HZ; + break; + case 96000: + sampling_rate = SN624X_SDCA_RATE_96000HZ; + break; + case 192000: + sampling_rate = SN624X_SDCA_RATE_192000HZ; + break; + default: + dev_err(dai->dev, "%s: Rate %d is not supported\n", + __func__, params_rate(params)); + return -EINVAL; + } + + if (dai->id == SN624X_DAI_JACK) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = regmap_write(sn624x->regmap, + SN624X_REG_JACK_OUT_RATE_SEL, + sampling_rate); + else + ret = regmap_write(sn624x->regmap, + SN624X_REG_JACK_CAP_RATE_SEL, + sampling_rate); + } else if (dai->id == SN624X_DAI_SPEAKER) { + ret = regmap_write(sn624x->regmap, SN624X_REG_RATE_SEL, + sampling_rate); + } else if (dai->id == SN624X_DAI_DMIC) { + ret = regmap_write(sn624x->regmap, SN624X_REG_DMIC_CAP_RATE_SEL, + sampling_rate); + } else { + return -EINVAL; + } + + return ret; +} + +static int sn624x_sdca_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(dai->component); + struct sdw_stream_runtime *sdw_stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!sn624x->slave) + return -EINVAL; + + return sdw_stream_remove_slave(sn624x->slave, sdw_stream); +} + +static int sn624x_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + snd_soc_dai_dma_data_set(dai, direction, sdw_stream); + return 0; +} + +static void sn624x_sdca_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + +static const struct snd_soc_dapm_widget sn624x_sdca_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("RX", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX", "Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SPK RX", "Speaker Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DMIC TX", "DMic Capture", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SUPPLY("PDE HP", SND_SOC_NOPM, 0, 0, + sn624x_pde_hp_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE SPK", SND_SOC_NOPM, 0, 0, + sn624x_pde_spk_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE MIC2", SND_SOC_NOPM, 0, 0, + sn624x_pde_mic2_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE DMIC", SND_SOC_NOPM, 0, 0, + sn624x_pde_dmic_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_DAC_E("FU HP", NULL, SND_SOC_NOPM, 0, 0, + sn624x_fu_hp_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC_E("FU SPK", NULL, SND_SOC_NOPM, 0, 0, + sn624x_fu_spk_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("FU MIC2", NULL, SND_SOC_NOPM, 0, 0, + sn624x_fu_mic2_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("FU DMIC", NULL, SND_SOC_NOPM, 0, 0, + sn624x_fu_dmic_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_OUTPUT("SPK"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("DMIC"), +}; + +static const struct snd_soc_dapm_route sn624x_sdca_audio_map[] = { + { "FU HP", NULL, "RX" }, + { "FU HP", NULL, "PDE HP" }, + { "HP", NULL, "FU HP" }, + + { "FU SPK", NULL, "SPK RX" }, + { "FU SPK", NULL, "PDE SPK" }, + { "SPK", NULL, "FU SPK" }, + + { "FU MIC2", NULL, "MIC2" }, + { "FU MIC2", NULL, "PDE MIC2" }, + /* UAJ headset capture also needs jack PDE03 (shared with HP) */ + { "FU MIC2", NULL, "PDE HP" }, + { "TX", NULL, "FU MIC2" }, + + { "FU DMIC", NULL, "DMIC" }, + { "FU DMIC", NULL, "PDE DMIC" }, + { "DMIC TX", NULL, "FU DMIC" }, +}; + +static const struct snd_soc_dai_ops sn624x_sdca_ops = { + .hw_params = sn624x_sdca_pcm_hw_params, + .hw_free = sn624x_sdca_pcm_hw_free, + .set_stream = sn624x_sdca_set_sdw_stream, + .shutdown = sn624x_sdca_shutdown, +}; + +static struct snd_soc_dai_driver sn624x_sdca_dai[] = { + { + .name = "sn624x-sdca-aif", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SN624X_STEREO_RATES, + .formats = SN624X_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SN624X_STEREO_RATES, + .formats = SN624X_FORMATS, + }, + .ops = &sn624x_sdca_ops, + }, + { + .name = "sn624x-sdca-aif2", + .id = 1, + .playback = { + .stream_name = "Speaker Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SN624X_STEREO_RATES, + .formats = SN624X_FORMATS, + }, + .ops = &sn624x_sdca_ops, + }, + { + .name = "sn624x-sdca-aif3", + .id = 2, + .capture = { + .stream_name = "DMic Capture", + .channels_min = 1, + .channels_max = 4, + .rates = SN624X_STEREO_RATES, + .formats = SN624X_FORMATS, + }, + .ops = &sn624x_sdca_ops, + }, +}; + +static const struct snd_soc_component_driver soc_sdca_dev_sn624x = { + .probe = sn624x_sdca_probe, + .dapm_widgets = sn624x_sdca_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sn624x_sdca_dapm_widgets), + .dapm_routes = sn624x_sdca_audio_map, + .num_dapm_routes = ARRAY_SIZE(sn624x_sdca_audio_map), + .set_jack = sn624x_sdca_set_jack_detect, + .endianness = 1, +}; + +int sn624x_sdca_init(struct device *dev, struct regmap *regmap, + struct sdw_slave *slave) +{ + struct sn624x_sdca_priv *sn624x; + int ret; + + sn624x = devm_kzalloc(dev, sizeof(*sn624x), GFP_KERNEL); + if (!sn624x) + return -ENOMEM; + + dev_set_drvdata(dev, sn624x); + sn624x->slave = slave; + sn624x->regmap = regmap; + + ret = sn624x_parse_sdca_functions(sn624x); + if (ret < 0) + return ret; + + mutex_init(&sn624x->disable_irq_lock); + INIT_DELAYED_WORK(&sn624x->jack_detect_work, sn624x_sdca_jack_detect_handler); + + regcache_cache_only(sn624x->regmap, true); + + ret = devm_snd_soc_register_component(dev, + &soc_sdca_dev_sn624x, sn624x_sdca_dai, + ARRAY_SIZE(sn624x_sdca_dai)); + if (ret < 0) + return ret; + + /* + * Do not enable runtime PM here. SoundWire slaves are only powered / + * accessible after ATTACHED; enable RPM from io_init() then. + */ + SN624X_DBG(dev, "snd_soc_register_component OK (DAI sn624x-sdca-aif)\n"); + return 0; +} + +static int sn624x_sdca_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *regmap; + int ret; + + SN624X_DBG(&slave->dev, "sdw_probe: dev_num=%u status=%s\n", + slave->dev_num, sn624x_status_str(slave->status)); + + 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); + } + + ret = sn624x_sdca_init(&slave->dev, regmap, slave); + if (ret < 0) + dev_err(&slave->dev, "sn624x: sn624x_sdca_init failed (%d)\n", ret); + else { + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev); + + if (sn624x) + sn624x_sdca_sdca_irq_mask_all(sn624x); + SN624X_DBG(&slave->dev, "SoundWire probe finished OK\n"); + } + + return ret; +} + +static void sn624x_sdca_sdw_remove(struct sdw_slave *slave) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev); + + SN624X_DBG(&slave->dev, "SoundWire driver remove\n"); + cancel_delayed_work_sync(&sn624x->jack_detect_work); + sn624x_jack_rpm_put(sn624x); + if (sn624x->first_hw_init) + pm_runtime_disable(&slave->dev); + mutex_destroy(&sn624x->disable_irq_lock); +} + +static const struct sdw_device_id sn624x_sdca_id[] = { + SDW_SLAVE_ENTRY_EXT(0x0496, 0x6242, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x0496, 0x6244, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x0496, 0x6247, 0x3, 0x1, 0), + { } +}; +MODULE_DEVICE_TABLE(sdw, sn624x_sdca_id); + +static int sn624x_sdca_dev_suspend(struct device *dev) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + + if (!sn624x->first_hw_init) + return 0; + + regcache_cache_only(sn624x->regmap, true); + /* + * Suspend / clock-stop may reset the peripheral. Mark the cache dirty + * so resume's regcache_sync restores software state to hardware. + */ + regcache_mark_dirty(sn624x->regmap); + + return 0; +} + +static int sn624x_sdca_dev_system_suspend(struct device *dev) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + + if (!sn624x->first_hw_init) + return 0; + + /* Mask first so IRQ cannot re-schedule work between cancel and disable. */ + mutex_lock(&sn624x->disable_irq_lock); + sn624x->disable_irq = true; + sn624x_sdca_jack_irq_mask(sn624x); + mutex_unlock(&sn624x->disable_irq_lock); + + cancel_delayed_work_sync(&sn624x->jack_detect_work); + + return sn624x_sdca_dev_suspend(dev); +} + +static int sn624x_sdca_regmap_resume(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + int ret; + + if (!sn624x->first_hw_init) + return 0; + + SN624X_DBG(dev, "resume: unattach_request=%u\n", slave->unattach_request); + if (slave->unattach_request) { + ret = sdw_slave_wait_for_init(slave, SN624X_PROBE_TIMEOUT_MS); + if (ret) { + sdw_show_ping_status(slave->bus, true); + return ret; + } + } + + regcache_cache_only(sn624x->regmap, false); + regcache_sync(sn624x->regmap); + + return 0; +} + +static int sn624x_sdca_dev_resume(struct device *dev) +{ + return sn624x_sdca_regmap_resume(dev); +} + +static int sn624x_sdca_dev_system_resume(struct device *dev) +{ + struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev); + int ret; + + ret = sn624x_sdca_regmap_resume(dev); + if (ret < 0) + return ret; + + mutex_lock(&sn624x->disable_irq_lock); + if (sn624x->disable_irq) { + sn624x_sdca_jack_irq_unmask(sn624x); + sn624x->disable_irq = false; + } + mutex_unlock(&sn624x->disable_irq_lock); + + if (sn624x->hs_jack && sn624x->hw_init) { + sn624x_sdca_jack_poll_and_report(sn624x); + sn624x_jack_schedule_poll(sn624x); + } + + return 0; +} + +static const struct dev_pm_ops sn624x_sdca_pm = { + SYSTEM_SLEEP_PM_OPS(sn624x_sdca_dev_system_suspend, sn624x_sdca_dev_system_resume) + RUNTIME_PM_OPS(sn624x_sdca_dev_suspend, sn624x_sdca_dev_resume, NULL) +}; + +static struct sdw_driver sn624x_sdca_sdw_driver = { + .driver = { + .name = "sn624x-sdca", + .pm = pm_ptr(&sn624x_sdca_pm), + }, + .probe = sn624x_sdca_sdw_probe, + .remove = sn624x_sdca_sdw_remove, + .ops = &sn624x_sdca_slave_ops, + .id_table = sn624x_sdca_id, +}; +module_sdw_driver(sn624x_sdca_sdw_driver); + +MODULE_DESCRIPTION("ASoC SN624X SDCA SoundWire driver with UAJ jack support"); +MODULE_AUTHOR("Senary Semiconductor Corp."); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("SND_SOC_SDCA"); +MODULE_SOFTDEP("pre: snd-soc-sdca"); diff --git a/sound/soc/codecs/sn624x-sdca.h b/sound/soc/codecs/sn624x-sdca.h new file mode 100644 index 000000000000..6eb588ba2b3b --- /dev/null +++ b/sound/soc/codecs/sn624x-sdca.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * sn624x-sdca.h -- SN624X SDCA ALSA SoC audio driver header + * + * Copyright(c) 2025 Senary Semiconductor Corp. + */ + +#ifndef __SN624X_H__ +#define __SN624X_H__ + +#include +#include +#include +#include +#include + +struct sn624x_sdca_priv { + struct regmap *regmap; + struct snd_soc_component *component; + struct sdw_slave *slave; + /* Parsed DisCo functions (init tables / defaults from ACPI) */ + struct sdca_function_data *jack_func; + struct sdca_function_data *mic_func; + struct sdca_function_data *amp_func; + bool hw_init; + bool first_hw_init; + struct snd_soc_jack *hs_jack; + struct delayed_work jack_detect_work; + struct mutex disable_irq_lock; + bool disable_irq; + /* Long-lived runtime PM ref while jack detection is enabled */ + bool jack_rpm; + int jack_type; + int jack_type_last; + unsigned int scp_sdca_stat1; + unsigned int scp_sdca_stat2; +}; + +/* SoundWire data port numbers (slave prop source/sink port masks) */ +#define SN624X_PORT_JACK_PLAYBACK 1 +#define SN624X_PORT_SPEAKER_PLAYBACK 2 +#define SN624X_PORT_JACK_CAPTURE 4 +#define SN624X_PORT_DMIC_CAPTURE 5 + +/* Order matches sn624x_sdca_dai[] .id */ +enum sn624x_dai_id { + SN624X_DAI_JACK = 0, + SN624X_DAI_SPEAKER = 1, + SN624X_DAI_DMIC = 2, +}; + +/* + * SDCA Function Numbers (from platform ACPI / device topology). + * Address with SDW_SDCA_CTL(fun, ent, ctl, ch) from sdw_registers.h. + */ +#define SN624X_FUNC_NUM_JACK_CODEC 0x01 +#define SN624X_FUNC_NUM_MIC_ARRAY 0x02 +#define SN624X_FUNC_NUM_SPEAKER_AMP 0x04 + +/* Entity instance IDs (from platform ACPI / SN624X topology) */ +#define SN624X_SDCA_ENT_ENTITY0 0x00 +#define SN624X_SDCA_ENT_CS01 0x01 +#define SN624X_SDCA_ENT_CS02 0x02 +#define SN624X_SDCA_ENT_PDE03 0x03 +#define SN624X_SDCA_ENT_PDE04 0x04 +#define SN624X_SDCA_ENT_CRU05 0x05 +#define SN624X_SDCA_ENT_USER_FU10 0x10 +#define SN624X_SDCA_ENT_USER_FU14 0x14 +#define SN624X_SDCA_ENT_USER_FU1E 0x1e +#define SN624X_UAJ_ENT_BLOCK 0x20 +#define SN624X_SDCA_ENT_GE35 0x1F +/* UAJ power domains from ssdt26.dsl AF01 entity list (PDE 34 / PDE 47) */ +#define SN624X_UAJ_ENT_PDE47 SN624X_SDCA_ENT_PDE03 +#define SN624X_UAJ_ENT_PDE34 SN624X_SDCA_ENT_PDE04 + +/* Control selectors (SDCA; same values as sound/sdca_function.h) */ +/* PDE power state selectors — use standard SDCA defines from sdca_function.h */ +#define SN624X_SDCA_CTL_FU_MUTE 0x01 +#define SN624X_SDCA_CTL_FU_VOLUME 0x02 +#define SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX 0x10 +#define SN624X_SDCA_CTL_CLUSTER_INDEX 0x10 +#define SN624X_SDCA_CTL_FUNCTION_STATUS 0x10 /* Entity 0 */ +#define SN624X_SDCA_CTL_SELECTED_MODE 0x01 +#define SN624X_SDCA_CTL_DETECTED_MODE 0x02 + +/* + * Control Number / channel for multi-channel FU controls (mute/volume). + * Single-value controls (PDE power, rate, function status, …) pass 0 + * directly to SDW_SDCA_CTL — no CH_0 alias. + */ +#define SN624X_SDCA_CH_L 0x01 +#define SN624X_SDCA_CH_R 0x02 + +/* GE35 Selected_Mode / Detected_Mode (SDCA Table 153, UAJ §5.5) */ +#define SN624X_GE35_MODE_HEADPHONE 0x03 +#define SN624X_GE35_MODE_HEADSET 0x05 + +/* Vendor UAJ SCP windows */ +#define SN624X_UAJ_CTL_INPUT_DELAY 0x30 +#define SN624X_UAJ_CTL_CHARGE_PUMP 0x31 +#define SN624X_UAJ_CTL_JACK_DETECT 0x32 + +/* + * Vendor SCP addresses used only when ACPI + * mipi-sdca-function-initialization-table is missing (temporary fallback). + * Remove once platform DisCo ships a complete init table. + */ +#define SN624X_UAJ_CTL_IT33_MIC_BIAS 0x40400418 +#define SN624X_UAJ_CTL_IT31_MIC_BIAS 0x40400318 +#define SN624X_UAJ_CTL_FU41_CH1_MUTE 0x40400A09 +#define SN624X_UAJ_CTL_FU41_CH2_MUTE 0x40400A0A +#define SN624X_UAJ_CTL_FU31_CH0_MUTE 0x40400C08 +#define SN624X_UAJ_CTL_FU32_CH0_MUTE 0x40400C88 +#define SN624X_UAJ_CTL_FU33_CH0_MUTE 0x40400D08 +#define SN624X_UAJ_CTL_FU36_CH1_MUTE 0x40400F09 +#define SN624X_UAJ_CTL_FU36_CH2_MUTE 0x40400F0A +#define SN624X_UAJ_CTL_FU31_CH0_GAIN_HIGH 0x40402C58 +#define SN624X_UAJ_CTL_FU31_CH0_GAIN_LOW 0x40400C58 +#define SN624X_UAJ_CTL_FU32_CH0_GAIN_HIGH 0x40402CD8 +#define SN624X_UAJ_CTL_FU32_CH0_GAIN_LOW 0x40400CD8 +#define SN624X_UAJ_CTL_FU33_CH0_GAIN_HIGH 0x40402D58 +#define SN624X_UAJ_CTL_FU33_CH0_GAIN_LOW 0x40400D58 +#define SN624X_UAJ_CTL_INPUT_DELAY_TIME 0x00002124 +#define SN624X_UAJ_CTL_PORTA_CHARGE_PUMP 0x00002086 +#define SN624X_UAJ_DMIC_PPU11 0x40880380 + +/* Jack poll interval (ms) */ +#define SN624X_JACK_POLL_MS 1000 + +/* µs settle times for headphone <-> speaker route switch during active PCM */ +#define SN624X_ROUTE_AFTER_MUTE_US_MIN 5000 +#define SN624X_ROUTE_AFTER_MUTE_US_MAX 8000 +#define SN624X_ROUTE_AFTER_D3_US_MIN 10000 +#define SN624X_ROUTE_AFTER_D3_US_MAX 15000 +#define SN624X_ROUTE_BEFORE_ACTIVE_US_MIN 10000 +#define SN624X_ROUTE_BEFORE_ACTIVE_US_MAX 15000 + +/* SDCA SampleFreqIndex (same encoding as other SDCA codecs, e.g. rt722) */ +#define SN624X_SDCA_RATE_44100HZ 0x08 +#define SN624X_SDCA_RATE_48000HZ 0x09 +#define SN624X_SDCA_RATE_96000HZ 0x0b +#define SN624X_SDCA_RATE_192000HZ 0x0d + +#define SN624X_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) +#define SN624X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +int sn624x_sdca_init(struct device *dev, struct regmap *regmap, + struct sdw_slave *slave); + +#endif /* __SN624X_H__ */ diff --git a/sound/soc/codecs/src4xxx.c b/sound/soc/codecs/src4xxx.c index 5a3489475225..6e187c930372 100644 --- a/sound/soc/codecs/src4xxx.c +++ b/sound/soc/codecs/src4xxx.c @@ -326,10 +326,18 @@ static int src4xxx_hw_params(struct snd_pcm_substream *substream, return 0; }; +static const u64 src4xxx_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops src4xxx_dai_ops = { .hw_params = src4xxx_hw_params, .set_sysclk = src4xxx_set_mclk_hz, .set_fmt = src4xxx_set_dai_fmt, + .auto_selectable_formats = &src4xxx_selectable_formats, + .num_auto_selectable_formats = 1, }; #define SRC4XXX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) diff --git a/sound/soc/codecs/ssm2518.c b/sound/soc/codecs/ssm2518.c index 5192569ba6a8..32e81b31479d 100644 --- a/sound/soc/codecs/ssm2518.c +++ b/sound/soc/codecs/ssm2518.c @@ -620,12 +620,25 @@ static int ssm2518_startup(struct snd_pcm_substream *substream, #define SSM2518_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32) +static const u64 ssm2518_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssm2518_dai_ops = { .startup = ssm2518_startup, .hw_params = ssm2518_hw_params, .mute_stream = ssm2518_mute, .set_fmt = ssm2518_set_dai_fmt, .set_tdm_slot = ssm2518_set_tdm_slot, + .auto_selectable_formats = &ssm2518_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index fccd2eacd7a6..92df44f86915 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -523,12 +523,25 @@ static int ssm2602_set_bias_level(struct snd_soc_component *component, #define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 ssm2602_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssm2602_dai_ops = { .startup = ssm2602_startup, .hw_params = ssm2602_hw_params, .mute_stream = ssm2602_mute, .set_sysclk = ssm2602_set_dai_sysclk, .set_fmt = ssm2602_set_dai_fmt, + .auto_selectable_formats = &ssm2602_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/ssm3515.c b/sound/soc/codecs/ssm3515.c index 8c6665677a17..f71509237fca 100644 --- a/sound/soc/codecs/ssm3515.c +++ b/sound/soc/codecs/ssm3515.c @@ -356,12 +356,22 @@ static int ssm3515_hw_free(struct snd_pcm_substream *substream, return 0; } +static const u64 ssm3515_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssm3515_dai_ops = { .mute_stream = ssm3515_mute, .hw_params = ssm3515_hw_params, .set_fmt = ssm3515_set_fmt, .set_tdm_slot = ssm3515_set_tdm_slot, .hw_free = ssm3515_hw_free, + .auto_selectable_formats = &ssm3515_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ssm3515_dai_driver = { diff --git a/sound/soc/codecs/ssm4567.c b/sound/soc/codecs/ssm4567.c index 8415dd163edd..181047b38d3d 100644 --- a/sound/soc/codecs/ssm4567.c +++ b/sound/soc/codecs/ssm4567.c @@ -389,11 +389,24 @@ static int ssm4567_set_bias_level(struct snd_soc_component *component, return ret; } +static const u64 ssm4567_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_PDM | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssm4567_dai_ops = { .hw_params = ssm4567_hw_params, .mute_stream = ssm4567_mute, .set_fmt = ssm4567_set_dai_fmt, .set_tdm_slot = ssm4567_set_tdm_slot, + .auto_selectable_formats = &ssm4567_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c index d6de739212f9..773a9b203456 100644 --- a/sound/soc/codecs/sta32x.c +++ b/sound/soc/codecs/sta32x.c @@ -342,27 +342,48 @@ static int sta32x_sync_coef_shadow(struct snd_soc_component *component) { struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); unsigned int cfud; - int i; + int i, ret; /* preserve reserved bits in STA32X_CFUD */ - regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); + ret = regmap_read(sta32x->regmap, STA32X_CFUD, &cfud); + if (ret) + return ret; + cfud &= 0xf0; for (i = 0; i < STA32X_COEF_COUNT; i++) { - regmap_write(sta32x->regmap, STA32X_CFADDR2, i); - regmap_write(sta32x->regmap, STA32X_B1CF1, - (sta32x->coef_shadow[i] >> 16) & 0xff); - regmap_write(sta32x->regmap, STA32X_B1CF2, - (sta32x->coef_shadow[i] >> 8) & 0xff); - regmap_write(sta32x->regmap, STA32X_B1CF3, - (sta32x->coef_shadow[i]) & 0xff); + ret = regmap_write(sta32x->regmap, STA32X_CFADDR2, i); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_B1CF1, + (sta32x->coef_shadow[i] >> 16) & 0xff); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_B1CF2, + (sta32x->coef_shadow[i] >> 8) & 0xff); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_B1CF3, + sta32x->coef_shadow[i] & 0xff); + if (ret) + return ret; + /* * chip documentation does not say if the bits are * self-clearing, so do it explicitly */ - regmap_write(sta32x->regmap, STA32X_CFUD, cfud); - regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x01); + ret = regmap_write(sta32x->regmap, STA32X_CFUD, cfud); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_CFUD, cfud | 0x01); + if (ret) + return ret; } + return 0; } @@ -370,15 +391,24 @@ static int sta32x_cache_sync(struct snd_soc_component *component) { struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); unsigned int mute; - int rc; + int restore_ret, ret; /* mute during register sync */ - regmap_read(sta32x->regmap, STA32X_MMUTE, &mute); - regmap_write(sta32x->regmap, STA32X_MMUTE, mute | STA32X_MMUTE_MMUTE); - sta32x_sync_coef_shadow(component); - rc = regcache_sync(sta32x->regmap); - regmap_write(sta32x->regmap, STA32X_MMUTE, mute); - return rc; + ret = regmap_read(sta32x->regmap, STA32X_MMUTE, &mute); + if (ret) + return ret; + + ret = regmap_write(sta32x->regmap, STA32X_MMUTE, + mute | STA32X_MMUTE_MMUTE); + if (ret) + return ret; + + ret = sta32x_sync_coef_shadow(component); + if (!ret) + ret = regcache_sync(sta32x->regmap); + + restore_ret = regmap_write(sta32x->regmap, STA32X_MMUTE, mute); + return ret ?: restore_ret; } /* work around ESD issue where sta32x resets and loses all configuration */ @@ -461,8 +491,8 @@ SOC_SINGLE("Ch2 Tone Control Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_TCB_SHIF SOC_SINGLE("Ch1 EQ Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0), SOC_SINGLE("Ch2 EQ Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0), SOC_SINGLE("Ch1 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), -SOC_SINGLE("Ch2 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), -SOC_SINGLE("Ch3 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch2 Master Volume Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch3 Master Volume Bypass Switch", STA32X_C3CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), SOC_ENUM("Ch1 Limiter Select", sta32x_limiter_ch1_enum), SOC_ENUM("Ch2 Limiter Select", sta32x_limiter_ch2_enum), SOC_ENUM("Ch3 Limiter Select", sta32x_limiter_ch3_enum), @@ -477,21 +507,21 @@ SOC_ENUM("Limiter2 Release Rate (dB/ms)", sta32x_limiter2_release_rate_enum), * two different enum definitions; provide both */ SOC_SINGLE_TLV("Limiter1 Attack Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT, - 16, 0, sta32x_limiter_ac_attack_tlv), + 15, 0, sta32x_limiter_ac_attack_tlv), SOC_SINGLE_TLV("Limiter2 Attack Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT, - 16, 0, sta32x_limiter_ac_attack_tlv), + 15, 0, sta32x_limiter_ac_attack_tlv), SOC_SINGLE_TLV("Limiter1 Release Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT, - 16, 0, sta32x_limiter_ac_release_tlv), + 15, 0, sta32x_limiter_ac_release_tlv), SOC_SINGLE_TLV("Limiter2 Release Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT, - 16, 0, sta32x_limiter_ac_release_tlv), + 15, 0, sta32x_limiter_ac_release_tlv), SOC_SINGLE_TLV("Limiter1 Attack Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT, - 16, 0, sta32x_limiter_drc_attack_tlv), + 15, 0, sta32x_limiter_drc_attack_tlv), SOC_SINGLE_TLV("Limiter2 Attack Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT, - 16, 0, sta32x_limiter_drc_attack_tlv), + 15, 0, sta32x_limiter_drc_attack_tlv), SOC_SINGLE_TLV("Limiter1 Release Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT, - 16, 0, sta32x_limiter_drc_release_tlv), + 15, 0, sta32x_limiter_drc_release_tlv), SOC_SINGLE_TLV("Limiter2 Release Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT, - 16, 0, sta32x_limiter_drc_release_tlv), + 15, 0, sta32x_limiter_drc_release_tlv), BIQUAD_COEFS("Ch1 - Biquad 1", 0), BIQUAD_COEFS("Ch1 - Biquad 2", 5), @@ -794,9 +824,9 @@ static int sta32x_startup_sequence(struct sta32x_priv *sta32x) static int sta32x_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { - int ret; struct sta32x_priv *sta32x = snd_soc_component_get_drvdata(component); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + int cleanup_ret, ret; dev_dbg(component->dev, "level = %d\n", level); switch (level) { @@ -820,15 +850,28 @@ static int sta32x_set_bias_level(struct snd_soc_component *component, return ret; } - sta32x_startup_sequence(sta32x); - sta32x_cache_sync(component); + ret = sta32x_startup_sequence(sta32x); + if (!ret) + ret = sta32x_cache_sync(component); + if (ret) + goto restore_failed; + + ret = regmap_update_bits(sta32x->regmap, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, + 0); + if (ret) + goto restore_failed; + sta32x_watchdog_start(sta32x); + break; } /* Power down */ - regmap_update_bits(sta32x->regmap, STA32X_CONFF, - STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, - 0); + ret = regmap_update_bits(sta32x->regmap, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, + 0); + if (ret) + return ret; break; @@ -846,12 +889,31 @@ static int sta32x_set_bias_level(struct snd_soc_component *component, break; } return 0; + +restore_failed: + gpiod_set_value(sta32x->gpiod_nreset, 0); + regcache_mark_dirty(sta32x->regmap); + cleanup_ret = regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + if (cleanup_ret) + dev_warn(component->dev, "Failed to disable supplies: %d\n", + cleanup_ret); + return ret; } +static const u64 sta32x_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops sta32x_dai_ops = { .hw_params = sta32x_hw_params, .set_sysclk = sta32x_set_dai_sysclk, .set_fmt = sta32x_set_dai_fmt, + .auto_selectable_formats = &sta32x_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sta32x_dai = { @@ -1035,7 +1097,7 @@ static int sta32x_probe_dt(struct device *dev, struct sta32x_priv *sta32x) { struct device_node *np = dev->of_node; struct sta32x_platform_data *pdata; - u16 tmp; + u32 tmp; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) @@ -1060,8 +1122,8 @@ static int sta32x_probe_dt(struct device *dev, struct sta32x_priv *sta32x) of_property_read_bool(np, "st,needs_esd_watchdog"); tmp = 140; - of_property_read_u16(np, "st,drop-compensation-ns", &tmp); - pdata->drop_compensation_ns = clamp_t(u16, tmp, 0, 300) / 20; + of_property_read_u32(np, "st,drop-compensation-ns", &tmp); + pdata->drop_compensation_ns = clamp_t(u32, tmp, 0, 300) / 20; /* CONFE */ pdata->max_power_use_mpcc = diff --git a/sound/soc/codecs/sta350.c b/sound/soc/codecs/sta350.c index 2ba35076732b..acf5dc618398 100644 --- a/sound/soc/codecs/sta350.c +++ b/sound/soc/codecs/sta350.c @@ -477,9 +477,9 @@ SOC_SINGLE("Ch2 EQ Bypass Switch", SOC_SINGLE("Ch1 Master Volume Bypass Switch", STA350_C1CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), SOC_SINGLE("Ch2 Master Volume Bypass Switch", - STA350_C1CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), + STA350_C2CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), SOC_SINGLE("Ch3 Master Volume Bypass Switch", - STA350_C1CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), + STA350_C3CFG, STA350_CxCFG_VBP_SHIFT, 1, 0), SOC_ENUM("Ch1 Binary Output Select", sta350_binary_output_ch1_enum), SOC_ENUM("Ch2 Binary Output Select", sta350_binary_output_ch2_enum), SOC_ENUM("Ch3 Binary Output Select", sta350_binary_output_ch3_enum), @@ -502,28 +502,28 @@ SOC_ENUM("Limiter2 Release Rate (dB/ms)", sta350_limiter2_release_rate_enum), */ SOC_SINGLE_TLV("Limiter1 Attack Threshold (AC Mode)", STA350_L1ATRT, STA350_LxA_SHIFT, - 16, 0, sta350_limiter_ac_attack_tlv), + 15, 0, sta350_limiter_ac_attack_tlv), SOC_SINGLE_TLV("Limiter2 Attack Threshold (AC Mode)", STA350_L2ATRT, STA350_LxA_SHIFT, - 16, 0, sta350_limiter_ac_attack_tlv), + 15, 0, sta350_limiter_ac_attack_tlv), SOC_SINGLE_TLV("Limiter1 Release Threshold (AC Mode)", STA350_L1ATRT, STA350_LxR_SHIFT, - 16, 0, sta350_limiter_ac_release_tlv), + 15, 0, sta350_limiter_ac_release_tlv), SOC_SINGLE_TLV("Limiter2 Release Threshold (AC Mode)", STA350_L2ATRT, STA350_LxR_SHIFT, - 16, 0, sta350_limiter_ac_release_tlv), + 15, 0, sta350_limiter_ac_release_tlv), SOC_SINGLE_TLV("Limiter1 Attack Threshold (DRC Mode)", STA350_L1ATRT, STA350_LxA_SHIFT, - 16, 0, sta350_limiter_drc_attack_tlv), + 15, 0, sta350_limiter_drc_attack_tlv), SOC_SINGLE_TLV("Limiter2 Attack Threshold (DRC Mode)", STA350_L2ATRT, STA350_LxA_SHIFT, - 16, 0, sta350_limiter_drc_attack_tlv), + 15, 0, sta350_limiter_drc_attack_tlv), SOC_SINGLE_TLV("Limiter1 Release Threshold (DRC Mode)", STA350_L1ATRT, STA350_LxR_SHIFT, - 16, 0, sta350_limiter_drc_release_tlv), + 15, 0, sta350_limiter_drc_release_tlv), SOC_SINGLE_TLV("Limiter2 Release Threshold (DRC Mode)", STA350_L2ATRT, STA350_LxR_SHIFT, - 16, 0, sta350_limiter_drc_release_tlv), + 15, 0, sta350_limiter_drc_release_tlv), BIQUAD_COEFS("Ch1 - Biquad 1", 0), BIQUAD_COEFS("Ch1 - Biquad 2", 5), @@ -882,10 +882,19 @@ static int sta350_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 sta350_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops sta350_dai_ops = { .hw_params = sta350_hw_params, .set_sysclk = sta350_set_dai_sysclk, .set_fmt = sta350_set_dai_fmt, + .auto_selectable_formats = &sta350_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sta350_dai = { @@ -1087,7 +1096,7 @@ static int sta350_probe_dt(struct device *dev, struct sta350_priv *sta350) struct device_node *np = dev->of_node; struct sta350_platform_data *pdata; const char *ffx_power_mode; - u16 tmp; + u32 tmp; u8 tmp8; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); @@ -1127,8 +1136,8 @@ static int sta350_probe_dt(struct device *dev, struct sta350_priv *sta350) } tmp = 140; - of_property_read_u16(np, "st,drop-compensation-ns", &tmp); - pdata->drop_compensation_ns = clamp_t(u16, tmp, 0, 300) / 20; + of_property_read_u32(np, "st,drop-compensation-ns", &tmp); + pdata->drop_compensation_ns = clamp_t(u32, tmp, 0, 300) / 20; pdata->oc_warning_adjustment = of_property_read_bool(np, "st,overcurrent-warning-adjustment"); diff --git a/sound/soc/codecs/sta529.c b/sound/soc/codecs/sta529.c index 7d57999f9cb1..0faf439ffee9 100644 --- a/sound/soc/codecs/sta529.c +++ b/sound/soc/codecs/sta529.c @@ -286,10 +286,17 @@ static int sta529_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) return 0; } +static const u64 sta529_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops sta529_dai_ops = { .hw_params = sta529_hw_params, .set_fmt = sta529_set_dai_fmt, .mute_stream = sta529_mute, + .auto_selectable_formats = &sta529_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2552.c b/sound/soc/codecs/tas2552.c index be2c9a0fa529..beb5654ac4b4 100644 --- a/sound/soc/codecs/tas2552.c +++ b/sound/soc/codecs/tas2552.c @@ -518,6 +518,18 @@ static const struct dev_pm_ops tas2552_pm = { RUNTIME_PM_OPS(tas2552_runtime_suspend, tas2552_runtime_resume, NULL) }; +static const u64 tas2552_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops tas2552_speaker_dai_ops = { .hw_params = tas2552_hw_params, .prepare = tas2552_prepare, @@ -525,6 +537,8 @@ static const struct snd_soc_dai_ops tas2552_speaker_dai_ops = { .set_fmt = tas2552_set_dai_fmt, .set_tdm_slot = tas2552_set_dai_tdm_slot, .mute_stream = tas2552_mute, + .auto_selectable_formats = tas2552_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(tas2552_selectable_formats), .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2562.c b/sound/soc/codecs/tas2562.c index 0b8644ad74f1..1fe17b0895fc 100644 --- a/sound/soc/codecs/tas2562.c +++ b/sound/soc/codecs/tas2562.c @@ -604,11 +604,21 @@ static const struct snd_soc_component_driver soc_component_dev_tas2562 = { .endianness = 1, }; +static const u64 tas2562_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops tas2562_speaker_dai_ops = { .hw_params = tas2562_hw_params, .set_fmt = tas2562_set_dai_fmt, .set_tdm_slot = tas2562_set_dai_tdm_slot, .mute_stream = tas2562_mute, + .auto_selectable_formats = &tas2562_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c index f9c65c9f6d0a..6654100ac91d 100644 --- a/sound/soc/codecs/tas2764.c +++ b/sound/soc/codecs/tas2764.c @@ -602,6 +602,16 @@ static int tas2764_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) return tas2764_write_sdout_idle_mask(tas2764, tas2764->idle_slot_config.tx_mask); } +static const u64 tas2764_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops tas2764_dai_ops = { .mute_stream = tas2764_mute, .hw_params = tas2764_hw_params, @@ -609,6 +619,8 @@ static const struct snd_soc_dai_ops tas2764_dai_ops = { .set_bclk_ratio = tas2764_set_bclk_ratio, .set_tdm_slot = tas2764_set_dai_tdm_slot, .set_tdm_idle = tas2764_set_dai_tdm_idle, + .auto_selectable_formats = &tas2764_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -960,7 +972,7 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764) } } - tas2764->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH); + tas2764->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_LOW); if (IS_ERR(tas2764->sdz_gpio)) { if (PTR_ERR(tas2764->sdz_gpio) == -EPROBE_DEFER) return -EPROBE_DEFER; diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c index e79754edac5c..882e19857697 100644 --- a/sound/soc/codecs/tas2770.c +++ b/sound/soc/codecs/tas2770.c @@ -566,12 +566,24 @@ static int tas2770_set_dai_tdm_idle(struct snd_soc_dai *dai, return 0; } +static const u64 tas2770_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops tas2770_dai_ops = { .mute_stream = tas2770_mute, .hw_params = tas2770_hw_params, .set_fmt = tas2770_set_fmt, .set_tdm_slot = tas2770_set_dai_tdm_slot, .set_tdm_idle = tas2770_set_dai_tdm_idle, + .auto_selectable_formats = &tas2770_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -886,7 +898,7 @@ static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770) if (rc) tas2770->pdm_slot = -1; - tas2770->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH); + tas2770->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_LOW); if (IS_ERR(tas2770->sdz_gpio)) { if (PTR_ERR(tas2770->sdz_gpio) == -EPROBE_DEFER) return -EPROBE_DEFER; diff --git a/sound/soc/codecs/tas2780.c b/sound/soc/codecs/tas2780.c index f1fce4305fa1..991d0c0efee2 100644 --- a/sound/soc/codecs/tas2780.c +++ b/sound/soc/codecs/tas2780.c @@ -441,11 +441,21 @@ static int tas2780_set_dai_tdm_slot(struct snd_soc_dai *dai, return ret; } +static const u64 tas2780_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops tas2780_dai_ops = { .mute_stream = tas2780_mute, .hw_params = tas2780_hw_params, .set_fmt = tas2780_set_fmt, .set_tdm_slot = tas2780_set_dai_tdm_slot, + .auto_selectable_formats = &tas2780_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas2781-comlib-i2c.c b/sound/soc/codecs/tas2781-comlib-i2c.c index 79b5f5b04c74..46777be765ef 100644 --- a/sound/soc/codecs/tas2781-comlib-i2c.c +++ b/sound/soc/codecs/tas2781-comlib-i2c.c @@ -2,7 +2,7 @@ // // TAS2563/TAS2781 Common functions for HDA and ASoC Audio drivers based on I2C // -// Copyright 2025 Texas Instruments, Inc. +// Copyright 2025 - 2026 Texas Instruments, Inc. // // Author: Shenghao Ding @@ -131,7 +131,7 @@ int tasdevice_dev_update_bits( ret = regmap_update_bits(map, TASDEVICE_PGRG(reg), mask, value); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, chn); diff --git a/sound/soc/codecs/tas2781-comlib.c b/sound/soc/codecs/tas2781-comlib.c index 4cec9f8a00af..58e19d6d1f75 100644 --- a/sound/soc/codecs/tas2781-comlib.c +++ b/sound/soc/codecs/tas2781-comlib.c @@ -2,7 +2,7 @@ // // TAS2563/TAS2781 Common functions for HDA and ASoC Audio drivers // -// Copyright 2023 - 2025 Texas Instruments, Inc. +// Copyright 2023 - 2026 Texas Instruments, Inc. // // Author: Shenghao Ding @@ -34,7 +34,7 @@ int tasdevice_dev_read(struct tasdevice_priv *tas_priv, ret = regmap_read(map, TASDEVICE_PGRG(reg), val); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -62,7 +62,7 @@ int tasdevice_dev_bulk_read(struct tasdevice_priv *tas_priv, ret = regmap_bulk_read(map, TASDEVICE_PGRG(reg), data, len); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, chn); @@ -88,7 +88,7 @@ int tasdevice_dev_write(struct tasdevice_priv *tas_priv, ret = regmap_write(map, TASDEVICE_PGRG(reg), value); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -118,7 +118,7 @@ int tasdevice_dev_bulk_write( ret = regmap_bulk_write(map, TASDEVICE_PGRG(reg), data, len); if (ret < 0) - dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + TAS_REG_ERR_LOG(tas_priv->dev, chn, reg, ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index 2b9efccf1a0b..f309a57337b0 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -110,7 +110,7 @@ struct tas2781_cali_specific { }; static const char deviceNumber[TASDEVICE_DSP_TAS_MAX_DEVICE] = { - 1, 2, 1, 2, 1, 1, 0, 2, 4, 3, 1, 2, 3, 4, 1, 2 + 1, 2, 1, 2, 1, 1, 0, 2, 4, 3, 1, 2, 3, 4, 1, 2, 1, 2, 3, 4, }; /* fixed m68k compiling issue: mapping table can save code field */ @@ -199,15 +199,22 @@ static struct tasdevice_config_info *tasdevice_add_config( dev_err(tas_priv->dev, "add conf: Out of boundary\n"); goto out; } - /* If in the RCA bin file are several profiles with the - * keyword "init", init_profile_id only store the last - * init profile id. + /* + * If in the RCA bin file are several profiles with the + * keyword "init"/"calib", init_profile_id only store the last + * init profile id, and same as calibration_profile_id. */ if (strnstr(&config_data[config_offset], "init", 64)) { tas_priv->rcabin.init_profile_id = tas_priv->rcabin.ncfgs - 1; dev_dbg(tas_priv->dev, "%s: init profile id = %d\n", __func__, tas_priv->rcabin.init_profile_id); + } else if (strnstr(&config_data[config_offset], "calib", 64)) { + tas_priv->rcabin.calibration_profile_id = + tas_priv->rcabin.ncfgs - 1; + dev_dbg(tas_priv->dev, "%s: calib profile id = %d\n", + __func__, + tas_priv->rcabin.calibration_profile_id); } config_offset += 64; } @@ -314,6 +321,7 @@ int tasdevice_rca_parser(void *context, const struct firmware *fmw) rca = &(tas_priv->rcabin); /* Initialize to none */ rca->init_profile_id = -1; + rca->calibration_profile_id = -1; fw_hdr = &(rca->fw_hdr); if (!fmw || !fmw->data) { dev_err(tas_priv->dev, "Failed to read %s\n", @@ -632,6 +640,17 @@ static int fw_parse_configuration_data_kernel( goto out; } memcpy(config->name, &data[offset], 64); + + /* + * If in the coef bin file are several configs with the + * keyword "calib", calibration_config_id only store the last + * calibration profile id. + */ + if (strnstr(config->name, "calib", 64)) { + tas_fmw->calibration_config_id = i; + dev_dbg(tas_priv->dev, "%s: calib config = %d\n", + __func__, tas_fmw->calibration_config_id); + } /*skip extra 16 bytes*/ offset += 80; @@ -1412,6 +1431,17 @@ static int fw_parse_configuration_data( memcpy(config->name, &data[offset], 64); offset += 64; + /* + * If in the coef bin file are several configs with the + * keyword "calib", calibration_config_id only store the last + * calibration profile id. + */ + if (strnstr(config->name, "calib", 64)) { + tas_fmw->calibration_config_id = i; + dev_dbg(tas_priv->dev, "%s: calib config id = %d\n", + __func__, tas_fmw->calibration_config_id); + } + n = tasdevice_fw_strnlen(fmw, offset); if (n < 0) { dev_err(tas_priv->dev, "Description err\n"); @@ -1959,8 +1989,11 @@ static void dspbin_type_check(struct tasdevice_priv *tas_priv, else tas_priv->dspbin_typ = TASDEV_ALPHA; } + if (tas_priv->chip_id == TAS2573) + return; if ((tas_priv->dspbin_typ != TASDEV_BASIC) && - (ppcver < PPC3_VERSION_TAS5825_BASE)) + (ppcver < PPC3_VERSION_TAS5825_BASE || + tas_priv->chip_id == TAS2781)) tas_priv->fw_parse_fct_param_address = fw_parse_fct_param_address; } @@ -1971,7 +2004,8 @@ static int dspfw_default_callback(struct tasdevice_priv *tas_priv, int rc = 0; if (drv_ver == 0x100) { - if (ppcver >= PPC3_VERSION_TAS5825_BASE) { + if (ppcver >= PPC3_VERSION_TAS5825_BASE && + tas_priv->chip_id == TAS5825) { tas_priv->fw_parse_variable_header = fw_parse_variable_header_kernel; tas_priv->fw_parse_program_data = diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c index 6f349f88f194..995bfd52f5fc 100644 --- a/sound/soc/codecs/tas2781-i2c.c +++ b/sound/soc/codecs/tas2781-i2c.c @@ -613,6 +613,39 @@ static void tas2563_calib_stop_put(struct tasdevice_priv *tas_priv) } } +static void tas2573_calib_stop_put(struct tasdevice_priv *tas_priv) +{ + int cal_prof_id = tas_priv->rcabin.calibration_profile_id; + + if (cal_prof_id >= 0) + tasdevice_select_cfg_blk(tas_priv, cal_prof_id, + TASDEVICE_BIN_BLK_PRE_SHUTDOWN); +} + +static int tas2573_calib_start_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(comp); + int cal_prof_id = tas_priv->rcabin.calibration_profile_id; + int cal_conf_id = tas_priv->fmw->calibration_config_id; + + guard(mutex)(&tas_priv->codec_lock); + if (tas_priv->chip_id != TAS2573) { + WARN_ON_ONCE(1); + return -ENODEV; + } + + if (cal_prof_id >= 0) + tasdevice_select_cfg_blk(tas_priv, cal_prof_id, + TASDEVICE_BIN_BLK_PRE_POWER_UP); + + tasdevice_select_tuningprm_cfg(tas_priv, tas_priv->cur_prog, + cal_conf_id, cal_prof_id); + + return 1; +} + static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -621,10 +654,22 @@ static int tasdev_calib_stop_put(struct snd_kcontrol *kcontrol, int i; guard(mutex)(&priv->codec_lock); - if (priv->chip_id == TAS2563) + + switch (priv->chip_id) { + case TAS2563: tas2563_calib_stop_put(priv); - else + break; + case TAS2573: + tas2573_calib_stop_put(priv); + break; + case TAS2781: tas2781_calib_stop_put(priv); + break; + default: + dev_err(priv->dev, "%s: Chip(%d) unsupports calibration\n", + __func__, priv->chip_id); + return -1; + } /* * Set reloading-firmware flag after calibration, the flag will work @@ -983,6 +1028,13 @@ static const struct snd_kcontrol_new tas2563_cali_controls[] = { tasdev_nop_get, tas2563_calib_start_put), }; +static const struct snd_kcontrol_new tas2573_cali_controls[] = { + SOC_SINGLE_EXT("Calibration Start", SND_SOC_NOPM, 0, 1, 0, + tasdev_nop_get, tas2573_calib_start_put), + SOC_SINGLE_EXT("Calibration Stop", SND_SOC_NOPM, 0, 1, 0, + tasdev_nop_get, tasdev_calib_stop_put), +}; + static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -1493,6 +1545,182 @@ static int create_tas2781_cali_start_ktrl(struct tasdevice_priv return 0; } +static int tas2573_re_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); + unsigned char *dst = ucontrol->value.bytes.data; + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *) kcontrol->private_value; + unsigned int i = 0; + unsigned int j; + int rc; + + guard(mutex)(&priv->codec_lock); + dst[i++] = bytes_ext->max; + dst[i++] = 'r'; + + dst[i++] = TASDEVICE_BOOK_ID(TAS2573_RE_OUT); + dst[i++] = TASDEVICE_PAGE_ID(TAS2573_RE_OUT); + dst[i++] = TASDEVICE_PAGE_REG(TAS2573_RE_OUT); + + dst[i++] = 'D'; + + for (j = 0; j < priv->ndev; j++) { + dst[i++] = j; + + rc = tasdevice_dev_bulk_read(priv, j, TAS2573_RE_OUT, + &dst[i], 4); + if (rc < 0) + dev_err(priv->dev, "chn %d bulk_rd err = %d\n", j, rc); + + i += 4; + } + + return 0; +} + +static int tas2573_calib_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp); + unsigned char *dst = ucontrol->value.bytes.data; + struct soc_bytes_ext *bytes_ext = + (struct soc_bytes_ext *) kcontrol->private_value; + unsigned int fct_status_regs[] = { + TAS2573_FCT_INT_LATCH, + TAS2573_FCT_STATUS_CTRL, + TAS2573_FCT_STATUS_BINNING, + TAS2573_SILENCE_DETECTED, + TAS2573_OPEN_CIRCUIT, + TAS2573_SHORTCKT, + TAS2573_FCT_OUTPUT_R0, + TAS2573_FCT_OUTPUT_R0_LOW, + TAS2573_FCT_OUTPUT_INV_R0, + TAS2573_FCT_OUTPUT_POWERTOT, + TAS2573_FCT_OUTPUT_F0, + TAS2573_RE_OUT, + }; + unsigned int j, k, val; + unsigned int i = 0; + int rc; + + guard(mutex)(&priv->codec_lock); + dst[i++] = bytes_ext->max; + dst[i++] = 'r'; + + for (j = 0; j < ARRAY_SIZE(fct_status_regs); j++) { + dst[i++] = TASDEVICE_BOOK_ID(fct_status_regs[j]); + dst[i++] = TASDEVICE_PAGE_ID(fct_status_regs[j]); + dst[i++] = TASDEVICE_PAGE_REG(fct_status_regs[j]); + } + dst[i++] = 'D'; + + for (j = 0; j < priv->ndev; j++) { + dst[i++] = j; + dst[i++] = 0; + dst[i++] = 0; + dst[i++] = 0; + + rc = tasdevice_dev_read(priv, j, fct_status_regs[0], &val); + if (rc < 0) + dev_err(priv->dev, + "chn %d fct_status_regs[0] rd err = %d\n", + j, rc); + else + dst[i++] = val; + + for (k = 1; k < ARRAY_SIZE(fct_status_regs); k++, i += 4) { + rc = tasdevice_dev_bulk_read(priv, j, + fct_status_regs[k], &dst[i], 4); + if (rc < 0) { + dev_err(priv->dev, + "chn %d regs[%u] bulk_rd err = %d\n", + j, k, rc); + } + } + + } + + return 0; +} + +static int create_tas2573_cali_status_ktrl(struct tasdevice_priv + *priv, struct snd_kcontrol_new *cali_ctrl) +{ + struct soc_bytes_ext *ext_cali_start; + char *cali_start_name; + + ext_cali_start = devm_kzalloc(priv->dev, + sizeof(*ext_cali_start), GFP_KERNEL); + if (!ext_cali_start) + return -ENOMEM; + + cali_start_name = devm_kstrdup(priv->dev, + "Calibration Status", GFP_KERNEL); + if (!cali_start_name) + return -ENOMEM; + /* + * package structure for tas2573 fct status: + * Pkg len (1 byte) + * Reg id (1 byte, constant 'r') + * book, page, register for fct status (total 36 bytes) + * Data Start Flag (1 byte, constant 'D') + * for (i = 0; i < Device-Sum; i++) { + * Device #i index_info (1 byte) + * Sine gain for Device #i (48 bytes) + * } + */ + ext_cali_start->max = 39 + priv->ndev * 49; + cali_ctrl->name = cali_start_name; + cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cali_ctrl->info = snd_soc_bytes_info_ext; + cali_ctrl->put = NULL; + cali_ctrl->get = tas2573_calib_status_get; + cali_ctrl->private_value = (unsigned long)ext_cali_start; + + return 0; +} + +static int create_tas2573_cali_re_ktrl(struct tasdevice_priv + *priv, struct snd_kcontrol_new *cali_ctrl) +{ + struct soc_bytes_ext *ext_cali_start; + char *cali_start_name; + + ext_cali_start = devm_kzalloc(priv->dev, + sizeof(*ext_cali_start), GFP_KERNEL); + if (!ext_cali_start) + return -ENOMEM; + + cali_start_name = devm_kstrdup(priv->dev, + "Real-time Speaker Impedance", GFP_KERNEL); + if (!cali_start_name) + return -ENOMEM; + /* + * package structure for tas2573 real-time spk impedance: + * Pkg len (1 byte) + * Reg id (1 byte, constant 'r') + * book, page, register for fct status (total 3 bytes) + * Data Start Flag (1 byte, constant 'D') + * for (i = 0; i < Device-Sum; i++) { + * Device #i index_info (1 byte) + * Sine gain for Device #i (4 bytes) + * } + */ + ext_cali_start->max = 6 + priv->ndev * 5; + cali_ctrl->name = cali_start_name; + cali_ctrl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cali_ctrl->info = snd_soc_bytes_info_ext; + cali_ctrl->put = NULL; + cali_ctrl->get = tas2573_re_get; + cali_ctrl->private_value = (unsigned long)ext_cali_start; + + return 0; +} + static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) { struct calidata *cali_data = &priv->cali_data; @@ -1504,17 +1732,43 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) char *cali_name; int rc, i; - rc = snd_soc_add_component_controls(priv->codec, - tasdevice_cali_controls, ARRAY_SIZE(tasdevice_cali_controls)); - if (rc < 0) { - dev_err(priv->dev, "%s: Add cali controls err rc = %d", - __func__, rc); - return rc; + switch (priv->chip_id) { + case TAS2563: { + rc = snd_soc_add_component_controls(priv->codec, + tasdevice_cali_controls, + ARRAY_SIZE(tasdevice_cali_controls)); + if (rc < 0) { + dev_err(priv->dev, "%s: Add cali controls err rc = %d", + __func__, rc); + return rc; + } + cali_ctrls = (struct snd_kcontrol_new *)tas2563_cali_controls; + nctrls = ARRAY_SIZE(tas2563_cali_controls); + for (i = 0; i < priv->ndev; i++) { + tasdev[i].cali_data_backup = + kmemdup(tas2563_cali_start_reg, + sizeof(tas2563_cali_start_reg), GFP_KERNEL); + if (!tasdev[i].cali_data_backup) + return -ENOMEM; + } } - - if (priv->chip_id == TAS2781) { + break; + case TAS2573: + cali_ctrls = (struct snd_kcontrol_new *)tas2573_cali_controls; + nctrls = ARRAY_SIZE(tas2573_cali_controls); + break; + case TAS2781: { struct fct_param_address *t = &(fmw->fct_par_addr); + rc = snd_soc_add_component_controls(priv->codec, + tasdevice_cali_controls, + ARRAY_SIZE(tasdevice_cali_controls)); + if (rc < 0) { + dev_err(priv->dev, "%s: Add cali controls err rc = %d", + __func__, rc); + return rc; + } + cali_ctrls = (struct snd_kcontrol_new *)tas2781_cali_controls; nctrls = ARRAY_SIZE(tas2781_cali_controls); for (i = 0; i < priv->ndev; i++) { @@ -1533,16 +1787,12 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) } } } - } else { - cali_ctrls = (struct snd_kcontrol_new *)tas2563_cali_controls; - nctrls = ARRAY_SIZE(tas2563_cali_controls); - for (i = 0; i < priv->ndev; i++) { - tasdev[i].cali_data_backup = - kmemdup(tas2563_cali_start_reg, - sizeof(tas2563_cali_start_reg), GFP_KERNEL); - if (!tasdev[i].cali_data_backup) - return -ENOMEM; - } + } + break; + default: + dev_err(priv->dev, "%s: Wrong chip id = %d", __func__, + priv->chip_id); + return -EINVAL; } rc = snd_soc_add_component_controls(priv->codec, cali_ctrls, nctrls); @@ -1554,10 +1804,17 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) /* index for cali_ctrls */ i = 0; - if (priv->chip_id == TAS2781) + switch (priv->chip_id) { + case TAS2573: + nctrls = 3; + break; + case TAS2781: nctrls = 2; - else + break; + default: nctrls = 1; + break; + } /* * Alloc kcontrol via devm_kzalloc(), which don't manually @@ -1610,12 +1867,24 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv) * it, for the default value is 0, which means the first device. */ cali_data->data[0] = 0xff; - if (priv->chip_id == TAS2781) { + + switch (priv->chip_id) { + case TAS2573: + rc = create_tas2573_cali_status_ktrl(priv, &cali_ctrls[i]); + if (rc != 0) + return rc; + i++; + rc = create_tas2573_cali_re_ktrl(priv, &cali_ctrls[i]); + if (rc != 0) + return rc; + i++; + break; + case TAS2781: rc = create_tas2781_cali_start_ktrl(priv, &cali_ctrls[i]); if (rc != 0) return rc; i++; - + break; } return snd_soc_add_component_controls(priv->codec, cali_ctrls, @@ -1822,7 +2091,8 @@ static void tasdevice_fw_ready(const struct firmware *fmw, tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; /* There is no calibration required for TAS58XX. */ - if (tas_priv->chip_id == TAS2563 || tas_priv->chip_id == TAS2781) { + if (tas_priv->chip_id == TAS2563 || tas_priv->chip_id == TAS2573 || + tas_priv->chip_id == TAS2781) { ret = tasdevice_create_cali_ctrls(tas_priv); if (ret) { dev_err(tas_priv->dev, "cali controls error\n"); diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index 7fcf8214bf34..a57230e01f75 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -97,6 +97,7 @@ struct tas2783_prv { u8 rca_binaryname[64]; u8 dev_name[32]; bool hw_init; + unsigned int fw_version; /* wq for firmware download */ wait_queue_head_t fw_wait; bool fw_dl_task_done; @@ -185,35 +186,17 @@ static const struct reg_default tas2783_reg_default[] = { {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x01, 1), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x0b, 1), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x01, 0), 0x1}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x06, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x07, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x09, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x0a, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS24, 0x02, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS21, 0x02, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS26, 0x02, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS28, 0x02, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, 0x1, 0), 0x3}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x05, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x06, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU26, 0x06, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT29, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT29, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT26, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT26, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT28, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT28, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT24, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT24, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT23, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT23, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT25, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT25, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT28, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT28, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 1), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 2), 0x0}, @@ -222,40 +205,28 @@ static const struct reg_default tas2783_reg_default[] = { {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 5), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 6), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x01, 7), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x06, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x04, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x01, 0), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x01, 1), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x01, 2), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x0b, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x0b, 1), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x0b, 2), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS127, 0x02, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x01, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x04, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x05, 0), 0x1}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x08, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x01, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x04, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x05, 0), 0x1}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x08, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU23, 0x10, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x10, 0), 0x1}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x12, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x13, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x14, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x15, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x16, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS24, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS21, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS26, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS28, 0x10, 0), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23, 0x10, 0), 0x3}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_UDMPU23, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x10, 0), 0x1}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x11, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x12, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x11, 0), 0x0}, @@ -286,7 +257,6 @@ static const struct reg_default tas2783_reg_default[] = { {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x12, 5), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x12, 6), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x12, 7), 0x0}, - {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_CS127, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x10, 0), 0x0}, {SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x11, 0), 0x0}, @@ -346,8 +316,10 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case 0x300 ... 0x340: /* Data port 3. */ case 0x400 ... 0x440: /* Data port 4. */ case 0x500 ... 0x540: /* Data port 5. */ - case 0x800000 ... 0x803fff: /* Page 0 ~ 127. */ - case 0x807e80 ... 0x807eff: /* Page 253. */ + case TASDEV_REG_SDW(0, 0, 0) ... TASDEV_REG_SDW(0x00, 0x01, 0x80): + case TASDEV_REG_SDW(0, 0xfd, 0) ... TASDEV_REG_SDW(0, 0xfd, 0x80): + case PRAM_ADDR_START ... PRAM_ADDR_END: + case YRAM_ADDR_START ... YRAM_ADDR_END: case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_UDMPU23, TAS2783_SDCA_CTL_UDMPU_CLUSTER, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU21, TAS2783_SDCA_CTL_FU_MUTE, @@ -364,7 +336,6 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU26, 0x11, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_TG23, 0x10, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x01, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x08, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x0a, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x10, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x14, 0): @@ -450,7 +421,6 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x0b, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x0b, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x0b, 1): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x07, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x09, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x12, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x12, 0): @@ -462,27 +432,8 @@ static int tas2783_sdca_mbq_size(struct device *dev, u32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x11, 0): return 2; - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU23, 0x10, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT21, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT26, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT28, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT29, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT23, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT24, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT25, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT28, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT127, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MU26, 0x06, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU127, 0x10, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU26, 0x10, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x06, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x12, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_XU22, 0x13, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x06, 0): - case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU26, 0x06, 0): return 4; default: @@ -495,59 +446,52 @@ static bool tas2783_readable_register(struct device *dev, unsigned int reg) return tas2783_sdca_mbq_size(dev, reg) > 0; } -static bool tas2783_writeable_register(struct device *dev, unsigned int reg) +/* + * The read-only SDCA Controls the driver describes: the Clock Valid of every + * Clock Source, the actual power state of the Power Domain Entity, the + * protection status and the algorithm ready flags. None of them is a + * setting; every one is a reading of device state. + */ +static bool tas2783_read_only_control(unsigned int reg) { - /* - * The Latency Control of every Entity, together with the Power Domain - * actual state and the protection status, is read-only. They are - * listed in tas2783_reg_default[] with a placeholder value, so without - * this a regcache_sync() would try to write them back and the - * peripheral would reject the transaction, aborting the sync. - */ switch (reg) { - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU21, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU23, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU26, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x07, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS24, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS21, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS25, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS26, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS28, 0x02, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PDE23, 0x10, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_UDMPU23, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x05, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_SAPU29, 0x11, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU21, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_PPU26, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT21, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT29, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT26, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_IT28, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT24, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT23, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT25, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT28, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MU26, 0x06, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_OT127, 0x08, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_FU127, 0x10, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_CS127, 0x02, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU21, 0x04, 0): - case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x08, 0): case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_MFPU26, 0x04, 0): - return false; + return true; default: - return tas2783_sdca_mbq_size(dev, reg) > 0; + return false; } } +static bool tas2783_writeable_register(struct device *dev, unsigned int reg) +{ + if (tas2783_read_only_control(reg)) + return false; + + return tas2783_sdca_mbq_size(dev, reg) > 0; +} + static bool tas2783_volatile_register(struct device *dev, u32 reg) { + if (tas2783_read_only_control(reg)) + return true; + switch (reg) { + /* + * The File Download Status is a handshake: the device raises its own + * bits on its own schedule and the host writes back a response, so it + * is writable, but it must never be answered from the cache. + */ + case SDW_SDCA_CTL(FUNC_NUM_SMART_AMP, TAS2783_SDCA_ENT_XU22, 0x14, 0): case 0x000 ... 0x080: /* Data port 0. */ case 0x100 ... 0x140: /* Data port 1. */ case 0x200 ... 0x240: /* Data port 2. */ @@ -564,13 +508,19 @@ static bool tas2783_volatile_register(struct device *dev, u32 reg) static const struct regmap_config tas_regmap = { .reg_bits = 32, - .val_bits = 8, + /* + * tas2783_sdca_mbq_size() declares registers of one, two and four + * bytes. val_bits has to cover the widest of those: the SoundWire MBQ + * bus rejects any register whose declared size exceeds val_bits, which + * would make every multi-byte register unreachable in both directions. + */ + .val_bits = 32, .readable_reg = tas2783_readable_register, .writeable_reg = tas2783_writeable_register, .volatile_reg = tas2783_volatile_register, .reg_defaults = tas2783_reg_default, .num_reg_defaults = ARRAY_SIZE(tas2783_reg_default), - .max_register = 0x41008000 + TASDEV_REG_SDW(0xa1, 0x60, 0x7f), + .max_register = 0x41000000 + PRAM_ADDR_END, .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, @@ -667,9 +617,9 @@ static s32 tas2783_validate_calibdata(struct tas2783_prv *tas_dev, static void tas2783_set_calib_params_to_device(struct tas2783_prv *tas_dev, u32 *cali_data) { - u32 dev_count, offset, i, device_num; + u32 dev_count, offset, i, j, device_num; u32 reg_value; - u8 buf[4]; + s32 ret; dev_count = cali_data[1]; offset = 3; @@ -683,12 +633,25 @@ static void tas2783_set_calib_params_to_device(struct tas2783_prv *tas_dev, u32 for (i = 0; i < ARRAY_SIZE(tas2783_cali_reg); i++) { reg_value = cali_data[offset + i]; - buf[0] = reg_value >> 24; - buf[1] = reg_value >> 16; - buf[2] = reg_value >> 8; - buf[3] = reg_value & 0xff; - regmap_bulk_write(tas_dev->regmap, tas2783_cali_reg[i], - buf, sizeof(u32)); + + /* + * A calibration value occupies four consecutive + * single-byte registers, most significant byte first. + * Write them one by one: these registers are declared + * as one byte wide by tas2783_sdca_mbq_size(), so a + * multi-byte regmap access would not match the layout. + */ + for (j = 0; j < sizeof(u32); j++) { + ret = regmap_write(tas_dev->regmap, + tas2783_cali_reg[i] + j, + (reg_value >> (24 - 8 * j)) & 0xff); + if (ret) { + dev_err(tas_dev->dev, + "calib write to 0x%x failed, err=%d\n", + tas2783_cali_reg[i] + j, ret); + break; + } + } } break; } @@ -785,6 +748,7 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) const u8 *buf = NULL; s32 img_sz, ret = 0, cur_file = 0; s32 offset = 0; + u32 val[4], fw_version; struct tas_fw_hdr *hdr __free(kfree) = kzalloc_obj(*hdr); struct tas_fw_file *file __free(kfree) = kzalloc_obj(*file); @@ -826,6 +790,11 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) } mutex_lock(&tas_dev->pde_lock); + ret = regmap_bulk_read(tas_dev->regmap, TAS2783_FW_VERSION, &val, 4); + fw_version = (val[0] << 24) | (val[1] << 16) | (val[2] << 8) | val[3]; + dev_dbg(tas_dev->dev, "Get Firmware version: %08x == %08x?, err=%d", + fw_version, tas_dev->fw_version, ret); + while (offset < (img_sz - FW_FL_HDR)) { offset += tas_fw_get_next_file(&buf[offset], file); dev_dbg(tas_dev->dev, @@ -834,6 +803,13 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) file->version, file->length, file->dest_addr, file->fw_data); + if (tas_dev->fw_version == fw_version && + file->dest_addr >= PRAM_ADDR_START && + (file->dest_addr + file->length) <= PRAM_ADDR_END) { + cur_file++; + dev_dbg(tas_dev->dev, "Ignore PRAM block"); + continue; + } ret = sdw_nwrite_no_pm(tas_dev->sdw_peripheral, file->dest_addr, file->length, @@ -841,17 +817,34 @@ static void tas2783_fw_ready(const struct firmware *fmw, void *context) if (ret < 0) { dev_err(tas_dev->dev, "FW download failed: %d", ret); - break; + /* + * We do retry here for some special case of download + * failed after Power-On. + */ + ret = sdw_nwrite_no_pm(tas_dev->sdw_peripheral, + file->dest_addr, + file->length, + file->fw_data); + if (ret < 0) { + dev_err(tas_dev->dev, + "FW download failed again: %d", ret); + break; + } } cur_file++; } mutex_unlock(&tas_dev->pde_lock); + regcache_drop_region(tas_dev->regmap, 0, UINT_MAX); if (cur_file == 0) { dev_err(tas_dev->dev, "fw with no files"); ret = -EINVAL; } else { tas2783_update_calibdata(tas_dev); + ret = regmap_bulk_read(tas_dev->regmap, TAS2783_FW_VERSION, + &val, 4); + tas_dev->fw_version = (val[0] << 24) | (val[1] << 16) | + (val[2] << 8) | val[3]; } out: @@ -1002,30 +995,6 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream *substream, snd_sdw_params_to_config(substream, params, &stream_config, &port_config); - /* - * The two mono amps each render one channel of the stereo stream: - * snd_sdw_params_to_config() hands every codec the full mask for - * playback, which leaves the pair in mirror mode and one channel - * unreproduced. Claim a single channel instead, keyed off the - * machine-assigned component prefix rather than the SoundWire - * address, which is board-specific: soc_sdw_ti_amp.c names the amps - * tas2783-1..4. - * - * Which side an amp then renders does not follow from the bit that - * is set - sdw_compute_slave_ports() advances the payload offset by - * the popcount of ch_mask and never looks at which bit it is - but - * from the amp's position in the codec order of the DAI link, which - * on these boards matches the prefix numbering. - */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && - params_channels(params) == 2 && component->name_prefix) { - const char *idx_str = strrchr(component->name_prefix, '-'); - unsigned long idx; - - if (idx_str && !kstrtoul(idx_str + 1, 10, &idx) && idx) - port_config.ch_mask = (idx & 1) ? BIT(0) : BIT(1); - } - /* port 1 for playback */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) port_config.num = 1; @@ -1114,7 +1083,7 @@ static const struct snd_soc_component_driver soc_codec_driver_tasdevice = { .num_dapm_widgets = ARRAY_SIZE(tas_dapm_widgets), .dapm_routes = tas_audio_map, .num_dapm_routes = ARRAY_SIZE(tas_audio_map), - .idle_bias_on = 1, + .idle_bias_on = 0, .endianness = 1, }; @@ -1274,6 +1243,8 @@ static s32 tas_io_init(struct device *dev, struct sdw_slave *slave) ret = regmap_multi_reg_write(tas_dev->regmap, tas2783_init_seq, ARRAY_SIZE(tas2783_init_seq)); + /* Re-active AMP after resume. */ + regmap_write(tas_dev->regmap, TASDEV_REG_SDW(0, 0, 2), 0); if (ret) dev_err(tas_dev->dev, "init writes failed, err=%d", ret); @@ -1453,6 +1424,7 @@ static s32 tas_sdw_probe(struct sdw_slave *peripheral, tas_dev->dev = dev; tas_dev->sdw_peripheral = peripheral; tas_dev->hw_init = false; + tas_dev->fw_version = 0; mutex_init(&tas_dev->calib_lock); mutex_init(&tas_dev->pde_lock); diff --git a/sound/soc/codecs/tas2783.h b/sound/soc/codecs/tas2783.h index d5996c73526c..2f034617e356 100644 --- a/sound/soc/codecs/tas2783.h +++ b/sound/soc/codecs/tas2783.h @@ -35,10 +35,12 @@ #define TAS2783_AMP_LEVEL_MASK GENMASK(5, 1) #define PRAM_ADDR_START TASDEV_REG_SDW(0x8c, 0x01, 0x8) -#define PRAM_ADDR_END TASDEV_REG_SDW(0x8c, 0xff, 0x7f) +#define PRAM_ADDR_END TASDEV_REG_SDW(0x8c, 0xff, 0x80) #define YRAM_ADDR_START TASDEV_REG_SDW(0x00, 0x02, 0x8) #define YRAM_ADDR_END TASDEV_REG_SDW(0x00, 0x37, 0x7f) +#define TAS2783_FW_VERSION TASDEV_REG_SDW(0x00, 0x20, 0x3c) + /* Calibration data */ #define TAS2783_CAL_R0 TASDEV_REG_SDW(0, 0x16, 0x4C) #define TAS2783_CAL_INVR0 TASDEV_REG_SDW(0, 0x16, 0x5C) diff --git a/sound/soc/codecs/tas5086.c b/sound/soc/codecs/tas5086.c index 4d1c122c8f04..1ab82ef1c41d 100644 --- a/sound/soc/codecs/tas5086.c +++ b/sound/soc/codecs/tas5086.c @@ -743,11 +743,18 @@ static const struct snd_soc_dapm_route tas5086_dapm_routes[] = { }; +static const u64 tas5086_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops tas5086_dai_ops = { .hw_params = tas5086_hw_params, .set_sysclk = tas5086_set_dai_sysclk, .set_fmt = tas5086_set_dai_fmt, .mute_stream = tas5086_mute_stream, + .auto_selectable_formats = &tas5086_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver tas5086_dai = { diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index 8b5f9accf120..5e2560347461 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -352,10 +352,17 @@ static int tas571x_set_bias_level(struct snd_soc_component *component, return 0; } +static const u64 tas571x_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops tas571x_dai_ops = { .set_fmt = tas571x_set_dai_fmt, .hw_params = tas571x_hw_params, .mute_stream = tas571x_mute, + .auto_selectable_formats = &tas571x_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas5720.c b/sound/soc/codecs/tas5720.c index 0bbcfaadf3f2..2539a097e743 100644 --- a/sound/soc/codecs/tas5720.c +++ b/sound/soc/codecs/tas5720.c @@ -682,11 +682,20 @@ static const struct snd_soc_component_driver soc_component_dev_tas5722 = { #define TAS5720_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 tas5720_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops tas5720_speaker_dai_ops = { .hw_params = tas5720_hw_params, .set_fmt = tas5720_set_dai_fmt, .set_tdm_slot = tas5720_set_dai_tdm_slot, .mute_stream = tas5720_mute, + .auto_selectable_formats = &tas5720_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas6424.c b/sound/soc/codecs/tas6424.c index f5d50f8a1cfb..2718183e864c 100644 --- a/sound/soc/codecs/tas6424.c +++ b/sound/soc/codecs/tas6424.c @@ -378,11 +378,20 @@ static const struct snd_soc_component_driver soc_codec_dev_tas6424 = { .endianness = 1, }; +static const u64 tas6424_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops tas6424_speaker_dai_ops = { .hw_params = tas6424_hw_params, .set_fmt = tas6424_set_dai_fmt, .set_tdm_slot = tas6424_set_dai_tdm_slot, .mute_stream = tas6424_mute, + .auto_selectable_formats = &tas6424_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c index 404706b62156..82eeaceb1dac 100644 --- a/sound/soc/codecs/tas675x.c +++ b/sound/soc/codecs/tas675x.c @@ -1379,10 +1379,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction) int ret; if (direction == SNDRV_PCM_STREAM_CAPTURE) { - if (mute) - clear_bit(dai->id, &tas->active_capture_dais); - else - set_bit(dai->id, &tas->active_capture_dais); + assign_bit(dai->id, &tas->active_capture_dais, !mute); return 0; } @@ -1391,10 +1388,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction) * The TAS675x has two playback DAIs (main audio and LLP). * Only transition to SLEEP when ALL are muted. */ - if (mute) - clear_bit(dai->id, &tas->active_playback_dais); - else - set_bit(dai->id, &tas->active_playback_dais); + assign_bit(dai->id, &tas->active_playback_dais, !mute); /* Last playback stream */ if (mute && !READ_ONCE(tas->active_playback_dais)) { diff --git a/sound/soc/codecs/tfa9879.c b/sound/soc/codecs/tfa9879.c index f30479f1f53b..519103731768 100644 --- a/sound/soc/codecs/tfa9879.c +++ b/sound/soc/codecs/tfa9879.c @@ -248,10 +248,19 @@ static const struct regmap_config tfa9879_regmap = { .cache_type = REGCACHE_RBTREE, }; +static const u64 tfa9879_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops tfa9879_dai_ops = { .hw_params = tfa9879_hw_params, .mute_stream = tfa9879_mute_stream, .set_fmt = tfa9879_set_fmt, + .auto_selectable_formats = &tfa9879_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tlv320adc3xxx.c b/sound/soc/codecs/tlv320adc3xxx.c index d7d958ecd8db..79f4abc245dd 100644 --- a/sound/soc/codecs/tlv320adc3xxx.c +++ b/sound/soc/codecs/tlv320adc3xxx.c @@ -691,7 +691,7 @@ static const struct snd_kcontrol_new adc3xxx_snd_controls[] = { SOC_DOUBLE_R("AGC Capture Switch", ADC3XXX_LEFT_CHN_AGC_1, ADC3XXX_RIGHT_CHN_AGC_1, 7, 1, 0), SOC_DOUBLE_R_TLV("AGC Target Level Capture Volume", ADC3XXX_LEFT_CHN_AGC_1, - ADC3XXX_RIGHT_CHN_AGC_2, 4, 0x07, 1, agc_target_tlv), + ADC3XXX_RIGHT_CHN_AGC_1, 4, 0x07, 1, agc_target_tlv), SOC_DOUBLE_R_TLV("AGC Noise Threshold Capture Volume", ADC3XXX_LEFT_CHN_AGC_2, ADC3XXX_RIGHT_CHN_AGC_2, 1, 0x1f, 1, agc_thresh_tlv), SOC_DOUBLE_R_TLV("AGC Hysteresis Capture Volume", ADC3XXX_LEFT_CHN_AGC_2, @@ -1362,10 +1362,24 @@ static int adc3xxx_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) return 0; } +static const u64 adc3xxx_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops adc3xxx_dai_ops = { .hw_params = adc3xxx_hw_params, .set_sysclk = adc3xxx_set_dai_sysclk, .set_fmt = adc3xxx_set_dai_fmt, + .auto_selectable_formats = adc3xxx_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(adc3xxx_selectable_formats), }; static struct snd_soc_dai_driver adc3xxx_dai = { diff --git a/sound/soc/codecs/tlv320adcx140.c b/sound/soc/codecs/tlv320adcx140.c index 4eb9cea27276..c29584d77649 100644 --- a/sound/soc/codecs/tlv320adcx140.c +++ b/sound/soc/codecs/tlv320adcx140.c @@ -916,10 +916,22 @@ static int adcx140_set_dai_tdm_slot(struct snd_soc_dai *codec_dai, return 0; } +static const u64 adcx140_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops adcx140_dai_ops = { .hw_params = adcx140_hw_params, .set_fmt = adcx140_set_dai_fmt, .set_tdm_slot = adcx140_set_dai_tdm_slot, + .auto_selectable_formats = &adcx140_selectable_formats, + .num_auto_selectable_formats = 1, }; static int adcx140_configure_gpo(struct adcx140_priv *adcx140) diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index 04ec8fb5c6e5..f31ffa189580 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -507,6 +507,13 @@ static int tlv320aic23_set_bias_level(struct snd_soc_component *component, #define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 tlv320aic23_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops tlv320aic23_dai_ops = { .prepare = tlv320aic23_pcm_prepare, .hw_params = tlv320aic23_hw_params, @@ -514,6 +521,8 @@ static const struct snd_soc_dai_ops tlv320aic23_dai_ops = { .mute_stream = tlv320aic23_mute, .set_fmt = tlv320aic23_set_dai_fmt, .set_sysclk = tlv320aic23_set_dai_sysclk, + .auto_selectable_formats = &tlv320aic23_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c index 84e954311c1b..7dcc960a0b9e 100644 --- a/sound/soc/codecs/tlv320aic26.c +++ b/sound/soc/codecs/tlv320aic26.c @@ -208,11 +208,19 @@ static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) #define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\ SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) +static const u64 aic26_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops aic26_dai_ops = { .hw_params = aic26_hw_params, .mute_stream = aic26_mute, .set_sysclk = aic26_set_sysclk, .set_fmt = aic26_set_fmt, + .auto_selectable_formats = &aic26_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index 43bcbc5449e1..e0a8db435d6a 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -1366,27 +1366,12 @@ static int aic31xx_set_jack(struct snd_soc_component *component, static int aic31xx_codec_probe(struct snd_soc_component *component) { struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component); - int i, ret; + int ret; dev_dbg(aic31xx->dev, "## %s\n", __func__); aic31xx->component = component; - for (i = 0; i < ARRAY_SIZE(aic31xx->supplies); i++) { - aic31xx->disable_nb[i].nb.notifier_call = - aic31xx_regulator_event; - aic31xx->disable_nb[i].aic31xx = aic31xx; - ret = devm_regulator_register_notifier( - aic31xx->supplies[i].consumer, - &aic31xx->disable_nb[i].nb); - if (ret) { - dev_err(component->dev, - "Failed to request regulator notifier: %d\n", - ret); - return ret; - } - } - regcache_cache_only(aic31xx->regmap, true); regcache_mark_dirty(aic31xx->regmap); @@ -1422,11 +1407,22 @@ static const struct snd_soc_component_driver soc_codec_driver_aic31xx = { .endianness = 1, }; +static const u64 aic31xx_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops aic31xx_dai_ops = { .hw_params = aic31xx_hw_params, .set_sysclk = aic31xx_set_dai_sysclk, .set_fmt = aic31xx_set_dai_fmt, .mute_stream = aic31xx_dac_mute, + .auto_selectable_formats = &aic31xx_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -1791,6 +1787,19 @@ static int aic31xx_i2c_probe(struct i2c_client *i2c) if (ret) return dev_err_probe(aic31xx->dev, ret, "Failed to request supplies\n"); + for (i = 0; i < ARRAY_SIZE(aic31xx->supplies); i++) { + aic31xx->disable_nb[i].nb.notifier_call = aic31xx_regulator_event; + aic31xx->disable_nb[i].aic31xx = aic31xx; + ret = devm_regulator_register_notifier(aic31xx->supplies[i].consumer, + &aic31xx->disable_nb[i].nb); + if (ret) { + dev_err(aic31xx->dev, + "Failed to request regulator notifier: %d\n", + ret); + return ret; + } + } + aic31xx_configure_ocmv(aic31xx); if (aic31xx->irq > 0) { diff --git a/sound/soc/codecs/tlv320aic32x4-clk.c b/sound/soc/codecs/tlv320aic32x4-clk.c index deed61650e09..ad15ce9c6079 100644 --- a/sound/soc/codecs/tlv320aic32x4-clk.c +++ b/sound/soc/codecs/tlv320aic32x4-clk.c @@ -451,8 +451,8 @@ static struct aic32x4_clkdesc aic32x4_clkdesc_array[] = { static struct clk *aic32x4_register_clk(struct device *dev, struct aic32x4_clkdesc *desc) { - struct clk_init_data init; struct clk_aic32x4 *priv; + struct clk_init_data init = {}; const char *devname = dev_name(dev); init.ops = desc->ops; diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c index 9e0e4aa2b09c..2ee4a9d2abf2 100644 --- a/sound/soc/codecs/tlv320aic32x4.c +++ b/sound/soc/codecs/tlv320aic32x4.c @@ -48,8 +48,18 @@ struct aic32x4_priv { enum aic32x4_type type; unsigned int fmt; + + struct clk_bulk_data clocks[7]; }; +#define AIC32X4_CLK_CODEC_CLKIN 0 +#define AIC32X4_CLK_PLL 1 +#define AIC32X4_CLK_NADC 2 +#define AIC32X4_CLK_MADC 3 +#define AIC32X4_CLK_NDAC 4 +#define AIC32X4_CLK_MDAC 5 +#define AIC32X4_CLK_BDIV 6 + static int aic32x4_reset_adc(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -591,14 +601,10 @@ static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_component *component = codec_dai->component; + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); struct clk *mclk; - struct clk *pll; - pll = devm_clk_get(component->dev, "pll"); - if (IS_ERR(pll)) - return PTR_ERR(pll); - - mclk = clk_get_parent(pll); + mclk = clk_get_parent(aic32x4->clocks[AIC32X4_CLK_PLL].clk); return clk_set_rate(mclk, freq); } @@ -750,17 +756,7 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component, unsigned long adc_clock_rate, dac_clock_rate; int ret; - struct clk_bulk_data clocks[] = { - { .id = "pll" }, - { .id = "nadc" }, - { .id = "madc" }, - { .id = "ndac" }, - { .id = "mdac" }, - { .id = "bdiv" }, - }; - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; + struct clk_bulk_data *clocks = &aic32x4->clocks[AIC32X4_CLK_PLL]; ret = aic32x4_configure_rate(component, sample_rate, &aosr, &adc_resource_class, &dac_resource_class, @@ -876,18 +872,15 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); int ret; struct clk_bulk_data clocks[] = { - { .id = "madc" }, - { .id = "mdac" }, - { .id = "bdiv" }, + aic32x4->clocks[AIC32X4_CLK_MADC], + aic32x4->clocks[AIC32X4_CLK_MDAC], + aic32x4->clocks[AIC32X4_CLK_BDIV], }; - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; - switch (level) { case SND_SOC_BIAS_ON: ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks); @@ -916,11 +909,19 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component, | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_3LE \ | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 aic32x4_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; static const struct snd_soc_dai_ops aic32x4_ops = { .hw_params = aic32x4_hw_params, .mute_stream = aic32x4_mute, .set_fmt = aic32x4_set_dai_fmt, .set_sysclk = aic32x4_set_dai_sysclk, + .auto_selectable_formats = &aic32x4_selectable_formats, + .num_auto_selectable_formats = 1, .no_capture_mute = 1, }; @@ -962,23 +963,16 @@ static int aic32x4_component_probe(struct snd_soc_component *component) { struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); u32 tmp_reg; - int ret; - struct clk_bulk_data clocks[] = { - { .id = "codec_clkin" }, - { .id = "pll" }, - { .id = "bdiv" }, - { .id = "mdac" }, - }; - - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; + struct clk *codec_clkin = aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].clk; + struct clk *pll = aic32x4->clocks[AIC32X4_CLK_PLL].clk; + struct clk *bdiv = aic32x4->clocks[AIC32X4_CLK_BDIV].clk; + struct clk *mdac = aic32x4->clocks[AIC32X4_CLK_MDAC].clk; aic32x4_setup_gpios(component); - clk_set_parent(clocks[0].clk, clocks[1].clk); - clk_set_parent(clocks[2].clk, clocks[3].clk); + clk_set_parent(codec_clkin, pll); + clk_set_parent(bdiv, mdac); /* Power platform configuration */ if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) { @@ -1063,7 +1057,7 @@ static const struct snd_kcontrol_new aic32x4_tas2505_snd_controls[] = { SOC_ENUM("DAC Playback PowerTune Switch", l_ptm_enum), SOC_SINGLE_TLV("HP Driver Gain Volume", - AIC32X4_HPLGAIN, 0, 0x74, 1, tlv_tas_driver_gain), + TAS2505_HPVOL, 0, 0x74, 1, tlv_tas_driver_gain), SOC_SINGLE("HP DAC Playback Switch", AIC32X4_HPLGAIN, 6, 1, 1), SOC_SINGLE_TLV("Speaker Driver Playback Volume", @@ -1119,23 +1113,16 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component) { struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component); u32 tmp_reg; - int ret; - struct clk_bulk_data clocks[] = { - { .id = "codec_clkin" }, - { .id = "pll" }, - { .id = "bdiv" }, - { .id = "mdac" }, - }; - - ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks); - if (ret) - return ret; + struct clk *codec_clkin = aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].clk; + struct clk *pll = aic32x4->clocks[AIC32X4_CLK_PLL].clk; + struct clk *bdiv = aic32x4->clocks[AIC32X4_CLK_BDIV].clk; + struct clk *mdac = aic32x4->clocks[AIC32X4_CLK_MDAC].clk; aic32x4_setup_gpios(component); - clk_set_parent(clocks[0].clk, clocks[1].clk); - clk_set_parent(clocks[2].clk, clocks[3].clk); + clk_set_parent(codec_clkin, pll); + clk_set_parent(bdiv, mdac); /* Power platform configuration */ if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) @@ -1354,6 +1341,21 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap, if (ret) goto err_disable_regulators; + aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].id = "codec_clkin"; + aic32x4->clocks[AIC32X4_CLK_PLL].id = "pll"; + aic32x4->clocks[AIC32X4_CLK_NADC].id = "nadc"; + aic32x4->clocks[AIC32X4_CLK_MADC].id = "madc"; + aic32x4->clocks[AIC32X4_CLK_NDAC].id = "ndac"; + aic32x4->clocks[AIC32X4_CLK_MDAC].id = "mdac"; + aic32x4->clocks[AIC32X4_CLK_BDIV].id = "bdiv"; + + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(aic32x4->clocks), + aic32x4->clocks); + if (ret) { + dev_err(dev, "Failed to get clocks\n"); + goto err_disable_regulators; + } + switch (aic32x4->type) { case AIC32X4_TYPE_TAS2505: ret = devm_snd_soc_register_component(dev, diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h index cfab6f8ce5ac..d0c2e1002e99 100644 --- a/sound/soc/codecs/tlv320aic32x4.h +++ b/sound/soc/codecs/tlv320aic32x4.h @@ -94,6 +94,7 @@ int aic32x4_register_clocks(struct device *dev, const char *mclk_name); #define AIC32X4_LOLGAIN AIC32X4_REG(1, 18) #define AIC32X4_LORGAIN AIC32X4_REG(1, 19) #define AIC32X4_HEADSTART AIC32X4_REG(1, 20) +#define TAS2505_HPVOL AIC32X4_REG(1, 22) #define TAS2505_SPK AIC32X4_REG(1, 45) #define TAS2505_SPKVOL1 AIC32X4_REG(1, 46) #define TAS2505_SPKVOL2 AIC32X4_REG(1, 48) diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index b38393a8130f..be83111accaa 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1500,6 +1500,18 @@ static int aic3x_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 aic3x_selectable_formats[] = { + /* Hi Priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF, + /* Low Priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops aic3x_dai_ops = { .hw_params = aic3x_hw_params, .prepare = aic3x_prepare, @@ -1507,6 +1519,8 @@ static const struct snd_soc_dai_ops aic3x_dai_ops = { .set_sysclk = aic3x_set_dai_sysclk, .set_fmt = aic3x_set_dai_fmt, .set_tdm_slot = aic3x_set_dai_tdm_slot, + .auto_selectable_formats = aic3x_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(aic3x_selectable_formats), .no_capture_mute = 1, }; @@ -1627,24 +1641,9 @@ static int aic3x_init(struct snd_soc_component *component) static int aic3x_component_probe(struct snd_soc_component *component) { struct aic3x_priv *aic3x = snd_soc_component_get_drvdata(component); - int ret, i; aic3x->component = component; - for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) { - aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event; - aic3x->disable_nb[i].aic3x = aic3x; - ret = devm_regulator_register_notifier( - aic3x->supplies[i].consumer, - &aic3x->disable_nb[i].nb); - if (ret) { - dev_err(component->dev, - "Failed to request regulator notifier: %d\n", - ret); - return ret; - } - } - regcache_mark_dirty(aic3x->regmap); aic3x_init(component); @@ -1845,6 +1844,19 @@ int aic3x_probe(struct device *dev, struct regmap *regmap, kernel_ulong_t driver if (ret) return dev_err_probe(dev, ret, "Failed to request supplies\n"); + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) { + aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event; + aic3x->disable_nb[i].aic3x = aic3x; + ret = devm_regulator_register_notifier(aic3x->supplies[i].consumer, + &aic3x->disable_nb[i].nb); + if (ret) { + dev_err(dev, + "Failed to request regulator notifier: %d\n", + ret); + return ret; + } + } + aic3x_configure_ocmv(dev, aic3x); ret = devm_snd_soc_register_component(dev, &soc_component_dev_aic3x, &aic3x_dai, 1); diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 9bd2ddd8dacd..b91ab42882d1 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -914,7 +914,7 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream, /* OSC calibration time */ dac33_write(component, DAC33_CALIB_TIME, 96); - /* adjustment treshold & step */ + /* adjustment threshold & step */ dac33_write(component, DAC33_INT_OSC_CTRL_B, DAC33_ADJTHRSHLD(2) | DAC33_ADJSTEP(1)); @@ -1435,6 +1435,12 @@ static const struct snd_soc_component_driver soc_component_dev_tlv320dac33 = { SNDRV_PCM_RATE_48000) #define DAC33_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 dac33_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops dac33_dai_ops = { .startup = dac33_startup, .shutdown = dac33_shutdown, @@ -1443,6 +1449,8 @@ static const struct snd_soc_dai_ops dac33_dai_ops = { .delay = dac33_dai_delay, .set_sysclk = dac33_set_dai_sysclk, .set_fmt = dac33_set_dai_fmt, + .auto_selectable_formats = &dac33_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver dac33_dai = { diff --git a/sound/soc/codecs/tscs454.c b/sound/soc/codecs/tscs454.c index b70c9d931e1e..e772dd39a849 100644 --- a/sound/soc/codecs/tscs454.c +++ b/sound/soc/codecs/tscs454.c @@ -3233,6 +3233,17 @@ static int tscs454_prepare(struct snd_pcm_substream *substream, return 0; } +static const u64 tscs454_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static struct snd_soc_dai_ops const tscs454_dai1_ops = { .set_sysclk = tscs454_set_sysclk, .set_bclk_ratio = tscs454_set_bclk_ratio, @@ -3241,6 +3252,8 @@ static struct snd_soc_dai_ops const tscs454_dai1_ops = { .hw_params = tscs454_hw_params, .hw_free = tscs454_hw_free, .prepare = tscs454_prepare, + .auto_selectable_formats = &tscs454_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_ops const tscs454_dai23_ops = { @@ -3251,6 +3264,8 @@ static struct snd_soc_dai_ops const tscs454_dai23_ops = { .hw_params = tscs454_hw_params, .hw_free = tscs454_hw_free, .prepare = tscs454_prepare, + .auto_selectable_formats = &tscs454_selectable_formats, + .num_auto_selectable_formats = 1, }; static int tscs454_probe(struct snd_soc_component *component) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 9476cdfd4dde..7117af5a1a56 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -202,34 +202,31 @@ static void twl4030_get_board_param_values(struct twl4030_board_params *board_params, struct device_node *node) { - int value; - of_property_read_u32(node, "ti,digimic_delay", &board_params->digimic_delay); of_property_read_u32(node, "ti,ramp_delay_value", &board_params->ramp_delay_value); of_property_read_u32(node, "ti,offset_cncl_path", &board_params->offset_cncl_path); - if (!of_property_read_u32(node, "ti,hs_extmute", &value)) - board_params->hs_extmute = value; + board_params->hs_extmute = of_property_read_bool(node, "ti,hs_extmute"); if (of_property_present(node, "ti,hs_extmute_gpio")) board_params->hs_extmute = 1; } static struct twl4030_board_params* -twl4030_get_board_params(struct snd_soc_component *component) +twl4030_get_board_params(struct device *dev) { struct twl4030_board_params *board_params = NULL; struct device_node *twl4030_codec_node = NULL; - twl4030_codec_node = of_get_child_by_name(component->dev->parent->of_node, + twl4030_codec_node = of_get_child_by_name(dev->parent->of_node, "codec"); if (twl4030_codec_node) { - board_params = devm_kzalloc(component->dev, + board_params = devm_kzalloc(dev, sizeof(struct twl4030_board_params), GFP_KERNEL); if (!board_params) { of_node_put(twl4030_codec_node); - return NULL; + return ERR_PTR(-ENOMEM); } twl4030_get_board_param_values(board_params, twl4030_codec_node); of_node_put(twl4030_codec_node); @@ -238,21 +235,22 @@ twl4030_get_board_params(struct snd_soc_component *component) return board_params; } -static int twl4030_init_chip(struct snd_soc_component *component) +static int twl4030_get_hw_params(struct device *dev, + struct twl4030_priv *twl4030) { struct twl4030_board_params *board_params; - struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); - u8 reg, byte; - int i = 0; - board_params = twl4030_get_board_params(component); + board_params = twl4030_get_board_params(dev); + if (IS_ERR(board_params)) + return PTR_ERR(board_params); if (board_params && board_params->hs_extmute) { - board_params->hs_extmute_gpio = devm_gpiod_get_optional(component->dev, + board_params->hs_extmute_gpio = devm_gpiod_get_optional(dev, "ti,hs_extmute", GPIOD_OUT_LOW); if (IS_ERR(board_params->hs_extmute_gpio)) - return dev_err_probe(component->dev, PTR_ERR(board_params->hs_extmute_gpio), + return dev_err_probe(dev, + PTR_ERR(board_params->hs_extmute_gpio), "Failed to get hs_extmute GPIO\n"); if (board_params->hs_extmute_gpio) { @@ -260,7 +258,7 @@ static int twl4030_init_chip(struct snd_soc_component *component) } else { u8 pin_mux; - dev_info(component->dev, "use TWL4030 GPIO6\n"); + dev_info(dev, "use TWL4030 GPIO6\n"); /* Set TWL4030 GPIO6 as EXTMUTE signal */ twl_i2c_read_u8(TWL4030_MODULE_INTBR, &pin_mux, @@ -272,6 +270,18 @@ static int twl4030_init_chip(struct snd_soc_component *component) } } + twl4030->board_params = board_params; + + return 0; +} + +static int twl4030_soc_probe(struct snd_soc_component *component) +{ + struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component); + struct twl4030_board_params *board_params = twl4030->board_params; + u8 reg, byte; + int i = 0; + /* Initialize the local ctl register cache */ tw4030_init_ctl_cache(twl4030); @@ -291,8 +301,6 @@ static int twl4030_init_chip(struct snd_soc_component *component) if (!board_params) return 0; - twl4030->board_params = board_params; - reg = twl4030_read(component, TWL4030_REG_HS_POPN_SET); reg &= ~TWL4030_RAMP_DELAY; reg |= (board_params->ramp_delay_value << 2); @@ -2097,6 +2105,10 @@ static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate) #define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000) #define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 twl4030_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A; + static const struct snd_soc_dai_ops twl4030_dai_hifi_ops = { .startup = twl4030_startup, .shutdown = twl4030_shutdown, @@ -2104,8 +2116,14 @@ static const struct snd_soc_dai_ops twl4030_dai_hifi_ops = { .set_sysclk = twl4030_set_dai_sysclk, .set_fmt = twl4030_set_dai_fmt, .set_tristate = twl4030_set_tristate, + .auto_selectable_formats = &twl4030_selectable_formats, + .num_auto_selectable_formats = 1, }; +static const u64 twl4030_voice_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops twl4030_dai_voice_ops = { .startup = twl4030_voice_startup, .shutdown = twl4030_voice_shutdown, @@ -2113,6 +2131,8 @@ static const struct snd_soc_dai_ops twl4030_dai_voice_ops = { .set_sysclk = twl4030_voice_set_dai_sysclk, .set_fmt = twl4030_voice_set_dai_fmt, .set_tristate = twl4030_voice_set_tristate, + .auto_selectable_formats = &twl4030_voice_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver twl4030_dai[] = { @@ -2152,21 +2172,6 @@ static struct snd_soc_dai_driver twl4030_dai[] = { }, }; -static int twl4030_soc_probe(struct snd_soc_component *component) -{ - struct twl4030_priv *twl4030; - - twl4030 = devm_kzalloc(component->dev, sizeof(struct twl4030_priv), - GFP_KERNEL); - if (!twl4030) - return -ENOMEM; - snd_soc_component_set_drvdata(component, twl4030); - /* Set the defaults, and power up the codec */ - twl4030->sysclk = twl4030_audio_get_mclk() / 1000; - - return twl4030_init_chip(component); -} - static const struct snd_soc_component_driver soc_component_dev_twl4030 = { .probe = twl4030_soc_probe, .read = twl4030_read, @@ -2184,6 +2189,22 @@ static const struct snd_soc_component_driver soc_component_dev_twl4030 = { static int twl4030_codec_probe(struct platform_device *pdev) { + struct twl4030_priv *twl4030; + int ret; + + twl4030 = devm_kzalloc(&pdev->dev, sizeof(*twl4030), GFP_KERNEL); + if (!twl4030) + return -ENOMEM; + + /* Set the defaults, and power up the codec */ + twl4030->sysclk = twl4030_audio_get_mclk() / 1000; + + ret = twl4030_get_hw_params(&pdev->dev, twl4030); + if (ret) + return ret; + + platform_set_drvdata(pdev, twl4030); + return devm_snd_soc_register_component(&pdev->dev, &soc_component_dev_twl4030, twl4030_dai, ARRAY_SIZE(twl4030_dai)); diff --git a/sound/soc/codecs/uda1334.c b/sound/soc/codecs/uda1334.c index 54c5cb5b3d4b..7ae6afdba38b 100644 --- a/sound/soc/codecs/uda1334.c +++ b/sound/soc/codecs/uda1334.c @@ -193,12 +193,18 @@ static int uda1334_mute_stream(struct snd_soc_dai *dai, int mute, int stream) #define UDA1334_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) +static const u64 uda1334_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops uda1334_dai_ops = { .startup = uda1334_startup, .shutdown = uda1334_shutdown, .set_sysclk = uda1334_set_dai_sysclk, .set_fmt = uda1334_set_fmt, .mute_stream = uda1334_mute_stream, + .auto_selectable_formats = &uda1334_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver uda1334_dai = { diff --git a/sound/soc/codecs/uda1342.c b/sound/soc/codecs/uda1342.c index 4e2d6240a869..63d087590ac9 100644 --- a/sound/soc/codecs/uda1342.c +++ b/sound/soc/codecs/uda1342.c @@ -223,6 +223,11 @@ static const struct snd_soc_dapm_route uda1342_dapm_routes[] = { { "VOUTR", NULL, "DAC" }, }; +static const u64 uda1342_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops uda1342_dai_ops = { .startup = uda1342_startup, .shutdown = uda1342_shutdown, @@ -230,6 +235,8 @@ static const struct snd_soc_dai_ops uda1342_dai_ops = { .mute_stream = uda1342_mute, .set_sysclk = uda1342_set_dai_sysclk, .set_fmt = uda1342_set_dai_fmt, + .auto_selectable_formats = &uda1342_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver uda1342_dai = { diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 1dd09d10fc4a..02adc534dfb0 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -653,11 +653,18 @@ static int uda1380_set_bias_level(struct snd_soc_component *component, SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +static const u64 uda1380_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops uda1380_dai_ops = { .hw_params = uda1380_pcm_hw_params, .shutdown = uda1380_pcm_shutdown, .trigger = uda1380_trigger, .set_fmt = uda1380_set_dai_fmt_both, + .auto_selectable_formats = &uda1380_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops uda1380_dai_ops_playback = { @@ -665,6 +672,8 @@ static const struct snd_soc_dai_ops uda1380_dai_ops_playback = { .shutdown = uda1380_pcm_shutdown, .trigger = uda1380_trigger, .set_fmt = uda1380_set_dai_fmt_playback, + .auto_selectable_formats = &uda1380_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops uda1380_dai_ops_capture = { @@ -672,6 +681,8 @@ static const struct snd_soc_dai_ops uda1380_dai_ops_capture = { .shutdown = uda1380_pcm_shutdown, .trigger = uda1380_trigger, .set_fmt = uda1380_set_dai_fmt_capture, + .auto_selectable_formats = &uda1380_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver uda1380_dai[] = { diff --git a/sound/soc/codecs/wcd-mbhc-v2.c b/sound/soc/codecs/wcd-mbhc-v2.c index bb0c8478a8eb..850be84d65ce 100644 --- a/sound/soc/codecs/wcd-mbhc-v2.c +++ b/sound/soc/codecs/wcd-mbhc-v2.c @@ -864,7 +864,7 @@ static int wcd_measure_adc_continuous(struct wcd_mbhc *mbhc) u8 adc_en; /* Pre-requisites for ADC continuous measurement */ - /* Read legacy electircal detection and disable */ + /* Read legacy electrical detection and disable */ wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, 0x00); /* Set ADC to continuous measurement */ wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 1); @@ -965,7 +965,7 @@ static int wcd_check_cross_conn(struct wcd_mbhc *mbhc) if (wcd_mbhc_read_field(mbhc, WCD_MBHC_HPH_PA_EN)) return -EINVAL; - /* Read legacy electircal detection and disable */ + /* Read legacy electrical detection and disable */ elect_ctl = wcd_mbhc_read_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC); wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, 0); @@ -1470,14 +1470,14 @@ int wcd_dt_parse_mbhc_data(struct device *dev, struct wcd_mbhc_config *cfg) ret = of_property_read_u32(np, "qcom,mbhc-headset-vthreshold-microvolt", µvolt); if (ret) - dev_dbg(dev, "missing qcom,mbhc-hs-mic-max-vthreshold--microvolt in dt node\n"); + dev_dbg(dev, "missing qcom,mbhc-headset-vthreshold-microvolt in dt node\n"); else cfg->hs_thr = microvolt/1000; ret = of_property_read_u32(np, "qcom,mbhc-headphone-vthreshold-microvolt", µvolt); if (ret) - dev_dbg(dev, "missing qcom,mbhc-hs-mic-min-vthreshold-microvolt entry\n"); + dev_dbg(dev, "missing qcom,mbhc-headphone-vthreshold-microvolt entry\n"); else cfg->hph_thr = microvolt/1000; diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c index 40de279a9750..a98d1fd78925 100644 --- a/sound/soc/codecs/wcd9335.c +++ b/sound/soc/codecs/wcd9335.c @@ -5096,6 +5096,13 @@ static int wcd9335_slim_probe(struct slim_device *slim) return 0; } +static void wcd9335_put_device_action(void *data) +{ + struct device *dev = data; + + put_device(dev); +} + static int wcd9335_slim_status(struct slim_device *sdev, enum slim_device_status status) { @@ -5120,28 +5127,40 @@ static int wcd9335_slim_status(struct slim_device *sdev, return -EINVAL; } + ret = devm_add_action_or_reset(dev, wcd9335_put_device_action, + &wcd->slim_ifc_dev->dev); + if (ret) + return ret; + slim_get_logical_addr(wcd->slim_ifc_dev); wcd->regmap = regmap_init_slimbus(sdev, &wcd9335_regmap_config); - if (IS_ERR(wcd->regmap)) + if (IS_ERR(wcd->regmap)) { + put_device(&wcd->slim_ifc_dev->dev); return dev_err_probe(dev, PTR_ERR(wcd->regmap), "Failed to allocate slim register map\n"); + } wcd->if_regmap = regmap_init_slimbus(wcd->slim_ifc_dev, &wcd9335_ifc_regmap_config); - if (IS_ERR(wcd->if_regmap)) + if (IS_ERR(wcd->if_regmap)) { + put_device(&wcd->slim_ifc_dev->dev); return dev_err_probe(dev, PTR_ERR(wcd->if_regmap), "Failed to allocate ifc register map\n"); + } ret = wcd9335_bring_up(wcd); if (ret) { dev_err(dev, "Failed to bringup WCD9335\n"); + put_device(&wcd->slim_ifc_dev->dev); return ret; } ret = wcd9335_irq_init(wcd); - if (ret) + if (ret) { + put_device(&wcd->slim_ifc_dev->dev); return ret; + } wcd9335_probe(wcd); diff --git a/sound/soc/codecs/wcd934x.c b/sound/soc/codecs/wcd934x.c index caca5cc25100..0ed24cca088f 100644 --- a/sound/soc/codecs/wcd934x.c +++ b/sound/soc/codecs/wcd934x.c @@ -2130,9 +2130,9 @@ static struct clk *wcd934x_register_mclk_output(struct wcd934x_codec *wcd) struct device *dev = wcd->dev; struct device_node *np = dev->parent->of_node; const char *parent_clk_name = NULL; + struct clk_init_data init = {}; const char *clk_name = "mclk"; struct clk_hw *hw; - struct clk_init_data init; int ret; if (of_property_read_u32(np, "clock-frequency", &wcd->rate)) diff --git a/sound/soc/codecs/wcd9378-sdca.c b/sound/soc/codecs/wcd9378-sdca.c new file mode 100644 index 000000000000..7c15f885390c --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdca.c @@ -0,0 +1,1080 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright (c) 2025 Qualcomm Technologies, Inc. All rights reserved. + +/* + * WCD9378 (Tambora) SDCA SimpleJack codec. Supplies the static SDCA + * topology on DT platforms where no ACPI/DisCo enumeration exists. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcd9378-sdca.h" + +struct wcd9378_priv { + struct sdca_class_drv class; +}; + +static struct sdca_entity wcd9378_sdca_entities[]; +/* + * Entity array index map. Order matches the ASL entity-id-list; + * Function (Entity 0) is last. + * + * [0] E001 IT 41 (0x1) [12] E00F IT 33 (0xf) + * [1] E002 CS 41 (0x2) [13] E010 PDE 34 (0x10) + * [2] E003 MFPU 21 (0x3) [14] E011 FU 33 (0x11) + * [3] E004 XU 42 (0x4) [15] E012 SU 35 (0x12) + * [4] E007 SU 43 (0x7) [16] E013 XU 36 (0x13) + * [5] E008 SU 45 (0x8) [17] E015 CS 36 (0x15) + * [6] E009 PDE 47 (0x9) [18] E016 OT 36 (0x16) + * [7] E00A OT 43 (0xa) [19] E017 MFPU 236 (0x17) + * [8] E00B OT 45 (0xb) [20] E018 CS 236 (0x18) + * [9] E00C GE 35 (0xc) [21] E019 OT 236 (0x19) + * [10] E00D IT 131 (0xd) [22] E006 FU 6 (0x6) + * [11] E00E CS 131 (0xe) [23] E000 Function (0x0) + */ +#define QSJ_IT41 0 +#define QSJ_CS41 1 +#define QSJ_MFPU21 2 +#define QSJ_XU42 3 +#define QSJ_SU43 4 +#define QSJ_SU45 5 +#define QSJ_PDE47 6 +#define QSJ_OT43 7 +#define QSJ_OT45 8 +#define QSJ_GE35 9 +#define QSJ_IT131 10 +#define QSJ_CS131 11 +#define QSJ_IT33 12 +#define QSJ_PDE34 13 +#define QSJ_FU33 14 +#define QSJ_SU35 15 +#define QSJ_XU36 16 +#define QSJ_CS36 17 +#define QSJ_OT36 18 +#define QSJ_MFPU236 19 +#define QSJ_CS236 20 +#define QSJ_OT236 21 +#define QSJ_FU6 22 + +/* Range data: {cols, rows, data[cols*rows]}, transcribed from ASL. */ + +/* IT 41 Usage: HIFI (0x2). ULP (0x3) does not route to the HPH analog driver. */ +static u32 range_it41_usage_data[] = { + /* usage, CBN, sample_rate, sample_width, full_scale, noise_floor, tag */ + 0x2, 0x2d0, 0xbb80, 0x10, 0x0, 0x0, 0x0, +}; + +static u32 range_it41_cluster_data[] = { 0x1, 0x1 }; + +/* IT 41 DataPort selector: DP6 (HPH render). */ +static u32 range_it41_dp_data[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* CS 41 SampleRateIndex: 1 -> 48kHz PCM. */ +static u32 range_cs41_sr_data[] = { 0x1, 0xbb80 }; + +/* SU selector: disconnected / connected. */ +static u32 range_su_sel_data[] = { 0x0, 0x1 }; + +/* PDE Requested_PS: PS0 / PS3. */ +static u32 range_pde_req_ps_data[] = { 0x0, 0x3 }; + +/* + * GE 35 SelectedMode -> terminal type. + * + * Tambora MBHC only handles mechanical detection; ADC HP/HS + * discrimination is not implemented, so modes 0 (Unplugged) and 1 + * (Unknown) are aliased to Headphone (mode 4) to keep the DAPM path + * alive. Mode 2 (Line-out) is not fitted on this board. + */ +static u32 range_ge35_mode_data[] = { + 0x0, 0x6c0, /* Unplugged -> HPH (alias) */ + 0x1, 0x6c0, /* Unknown -> HPH (alias) */ + 0x3, 0x6d0, /* Headset */ + 0x4, 0x6c0, /* Headphone */ +}; + +/* IT 131 Usage: optimization render stream at 192kHz. */ +static u32 range_it131_usage_data[] = { + 0x3, 0x334, 0x2ee00, 0x8, 0x0, 0x0, 0x0, +}; + +static u32 range_it131_cluster_data[] = { 0x1, 0x3 }; + +/* IT 131 DataPort selector: DP7. */ +static u32 range_it131_dp_data[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* CS 131 SampleRateIndex: 1 -> 192kHz. */ +static u32 range_cs131_sr_data[] = { 0x1, 0x2ee00 }; + +/* IT 33 MIC_BIAS: SDCA MIC_BIAS index 0x5 (2.75V). */ +static u32 range_it33_micbias_data[] = { 0x5 }; + +static u32 range_it33_usage_data[] = { + 0x1, 0x2c6, 0x0, 0x0, 0x0, 0x0, 0x0, +}; + +static u32 range_it33_cluster_data[] = { 0x1, 0x2 }; + +static u32 range_cs36_sr_data[] = { 0x1, 0xbb80 }; + +/* OT 36 Usage: PDM capture, host-visible 48kHz/16-bit PCM. */ +static u32 range_ot36_usage_data[] = { + 0x1, 0x2c6, 0xbb80, 0x10, 0x0, 0x0, 0x0, +}; + +/* OT 36 DataPort selector: DP2. */ +static u32 range_ot36_dp_data[] = { + 0xff, 0xff, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static u32 range_cs236_sr_data[] = { 0x1, 0xbb80 }; + +/* OT 236 Usage: optimization capture at 192kHz (clocked by CS 131). */ +static u32 range_ot236_usage_data[] = { + 0x1, 0x334, 0x2ee00, 0x8, 0x0, 0x0, 0x0, +}; + +/* OT 236 DataPort selector: DP5. */ +static u32 range_ot236_dp_data[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0x5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* Entity 0 (Function) Control Values */ +static int ctrl_fun_sdca_ver_vals[] = { 0x11 }; +static int ctrl_fun_type_vals[] = { 0x08 }; /* SimpleJack */ +static int ctrl_fun_man_id_vals[] = { 0x0217 }; +static int ctrl_fun_id_vals[] = { 0x3 }; +static int ctrl_fun_ver_vals[] = { 0x0 }; +static int ctrl_dev_sdca_ver_vals[] = { 0x11 }; + +/* Entity 1 (IT 41) Control Values */ +static int ctrl_it41_latency_vals[] = { 0x0 }; +static int ctrl_it41_cluster_vals[] = { 0x1 }; +static int ctrl_it41_dp_vals[] = { 0x6 }; + +/* Entity 2 (CS 41) Control Values */ +static int ctrl_cs41_sr_vals[] = { 0x1 }; + +/* Entity 3 (MFPU 21) Control Values */ +static int ctrl_mfpu21_bypass_vals[] = { 0x1 }; + +/* Entity 4 (XU 42) Control Values */ +static int ctrl_xu42_id_vals[] = { 0x2131 }; +static int ctrl_xu42_ver_vals[] = { 0x1 }; + +/* Entity 0xd (IT 131) Control Values */ +static int ctrl_it131_latency_vals[] = { 0x0 }; +static int ctrl_it131_cluster_vals[] = { 0x1 }; +static int ctrl_it131_dp_vals[] = { 0x7 }; + +/* Entity 0xe (CS 131) Control Values */ +static int ctrl_cs131_sr_vals[] = { 0x1 }; + +/* IT 33 MIC_BIAS: fixed 2.75V (SDCA MIC_BIAS index 0x5), reapplied by PDE34. */ +static int ctrl_it33_micbias_vals[] = { 0x5 }; +static int ctrl_it33_latency_vals[] = { 0x0 }; +static int ctrl_it33_cluster_vals[] = { 0x1 }; + +/* Entity 0x13 (XU 36) Control Values */ +static int ctrl_xu36_bypass_vals[] = { 0x1 }; +static int ctrl_xu36_id_vals[] = { 0x2131 }; +static int ctrl_xu36_ver_vals[] = { 0x1 }; + +/* Entity 0x15 (CS 36) Control Values */ +static int ctrl_cs36_sr_vals[] = { 0x1 }; + +/* Entity 0x16 (OT 36) Control Values */ +static int ctrl_ot36_latency_vals[] = { 0x0 }; +static int ctrl_ot36_dp_vals[] = { 0x2 }; + +/* Entity 0x17 (MFPU 236) Control Values */ +static int ctrl_mfpu236_bypass_vals[] = { 0x1 }; + +/* Entity 0x18 (CS 236) Control Values */ +static int ctrl_cs236_sr_vals[] = { 0x1 }; + +/* Entity 0x19 (OT 236) Control Values */ +static int ctrl_ot236_latency_vals[] = { 0x0 }; +static int ctrl_ot236_dp_vals[] = { 0x5 }; + +/* Entity 0 (Function) Controls */ +static struct sdca_control entity0_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_COMMIT_GROUP_MASK_NAME }, + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_sdca_ver_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_SDCA_VERSION_NAME }, + { .sel = 0x5, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_type_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_TYPE_NAME }, + { .sel = 0x6, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_man_id_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_MANUFACTURER_ID_NAME }, + { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_id_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_ID_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_fun_ver_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_FUNCTION_VERSION_NAME }, + { .sel = 0x9, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_EXTENSION_ID_NAME }, + { .sel = 0xa, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_EXTENSION_VERSION_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RW1C, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_STATUS_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_RW1S, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_FUNCTION_ACTION_NAME }, + { .sel = 0x2c, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_DEVICE_MANUFACTURER_ID_NAME }, + { .sel = 0x2d, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_DEVICE_PART_ID_NAME }, + { .sel = 0x2e, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_DEVICE_VERSION_NAME }, + { .sel = 0x2f, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_dev_sdca_ver_vals, .has_fixed = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DEVICE_SDCA_VERSION_NAME }, +}; + +/* Entity 1 (IT 41) Controls */ +static struct sdca_control entity_it41_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_it41_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_it41_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it41_cluster_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_it41_cluster_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CLUSTERINDEX_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it41_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_it41_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +/* Entity 2 (CS 41) Controls */ +static struct sdca_control entity_cs41_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs41_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs41_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* Entity 3 (MFPU 21) Controls */ +static struct sdca_entity *entity_mfpu21_sources[] = { + &wcd9378_sdca_entities[QSJ_IT41], + &wcd9378_sdca_entities[QSJ_IT131], +}; + +static struct sdca_control entity_mfpu21_controls[] = { + { .sel = SDCA_CTL_MFPU_BYPASS, .mode = SDCA_ACCESS_MODE_DC, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .values = ctrl_mfpu21_bypass_vals, .has_fixed = true, .label = SDCA_CTL_BYPASS_NAME }, +}; + +/* Entity 4 (XU 42) Controls */ +static struct sdca_entity *entity_xu42_sources[] = { &wcd9378_sdca_entities[QSJ_MFPU21] }; + +static struct sdca_control entity_xu42_controls[] = { + { .sel = SDCA_CTL_XU_BYPASS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .label = SDCA_CTL_BYPASS_NAME }, + { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu42_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu42_ver_vals, .has_fixed = true, .label = SDCA_CTL_XU_VERSION_NAME }, +}; + +/* + * SU Selector is RO but its value is deterministic from the current + * GE mode which the class framework drives; leave it non-volatile so + * regmap caches it and DAPM reads succeed when the codec is + * runtime-suspended. + */ + +/* Entity 7 (SU 43) Controls */ +static struct sdca_entity *entity_su43_sources[] = { &wcd9378_sdca_entities[QSJ_XU42] }; + +static struct sdca_control entity_su43_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_DEVICE, + .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SELECTOR_NAME }, +}; + +/* Entity 8 (SU 45) Controls */ +static struct sdca_entity *entity_su45_sources[] = { &wcd9378_sdca_entities[QSJ_XU42] }; + +static struct sdca_control entity_su45_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_DEVICE, + .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SELECTOR_NAME }, +}; + +/* + * PDE 47 manages the HPH render path (OT 43, OT 45). FU 6 is listed + * so its cached mute/volume are reasserted on PS0 entry. + */ +static struct sdca_entity *entity_pde47_managed[] = { + &wcd9378_sdca_entities[QSJ_FU6], + &wcd9378_sdca_entities[QSJ_OT43], + &wcd9378_sdca_entities[QSJ_OT45], +}; + +static struct sdca_pde_delay pde47_delays[] = { + { .from_ps = 3, .to_ps = 0, .us = 30000 }, + { .from_ps = 0, .to_ps = 3, .us = 30000 }, +}; + +static struct sdca_control entity_pde47_controls[] = { + { .sel = SDCA_CTL_PDE_REQUESTED_PS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_pde_req_ps_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 3, + .label = SDCA_CTL_REQUESTED_PS_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_NAME }, + /* + * HPH protection IRQs (OCP/CNP/SURGE) fire in codec HW but are + * not exposed to Linux; needs an SDCA framework interface for + * standalone status-bit IRQs. Wire up in a follow-up. + */ +}; + +/* OT 43 (Headphone), OT 45 (Headset): no controls. */ +static struct sdca_entity *entity_ot43_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] }; +static struct sdca_entity *entity_ot45_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] }; + +/* + * GE 35 mode -> SU selector. Modes 0/1 alias to Headphone (see + * range_ge35_mode_data). SU 45 = source 1 (XU 42) for HPH paths, + * SU 43 = source 1 (XU 42) for Headset. + */ +static struct sdca_ge_control ge35_mode0_controls[] = { + { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_control ge35_mode1_controls[] = { + { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_control ge35_mode3_controls[] = { + { .id = 0x7, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_control ge35_mode4_controls[] = { + { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, +}; + +static struct sdca_ge_mode ge35_modes[] = { + { .val = 0x0, .num_controls = ARRAY_SIZE(ge35_mode0_controls), .controls = ge35_mode0_controls }, + { .val = 0x1, .num_controls = ARRAY_SIZE(ge35_mode1_controls), .controls = ge35_mode1_controls }, + { .val = 0x3, .num_controls = ARRAY_SIZE(ge35_mode3_controls), .controls = ge35_mode3_controls }, + { .val = 0x4, .num_controls = ARRAY_SIZE(ge35_mode4_controls), .controls = ge35_mode4_controls }, +}; + +static struct sdca_control entity_ge35_controls[] = { + { .sel = SDCA_CTL_GE_SELECTED_MODE, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .range = { .cols = 0x2, .rows = 0x4, .data = range_ge35_mode_data }, + .has_reset = true, .reset = 0, + .label = SDCA_CTL_SELECTED_MODE_NAME }, + { .sel = 0x2, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = 4, /* SDCA_4 = GE_DETECTED_MODE, see init_table INTMASK_1 */ + .is_volatile = true, .label = SDCA_CTL_DETECTED_MODE_NAME }, +}; + +static struct sdca_control entity_it131_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_it131_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_it131_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it131_cluster_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_it131_cluster_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CLUSTERINDEX_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it131_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_it131_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +/* Entity 0xe (CS 131) Controls */ +static struct sdca_control entity_cs131_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs131_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs131_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* Entity 0xf (IT 33) - headset mic input */ +static struct sdca_control entity_it33_controls[] = { + { .sel = 0x3, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it33_micbias_vals, .has_default = true, + .range = { .cols = 0x1, .rows = 0x1, .data = range_it33_micbias_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_MIC_BIAS_NAME }, + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_it33_usage_data }, + .has_reset = true, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_it33_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_it33_cluster_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_it33_cluster_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CLUSTERINDEX_NAME }, +}; + +/* Entity 0x10 (PDE 34) - manages IT 33 */ +static struct sdca_entity *entity_pde34_managed[] = { &wcd9378_sdca_entities[QSJ_IT33] }; + +static struct sdca_pde_delay pde34_delays[] = { + { .from_ps = 3, .to_ps = 0, .us = 30000 }, + { .from_ps = 0, .to_ps = 3, .us = 30000 }, +}; + +static struct sdca_control entity_pde34_controls[] = { + { .sel = SDCA_CTL_PDE_REQUESTED_PS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_pde_req_ps_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 3, + .label = SDCA_CTL_REQUESTED_PS_NAME }, + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_NAME }, +}; + +/* + * FU 6 (vendor FU42): HPH mute + Q7.8 volume. DUAL-mode CVR-alias + * writes take effect without an SCP_COMMIT. + */ +/* + * FU 6 volume range: MIN, MAX, STEP in Q7.8 (LSB = 1/256 dB). + * 0x8000 = -128 dB, 0x7fff = +127.996 dB, STEP = 1. Framework + * sign-extends and converts to 0.01 dB TLV via (val * 100) >> 8. + */ +static u32 range_fu6_vol_data[] = { + 0x00008000, 0x00007fff, 0x00000001, +}; + +static struct sdca_control entity_fu6_controls[] = { + { .sel = SDCA_CTL_FU_MUTE, .mode = SDCA_ACCESS_MODE_DUAL, + .layers = SDCA_ACCESS_LAYER_USER, .cn_list = 0x6, .nbits = 1, + .type = SDCA_CTL_DATATYPE_ONEBIT, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .label = SDCA_CTL_MUTE_NAME }, + { .sel = SDCA_CTL_FU_CHANNEL_VOLUME, .mode = SDCA_ACCESS_MODE_DUAL, + .layers = SDCA_ACCESS_LAYER_USER, .cn_list = 0x6, .nbits = 16, + .type = SDCA_CTL_DATATYPE_Q7P8DB, + .range = { .cols = SDCA_VOLUME_LINEAR_NCOLS, .rows = 1, + .data = range_fu6_vol_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_CHANNEL_VOLUME_NAME }, +}; + +/* Entity 0x11 (FU 33) - no controls */ +static struct sdca_entity *entity_fu33_sources[] = { &wcd9378_sdca_entities[QSJ_IT33] }; + +/* Entity 0x12 (SU 35) Controls */ +static struct sdca_entity *entity_su35_sources[] = { &wcd9378_sdca_entities[QSJ_FU33] }; + +/* + * SU 35 is class-layer: the SDCA class framework drives the Selector + * directly via a DAPM mux widget, so the Control must be writeable. + */ +static struct sdca_control entity_su35_controls[] = { + { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SELECTOR_NAME }, +}; + +/* Entity 0x13 (XU 36) Controls */ +static struct sdca_entity *entity_xu36_sources[] = { &wcd9378_sdca_entities[QSJ_SU35] }; + +static struct sdca_control entity_xu36_controls[] = { + /* bypass=1: pass mic signal through XU36 without proprietary processing */ + { .sel = SDCA_CTL_XU_BYPASS, .mode = SDCA_ACCESS_MODE_RW, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .values = ctrl_xu36_bypass_vals, .has_default = true, .label = SDCA_CTL_BYPASS_NAME }, + { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu36_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_xu36_ver_vals, .has_fixed = true, .label = SDCA_CTL_XU_VERSION_NAME }, +}; + +/* Entity 0x15 (CS 36) Controls */ +static struct sdca_control entity_cs36_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs36_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs36_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* OT 36 mic capture: IT33 -> FU33 -> SU35 -> XU36 -> OT36. */ +static struct sdca_entity *entity_ot36_sources[] = { &wcd9378_sdca_entities[QSJ_XU36] }; + +static struct sdca_control entity_ot36_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_ot36_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_ot36_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_ot36_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_ot36_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +/* Entity 0x17 (MFPU 236) Controls */ +static struct sdca_control entity_mfpu236_controls[] = { + { .sel = SDCA_CTL_MFPU_BYPASS, .mode = SDCA_ACCESS_MODE_DC, + .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .has_reset = true, .reset = 1, + .values = ctrl_mfpu236_bypass_vals, .has_fixed = true, .label = SDCA_CTL_BYPASS_NAME }, +}; + +/* Entity 0x18 (CS 236) Controls */ +static struct sdca_control entity_cs236_controls[] = { + { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_cs236_sr_vals, .has_fixed = true, + .range = { .cols = 0x2, .rows = 0x1, .data = range_cs236_sr_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_SAMPLERATEINDEX_NAME }, +}; + +/* Entity 0x19 (OT 236) - optimization stream capture output */ +static struct sdca_entity *entity_ot236_sources[] = { &wcd9378_sdca_entities[QSJ_IT33] }; + +static struct sdca_control entity_ot236_controls[] = { + { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .range = { .cols = 0x7, .rows = 0x1, .data = range_ot236_usage_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_USAGE_NAME }, + { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .interrupt_position = SDCA_NO_INTERRUPT, + .values = ctrl_ot236_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME }, + { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1, + .values = ctrl_ot236_dp_vals, .has_fixed = true, + .range = { .cols = 0x10, .rows = 0x4, .data = range_ot236_dp_data }, + .interrupt_position = SDCA_NO_INTERRUPT, + .label = SDCA_CTL_DATAPORT_SELECTOR_NAME }, +}; + +static struct sdca_entity wcd9378_sdca_entities[] = { + /* [0] E001: IT 41 - PDM render stream input */ + { .id = 0x1, .label = "IT 41", .type = SDCA_ENTITY_TYPE_IT, + .iot = { .type = 0x0191, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS41] }, + .num_controls = ARRAY_SIZE(entity_it41_controls), .controls = entity_it41_controls }, + /* [1] E002: CS 41 */ + { .id = 0x2, .label = "CS 41", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs41_controls), .controls = entity_cs41_controls }, + /* [2] E003: MFPU 21 */ + { .id = 0x3, .label = "MFPU 21", .type = SDCA_ENTITY_TYPE_MFPU, + .num_controls = ARRAY_SIZE(entity_mfpu21_controls), .controls = entity_mfpu21_controls, + .num_sources = ARRAY_SIZE(entity_mfpu21_sources), .sources = entity_mfpu21_sources }, + /* [3] E004: XU 42 */ + { .id = 0x4, .label = "XU 42", .type = SDCA_ENTITY_TYPE_XU, + .num_controls = ARRAY_SIZE(entity_xu42_controls), .controls = entity_xu42_controls, + .num_sources = ARRAY_SIZE(entity_xu42_sources), .sources = entity_xu42_sources }, + /* [4] E007: SU 43 - render selector (headset path), driven by GE 35 jack detection */ + { .id = 0x7, .label = "SU 43", .type = SDCA_ENTITY_TYPE_SU, + .group = &wcd9378_sdca_entities[QSJ_GE35], + .num_controls = ARRAY_SIZE(entity_su43_controls), .controls = entity_su43_controls, + .num_sources = ARRAY_SIZE(entity_su43_sources), .sources = entity_su43_sources }, + /* [5] E008: SU 45 - render selector (headphone path), driven by GE 35 jack detection */ + { .id = 0x8, .label = "SU 45", .type = SDCA_ENTITY_TYPE_SU, + .group = &wcd9378_sdca_entities[QSJ_GE35], + .num_controls = ARRAY_SIZE(entity_su45_controls), .controls = entity_su45_controls, + .num_sources = ARRAY_SIZE(entity_su45_sources), .sources = entity_su45_sources }, + /* [6] E009: PDE 47 - render power domain */ + { .id = 0x9, .label = "PDE 47", .type = SDCA_ENTITY_TYPE_PDE, + .pde = { .num_managed = ARRAY_SIZE(entity_pde47_managed), .managed = entity_pde47_managed, + .num_max_delay = ARRAY_SIZE(pde47_delays), .max_delay = pde47_delays }, + .num_controls = ARRAY_SIZE(entity_pde47_controls), .controls = entity_pde47_controls }, + /* [7] E00A: OT 43 - Headphone on jack */ + { .id = 0xa, .label = "OT 43", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x06c0 }, + .num_sources = ARRAY_SIZE(entity_ot43_sources), .sources = entity_ot43_sources }, + /* [8] E00B: OT 45 - Headset output on jack */ + { .id = 0xb, .label = "OT 45", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x06d0 }, + .num_sources = ARRAY_SIZE(entity_ot45_sources), .sources = entity_ot45_sources }, + /* [9] E00C: GE 35 - jack detection group entity */ + { .id = 0xc, .label = "GE 35", .type = SDCA_ENTITY_TYPE_GE, + .ge = { .num_modes = ARRAY_SIZE(ge35_modes), .modes = ge35_modes }, + .num_controls = ARRAY_SIZE(entity_ge35_controls), .controls = entity_ge35_controls }, + /* [10] E00D: IT 131 - optimization stream input */ + { .id = 0xd, .label = "IT 131", .type = SDCA_ENTITY_TYPE_IT, + .iot = { .type = 0x0190, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS131] }, + .num_controls = ARRAY_SIZE(entity_it131_controls), .controls = entity_it131_controls }, + /* [11] E00E: CS 131 */ + { .id = 0xe, .label = "CS 131", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs131_controls), .controls = entity_cs131_controls }, + /* [12] E00F: IT 33 - headset mic input */ + { .id = 0xf, .label = "IT 33", .type = SDCA_ENTITY_TYPE_IT, + .iot = { .type = 0x06d0 }, + .num_controls = ARRAY_SIZE(entity_it33_controls), .controls = entity_it33_controls }, + /* [13] E010: PDE 34 - mic power domain */ + { .id = 0x10, .label = "PDE 34", .type = SDCA_ENTITY_TYPE_PDE, + .pde = { .num_managed = ARRAY_SIZE(entity_pde34_managed), .managed = entity_pde34_managed, + .num_max_delay = ARRAY_SIZE(pde34_delays), .max_delay = pde34_delays }, + .num_controls = ARRAY_SIZE(entity_pde34_controls), .controls = entity_pde34_controls }, + /* [14] E011: FU 33 - mic feature unit */ + { .id = 0x11, .label = "FU 33", .type = SDCA_ENTITY_TYPE_FU, + .num_sources = ARRAY_SIZE(entity_fu33_sources), .sources = entity_fu33_sources }, + /* [15] E012: SU 35 - mic selector */ + { .id = 0x12, .label = "SU 35", .type = SDCA_ENTITY_TYPE_SU, + .num_controls = ARRAY_SIZE(entity_su35_controls), .controls = entity_su35_controls, + .num_sources = ARRAY_SIZE(entity_su35_sources), .sources = entity_su35_sources }, + /* [16] E013: XU 36 - mic extension unit */ + { .id = 0x13, .label = "XU 36", .type = SDCA_ENTITY_TYPE_XU, + .num_controls = ARRAY_SIZE(entity_xu36_controls), .controls = entity_xu36_controls, + .num_sources = ARRAY_SIZE(entity_xu36_sources), .sources = entity_xu36_sources }, + /* [17] E015: CS 36 */ + { .id = 0x15, .label = "CS 36", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs36_controls), .controls = entity_cs36_controls }, + /* [18] E016: OT 36 - PDM mic capture output */ + { .id = 0x16, .label = "OT 36", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x0191, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS36] }, + .num_controls = ARRAY_SIZE(entity_ot36_controls), .controls = entity_ot36_controls, + .num_sources = ARRAY_SIZE(entity_ot36_sources), .sources = entity_ot36_sources }, + /* [19] E017: MFPU 236 - optimization TX processing */ + { .id = 0x17, .label = "MFPU 236", .type = SDCA_ENTITY_TYPE_MFPU, + .num_controls = ARRAY_SIZE(entity_mfpu236_controls), .controls = entity_mfpu236_controls }, + /* [20] E018: CS 236 */ + { .id = 0x18, .label = "CS 236", .type = SDCA_ENTITY_TYPE_CS, + .cs = { .type = 0x0 }, + .num_controls = ARRAY_SIZE(entity_cs236_controls), .controls = entity_cs236_controls }, + /* [21] E019: OT 236 - optimization stream capture output */ + { .id = 0x19, .label = "OT 236", .type = SDCA_ENTITY_TYPE_OT, + .iot = { .type = 0x0190, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS131] }, + .num_controls = ARRAY_SIZE(entity_ot236_controls), .controls = entity_ot236_controls, + .num_sources = ARRAY_SIZE(entity_ot236_sources), .sources = entity_ot236_sources }, + /* E006: FU 6 (FU42) - vendor Feature Unit; HPH mute + Q7.8 volume. */ + { .id = 0x6, .label = "FU 6", .type = SDCA_ENTITY_TYPE_FU, + .num_controls = ARRAY_SIZE(entity_fu6_controls), .controls = entity_fu6_controls }, + /* Entity 0 (Function) */ + { .id = 0x0, .label = "entity0", + .num_controls = ARRAY_SIZE(entity0_controls), .controls = entity0_controls }, +}; + +/* Clusters */ +static struct sdca_channel cl1_channels[] = { /* CL01 - render (HPH) stereo */ + { .id = 0x1, .purpose = 0x1, .relationship = 0x2 }, /* Left */ + { .id = 0x2, .purpose = 0x1, .relationship = 0x3 }, /* Right */ +}; + +static struct sdca_channel cl2_channels[] = { /* CL02 - mic capture mono */ + { .id = 0xff, .purpose = 0x1, .relationship = 0x1 }, +}; + +static struct sdca_channel cl3_channels[] = { /* CL03 - optimization RX */ + { .id = 0xff, .purpose = 0x1, .relationship = 0x1 }, /* Mono */ + { .id = 0x1, .purpose = 0x1, .relationship = 0x2 }, /* Left */ + { .id = 0x2, .purpose = 0x1, .relationship = 0x3 }, /* Right */ +}; + +static struct sdca_channel cl5_channels[] = { /* CL05 - optimization TX mono */ + { .id = 0xff, .purpose = 0x1, .relationship = 0x1 }, +}; + +static struct sdca_cluster wcd9378_sdca_clusters[] = { + { .id = 0x1, .num_channels = ARRAY_SIZE(cl1_channels), .channels = cl1_channels }, + { .id = 0x2, .num_channels = ARRAY_SIZE(cl2_channels), .channels = cl2_channels }, + { .id = 0x3, .num_channels = ARRAY_SIZE(cl3_channels), .channels = cl3_channels }, + { .id = 0x5, .num_channels = ARRAY_SIZE(cl5_channels), .channels = cl5_channels }, +}; + +/* Init table transcribed from ASL. */ +static struct sdca_init_write wcd9378_sdca_init_table[] = { + { .addr = 0x401804f0, .val = 0x00 }, /* DIGITAL_PLATFORM_CTL */ + { .addr = 0x4018046e, .val = 0x10 }, /* DIGITAL_INTR_MODE */ + { .addr = 0x0000004d, .val = 0x01 }, /* SWRS_SCP_BUSCLOCK_BASE */ + { .addr = 0x00000062, .val = 0x02 }, /* SWRS_SCP_BUSCLOCK_SCALE_BANK */ + { .addr = 0x4018016a, .val = 0x80 }, /* CP_DTOP_CTRL_14 */ + { .addr = 0x40180165, .val = 0x6b }, /* CP_DTOP_CTRL_9 */ + { .addr = 0x40180103, .val = 0x1e }, /* SLEEP_CTL BG_CTL (0.9V) */ + { .addr = 0x40180103, .val = 0x9e }, /* SLEEP_CTL BG_EN */ + { .addr = 0x40180103, .val = 0xde }, /* SLEEP_CTL LDOL_BG_SEL */ + { .addr = 0x40180029, .val = 0xb5 }, /* BIAS_VBG_FINE_ADJ */ + { .addr = 0x40180001, .val = 0x80 }, /* ANA_BIAS ANALOG_BIAS_EN */ + { .addr = 0x40180001, .val = 0xc0 }, /* ANA_BIAS PRECHRG_EN(1) */ + { .addr = 0x40180001, .val = 0x80 }, /* ANA_BIAS PRECHRG_EN(0) */ + { .addr = 0x4018007b, .val = 0xa2 }, /* TX_COM_TXFE_DIV_CTL SEQ_BYPASS */ + { .addr = 0x40180465, .val = 0x17 }, /* PDM_WD_CTL0 TIME_OUT_SEL_PCM */ + { .addr = 0x40180466, .val = 0x17 }, /* PDM_WD_CTL1 TIME_OUT_SEL_PCM */ + { .addr = 0x4018006c, .val = 0x01 }, /* MICB1_TEST_CTL_2 IBIAS_LDO_DRIVER */ + { .addr = 0x40180072, .val = 0x81 }, /* MICB3_TEST_CTL_2 IBIAS_LDO_DRIVER */ + { .addr = 0x401800ce, .val = 0x38 }, /* HPH_OCP_CTL OCP_FSM_EN */ + { .addr = 0x401800ce, .val = 0x3a }, /* HPH_OCP_CTL SCD_OP_EN */ + { .addr = 0x401800d4, .val = 0xe1 }, /* HPH_L_TEST OCP_DET_EN */ + { .addr = 0x401800d7, .val = 0xe1 }, /* HPH_R_TEST OCP_DET_EN */ + { .addr = 0x4018044e, .val = 0x04 }, /* CDC_HPH_GAIN_CTL HPHL_RX_EN */ + { .addr = 0x4018044e, .val = 0x0c }, /* CDC_HPH_GAIN_CTL HPHR_RX_EN */ + { .addr = 0x4018000f, .val = 0x0c }, /* ANA_TX_CH2 GAIN (18.0dB) */ + { .addr = 0x40180133, .val = 0x84 }, /* HPH_NEW_INT_RDAC_HD2_CTL_L */ + { .addr = 0x40180136, .val = 0x84 }, /* HPH_NEW_INT_RDAC_HD2_CTL_R */ + { .addr = 0x401800d9, .val = 0x19 }, /* HPH_RDAC_CLK_CTL1 OPAMP_CHOP_CLK_EN */ + { .addr = 0x40180132, .val = 0x50 }, /* HPH_NEW_INT_RDAC_GAIN_CTL RDAC_GAINCTL(0.55) */ + { .addr = 0x40180510, .val = 0x05 }, /* SEQR_CTRL HPH_UP_T0 */ + { .addr = 0x40180519, .val = 0x05 }, /* SEQR_CTRL HPH_UP_T9 */ + { .addr = 0x4018051b, .val = 0x06 }, /* SEQR_CTRL HPH_DN_T0 */ + { .addr = 0x40180414, .val = 0x02 }, /* CDC_COMP_CTL_0 HPHL_COMP_EN */ + { .addr = 0x40180414, .val = 0x03 }, /* CDC_COMP_CTL_0 HPHR_COMP_EN */ + { .addr = 0x401804f2, .val = 0x80 }, /* DRE_DLY_VAL SWR_HPHL(0) */ + { .addr = 0x401804f2, .val = 0x00 }, /* DRE_DLY_VAL SWR_HPHR(0) */ + { .addr = 0x40180501, .val = 0x01 }, /* SEQR_CTRL SYS_USAGE_CTRL */ + /* Arms MBHC: jack insertion asserts SDCA_4 (GE_DETECTED_MODE). */ + { .addr = 0x40180601, .val = 0x01 }, /* MBHC_CTRL DEVICE_DET */ + { .addr = 0x40180414, .val = 0x00 }, /* CDC_COMP_CTL_0 */ + { .addr = 0x401804f2, .val = 0x88 }, /* DRE_DLY_VAL */ + { .addr = 0x40180517, .val = 0x07 }, /* SEQR_CTRL HPH_UP_T7 */ + { .addr = 0x4018051c, .val = 0x07 }, /* SEQR_CTRL HPH_DN_T1 */ + { .addr = 0x401800ce, .val = 0x28 }, /* HPH_OCP_CTL */ + { .addr = 0x401800d4, .val = 0xe0 }, /* HPH_L_TEST */ + { .addr = 0x401800d7, .val = 0xe0 }, /* HPH_R_TEST */ + { .addr = 0x40180510, .val = 0x07 }, /* SEQR_CTRL HPH_UP_T0 */ + { .addr = 0x40c80008, .val = 0x01 }, /* SMP_JACK_CTRL FUNC_ACT (RESET_FUNCTION_NOW) */ + { .addr = 0x40c00008, .val = 0x02 }, /* SMP_JACK_CTRL CMT_GRP_MASK */ + { .addr = 0x40c80000, .val = 0xff }, /* SMP_JACK_CTRL FUNC_STAT */ + { .addr = 0x00000000, .val = 0x08 }, /* clear DP0 INT status SDCA_CASCADE */ + { .addr = 0x00000041, .val = 0x08 }, /* SCP_INT_STATUS_MASK_1 PORT_0_CASCADE_3 */ + /* Only SDCA_4 unmasked; protection IRQs stay masked. */ + { .addr = 0x0000005c, .val = 0x10 }, /* INTMASK_1 SDCA_4 (GE_DETECTED_MODE) */ + { .addr = 0x4018016a, .val = 0x00 }, /* CP_DTOP_CTRL_14 */ + { .addr = 0x40180165, .val = 0x6b }, /* CP_DTOP_CTRL_9 */ + /* + * L_DET_EN is left disabled: asserting SDCA_4 before the machine + * card registers the jack blocks the deferred card probe. + */ +}; + +/* Function Descriptor */ +static struct sdca_function_desc wcd9378_sdca_desc = { + .adr = 0x3, + .type = SDCA_FUNCTION_TYPE_SIMPLE_JACK, + .name = SDCA_FUNCTION_TYPE_SIMPLE_NAME, +}; + +/* Main Function Data */ +static struct sdca_function_data wcd9378_sdca_data = { + .desc = &wcd9378_sdca_desc, + .num_entities = ARRAY_SIZE(wcd9378_sdca_entities), + .entities = wcd9378_sdca_entities, + .num_clusters = ARRAY_SIZE(wcd9378_sdca_clusters), + .clusters = wcd9378_sdca_clusters, + .num_init_table = ARRAY_SIZE(wcd9378_sdca_init_table), + .init_table = wcd9378_sdca_init_table, + .reset_max_delay = 25000, /* 25ms — WCD9378 power-on reset completes well within this */ +}; + +/* Vendor SCP register: host clock divide-by-2 (bank 1 shadow). */ +#define WCD9378_SCP_HOST_CLK_DIV2_CTL_B1 0xf0 + +/* Slave ports 1..8, mapped to master ports via qcom,port-mapping. */ +#define WCD9378_SDCA_MAX_PORTS 8 + +static const char * const wcd9378_sdca_supplies[] = { + "vdd-buck", "vdd-rxtx", "vdd-io", "vdd-mic-bias", +}; + +int wcd9378_sdca_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + struct device *dev = &slave->dev; + struct sdw_dpn_prop *sink, *src; + int ret; + + /* + * All SoundWire slave properties for compute-mode WCD9378 are + * fixed by the silicon and are not described in DT, so we do + * not call sdw_slave_read_prop() here. + */ + prop->use_domain_irq = true; + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY | + SDW_SCP_INT1_IMPL_DEF; + prop->simple_clk_stop_capable = true; + prop->paging_support = true; + prop->clock_reg_supported = true; + prop->lane_control_support = true; + + /* Source ports: DP2 (headset mic), DP5 (optimisation TX). */ + prop->source_ports = BIT(2) | BIT(5); + /* Sink ports: DP6 (HPH audio), DP7 (HPH envelope), DP8 (optimisation RX). */ + prop->sink_ports = BIT(6) | BIT(7) | BIT(8); + + src = devm_kcalloc(dev, 2, sizeof(*src), GFP_KERNEL); + if (!src) + return -ENOMEM; + + src[0].num = 2; + src[0].type = SDW_DPN_SIMPLE; + src[0].simple_ch_prep_sm = true; + src[0].ch_prep_timeout = 10; + src[0].max_ch = 1; + src[0].min_ch = 1; + + src[1].num = 5; + src[1].type = SDW_DPN_SIMPLE; + src[1].simple_ch_prep_sm = true; + src[1].ch_prep_timeout = 10; + src[1].max_ch = 1; + src[1].min_ch = 1; + + prop->src_dpn_prop = src; + + sink = devm_kcalloc(dev, 3, sizeof(*sink), GFP_KERNEL); + if (!sink) + return -ENOMEM; + + sink[0].num = 6; + sink[0].type = SDW_DPN_SIMPLE; + sink[0].simple_ch_prep_sm = true; + sink[0].ch_prep_timeout = 10; + sink[0].max_ch = 2; + sink[0].min_ch = 1; + + sink[1].num = 7; + sink[1].type = SDW_DPN_FULL; + sink[1].simple_ch_prep_sm = true; + sink[1].ch_prep_timeout = 10; + sink[1].max_ch = 1; + sink[1].min_ch = 1; + + sink[2].num = 8; + sink[2].type = SDW_DPN_REDUCED; + sink[2].simple_ch_prep_sm = true; + sink[2].ch_prep_timeout = 10; + sink[2].max_ch = 2; + sink[2].min_ch = 1; + + prop->sink_dpn_prop = sink; + + ret = device_property_read_u32_array(dev, "qcom,port-mapping", + &slave->m_port_map[1], + WCD9378_SDCA_MAX_PORTS); + if (ret) + return dev_err_probe(dev, ret, "qcom,port-mapping missing\n"); + + return 0; +} + +static int wcd9378_sdca_populate_function(struct device *dev, + struct sdca_function_data *function) +{ + /* @function->desc is already set by the framework; fill payload only. */ + if (function->desc->type != wcd9378_sdca_desc.type) + return -EINVAL; + + function->num_entities = wcd9378_sdca_data.num_entities; + function->entities = wcd9378_sdca_data.entities; + function->num_clusters = wcd9378_sdca_data.num_clusters; + function->clusters = wcd9378_sdca_data.clusters; + function->num_init_table = wcd9378_sdca_data.num_init_table; + function->init_table = wcd9378_sdca_data.init_table; + function->reset_max_delay = wcd9378_sdca_data.reset_max_delay; + + return 0; +} + +static const struct sdca_class_ops wcd9378_sdca_class_ops = { + .populate_function = wcd9378_sdca_populate_function, +}; + +int wcd9378_sdca_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct device *dev = &slave->dev; + struct sdca_device_data *data = &slave->sdca_data; + struct wcd9378_priv *priv; + struct gpio_desc *reset; + int ret; + + /* + * 0x0217:0x0110 covers both mobile (non-SDCA) and SDCA-compliant + * fuse variants. Only SDCA-compliant nodes bind to this driver; + * mobile-mode nodes are handled by a different driver. + */ + if (!device_property_read_bool(dev, "qcom,sdca-compliant")) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + /* No SPMI parent: supplies and reset live on the SoundWire DT node. */ + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(wcd9378_sdca_supplies), + wcd9378_sdca_supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to enable supplies\n"); + + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset)) + return dev_err_probe(dev, PTR_ERR(reset), + "failed to get reset GPIO\n"); + + if (reset) { + gpiod_set_value(reset, 1); + usleep_range(20, 30); + gpiod_set_value(reset, 0); + usleep_range(20, 30); + } + + /* SCP writes below need the slave attached. */ + ret = sdw_slave_wait_for_init(slave, 5000); + if (ret) + return dev_err_probe(dev, ret, + "slave attach timeout: %d\n", ret); + + /* + * TX PDM clock: bank-1 shadow + SCP_COMMIT. SCP survives PDE + * cycles; one-shot before any port is enabled. + */ + ret = sdw_write_no_pm(slave, WCD9378_SCP_HOST_CLK_DIV2_CTL_B1, 0x01); + if (ret) + return dev_err_probe(dev, ret, + "HOST_CLK_DIV2_CTL_B1: %d\n", ret); + + ret = sdw_write_no_pm(slave, SDW_SCP_COMMIT, 0x02); + if (ret) + return dev_err_probe(dev, ret, "SCP_COMMIT: %d\n", ret); + + /* DT has no DisCo enumeration; seed the descriptor here. */ + if (!data->num_functions) { + data->function[0].type = wcd9378_sdca_desc.type; + data->function[0].adr = wcd9378_sdca_desc.adr; + data->function[0].name = wcd9378_sdca_desc.name; + data->num_functions = 1; + } + + return sdca_class_probe(slave, &priv->class, &wcd9378_sdca_class_ops); +} + +void wcd9378_sdca_remove(struct sdw_slave *slave) +{ + struct wcd9378_priv *priv = dev_get_drvdata(&slave->dev); + + sdca_class_remove(&priv->class); +} + +int wcd9378_sdca_runtime_suspend(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_runtime_suspend(&priv->class); +} + +int wcd9378_sdca_runtime_resume(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_runtime_resume(&priv->class); +} + +int wcd9378_sdca_system_suspend(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_system_suspend(&priv->class); +} + +int wcd9378_sdca_system_resume(struct device *dev) +{ + struct wcd9378_priv *priv = dev_get_drvdata(dev); + + return sdca_class_system_resume(&priv->class); +} + +/* SoundWire slave-driver plumbing lives in wcd9378-sdw.c. */ diff --git a/sound/soc/codecs/wcd9378-sdca.h b/sound/soc/codecs/wcd9378-sdca.h new file mode 100644 index 000000000000..6ddae9da395b --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdca.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* Copyright (c) 2025 Qualcomm Technologies, Inc. */ + +#ifndef _WCD9378_SDCA_H +#define _WCD9378_SDCA_H + +#include +#include + +int wcd9378_sdca_probe(struct sdw_slave *slave, + const struct sdw_device_id *id); +void wcd9378_sdca_remove(struct sdw_slave *slave); +int wcd9378_sdca_read_prop(struct sdw_slave *slave); + +int wcd9378_sdca_runtime_suspend(struct device *dev); +int wcd9378_sdca_runtime_resume(struct device *dev); +int wcd9378_sdca_system_suspend(struct device *dev); +int wcd9378_sdca_system_resume(struct device *dev); + +#endif /* _WCD9378_SDCA_H */ diff --git a/sound/soc/codecs/wcd9378-sdw.c b/sound/soc/codecs/wcd9378-sdw.c new file mode 100644 index 000000000000..52295e7646cf --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdw.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright (c) 2025 Qualcomm Technologies, Inc. + +/* + * WCD9378 (Tambora) SoundWire driver glue for the SDCA compute-mode + * codec. The SDCA topology and hardware-specific probe logic live in + * wcd9378-sdca.c; this file carries the SoundWire slave driver plumbing + * and the module boilerplate. + */ + +#include +#include +#include +#include +#include +#include "wcd9378-sdca.h" + +static const struct dev_pm_ops wcd9378_sdw_pm_ops = { + SYSTEM_SLEEP_PM_OPS(wcd9378_sdca_system_suspend, + wcd9378_sdca_system_resume) + RUNTIME_PM_OPS(wcd9378_sdca_runtime_suspend, + wcd9378_sdca_runtime_resume, NULL) +}; + +static const struct sdw_slave_ops wcd9378_sdw_ops = { + .read_prop = wcd9378_sdca_read_prop, +}; + +static const struct sdw_device_id wcd9378_sdw_id[] = { + SDW_SLAVE_ENTRY(0x0217, 0x0110, 0), + { } +}; +MODULE_DEVICE_TABLE(sdw, wcd9378_sdw_id); + +static struct sdw_driver wcd9378_sdw_driver = { + .driver = { + .name = "wcd9378", + .pm = pm_ptr(&wcd9378_sdw_pm_ops), + }, + .probe = wcd9378_sdca_probe, + .remove = wcd9378_sdca_remove, + .id_table = wcd9378_sdw_id, + .ops = &wcd9378_sdw_ops, +}; +module_sdw_driver(wcd9378_sdw_driver); + +MODULE_DESCRIPTION("Qualcomm WCD9378 (Tambora) SoundWire codec"); +MODULE_AUTHOR("Qualcomm Technologies, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS("SND_SOC_SDCA"); +MODULE_IMPORT_NS("SND_SOC_SDCA_CLASS"); diff --git a/sound/soc/codecs/wcd937x.c b/sound/soc/codecs/wcd937x.c index 0dd05604f5b8..7ffbb445f8ac 100644 --- a/sound/soc/codecs/wcd937x.c +++ b/sound/soc/codecs/wcd937x.c @@ -2536,23 +2536,23 @@ static int wcd937x_soc_codec_probe(struct snd_soc_component *component) WCD937X_IRQ_AUX_PDM_WD_INT); /* Request for watchdog interrupt */ - ret = devm_request_threaded_irq(dev, wcd937x->hphr_pdm_wd_int, NULL, wcd937x_wd_handle_irq, - IRQF_ONESHOT | IRQF_TRIGGER_RISING, - "HPHR PDM WDOG INT", wcd937x); + ret = request_threaded_irq(wcd937x->hphr_pdm_wd_int, NULL, wcd937x_wd_handle_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "HPHR PDM WDOG INT", wcd937x); if (ret) - dev_err(dev, "Failed to request HPHR watchdog interrupt (%d)\n", ret); + goto err_free_clsh; - ret = devm_request_threaded_irq(dev, wcd937x->hphl_pdm_wd_int, NULL, wcd937x_wd_handle_irq, - IRQF_ONESHOT | IRQF_TRIGGER_RISING, - "HPHL PDM WDOG INT", wcd937x); + ret = request_threaded_irq(wcd937x->hphl_pdm_wd_int, NULL, wcd937x_wd_handle_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "HPHL PDM WDOG INT", wcd937x); if (ret) - dev_err(dev, "Failed to request HPHL watchdog interrupt (%d)\n", ret); + goto err_free_hphr_irq; - ret = devm_request_threaded_irq(dev, wcd937x->aux_pdm_wd_int, NULL, wcd937x_wd_handle_irq, - IRQF_ONESHOT | IRQF_TRIGGER_RISING, - "AUX PDM WDOG INT", wcd937x); + ret = request_threaded_irq(wcd937x->aux_pdm_wd_int, NULL, wcd937x_wd_handle_irq, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "AUX PDM WDOG INT", wcd937x); if (ret) - dev_err(dev, "Failed to request Aux watchdog interrupt (%d)\n", ret); + goto err_free_hphl_irq; /* Disable watchdog interrupt for HPH and AUX */ disable_irq_nosync(wcd937x->hphr_pdm_wd_int); @@ -2564,24 +2564,34 @@ static int wcd937x_soc_codec_probe(struct snd_soc_component *component) ARRAY_SIZE(wcd9375_dapm_widgets)); if (ret < 0) { dev_err(component->dev, "Failed to add snd_ctls\n"); - wcd_clsh_ctrl_free(wcd937x->clsh_info); - return ret; + goto err_free_irqs; } ret = snd_soc_dapm_add_routes(dapm, wcd9375_audio_map, ARRAY_SIZE(wcd9375_audio_map)); if (ret < 0) { dev_err(component->dev, "Failed to add routes\n"); - wcd_clsh_ctrl_free(wcd937x->clsh_info); - return ret; + goto err_free_irqs; } } ret = wcd937x_mbhc_init(component); - if (ret) + if (ret) { dev_err(component->dev, "mbhc initialization failed\n"); + goto err_free_irqs; + } return ret; + +err_free_irqs: + free_irq(wcd937x->aux_pdm_wd_int, wcd937x); +err_free_hphl_irq: + free_irq(wcd937x->hphl_pdm_wd_int, wcd937x); +err_free_hphr_irq: + free_irq(wcd937x->hphr_pdm_wd_int, wcd937x); +err_free_clsh: + wcd_clsh_ctrl_free(wcd937x->clsh_info); + return ret; } static void wcd937x_soc_codec_remove(struct snd_soc_component *component) diff --git a/sound/soc/codecs/wcd939x.c b/sound/soc/codecs/wcd939x.c index 26fe0b6313cd..2d463f062e6d 100644 --- a/sound/soc/codecs/wcd939x.c +++ b/sound/soc/codecs/wcd939x.c @@ -1794,8 +1794,9 @@ static int wcd939x_get_swr_port(struct snd_kcontrol *kcontrol, struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(comp); struct wcd939x_sdw_priv *wcd = wcd939x->sdw_priv[mixer->shift]; unsigned int portidx = wcd->ch_info[mixer->reg].port_num; + u8 ch_mask = wcd->ch_info[mixer->reg].ch_mask; - ucontrol->value.integer.value[0] = wcd->port_enable[portidx] ? 1 : 0; + ucontrol->value.integer.value[0] = !!(wcd->port_config[portidx - 1].ch_mask & ch_mask); return 0; } @@ -1821,10 +1822,13 @@ static int wcd939x_set_swr_port(struct snd_kcontrol *kcontrol, struct wcd939x_priv *wcd939x = snd_soc_component_get_drvdata(comp); struct wcd939x_sdw_priv *wcd = wcd939x->sdw_priv[mixer->shift]; unsigned int portidx = wcd->ch_info[mixer->reg].port_num; + u8 ch_mask = wcd->ch_info[mixer->reg].ch_mask; + bool enable = !!ucontrol->value.integer.value[0]; - wcd->port_enable[portidx] = !!ucontrol->value.integer.value[0]; + if (enable == !!(wcd->port_config[portidx - 1].ch_mask & ch_mask)) + return 0; - wcd939x_connect_port(wcd, portidx, mixer->reg, wcd->port_enable[portidx]); + wcd939x_connect_port(wcd, portidx, mixer->reg, enable); return 1; } diff --git a/sound/soc/codecs/wcd939x.h b/sound/soc/codecs/wcd939x.h index 6bd2366587a8..dab7ef108b48 100644 --- a/sound/soc/codecs/wcd939x.h +++ b/sound/soc/codecs/wcd939x.h @@ -899,7 +899,6 @@ struct wcd939x_sdw_priv { struct sdw_stream_runtime *sruntime; struct sdw_port_config port_config[WCD939X_MAX_SWR_PORTS]; const struct wcd_sdw_ch_info *ch_info; - bool port_enable[WCD939X_MAX_SWR_CH_IDS]; int active_ports; bool is_tx; struct wcd939x_priv *wcd939x; diff --git a/sound/soc/codecs/wm2200.c b/sound/soc/codecs/wm2200.c index bc78b553b6cb..692e933dba75 100644 --- a/sound/soc/codecs/wm2200.c +++ b/sound/soc/codecs/wm2200.c @@ -2066,10 +2066,20 @@ static int wm2200_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 wm2200_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm2200_dai_ops = { .probe = wm2200_dai_probe, .set_fmt = wm2200_set_fmt, .hw_params = wm2200_hw_params, + .auto_selectable_formats = &wm2200_selectable_formats, + .num_auto_selectable_formats = 1, }; #define WM2200_RATES SNDRV_PCM_RATE_8000_48000 diff --git a/sound/soc/codecs/wm5100.c b/sound/soc/codecs/wm5100.c index 2fe937a93026..cdee1561d596 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -658,7 +658,7 @@ SOC_SINGLE_TLV("EQ2 Band 4 Volume", WM5100_EQ2_2, WM5100_EQ2_B4_GAIN_SHIFT, SOC_SINGLE_TLV("EQ2 Band 5 Volume", WM5100_EQ2_2, WM5100_EQ2_B5_GAIN_SHIFT, 24, 0, eq_tlv), -SOC_SINGLE_TLV("EQ3 Band 1 Volume", WM5100_EQ1_1, WM5100_EQ3_B1_GAIN_SHIFT, +SOC_SINGLE_TLV("EQ3 Band 1 Volume", WM5100_EQ3_1, WM5100_EQ3_B1_GAIN_SHIFT, 24, 0, eq_tlv), SOC_SINGLE_TLV("EQ3 Band 2 Volume", WM5100_EQ3_1, WM5100_EQ3_B2_GAIN_SHIFT, 24, 0, eq_tlv), @@ -1501,9 +1501,19 @@ static int wm5100_hw_params(struct snd_pcm_substream *substream, return 0; } +static const u64 wm5100_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm5100_dai_ops = { .set_fmt = wm5100_set_fmt, .hw_params = wm5100_hw_params, + .auto_selectable_formats = &wm5100_selectable_formats, + .num_auto_selectable_formats = 1, }; static int wm5100_set_sysclk(struct snd_soc_component *component, int clk_id, diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index ad68ff1790e0..0e7c8a5fae72 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1426,6 +1426,28 @@ EXPORT_SYMBOL_GPL(wm8350_mic_jack_detect); SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8350_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* + * 2nd priority + * + * iface of DSP_A/B are same, the diff is only LRCLK_INV. + * It will be impossible to distinguish between DSP_A/B + * if xB_IF are used. Allow xB_NF only. + */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8350_dai_ops = { .hw_params = wm8350_pcm_hw_params, .mute_stream = wm8350_mute, @@ -1434,6 +1456,8 @@ static const struct snd_soc_dai_ops wm8350_dai_ops = { .set_pll = wm8350_set_fll, .set_clkdiv = wm8350_set_clkdiv, .no_capture_mute = 1, + .auto_selectable_formats = wm8350_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8350_selectable_formats), }; static struct snd_soc_dai_driver wm8350_dai = { diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 0eecc8657a38..83e2d72a4a76 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -1222,6 +1222,13 @@ static int wm8400_set_bias_level(struct snd_soc_component *component, #define WM8400_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8400_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops wm8400_dai_ops = { .hw_params = wm8400_hw_params, .mute_stream = wm8400_mute, @@ -1230,6 +1237,8 @@ static const struct snd_soc_dai_ops wm8400_dai_ops = { .set_sysclk = wm8400_set_dai_sysclk, .set_pll = wm8400_set_dai_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8400_selectable_formats, + .num_auto_selectable_formats = 1, }; /* diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 137dcd3d7487..2d48bfffcebe 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -134,7 +134,7 @@ SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), -SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), +SOC_SINGLE("ADC Inversion Switch", WM8510_ADC, 0, 1, 0), SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0), @@ -545,6 +545,16 @@ static int wm8510_set_bias_level(struct snd_soc_component *component, #define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8510_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8510_dai_ops = { .hw_params = wm8510_pcm_hw_params, .mute_stream = wm8510_mute, @@ -552,6 +562,8 @@ static const struct snd_soc_dai_ops wm8510_dai_ops = { .set_clkdiv = wm8510_set_dai_clkdiv, .set_pll = wm8510_set_dai_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8510_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8510_dai = { diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index b8832a1d61fe..9972d6599002 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -374,11 +374,24 @@ static int wm8523_set_bias_level(struct snd_soc_component *component, #define WM8523_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8523_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8523_dai_ops = { .startup = wm8523_startup, .hw_params = wm8523_hw_params, .set_sysclk = wm8523_set_dai_sysclk, .set_fmt = wm8523_set_dai_fmt, + .auto_selectable_formats = &wm8523_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8523_dai = { diff --git a/sound/soc/codecs/wm8524.c b/sound/soc/codecs/wm8524.c index 23daf158f35f..8d0565b103cd 100644 --- a/sound/soc/codecs/wm8524.c +++ b/sound/soc/codecs/wm8524.c @@ -190,6 +190,10 @@ static int wm8524_hw_params(struct snd_pcm_substream *substream, SNDRV_PCM_FMTBIT_S24_LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8525_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops wm8524_dai_ops = { .startup = wm8524_startup, .shutdown = wm8524_shutdown, @@ -197,6 +201,8 @@ static const struct snd_soc_dai_ops wm8524_dai_ops = { .set_fmt = wm8524_set_fmt, .mute_stream = wm8524_mute_stream, .hw_params = wm8524_hw_params, + .auto_selectable_formats = &wm8525_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8524_dai = { diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index eb374ba6e5b5..cee80da8f753 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -852,14 +852,41 @@ static int wm8580_playback_startup(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct wm8580_priv *wm8580 = snd_soc_component_get_drvdata(component); + int ret; - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, 1, wm8580->drvdata->num_dacs * 2); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 1, + wm8580->drvdata->num_dacs * 2); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ + return 0; } #define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8580_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8580_dai_ops_playback = { .startup = wm8580_playback_startup, .set_sysclk = wm8580_set_sysclk, @@ -869,6 +896,8 @@ static const struct snd_soc_dai_ops wm8580_dai_ops_playback = { .set_pll = wm8580_set_dai_pll, .mute_stream = wm8580_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8580_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8580_selectable_formats), }; static const struct snd_soc_dai_ops wm8580_dai_ops_capture = { @@ -877,6 +906,8 @@ static const struct snd_soc_dai_ops wm8580_dai_ops_capture = { .set_fmt = wm8580_set_paif_dai_fmt, .set_clkdiv = wm8580_set_dai_clkdiv, .set_pll = wm8580_set_dai_pll, + .auto_selectable_formats = wm8580_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8580_selectable_formats), }; static struct snd_soc_dai_driver wm8580_dai[] = { diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c index 2db8829661c3..390af15a749a 100644 --- a/sound/soc/codecs/wm8711.c +++ b/sound/soc/codecs/wm8711.c @@ -325,6 +325,17 @@ static int wm8711_set_bias_level(struct snd_soc_component *component, #define WM8711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8711_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8711_ops = { .prepare = wm8711_pcm_prepare, .hw_params = wm8711_hw_params, @@ -333,6 +344,8 @@ static const struct snd_soc_dai_ops wm8711_ops = { .set_sysclk = wm8711_set_dai_sysclk, .set_fmt = wm8711_set_dai_fmt, .no_capture_mute = 1, + .auto_selectable_formats = &wm8711_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8711_dai = { diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index 3109a6a0df74..7af5ce0fffe0 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -190,11 +190,20 @@ static int wm8728_set_bias_level(struct snd_soc_component *component, #define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8728_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8728_dai_ops = { .hw_params = wm8728_hw_params, .mute_stream = wm8728_mute, .set_fmt = wm8728_set_dai_fmt, .no_capture_mute = 1, + .auto_selectable_formats = &wm8728_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8728_dai = { diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index ce87280d590d..f25b50919a9a 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -519,6 +519,17 @@ static int wm8731_startup(struct snd_pcm_substream *substream, #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8731_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8731_dai_ops = { .startup = wm8731_startup, .hw_params = wm8731_hw_params, @@ -526,6 +537,8 @@ static const struct snd_soc_dai_ops wm8731_dai_ops = { .set_sysclk = wm8731_set_dai_sysclk, .set_fmt = wm8731_set_dai_fmt, .no_capture_mute = 1, + .auto_selectable_formats = &wm8731_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8731_dai = { diff --git a/sound/soc/codecs/wm8737.c b/sound/soc/codecs/wm8737.c index 33a3f88fffb3..7f59dc6c74c0 100644 --- a/sound/soc/codecs/wm8737.c +++ b/sound/soc/codecs/wm8737.c @@ -148,13 +148,13 @@ SOC_DOUBLE_R_TLV("Mic Boost Volume", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, 6, 3, 0, micboost_tlv), SOC_DOUBLE_R("Mic Boost Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, 4, 1, 0), -SOC_DOUBLE("Mic ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, - 3, 1, 0), +SOC_DOUBLE_R("Mic ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 3, 1, 0), SOC_DOUBLE_R_TLV("Capture Volume", WM8737_LEFT_PGA_VOLUME, WM8737_RIGHT_PGA_VOLUME, 0, 255, 0, pga_tlv), -SOC_DOUBLE("Capture ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, - 2, 1, 0), +SOC_DOUBLE_R("Capture ZC Switch", WM8737_AUDIO_PATH_L, WM8737_AUDIO_PATH_R, + 2, 1, 0), SOC_DOUBLE("INPUT1 DC Bias Switch", WM8737_MISC_BIAS_CONTROL, 0, 1, 1, 0), @@ -516,10 +516,21 @@ static int wm8737_set_bias_level(struct snd_soc_component *component, #define WM8737_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8737_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops wm8737_dai_ops = { .hw_params = wm8737_hw_params, .set_sysclk = wm8737_set_dai_sysclk, .set_fmt = wm8737_set_dai_fmt, + .auto_selectable_formats = &wm8737_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8737_dai = { diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c index ca56fdfb5088..e49adc9808b4 100644 --- a/sound/soc/codecs/wm8741.c +++ b/sound/soc/codecs/wm8741.c @@ -381,6 +381,17 @@ static int wm8741_mute(struct snd_soc_dai *codec_dai, int mute, int direction) #define WM8741_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8741_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8741_dai_ops = { .startup = wm8741_startup, .hw_params = wm8741_hw_params, @@ -388,6 +399,8 @@ static const struct snd_soc_dai_ops wm8741_dai_ops = { .set_fmt = wm8741_set_dai_fmt, .mute_stream = wm8741_mute, .no_capture_mute = 1, + .auto_selectable_formats = &wm8741_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8741_dai = { diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index d3f1178c2d34..e87b7d5e94e2 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -171,7 +171,7 @@ SOC_SINGLE("ALC Capture Hold Time", WM8750_ALC2, 0, 15, 0), SOC_SINGLE("ALC Capture Decay Time", WM8750_ALC3, 4, 15, 0), SOC_SINGLE("ALC Capture Attack Time", WM8750_ALC3, 0, 15, 0), SOC_SINGLE("ALC Capture NG Threshold", WM8750_NGATE, 3, 31, 0), -SOC_ENUM("ALC Capture NG Type", wm8750_enum[4]), +SOC_ENUM("ALC Capture NG Type", wm8750_enum[7]), SOC_SINGLE("ALC Capture NG Switch", WM8750_NGATE, 0, 1, 0), SOC_SINGLE("Left ADC Capture Volume", WM8750_LADC, 0, 255, 0), @@ -659,12 +659,25 @@ static int wm8750_set_bias_level(struct snd_soc_component *component, #define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8750_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8750_dai_ops = { .hw_params = wm8750_pcm_hw_params, .mute_stream = wm8750_mute, .set_fmt = wm8750_set_dai_fmt, .set_sysclk = wm8750_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8750_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8750_dai = { diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index ac4008b4832d..cbdf1e6aea5b 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -1380,6 +1380,22 @@ static int wm8753_set_bias_level(struct snd_soc_component *component, * 3. Voice disabled - HIFI over HIFI * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture */ +static const u64 wm8753_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, +}; + static const struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode = { .hw_params = wm8753_i2s_hw_params, .mute_stream = wm8753_mute, @@ -1388,6 +1404,8 @@ static const struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode = { .set_pll = wm8753_set_dai_pll, .set_sysclk = wm8753_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = wm8753_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8753_selectable_formats), }; static const struct snd_soc_dai_ops wm8753_dai_ops_voice_mode = { @@ -1398,6 +1416,8 @@ static const struct snd_soc_dai_ops wm8753_dai_ops_voice_mode = { .set_pll = wm8753_set_dai_pll, .set_sysclk = wm8753_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = wm8753_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8753_selectable_formats), }; static struct snd_soc_dai_driver wm8753_dai[] = { diff --git a/sound/soc/codecs/wm8770.c b/sound/soc/codecs/wm8770.c index b8b4d1e823e6..1e485d2fe21a 100644 --- a/sound/soc/codecs/wm8770.c +++ b/sound/soc/codecs/wm8770.c @@ -537,12 +537,23 @@ static int wm8770_set_bias_level(struct snd_soc_component *component, #define WM8770_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8770_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8770_dai_ops = { .mute_stream = wm8770_mute, .hw_params = wm8770_hw_params, .set_fmt = wm8770_set_fmt, .set_sysclk = wm8770_set_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8770_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8770_dai = { diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c index a8e4f71c77d1..c633fe824356 100644 --- a/sound/soc/codecs/wm8776.c +++ b/sound/soc/codecs/wm8776.c @@ -360,18 +360,31 @@ static int wm8776_set_bias_level(struct snd_soc_component *component, #define WM8776_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8776_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8776_dac_ops = { .mute_stream = wm8776_mute, .hw_params = wm8776_hw_params, .set_fmt = wm8776_set_fmt, .set_sysclk = wm8776_set_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8776_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops wm8776_adc_ops = { .hw_params = wm8776_hw_params, .set_fmt = wm8776_set_fmt, .set_sysclk = wm8776_set_sysclk, + .auto_selectable_formats = &wm8776_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8776_dai[] = { diff --git a/sound/soc/codecs/wm8782.c b/sound/soc/codecs/wm8782.c index 3a2acdfa9b85..cbf0c823c022 100644 --- a/sound/soc/codecs/wm8782.c +++ b/sound/soc/codecs/wm8782.c @@ -39,9 +39,19 @@ static int wm8782_dai_startup(struct snd_pcm_substream *sub, struct snd_soc_dai struct snd_pcm_runtime *runtime = sub->runtime; struct wm8782_priv *priv = snd_soc_component_get_drvdata(dai->component); + int ret; - return snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, 8000, priv->max_rate); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI is correctly marked as started. + */ + return 0; } static const struct snd_soc_dapm_widget wm8782_dapm_widgets[] = { diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index 94aa3c8de0ab..2fc9634fe51f 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -502,12 +502,25 @@ static int wm8804_set_clkdiv(struct snd_soc_dai *dai, return 0; } +static const u64 wm8804_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8804_dai_ops = { .hw_params = wm8804_hw_params, .set_fmt = wm8804_set_fmt, .set_sysclk = wm8804_set_sysclk, .set_clkdiv = wm8804_set_clkdiv, - .set_pll = wm8804_set_pll + .set_pll = wm8804_set_pll, + .auto_selectable_formats = &wm8804_selectable_formats, + .num_auto_selectable_formats = 1, }; #define WM8804_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index a9128cfa6ff1..dcda7a7be0b1 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -992,6 +992,22 @@ static int wm8900_mute(struct snd_soc_dai *codec_dai, int mute, int direction) (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8900_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8900_dai_ops = { .hw_params = wm8900_hw_params, .set_clkdiv = wm8900_set_dai_clkdiv, @@ -999,6 +1015,8 @@ static const struct snd_soc_dai_ops wm8900_dai_ops = { .set_fmt = wm8900_set_dai_fmt, .mute_stream = wm8900_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8900_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8900_selectable_formats), }; static struct snd_soc_dai_driver wm8900_dai = { diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index fbf388cf966d..553f8d68a546 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1733,12 +1733,30 @@ static irqreturn_t wm8903_irq(int irq, void *data) SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8903_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8903_dai_ops = { .hw_params = wm8903_hw_params, .mute_stream = wm8903_mute, .set_fmt = wm8903_set_dai_fmt, .set_sysclk = wm8903_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = wm8903_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8903_selectable_formats), }; static struct snd_soc_dai_driver wm8903_dai = { diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 9e5782e50f47..96d2e54a2c6a 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2026,6 +2026,22 @@ static int wm8904_set_bias_level(struct snd_soc_component *component, #define WM8904_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8904_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8904_dai_ops = { .set_sysclk = wm8904_set_sysclk, .set_fmt = wm8904_set_fmt, @@ -2034,6 +2050,8 @@ static const struct snd_soc_dai_ops wm8904_dai_ops = { .hw_params = wm8904_hw_params, .mute_stream = wm8904_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8904_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8904_selectable_formats), }; static struct snd_soc_dai_driver wm8904_dai = { @@ -2186,7 +2204,7 @@ static void wm8904_handle_pdata(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8904->drc_texts = kmalloc_array(pdata->num_drc_cfgs, - sizeof(char *), + sizeof(*wm8904->drc_texts), GFP_KERNEL); if (!wm8904->drc_texts) return; diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index 229252363188..012556215d12 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -730,6 +730,17 @@ static int wm8940_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8940_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8940_dai_ops = { .hw_params = wm8940_i2s_hw_params, .set_sysclk = wm8940_set_dai_sysclk, @@ -738,6 +749,8 @@ static const struct snd_soc_dai_ops wm8940_dai_ops = { .set_clkdiv = wm8940_set_dai_clkdiv, .set_pll = wm8940_set_dai_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8940_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8940_dai = { diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index c897a9ab764d..70daf9e6433b 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -426,7 +426,7 @@ static const char *treble_cutoff_text[] = { "High", "Low" }; -static SOC_ENUM_SINGLE_DECL(treble_cutoff, WM8955_TREBLE_CONTROL, 2, +static SOC_ENUM_SINGLE_DECL(treble_cutoff, WM8955_TREBLE_CONTROL, 6, treble_cutoff_text); static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1); @@ -845,12 +845,30 @@ static int wm8955_set_bias_level(struct snd_soc_component *component, #define WM8955_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8955_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8955_dai_ops = { .set_sysclk = wm8955_set_sysclk, .set_fmt = wm8955_set_fmt, .hw_params = wm8955_hw_params, .mute_stream = wm8955_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8955_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8955_selectable_formats), }; static struct snd_soc_dai_driver wm8955_dai = { @@ -870,17 +888,7 @@ static int wm8955_probe(struct snd_soc_component *component) struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component); struct wm8955_pdata *pdata = dev_get_platdata(component->dev); - int ret, i; - - for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++) - wm8955->supplies[i].supply = wm8955_supply_names[i]; - - ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8955->supplies), - wm8955->supplies); - if (ret != 0) { - dev_err(component->dev, "Failed to request supplies: %d\n", ret); - return ret; - } + int ret; ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); @@ -972,7 +980,7 @@ static const struct regmap_config wm8955_regmap = { static int wm8955_i2c_probe(struct i2c_client *i2c) { struct wm8955_priv *wm8955; - int ret; + int i, ret; wm8955 = devm_kzalloc(&i2c->dev, sizeof(struct wm8955_priv), GFP_KERNEL); @@ -989,6 +997,16 @@ static int wm8955_i2c_probe(struct i2c_client *i2c) i2c_set_clientdata(i2c, wm8955); + for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++) + wm8955->supplies[i].supply = wm8955_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm8955, &wm8955_dai, 1); diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c index f75a6dc9d2bb..b26c36ca8249 100644 --- a/sound/soc/codecs/wm8958-dsp2.c +++ b/sound/soc/codecs/wm8958-dsp2.c @@ -928,7 +928,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->mbc_texts = kmalloc_array(pdata->num_mbc_cfgs, - sizeof(char *), + sizeof(*wm8994->mbc_texts), GFP_KERNEL); if (!wm8994->mbc_texts) return; @@ -954,7 +954,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->vss_texts = kmalloc_array(pdata->num_vss_cfgs, - sizeof(char *), + sizeof(*wm8994->vss_texts), GFP_KERNEL); if (!wm8994->vss_texts) return; @@ -981,7 +981,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->vss_hpf_texts = kmalloc_array(pdata->num_vss_hpf_cfgs, - sizeof(char *), + sizeof(*wm8994->vss_hpf_texts), GFP_KERNEL); if (!wm8994->vss_hpf_texts) return; @@ -1009,7 +1009,7 @@ void wm8958_dsp2_init(struct snd_soc_component *component) /* We need an array of texts for the enum API */ wm8994->enh_eq_texts = kmalloc_array(pdata->num_enh_eq_cfgs, - sizeof(char *), + sizeof(*wm8994->enh_eq_texts), GFP_KERNEL); if (!wm8994->enh_eq_texts) return; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index a810732c6af0..093530ae872b 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -1340,6 +1340,17 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8960_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8960_dai_ops = { .hw_params = wm8960_hw_params, .hw_free = wm8960_hw_free, @@ -1349,6 +1360,8 @@ static const struct snd_soc_dai_ops wm8960_dai_ops = { .set_pll = wm8960_set_dai_pll, .set_sysclk = wm8960_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8960_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8960_dai = { diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 504fc59e4be2..55ccac7286c9 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -803,6 +803,22 @@ static int wm8961_set_bias_level(struct snd_soc_component *component, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8961_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8961_dai_ops = { .hw_params = wm8961_hw_params, .set_sysclk = wm8961_set_sysclk, @@ -811,6 +827,8 @@ static const struct snd_soc_dai_ops wm8961_dai_ops = { .set_tristate = wm8961_set_tristate, .set_clkdiv = wm8961_set_clkdiv, .no_capture_mute = 1, + .auto_selectable_formats = wm8961_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8961_selectable_formats), }; static struct snd_soc_dai_driver wm8961_dai = { diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 8a9598161b35..db8b86d522ff 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -3056,6 +3056,22 @@ static int wm8962_mute(struct snd_soc_dai *dai, int mute, int direction) #define WM8962_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8962_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8962_dai_ops = { .hw_params = wm8962_hw_params, .set_sysclk = wm8962_set_dai_sysclk, @@ -3063,6 +3079,8 @@ static const struct snd_soc_dai_ops wm8962_dai_ops = { .set_tdm_slot = wm8962_set_tdm_slot, .mute_stream = wm8962_mute, .no_capture_mute = 1, + .auto_selectable_formats = wm8962_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8962_selectable_formats), }; static struct snd_soc_dai_driver wm8962_dai = { @@ -3355,7 +3373,7 @@ static void wm8962_init_beep(struct snd_soc_component *component) struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); int ret; - wm8962->beep = devm_input_allocate_device(component->dev); + wm8962->beep = input_allocate_device(); if (!wm8962->beep) { dev_err(component->dev, "Failed to allocate beep device\n"); return; @@ -3376,8 +3394,10 @@ static void wm8962_init_beep(struct snd_soc_component *component) ret = input_register_device(wm8962->beep); if (ret != 0) { + input_free_device(wm8962->beep); wm8962->beep = NULL; dev_err(component->dev, "Failed to register beep device\n"); + return; } ret = device_create_file(component->dev, &dev_attr_beep); @@ -3393,7 +3413,10 @@ static void wm8962_free_beep(struct snd_soc_component *component) device_remove_file(component->dev, &dev_attr_beep); cancel_work_sync(&wm8962->beep_work); - wm8962->beep = NULL; + if (wm8962->beep) { + input_unregister_device(wm8962->beep); + wm8962->beep = NULL; + } snd_soc_component_update_bits(component, WM8962_BEEP_GENERATOR_1, WM8962_BEEP_ENA,0); } @@ -3430,7 +3453,7 @@ static int wm8962_gpio_request(struct gpio_chip *chip, unsigned offset) /* The WM8962 GPIOs aren't linearly numbered. For simplicity * we export linear numbers and error out if the unsupported - * ones are requsted. + * ones are requested. */ switch (offset + 1) { case 2: @@ -3525,34 +3548,12 @@ static void wm8962_free_gpio(struct snd_soc_component *component) static int wm8962_probe(struct snd_soc_component *component) { struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); - int ret; struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component); int i; bool dmicclk, dmicdat; wm8962->component = component; - wm8962->disable_nb[0].notifier_call = wm8962_regulator_event_0; - wm8962->disable_nb[1].notifier_call = wm8962_regulator_event_1; - wm8962->disable_nb[2].notifier_call = wm8962_regulator_event_2; - wm8962->disable_nb[3].notifier_call = wm8962_regulator_event_3; - wm8962->disable_nb[4].notifier_call = wm8962_regulator_event_4; - wm8962->disable_nb[5].notifier_call = wm8962_regulator_event_5; - wm8962->disable_nb[6].notifier_call = wm8962_regulator_event_6; - wm8962->disable_nb[7].notifier_call = wm8962_regulator_event_7; - - /* This should really be moved into the regulator core */ - for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) { - ret = devm_regulator_register_notifier( - wm8962->supplies[i].consumer, - &wm8962->disable_nb[i]); - if (ret != 0) { - dev_err(component->dev, - "Failed to register regulator notifier: %d\n", - ret); - } - } - wm8962_add_widgets(component); /* Save boards having to disable DMIC when not in use */ @@ -3742,6 +3743,27 @@ static int wm8962_i2c_probe(struct i2c_client *i2c) regcache_cache_bypass(wm8962->regmap, false); + wm8962->disable_nb[0].notifier_call = wm8962_regulator_event_0; + wm8962->disable_nb[1].notifier_call = wm8962_regulator_event_1; + wm8962->disable_nb[2].notifier_call = wm8962_regulator_event_2; + wm8962->disable_nb[3].notifier_call = wm8962_regulator_event_3; + wm8962->disable_nb[4].notifier_call = wm8962_regulator_event_4; + wm8962->disable_nb[5].notifier_call = wm8962_regulator_event_5; + wm8962->disable_nb[6].notifier_call = wm8962_regulator_event_6; + wm8962->disable_nb[7].notifier_call = wm8962_regulator_event_7; + + /* This should really be moved into the regulator core */ + for (i = 0; i < ARRAY_SIZE(wm8962->supplies); i++) { + ret = devm_regulator_register_notifier(wm8962->supplies[i].consumer, + &wm8962->disable_nb[i]); + if (ret != 0) { + dev_err(&i2c->dev, + "Failed to register regulator notifier: %d\n", + ret); + goto err_enable; + } + } + ret = wm8962_reset(wm8962); if (ret < 0) { dev_err(&i2c->dev, "Failed to issue reset\n"); @@ -3813,7 +3835,7 @@ static int wm8962_i2c_probe(struct i2c_client *i2c) regmap_update_bits(wm8962->regmap, WM8962_EQ1, WM8962_EQ_SHARED_COEFF, 0); - /* Don't debouce interrupts so we don't need SYSCLK */ + /* Don't debounce interrupts so we don't need SYSCLK */ regmap_update_bits(wm8962->regmap, WM8962_IRQ_DEBOUNCE, WM8962_FLL_LOCK_DB | WM8962_PLL3_LOCK_DB | WM8962_PLL2_LOCK_DB | WM8962_TEMP_SHUT_DB, diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index a5d2e91b66bb..388ee05d4520 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -601,12 +601,25 @@ static int wm8971_set_bias_level(struct snd_soc_component *component, #define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8971_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8971_dai_ops = { .hw_params = wm8971_pcm_hw_params, .mute_stream = wm8971_mute, .set_fmt = wm8971_set_dai_fmt, .set_sysclk = wm8971_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8971_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8971_dai = { diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index 4656652b8e97..5e1737d455b1 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -623,6 +623,21 @@ static int wm8974_set_bias_level(struct snd_soc_component *component, #define WM8974_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8974_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8974_ops = { .hw_params = wm8974_pcm_hw_params, .mute_stream = wm8974_mute, @@ -631,6 +646,8 @@ static const struct snd_soc_dai_ops wm8974_ops = { .set_pll = wm8974_set_dai_pll, .set_sysclk = wm8974_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = wm8974_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8974_selectable_formats), }; static struct snd_soc_dai_driver wm8974_dai = { diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index e40ba4d43ada..258bc9f88f52 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -892,6 +892,16 @@ static int wm8978_set_bias_level(struct snd_soc_component *component, #define WM8978_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8978_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8978_dai_ops = { .hw_params = wm8978_hw_params, .mute_stream = wm8978_mute, @@ -899,6 +909,8 @@ static const struct snd_soc_dai_ops wm8978_dai_ops = { .set_clkdiv = wm8978_set_dai_clkdiv, .set_sysclk = wm8978_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8978_selectable_formats, + .num_auto_selectable_formats = 1, }; /* Also supports 12kHz */ diff --git a/sound/soc/codecs/wm8983.c b/sound/soc/codecs/wm8983.c index fab25f35fd14..d4b0f8e141cd 100644 --- a/sound/soc/codecs/wm8983.c +++ b/sound/soc/codecs/wm8983.c @@ -234,7 +234,7 @@ static const struct snd_kcontrol_new wm8983_snd_controls[] = { 8, 1, 0, pga_boost_tlv), SOC_DOUBLE("ADC Inversion Switch", WM8983_ADC_CONTROL, 0, 1, 1, 0), - SOC_SINGLE("ADC 128x Oversampling Switch", WM8983_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8983_ADC_CONTROL, 3, 1, 0), SOC_DOUBLE_R_TLV("Playback Volume", WM8983_LEFT_DAC_DIGITAL_VOL, WM8983_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv), @@ -943,6 +943,16 @@ static int wm8983_probe(struct snd_soc_component *component) return 0; } +/* FIXME: it doesn't support DSP A/B for now */ +static const u64 wm8983_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8983_dai_ops = { .mute_stream = wm8983_dac_mute, .hw_params = wm8983_hw_params, @@ -950,6 +960,8 @@ static const struct snd_soc_dai_ops wm8983_dai_ops = { .set_sysclk = wm8983_set_sysclk, .set_pll = wm8983_set_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8983_selectable_formats, + .num_auto_selectable_formats = 1, }; #define WM8983_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c index 2a64d5a851da..9264374bb8e4 100644 --- a/sound/soc/codecs/wm8985.c +++ b/sound/soc/codecs/wm8985.c @@ -327,7 +327,7 @@ static const struct snd_kcontrol_new wm8985_common_snd_controls[] = { 8, 1, 0, pga_boost_tlv), SOC_DOUBLE("ADC Inversion Switch", WM8985_ADC_CONTROL, 0, 1, 1, 0), - SOC_SINGLE("ADC 128x Oversampling Switch", WM8985_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("ADC 128x Oversampling Switch", WM8985_ADC_CONTROL, 3, 1, 0), SOC_DOUBLE_R_TLV("Playback Volume", WM8985_LEFT_DAC_DIGITAL_VOL, WM8985_RIGHT_DAC_DIGITAL_VOL, 0, 255, 0, dac_tlv), @@ -1024,6 +1024,23 @@ static int wm8985_set_bias_level(struct snd_soc_component *component, return 0; } +static int wm8985_get_regulators(struct device *dev, + struct wm8985_priv *wm8985) +{ + size_t i; + int ret; + + for (i = 0; i < ARRAY_SIZE(wm8985->supplies); i++) + wm8985->supplies[i].supply = wm8985_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(wm8985->supplies), + wm8985->supplies); + if (ret) + dev_err(dev, "Failed to request supplies: %d\n", ret); + + return ret; +} + static int wm8985_probe(struct snd_soc_component *component) { size_t i; @@ -1032,16 +1049,6 @@ static int wm8985_probe(struct snd_soc_component *component) wm8985 = snd_soc_component_get_drvdata(component); - for (i = 0; i < ARRAY_SIZE(wm8985->supplies); i++) - wm8985->supplies[i].supply = wm8985_supply_names[i]; - - ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8985->supplies), - wm8985->supplies); - if (ret) { - dev_err(component->dev, "Failed to request supplies: %d\n", ret); - return ret; - } - ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies), wm8985->supplies); if (ret) { @@ -1072,6 +1079,22 @@ static int wm8985_probe(struct snd_soc_component *component) return ret; } +static const u64 wm8985_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8985_dai_ops = { .mute_stream = wm8985_dac_mute, .hw_params = wm8985_hw_params, @@ -1079,6 +1102,8 @@ static const struct snd_soc_dai_ops wm8985_dai_ops = { .set_sysclk = wm8985_set_sysclk, .set_pll = wm8985_set_pll, .no_capture_mute = 1, + .auto_selectable_formats = wm8985_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8985_selectable_formats), }; #define WM8985_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ @@ -1153,6 +1178,10 @@ static int wm8985_spi_probe(struct spi_device *spi) return ret; } + ret = wm8985_get_regulators(&spi->dev, wm8985); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&spi->dev, &soc_component_dev_wm8985, &wm8985_dai, 1); return ret; @@ -1189,6 +1218,10 @@ static int wm8985_i2c_probe(struct i2c_client *i2c) return ret; } + ret = wm8985_get_regulators(&i2c->dev, wm8985); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm8985, &wm8985_dai, 1); return ret; diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index 741aecc7641a..6f97c9aa453a 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -762,6 +762,17 @@ static int wm8988_set_bias_level(struct snd_soc_component *component, #define WM8988_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8988_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8988_ops = { .startup = wm8988_pcm_startup, .hw_params = wm8988_pcm_hw_params, @@ -769,6 +780,8 @@ static const struct snd_soc_dai_ops wm8988_ops = { .set_sysclk = wm8988_set_dai_sysclk, .mute_stream = wm8988_mute, .no_capture_mute = 1, + .auto_selectable_formats = &wm8988_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8988_dai = { diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index 11dfbbbdcd92..a5e032a3f9f6 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -97,8 +97,8 @@ static const struct snd_kcontrol_new wm8990_snd_controls[] = { SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0), SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0), /* INMIXR */ -SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0), -SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0), +SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER4, WM8990_R12MNBST_BIT, 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER4, WM8990_R34MNBST_BIT, 1, 0), /* LOMIX */ SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3, @@ -112,7 +112,7 @@ SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5, SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5, WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5, - WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), + WM8990_LLBLOVOL_SHIFT, WM8990_LLBLOVOL_MASK, 1, out_mix_tlv), /* ROMIX */ SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4, @@ -377,9 +377,9 @@ SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4, WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv), SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT, 7, 0, in_mix_tlv), -SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER4, WM8990_R12MNB_BIT, 1, 0), -SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER4, WM8990_R34MNB_BIT, 1, 0), }; @@ -1151,6 +1151,13 @@ static int wm8990_set_bias_level(struct snd_soc_component *component, * 1. ADC/DAC on Primary Interface * 2. ADC on Primary Interface/DAC on secondary */ +static const u64 wm8990_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops wm8990_dai_ops = { .hw_params = wm8990_hw_params, .mute_stream = wm8990_mute, @@ -1159,6 +1166,8 @@ static const struct snd_soc_dai_ops wm8990_dai_ops = { .set_pll = wm8990_set_dai_pll, .set_sysclk = wm8990_set_dai_sysclk, .no_capture_mute = 1, + .auto_selectable_formats = &wm8990_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8990_dai = { diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c index 225c235d3d89..553022bcd666 100644 --- a/sound/soc/codecs/wm8991.c +++ b/sound/soc/codecs/wm8991.c @@ -169,8 +169,8 @@ static const struct snd_kcontrol_new wm8991_snd_controls[] = { SOC_SINGLE("LIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L12MNBST_BIT, 1, 0), SOC_SINGLE("LIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L34MNBST_BIT, 1, 0), /* INMIXR */ - SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R12MNBST_BIT, 1, 0), - SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R34MNBST_BIT, 1, 0), + SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER4, WM8991_R12MNBST_BIT, 1, 0), + SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER4, WM8991_R34MNBST_BIT, 1, 0), /* LOMIX */ SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER3, @@ -184,7 +184,7 @@ static const struct snd_kcontrol_new wm8991_snd_controls[] = { SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER5, WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER5, - WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), + WM8991_LLBLOVOL_SHIFT, WM8991_LLBLOVOL_MASK, 1, out_mix_tlv), /* ROMIX */ SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER4, @@ -446,9 +446,9 @@ static const struct snd_kcontrol_new wm8991_dapm_inmixr_controls[] = { WM8991_RDBVOL_SHIFT, WM8991_RDBVOL_MASK, 0, in_mix_tlv), SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8991_INPUT_MIXER6, WM8991_RI2BVOL_SHIFT, 7, 0, in_mix_tlv), - SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT, + SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER4, WM8991_R12MNB_BIT, 1, 0), - SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT, + SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER4, WM8991_R34MNB_BIT, 1, 0), }; @@ -1195,6 +1195,13 @@ static int wm8991_set_bias_level(struct snd_soc_component *component, #define WM8991_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm8991_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops wm8991_ops = { .hw_params = wm8991_hw_params, .mute_stream = wm8991_mute, @@ -1202,6 +1209,8 @@ static const struct snd_soc_dai_ops wm8991_ops = { .set_clkdiv = wm8991_set_dai_clkdiv, .set_pll = wm8991_set_dai_pll, .no_capture_mute = 1, + .auto_selectable_formats = &wm8991_selectable_formats, + .num_auto_selectable_formats = 1, }; /* diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 2a94b23319b5..934fa41a53cf 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -1441,6 +1441,22 @@ static irqreturn_t wm8993_irq(int irq, void *data) return IRQ_HANDLED; } +static const u64 wm8993_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8993_ops = { .set_sysclk = wm8993_set_sysclk, .set_fmt = wm8993_set_dai_fmt, @@ -1449,6 +1465,8 @@ static const struct snd_soc_dai_ops wm8993_ops = { .set_pll = wm8993_set_fll, .set_tdm_slot = wm8993_set_tdm_slot, .no_capture_mute = 1, + .auto_selectable_formats = wm8993_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8993_selectable_formats), }; #define WM8993_RATES SNDRV_PCM_RATE_8000_48000 diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 8bf58a6a8af7..c65a7769a9d2 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -701,17 +701,17 @@ static const char *wm8958_ng_text[] = { static SOC_ENUM_SINGLE_DECL(wm8958_aif1dac1_ng_hold, WM8958_AIF1_DAC1_NOISE_GATE, - WM8958_AIF1DAC1_NG_THR_SHIFT, + WM8958_AIF1DAC1_NG_HLD_SHIFT, wm8958_ng_text); static SOC_ENUM_SINGLE_DECL(wm8958_aif1dac2_ng_hold, WM8958_AIF1_DAC2_NOISE_GATE, - WM8958_AIF1DAC2_NG_THR_SHIFT, + WM8958_AIF1DAC2_NG_HLD_SHIFT, wm8958_ng_text); static SOC_ENUM_SINGLE_DECL(wm8958_aif2dac_ng_hold, WM8958_AIF2_DAC_NOISE_GATE, - WM8958_AIF2DAC_NG_THR_SHIFT, + WM8958_AIF2DAC_NG_HLD_SHIFT, wm8958_ng_text); static const struct snd_kcontrol_new wm8958_snd_controls[] = { @@ -3202,6 +3202,22 @@ static int wm8994_aif2_probe(struct snd_soc_dai *dai) #define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8994_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = { .set_sysclk = wm8994_set_dai_sysclk, .set_fmt = wm8994_set_dai_fmt, @@ -3210,6 +3226,8 @@ static const struct snd_soc_dai_ops wm8994_aif1_dai_ops = { .set_pll = wm8994_set_fll, .set_tristate = wm8994_set_tristate, .no_capture_mute = 1, + .auto_selectable_formats = wm8994_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8994_selectable_formats), }; static const struct snd_soc_dai_ops wm8994_aif2_dai_ops = { @@ -3221,6 +3239,8 @@ static const struct snd_soc_dai_ops wm8994_aif2_dai_ops = { .set_pll = wm8994_set_fll, .set_tristate = wm8994_set_tristate, .no_capture_mute = 1, + .auto_selectable_formats = wm8994_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8994_selectable_formats), }; static const struct snd_soc_dai_ops wm8994_aif3_dai_ops = { @@ -3532,6 +3552,7 @@ int wm8994_mic_detect(struct snd_soc_component *component, struct snd_soc_jack * break; default: dev_warn(component->dev, "Invalid MICBIAS %d\n", micbias); + pm_runtime_put(component->dev); return -EINVAL; } @@ -4340,6 +4361,7 @@ static int wm8994_component_probe(struct snd_soc_component *component) ret = regmap_read(control->regmap, WM8994_GPIO_1, ®); if (ret < 0) { dev_err(component->dev, "Failed to read GPIO1 state: %d\n", ret); + pm_runtime_put(component->dev); goto err_irq; } if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { @@ -4352,6 +4374,7 @@ static int wm8994_component_probe(struct snd_soc_component *component) ret = regmap_read(control->regmap, WM8994_GPIO_6, ®); if (ret < 0) { dev_err(component->dev, "Failed to read GPIO6 state: %d\n", ret); + pm_runtime_put(component->dev); goto err_irq; } if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { diff --git a/sound/soc/codecs/wm8995.c b/sound/soc/codecs/wm8995.c index 799989a5bf0f..7f01d203e058 100644 --- a/sound/soc/codecs/wm8995.c +++ b/sound/soc/codecs/wm8995.c @@ -1993,23 +1993,17 @@ static int wm8995_set_bias_level(struct snd_soc_component *component, return 0; } -static int wm8995_probe(struct snd_soc_component *component) +static int wm8995_hw_init(struct device *dev, struct wm8995_priv *wm8995) { - struct wm8995_priv *wm8995; - int i; - int ret; - - wm8995 = snd_soc_component_get_drvdata(component); - wm8995->component = component; + int i, ret; for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) wm8995->supplies[i].supply = wm8995_supply_names[i]; - ret = devm_regulator_bulk_get(component->dev, - ARRAY_SIZE(wm8995->supplies), + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(wm8995->supplies), wm8995->supplies); if (ret) { - dev_err(component->dev, "Failed to request supplies: %d\n", ret); + dev_err(dev, "Failed to request supplies: %d\n", ret); return ret; } @@ -2024,16 +2018,27 @@ static int wm8995_probe(struct snd_soc_component *component) /* This should really be moved into the regulator core */ for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) { - ret = devm_regulator_register_notifier( - wm8995->supplies[i].consumer, - &wm8995->disable_nb[i]); + ret = devm_regulator_register_notifier(wm8995->supplies[i].consumer, + &wm8995->disable_nb[i]); if (ret) { - dev_err(component->dev, + dev_err(dev, "Failed to register regulator notifier: %d\n", ret); + return ret; } } + return 0; +} + +static int wm8995_probe(struct snd_soc_component *component) +{ + struct wm8995_priv *wm8995; + int ret; + + wm8995 = snd_soc_component_get_drvdata(component); + wm8995->component = component; + ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies), wm8995->supplies); if (ret) { @@ -2091,6 +2096,22 @@ static int wm8995_probe(struct snd_soc_component *component) #define WM8995_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8995_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm8995_aif1_dai_ops = { .set_sysclk = wm8995_set_dai_sysclk, .set_fmt = wm8995_set_dai_fmt, @@ -2099,6 +2120,8 @@ static const struct snd_soc_dai_ops wm8995_aif1_dai_ops = { .set_pll = wm8995_set_fll, .set_tristate = wm8995_set_tristate, .no_capture_mute = 1, + .auto_selectable_formats = wm8995_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8995_selectable_formats), }; static const struct snd_soc_dai_ops wm8995_aif2_dai_ops = { @@ -2109,6 +2132,8 @@ static const struct snd_soc_dai_ops wm8995_aif2_dai_ops = { .set_pll = wm8995_set_fll, .set_tristate = wm8995_set_tristate, .no_capture_mute = 1, + .auto_selectable_formats = wm8995_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm8995_selectable_formats), }; static const struct snd_soc_dai_ops wm8995_aif3_dai_ops = { @@ -2216,6 +2241,10 @@ static int wm8995_spi_probe(struct spi_device *spi) return ret; } + ret = wm8995_hw_init(&spi->dev, wm8995); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&spi->dev, &soc_component_dev_wm8995, wm8995_dai, ARRAY_SIZE(wm8995_dai)); @@ -2249,6 +2278,10 @@ static int wm8995_i2c_probe(struct i2c_client *i2c) return ret; } + ret = wm8995_hw_init(&i2c->dev, wm8995); + if (ret) + return ret; + ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_dev_wm8995, wm8995_dai, ARRAY_SIZE(wm8995_dai)); diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c index dface555f928..9d8fb4ee4312 100644 --- a/sound/soc/codecs/wm8996.c +++ b/sound/soc/codecs/wm8996.c @@ -2106,7 +2106,7 @@ static int wm8996_set_fll(struct snd_soc_component *component, int fll_id, int s if (i2c->irq) timeout *= 10; else - /* ensure timeout of atleast 1 jiffies */ + /* ensure timeout of at least 1 jiffies */ timeout = (timeout/2) ? : 1; for (retry = 0; retry < 10; retry++) { @@ -2703,10 +2703,22 @@ static const struct snd_soc_component_driver soc_component_dev_wm8996 = { SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE |\ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm8996_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm8996_dai_ops = { .set_fmt = wm8996_set_fmt, .hw_params = wm8996_hw_params, .set_sysclk = wm8996_set_sysclk, + .auto_selectable_formats = &wm8996_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm8996_dai[] = { diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 2e4e0a76499d..824e9c6b5f3e 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -1230,12 +1230,30 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 wm9081_selectable_formats[] = { + /* 1st priority */ + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF, + /* 2nd priority */ + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF, +}; + static const struct snd_soc_dai_ops wm9081_dai_ops = { .hw_params = wm9081_hw_params, .set_fmt = wm9081_set_dai_fmt, .mute_stream = wm9081_mute, .set_tdm_slot = wm9081_set_tdm_slot, .no_capture_mute = 1, + .auto_selectable_formats = wm9081_selectable_formats, + .num_auto_selectable_formats = ARRAY_SIZE(wm9081_selectable_formats), }; /* We report two channels because the CODEC processes a stereo signal, even diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index 3ba7ca3c1770..3cffdfbbf9dd 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -1067,6 +1067,17 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 wm9713_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops wm9713_dai_ops_hifi = { .prepare = ac97_hifi_prepare, .set_clkdiv = wm9713_set_dai_clkdiv, @@ -1085,6 +1096,8 @@ static const struct snd_soc_dai_ops wm9713_dai_ops_voice = { .set_pll = wm9713_set_dai_pll, .set_fmt = wm9713_set_dai_fmt, .set_tristate = wm9713_set_dai_tristate, + .auto_selectable_formats = &wm9713_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver wm9713_dai[] = { diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index f80dfc488e3c..7b77e9725385 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -338,7 +338,7 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, ret = snd_soc_put_volsw(kcontrol, ucontrol); /* If we're applying an offset correction then updating the - * callibration would be likely to introduce further offsets. */ + * calibration would be likely to introduce further offsets. */ if (hubs->dcs_codes_l || hubs->dcs_codes_r || hubs->no_series_update) return ret; @@ -427,7 +427,7 @@ SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume", SOC_SINGLE_TLV("Right Output Mixer Left Input Volume", WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv), SOC_SINGLE_TLV("Right Output Mixer Right Input Volume", - WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), + WM8993_OUTPUT_MIXER6, 0, 7, 1, outmix_tlv), SOC_SINGLE_TLV("Right Output Mixer DAC Volume", WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv), diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c index 1faa8541a872..5ffbf9e89fd9 100644 --- a/sound/soc/codecs/wsa885x.c +++ b/sound/soc/codecs/wsa885x.c @@ -896,7 +896,6 @@ static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); - regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); return 0; } @@ -1006,6 +1005,16 @@ static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { static const struct reg_sequence regs[] = { + { WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00 }, + { WSA885X_DIG_CTRL1_I2S_CTL0, 0x06 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, 0x08 }, + { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, + { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, }; struct wsa885x_priv *wsa885x; diff --git a/sound/soc/codecs/zl38060.c b/sound/soc/codecs/zl38060.c index 894b8eb42e39..b7062860cca1 100644 --- a/sound/soc/codecs/zl38060.c +++ b/sound/soc/codecs/zl38060.c @@ -334,10 +334,16 @@ static const struct reg_sequence cp_config_stereo_bypass[] = { { 0x0202, 0x000F }, /* enable I2S1 + DAC */ }; +static const u64 zl38_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops zl38_dai_ops = { .set_fmt = zl38_set_fmt, .hw_params = zl38_hw_params, .hw_free = zl38_hw_free, + .auto_selectable_formats = &zl38_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver zl38_dai = { diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 2e9fb79b1cd8..dbda943cfaf3 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -464,6 +465,13 @@ static int dw_i2s_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 dw_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + static const struct snd_soc_dai_ops dw_i2s_dai_ops = { .probe = dw_i2s_dai_probe, .startup = dw_i2s_startup, @@ -472,6 +480,8 @@ static const struct snd_soc_dai_ops dw_i2s_dai_ops = { .trigger = dw_i2s_trigger, .set_fmt = dw_i2s_set_fmt, .set_tdm_slot = dw_i2s_set_tdm_slot, + .auto_selectable_formats = &dw_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static int dw_i2s_runtime_suspend(struct device *dev) @@ -913,13 +923,16 @@ static int jh7110_i2stx0_clk_cfg(struct i2s_clk_config_data *config) static int dw_i2s_probe(struct platform_device *pdev) { - const struct i2s_platform_data *pdata = pdev->dev.platform_data; + const struct i2s_platform_data *pdata = device_get_match_data(&pdev->dev); struct dw_i2s_dev *dev; struct resource *res; int ret, irq; struct snd_soc_dai_driver *dw_i2s_dai; const char *clk_id; + if (!pdata) + pdata = dev_get_platdata(&pdev->dev); + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 04940879dfd8..101bc952aa14 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -234,7 +234,7 @@ config SND_SOC_MPC5200_I2S config SND_SOC_MPC5200_AC97 tristate "Freescale MPC5200 PSC in AC97 mode driver" - depends on PPC_BESTCOMM + depends on PPC_BESTCOMM && PPC_MPC52xx select SND_SOC_AC97_BUS select SND_MPC52xx_DMA select PPC_BESTCOMM_GEN_BD diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index b24737add001..faf348dd582d 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -521,10 +521,10 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool use_ideal_rate) ASRCTR_USR(index)); /* Set the input and output clock sources */ - regmap_update_bits(asrc->regmap, REG_ASRCSR, - ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index), - ASRCSR_AICS(index, clk_index[IN]) | - ASRCSR_AOCS(index, clk_index[OUT])); + regmap_write_bits(asrc->regmap, REG_ASRCSR, + ASRCSR_AICSi_MASK(index) | ASRCSR_AOCSi_MASK(index), + ASRCSR_AICS(index, clk_index[IN]) | + ASRCSR_AOCS(index, clk_index[OUT])); /* Calculate the input clock divisors */ indiv = fsl_asrc_cal_asrck_divisor(pair, div[IN]); @@ -638,7 +638,7 @@ static struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, sprintf(name, "%cx%c", dir == IN ? 'r' : 't', index + 'a'); - return dma_request_slave_channel(&asrc->pdev->dev, name); + return dma_request_chan(&asrc->pdev->dev, name); } static int fsl_asrc_dai_startup(struct snd_pcm_substream *substream, @@ -781,18 +781,7 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } -static int fsl_asrc_dai_probe(struct snd_soc_dai *dai) -{ - struct fsl_asrc *asrc = snd_soc_dai_get_drvdata(dai); - - snd_soc_dai_init_dma_data(dai, &asrc->dma_params_tx, - &asrc->dma_params_rx); - - return 0; -} - static const struct snd_soc_dai_ops fsl_asrc_dai_ops = { - .probe = fsl_asrc_dai_probe, .startup = fsl_asrc_dai_startup, .hw_params = fsl_asrc_dai_hw_params, .hw_free = fsl_asrc_dai_hw_free, @@ -803,27 +792,76 @@ static const struct snd_soc_dai_ops fsl_asrc_dai_ops = { SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S24_3LE) -static struct snd_soc_dai_driver fsl_asrc_dai = { - .playback = { - .stream_name = "ASRC-Playback", - .channels_min = 1, - .channels_max = 10, - .rate_min = 5512, - .rate_max = 192000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS | - SNDRV_PCM_FMTBIT_S8, +static struct snd_soc_dai_driver fsl_asrc_dai[] = { + { + .name = "asrc-0", + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS | + SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, }, - .capture = { - .stream_name = "ASRC-Capture", - .channels_min = 1, - .channels_max = 10, - .rate_min = 5512, - .rate_max = 192000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_ASRC_FORMATS, + { + .name = "asrc-1", + .playback = { + .stream_name = "ASRC1-Playback", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS | + SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "ASRC1-Capture", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, + }, + { + .name = "asrc-2", + .playback = { + .stream_name = "ASRC2-Playback", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS | + SNDRV_PCM_FMTBIT_S8, + }, + .capture = { + .stream_name = "ASRC2-Capture", + .channels_min = 1, + .channels_max = 10, + .rate_min = 5512, + .rate_max = 192000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_ASRC_FORMATS, + }, + .ops = &fsl_asrc_dai_ops, }, - .ops = &fsl_asrc_dai_ops, }; static bool fsl_asrc_readable_reg(struct device *dev, unsigned int reg) @@ -1404,7 +1442,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) goto err_pm_get_sync; ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component, - &fsl_asrc_dai, 1); + fsl_asrc_dai, ARRAY_SIZE(fsl_asrc_dai)); if (ret) { dev_err(&pdev->dev, "failed to register ASoC DAI\n"); goto err_pm_get_sync; diff --git a/sound/soc/fsl/fsl_asrc_common.h b/sound/soc/fsl/fsl_asrc_common.h index c8a1a2b5915d..4e6b00cb5d63 100644 --- a/sound/soc/fsl/fsl_asrc_common.h +++ b/sound/soc/fsl/fsl_asrc_common.h @@ -53,6 +53,7 @@ struct fsl_asrc_m2m_cap { * @dma_data: private dma data * @pos: hardware pointer position * @req_dma_chan: flag to release dev_to_dev chan + * @dma_params: DMA parameters for transmit/receive channel * @private: pair private area * @complete: dma task complete * @sample_format: format of m2m @@ -76,6 +77,8 @@ struct fsl_asrc_pair { unsigned int pos; bool req_dma_chan; + struct snd_dmaengine_dai_dma_data dma_params; + void *private; /* used for m2m */ @@ -92,8 +95,6 @@ struct fsl_asrc_pair { /** * fsl_asrc: ASRC common data * - * @dma_params_rx: DMA parameters for receive channel - * @dma_params_tx: DMA parameters for transmit channel * @pdev: platform device pointer * @regmap: regmap handler * @paddr: physical address to the base address of registers @@ -128,8 +129,6 @@ struct fsl_asrc_pair { * @private: private data structure */ struct fsl_asrc { - struct snd_dmaengine_dai_dma_data dma_params_rx; - struct snd_dmaengine_dai_dma_data dma_params_tx; struct platform_device *pdev; struct regmap *regmap; unsigned long paddr; diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 2f662bdf14d0..b417de6abebe 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -136,8 +136,8 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, struct snd_dmaengine_dai_dma_data *dma_params_be = NULL; struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; - struct dma_chan *tmp_chan = NULL, *be_chan = NULL; struct snd_soc_component *component_be = NULL; + struct dma_chan *tmp_chan, *be_chan = NULL; struct fsl_asrc *asrc = pair->asrc; struct dma_slave_config config_fe = {}, config_be = {}; struct sdma_peripheral_config audio_config; @@ -190,11 +190,12 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, dma_params_fe->addr = asrc->paddr + asrc->get_fifo_addr(!dir, index); dma_params_fe->maxburst = dma_params_be->maxburst; - pair->dma_chan[!dir] = asrc->get_dma_channel(pair, !dir); - if (!pair->dma_chan[!dir]) { + tmp_chan = asrc->get_dma_channel(pair, !dir); + if (IS_ERR(tmp_chan)) { dev_err(dev, "failed to request DMA channel\n"); - return -EINVAL; + return PTR_ERR(tmp_chan); } + pair->dma_chan[!dir] = tmp_chan; ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe); if (ret) { @@ -223,6 +224,8 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, be_chan = pcm->chan[substream->stream]; tmp_chan = be_chan; + } else { + tmp_chan = NULL; } if (!tmp_chan) { tmp_chan = dma_request_chan(dev_be, tx ? "tx" : "rx"); @@ -248,6 +251,11 @@ 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) { + dma_release_channel(pair->dma_chan[!dir]); + pair->dma_chan[!dir] = NULL; + return -EINVAL; + } tmp_data = tmp_chan->private; pair->dma_data.dma_request2 = tmp_data->dma_request; pair->dma_data.peripheral_type = tmp_data->peripheral_type; @@ -392,6 +400,20 @@ static int fsl_asrc_dma_startup(struct snd_soc_component *component, runtime->private_data = pair; + /* + * Point the cpu DAI dma_data at the per-pair params so that + * concurrent hw_params calls on different pairs each write to + * their own struct and do not race on addr/maxburst. Use the + * per-direction setters so that a concurrent open of the other + * direction on the same DAI does not NULL out its pointer. + */ + if (tx) + snd_soc_dai_dma_data_set_playback(snd_soc_rtd_to_cpu(rtd, 0), + &pair->dma_params); + else + snd_soc_dai_dma_data_set_capture(snd_soc_rtd_to_cpu(rtd, 0), + &pair->dma_params); + /* Request a dummy pair, which will be released later. * Request pair function needs channel num as input, for this * dummy pair, we just request "1" channel temporarily. @@ -404,9 +426,9 @@ static int fsl_asrc_dma_startup(struct snd_soc_component *component, /* Request a dummy dma channel, which will be released later. */ tmp_chan = asrc->get_dma_channel(pair, dir); - if (!tmp_chan) { + ret = PTR_ERR_OR_ZERO(tmp_chan); + if (ret) { dev_err(dev, "failed to get dma channel\n"); - ret = -EINVAL; goto dma_chan_err; } @@ -497,9 +519,9 @@ static int fsl_asrc_dma_pcm_new(struct snd_soc_component *component, /* Request a dma channel, which will be released later. */ chan = asrc->get_dma_channel(pair, IN); - if (!chan) { + ret = PTR_ERR_OR_ZERO(chan); + if (ret) { dev_err(dev, "failed to get dma channel\n"); - ret = -EINVAL; goto dma_chan_err; } diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 4bc40f328f58..58101ce07c9f 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -320,7 +320,7 @@ static int fsl_asrc_m2m_comp_open(struct snd_compr_stream *stream) if (ret) goto error_alloc_out_buf; - ret = pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); if (ret < 0) { dev_err(dev, "Failed to power up asrc\n"); goto err_pm_runtime; @@ -466,6 +466,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, struct snd_compr_runtime *runtime = stream->runtime; struct fsl_asrc_pair *pair = runtime->private_data; struct device *dev = &asrc->pdev->dev; + struct dma_chan *tmp_chan; int ret; exp_info_in.ops = &fsl_asrc_m2m_dma_buf_ops; @@ -475,6 +476,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, task->input = dma_buf_export(&exp_info_in); if (IS_ERR(task->input)) { ret = PTR_ERR(task->input); + task->input = NULL; return ret; } @@ -485,6 +487,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, task->output = dma_buf_export(&exp_info_out); if (IS_ERR(task->output)) { ret = PTR_ERR(task->output); + task->output = NULL; return ret; } @@ -502,19 +505,21 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, } /* Request dma channels */ - pair->dma_chan[IN] = asrc->get_dma_channel(pair, IN); - if (!pair->dma_chan[IN]) { + tmp_chan = asrc->get_dma_channel(pair, IN); + ret = PTR_ERR_OR_ZERO(tmp_chan); + if (ret) { dev_err(dev, "[ctx%d] failed to get input DMA channel\n", pair->index); - ret = -EBUSY; goto err_dma_channel_in; } + pair->dma_chan[IN] = tmp_chan; - pair->dma_chan[OUT] = asrc->get_dma_channel(pair, OUT); - if (!pair->dma_chan[OUT]) { + tmp_chan = asrc->get_dma_channel(pair, OUT); + ret = PTR_ERR_OR_ZERO(tmp_chan); + if (ret) { dev_err(dev, "[ctx%d] failed to get output DMA channel\n", pair->index); - ret = -EBUSY; goto err_dma_channel_out; } + pair->dma_chan[OUT] = tmp_chan; return 0; diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index aced16d1228a..5d715bc07357 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -1433,7 +1433,7 @@ static struct dma_chan *fsl_easrc_get_dma_channel(struct fsl_asrc_pair *ctx, /* Example of dma name: ctx0_rx */ sprintf(name, "ctx%c_%cx", index + '0', dir == IN ? 'r' : 't'); - return dma_request_slave_channel(&easrc->pdev->dev, name); + return dma_request_chan(&easrc->pdev->dev, name); }; static const unsigned int easrc_rates[] = { @@ -1582,45 +1582,106 @@ static int fsl_easrc_hw_free(struct snd_pcm_substream *substream, return 0; } -static int fsl_easrc_dai_probe(struct snd_soc_dai *cpu_dai) -{ - struct fsl_asrc *easrc = dev_get_drvdata(cpu_dai->dev); - - snd_soc_dai_init_dma_data(cpu_dai, - &easrc->dma_params_tx, - &easrc->dma_params_rx); - return 0; -} - static const struct snd_soc_dai_ops fsl_easrc_dai_ops = { - .probe = fsl_easrc_dai_probe, .startup = fsl_easrc_startup, .trigger = fsl_easrc_trigger, .hw_params = fsl_easrc_hw_params, .hw_free = fsl_easrc_hw_free, }; -static struct snd_soc_dai_driver fsl_easrc_dai = { - .playback = { - .stream_name = "ASRC-Playback", - .channels_min = 1, - .channels_max = 32, - .rate_min = 8000, - .rate_max = 768000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_EASRC_FORMATS, +static struct snd_soc_dai_driver fsl_easrc_dai[] = { + { + .name = "easrc-0", + .playback = { + .stream_name = "ASRC-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, }, - .capture = { - .stream_name = "ASRC-Capture", - .channels_min = 1, - .channels_max = 32, - .rate_min = 8000, - .rate_max = 768000, - .rates = SNDRV_PCM_RATE_KNOT, - .formats = FSL_EASRC_FORMATS | - SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + { + .name = "easrc-1", + .playback = { + .stream_name = "ASRC1-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC1-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, + }, + { + .name = "easrc-2", + .playback = { + .stream_name = "ASRC2-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC2-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, + }, + { + .name = "easrc-3", + .playback = { + .stream_name = "ASRC3-Playback", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS, + }, + .capture = { + .stream_name = "ASRC3-Capture", + .channels_min = 1, + .channels_max = 32, + .rate_min = 8000, + .rate_max = 768000, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, + }, + .ops = &fsl_easrc_dai_ops, }, - .ops = &fsl_easrc_dai_ops, }; static const struct snd_soc_component_driver fsl_easrc_component = { @@ -2245,7 +2306,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) regcache_cache_only(easrc->regmap, true); ret = devm_snd_soc_register_component(dev, &fsl_easrc_component, - &fsl_easrc_dai, 1); + fsl_easrc_dai, ARRAY_SIZE(fsl_easrc_dai)); if (ret) { dev_err(dev, "failed to register ASoC DAI\n"); goto err_pm_disable; diff --git a/sound/soc/fsl/fsl_mqs.c b/sound/soc/fsl/fsl_mqs.c index 901f840df904..5b00488253bb 100644 --- a/sound/soc/fsl/fsl_mqs.c +++ b/sound/soc/fsl/fsl_mqs.c @@ -183,7 +183,21 @@ static void fsl_mqs_shutdown(struct snd_pcm_substream *substream, mqs_priv->soc->en_mask, 0); } +static const struct snd_soc_dapm_widget fsl_mqs_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("MQS_L"), + SND_SOC_DAPM_OUTPUT("MQS_R"), +}; + +static const struct snd_soc_dapm_route fsl_mqs_dapm_routes[] = { + { "MQS_L", NULL, "Playback" }, + { "MQS_R", NULL, "Playback" }, +}; + static const struct snd_soc_component_driver soc_codec_fsl_mqs = { + .dapm_widgets = fsl_mqs_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(fsl_mqs_dapm_widgets), + .dapm_routes = fsl_mqs_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(fsl_mqs_dapm_routes), .idle_bias_on = 1, }; diff --git a/sound/soc/fsl/fsl_xcvr.c b/sound/soc/fsl/fsl_xcvr.c index 982827204351..29929dfcce2a 100644 --- a/sound/soc/fsl/fsl_xcvr.c +++ b/sound/soc/fsl/fsl_xcvr.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -57,6 +58,7 @@ struct fsl_xcvr { struct snd_aes_iec958 tx_iec958; u8 cap_ds[FSL_XCVR_CAPDS_SIZE]; struct work_struct work_rst; + int irq; spinlock_t lock; /* Protect hw_reset and trigger */ struct snd_pcm_hw_constraint_list spdif_constr_rates; u32 spdif_constr_rates_list[SPDIF_NUM_RATES]; @@ -1617,7 +1619,7 @@ static int fsl_xcvr_probe(struct platform_device *pdev) struct fsl_xcvr *xcvr; struct resource *rx_res, *tx_res; void __iomem *regs; - int ret, irq; + int ret; xcvr = devm_kzalloc(dev, sizeof(*xcvr), GFP_KERNEL); if (!xcvr) @@ -1705,12 +1707,22 @@ static int fsl_xcvr_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(xcvr->reset), "failed to get XCVR reset control\n"); - /* get IRQs */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + /* + * irq0_isr() schedules work_rst. Prepare the work and its lock + * before the IRQ, and register the cancel action first so a failed + * probe frees the IRQ and then cancels any queued work. + */ + spin_lock_init(&xcvr->lock); + ret = devm_work_autocancel(dev, &xcvr->work_rst, reset_rx_work); + if (ret) + return ret; - ret = devm_request_irq(dev, irq, irq0_isr, 0, pdev->name, xcvr); + /* get IRQs */ + xcvr->irq = platform_get_irq(pdev, 0); + if (xcvr->irq < 0) + return xcvr->irq; + + ret = devm_request_irq(dev, xcvr->irq, irq0_isr, 0, pdev->name, xcvr); if (ret) return dev_err_probe(dev, ret, "failed to claim IRQ0\n"); @@ -1751,8 +1763,6 @@ static int fsl_xcvr_probe(struct platform_device *pdev) fsl_xcvr_comp.name); } - INIT_WORK(&xcvr->work_rst, reset_rx_work); - spin_lock_init(&xcvr->lock); return ret; } @@ -1760,6 +1770,8 @@ static void fsl_xcvr_remove(struct platform_device *pdev) { struct fsl_xcvr *xcvr = dev_get_drvdata(&pdev->dev); + /* Free the IRQ first so irq0_isr() cannot requeue work_rst. */ + devm_free_irq(&pdev->dev, xcvr->irq, xcvr); cancel_work_sync(&xcvr->work_rst); pm_runtime_disable(&pdev->dev); } diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c index 7086fb7fc1eb..85b7c929a28a 100644 --- a/sound/soc/fsl/imx-pcm-rpmsg.c +++ b/sound/soc/fsl/imx-pcm-rpmsg.c @@ -269,7 +269,13 @@ static int imx_rpmsg_pcm_open(struct snd_soc_component *component, info->stream_timer[substream->stream].substream = substream; timer_setup(&info->stream_timer[substream->stream].timer, imx_rpmsg_timer_callback, 0); - return ret; + + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .close() is not skipped on rollback. + */ + return 0; } static int imx_rpmsg_pcm_close(struct snd_soc_component *component, diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index 0a8a6d891d1f..127c2e6b308e 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -629,6 +629,7 @@ static int graph_probe(struct platform_device *pdev) card->dapm_widgets = graph_dapm_widgets; card->num_dapm_widgets = ARRAY_SIZE(graph_dapm_widgets); card->probe = graph_util_card_probe; + card->remove = graph_util_card_remove; if (of_device_get_match_data(dev)) priv->dpcm_selectable = 1; diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index 9fb3d3df5cf6..503170578cff 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -969,7 +969,8 @@ int audio_graph2_link_c2c(struct simple_util_priv *priv, struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); struct device_node *port0 = lnk; struct device_node *ports __free(device_node) = port_to_ports(port0); - struct device_node *port1 __free(device_node) = of_graph_get_next_port(ports, port0); + struct device_node *port1 __free(device_node) = + of_graph_get_next_port(ports, of_node_get(port0)); u32 val = 0; int ret = -EINVAL; @@ -1310,6 +1311,7 @@ int audio_graph2_parse_of(struct simple_util_priv *priv, struct device *dev, goto end; card->probe = graph_util_card_probe; + card->remove = graph_util_card_remove; card->owner = THIS_MODULE; card->dev = dev; diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 5f3423129b13..de27163eeb0b 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -825,6 +825,16 @@ int simple_util_init_jack(struct snd_soc_card *card, } EXPORT_SYMBOL_GPL(simple_util_init_jack); +void simple_util_remove_jack(struct simple_util_jack *sjack) +{ + if (!sjack->gpio.desc) + return; + + snd_soc_jack_free_gpios(&sjack->jack, 1, &sjack->gpio); + sjack->gpio.desc = NULL; +} +EXPORT_SYMBOL_GPL(simple_util_remove_jack); + int simple_util_init_aux_jacks(struct snd_soc_card *card, char *prefix) { struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); @@ -1018,6 +1028,17 @@ int graph_util_card_probe(struct snd_soc_card *card) } EXPORT_SYMBOL_GPL(graph_util_card_probe); +int graph_util_card_remove(struct snd_soc_card *card) +{ + struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + + simple_util_remove_jack(&priv->hp_jack); + simple_util_remove_jack(&priv->mic_jack); + + return 0; +} +EXPORT_SYMBOL_GPL(graph_util_card_remove); + int graph_util_is_ports0(struct device_node *np) { struct device_node *parent __free(device_node) = of_get_parent(np); @@ -1116,7 +1137,7 @@ int graph_util_parse_dai(struct simple_util_priv *priv, struct device_node *ep, args.np = ep; dai = snd_soc_get_dai_via_args(&args); if (dai) { - const char *dai_name = snd_soc_dai_name_get(dai); + const char *dai_name = snd_soc_dai_name(dai); const struct of_phandle_args *dai_args = snd_soc_copy_dai_args(dev, &args); ret = -ENOMEM; diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index e7ad7af04714..b2fc91b8ab81 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -92,7 +92,7 @@ static int simple_parse_dai(struct simple_util_priv *priv, dai = snd_soc_get_dai_via_args(&args); if (dai) { ret = -ENOMEM; - dlc->dai_name = snd_soc_dai_name_get(dai); + dlc->dai_name = snd_soc_dai_name(dai); dlc->dai_args = snd_soc_copy_dai_args(dev, &args); if (!dlc->dai_args) goto end; @@ -666,6 +666,16 @@ static int simple_soc_probe(struct snd_soc_card *card) return simple_ret(priv, ret); } +static int simple_soc_remove(struct snd_soc_card *card) +{ + struct simple_util_priv *priv = snd_soc_card_get_drvdata(card); + + simple_util_remove_jack(&priv->hp_jack); + simple_util_remove_jack(&priv->mic_jack); + + return 0; +} + static int simple_parse_of(struct simple_util_priv *priv) { struct snd_soc_card *card = simple_priv_to_card(priv); @@ -754,6 +764,7 @@ static int simple_probe(struct platform_device *pdev) card->owner = THIS_MODULE; card->dev = dev; card->probe = simple_soc_probe; + card->remove = simple_soc_remove; card->driver_name = "simple-card"; return simple_parse_of(priv); diff --git a/sound/soc/hisilicon/hi6210-i2s.c b/sound/soc/hisilicon/hi6210-i2s.c index 33b1dafb2540..554356f060b0 100644 --- a/sound/soc/hisilicon/hi6210-i2s.c +++ b/sound/soc/hisilicon/hi6210-i2s.c @@ -507,6 +507,11 @@ static int hi6210_i2s_dai_probe(struct snd_soc_dai *dai) } +static const u64 hi6210_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops hi6210_i2s_dai_ops = { .probe = hi6210_i2s_dai_probe, .trigger = hi6210_i2s_trigger, @@ -514,6 +519,8 @@ static const struct snd_soc_dai_ops hi6210_i2s_dai_ops = { .set_fmt = hi6210_i2s_set_fmt, .startup = hi6210_i2s_startup, .shutdown = hi6210_i2s_shutdown, + .auto_selectable_formats = &hi6210_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver hi6210_i2s_dai_init = { diff --git a/sound/soc/img/img-i2s-in.c b/sound/soc/img/img-i2s-in.c index 98d2ab68883a..a0c87e2a9b30 100644 --- a/sound/soc/img/img-i2s-in.c +++ b/sound/soc/img/img-i2s-in.c @@ -379,11 +379,21 @@ static int img_i2s_in_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 img_i2s_in_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops img_i2s_in_dai_ops = { .probe = img_i2s_in_dai_probe, .trigger = img_i2s_in_trigger, .hw_params = img_i2s_in_hw_params, - .set_fmt = img_i2s_in_set_fmt + .set_fmt = img_i2s_in_set_fmt, + .auto_selectable_formats = &img_i2s_in_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_component_driver img_i2s_in_component = { diff --git a/sound/soc/img/img-i2s-out.c b/sound/soc/img/img-i2s-out.c index aedcf56a5359..caa7870dbec6 100644 --- a/sound/soc/img/img-i2s-out.c +++ b/sound/soc/img/img-i2s-out.c @@ -385,11 +385,22 @@ static int img_i2s_out_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 img_i2s_out_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops img_i2s_out_dai_ops = { .probe = img_i2s_out_dai_probe, .trigger = img_i2s_out_trigger, .hw_params = img_i2s_out_hw_params, - .set_fmt = img_i2s_out_set_fmt + .set_fmt = img_i2s_out_set_fmt, + .auto_selectable_formats = &img_i2s_out_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_component_driver img_i2s_out_component = { diff --git a/sound/soc/img/img-parallel-out.c b/sound/soc/img/img-parallel-out.c index 86b60e6dab38..89270913ab35 100644 --- a/sound/soc/img/img-parallel-out.c +++ b/sound/soc/img/img-parallel-out.c @@ -183,11 +183,17 @@ static int img_prl_out_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 img_prl_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops img_prl_out_dai_ops = { .probe = img_prl_out_dai_probe, .trigger = img_prl_out_trigger, .hw_params = img_prl_out_hw_params, - .set_fmt = img_prl_out_set_fmt + .set_fmt = img_prl_out_set_fmt, + .auto_selectable_formats = &img_prl_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver img_prl_out_dai = { diff --git a/sound/soc/intel/atom/sst-mfld-dsp.h b/sound/soc/intel/atom/sst-mfld-dsp.h index c8f0816edb53..59606259a06e 100644 --- a/sound/soc/intel/atom/sst-mfld-dsp.h +++ b/sound/soc/intel/atom/sst-mfld-dsp.h @@ -189,7 +189,7 @@ struct ipc_dsp_hdr { union ipc_header_high { struct { u32 msg_id:8; /* Message ID - Max 256 Message Types */ - u32 task_id:4; /* Task ID associated with this comand */ + u32 task_id:4; /* Task ID associated with this command */ u32 drv_id:4; /* Identifier for the driver to track*/ u32 rsvd1:8; /* Reserved */ u32 result:4; /* Reserved */ diff --git a/sound/soc/intel/atom/sst-mfld-platform-pcm.c b/sound/soc/intel/atom/sst-mfld-platform-pcm.c index a4c8cbfba096..58681e007abf 100644 --- a/sound/soc/intel/atom/sst-mfld-platform-pcm.c +++ b/sound/soc/intel/atom/sst-mfld-platform-pcm.c @@ -341,7 +341,12 @@ static int sst_media_open(struct snd_pcm_substream *substream, stream = NULL; - return ret_val; + /* + * ASoC only marks the DAI as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure .shutdown() is not skipped on rollback. + */ + return 0; } static void sst_media_close(struct snd_pcm_substream *substream, diff --git a/sound/soc/intel/atom/sst/sst.h b/sound/soc/intel/atom/sst/sst.h index c43946c5ecee..e24d9dad7ef6 100644 --- a/sound/soc/intel/atom/sst/sst.h +++ b/sound/soc/intel/atom/sst/sst.h @@ -182,7 +182,7 @@ struct sst_block { * @pcm_substream : PCM substream * @period_elapsed : PCM period elapsed callback * @sfreq : stream sampling freq - * @cumm_bytes : cummulative bytes decoded + * @cumm_bytes : cumulative bytes decoded */ struct stream_info { unsigned int status; diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c index 73624e1b138a..de1ad168008b 100644 --- a/sound/soc/intel/atom/sst/sst_acpi.c +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -254,6 +254,11 @@ static int sst_platform_get_resources(struct intel_sst_drv *ctx) return 0; } +static void sst_unregister_platform_device(void *data) +{ + platform_device_unregister(data); +} + static int sst_acpi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -342,7 +347,10 @@ static int sst_acpi_probe(struct platform_device *pdev) pdata->platform); return PTR_ERR(plat_dev); } - + ret = devm_add_action_or_reset(dev, sst_unregister_platform_device, + plat_dev); + if (ret) + return ret; /* * Create platform device for sst machine driver, * pass machine info as pdata @@ -355,6 +363,10 @@ static int sst_acpi_probe(struct platform_device *pdev) return PTR_ERR(mdev); } + ret = devm_add_action_or_reset(dev, sst_unregister_platform_device, + mdev); + if (ret) + return ret; /* Fill sst platform data */ ctx->pdata = pdata; strscpy(ctx->firmware_name, mach->fw_filename); diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c index 2e15a293298d..c1d2d0082b14 100644 --- a/sound/soc/intel/avs/boards/hdaudio.c +++ b/sound/soc/intel/avs/boards/hdaudio.c @@ -147,17 +147,15 @@ static int avs_probing_link_init(struct snd_soc_pcm_runtime *rtm) struct snd_soc_dai_link *links = NULL; struct snd_soc_card *card = rtm->card; struct hda_codec *codec; - struct hda_pcm *pcm; - int ret, pcm_count = 0; + int ret, pcm_count; mach = dev_get_platdata(card->dev); pdata = mach->pdata; codec = pdata->codec; + pcm_count = list_count_nodes(&codec->pcm_list_head); - if (list_empty(&codec->pcm_list_head)) + if (!pcm_count) return -EINVAL; - list_for_each_entry(pcm, &codec->pcm_list_head, list) - pcm_count++; ret = avs_create_dai_links(card->dev, codec, pcm_count, &links); if (ret < 0) { diff --git a/sound/soc/intel/avs/pcm.c b/sound/soc/intel/avs/pcm.c index 880cd7a9fb34..f17bbc2ba63d 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -461,6 +461,8 @@ static int avs_dai_hda_be_prepare(struct snd_pcm_substream *substream, struct sn link_stream = data->link_stream; p = &be->dpcm[substream->stream].hw_params; + if (substream->runtime->state != SNDRV_PCM_STATE_PREPARED) + link_stream->link_prepared = false; if (link_stream->link_prepared) return 0; @@ -1065,7 +1067,7 @@ static int avs_component_probe(struct snd_soc_component *component) return 0; err_load_libs: - avs_remove_topology(component); + snd_soc_tplg_component_remove(component); return ret; } @@ -1083,7 +1085,7 @@ static void avs_component_remove(struct snd_soc_component *component) mutex_unlock(&adev->comp_list_mutex); if (mach->tplg_filename) { - ret = avs_remove_topology(component); + ret = snd_soc_tplg_component_remove(component); if (ret < 0) dev_err(component->dev, "unload topology failed: %d\n", ret); } @@ -1399,10 +1401,6 @@ int avs_register_component(struct device *dev, const char *name, if (!acomp) return -ENOMEM; - acomp->base = snd_soc_component_alloc(dev); - if (!acomp->base) - return -ENOMEM; - comp_name = devm_kstrdup(dev, name, GFP_KERNEL); if (!comp_name) return -ENOMEM; @@ -1411,10 +1409,13 @@ int avs_register_component(struct device *dev, const char *name, drv->use_dai_pcm_id = !obsolete_card_names; - snd_soc_component_set_name(acomp->base, comp_name); + acomp->base = snd_soc_component_new(dev, comp_name, drv); + if (IS_ERR(acomp->base)) + return PTR_ERR(acomp->base); + snd_soc_component_set_priv(acomp->base, acomp); - return snd_soc_register_component(acomp->base, drv, cpu_dais, num_cpu_dais); + return snd_soc_component_register(acomp->base, cpu_dais, num_cpu_dais); } static struct snd_soc_dai_driver dmic_cpu_dais[] = { @@ -1614,7 +1615,7 @@ static int avs_component_hda_probe(struct snd_soc_component *component) struct hda_codec *codec; struct hda_pcm *pcm; const char *cname; - int pcm_count = 0, ret, i; + int pcm_count, ret, i; mach = dev_get_platdata(component->card->dev); if (!mach) @@ -1622,10 +1623,9 @@ static int avs_component_hda_probe(struct snd_soc_component *component) pdata = mach->pdata; codec = pdata->codec; - if (list_empty(&codec->pcm_list_head)) + pcm_count = list_count_nodes(&codec->pcm_list_head); + if (!pcm_count) return -EINVAL; - list_for_each_entry(pcm, &codec->pcm_list_head, list) - pcm_count++; dais = devm_kcalloc(component->dev, pcm_count, sizeof(*dais), GFP_KERNEL); diff --git a/sound/soc/intel/avs/probes.c b/sound/soc/intel/avs/probes.c index 2e4063b743ba..483e151dfd66 100644 --- a/sound/soc/intel/avs/probes.c +++ b/sound/soc/intel/avs/probes.c @@ -298,17 +298,13 @@ int avs_register_probe_component(struct avs_dev *adev, const char *name) struct snd_soc_component *component; const char *comp_name; - component = snd_soc_component_alloc(adev->dev); - if (!component) - return -ENOMEM; - comp_name = devm_kstrdup(adev->dev, name, GFP_KERNEL); if (!comp_name) return -ENOMEM; - snd_soc_component_set_name(component, comp_name); + component = snd_soc_component_new(adev->dev, comp_name, &avs_probe_component_driver); + if (IS_ERR(component)) + return PTR_ERR(component); - return snd_soc_register_component(component, - &avs_probe_component_driver, - probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); + return snd_soc_component_register(component, probe_cpu_dais, ARRAY_SIZE(probe_cpu_dais)); } diff --git a/sound/soc/intel/avs/topology.c b/sound/soc/intel/avs/topology.c index 5d70be63a4a7..e81401bb6dbd 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -1720,6 +1720,10 @@ static int avs_tplg_parse_initial_configs(struct snd_soc_component *comp, AVS_TKN_INIT_CONFIG_ID_U32, mod_init_config_parsers, ARRAY_SIZE(mod_init_config_parsers)); + if (ret) + return ret; + if (config->length > block_size) + return -EINVAL; block_size -= esize; @@ -1790,6 +1794,8 @@ static int avs_tplg_parse_nhlt_configs(struct snd_soc_component *comp, /* With tuples parsed, the blob shall be allocated. */ if (!config->blob) return -EINVAL; + if (config->blob->capabilities_size > block_size) + return -EINVAL; /* Consume the raw data and move to the next entry. */ memcpy(config->blob->capabilities, (u8 *)tuples + esize, @@ -2226,28 +2232,3 @@ struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp) return tplg; } - -int avs_load_topology(struct snd_soc_component *comp, const char *filename) -{ - const struct firmware *fw __free(firmware) = NULL; - int ret; - - ret = request_firmware(&fw, filename, comp->dev); - if (ret < 0) { - dev_err(comp->dev, "request topology \"%s\" failed: %d\n", filename, ret); - return ret; - } - - ret = snd_soc_tplg_component_load(comp, &avs_tplg_ops, fw); - if (ret < 0) - dev_err(comp->dev, "load topology \"%s\" failed: %d\n", filename, ret); - - return ret; -} - -int avs_remove_topology(struct snd_soc_component *comp) -{ - snd_soc_tplg_component_remove(comp); - - return 0; -} diff --git a/sound/soc/intel/avs/topology.h b/sound/soc/intel/avs/topology.h index 189984ce7b51..25f3888a1939 100644 --- a/sound/soc/intel/avs/topology.h +++ b/sound/soc/intel/avs/topology.h @@ -233,7 +233,4 @@ struct avs_tplg_module { extern const struct snd_soc_tplg_ops avs_tplg_ops; struct avs_tplg *avs_tplg_new(struct snd_soc_component *comp); -int avs_load_topology(struct snd_soc_component *comp, const char *filename); -int avs_remove_topology(struct snd_soc_component *comp); - #endif diff --git a/sound/soc/intel/avs/utils.c b/sound/soc/intel/avs/utils.c index ea14ec173855..f19a62e7e8b5 100644 --- a/sound/soc/intel/avs/utils.c +++ b/sound/soc/intel/avs/utils.c @@ -116,9 +116,11 @@ avs_module_ida_alloc(struct avs_dev *adev, struct avs_mods_info *newinfo, bool p int i; if (!purge && oldinfo) { - if (oldinfo->count >= newinfo->count) - dev_warn(adev->dev, "refreshing %d modules info with %d\n", + if (oldinfo->count >= newinfo->count) { + dev_warn(adev->dev, "modules info underflow, old: %d, new: %d\n", oldinfo->count, newinfo->count); + return -EPROTO; + } tocopy_count = oldinfo->count; } diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index b795bcade8e9..553ad123d6e6 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -161,6 +161,20 @@ config SND_SOC_INTEL_CHT_BSW_RT5672_MACH Say Y or m if you have such a device. This is a recommended option. If unsure select "N". +config SND_SOC_INTEL_CHT_RT5677_MACH + tristate "Cherrytrail with RT5677 codec" + depends on I2C && ACPI + depends on X86_INTEL_LPSS || COMPILE_TEST + depends on GPIOLIB || COMPILE_TEST + select SND_SOC_ACPI + select SND_SOC_RT5677 + select SND_SOC_TS3A227E + help + This adds support for Cherry Trail platforms with an RT5677 codec + and TS3A227E headset-jack detector. It currently supports the + Lenovo Yoga Book YB1-X91F/L. + Say Y or m if you have such a device. If unsure select "N". + config SND_SOC_INTEL_CHT_BSW_RT5645_MACH tristate "Cherrytrail & Braswell with RT5645/5650 codec" depends on I2C && ACPI @@ -536,6 +550,7 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH imply SND_SOC_TAC5XX2_SDW select SND_SOC_DMIC select SND_SOC_INTEL_HDA_DSP_COMMON + select SND_SOC_SN624X_SDCA_SDW imply SND_SOC_SDW_MOCKUP help Add support for Intel SoundWire-based platforms connected to diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index 25a1a9066cbf..28940ca02aa7 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -9,6 +9,7 @@ snd-soc-sst-bytcr-rt5640-y := bytcr_rt5640.o snd-soc-sst-bytcr-rt5651-y := bytcr_rt5651.o snd-soc-sst-bytcr-wm5102-y := bytcr_wm5102.o snd-soc-sst-cht-bsw-rt5672-y := cht_bsw_rt5672.o +snd-soc-sst-cht-rt5677-y := cht_rt5677.o snd-soc-sst-cht-bsw-rt5645-y := cht_bsw_rt5645.o snd-soc-sst-cht-bsw-max98090_ti-y := cht_bsw_max98090_ti.o snd-soc-sst-cht-bsw-nau8824-y := cht_bsw_nau8824.o @@ -41,6 +42,7 @@ obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5651_MACH) += snd-soc-sst-bytcr-rt5651.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_WM5102_MACH) += snd-soc-sst-bytcr-wm5102.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5672_MACH) += snd-soc-sst-cht-bsw-rt5672.o +obj-$(CONFIG_SND_SOC_INTEL_CHT_RT5677_MACH) += snd-soc-sst-cht-rt5677.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_RT5645_MACH) += snd-soc-sst-cht-bsw-rt5645.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH) += snd-soc-sst-cht-bsw-max98090_ti.o obj-$(CONFIG_SND_SOC_INTEL_CHT_BSW_NAU8824_MACH) += snd-soc-sst-cht-bsw-nau8824.o diff --git a/sound/soc/intel/boards/cht_rt5677.c b/sound/soc/intel/boards/cht_rt5677.c new file mode 100644 index 000000000000..0f5e169fe96f --- /dev/null +++ b/sound/soc/intel/boards/cht_rt5677.c @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * cht_rt5677.c - ASoC machine driver for Cherry Trail with RT5677 + * + * Copyright (C) 2026 Yauhen Kharuzhy + * + * Based on the mainline cht_bsw_rt5672.c driver and Lenovo's + * cht_bl_dpcm_rt5677.c Android driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../codecs/rt5677.h" +#include "../../codecs/ts3a227e.h" +#include "../atom/sst-atom-controls.h" + +#define RT5677_I2C "i2c-rt5677" +/* x86-android-tablets creates this client with dev_name "ts3a227e". */ +#define TS3A227E_I2C "i2c-ts3a227e" + +/* Platform clock 3 provides the codec's 19.2 MHz I2S master clock. */ +#define CHT_PLAT_CLK_3_HZ 19200000 +#define CHT_CODEC_DAI "rt5677-aif1" + +struct cht_rt5677_private { + char codec_name[SND_ACPI_I2C_ID_LEN]; + struct clk *mclk; + struct gpio_desc *gpio_spk_en1; + struct gpio_desc *gpio_spk_en2; + struct gpio_desc *gpio_hp_en; +}; + +static int cht_rt5677_platform_clock_enable(struct snd_soc_card *card, + struct snd_soc_dai *codec_dai) +{ + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + ret = clk_prepare_enable(ctx->mclk); + if (ret) { + dev_err(card->dev, "enabling MCLK failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5677_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, 48000 * 512); + if (ret) { + dev_err(card->dev, "setting codec PLL failed: %d\n", ret); + goto disable_mclk; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_PLL1, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "setting codec sysclk failed: %d\n", ret); + goto disable_mclk; + } + + return 0; + +disable_mclk: + clk_disable_unprepare(ctx->mclk); + return ret; +} + +static int cht_rt5677_platform_clock_disable(struct snd_soc_card *card, + struct snd_soc_dai *codec_dai) +{ + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_RCCLK, + 48000 * 512, SND_SOC_CLOCK_IN); + if (ret) + dev_warn(card->dev, "setting codec idle sysclk failed: %d\n", ret); + + clk_disable_unprepare(ctx->mclk); + + return ret; +} + +static int cht_rt5677_platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctl, + int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct snd_soc_dai *codec_dai; + + codec_dai = snd_soc_card_get_codec_dai(card, CHT_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "codec DAI not found\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) + return cht_rt5677_platform_clock_enable(card, codec_dai); + + return cht_rt5677_platform_clock_disable(card, codec_dai); +} + +static int cht_rt5677_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + + return gpiod_set_value_cansleep(ctx->gpio_hp_en, + SND_SOC_DAPM_EVENT_ON(event)); +} + +static int cht_rt5677_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(card); + bool enable = SND_SOC_DAPM_EVENT_ON(event); + int rollback_ret; + int ret; + int i; + + /* Program the external boost amplifier for mode 3 before enabling it. */ + if (enable) { + for (i = 0; i < 4; i++) { + ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, + !(i & 1)); + if (ret) + goto disable_boost; + + udelay(2); + } + } + + ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, enable); + if (ret) + return ret; + + ret = gpiod_set_value_cansleep(ctx->gpio_spk_en2, enable); + if (ret) { + rollback_ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, + !enable); + if (rollback_ret) + dev_warn(card->dev, "rolling back speaker GPIO failed: %d\n", + rollback_ret); + return ret; + } + + if (enable) + msleep(50); + + return 0; + +disable_boost: + rollback_ret = gpiod_set_value_cansleep(ctx->gpio_spk_en1, 0); + if (rollback_ret) + dev_warn(card->dev, "disabling speaker boost GPIO failed: %d\n", + rollback_ret); + + return ret; +} + +static const struct snd_soc_dapm_widget cht_rt5677_widgets[] = { + SND_SOC_DAPM_HP("Headphone", cht_rt5677_hp_event), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", cht_rt5677_spk_event), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + cht_rt5677_platform_clock_control, + SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route cht_rt5677_map[] = { + {"IN1P", NULL, "Headset Mic"}, + {"IN1N", NULL, "Headset Mic"}, + {"DMIC L1", NULL, "Int Mic"}, + {"DMIC R1", NULL, "Int Mic"}, + {"Headphone", NULL, "LOUT1"}, + {"Headphone", NULL, "LOUT2"}, + {"Speaker", NULL, "LOUT1"}, + {"Speaker", NULL, "LOUT2"}, + + {"AIF1 Playback", NULL, "ssp2 Tx"}, + {"ssp2 Tx", NULL, "codec_out0"}, + {"ssp2 Tx", NULL, "codec_out1"}, + {"codec_in0", NULL, "ssp2 Rx"}, + {"codec_in1", NULL, "ssp2 Rx"}, + {"ssp2 Rx", NULL, "AIF1 Capture"}, + {"Headphone", NULL, "Platform Clock"}, + {"Speaker", NULL, "Platform Clock"}, + {"Headset Mic", NULL, "Platform Clock"}, + {"Int Mic", NULL, "Platform Clock"}, +}; + +static const struct snd_kcontrol_new cht_rt5677_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int cht_rt5677_aif1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT5677_PLL1_S_MCLK, + CHT_PLAT_CLK_3_HZ, params_rate(params) * 512); + if (ret) { + dev_err(rtd->dev, "setting codec PLL failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, RT5677_SCLK_S_PLL1, + params_rate(params) * 512, + SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->dev, "setting codec sysclk failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBC_CFC); + if (ret) { + dev_err(codec_dai->dev, "setting TDM format failed: %d\n", ret); + return ret; + } + + /* Four 25-bit DSP_B slots carry 24-bit samples; the codec uses slots 0 and 1. */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x3, 0x3, 4, 25); + if (ret) { + dev_err(rtd->dev, "setting codec TDM slots failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_rt5677_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct cht_rt5677_private *ctx = snd_soc_card_get_drvdata(runtime->card); + int ret; + + /* + * The codec derives its asynchronous sample-rate conversion clocks from + * I2S1 while the SSP link runs from the Cherry Trail platform clock. + */ + ret = rt5677_sel_asrc_clk_src(component, RT5677_DA_STEREO_FILTER | + RT5677_AD_STEREO1_FILTER | + RT5677_I2S1_SOURCE, + RT5677_CLK_SEL_I2S1_ASRC); + if (ret) { + dev_err(runtime->dev, "setting stereo ASRC clock failed: %d\n", ret); + return ret; + } + + /* Mono ADC L uses the codec system clock rather than the I2S1 clock. */ + ret = rt5677_sel_asrc_clk_src(component, RT5677_AD_MONO_L_FILTER, RT5677_CLK_SEL_SYS2); + if (ret) { + dev_err(runtime->dev, "setting mono ASRC clock failed: %d\n", ret); + return ret; + } + + /* Firmware may leave MCLK enabled without updating the CCF count. */ + ret = clk_prepare_enable(ctx->mclk); + if (ret) { + dev_err(runtime->dev, "preparing MCLK failed: %d\n", ret); + return ret; + } + clk_disable_unprepare(ctx->mclk); + + ret = clk_set_rate(ctx->mclk, CHT_PLAT_CLK_3_HZ); + if (ret) { + dev_err(runtime->dev, "setting MCLK rate failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int cht_rt5677_codec_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + /* The DSP will convert the FE rate to 48k, stereo, 24bits */ + rate->min = 48000; + rate->max = 48000; + channels->min = 2; + channels->max = 2; + + /* + * Configure SSP2 for the 24-bit format expected by the codec. The + * ssp2-port front end still advertises S16_LE and converts the stream. + */ + snd_mask_none(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT)); + params_set_format(params, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static struct snd_soc_jack_pin cht_rt5677_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int cht_rt5677_headset_init(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + struct snd_soc_jack *jack; + int jack_type; + int ret; + + jack = devm_kzalloc(card->dev, sizeof(*jack), GFP_KERNEL); + if (!jack) + return -ENOMEM; + + /* + * TI supports four headset buttons: + * KEY_MEDIA + * KEY_VOICECOMMAND + * KEY_VOLUMEUP + * KEY_VOLUMEDOWN + */ + jack_type = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3; + + ret = snd_soc_card_jack_new_pins(card, "Headset Jack", jack_type, + jack, cht_rt5677_jack_pins, + ARRAY_SIZE(cht_rt5677_jack_pins)); + if (ret) { + dev_err(card->dev, "creating headset jack failed: %d\n", ret); + return ret; + } + + return ts3a227e_enable_jack_detect(component, jack); +} + +static int cht_rt5677_aif1_startup(struct snd_pcm_substream *substream) +{ + return snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, 48000); +} + +static const struct snd_soc_ops cht_rt5677_aif1_ops = { + .startup = cht_rt5677_aif1_startup, +}; + +static const struct snd_soc_ops cht_rt5677_be_ssp2_ops = { + .hw_params = cht_rt5677_aif1_hw_params, +}; + +static const struct snd_soc_aux_dev cht_rt5677_headset_dev = { + .dlc = COMP_AUX(TS3A227E_I2C), + .init = cht_rt5677_headset_init, +}; + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); +SND_SOC_DAILINK_DEF(media, DAILINK_COMP_ARRAY(COMP_CPU("media-cpu-dai"))); +SND_SOC_DAILINK_DEF(deepbuffer, DAILINK_COMP_ARRAY(COMP_CPU("deepbuffer-cpu-dai"))); +SND_SOC_DAILINK_DEF(ssp2_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp2-port"))); +SND_SOC_DAILINK_DEF(ssp2_codec, DAILINK_COMP_ARRAY(COMP_CODEC(RT5677_I2C, CHT_CODEC_DAI))); +SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("sst-mfld-platform"))); + +static const struct snd_soc_dai_link cht_rt5677_dailink[] = { + /* Front End DAI links */ + [MERR_DPCM_AUDIO] = { + .name = "Audio Port", + .stream_name = "Audio", + .nonatomic = true, + .dynamic = 1, + .ops = &cht_rt5677_aif1_ops, + SND_SOC_DAILINK_REG(media, dummy, platform), + }, + [MERR_DPCM_DEEP_BUFFER] = { + .name = "Deep-Buffer Audio Port", + .stream_name = "Deep-Buffer Audio", + .nonatomic = true, + .dynamic = 1, + .playback_only = 1, + .ops = &cht_rt5677_aif1_ops, + SND_SOC_DAILINK_REG(deepbuffer, dummy, platform), + }, + + /* Back End DAI links */ + { + /* SSP2 - Codec */ + .name = "SSP2-Codec", + .id = 0, + .no_pcm = 1, + .nonatomic = true, + .init = cht_rt5677_codec_init, + .be_hw_params_fixup = cht_rt5677_codec_fixup, + .ops = &cht_rt5677_be_ssp2_ops, + SND_SOC_DAILINK_REG(ssp2_port, ssp2_codec, platform), + }, +}; + +static const struct snd_soc_card cht_rt5677_card = { + .owner = THIS_MODULE, + .num_links = ARRAY_SIZE(cht_rt5677_dailink), + .num_aux_devs = 1, + .dapm_widgets = cht_rt5677_widgets, + .num_dapm_widgets = ARRAY_SIZE(cht_rt5677_widgets), + .dapm_routes = cht_rt5677_map, + .num_dapm_routes = ARRAY_SIZE(cht_rt5677_map), + .controls = cht_rt5677_controls, + .num_controls = ARRAY_SIZE(cht_rt5677_controls), +}; + +static const struct acpi_gpio_params speaker_enable_gpio = { 2, 0, false }; +static const struct acpi_gpio_mapping cht_rt5677_acpi_gpios[] = { + { "speaker-enable-gpios", &speaker_enable_gpio, 1 }, + { } +}; + +#define SOF_CARD_NAME "cht rt5677" +#define SOF_DRIVER_NAME "SOF" + +#define CARD_NAME "cht-rt5677" +#define DRIVER_NAME NULL + +static void cht_rt5677_gpiod_put(void *data) +{ + gpiod_put(data); +} + +static void cht_rt5677_remove_driver_gpios(void *data) +{ + acpi_dev_remove_driver_gpios(data); +} + +static int cht_rt5677_get_gpio(struct device *dev, struct device *codec_dev, + const char *con_id, struct gpio_desc **gpio) +{ + int ret; + + *gpio = gpiod_get(codec_dev, con_id, GPIOD_OUT_LOW); + if (IS_ERR(*gpio)) { + ret = PTR_ERR(*gpio); + return dev_err_probe(dev, ret, "getting %s GPIO failed\n", con_id); + } + + ret = devm_add_action_or_reset(dev, cht_rt5677_gpiod_put, *gpio); + if (ret) + return dev_err_probe(dev, ret, "registering %s GPIO cleanup failed\n", + con_id); + + return 0; +} + +static int cht_rt5677_clone_dai_links(struct device *dev, struct snd_soc_card *card) +{ + struct snd_soc_dai_link_component *components; + struct snd_soc_dai_link *links; + size_t size; + int i; + + links = devm_kmemdup(dev, cht_rt5677_dailink, + sizeof(cht_rt5677_dailink), GFP_KERNEL); + if (!links) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(cht_rt5677_dailink); i++) { + if (links[i].num_cpus) { + size = sizeof(*components) * links[i].num_cpus; + components = devm_kmemdup(dev, links[i].cpus, size, GFP_KERNEL); + if (!components) + return -ENOMEM; + links[i].cpus = components; + } + + if (links[i].num_codecs) { + size = sizeof(*components) * links[i].num_codecs; + components = devm_kmemdup(dev, links[i].codecs, size, GFP_KERNEL); + if (!components) + return -ENOMEM; + links[i].codecs = components; + } + + if (links[i].num_platforms) { + size = sizeof(*components) * links[i].num_platforms; + components = devm_kmemdup(dev, links[i].platforms, size, GFP_KERNEL); + if (!components) + return -ENOMEM; + links[i].platforms = components; + } + } + + card->dai_link = links; + return 0; +} + +static int snd_cht_rt5677_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cht_rt5677_private *ctx; + struct snd_soc_acpi_mach *mach = dev_get_platdata(dev); + struct snd_soc_aux_dev *aux_dev; + struct snd_soc_dai_link *dai_links; + struct snd_soc_card *card; + struct acpi_device *adev; + struct device *codec_dev; + bool sof_parent; + int ret; + int i; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + card = devm_kmemdup(dev, &cht_rt5677_card, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + ret = cht_rt5677_clone_dai_links(dev, card); + if (ret) + return ret; + dai_links = card->dai_link; + + aux_dev = devm_kmemdup(dev, &cht_rt5677_headset_dev, + sizeof(cht_rt5677_headset_dev), GFP_KERNEL); + if (!aux_dev) + return -ENOMEM; + + card->aux_dev = aux_dev; + + strscpy(ctx->codec_name, RT5677_I2C, sizeof(ctx->codec_name)); + + /* Use the ACPI-enumerated codec name when firmware describes the codec. */ + adev = acpi_dev_get_first_match_dev(mach->id, NULL, -1); + if (adev) { + snprintf(ctx->codec_name, sizeof(ctx->codec_name), + "i2c-%s", acpi_dev_name(adev)); + + acpi_dev_put(adev); + for (i = 0; i < card->num_links; i++) { + if (dai_links[i].codecs->name && + !strcmp(dai_links[i].codecs->name, + RT5677_I2C)) { + dai_links[i].codecs->name = ctx->codec_name; + break; + } + } + } + + codec_dev = bus_find_device_by_name(&i2c_bus_type, NULL, + ctx->codec_name); + if (!codec_dev) + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for codec %s\n", ctx->codec_name); + + adev = ACPI_COMPANION(codec_dev); + if (adev) { + /* + * Firmware describes the external speaker enable in _CRS. The + * x86-android-tablets quirk supplies software-node properties for + * the RT5677 GPIO2 and GPIO4 speaker/headphone enables. + */ + ret = acpi_dev_add_driver_gpios(adev, cht_rt5677_acpi_gpios); + if (ret) { + dev_err_probe(dev, ret, "adding codec GPIO mappings failed\n"); + goto out_put_codec; + } + + ret = devm_add_action_or_reset(dev, cht_rt5677_remove_driver_gpios, adev); + if (ret) { + dev_err_probe(dev, ret, "registering GPIO mapping cleanup failed\n"); + goto out_put_codec; + } + } + + ret = cht_rt5677_get_gpio(dev, codec_dev, "speaker-enable", &ctx->gpio_spk_en1); + if (ret) + goto out_put_codec; + + ret = cht_rt5677_get_gpio(dev, codec_dev, "speaker-enable2", &ctx->gpio_spk_en2); + if (ret) + goto out_put_codec; + + ret = cht_rt5677_get_gpio(dev, codec_dev, "headphone-enable", &ctx->gpio_hp_en); + if (ret) + goto out_put_codec; + + put_device(codec_dev); + + card->dev = dev; + + ctx->mclk = devm_clk_get(dev, "pmc_plt_clk_3"); + if (IS_ERR(ctx->mclk)) + return dev_err_probe(dev, PTR_ERR(ctx->mclk), "getting MCLK failed\n"); + + snd_soc_card_set_drvdata(card, ctx); + + sof_parent = snd_soc_acpi_sof_parent(dev); + + if (sof_parent) { + card->name = SOF_CARD_NAME; + card->driver_name = SOF_DRIVER_NAME; + } else { + card->name = CARD_NAME; + card->driver_name = DRIVER_NAME; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret) + return dev_err_probe(dev, ret, "registering sound card failed\n"); + + return 0; + +out_put_codec: + put_device(codec_dev); + return ret; +} + +static struct platform_driver snd_cht_rt5677_driver = { + .driver = { + .name = "cht-rt5677", + .pm = &snd_soc_pm_ops, + }, + .probe = snd_cht_rt5677_probe, +}; + +module_platform_driver(snd_cht_rt5677_driver); + +MODULE_DESCRIPTION("Cherry Trail RT5677 machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cht-rt5677"); diff --git a/sound/soc/intel/boards/sof_cirrus_common.c b/sound/soc/intel/boards/sof_cirrus_common.c index a4ecb1f9a615..c7e77e20a096 100644 --- a/sound/soc/intel/boards/sof_cirrus_common.c +++ b/sound/soc/intel/boards/sof_cirrus_common.c @@ -151,7 +151,7 @@ static const char * const cs35l41_name_prefixes[] = { "WL", "WR", "TL", "TR" }; * UID 0x2 -> TL * UID 0x3 -> TR * Note: If there are less than 4 Amps, UIDs still map to WL/WR/TL/TR. Dynamic code will only create - * dai links for UIDs which exist, and ignore non-existant ones. Only 2 or 4 amps are expected. + * dai links for UIDs which exist, and ignore non-existent ones. Only 2 or 4 amps are expected. * Return number of codecs found. */ static int cs35l41_compute_codec_conf(void) diff --git a/sound/soc/intel/catpt/pcm.c b/sound/soc/intel/catpt/pcm.c index bbba0d959ea2..ca42bc531c6b 100644 --- a/sound/soc/intel/catpt/pcm.c +++ b/sound/soc/intel/catpt/pcm.c @@ -1073,13 +1073,9 @@ int catpt_register_plat_component(struct catpt_dev *cdev) { struct snd_soc_component *component; - component = snd_soc_component_alloc(cdev->dev); - if (!component) - return -ENOMEM; + component = snd_soc_component_new(cdev->dev, catpt_comp_driver.name, &catpt_comp_driver); + if (IS_ERR(component)) + return PTR_ERR(component); - snd_soc_component_set_name(component, catpt_comp_driver.name); - - return snd_soc_register_component(component, - &catpt_comp_driver, - dai_drivers, ARRAY_SIZE(dai_drivers)); + return snd_soc_component_register(component, dai_drivers, ARRAY_SIZE(dai_drivers)); } diff --git a/sound/soc/intel/common/soc-acpi-intel-arl-match.c b/sound/soc/intel/common/soc-acpi-intel-arl-match.c index 1033f2d45c8a..575889a4c69b 100644 --- a/sound/soc/intel/common/soc-acpi-intel-arl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-arl-match.c @@ -531,6 +531,63 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_machines[] = { EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_arl_machines); /* this table is used when there is no I2S codec present */ + +/* + * Senary SN624x on Arrow Lake: one multi-function device on link 0. + * Separate mach entries per part ID (adr_d[] is AND, not OR). Amp endpoint + * is aggregated like the generic SOC_SDW_DAI_TYPE_AMP shape from + * find_acpi_adr_device(). + */ +static const struct snd_soc_acpi_endpoint sn624x_endpoints[] = { + /* Jack */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp */ + { + .num = 1, + .aggregated = 1, + .group_position = 1, + .group_id = 1, + }, + /* DMIC */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +#define SN624X_ARL_SINGLE_ADR(_sym, _adr) \ +static const struct snd_soc_acpi_adr_device _sym[] = { \ + { \ + .adr = (_adr), \ + .num_endpoints = ARRAY_SIZE(sn624x_endpoints), \ + .endpoints = sn624x_endpoints, \ + .name_prefix = "sn624x", \ + }, \ +} + +#define SN624X_ARL_LINK(_sym, _adr_arr) \ +static const struct snd_soc_acpi_link_adr _sym[] = { \ + { \ + .mask = BIT(0), \ + .num_adr = ARRAY_SIZE(_adr_arr), \ + .adr_d = _adr_arr, \ + }, \ + {} \ +} + +SN624X_ARL_SINGLE_ADR(sn6242_single_adr, 0x0000000496624201ull); +SN624X_ARL_SINGLE_ADR(sn6244_single_adr, 0x0000000496624401ull); + +SN624X_ARL_LINK(arl_link_sn6242, sn6242_single_adr); +SN624X_ARL_LINK(arl_link_sn6244, sn6244_single_adr); + struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { { .link_mask = BIT(0) | BIT(2) | BIT(3), @@ -624,6 +681,20 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_arl_sdw_machines[] = { .sof_tplg_filename = "sof-arl-es9356.tplg", .get_function_tplg_files = sof_sdw_get_tplg_files, }, + { + .link_mask = BIT(0), + .links = arl_link_sn6242, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = arl_link_sn6244, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-arl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_arl_sdw_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c index e4c3492a0c28..442ef30ffcc7 100644 --- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c @@ -137,6 +137,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_cherrytrail_machines[] = { .board = "cht-bsw", .sof_tplg_filename = "sof-cht-rt5670.tplg", }, + { + /* Lenovo Yoga Book YB1-X91F/L */ + .id = "10EC5677", + .drv_name = "cht-rt5677", + .fw_filename = "intel/fw_sst_22a8.bin", + .board = "cht_rt5677", + .sof_tplg_filename = "sof-cht-rt5677.tplg", + }, { .comp_ids = &rt5645_comp_ids, .drv_name = "cht-bsw-rt5645", diff --git a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c index 62b8811395cb..cbf79eb2ec00 100644 --- a/sound/soc/intel/common/soc-acpi-intel-lnl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-lnl-match.c @@ -685,6 +685,63 @@ static const struct snd_soc_acpi_link_adr lnl_sdw_rt712_vb_l2_rt1320_l1[] = { }; /* this table is used when there is no I2S codec present */ + +/* + * Senary SN624x on Lunar Lake: one multi-function device on link 0. + * Separate mach entries per part ID (adr_d[] is AND, not OR). Amp endpoint + * is aggregated like the generic SOC_SDW_DAI_TYPE_AMP shape from + * find_acpi_adr_device(). + */ +static const struct snd_soc_acpi_endpoint sn624x_endpoints[] = { + /* Jack */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp */ + { + .num = 1, + .aggregated = 1, + .group_position = 1, + .group_id = 1, + }, + /* DMIC */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +#define SN624X_LNL_SINGLE_ADR(_sym, _adr) \ +static const struct snd_soc_acpi_adr_device _sym[] = { \ + { \ + .adr = (_adr), \ + .num_endpoints = ARRAY_SIZE(sn624x_endpoints), \ + .endpoints = sn624x_endpoints, \ + .name_prefix = "sn624x", \ + }, \ +} + +#define SN624X_LNL_LINK(_sym, _adr_arr) \ +static const struct snd_soc_acpi_link_adr _sym[] = { \ + { \ + .mask = BIT(0), \ + .num_adr = ARRAY_SIZE(_adr_arr), \ + .adr_d = _adr_arr, \ + }, \ + {} \ +} + +SN624X_LNL_SINGLE_ADR(sn6242_single_adr, 0x0000000496624201ull); +SN624X_LNL_SINGLE_ADR(sn6244_single_adr, 0x0000000496624401ull); + +SN624X_LNL_LINK(lnl_link_sn6242, sn6242_single_adr); +SN624X_LNL_LINK(lnl_link_sn6244, sn6244_single_adr); + /* this table is used when there is no I2S codec present */ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { /* mockup tests need to be first */ @@ -806,6 +863,20 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { .sof_tplg_filename = "sof-lnl-rt713-l2-rt1320-l13.tplg", .get_function_tplg_files = sof_sdw_get_tplg_files, }, + { + .link_mask = BIT(0), + .links = lnl_link_sn6242, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = lnl_link_sn6244, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_lnl_sdw_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c index 596582053d93..e89c5cfd6ed1 100644 --- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c @@ -1296,6 +1296,63 @@ static const struct snd_soc_acpi_link_adr mtl_cs35l63_x2_link1_link3_fb[] = { }; /* this table is used when there is no I2S codec present */ + +/* + * Senary SN624x on Meteor Lake: one multi-function device on link 0. + * Separate mach entries per part ID (adr_d[] is AND, not OR). Amp endpoint + * is aggregated like the generic SOC_SDW_DAI_TYPE_AMP shape from + * find_acpi_adr_device(). + */ +static const struct snd_soc_acpi_endpoint sn624x_endpoints[] = { + /* Jack */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp */ + { + .num = 1, + .aggregated = 1, + .group_position = 1, + .group_id = 1, + }, + /* DMIC */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +#define SN624X_MTL_SINGLE_ADR(_sym, _adr) \ +static const struct snd_soc_acpi_adr_device _sym[] = { \ + { \ + .adr = (_adr), \ + .num_endpoints = ARRAY_SIZE(sn624x_endpoints), \ + .endpoints = sn624x_endpoints, \ + .name_prefix = "sn624x", \ + }, \ +} + +#define SN624X_MTL_LINK(_sym, _adr_arr) \ +static const struct snd_soc_acpi_link_adr _sym[] = { \ + { \ + .mask = BIT(0), \ + .num_adr = ARRAY_SIZE(_adr_arr), \ + .adr_d = _adr_arr, \ + }, \ + {} \ +} + +SN624X_MTL_SINGLE_ADR(sn6242_single_adr, 0x0000000496624201ull); +SN624X_MTL_SINGLE_ADR(sn6244_single_adr, 0x0000000496624401ull); + +SN624X_MTL_LINK(mtl_link_sn6242, sn6242_single_adr); +SN624X_MTL_LINK(mtl_link_sn6244, sn6244_single_adr); + struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { /* mockup tests need to be first */ { @@ -1503,6 +1560,20 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { .sof_tplg_filename = "sof-mtl-sdw-cs42l42-l0-max98363-l2.tplg", .get_function_tplg_files = sof_sdw_get_tplg_files, }, + { + .link_mask = BIT(0), + .links = mtl_link_sn6242, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(0), + .links = mtl_link_sn6244, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_mtl_sdw_machines); diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c index 756bbf82a326..0ce51c44bfe6 100644 --- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c @@ -51,12 +51,14 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_machines[] = { .drv_name = "ptl_es83x6_c1_h02", .machine_quirk = snd_soc_acpi_codec_list, .quirk_data = &ptl_lt6911_hdmi, + .get_function_tplg_files = sof_i2s_get_tplg_files, .sof_tplg_filename = "sof-ptl-es83x6-ssp1-hdmi-ssp02.tplg", }, { .comp_ids = &ptl_essx_83x6, .drv_name = "sof-essx8336", .sof_tplg_filename = "sof-ptl-es8336", /* the tplg suffix is added at run time */ + .get_function_tplg_files = sof_i2s_get_tplg_files, .tplg_quirk_mask = SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER | SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, @@ -65,6 +67,7 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_machines[] = { { .id = "INTC10B0", .drv_name = "ptl_lt6911_hdmi_ssp", + .get_function_tplg_files = sof_i2s_get_tplg_files, .sof_tplg_filename = "sof-ptl-hdmi-ssp02.tplg", }, {}, @@ -438,6 +441,66 @@ static const struct snd_soc_acpi_link_adr ptl_sdw_rt712_vb_l3_rt1320_l3[] = { }; /* this table is used when there is no I2S codec present */ + +/* + * Senary SN624x on Panther Lake: one multi-function device on link 3. + * Separate mach entries per part ID (adr_d[] is AND, not OR). ADR version + * nibble 0x3 matches the silicon SoundWire version. Amp endpoint is + * aggregated like the generic SOC_SDW_DAI_TYPE_AMP shape from + * find_acpi_adr_device(). + */ +static const struct snd_soc_acpi_endpoint sn624x_endpoints[] = { + /* Jack */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp */ + { + .num = 1, + .aggregated = 1, + .group_position = 1, + .group_id = 1, + }, + /* DMIC */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +#define SN624X_PTL_SINGLE_ADR(_sym, _adr) \ +static const struct snd_soc_acpi_adr_device _sym[] = { \ + { \ + .adr = (_adr), \ + .num_endpoints = ARRAY_SIZE(sn624x_endpoints), \ + .endpoints = sn624x_endpoints, \ + .name_prefix = "sn624x", \ + }, \ +} + +#define SN624X_PTL_LINK(_sym, _adr_arr) \ +static const struct snd_soc_acpi_link_adr _sym[] = { \ + { \ + .mask = BIT(3), \ + .num_adr = ARRAY_SIZE(_adr_arr), \ + .adr_d = _adr_arr, \ + }, \ + {} \ +} + +SN624X_PTL_SINGLE_ADR(sn6242_single_adr, 0x0003300496624201ull); +SN624X_PTL_SINGLE_ADR(sn6244_single_adr, 0x0003300496624401ull); +SN624X_PTL_SINGLE_ADR(sn6247_single_adr, 0x0003300496624701ull); + +SN624X_PTL_LINK(ptl_link_sn6242, sn6242_single_adr); +SN624X_PTL_LINK(ptl_link_sn6244, sn6244_single_adr); +SN624X_PTL_LINK(ptl_link_sn6247, sn6247_single_adr); + struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { /* Order Priority: mockup > most links > most bit link-mask > alphabetical */ { @@ -531,6 +594,28 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = { .machine_check = snd_soc_acpi_intel_rt712_vb_no_function_topology, .sof_tplg_filename = "sof-ptl-rt712-l3-rt1320-l3.tplg", }, + + { + .link_mask = BIT(3), + .links = ptl_link_sn6242, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3), + .links = ptl_link_sn6244, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, + { + .link_mask = BIT(3), + .links = ptl_link_sn6247, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-ptl-sn624x.tplg", + .get_function_tplg_files = sof_sdw_get_tplg_files, + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_ptl_sdw_machines); diff --git a/sound/soc/intel/common/sof-function-topology-lib.c b/sound/soc/intel/common/sof-function-topology-lib.c index b6e5a40b78cc..57e089f8e721 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.c +++ b/sound/soc/intel/common/sof-function-topology-lib.c @@ -19,6 +19,10 @@ enum tplg_device_id { TPLG_DEVICE_SDCA_MIC, TPLG_DEVICE_INTEL_PCH_DMIC, TPLG_DEVICE_HDMI, + TPLG_DEVICE_SSP_JACK, + TPLG_DEVICE_SSP_AMP, + TPLG_DEVICE_SSP_BT, + TPLG_DEVICE_SSP_HDMI_IN, TPLG_DEVICE_LOOPBACK_VIRTUAL, TPLG_DEVICE_MAX }; @@ -28,6 +32,137 @@ enum tplg_device_id { #define SOF_INTEL_PLATFORM_NAME_MAX 4 +static int get_platform_name(struct snd_soc_card *card, + const struct snd_soc_acpi_mach *mach, char *platform) +{ + int ret; + + ret = sscanf(mach->sof_tplg_filename, "sof-%3s-*.tplg", platform); + if (ret != 1) { + dev_err(card->dev, "Invalid platform name of tplg %s\n", + mach->sof_tplg_filename); + return -EINVAL; + } + + return 0; +} + +static bool tplg_files_exist(struct device *dev, const char *tplg_files) +{ + const struct firmware *fw; + int ret; + + ret = firmware_request_nowarn(&fw, tplg_files, dev); + if (!ret) { + release_firmware(fw); + return true; + } + + dev_warn(dev, + "Failed to open topology file: %s, you might need to\n", + tplg_files); + dev_warn(dev, + "download it from https://github.com/thesofproject/sof-bin/\n"); + return false; +} + +static char *get_tplg_filename(struct device *dev, const char *prefix, + const char *platform, const char *tplg_dev_name, + int dai_link_id, int tplg_dev) +{ + char *filename = NULL; + + /* + * The tplg file naming rule is sof---id.tplg + * where is required for functions that depend on NHLT blobs (e.g. DMIC/SSP) + * as the nhlt blob is platform dependent. + */ + switch (tplg_dev) { + case TPLG_DEVICE_INTEL_PCH_DMIC: + case TPLG_DEVICE_SSP_JACK: + case TPLG_DEVICE_SSP_AMP: + case TPLG_DEVICE_SSP_BT: + case TPLG_DEVICE_SSP_HDMI_IN: + filename = devm_kasprintf(dev, GFP_KERNEL, "%s/sof-%s-%s-id%d.tplg", + prefix, platform, tplg_dev_name, dai_link_id); + break; + default: + filename = devm_kasprintf(dev, GFP_KERNEL, "%s/sof-%s-id%d.tplg", + prefix, tplg_dev_name, dai_link_id); + break; + } + + return filename; +} + +static int get_dmic_tplg_dev(struct device *dev, int dmic_num, + int *tplg_dev, char **tplg_dev_name) +{ + switch (dmic_num) { + case 2: + *tplg_dev_name = "dmic-2ch"; + break; + case 4: + *tplg_dev_name = "dmic-4ch"; + break; + default: + dev_warn(dev, + "unsupported number of dmics: %d\n", + dmic_num); + return -EINVAL; + } + *tplg_dev = TPLG_DEVICE_INTEL_PCH_DMIC; + + return 0; +} + +static int get_ssp_tplg_dev(struct device *dev, struct snd_soc_dai_link *dai_link, + u16 *hdmi_in_mask, int *tplg_dev, char **tplg_dev_name) +{ + unsigned int ssp_port; + + if (sscanf(dai_link->name, "SSP%u", &ssp_port) != 1) { + dev_err(dev, "Can't get SSP port from dai_link->name %s\n", dai_link->name); + return -EINVAL; + } + if (strstr(dai_link->name, "Codec")) { + /* + * Assume DAI link 0 is jack which is true in all existing + * machine drivers + */ + if (dai_link->id == 0) { + *tplg_dev = TPLG_DEVICE_SSP_JACK; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-jack", ssp_port); + } else { + *tplg_dev = TPLG_DEVICE_SSP_AMP; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-amp", ssp_port); + } + } else if (strstr(dai_link->name, "BT")) { + *tplg_dev = TPLG_DEVICE_SSP_BT; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%d-bt", ssp_port); + } else if (strstr(dai_link->name, "HDMI")) { + *hdmi_in_mask |= BIT(ssp_port); + /* The number of HDMI in dai link is always 2 right now */ + if (hweight16(*hdmi_in_mask) != 2) + return -EINVAL; + + *tplg_dev = TPLG_DEVICE_SSP_HDMI_IN; + *tplg_dev_name = devm_kasprintf(dev, GFP_KERNEL, + "ssp%x-hdmiin", *hdmi_in_mask); + } else { + dev_warn(dev, + "unsupported SSP link %s\n", dai_link->name); + return -EINVAL; + } + if (!*tplg_dev_name) + return -ENOMEM; + + return 0; +} + int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, const char *prefix, const char ***tplg_files, bool best_effort) { @@ -38,20 +173,18 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ */ struct snd_soc_acpi_mach_params mach_params = card_mach->mach_params; struct snd_soc_dai_link *dai_link; - const struct firmware *fw; char platform[SOF_INTEL_PLATFORM_NAME_MAX]; unsigned long tplg_mask = 0; + u16 hdmi_in_mask = 0; int tplg_num = 0; + char *tplg_file; int tplg_dev; int ret; int i; - ret = sscanf(mach->sof_tplg_filename, "sof-%3s-*.tplg", platform); - if (ret != 1) { - dev_err(card->dev, "Invalid platform name %s of tplg %s\n", - platform, mach->sof_tplg_filename); - return -EINVAL; - } + ret = get_platform_name(card, mach, platform); + if (ret < 0) + return ret; for_each_card_prelinks(card, i, dai_link) { char *tplg_dev_name; @@ -70,23 +203,16 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ tplg_dev = TPLG_DEVICE_SDCA_MIC; tplg_dev_name = "sdca-mic"; } else if (strstr(dai_link->name, "dmic")) { - switch (mach_params.dmic_num) { - case 2: - tplg_dev_name = "dmic-2ch"; - break; - case 4: - tplg_dev_name = "dmic-4ch"; - break; - default: - dev_warn(card->dev, - "unsupported number of dmics: %d\n", - mach_params.dmic_num); + if (get_dmic_tplg_dev(card->dev, mach_params.dmic_num, + &tplg_dev, &tplg_dev_name) < 0) continue; - } - tplg_dev = TPLG_DEVICE_INTEL_PCH_DMIC; } else if (strstr(dai_link->name, "iDisp")) { tplg_dev = TPLG_DEVICE_HDMI; tplg_dev_name = "hdmi-pcm5"; + } else if (strstr(dai_link->name, "SSP")) { + if (get_ssp_tplg_dev(card->dev, dai_link, &hdmi_in_mask, + &tplg_dev, &tplg_dev_name) < 0) + continue; } else if (strstr(dai_link->name, "Loopback_Virtual")) { tplg_dev = TPLG_DEVICE_LOOPBACK_VIRTUAL; /* @@ -109,49 +235,100 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_ if (tplg_mask & BIT(tplg_dev)) continue; + tplg_file = get_tplg_filename(card->dev, prefix, platform, tplg_dev_name, + dai_link->id, tplg_dev); + if (!tplg_file) + return -ENOMEM; + + /* Check presence of sub-topologies */ + if (!tplg_files_exist(card->dev, tplg_file)) { + devm_kfree(card->dev, tplg_file); + if (best_effort) + continue; + + return 0; + } + tplg_mask |= BIT(tplg_dev); - /* - * The tplg file naming rule is sof---id.tplg - * where is only required for the DMIC function as the nhlt blob - * is platform dependent. - */ - switch (tplg_dev) { - case TPLG_DEVICE_INTEL_PCH_DMIC: - (*tplg_files)[tplg_num] = devm_kasprintf(card->dev, GFP_KERNEL, - "%s/sof-%s-%s-id%d.tplg", - prefix, platform, - tplg_dev_name, dai_link->id); - break; - default: - (*tplg_files)[tplg_num] = devm_kasprintf(card->dev, GFP_KERNEL, - "%s/sof-%s-id%d.tplg", - prefix, tplg_dev_name, - dai_link->id); - break; - } - if (!(*tplg_files)[tplg_num]) - return -ENOMEM; + (*tplg_files)[tplg_num] = tplg_file; tplg_num++; } dev_dbg(card->dev, "tplg_mask %#lx tplg_num %d\n", tplg_mask, tplg_num); - /* Check presence of sub-topologies */ - for (i = 0; i < tplg_num; i++) { - ret = firmware_request_nowarn(&fw, (*tplg_files)[i], card->dev); - if (!ret) { - release_firmware(fw); - } else { - dev_warn(card->dev, - "Failed to open topology file: %s, you might need to\n", - (*tplg_files)[i]); - dev_warn(card->dev, - "download it from https://github.com/thesofproject/sof-bin/\n"); - return 0; - } - } - return tplg_num; } EXPORT_SYMBOL_GPL(sof_sdw_get_tplg_files); + +int sof_i2s_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, + const char *prefix, const char ***tplg_files, bool best_effort) +{ + struct snd_soc_acpi_mach_params mach_params = mach->mach_params; + struct snd_soc_dai_link *dai_link; + char platform[SOF_INTEL_PLATFORM_NAME_MAX]; + unsigned long tplg_mask = 0; + u16 hdmi_in_mask = 0; + int tplg_num = 0; + char *tplg_file; + int tplg_dev; + int ret; + int i; + + ret = get_platform_name(card, mach, platform); + if (ret < 0) + return ret; + + for_each_card_prelinks(card, i, dai_link) { + char *tplg_dev_name; + + dev_dbg(card->dev, "dai_link %s id %d\n", dai_link->name, dai_link->id); + if (strstr(dai_link->name, "SSP")) { + if (get_ssp_tplg_dev(card->dev, dai_link, &hdmi_in_mask, + &tplg_dev, &tplg_dev_name) < 0) + continue; + } else if (strstr(dai_link->name, "dmic")) { + if (get_dmic_tplg_dev(card->dev, mach_params.dmic_num, + &tplg_dev, &tplg_dev_name) < 0) + continue; + } else if (strstr(dai_link->name, "iDisp")) { + tplg_dev = TPLG_DEVICE_HDMI; + tplg_dev_name = "hdmi-pcm5"; + } else { + /* The dai link is not supported by separated tplg yet */ + dev_dbg(card->dev, + "dai_link %s is not supported by separated tplg yet\n", + dai_link->name); + if (best_effort) + continue; + + return 0; + } + if (tplg_mask & BIT(tplg_dev)) + continue; + + tplg_file = get_tplg_filename(card->dev, prefix, platform, tplg_dev_name, + dai_link->id, tplg_dev); + if (!tplg_file) + return -ENOMEM; + + /* Check presence of sub-topologies */ + if (!tplg_files_exist(card->dev, tplg_file)) { + devm_kfree(card->dev, tplg_file); + if (best_effort) + continue; + + return 0; + } + + tplg_mask |= BIT(tplg_dev); + + (*tplg_files)[tplg_num] = tplg_file; + tplg_num++; + } + + dev_dbg(card->dev, "tplg_mask %#lx tplg_num %d\n", tplg_mask, tplg_num); + + return tplg_num; +} +EXPORT_SYMBOL_GPL(sof_i2s_get_tplg_files); diff --git a/sound/soc/intel/common/sof-function-topology-lib.h b/sound/soc/intel/common/sof-function-topology-lib.h index f358f8c52d78..9755e9770968 100644 --- a/sound/soc/intel/common/sof-function-topology-lib.h +++ b/sound/soc/intel/common/sof-function-topology-lib.h @@ -12,4 +12,7 @@ int sof_sdw_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, const char *prefix, const char ***tplg_files, bool best_effort); +int sof_i2s_get_tplg_files(struct snd_soc_card *card, const struct snd_soc_acpi_mach *mach, + const char *prefix, const char ***tplg_files, bool best_effort); + #endif diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index d36bbc820618..7a1589cb775a 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -326,6 +326,11 @@ static int jz4740_i2s_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 jz4740_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops jz4740_i2s_dai_ops = { .probe = jz4740_i2s_dai_probe, .startup = jz4740_i2s_startup, @@ -333,6 +338,8 @@ static const struct snd_soc_dai_ops jz4740_i2s_dai_ops = { .trigger = jz4740_i2s_trigger, .hw_params = jz4740_i2s_hw_params, .set_fmt = jz4740_i2s_set_fmt, + .auto_selectable_formats = &jz4740_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; #define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \ diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c index 99bd066c7309..739a819d8494 100644 --- a/sound/soc/kirkwood/kirkwood-i2s.c +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -544,11 +544,18 @@ static int kirkwood_i2s_init(struct kirkwood_dma_data *priv) } +static const u64 kirkwood_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops kirkwood_i2s_dai_ops = { .startup = kirkwood_i2s_startup, .trigger = kirkwood_i2s_trigger, .hw_params = kirkwood_i2s_hw_params, .set_fmt = kirkwood_i2s_set_fmt, + .auto_selectable_formats = &kirkwood_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver kirkwood_i2s_dai[2] = { diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c index cfe102a8b604..da0d029c028a 100644 --- a/sound/soc/loongson/loongson_i2s.c +++ b/sound/soc/loongson/loongson_i2s.c @@ -220,12 +220,18 @@ static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai) return 0; } +static const u64 loongson_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J; + static const struct snd_soc_dai_ops loongson_i2s_dai_ops = { .probe = loongson_i2s_dai_probe, .trigger = loongson_i2s_trigger, .hw_params = loongson_i2s_hw_params, .set_sysclk = loongson_i2s_set_dai_sysclk, .set_fmt = loongson_i2s_set_fmt, + .auto_selectable_formats = &loongson_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; struct snd_soc_dai_driver loongson_i2s_dai = { diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 224746e7664f..ff2869641a35 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -1,10 +1,35 @@ # SPDX-License-Identifier: GPL-2.0-only -menu "Mediatek" config SND_SOC_MEDIATEK tristate select REGMAP_MMIO +menu "Airoha" + +config SND_SOC_AN7581 + tristate "ASoC support for Airoha AN7581 chip" + depends on ARCH_AIROHA || COMPILE_TEST + select SND_SOC_MEDIATEK + help + This adds ASoC platform driver support for Airoha AN7581 chip + that can be used with other codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_AN7581_WM8960 + tristate "ASoc Audio driver for Airoha AN7581 with WM8960 codec" + depends on SND_SOC_AN7581 && I2C + select SND_SOC_WM8960 + help + This adds support for ASoC machine driver for Airoha AN7581 + boards with the WM8960 codecs. + Select Y if you have such device. + If unsure select "N". + +endmenu + +menu "Mediatek" + config SND_SOC_MT2701 tristate "ASoC support for Mediatek MT2701 chip" depends on ARCH_MEDIATEK || COMPILE_TEST diff --git a/sound/soc/mediatek/Makefile b/sound/soc/mediatek/Makefile index a6815a3c5988..54522e1fa016 100644 --- a/sound/soc/mediatek/Makefile +++ b/sound/soc/mediatek/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SND_SOC_MEDIATEK) += common/ +obj-$(CONFIG_SND_SOC_AN7581) += an7581/ obj-$(CONFIG_SND_SOC_MT2701) += mt2701/ obj-$(CONFIG_SND_SOC_MT6797) += mt6797/ obj-$(CONFIG_SND_SOC_MT7986) += mt7986/ diff --git a/sound/soc/mediatek/an7581/Makefile b/sound/soc/mediatek/an7581/Makefile new file mode 100644 index 000000000000..f48cd0269bea --- /dev/null +++ b/sound/soc/mediatek/an7581/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 + +# platform driver +snd-soc-an7581-afe-y := \ + an7581-afe-pcm.o \ + an7581-dai-etdm.o + +obj-$(CONFIG_SND_SOC_AN7581) += snd-soc-an7581-afe.o +obj-$(CONFIG_SND_SOC_AN7581_WM8960) += an7581-wm8960.o diff --git a/sound/soc/mediatek/an7581/an7581-afe-common.h b/sound/soc/mediatek/an7581/an7581-afe-common.h new file mode 100644 index 000000000000..ebc8e2f7fec4 --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-afe-common.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * an7581-afe-common.h -- Airoha AN7581 audio driver definitions + */ + +#ifndef _AN7581_AFE_COMMON_H_ +#define _AN7581_AFE_COMMON_H_ + +#include +#include +#include +#include + +#include "../../mediatek/common/mtk-base-afe.h" + +enum { + AN7581_MEMIF_DL1, + AN7581_MEMIF_UL1, + AN7581_MEMIF_NUM, + AN7581_DAI_ETDM = AN7581_MEMIF_NUM, + AN7581_DAI_NUM, +}; + +enum { + AN7581_IRQ_0, + AN7581_IRQ_1, + AN7581_IRQ_NUM, +}; + +struct an7581_memif_irq_desc { + u8 irq; + u32 status_bit; + u32 clear_reg; +}; + +struct an7581_afe_private { + unsigned int users; + /* protect concurrent ETDM user tracking */ + struct mutex user_lock; + + void *dai_priv[AN7581_DAI_NUM]; +}; + +unsigned int an7581_afe_rate_transform(struct device *dev, + unsigned int rate); + +int an7581_dai_etdm_register(struct mtk_base_afe *afe); + +#endif diff --git a/sound/soc/mediatek/an7581/an7581-afe-pcm.c b/sound/soc/mediatek/an7581/an7581-afe-pcm.c new file mode 100644 index 000000000000..d47edb630452 --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-afe-pcm.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC AFE platform driver for AN7581 + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "an7581-afe-common.h" +#include "an7581-reg.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" + +enum { + ARH_AFE_RATE_8K = 0, + ARH_AFE_RATE_12K = 1, + ARH_AFE_RATE_16K = 2, + ARH_AFE_RATE_24K = 3, + ARH_AFE_RATE_32K = 4, + ARH_AFE_RATE_48K = 5, + ARH_AFE_RATE_96K = 6, + ARH_AFE_RATE_192K = 7, + ARH_AFE_RATE_384K = 8, + ARH_AFE_RATE_7K = 16, + ARH_AFE_RATE_11K = 17, + ARH_AFE_RATE_14K = 18, + ARH_AFE_RATE_22K = 19, + ARH_AFE_RATE_29K = 20, + ARH_AFE_RATE_44K = 21, + ARH_AFE_RATE_88K = 22, + ARH_AFE_RATE_176K = 23, + ARH_AFE_RATE_352K = 24, +}; + +unsigned int an7581_afe_rate_transform(struct device *dev, unsigned int rate) +{ + switch (rate) { + case 7350: + return ARH_AFE_RATE_7K; + case 8000: + return ARH_AFE_RATE_8K; + case 11025: + return ARH_AFE_RATE_11K; + case 12000: + return ARH_AFE_RATE_12K; + case 14700: + return ARH_AFE_RATE_14K; + case 16000: + return ARH_AFE_RATE_16K; + case 22050: + return ARH_AFE_RATE_22K; + case 24000: + return ARH_AFE_RATE_24K; + case 29400: + return ARH_AFE_RATE_29K; + case 32000: + return ARH_AFE_RATE_32K; + case 44100: + return ARH_AFE_RATE_44K; + case 48000: + return ARH_AFE_RATE_48K; + case 88200: + return ARH_AFE_RATE_88K; + case 96000: + return ARH_AFE_RATE_96K; + case 176400: + return ARH_AFE_RATE_176K; + case 192000: + return ARH_AFE_RATE_192K; + case 352800: + return ARH_AFE_RATE_352K; + case 384000: + return ARH_AFE_RATE_384K; + default: + dev_warn_ratelimited(dev, "%s(), rate %u invalid, using %d!\n", + __func__, rate, ARH_AFE_RATE_48K); + return ARH_AFE_RATE_48K; + } +} + +static const struct an7581_memif_irq_desc an7581_memif_irq_descs[AN7581_MEMIF_NUM] = { + [AN7581_MEMIF_DL1] = { + .irq = AN7581_IRQ_0, + .status_bit = AFE_IRQ_STS_PLAY, + .clear_reg = AFE_IRQ_CON0, + }, + [AN7581_MEMIF_UL1] = { + .irq = AN7581_IRQ_1, + .status_bit = AFE_IRQ_STS_RECORD, + .clear_reg = AFE_IRQ1_CON0, + }, +}; + +static const struct snd_pcm_hardware an7581_afe_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 512, + .period_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 256 * 1024, +}; + +static int an7581_memif_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component); + + return an7581_afe_rate_transform(afe->dev, rate); +} + +static int an7581_afe_fe_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = mtk_afe_fe_startup(substream, dai); + if (ret < 0) + return ret; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 0x2000, UINT_MAX); + if (ret < 0) + dev_err(afe->dev, "hw_constraint_minmax failed\n"); + } + + return ret; +} + +static const struct snd_soc_dai_ops an7581_afe_fe_ops = { + .startup = an7581_afe_fe_startup, + .shutdown = mtk_afe_fe_shutdown, + .hw_params = mtk_afe_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mtk_afe_fe_trigger, +}; + +#define ARH_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver an7581_memif_dai_driver[] = { + /* FE DAIs: memory intefaces to CPU */ + { + .name = "DL1", + .id = AN7581_MEMIF_DL1, + .playback = { + .stream_name = "DL1", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = ARH_PCM_FORMATS, + }, + .ops = &an7581_afe_fe_ops, + }, + { + .name = "UL1", + .id = AN7581_MEMIF_UL1, + .capture = { + .stream_name = "UL1", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = ARH_PCM_FORMATS, + }, + .ops = &an7581_afe_fe_ops, + }, +}; + +static const struct snd_soc_dapm_widget an7581_memif_widgets[] = { + /* DL */ + SND_SOC_DAPM_MIXER("I032", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I033", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* UL */ + SND_SOC_DAPM_MIXER("O018", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("O019", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route an7581_memif_routes[] = { + {"I032", NULL, "DL1"}, + {"I033", NULL, "DL1"}, + {"UL1", NULL, "O018"}, + {"UL1", NULL, "O019"}, + {"O018", NULL, "I150"}, + {"O019", NULL, "I151"}, +}; + +static const struct snd_soc_component_driver an7581_afe_pcm_dai_component = { + .name = "an7581-afe-pcm-dai", +}; + +static const struct mtk_base_memif_data memif_data[AN7581_MEMIF_NUM] = { + [AN7581_MEMIF_DL1] = { + .name = "DL1", + .id = AN7581_MEMIF_DL1, + .reg_ofs_base = AFE_DL1_BASE, + .reg_ofs_cur = AFE_DL1_CUR, + .reg_ofs_end = AFE_DL1_END, + .fs_reg = -1, + .fs_shift = -1, + .fs_maskbit = -1, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = AFE_DL1_CON0, + .hd_shift = AFE_HD_SHIFT, + .hd_align_reg = -1, + .hd_align_mshift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = AFE_DL1_ENABLE_SHIFT, + .msb_reg = -1, + .msb_shift = -1, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, + [AN7581_MEMIF_UL1] = { + .name = "UL1", + .id = AN7581_MEMIF_UL1, + .reg_ofs_base = AFE_UL1_BASE, + .reg_ofs_cur = AFE_UL1_CUR, + .reg_ofs_end = AFE_UL1_END, + .fs_reg = -1, + .fs_shift = -1, + .fs_maskbit = -1, + .mono_reg = -1, + .mono_shift = -1, + .hd_reg = AFE_UL1_CON0, + .hd_shift = AFE_HD_SHIFT, + .hd_align_reg = AFE_UL1_CON0, + .hd_align_mshift = AFE_HD_ALIGN_SHIFT, + .enable_reg = AFE_DAC_CON0, + .enable_shift = AFE_UL1_ENABLE_SHIFT, + .msb_reg = -1, + .msb_shift = -1, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[AN7581_IRQ_NUM] = { + [AN7581_IRQ_0] = { + .id = AN7581_IRQ_0, + .irq_cnt_reg = AFE_IRQ_CNT, + .irq_cnt_shift = AFE_IRQ_CNT_SHIFT, + .irq_cnt_maskbit = AFE_IRQ_CNT_MASK, + .irq_en_reg = AFE_IRQ_CON0, + .irq_en_shift = AFE_IRQ_ON_SHIFT, + .irq_fs_reg = -1, + .irq_fs_shift = -1, + .irq_fs_maskbit = -1, + .irq_clr_reg = -1, + .irq_clr_shift = -1, + }, + [AN7581_IRQ_1] = { + .id = AN7581_IRQ_1, + .irq_cnt_reg = AFE_IRQ1_CNT, + .irq_cnt_shift = AFE_IRQ_CNT_SHIFT, + .irq_cnt_maskbit = AFE_IRQ_CNT_MASK, + .irq_en_reg = AFE_IRQ1_CON0, + .irq_en_shift = AFE_IRQ_ON_SHIFT, + .irq_fs_reg = -1, + .irq_fs_shift = -1, + .irq_fs_maskbit = -1, + .irq_clr_reg = -1, + .irq_clr_shift = -1, + }, +}; + +static const struct regmap_config an7581_afe_regmap_config = { + .name = "afe", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_MAX_REGISTER, +}; + +static const struct regmap_config an7581_afe_irq1_regmap_config = { + .name = "afe_irq1", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_IRQ1_MAX_REGISTER, +}; + +static irqreturn_t an7581_afe_irq_handler(int irq_id, void *dev) +{ + const struct an7581_memif_irq_desc *irq_desc; + struct mtk_base_afe *afe = dev; + u32 status = 0; + int i; + + regmap_read(afe->regmap, AFE_IRQ_STS, &status); + + for (i = 0; i < AN7581_MEMIF_NUM; i++) { + struct mtk_base_afe_memif *memif = &afe->memif[i]; + + if (!memif->substream) + continue; + + if (memif->irq_usage < 0) + continue; + + irq_desc = &an7581_memif_irq_descs[i]; + if (status & irq_desc->status_bit) + snd_pcm_period_elapsed(memif->substream); + } + + for (i = 0; i < AN7581_MEMIF_NUM; i++) { + struct regmap *irq_regmap; + + irq_desc = &an7581_memif_irq_descs[i]; + if (!(status & irq_desc->status_bit)) + continue; + + irq_regmap = afe->irqs[irq_desc->irq].regmap; + regmap_set_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_CLR_SHIFT)); + regmap_clear_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_CLR_SHIFT)); + + regmap_set_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_MISS_FLG_CLR_SHIFT)); + regmap_clear_bits(irq_regmap, irq_desc->clear_reg, + BIT(AFE_IRQ_MISS_FLG_CLR_SHIFT)); + } + + return IRQ_HANDLED; +} + +static int an7581_dai_memif_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = an7581_memif_dai_driver; + dai->num_dai_drivers = ARRAY_SIZE(an7581_memif_dai_driver); + + dai->dapm_widgets = an7581_memif_widgets; + dai->num_dapm_widgets = ARRAY_SIZE(an7581_memif_widgets); + dai->dapm_routes = an7581_memif_routes; + dai->num_dapm_routes = ARRAY_SIZE(an7581_memif_routes); + + return 0; +} + +typedef int (*dai_register_cb)(struct mtk_base_afe *); +static const dai_register_cb dai_register_cbs[] = { + an7581_dai_etdm_register, + an7581_dai_memif_register, +}; + +static int an7581_afe_pcm_dev_probe(struct platform_device *pdev) +{ + struct an7581_afe_private *afe_priv; + struct device *dev = &pdev->dev; + struct reset_control *reset; + struct mtk_base_afe *afe; + int i, irq_id, ret; + void __iomem *base; + + afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + platform_set_drvdata(pdev, afe); + + afe->platform_priv = devm_kzalloc(dev, sizeof(*afe_priv), + GFP_KERNEL); + if (!afe->platform_priv) + return -ENOMEM; + + afe->irqs_size = AN7581_IRQ_NUM; + afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs), + GFP_KERNEL); + if (!afe->irqs) + return -ENOMEM; + + afe->memif_size = AN7581_MEMIF_NUM; + afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif), + GFP_KERNEL); + if (!afe->memif) + return -ENOMEM; + + afe_priv = afe->platform_priv; + mutex_init(&afe_priv->user_lock); + afe->dev = &pdev->dev; + + reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + /* Global reset I2S */ + reset_control_assert(reset); + usleep_range(10, 20); + reset_control_deassert(reset); + + afe->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + + base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(base)) + return PTR_ERR(base); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, + &an7581_afe_regmap_config); + if (IS_ERR(afe->regmap)) + return PTR_ERR(afe->regmap); + + mutex_init(&afe->irq_alloc_lock); + + /* irq initialize */ + for (i = 0; i < afe->irqs_size; i++) + afe->irqs[i].irq_data = &irq_data[i]; + + afe->irqs[AN7581_IRQ_0].regmap = afe->regmap; + afe->irqs[AN7581_IRQ_1].regmap = devm_regmap_init_mmio(&pdev->dev, base, + &an7581_afe_irq1_regmap_config); + if (IS_ERR(afe->irqs[AN7581_IRQ_1].regmap)) + return PTR_ERR(afe->irqs[AN7581_IRQ_1].regmap); + + /* request irq */ + irq_id = platform_get_irq(pdev, 0); + if (irq_id < 0) + return irq_id; + + /* init memif */ + for (i = 0; i < afe->memif_size; i++) { + int sel_irq = an7581_memif_irq_descs[i].irq; + + afe->memif[i].data = &memif_data[i]; + afe->memif[i].irq_usage = sel_irq; + afe->memif[i].const_irq = 1; + afe->irqs[sel_irq].irq_occupyed = true; + } + + /* init sub_dais */ + INIT_LIST_HEAD(&afe->sub_dais); + + ret = devm_request_irq(dev, irq_id, an7581_afe_irq_handler, + IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); + if (ret) + return dev_err_probe(dev, ret, "Failed to request irq for asys-isr\n"); + + for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) { + ret = dai_register_cbs[i](afe); + if (ret) + return dev_err_probe(dev, ret, "DAI register failed, i: %d\n", i); + } + + /* init dai_driver and component_driver */ + ret = mtk_afe_combine_sub_dai(afe); + if (ret) + return dev_err_probe(dev, ret, "mtk_afe_combine_sub_dai fail\n"); + + afe->mtk_afe_hardware = &an7581_afe_hardware; + afe->memif_fs = an7581_memif_irq_fs; + afe->irq_fs = an7581_memif_irq_fs; + + /* register component */ + ret = devm_snd_soc_register_component(&pdev->dev, + &mtk_afe_pcm_platform, + NULL, 0); + if (ret) + return dev_err_probe(dev, ret, "Cannot register AFE component\n"); + + ret = devm_snd_soc_register_component(afe->dev, + &an7581_afe_pcm_dai_component, + afe->dai_drivers, + afe->num_dai_drivers); + if (ret) + return dev_err_probe(dev, ret, "Cannot register PCM DAI component\n"); + + return 0; +} + +static const struct of_device_id an7581_afe_pcm_dt_match[] = { + { .compatible = "airoha,an7581-afe" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an7581_afe_pcm_dt_match); + +static struct platform_driver an7581_afe_pcm_driver = { + .driver = { + .name = "an7581-audio", + .of_match_table = an7581_afe_pcm_dt_match, + }, + .probe = an7581_afe_pcm_dev_probe, +}; +module_platform_driver(an7581_afe_pcm_driver); + +MODULE_DESCRIPTION("Airoha SoC AFE platform driver for ALSA AN7581"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mediatek/an7581/an7581-dai-etdm.c b/sound/soc/mediatek/an7581/an7581-dai-etdm.c new file mode 100644 index 000000000000..7fbfd8fe1836 --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-dai-etdm.c @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC Audio DAI eTDM Control + * + */ + +#include +#include +#include +#include +#include "an7581-afe-common.h" +#include "an7581-reg.h" + +#define HOPPING_CLK 0 +#define APLL_CLK 1 +#define MTK_DAI_ETDM_FORMAT_I2S 0 +#define MTK_DAI_ETDM_FORMAT_DSPA 4 +#define MTK_DAI_ETDM_FORMAT_DSPB 5 + +enum { + MTK_ETDM_RATE_8K = 0, + MTK_ETDM_RATE_12K = 1, + MTK_ETDM_RATE_16K = 2, + MTK_ETDM_RATE_24K = 3, + MTK_ETDM_RATE_32K = 4, + MTK_ETDM_RATE_48K = 5, + MTK_ETDM_RATE_96K = 6, + MTK_ETDM_RATE_192K = 7, + MTK_ETDM_RATE_384K = 8, + MTK_ETDM_RATE_7K = 16, + MTK_ETDM_RATE_11K = 17, + MTK_ETDM_RATE_14K = 18, + MTK_ETDM_RATE_22K = 19, + MTK_ETDM_RATE_29K = 20, + MTK_ETDM_RATE_44K = 21, + MTK_ETDM_RATE_88K = 22, + MTK_ETDM_RATE_176K = 23, + MTK_ETDM_RATE_352K = 24, +}; + +struct mtk_dai_etdm_priv { + bool bck_inv; + bool lrck_inv; + bool slave_mode; + unsigned int format; +}; + +static unsigned int an7581_etdm_rate_transform(struct device *dev, unsigned int rate) +{ + switch (rate) { + case 7350: + return MTK_ETDM_RATE_7K; + case 8000: + return MTK_ETDM_RATE_8K; + case 11025: + return MTK_ETDM_RATE_11K; + case 12000: + return MTK_ETDM_RATE_12K; + case 14700: + return MTK_ETDM_RATE_14K; + case 16000: + return MTK_ETDM_RATE_16K; + case 22050: + return MTK_ETDM_RATE_22K; + case 24000: + return MTK_ETDM_RATE_24K; + case 29400: + return MTK_ETDM_RATE_29K; + case 32000: + return MTK_ETDM_RATE_32K; + case 44100: + return MTK_ETDM_RATE_44K; + case 48000: + return MTK_ETDM_RATE_48K; + case 88200: + return MTK_ETDM_RATE_88K; + case 96000: + return MTK_ETDM_RATE_96K; + case 176400: + return MTK_ETDM_RATE_176K; + case 192000: + return MTK_ETDM_RATE_192K; + case 352800: + return MTK_ETDM_RATE_352K; + case 384000: + return MTK_ETDM_RATE_384K; + default: + dev_warn_ratelimited(dev, "%s(), rate %u invalid, using %d!\n", + __func__, rate, MTK_ETDM_RATE_48K); + return MTK_ETDM_RATE_48K; + } +} + +static int get_etdm_wlen(unsigned int bitwidth) +{ + return bitwidth <= 16 ? 16 : 32; +} + +static const struct snd_soc_dapm_widget mtk_dai_etdm_widgets[] = { + /* DL */ + SND_SOC_DAPM_MIXER("I150", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I151", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* UL */ + SND_SOC_DAPM_MIXER("O124", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("O125", SND_SOC_NOPM, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route mtk_dai_etdm_routes[] = { + {"I150", NULL, "ETDM Capture"}, + {"I151", NULL, "ETDM Capture"}, + {"ETDM Playback", NULL, "O124"}, + {"ETDM Playback", NULL, "O125"}, + {"O124", NULL, "I032"}, + {"O125", NULL, "I033"}, +}; + +/* dai ops */ +static int mtk_dai_etdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct an7581_afe_private *afe_priv = afe->platform_priv; + + guard(mutex)(&afe_priv->user_lock); + + if (!afe_priv->users) + regmap_set_bits(afe->regmap, AFE_DAC_CON0, + BIT(AFE_AFE_ENABLE_SHIFT)); + afe_priv->users++; + + return 0; +} + +static void mtk_dai_etdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct an7581_afe_private *afe_priv = afe->platform_priv; + + guard(mutex)(&afe_priv->user_lock); + + afe_priv->users--; + if (!afe_priv->users) + regmap_clear_bits(afe->regmap, AFE_DAC_CON0, + BIT(AFE_AFE_ENABLE_SHIFT)); +} + +static unsigned int get_etdm_ch_fixup(unsigned int channels) +{ + if (channels > 16) + return 24; + + if (channels > 8) + return 16; + + if (channels > 4) + return 8; + + if (channels > 2) + return 4; + + return 2; +} + +static int mtk_dai_etdm_config(struct mtk_base_afe *afe, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, + int stream) +{ + struct an7581_afe_private *afe_priv = afe->platform_priv; + struct mtk_dai_etdm_priv *etdm_data = afe_priv->dai_priv[dai->id]; + unsigned int rate = params_rate(params); + unsigned int etdm_rate = an7581_etdm_rate_transform(afe->dev, rate); + unsigned int bit_width = params_width(params); + unsigned int mask, mask1; + unsigned int val, val1; + + dev_dbg(afe->dev, "%s(), stream %d, rate %u, bitwidth %u\n", + __func__, stream, rate, params_width(params)); + + /* CON0 */ + mask = ETDM_SLAVE_MODE | ETDM_BIT_LEN | ETDM_WRD_LEN | + ETDM_FMT | ETDM_CH_NUM; + val = FIELD_PREP(ETDM_BIT_LEN, params_width(params) - 1) | + FIELD_PREP(ETDM_WRD_LEN, get_etdm_wlen(bit_width) - 1) | + FIELD_PREP(ETDM_FMT, etdm_data->format) | + FIELD_PREP(ETDM_CH_NUM, + get_etdm_ch_fixup(params_channels(params)) - 1); + if (etdm_data->slave_mode) + val |= ETDM_SLAVE_MODE; + + /* CON1 */ + mask1 = EDTM_LRCK_AUTO_MODE | EDTM_CKEN_SEL | EDTM_LRCK_AUTO_OFF | + EDTM_INITIAL_POINT | EDTM_INITIAL_COUNT; + val1 = EDTM_LRCK_AUTO_MODE | EDTM_CKEN_SEL | EDTM_LRCK_AUTO_OFF | + FIELD_PREP(EDTM_INITIAL_POINT, 14) | FIELD_PREP(EDTM_INITIAL_COUNT, 14); + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + regmap_update_bits(afe->regmap, ETDM_OUT1_CON0, mask, val); + + mask1 |= EDTM_DIRECT_INPUT_MASTER_BCK; + val1 |= EDTM_DIRECT_INPUT_MASTER_BCK; + + regmap_update_bits(afe->regmap, ETDM_OUT1_CON1, mask1, val1); + + regmap_update_bits(afe->regmap, ETDM_OUT1_CON4, OUT_SEL_FS, + FIELD_PREP(OUT_SEL_FS, etdm_rate)); + + regmap_update_bits(afe->irqs[AN7581_IRQ_0].regmap, + afe->irqs[AN7581_IRQ_0].irq_data->irq_en_reg, + AFE_IRQ_EN_SEL, AFE_IRQ_EN_SEL_I2SOUT); + break; + case SNDRV_PCM_STREAM_CAPTURE: + regmap_update_bits(afe->regmap, ETDM_IN1_CON0, mask, val); + + regmap_update_bits(afe->regmap, ETDM_IN1_CON1, mask1, val1); + + regmap_update_bits(afe->regmap, ETDM_IN1_CON3, IN_SEL_FS, + FIELD_PREP(IN_SEL_FS, etdm_rate)); + + regmap_update_bits(afe->irqs[AN7581_IRQ_1].regmap, + afe->irqs[AN7581_IRQ_1].irq_data->irq_en_reg, + AFE_IRQ_EN_SEL, AFE_IRQ_EN_SEL_I2SIN); + break; + default: + break; + } + + return 0; +} + +static int mtk_dai_etdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + unsigned int rate = params_rate(params); + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + regmap_update_bits(afe->regmap, ETDM_COWORK_CON0, + EDTM_IN1_SLAVE_SEL, + EDTM_IN1_SLAVE_FROM_ETDMIN1_SLAVE); + regmap_update_bits(afe->regmap, ETDM_COWORK_CON0, + EDTM_OUT1_SLAVE_SEL, + EDTM_OUT1_SLAVE_FROM_ETDMOUT1_SLAVE); + regmap_update_bits(afe->regmap, ETDM_COWORK_CON1, + EDTM_IN1_SDATA0_SEL, + EDTM_IN1_SDATA0_FROM_PAD); + + switch (rate) { + case 7350: + case 8000: + case 11025: + case 12000: + case 14700: + case 16000: + case 22050: + case 24000: + case 29400: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + case 352800: + case 384000: + return mtk_dai_etdm_config(afe, params, dai, substream->stream); + default: + dev_err(afe->dev, "Sample rate %d invalid\n", rate); + return -EINVAL; + } +} + +static int mtk_dai_etdm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + + dev_dbg(afe->dev, "%s(), cmd %d, dai id %d\n", __func__, cmd, dai->id); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_set_bits(afe->regmap, ETDM_OUT1_CON0, + ETDM_OUT_EN); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + regmap_set_bits(afe->regmap, ETDM_IN1_CON0, + ETDM_IN_EN); + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_clear_bits(afe->regmap, ETDM_OUT1_CON0, + ETDM_OUT_EN); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + regmap_clear_bits(afe->regmap, ETDM_IN1_CON0, + ETDM_IN_EN); + + break; + default: + break; + } + + return 0; +} + +static int mtk_dai_etdm_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); + struct an7581_afe_private *afe_priv = afe->platform_priv; + struct mtk_dai_etdm_priv *etdm_data; + void *priv_data; + + priv_data = devm_kzalloc(afe->dev, sizeof(struct mtk_dai_etdm_priv), + GFP_KERNEL); + if (!priv_data) + return -ENOMEM; + + afe_priv->dai_priv[dai->id] = priv_data; + etdm_data = afe_priv->dai_priv[dai->id]; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + etdm_data->format = MTK_DAI_ETDM_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + etdm_data->format = MTK_DAI_ETDM_FORMAT_DSPA; + break; + case SND_SOC_DAIFMT_DSP_B: + etdm_data->format = MTK_DAI_ETDM_FORMAT_DSPB; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + etdm_data->bck_inv = false; + etdm_data->lrck_inv = false; + break; + case SND_SOC_DAIFMT_NB_IF: + etdm_data->bck_inv = false; + etdm_data->lrck_inv = true; + break; + case SND_SOC_DAIFMT_IB_NF: + etdm_data->bck_inv = true; + etdm_data->lrck_inv = false; + break; + case SND_SOC_DAIFMT_IB_IF: + etdm_data->bck_inv = true; + etdm_data->lrck_inv = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: + etdm_data->slave_mode = false; + break; + case SND_SOC_DAIFMT_BC_FC: + etdm_data->slave_mode = true; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops mtk_dai_etdm_ops = { + .startup = mtk_dai_etdm_startup, + .shutdown = mtk_dai_etdm_shutdown, + .hw_params = mtk_dai_etdm_hw_params, + .trigger = mtk_dai_etdm_trigger, + .set_fmt = mtk_dai_etdm_set_fmt, +}; + +/* dai driver */ +#define MTK_ETDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mtk_dai_etdm_driver[] = { + { + .name = "ETDM", + .id = AN7581_DAI_ETDM, + .capture = { + .stream_name = "ETDM Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = MTK_ETDM_FORMATS, + }, + .playback = { + .stream_name = "ETDM Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = MTK_ETDM_FORMATS, + }, + .ops = &mtk_dai_etdm_ops, + .symmetric_rate = 1, + .symmetric_sample_bits = 1, + }, +}; + +int an7581_dai_etdm_register(struct mtk_base_afe *afe) +{ + struct mtk_base_afe_dai *dai; + + dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + list_add(&dai->list, &afe->sub_dais); + + dai->dai_drivers = mtk_dai_etdm_driver; + dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_etdm_driver); + + dai->dapm_widgets = mtk_dai_etdm_widgets; + dai->num_dapm_widgets = ARRAY_SIZE(mtk_dai_etdm_widgets); + dai->dapm_routes = mtk_dai_etdm_routes; + dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_etdm_routes); + + return 0; +} diff --git a/sound/soc/mediatek/an7581/an7581-reg.h b/sound/soc/mediatek/an7581/an7581-reg.h new file mode 100644 index 000000000000..a6ada3ca97ab --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-reg.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * an7581-reg.h -- Airoha AN7581 audio driver reg definition + */ + +#ifndef _AN7581_REG_H_ +#define _AN7581_REG_H_ + +#define AFE_DAC_CON0 0x0 +#define AFE_DL1_ENABLE_SHIFT 17 +#define AFE_UL1_ENABLE_SHIFT 1 +#define AFE_AFE_ENABLE_SHIFT 0 + +#define ETDM_COWORK_CON0 0x4c +#define EDTM_IN1_SLAVE_SEL GENMASK(27, 24) +#define EDTM_IN1_SLAVE_FROM_ETDMIN1_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x1) +#define EDTM_IN1_SLAVE_FROM_ETDMIN2_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x2) +#define EDTM_IN1_SLAVE_FROM_ETDMIN2_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x3) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT1_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x8) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT1_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0x9) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT2_MASTER FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0xa) +#define EDTM_IN1_SLAVE_FROM_ETDMOUT2_SLAVE FIELD_PREP_CONST(EDTM_IN1_SLAVE_SEL, 0xb) +#define EDTM_OUT1_SLAVE_SEL GENMASK(11, 8) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN1_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x0) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN1_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x1) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN2_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x2) +#define EDTM_OUT1_SLAVE_FROM_ETDMIN2_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x3) +#define EDTM_OUT1_SLAVE_FROM_ETDMOUT1_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0x9) +#define EDTM_OUT1_SLAVE_FROM_ETDMOUT2_MASTER FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0xa) +#define EDTM_OUT1_SLAVE_FROM_ETDMOUT2_SLAVE FIELD_PREP_CONST(EDTM_OUT1_SLAVE_SEL, 0xb) +#define ETDM_COWORK_CON1 0x50 +#define EDTM_IN1_SDATA0_SEL GENMASK(3, 0) +#define EDTM_IN1_SDATA0_FROM_PAD FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x0) +#define EDTM_IN1_SDATA0_FROM_ETDMIN2 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x2) +#define EDTM_IN1_SDATA0_FROM_ETDMOUT1 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0x8) +#define EDTM_IN1_SDATA0_FROM_ETDMOUT2 FIELD_PREP_CONST(EDTM_IN1_SDATA0_SEL, 0xa) +#define ETDM_IN1_CON0 0x5c +#define ETDM_CH_NUM GENMASK(26, 23) +#define ETDM_WRD_LEN GENMASK(20, 16) +#define ETDM_BIT_LEN GENMASK(15, 11) +#define ETDM_FMT GENMASK(8, 6) +#define ETDM_SLAVE_MODE BIT(5) +#define ETDM_IN_EN BIT(0) +#define ETDM_IN1_CON1 0x60 +#define EDTM_LRCK_AUTO_MODE BIT(29) +#define EDTM_DIRECT_INPUT_MASTER_BCK BIT(30) +#define EDTM_CKEN_SEL BIT(12) +#define EDTM_LRCK_AUTO_OFF BIT(10) +#define EDTM_INITIAL_POINT GENMASK(9, 5) +#define EDTM_INITIAL_COUNT GENMASK(4, 0) +#define ETDM_IN1_CON2 0x64 +#define IN_CLK_SRC GENMASK(12, 10) +#define ETDM_IN1_CON3 0x68 +#define IN_SEL_FS GENMASK(30, 26) +#define ETDM_IN1_CON4 0x6c +#define IN_RELATCH GENMASK(24, 20) +#define IN_CLK_INV BIT(18) +#define ETDM_IN1_CON5 0x70 +#define ETDM_IN1_CON6 0x74 +#define ETDM_OUT1_CON0 0x7c +#define ETDM_SYNC_MODE BIT(1) +#define ETDM_OUT_EN BIT(0) +#define ETDM_OUT1_CON1 0x80 +#define ETDM_OUT1_CON2 0x84 +#define ETDM_OUT1_CON3 0x88 +#define ETDM_OUT1_CON4 0x8c +#define OUT_RELATCH GENMASK(28, 24) +#define OUT_CLK_SRC GENMASK(8, 6) +#define OUT_SEL_FS GENMASK(4, 0) +#define ETDM_OUT1_CON5 0x90 +#define ETDM_CLK_DIV BIT(12) +#define OUT_CLK_INV BIT(9) +#define ETDM_OUT1_CON6 0x94 +#define ETDM_OUT1_CON7 0x98 + +#define AFE_DL1_BASE 0xa8 +#define AFE_DL1_END 0xb0 +#define AFE_DL1_CUR 0xac +#define AFE_DL1_CON0 0xb4 +#define AFE_PBUF_SIZE_SHIFT 16 +#define AFE_PBUF_SIZE_MASK GENMASK(1, 0) +#define AFE_MINLEN_SHIFT 8 +#define AFE_MINLEN_MASK GENMASK(3, 0) +#define AFE_HD_SHIFT 5 + +#define AFE_UL1_BASE 0xc4 +#define AFE_UL1_END 0xc8 +#define AFE_UL1_CUR 0xcc +#define AFE_UL1_CON0 0xd0 +#define AFE_HD_ALIGN_SHIFT 6 + +#define AFE_IRQ_CON0 0xe4 +#define AFE_IRQ_EN_SEL BIT(4) +#define AFE_IRQ_EN_SEL_I2SIN FIELD_PREP_CONST(AFE_IRQ_EN_SEL, 0x0) +#define AFE_IRQ_EN_SEL_I2SOUT FIELD_PREP_CONST(AFE_IRQ_EN_SEL, 0x1) +#define AFE_IRQ_MISS_FLG_CLR_SHIFT 3 +#define AFE_IRQ_CLR_SHIFT 2 +#define AFE_IRQ_ON_SHIFT 0 +#define AFE_IRQ_CNT 0xe8 +#define AFE_IRQ_CNT_SHIFT 0 +#define AFE_IRQ_CNT_MASK GENMASK(31, 0) + +#define AFE_IRQ_STS 0xf8 +#define AFE_IRQ_STS_PLAY BIT(1) +#define AFE_IRQ_STS_RECORD BIT(0) + +#define AFE_MAX_REGISTER AFE_IRQ_STS + +#define AFE_IRQ1_CON0 0x0 +#define AFE_IRQ1_CNT 0x4 + +#define AFE_IRQ1_MAX_REGISTER AFE_IRQ1_CNT + +#endif diff --git a/sound/soc/mediatek/an7581/an7581-wm8960.c b/sound/soc/mediatek/an7581/an7581-wm8960.c new file mode 100644 index 000000000000..03ea5732563c --- /dev/null +++ b/sound/soc/mediatek/an7581/an7581-wm8960.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha ALSA SoC I2S platform driver for AN7581 + * + */ + +#include +#include + +#include "an7581-afe-common.h" + +SND_SOC_DAILINK_DEFS(playback, + DAILINK_COMP_ARRAY(COMP_CPU("DL1")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY(), COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(capture, + DAILINK_COMP_ARRAY(COMP_CPU("UL1")), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY(), COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(codec, + DAILINK_COMP_ARRAY(COMP_CPU("ETDM")), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY(), COMP_EMPTY())); + +static struct snd_soc_dai_link an7581_wm8960_dai_links[] = { + /* FE */ + { + .name = "wm8960-playback", + .stream_name = "wm8960-playback", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .playback_only = 1, + SND_SOC_DAILINK_REG(playback), + }, + { + .name = "wm8960-capture", + .stream_name = "wm8960-capture", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .capture_only = 1, + SND_SOC_DAILINK_REG(capture), + }, + /* BE */ + { + .name = "wm8960-codec", + .no_pcm = 1, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBP_CFP | + SND_SOC_DAIFMT_GATED, + SND_SOC_DAILINK_REG(codec), + }, +}; + +static struct snd_soc_card an7581_wm8960_card = { + .name = "an7581-wm8960", + .owner = THIS_MODULE, + .dai_link = an7581_wm8960_dai_links, + .num_links = ARRAY_SIZE(an7581_wm8960_dai_links), +}; + +static int an7581_wm8960_machine_probe(struct platform_device *pdev) +{ + struct device_node *platform_dai_node, *codec_dai_node; + struct snd_soc_card *card = &an7581_wm8960_card; + struct device_node *of_platform, *codec; + struct snd_soc_dai_link *dai_link; + int i, d, ret; + + card->dev = &pdev->dev; + + of_platform = of_get_child_by_name(pdev->dev.of_node, "platform"); + + if (of_platform) { + platform_dai_node = of_parse_phandle(of_platform, "sound-dai", 0); + of_node_put(of_platform); + + if (!platform_dai_node) { + dev_err(&pdev->dev, "Failed to parse platform/sound-dai property\n"); + return -EINVAL; + } + } else { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + struct snd_soc_dai_link_component *platform; + + dai_link->num_platforms = 2; + for_each_link_platforms(dai_link, d, platform) { + if (platform->name) + continue; + platform->of_node = platform_dai_node; + } + } + + codec = of_get_child_by_name(pdev->dev.of_node, "codec"); + + if (codec) { + codec_dai_node = of_parse_phandle(codec, "sound-dai", 0); + of_node_put(codec); + + if (!codec_dai_node) { + of_node_put(platform_dai_node); + dev_err(&pdev->dev, "Failed to parse codec/sound-dai property\n"); + return -EINVAL; + } + } else { + of_node_put(platform_dai_node); + dev_err(&pdev->dev, "Property 'codec' missing or invalid\n"); + return -EINVAL; + } + + for_each_card_prelinks(card, i, dai_link) { + if (dai_link->codecs->name) + continue; + dai_link->codecs->of_node = codec_dai_node; + } + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "Failed to parse audio-routing: %d\n", ret); + goto err_of_node_put; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err_probe(&pdev->dev, ret, "%s snd_soc_register_card fail\n", __func__); + goto err_of_node_put; + } + + return 0; + +err_of_node_put: + of_node_put(platform_dai_node); + of_node_put(codec_dai_node); + return ret; +} + +static const struct of_device_id an7581_wm8960_machine_dt_match[] = { + { .compatible = "airoha,an7581-wm8960-sound" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an7581_wm8960_machine_dt_match); + +static struct platform_driver an7581_wm8960_driver = { + .driver = { + .name = "an7581-wm8960", + .of_match_table = an7581_wm8960_machine_dt_match, + }, + .probe = an7581_wm8960_machine_probe, +}; +module_platform_driver(an7581_wm8960_driver); + +MODULE_DESCRIPTION("Airoha SoC I2S platform driver for ALSA AN7581"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c index 2a20fa5dba49..ec99bc4a5d5e 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -75,8 +75,10 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } /* dynamic allocate irq to memif */ if (memif->irq_usage < 0) { @@ -88,10 +90,10 @@ int mtk_afe_fe_startup(struct snd_pcm_substream *substream, } else { dev_err(afe->dev, "%s() error: no more asys irq\n", __func__); - ret = -EBUSY; + return -EBUSY; } } - return ret; + return 0; } EXPORT_SYMBOL_GPL(mtk_afe_fe_startup); @@ -204,11 +206,15 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, struct mtk_base_afe_irq *irqs = &afe->irqs[memif->irq_usage]; const struct mtk_base_irq_data *irq_data = irqs->irq_data; unsigned int counter = runtime->period_size; + struct regmap *regmap = afe->regmap; int fs; int ret; dev_dbg(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd); + if (irqs->regmap) + regmap = irqs->regmap; + switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: @@ -220,7 +226,7 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, } /* set irq counter */ - mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_cnt_reg, irq_data->irq_cnt_maskbit, counter, irq_data->irq_cnt_shift); @@ -230,12 +236,12 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, if (fs < 0) return -EINVAL; - mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_fs_reg, irq_data->irq_fs_maskbit, fs, irq_data->irq_fs_shift); /* enable interrupt */ - mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_en_reg, 1, 1, irq_data->irq_en_shift); return 0; @@ -248,10 +254,10 @@ int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, } /* disable interrupt */ - mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + mtk_regmap_update_bits(regmap, irq_data->irq_en_reg, 1, 0, irq_data->irq_en_shift); /* and clear pending IRQ */ - mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg, + mtk_regmap_write(regmap, irq_data->irq_clr_reg, 1 << irq_data->irq_clr_shift); return ret; default: diff --git a/sound/soc/mediatek/common/mtk-base-afe.h b/sound/soc/mediatek/common/mtk-base-afe.h index a406f2e3e7a8..b5300c9ed415 100644 --- a/sound/soc/mediatek/common/mtk-base-afe.h +++ b/sound/soc/mediatek/common/mtk-base-afe.h @@ -153,6 +153,8 @@ struct mtk_base_afe_memif { struct mtk_base_afe_irq { const struct mtk_base_irq_data *irq_data; int irq_occupyed; + + struct regmap *regmap; }; struct mtk_base_afe_dai { diff --git a/sound/soc/mediatek/common/mtk-soundcard-driver.c b/sound/soc/mediatek/common/mtk-soundcard-driver.c index 2d39ff23f854..a84f4ab4488b 100644 --- a/sound/soc/mediatek/common/mtk-soundcard-driver.c +++ b/sound/soc/mediatek/common/mtk-soundcard-driver.c @@ -129,6 +129,11 @@ void clean_card_reference(struct snd_soc_card *card) } EXPORT_SYMBOL_GPL(clean_card_reference); +static void mtk_soundcard_put_card_reference(void *data) +{ + clean_card_reference(data); +} + int mtk_soundcard_startup(struct snd_pcm_substream *substream, enum mtk_pcm_constraint_type ctype) { @@ -180,6 +185,43 @@ const struct snd_soc_ops mtk_soundcard_common_capture_ops = { }; EXPORT_SYMBOL_GPL(mtk_soundcard_common_capture_ops); +struct mtk_soundcard_name { + struct snd_soc_card *card; + const char *name; +}; + +static void mtk_soundcard_restore_name(void *data) +{ + struct mtk_soundcard_name *saved = data; + + saved->card->name = saved->name; + saved->card->topology_shortname = NULL; +} + +/* + * The card is static and outlives this probe, but the topology name is + * devm-allocated. Restore the original name when the probe's resources are + * released, so that a deferred or failed probe does not leave the card name + * pointing to freed memory for the next one. + */ +static int mtk_soundcard_set_topology_name(struct device *dev, + struct snd_soc_card *card) +{ + struct mtk_soundcard_name *saved; + + saved = devm_kzalloc(dev, sizeof(*saved), GFP_KERNEL); + if (!saved) + return -ENOMEM; + + saved->card = card; + saved->name = card->name; + card->topology_shortname = NULL; + + snd_soc_card_set_topology_name(card, "sof"); + + return devm_add_action_or_reset(dev, mtk_soundcard_restore_name, saved); +} + int mtk_soundcard_common_probe(struct platform_device *pdev) { struct device_node *platform_node, *adsp_node, *accdet_node; @@ -287,7 +329,12 @@ int mtk_soundcard_common_probe(struct platform_device *pdev) card->probe = mtk_sof_card_probe; card->late_probe = mtk_sof_card_late_probe; - snd_soc_card_set_topology_name(card, "sof"); + ret = mtk_soundcard_set_topology_name(&pdev->dev, card); + if (ret) { + of_node_put(adsp_node); + of_node_put(platform_node); + return ret; + } } /* @@ -325,8 +372,22 @@ int mtk_soundcard_common_probe(struct platform_device *pdev) ret = devm_snd_soc_register_card(&pdev->dev, card); - if (!needs_legacy_probe) - clean_card_reference(card); + /* + * When a component is still missing, snd_soc_bind_card() queues the + * card for a later rebind and returns success, so the card is not + * instantiated yet. That rebind reuses this same dai_link array, so + * the codec references parsed from the devicetree must be kept until + * the device is unbound: dropping them leaves both name and of_node + * unset and the rebind fails the dai link sanity check instead. + */ + if (!needs_legacy_probe) { + if (ret || snd_soc_card_is_instantiated(card)) + clean_card_reference(card); + else + ret = devm_add_action_or_reset(&pdev->dev, + mtk_soundcard_put_card_reference, + card); + } if (ret) { dev_err_probe(&pdev->dev, ret, "Cannot register card\n"); diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c index d217f9320ad2..1cf66ca44a33 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c @@ -30,10 +30,9 @@ int mt2701_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT2701_BASE_CLK_NUM; i++) { afe_priv->base_ck[i] = devm_clk_get(afe->dev, base_clks[i]); - if (IS_ERR(afe_priv->base_ck[i])) { - dev_err(afe->dev, "failed to get %s\n", base_clks[i]); - return PTR_ERR(afe_priv->base_ck[i]); - } + if (IS_ERR(afe_priv->base_ck[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->base_ck[i]), + "failed to get %s\n", base_clks[i]); } i2s_num = min(afe_priv->soc->i2s_num, MT2701_BASE_CLK_NUM); @@ -45,57 +44,49 @@ int mt2701_init_clock(struct mtk_base_afe *afe) snprintf(name, sizeof(name), "i2s%d_src_sel", i); i2s_path->sel_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_path->sel_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_path->sel_ck); - } + if (IS_ERR(i2s_path->sel_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_path->sel_ck), + "failed to get %s\n", name); snprintf(name, sizeof(name), "i2s%d_src_div", i); i2s_path->div_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_path->div_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_path->div_ck); - } + if (IS_ERR(i2s_path->div_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_path->div_ck), + "failed to get %s\n", name); snprintf(name, sizeof(name), "i2s%d_mclk_en", i); i2s_path->mclk_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_path->mclk_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_path->mclk_ck); - } + if (IS_ERR(i2s_path->mclk_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_path->mclk_ck), + "failed to get %s\n", name); snprintf(name, sizeof(name), "i2so%d_hop_ck", i); i2s_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_ck); - } + if (IS_ERR(i2s_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_ck), + "failed to get %s\n", name); + i2s_path->hop_ck[SNDRV_PCM_STREAM_PLAYBACK] = i2s_ck; snprintf(name, sizeof(name), "i2si%d_hop_ck", i); i2s_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_ck); - } + if (IS_ERR(i2s_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_ck), + "failed to get %s\n", name); + i2s_path->hop_ck[SNDRV_PCM_STREAM_CAPTURE] = i2s_ck; snprintf(name, sizeof(name), "asrc%d_out_ck", i); i2s_path->asrco_ck = devm_clk_get(afe->dev, name); - if (IS_ERR(i2s_path->asrco_ck)) { - dev_err(afe->dev, "failed to get %s\n", name); - return PTR_ERR(i2s_path->asrco_ck); - } + if (IS_ERR(i2s_path->asrco_ck)) + return dev_err_probe(afe->dev, PTR_ERR(i2s_path->asrco_ck), + "failed to get %s\n", name); } /* Some platforms may support BT path */ - afe_priv->mrgif_ck = devm_clk_get(afe->dev, "audio_mrgif_pd"); - if (IS_ERR(afe_priv->mrgif_ck)) { - if (PTR_ERR(afe_priv->mrgif_ck) == -EPROBE_DEFER) - return -EPROBE_DEFER; - - afe_priv->mrgif_ck = NULL; - } + afe_priv->mrgif_ck = devm_clk_get_optional(afe->dev, "audio_mrgif_pd"); + if (IS_ERR(afe_priv->mrgif_ck)) + return PTR_ERR(afe_priv->mrgif_ck); /* * Optional HDMI audio clocks. Platforms that do not wire up the diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c index d56b498e8c0c..3c5a76591ea7 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -1622,10 +1622,8 @@ static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt2701_asys_isr, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq for asys-isr\n"); + if (ret) return ret; - } afe->regmap = syscon_node_to_regmap(dev->parent->of_node); if (IS_ERR(afe->regmap)) { diff --git a/sound/soc/mediatek/mt2701/mt2701-cs42448.c b/sound/soc/mediatek/mt2701/mt2701-cs42448.c index 778a9dccfcaa..af833a8daa2a 100644 --- a/sound/soc/mediatek/mt2701/mt2701-cs42448.c +++ b/sound/soc/mediatek/mt2701/mt2701-cs42448.c @@ -366,10 +366,8 @@ static int mt2701_cs42448_machine_probe(struct platform_device *pdev) = codec_node_bt_mrg; ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); - if (ret) { - dev_err(dev, "failed to parse audio-routing: %d\n", ret); + if (ret) return ret; - } priv->i2s1_in_mux_sel_1 = devm_gpiod_get_optional(dev, "i2s1-in-sel-gpio1", GPIOD_OUT_LOW); diff --git a/sound/soc/mediatek/mt2701/mt2701-wm8960.c b/sound/soc/mediatek/mt2701/mt2701-wm8960.c index 84b3d6cd77a5..25ea3e274aa8 100644 --- a/sound/soc/mediatek/mt2701/mt2701-wm8960.c +++ b/sound/soc/mediatek/mt2701/mt2701-wm8960.c @@ -137,10 +137,8 @@ static int mt2701_wm8960_machine_probe(struct platform_device *pdev) } ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); - if (ret) { - dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + if (ret) goto put_codec_node; - } ret = devm_snd_soc_register_card(&pdev->dev, card); if (ret) diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-clk.c b/sound/soc/mediatek/mt6797/mt6797-afe-clk.c index 6f3e6acfcfab..2326496f8b27 100644 --- a/sound/soc/mediatek/mt6797/mt6797-afe-clk.c +++ b/sound/soc/mediatek/mt6797/mt6797-afe-clk.c @@ -43,12 +43,9 @@ int mt6797_init_clock(struct mtk_base_afe *afe) for (i = 0; i < CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } return 0; @@ -63,21 +60,21 @@ int mt6797_afe_enable_clock(struct mtk_base_afe *afe) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_SYS_AUD], ret); - goto CLK_INFRA_SYS_AUDIO_ERR; + return ret; } ret = clk_prepare_enable(afe_priv->clk[CLK_INFRA_SYS_AUD_26M]); if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_SYS_AUD_26M], ret); - goto CLK_INFRA_SYS_AUD_26M_ERR; + goto CLK_INFRA_SYS_AUDIO_ERR; } ret = clk_prepare_enable(afe_priv->clk[CLK_TOP_MUX_AUD]); if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD], ret); - goto CLK_MUX_AUDIO_ERR; + goto CLK_INFRA_SYS_AUD_26M_ERR; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD], @@ -93,13 +90,11 @@ int mt6797_afe_enable_clock(struct mtk_base_afe *afe) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_BUS], ret); - goto CLK_MUX_AUDIO_INTBUS_ERR; + goto CLK_MUX_AUDIO_ERR; } - return ret; + return 0; -CLK_MUX_AUDIO_INTBUS_ERR: - clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_BUS]); CLK_MUX_AUDIO_ERR: clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD]); CLK_INFRA_SYS_AUD_26M_ERR: @@ -107,7 +102,7 @@ int mt6797_afe_enable_clock(struct mtk_base_afe *afe) CLK_INFRA_SYS_AUDIO_ERR: clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUD]); - return 0; + return ret; } int mt6797_afe_disable_clock(struct mtk_base_afe *afe) diff --git a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c index f62a32f2f2b6..dfe67727fe47 100644 --- a/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c +++ b/sound/soc/mediatek/mt6797/mt6797-afe-pcm.c @@ -754,10 +754,8 @@ static int mt6797_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt6797_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error\n"); + if (ret) return ret; - } /* regmap init */ afe->base_addr = devm_platform_ioremap_resource(pdev, 0); @@ -800,10 +798,8 @@ static int mt6797_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt6797_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq for asys-isr\n"); + if (ret) return ret; - } /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); diff --git a/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c b/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c index 7a6ad9116e55..e7ecb2761b4d 100644 --- a/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c +++ b/sound/soc/mediatek/mt7986/mt7986-afe-pcm.c @@ -334,7 +334,7 @@ static int mt7986_init_clock(struct mtk_base_afe *afe) ret = devm_clk_bulk_get(afe->dev, afe_priv->num_clks, afe_priv->clks); if (ret) - return dev_err_probe(afe->dev, ret, "Failed to get clocks\n"); + return ret; return 0; } @@ -414,7 +414,7 @@ static int mt7986_afe_runtime_resume(struct device *dev) ret = clk_bulk_prepare_enable(afe_priv->num_clks, afe_priv->clks); if (ret) - return dev_err_probe(afe->dev, ret, "Failed to enable clocks\n"); + return ret; if (!afe->regmap || afe_priv->pm_runtime_bypass_reg_ctl) return 0; @@ -484,7 +484,7 @@ static int mt7986_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt7986_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "Cannot initialize clocks\n"); + return ret; ret = devm_pm_runtime_enable(dev); if (ret) @@ -535,7 +535,7 @@ static int mt7986_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt7986_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "Failed to request irq for asys-isr\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); diff --git a/sound/soc/mediatek/mt7986/mt7986-dai-etdm.c b/sound/soc/mediatek/mt7986/mt7986-dai-etdm.c index fc55ff47b7bc..49f5b2bf565f 100644 --- a/sound/soc/mediatek/mt7986/mt7986-dai-etdm.c +++ b/sound/soc/mediatek/mt7986/mt7986-dai-etdm.c @@ -124,7 +124,7 @@ static int mtk_dai_etdm_startup(struct snd_pcm_substream *substream, ret = clk_bulk_prepare_enable(afe_priv->num_clks, afe_priv->clks); if (ret) - return dev_err_probe(afe->dev, ret, "Failed to enable clocks\n"); + return ret; regmap_update_bits(afe->regmap, AUDIO_TOP_CON2, CLK_OUT5_PDN_MASK, 0); regmap_update_bits(afe->regmap, AUDIO_TOP_CON2, CLK_IN5_PDN_MASK, 0); diff --git a/sound/soc/mediatek/mt7986/mt7986-wm8960.c b/sound/soc/mediatek/mt7986/mt7986-wm8960.c index f1dc18222be7..4517af9e2d14 100644 --- a/sound/soc/mediatek/mt7986/mt7986-wm8960.c +++ b/sound/soc/mediatek/mt7986/mt7986-wm8960.c @@ -135,10 +135,8 @@ static int mt7986_wm8960_machine_probe(struct platform_device *pdev) } ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); - if (ret) { - dev_err(&pdev->dev, "Failed to parse audio-routing: %d\n", ret); + if (ret) goto err_of_node_put; - } ret = devm_snd_soc_register_card(&pdev->dev, card); if (ret) { diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index 69cadc91c97f..c95699f84191 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -997,10 +997,10 @@ static int mt8173_afe_runtime_resume(struct device *dev) goto err_bck0; ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S1_M]); if (ret) - goto err_i2s1_m; + goto err_bck1; ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S2_M]); if (ret) - goto err_i2s2_m; + goto err_i2s1_m; /* enable AFE clk */ regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_AFE, 0); @@ -1018,8 +1018,8 @@ static int mt8173_afe_runtime_resume(struct device *dev) err_i2s1_m: clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]); -err_i2s2_m: - clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]); +err_bck1: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK1]); err_bck0: clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]); err_top_aud: @@ -1038,11 +1038,9 @@ static int mt8173_afe_init_audio_clk(struct mtk_base_afe *afe) for (i = 0; i < ARRAY_SIZE(aud_clks); i++) { afe_priv->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clocks[i])) { - dev_err(afe->dev, "%s devm_clk_get %s fail\n", - __func__, aud_clks[i]); - return PTR_ERR(afe_priv->clocks[i]); - } + if (IS_ERR(afe_priv->clocks[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clocks[i]), + "failed to get clock %s\n", aud_clks[i]); } clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK0], 22579200); /* 22M */ clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK1], 24576000); /* 24M */ @@ -1072,7 +1070,7 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) afe->dev = dev; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_info(dev, "no reserved memory found, pre-allocating buffers instead\n"); afe->preallocate_buffers = true; @@ -1093,10 +1091,8 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt8173_afe_init_audio_clk(afe); - if (ret) { - dev_err(dev, "mt8173_afe_init_audio_clk fail\n"); + if (ret) return ret; - } /* memif % irq initialize*/ afe->memif_size = MT8173_AFE_MEMIF_NUM; @@ -1156,10 +1152,8 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8173_afe_irq_handler, 0, "Afe_ISR_Handle", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq\n"); + if (ret) goto err_cleanup_components; - } dev_info(dev, "MT8173 AFE driver initialized.\n"); return 0; diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c index cc4f8f4d3dab..40f9afcdaa16 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-clk.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-clk.c @@ -101,12 +101,9 @@ int mt8183_init_clock(struct mtk_base_afe *afe) for (i = 0; i < CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } return 0; @@ -137,7 +134,7 @@ int mt8183_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], aud_clks[CLK_CLK26M], ret); - goto CLK_MUX_AUDIO_ERR; + goto CLK_MUX_AUDIO_INTBUS_ERR; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); @@ -153,7 +150,7 @@ int mt8183_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIOINTBUS], aud_clks[CLK_TOP_SYSPLL_D2_D4], ret); - goto CLK_MUX_AUDIO_INTBUS_ERR; + goto CLK_AFE_ERR; } ret = clk_prepare_enable(afe_priv->clk[CLK_AFE]); @@ -271,7 +268,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); @@ -281,7 +277,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); } @@ -298,7 +293,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) afe_priv->clk[CLK_CLK26M]); clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); ERR_ENABLE_CLK_TOP_MUX_AUD_1: -EXIT: return ret; } @@ -345,7 +339,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); @@ -355,7 +348,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_CLK26M], ret); - goto EXIT; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); } @@ -372,7 +364,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) afe_priv->clk[CLK_CLK26M]); clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); ERR_ENABLE_CLK_TOP_MUX_AUD_2: -EXIT: return ret; } @@ -382,7 +373,9 @@ int mt8183_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL22M]); if (ret) { @@ -411,6 +404,8 @@ int mt8183_apll1_enable(struct mtk_base_afe *afe) ERR_CLK_APLL1_TUNER: clk_disable_unprepare(afe_priv->clk[CLK_APLL22M]); ERR_CLK_APLL22M: + apll1_mux_setting(afe, false); + return ret; } @@ -436,7 +431,9 @@ int mt8183_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL24M]); if (ret) { @@ -465,6 +462,8 @@ int mt8183_apll2_enable(struct mtk_base_afe *afe) ERR_CLK_APLL2_TUNER: clk_disable_unprepare(afe_priv->clk[CLK_APLL24M]); ERR_CLK_APLL24M: + apll2_mux_setting(afe, false); + return ret; } diff --git a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c index 2634699534db..d8938542b828 100644 --- a/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c +++ b/sound/soc/mediatek/mt8183/mt8183-afe-pcm.c @@ -766,11 +766,6 @@ static const dai_register_cb dai_register_cbs[] = { mt8183_dai_memif_register, }; -static void mt8183_afe_release_reserved_mem(void *data) -{ - of_reserved_mem_device_release(data); -} - static int mt8183_afe_pcm_dev_probe(struct platform_device *pdev) { struct mtk_base_afe *afe; @@ -795,24 +790,16 @@ static int mt8183_afe_pcm_dev_probe(struct platform_device *pdev) afe_priv = afe->platform_priv; afe->dev = dev; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_info(dev, "no reserved memory found, pre-allocating buffers instead\n"); afe->preallocate_buffers = true; - } else { - ret = devm_add_action_or_reset(dev, - mt8183_afe_release_reserved_mem, - dev); - if (ret) - return ret; } /* initial audio related clock */ ret = mt8183_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error\n"); + if (ret) return ret; - } pm_runtime_enable(dev); @@ -903,10 +890,8 @@ static int mt8183_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8183_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq for asys-isr\n"); + if (ret) goto err_pm_disable; - } /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c index daaca36a2d08..62b008b7caca 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-clk.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-clk.c @@ -98,7 +98,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], ret); - return ret; + goto ERR_ENABLE_CLK_TOP_MUX_AUD_1; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], afe_priv->clk[CLK_TOP_APLL1_CK]); @@ -106,7 +106,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_TOP_APLL1_CK], ret); - return ret; + goto ERR_SELECT_CLK_TOP_MUX_AUD_1; } /* 180.6336 / 8 = 22.5792MHz */ @@ -114,7 +114,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], ret); - return ret; + goto ERR_ENABLE_CLK_TOP_MUX_AUD_ENG1; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], afe_priv->clk[CLK_TOP_APLL1_D8]); @@ -122,7 +122,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_TOP_APLL1_D8], ret); - return ret; + goto ERR_SELECT_CLK_TOP_MUX_AUD_ENG1; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], @@ -131,7 +131,6 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_CLK26M], ret); - return ret; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); @@ -141,12 +140,23 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_CLK26M], ret); - return ret; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); } return 0; + +ERR_SELECT_CLK_TOP_MUX_AUD_ENG1: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], + afe_priv->clk[CLK_CLK26M]); + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); +ERR_ENABLE_CLK_TOP_MUX_AUD_ENG1: +ERR_SELECT_CLK_TOP_MUX_AUD_1: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], + afe_priv->clk[CLK_CLK26M]); + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); +ERR_ENABLE_CLK_TOP_MUX_AUD_1: + return ret; } static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) @@ -159,7 +169,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], ret); - return ret; + goto ERR_ENABLE_CLK_TOP_MUX_AUD_2; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], afe_priv->clk[CLK_TOP_APLL2_CK]); @@ -167,7 +177,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_TOP_APLL2_CK], ret); - return ret; + goto ERR_SELECT_CLK_TOP_MUX_AUD_2; } /* 196.608 / 8 = 24.576MHz */ @@ -175,7 +185,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], ret); - return ret; + goto ERR_ENABLE_CLK_TOP_MUX_AUD_ENG2; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], afe_priv->clk[CLK_TOP_APLL2_D8]); @@ -183,7 +193,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_TOP_APLL2_D8], ret); - return ret; + goto ERR_SELECT_CLK_TOP_MUX_AUD_ENG2; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], @@ -192,7 +202,6 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_CLK26M], ret); - return ret; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); @@ -202,12 +211,23 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_CLK26M], ret); - return ret; } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); } return 0; + +ERR_SELECT_CLK_TOP_MUX_AUD_ENG2: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], + afe_priv->clk[CLK_CLK26M]); + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); +ERR_ENABLE_CLK_TOP_MUX_AUD_ENG2: +ERR_SELECT_CLK_TOP_MUX_AUD_2: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], + afe_priv->clk[CLK_CLK26M]); + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); +ERR_ENABLE_CLK_TOP_MUX_AUD_2: + return ret; } int mt8186_afe_enable_cgs(struct mtk_base_afe *afe) @@ -268,7 +288,7 @@ int mt8186_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], aud_clks[CLK_CLK26M], ret); - goto clk_mux_audio_err; + goto clk_mux_audio_intbus_err; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); @@ -295,24 +315,22 @@ int mt8186_afe_enable_clock(struct mtk_base_afe *afe) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_AFE], ret); - goto clk_afe_err; + goto clk_mux_audio_h_parent_err; } return 0; -clk_afe_err: - clk_disable_unprepare(afe_priv->clk[CLK_AFE]); clk_mux_audio_h_parent_err: -clk_mux_audio_intbus_parent_err: mt8186_set_audio_int_bus_parent(afe, CLK_CLK26M); -clk_mux_audio_intbus_err: +clk_mux_audio_intbus_parent_err: clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); -clk_mux_audio_err: +clk_mux_audio_intbus_err: clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIO]); -clk_infra_sys_audio_err: - clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUDIO]); -clk_infra_audio_26m_err: +clk_mux_audio_err: clk_disable_unprepare(afe_priv->clk[CLK_INFRA_AUDIO_26M]); +clk_infra_audio_26m_err: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUDIO]); +clk_infra_sys_audio_err: return ret; } @@ -335,7 +353,9 @@ int mt8186_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL22M]); if (ret) { @@ -360,9 +380,9 @@ int mt8186_apll1_enable(struct mtk_base_afe *afe) return 0; err_clk_apll1_tuner: - clk_disable_unprepare(afe_priv->clk[CLK_APLL1_TUNER]); -err_clk_apll22m: clk_disable_unprepare(afe_priv->clk[CLK_APLL22M]); +err_clk_apll22m: + apll1_mux_setting(afe, false); return ret; } @@ -388,7 +408,9 @@ int mt8186_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL24M]); if (ret) { @@ -413,9 +435,9 @@ int mt8186_apll2_enable(struct mtk_base_afe *afe) return 0; err_clk_apll2_tuner: - clk_disable_unprepare(afe_priv->clk[CLK_APLL2_TUNER]); -err_clk_apll24m: clk_disable_unprepare(afe_priv->clk[CLK_APLL24M]); +err_clk_apll24m: + apll2_mux_setting(afe, false); return ret; } @@ -498,7 +520,7 @@ int mt8186_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[m_sel_id], ret); - return ret; + goto ERR_ENABLE_MCLK; } ret = clk_set_parent(afe_priv->clk[m_sel_id], afe_priv->clk[apll_clk_id]); @@ -506,7 +528,7 @@ int mt8186_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[m_sel_id], aud_clks[apll_clk_id], ret); - return ret; + goto ERR_SELECT_MCLK; } } @@ -515,16 +537,25 @@ int mt8186_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[div_clk_id], ret); - return ret; + goto ERR_ENABLE_MCLK_DIV; } ret = clk_set_rate(afe_priv->clk[div_clk_id], rate); if (ret) { dev_err(afe->dev, "%s(), clk_set_rate %s, rate %d, fail %d\n", __func__, aud_clks[div_clk_id], rate, ret); - return ret; + goto ERR_SET_MCLK_RATE; } return 0; + +ERR_SET_MCLK_RATE: + clk_disable_unprepare(afe_priv->clk[div_clk_id]); +ERR_ENABLE_MCLK_DIV: +ERR_SELECT_MCLK: + if (m_sel_id >= 0) + clk_disable_unprepare(afe_priv->clk[m_sel_id]); +ERR_ENABLE_MCLK: + return ret; } void mt8186_mck_disable(struct mtk_base_afe *afe, int mck_id) diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-gpio.c b/sound/soc/mediatek/mt8186/mt8186-afe-gpio.c index aced8e7e920c..1cff2a63ef37 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-gpio.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-gpio.c @@ -143,6 +143,7 @@ static int mt8186_afe_gpio_adda_dl(struct device *dev, bool enable) ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MOSI_ON); if (ret) { dev_dbg(dev, "%s(), MOSI DAT ON select fail!\n", __func__); + mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MOSI_OFF); return ret; } } else { @@ -154,7 +155,8 @@ static int mt8186_afe_gpio_adda_dl(struct device *dev, bool enable) ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MOSI_OFF); if (ret) { - dev_dbg(dev, "%s(), MOSI CLK ON select fail!\n", __func__); + dev_dbg(dev, "%s(), MOSI CLK OFF select fail!\n", __func__); + mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MOSI_ON); return ret; } } @@ -176,6 +178,7 @@ static int mt8186_afe_gpio_adda_ul(struct device *dev, bool enable) ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MISO_ON); if (ret) { dev_dbg(dev, "%s(), MISO DAT ON select fail!\n", __func__); + mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MISO_OFF); return ret; } } else { @@ -188,6 +191,7 @@ static int mt8186_afe_gpio_adda_ul(struct device *dev, bool enable) ret = mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_CLK_MISO_OFF); if (ret) { dev_dbg(dev, "%s(), MISO CLK OFF select fail!\n", __func__); + mt8186_afe_gpio_select(dev, MT8186_AFE_GPIO_DAT_MISO_ON); return ret; } } diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c index 44a521c3a610..2f50f78a6f8c 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-pcm.c @@ -2743,7 +2743,11 @@ static int mt8186_afe_runtime_resume(struct device *dev) goto skip_regmap; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) { + regcache_cache_only(afe->regmap, true); + return ret; + } /* enable audio sys DCM for power saving */ regmap_update_bits(afe_priv->infracfg, PERI_BUS_DCM_CTRL, BIT(29), BIT(29)); @@ -2836,7 +2840,7 @@ static int mt8186_afe_pcm_dev_probe(struct platform_device *pdev) afe_priv = afe->platform_priv; afe->dev = &pdev->dev; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_info(dev, "no reserved memory found, pre-allocating buffers instead\n"); afe->preallocate_buffers = true; @@ -2848,10 +2852,8 @@ static int mt8186_afe_pcm_dev_probe(struct platform_device *pdev) /* init audio related clock */ ret = mt8186_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error, ret %d\n", ret); + if (ret) return ret; - } /* init memif */ afe->memif_32bit_supported = 0; @@ -2881,15 +2883,14 @@ static int mt8186_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); - if (irq_id <= 0) - return dev_err_probe(dev, irq_id < 0 ? irq_id : -ENXIO, - "no irq found"); + if (irq_id < 0) + return irq_id; ret = devm_request_irq(dev, irq_id, mt8186_afe_irq_handler, IRQF_TRIGGER_NONE, "Afe_ISR_Handle", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; ret = enable_irq_wake(irq_id); if (ret < 0) @@ -2953,10 +2954,8 @@ static int mt8186_afe_pcm_dev_probe(struct platform_device *pdev) &mt8186_afe_component, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_err(dev, "err_dai_component\n"); + if (ret) goto err_pm_disable; - } ret = pm_runtime_put_sync(dev); if (ret) { diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c index fc6cb3f0469e..8b6dca22c8ed 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.c @@ -260,15 +260,28 @@ static int mt8188_afe_enable_tuner_clk(struct mtk_base_afe *afe, unsigned int id) { struct mt8188_afe_private *afe_priv = afe->platform_priv; + int ret; switch (id) { case MT8188_AUD_PLL1: - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL1_TUNER]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL]); + if (ret) + return ret; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL1_TUNER]); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL]); + return ret; + } break; case MT8188_AUD_PLL2: - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2_TUNER]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2]); + if (ret) + return ret; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2_TUNER]); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_APLL2]); + return ret; + } break; default: return -EINVAL; @@ -416,12 +429,9 @@ int mt8188_afe_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8188_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } /* initial tuner */ @@ -430,7 +440,7 @@ int mt8188_afe_init_clock(struct mtk_base_afe *afe) if (ret) { dev_info(afe->dev, "%s(), init apll_tuner%d failed", __func__, (i + 1)); - return -EINVAL; + return ret; } } @@ -554,9 +564,7 @@ static int mt8188_afe_enable_top_cg(struct mtk_base_afe *afe, unsigned int cg_ty unsigned int mask = get_top_cg_mask(cg_type); unsigned int val = get_top_cg_on_val(cg_type); - regmap_update_bits(afe->regmap, reg, mask, val); - - return 0; + return regmap_update_bits(afe->regmap, reg, mask, val); } static int mt8188_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_type) @@ -565,30 +573,53 @@ static int mt8188_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_t unsigned int mask = get_top_cg_mask(cg_type); unsigned int val = get_top_cg_off_val(cg_type); - regmap_update_bits(afe->regmap, reg, mask, val); - - return 0; + return regmap_update_bits(afe->regmap, reg, mask, val); } int mt8188_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) { struct mt8188_afe_private *afe_priv = afe->platform_priv; - + int ret; /* bus clock for AFE external access, like DRAM */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUDIO_LOCAL_BUS_SEL]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUDIO_LOCAL_BUS_SEL]); + if (ret) + goto err_local_bus; /* bus clock for AFE internal access, like AFE SRAM */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUD_INTBUS_SEL]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUD_INTBUS_SEL]); + if (ret) + goto err_intbus; /* audio 26m clock source */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_ADSP_AUDIO_26M]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_ADSP_AUDIO_26M]); + if (ret) + goto err_26m; /* AFE hw clock */ - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_AFE]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS_HP]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_AFE]); + if (ret) + goto err_afe; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS_HP]); + if (ret) + goto err_a1sys_hp; + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + if (ret) + goto err_a1sys; return 0; + +err_a1sys: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS_HP]); +err_a1sys_hp: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_AFE]); +err_afe: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_ADSP_AUDIO_26M]); +err_26m: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUD_INTBUS_SEL]); +err_intbus: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_AUDIO_LOCAL_BUS_SEL]); +err_local_bus: + return ret; } int mt8188_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) @@ -607,14 +638,12 @@ int mt8188_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) static int mt8188_afe_enable_afe_on(struct mtk_base_afe *afe) { - regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1); - return 0; + return regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1); } static int mt8188_afe_disable_afe_on(struct mtk_base_afe *afe) { - regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x0); - return 0; + return regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x0); } static int mt8188_afe_enable_a1sys(struct mtk_base_afe *afe) @@ -626,7 +655,13 @@ static int mt8188_afe_enable_a1sys(struct mtk_base_afe *afe) if (ret) return ret; - return mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); + ret = mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + return ret; + } + + return 0; } static int mt8188_afe_disable_a1sys(struct mtk_base_afe *afe) @@ -647,7 +682,13 @@ static int mt8188_afe_enable_a2sys(struct mtk_base_afe *afe) if (ret) return ret; - return mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); + ret = mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); + if (ret) { + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A2SYS]); + return ret; + } + + return 0; } static int mt8188_afe_disable_a2sys(struct mtk_base_afe *afe) @@ -735,8 +776,18 @@ int mt8188_apll2_disable(struct mtk_base_afe *afe) int mt8188_afe_enable_main_clock(struct mtk_base_afe *afe) { - mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); - mt8188_afe_enable_afe_on(afe); + int ret; + + ret = mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); + if (ret) + return ret; + + ret = mt8188_afe_enable_afe_on(afe); + if (ret) { + mt8188_afe_disable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); + return ret; + } + return 0; } diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c index 7b1f5d05f4d6..87d6098caf99 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -318,8 +318,11 @@ static int mt8188_afe_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 1, MT8188_MEMIF_DL7_MAX_PERIOD_SIZE); - if (ret < 0) + if (ret >= 0) + ret = 0; + else dev_dbg(afe->dev, "hw_constraint_minmax failed\n"); + out: return ret; } @@ -3030,22 +3033,33 @@ static int mt8188_afe_runtime_resume(struct device *dev) struct mtk_base_afe *afe = dev_get_drvdata(dev); struct mt8188_afe_private *afe_priv = afe->platform_priv; struct arm_smccc_res res; + int ret; arm_smccc_smc(MTK_SIP_AUDIO_CONTROL, MTK_AUDIO_SMC_OP_DOMAIN_SIDEBANDS, 0, 0, 0, 0, 0, 0, &res); - mt8188_afe_enable_reg_rw_clk(afe); + ret = mt8188_afe_enable_reg_rw_clk(afe); + if (ret) + return ret; if (!afe->regmap || afe_priv->pm_runtime_bypass_reg_ctl) - goto skip_regmap; + return 0; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err; + + ret = mt8188_afe_enable_main_clock(afe); + if (ret) + goto err; - mt8188_afe_enable_main_clock(afe); -skip_regmap: return 0; +err: + mt8188_afe_disable_reg_rw_clk(afe); + regcache_cache_only(afe->regmap, true); + return ret; } static int init_memif_priv_data(struct mtk_base_afe *afe) @@ -3199,7 +3213,7 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) struct regmap *infra_ao; int i, irq_id, ret; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) dev_dbg(dev, "failed to assign memory region: %d\n", ret); @@ -3221,8 +3235,7 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) afe->base_addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(afe->base_addr)) - return dev_err_probe(dev, PTR_ERR(afe->base_addr), - "AFE base_addr not found\n"); + return PTR_ERR(afe->base_addr); infra_ao = syscon_regmap_lookup_by_phandle(dev->of_node, "mediatek,infracfg"); @@ -3258,7 +3271,7 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt8188_afe_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error"); + return ret; spin_lock_init(&afe_priv->afe_ctrl_lock); @@ -3291,12 +3304,12 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return dev_err_probe(dev, irq_id, "no irq found"); + return irq_id; ret = devm_request_irq(dev, irq_id, mt8188_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for asys-isr\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -3352,10 +3365,8 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* register component */ ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_warn(dev, "err_platform\n"); + if (ret) goto err_pm_put; - } mt8188_afe_init_registers(afe); diff --git a/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c b/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c index 972f097a13ca..9f3b3a777577 100644 --- a/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c +++ b/sound/soc/mediatek/mt8188/mt8188-audsys-clk.c @@ -170,7 +170,7 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) struct mt8188_afe_private *afe_priv = afe->platform_priv; struct clk *clk; struct clk_lookup *cl; - int i; + int i, ret; afe_priv->lookup = devm_kcalloc(afe->dev, CLK_AUD_NR_CLK, sizeof(*afe_priv->lookup), @@ -179,6 +179,10 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) if (!afe_priv->lookup) return -ENOMEM; + ret = devm_add_action_or_reset(afe->dev, mt8188_audsys_clk_unregister, afe); + if (ret) + return ret; + for (i = 0; i < ARRAY_SIZE(aud_clks); i++) { const struct afe_gate *gate = &aud_clks[i]; @@ -194,8 +198,10 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) /* add clk_lookup for devm_clk_get(SND_SOC_DAPM_CLOCK_SUPPLY) */ cl = kzalloc_obj(*cl); - if (!cl) + if (!cl) { + clk_unregister_gate(clk); return -ENOMEM; + } cl->clk = clk; cl->con_id = gate->name; @@ -206,5 +212,5 @@ int mt8188_audsys_clk_register(struct mtk_base_afe *afe) afe_priv->lookup[i] = cl; } - return devm_add_action_or_reset(afe->dev, mt8188_audsys_clk_unregister, afe); + return 0; } diff --git a/sound/soc/mediatek/mt8188/mt8188-dai-adda.c b/sound/soc/mediatek/mt8188/mt8188-dai-adda.c index ac547fc864a6..395f4ab972f8 100644 --- a/sound/soc/mediatek/mt8188/mt8188-dai-adda.c +++ b/sound/soc/mediatek/mt8188/mt8188-dai-adda.c @@ -123,10 +123,7 @@ static void mtk_adda_ul_mictype(struct mtk_base_afe *afe, bool dmic) UL_MODE_3P25M_CH2_CTL); /* turn on dmic, ch1, ch2 */ - if (dmic) - regmap_set_bits(afe->regmap, reg, val); - else - regmap_clear_bits(afe->regmap, reg, val); + regmap_assign_bits(afe->regmap, reg, val, dmic); } static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c b/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c index a9515d7fb70a..7ac847a5925c 100644 --- a/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c +++ b/sound/soc/mediatek/mt8188/mt8188-dai-dmic.c @@ -143,10 +143,7 @@ static void mtk_dai_dmic_hw_gain_bypass(struct mtk_base_afe *afe, return; } - if (bypass) - regmap_set_bits(afe->regmap, reg->bypass, msk); - else - regmap_clear_bits(afe->regmap, reg->bypass, msk); + regmap_assign_bits(afe->regmap, reg->bypass, msk, bypass); } static void mtk_dai_dmic_hw_gain_on(struct mtk_base_afe *afe, unsigned int id, @@ -157,10 +154,7 @@ static void mtk_dai_dmic_hw_gain_on(struct mtk_base_afe *afe, unsigned int id, if (!reg) return; - if (on) - regmap_set_bits(afe->regmap, reg->con0, DMIC_GAIN_CON0_GAIN_ON); - else - regmap_clear_bits(afe->regmap, reg->con0, DMIC_GAIN_CON0_GAIN_ON); + regmap_assign_bits(afe->regmap, reg->con0, DMIC_GAIN_CON0_GAIN_ON, on); } static const struct reg_sequence mtk_dai_dmic_iir_coeff_reg_defaults[] = { diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 75c90d1d165f..f07aeb637108 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -1142,6 +1142,9 @@ static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = { }, .dynamic = 1, .capture_only = 1, + .dpcm_merged_chan = 1, + .dpcm_merged_rate = 1, + .dpcm_merged_format = 1, SND_SOC_DAILINK_REG(capture8), }, [DAI_LINK_UL9_FE] = { diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c index fc7a7a73b0cf..594c214fa697 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-clk.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-clk.c @@ -454,30 +454,47 @@ int mt8189_apll1_enable(struct mtk_base_afe *afe) ret = mt8189_afe_enable_top_cg(afe, MT8189_CG_APLL1_CK); if (ret) - return ret; + goto err_clear_mux_setting; ret = mt8189_afe_enable_top_cg(afe, MT8189_PDN_APLL_TUNER1); if (ret) - return ret; + goto err_disable_apll1_ck; /* sel 44.1kHz:1, apll_div:7, upper bound:3 */ - regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, - XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | - UPPER_BOUND_MASK_SFT, - (0x1 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | - (3 << UPPER_BOUND_SFT)); + ret = regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | + UPPER_BOUND_MASK_SFT, + (0x1 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | + (3 << UPPER_BOUND_SFT)); + if (ret) + goto err_disable_apll_tuner1; /* apll1 freq tuner enable */ - regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, - FREQ_TUNER_EN_MASK_SFT, - 0x1 << FREQ_TUNER_EN_SFT); + ret = regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x1 << FREQ_TUNER_EN_SFT); + if (ret) + goto err_disable_apll_tuner1; /* audio apll1 on */ ret = mt8189_afe_enable_top_cg(afe, MT8189_AUDIO_APLL1_EN_ON); if (ret) - return ret; + goto err_clear_freq_tuner_en; return 0; + +err_clear_freq_tuner_en: + regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); +err_disable_apll_tuner1: + mt8189_afe_disable_top_cg(afe, MT8189_PDN_APLL_TUNER1); +err_disable_apll1_ck: + mt8189_afe_disable_top_cg(afe, MT8189_CG_APLL1_CK); +err_clear_mux_setting: + apll1_mux_setting(afe, false); + + return ret; } void mt8189_apll1_disable(struct mtk_base_afe *afe) @@ -506,30 +523,47 @@ int mt8189_apll2_enable(struct mtk_base_afe *afe) ret = mt8189_afe_enable_top_cg(afe, MT8189_CG_APLL2_CK); if (ret) - return ret; + goto err_clear_mux_setting; ret = mt8189_afe_enable_top_cg(afe, MT8189_PDN_APLL_TUNER2); if (ret) - return ret; + goto err_disable_apll2_ck; /* sel 48kHz: 2, apll_div: 7, upper bound: 3*/ - regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, - XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | - UPPER_BOUND_MASK_SFT, - (0x2 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | - (3 << UPPER_BOUND_SFT)); + ret = regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | + UPPER_BOUND_MASK_SFT, + (0x2 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) | + (3 << UPPER_BOUND_SFT)); + if (ret) + goto err_disable_apll_tuner2; /* apll2 freq tuner enable */ - regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, - FREQ_TUNER_EN_MASK_SFT, - 0x1 << FREQ_TUNER_EN_SFT); + ret = regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x1 << FREQ_TUNER_EN_SFT); + if (ret) + goto err_disable_apll_tuner2; /* audio apll2 on */ ret = mt8189_afe_enable_top_cg(afe, MT8189_AUDIO_APLL2_EN_ON); if (ret) - return ret; + goto err_clear_freq_tuner_en; return 0; + +err_clear_freq_tuner_en: + regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); +err_disable_apll_tuner2: + mt8189_afe_disable_top_cg(afe, MT8189_PDN_APLL_TUNER2); +err_disable_apll2_ck: + mt8189_afe_disable_top_cg(afe, MT8189_CG_APLL2_CK); +err_clear_mux_setting: + apll2_mux_setting(afe, false); + + return ret; } void mt8189_apll2_disable(struct mtk_base_afe *afe) @@ -605,7 +639,7 @@ int mt8189_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) ret = mt8189_afe_set_clk_parent(afe, afe_priv->clk[m_sel_id], afe_priv->clk[apll_clk_id]); if (ret) - return ret; + goto err_disable_m_sel_clk; } /* enable div, set rate */ @@ -616,13 +650,21 @@ int mt8189_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) ret = mt8189_afe_enable_clk(afe, afe_priv->clk[div_clk_id]); if (ret) - return ret; + goto err_disable_m_sel_clk; ret = mt8189_afe_set_clk_rate(afe, afe_priv->clk[div_clk_id], rate); if (ret) - return ret; + goto err_disable_div_clk; return 0; + +err_disable_div_clk: + mt8189_afe_disable_clk(afe, afe_priv->clk[div_clk_id]); +err_disable_m_sel_clk: + if (m_sel_id >= 0) + mt8189_afe_disable_clk(afe, afe_priv->clk[m_sel_id]); + + return ret; } int mt8189_mck_disable(struct mtk_base_afe *afe, int mck_id) @@ -633,8 +675,8 @@ int mt8189_mck_disable(struct mtk_base_afe *afe, int mck_id) dev_dbg(afe->dev, "mck_id: %d.\n", mck_id); - if (mck_id < 0) { - dev_err(afe->dev, "mck_id = %d < 0\n", mck_id); + if (mck_id >= MT8189_MCK_NUM || mck_id < 0) { + dev_err(afe->dev, "mck_id = %d\n", mck_id); return -EINVAL; } @@ -658,17 +700,36 @@ int mt8189_mck_disable(struct mtk_base_afe *afe, int mck_id) int mt8189_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) { struct mt8189_afe_private *afe_priv = afe->platform_priv; + int ret; /* bus clock for AFE internal access, like AFE SRAM */ - mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS]); - mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS], - afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + ret = mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS]); + if (ret) + return ret; + + ret = mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS], + afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + if (ret) + goto err_disable_audiointbus_clk; + /* enable audio clock source */ - mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H]); - mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H], - afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + ret = mt8189_afe_enable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H]); + if (ret) + goto err_disable_audiointbus_clk; + + ret = mt8189_afe_set_clk_parent(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H], + afe_priv->clk[MT8189_CLK_TOP_CLK26M]); + if (ret) + goto err_disable_audio_h_clk; return 0; + +err_disable_audio_h_clk: + mt8189_afe_disable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIO_H]); +err_disable_audiointbus_clk: + mt8189_afe_disable_clk(afe, afe_priv->clk[MT8189_CLK_TOP_MUX_AUDIOINTBUS]); + + return ret; } int mt8189_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) @@ -732,10 +793,9 @@ int mt8189_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8189_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "devm_clk_get %s fail\n", aud_clks[i]); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } ret = mt8189_afe_disable_apll(afe); diff --git a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c index 77cf2b604f6c..eefa96b701a5 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -155,8 +155,10 @@ static int mt8189_fe_startup(struct snd_pcm_substream *substream, ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_warn(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } /* dynamic allocate irq to memif */ if (memif->irq_usage < 0) { @@ -168,11 +170,11 @@ static int mt8189_fe_startup(struct snd_pcm_substream *substream, } else { dev_err(afe->dev, "%s() error: no more asys irq\n", __func__); - ret = -EBUSY; + return -EBUSY; } } - return ret; + return 0; } static void mt8189_fe_shutdown(struct snd_pcm_substream *substream, @@ -2332,20 +2334,40 @@ static int mt8189_afe_runtime_resume(struct device *dev) } regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err_set_cache_only; /* set audio 26M request */ - regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1); - regmap_update_bits(afe->regmap, AFE_CBIP_CFG0, 0x1, 0x1); + ret = regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1); + if (ret) + goto err_set_cache_only; + + ret = regmap_update_bits(afe->regmap, AFE_CBIP_CFG0, 0x1, 0x1); + if (ret) + goto err_clear_26m_req; /* force cpu use 8_24 format when writing 32bit data */ - regmap_update_bits(afe->regmap, AFE_MEMIF_CON0, - CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT); + ret = regmap_update_bits(afe->regmap, AFE_MEMIF_CON0, + CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT); + if (ret) + goto err_clear_26m_req; /* enable AFE */ - mt8189_afe_enable_main_clock(afe); + ret = mt8189_afe_enable_main_clock(afe); + if (ret) + goto err_clear_26m_req; return 0; + +err_clear_26m_req: + regmap_update_bits(afe->regmap, + AFE_SPM_CONTROL_REQ, 0x1, 0x0); +err_set_cache_only: + regcache_cache_only(afe->regmap, true); + mt8189_afe_disable_reg_rw_clk(afe); + + return ret; } static int mt8189_afe_component_probe(struct snd_soc_component *component) @@ -2421,11 +2443,6 @@ static const struct reg_sequence mt8189_cg_patch[] = { { AUDIO_TOP_CON4, 0x361c }, }; -static void mt8189_afe_release_reserved_mem(void *data) -{ - of_reserved_mem_device_release(data); -} - static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) { int ret, i; @@ -2439,16 +2456,9 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) if (ret) return ret; - ret = of_reserved_mem_device_init(dev); - if (ret) { + ret = devm_of_reserved_mem_device_init(dev); + if (ret) dev_warn(dev, "failed to assign memory region: %d\n", ret); - } else { - ret = devm_add_action_or_reset(dev, - mt8189_afe_release_reserved_mem, - dev); - if (ret) - return ret; - } afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); if (!afe) @@ -2466,13 +2476,12 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) afe->base_addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(afe->base_addr)) - return dev_err_probe(dev, PTR_ERR(afe->base_addr), - "AFE base_addr not found\n"); + return PTR_ERR(afe->base_addr); /* init audio related clock */ ret = mt8189_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error.\n"); + return ret; /* init memif */ /* IPM2.0 no need banding */ @@ -2506,13 +2515,13 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return dev_err_probe(dev, irq_id, "no irq found"); + return irq_id; ret = devm_request_irq(dev, irq_id, mt8189_afe_irq_handler, IRQF_TRIGGER_NONE, "Afe_ISR_Handle", afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -2581,10 +2590,8 @@ static int mt8189_afe_pcm_dev_probe(struct platform_device *pdev) &mt8189_afe_component, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_err(dev, "afe component err: %d\n", ret); + if (ret) return ret; - } return 0; diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c index 94c49a662e2d..c9e80ef42f60 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-i2s.c @@ -485,6 +485,7 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -492,9 +493,11 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: if (strcmp(w->name, APLL1_W_NAME) == 0) - mt8189_apll1_enable(afe); + ret = mt8189_apll1_enable(afe); else - mt8189_apll2_enable(afe); + ret = mt8189_apll2_enable(afe); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: if (strcmp(w->name, APLL1_W_NAME) == 0) @@ -516,6 +519,7 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -526,17 +530,18 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8189_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + ret = mt8189_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: i2s_priv->mclk_rate = 0; - mt8189_mck_disable(afe, i2s_priv->mclk_id); + ret = mt8189_mck_disable(afe, i2s_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_i2s_widgets[] = { @@ -1282,6 +1287,9 @@ static int mtk_dai_i2s_set_sysclk(struct snd_soc_dai *dai, dir != SND_SOC_CLOCK_OUT) return -EINVAL; + if (!freq) + return -EINVAL; + i2s_priv = afe_priv->dai_priv[dai->id]; if (!i2s_priv) return -EINVAL; diff --git a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c index 5d68a55ccc45..e113c52fe06a 100644 --- a/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c +++ b/sound/soc/mediatek/mt8189/mt8189-dai-tdm.c @@ -261,22 +261,24 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, struct mt8189_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d, bck: %d\n", w->name, event, dai_id, tdm_priv->bck_rate); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8189_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + ret = mt8189_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); break; case SND_SOC_DAPM_POST_PMD: - mt8189_mck_disable(afe, tdm_priv->bck_id); + ret = mt8189_mck_disable(afe, tdm_priv->bck_id); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, @@ -288,23 +290,25 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, struct mt8189_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d, mclk %d\n", w->name, event, dai_id, tdm_priv->mclk_rate); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8189_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + ret = mt8189_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: tdm_priv->mclk_rate = 0; - mt8189_mck_disable(afe, tdm_priv->mclk_id); + ret = mt8189_mck_disable(afe, tdm_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_tdm_widgets[] = { @@ -379,6 +383,9 @@ static int mtk_dai_tdm_cal_mclk(struct mtk_base_afe *afe, int apll; int apll_rate; + if (freq <= 0) + return -EINVAL; + apll = mt8189_get_apll_by_rate(afe, freq); apll_rate = mt8189_get_apll_rate(afe, apll); diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c index 416aff726253..c6ca4fe7fb19 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-clk.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-clk.c @@ -87,7 +87,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], ret); - goto EXIT; + return ret; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], afe_priv->clk[CLK_TOP_APLL1_CK]); @@ -95,7 +95,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_TOP_APLL1_CK], ret); - goto EXIT; + goto err_disable_mux_aud_1; } /* 180.6336 / 4 = 45.1584MHz */ @@ -103,7 +103,7 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], ret); - goto EXIT; + goto err_set_parent_mux_aud_1; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], afe_priv->clk[CLK_TOP_APLL1_D4]); @@ -111,31 +111,36 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_TOP_APLL1_D4], ret); - goto EXIT; + goto err_disable_mux_aud_eng1; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG1], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_1], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); } -EXIT: + return 0; + +err_disable_mux_aud_eng1: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG1]); +err_set_parent_mux_aud_1: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_1], + afe_priv->clk[CLK_CLK26M]); +err_disable_mux_aud_1: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_1]); + return ret; } @@ -149,7 +154,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], ret); - goto EXIT; + return ret; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], afe_priv->clk[CLK_TOP_APLL2_CK]); @@ -157,7 +162,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_TOP_APLL2_CK], ret); - goto EXIT; + goto err_disable_mux_aud_2; } /* 196.608 / 4 = 49.152MHz */ @@ -165,7 +170,7 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], ret); - goto EXIT; + goto err_set_parent_mux_aud_2; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], afe_priv->clk[CLK_TOP_APLL2_D4]); @@ -173,31 +178,36 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_TOP_APLL2_D4], ret); - goto EXIT; + goto err_disable_mux_aud_eng2; } } else { ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_ENG2], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], afe_priv->clk[CLK_CLK26M]); - if (ret) { + if (ret) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUD_2], aud_clks[CLK_CLK26M], ret); - goto EXIT; - } clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); } -EXIT: + return 0; + +err_disable_mux_aud_eng2: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_ENG2]); +err_set_parent_mux_aud_2: + clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUD_2], + afe_priv->clk[CLK_CLK26M]); +err_disable_mux_aud_2: + clk_disable_unprepare(afe_priv->clk[CLK_TOP_MUX_AUD_2]); + return ret; } @@ -210,21 +220,21 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_SYS_AUDIO], ret); - goto EXIT; + return ret; } ret = clk_prepare_enable(afe_priv->clk[CLK_INFRA_AUDIO_26M]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_INFRA_AUDIO_26M], ret); - goto EXIT; + goto err_disable_infra_sys_audio; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIO]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], ret); - goto EXIT; + goto err_disable_infra_audio_26m; } ret = clk_set_parent(afe_priv->clk[CLK_MUX_AUDIO], afe_priv->clk[CLK_CLK26M]); @@ -232,14 +242,14 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIO], aud_clks[CLK_CLK26M], ret); - goto EXIT; + goto err_disable_mux_audio; } ret = clk_prepare_enable(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIOINTBUS], ret); - goto EXIT; + goto err_disable_mux_audio; } ret = mt8192_set_audio_int_bus_parent(afe, CLK_CLK26M); @@ -247,7 +257,7 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_MUX_AUDIOINTBUS], aud_clks[CLK_CLK26M], ret); - goto EXIT; + goto err_disable_mux_audiointbus; } ret = clk_set_parent(afe_priv->clk[CLK_TOP_MUX_AUDIO_H], @@ -256,17 +266,27 @@ int mt8192_afe_enable_clock(struct mtk_base_afe *afe) dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, aud_clks[CLK_TOP_MUX_AUDIO_H], aud_clks[CLK_TOP_APLL2_CK], ret); - goto EXIT; + goto err_disable_mux_audiointbus; } ret = clk_prepare_enable(afe_priv->clk[CLK_AFE]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_AFE], ret); - goto EXIT; + goto err_disable_mux_audiointbus; } -EXIT: + return 0; + +err_disable_mux_audiointbus: + clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIOINTBUS]); +err_disable_mux_audio: + clk_disable_unprepare(afe_priv->clk[CLK_MUX_AUDIO]); +err_disable_infra_audio_26m: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_AUDIO_26M]); +err_disable_infra_sys_audio: + clk_disable_unprepare(afe_priv->clk[CLK_INFRA_SYS_AUDIO]); + return ret; } @@ -288,20 +308,22 @@ int mt8192_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL22M]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL22M], ret); - goto EXIT; + goto err_disable_mux_setting; } ret = clk_prepare_enable(afe_priv->clk[CLK_APLL1_TUNER]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL1_TUNER], ret); - goto EXIT; + goto err_disable_apll22m; } regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, @@ -312,7 +334,13 @@ int mt8192_apll1_enable(struct mtk_base_afe *afe) AFE_22M_ON_MASK_SFT, 0x1 << AFE_22M_ON_SFT); -EXIT: + return 0; + +err_disable_apll22m: + clk_disable_unprepare(afe_priv->clk[CLK_APLL22M]); +err_disable_mux_setting: + apll1_mux_setting(afe, false); + return ret; } @@ -338,20 +366,22 @@ int mt8192_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = clk_prepare_enable(afe_priv->clk[CLK_APLL24M]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL24M], ret); - goto EXIT; + goto err_disable_mux_setting; } ret = clk_prepare_enable(afe_priv->clk[CLK_APLL2_TUNER]); if (ret) { dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", __func__, aud_clks[CLK_APLL2_TUNER], ret); - goto EXIT; + goto err_disable_apll24m; } regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, @@ -362,7 +392,13 @@ int mt8192_apll2_enable(struct mtk_base_afe *afe) AFE_24M_ON_MASK_SFT, 0x1 << AFE_24M_ON_SFT); -EXIT: + return 0; + +err_disable_apll24m: + clk_disable_unprepare(afe_priv->clk[CLK_APLL24M]); +err_disable_mux_setting: + apll2_mux_setting(afe, false); + return ret; } @@ -583,7 +619,7 @@ int mt8192_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) dev_err(afe->dev, "%s(), clk_set_parent %s-%s fail %d\n", __func__, aud_clks[m_sel_id], aud_clks[apll_clk_id], ret); - return ret; + goto err_disable_m_sel; } } @@ -592,17 +628,25 @@ int mt8192_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) if (ret) { dev_err(afe->dev, "%s(), clk_prepare_enable %s fail %d\n", __func__, aud_clks[div_clk_id], ret); - return ret; + goto err_disable_m_sel; } ret = clk_set_rate(afe_priv->clk[div_clk_id], rate); if (ret) { dev_err(afe->dev, "%s(), clk_set_rate %s, rate %d, fail %d\n", __func__, aud_clks[div_clk_id], rate, ret); - return ret; + goto err_disable_div_clk; } return 0; + +err_disable_div_clk: + clk_disable_unprepare(afe_priv->clk[div_clk_id]); +err_disable_m_sel: + if (m_sel_id >= 0) + clk_disable_unprepare(afe_priv->clk[m_sel_id]); + + return ret; } void mt8192_mck_disable(struct mtk_base_afe *afe, int mck_id) @@ -628,38 +672,29 @@ int mt8192_init_clock(struct mtk_base_afe *afe) return -ENOMEM; for (i = 0; i < CLK_NUM; i++) { - afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_warn(afe->dev, "%s devm_clk_get %s fail, ret %ld\n", - __func__, - aud_clks[i], PTR_ERR(afe_priv->clk[i])); - afe_priv->clk[i] = NULL; - } + afe_priv->clk[i] = devm_clk_get_optional(afe->dev, aud_clks[i]); + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } afe_priv->apmixedsys = syscon_regmap_lookup_by_phandle(of_node, "mediatek,apmixedsys"); - if (IS_ERR(afe_priv->apmixedsys)) { - dev_err(afe->dev, "%s() Cannot find apmixedsys controller: %ld\n", - __func__, PTR_ERR(afe_priv->apmixedsys)); - return PTR_ERR(afe_priv->apmixedsys); - } + if (IS_ERR(afe_priv->apmixedsys)) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->apmixedsys), + "Cannot find apmixedsys controller\n"); afe_priv->topckgen = syscon_regmap_lookup_by_phandle(of_node, "mediatek,topckgen"); - if (IS_ERR(afe_priv->topckgen)) { - dev_err(afe->dev, "%s() Cannot find topckgen controller: %ld\n", - __func__, PTR_ERR(afe_priv->topckgen)); - return PTR_ERR(afe_priv->topckgen); - } + if (IS_ERR(afe_priv->topckgen)) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->topckgen), + "Cannot find topckgen controller\n"); afe_priv->infracfg = syscon_regmap_lookup_by_phandle(of_node, "mediatek,infracfg"); - if (IS_ERR(afe_priv->infracfg)) { - dev_err(afe->dev, "%s() Cannot find infracfg: %ld\n", - __func__, PTR_ERR(afe_priv->infracfg)); - return PTR_ERR(afe_priv->infracfg); - } + if (IS_ERR(afe_priv->infracfg)) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->infracfg), + "Cannot find infracfg\n"); return 0; } diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c index b993ca2dbd7c..d2e99c55b8c6 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-gpio.c @@ -132,12 +132,8 @@ int mt8192_afe_gpio_init(struct device *dev) int i, ret; aud_pinctrl = devm_pinctrl_get(dev); - if (IS_ERR(aud_pinctrl)) { - ret = PTR_ERR(aud_pinctrl); - dev_err(dev, "%s(), ret %d, cannot get aud_pinctrl!\n", - __func__, ret); - return ret; - } + if (IS_ERR(aud_pinctrl)) + return dev_err_probe(dev, PTR_ERR(aud_pinctrl), "cannot get aud_pinctrl!\n"); for (i = 0; i < ARRAY_SIZE(aud_gpios); i++) { aud_gpios[i].gpioctrl = pinctrl_lookup_state(aud_pinctrl, @@ -151,13 +147,17 @@ int mt8192_afe_gpio_init(struct device *dev) } } - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_CLK_MOSI_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_CLK_MOSI_ON); + if (ret) + return ret; /* gpio status init */ - mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 0); - mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 1); + ret = mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 0); + if (ret) + return ret; + ret = mt8192_afe_gpio_request(dev, false, MT8192_DAI_ADDA, 1); - return 0; + return ret; } EXPORT_SYMBOL(mt8192_afe_gpio_init); @@ -208,98 +208,104 @@ static int mt8192_afe_gpio_adda_ch34_ul(struct device *dev, bool enable) int mt8192_afe_gpio_request(struct device *dev, bool enable, int dai, int uplink) { + int ret; + guard(mutex)(&gpio_request_mutex); switch (dai) { case MT8192_DAI_ADDA: if (uplink) - mt8192_afe_gpio_adda_ul(dev, enable); + ret = mt8192_afe_gpio_adda_ul(dev, enable); else - mt8192_afe_gpio_adda_dl(dev, enable); + ret = mt8192_afe_gpio_adda_dl(dev, enable); break; case MT8192_DAI_ADDA_CH34: if (uplink) - mt8192_afe_gpio_adda_ch34_ul(dev, enable); + ret = mt8192_afe_gpio_adda_ch34_ul(dev, enable); else - mt8192_afe_gpio_adda_ch34_dl(dev, enable); + ret = mt8192_afe_gpio_adda_ch34_dl(dev, enable); break; case MT8192_DAI_I2S_0: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S0_OFF); break; case MT8192_DAI_I2S_1: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S1_OFF); break; case MT8192_DAI_I2S_2: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S2_OFF); break; case MT8192_DAI_I2S_3: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S3_OFF); break; case MT8192_DAI_I2S_5: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S5_OFF); break; case MT8192_DAI_I2S_6: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S6_OFF); break; case MT8192_DAI_I2S_7: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S7_OFF); break; case MT8192_DAI_I2S_8: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S8_OFF); break; case MT8192_DAI_I2S_9: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_I2S9_OFF); break; case MT8192_DAI_TDM: if (enable) - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_ON); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_ON); else - mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_OFF); + ret = mt8192_afe_gpio_select(dev, MT8192_AFE_GPIO_TDM_OFF); break; case MT8192_DAI_VOW: if (enable) { - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_CLK_ON); - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_DAT_ON); + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_CLK_ON); + if (ret) + break; + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_DAT_ON); } else { - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_CLK_OFF); - mt8192_afe_gpio_select(dev, - MT8192_AFE_GPIO_VOW_DAT_OFF); + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_CLK_OFF); + if (ret) + break; + ret = mt8192_afe_gpio_select(dev, + MT8192_AFE_GPIO_VOW_DAT_OFF); } break; default: dev_warn(dev, "%s(), invalid dai %d\n", __func__, dai); - return -EINVAL; + ret = -EINVAL; } - return 0; + return ret; } EXPORT_SYMBOL(mt8192_afe_gpio_request); diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c index db0ae44a86af..45addf9f3790 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-pcm.c @@ -2104,7 +2104,11 @@ static int mt8192_afe_runtime_resume(struct device *dev) goto skip_regmap; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) { + regcache_cache_only(afe->regmap, true); + mt8192_afe_disable_clock(afe); + } /* enable audio sys DCM for power saving */ regmap_update_bits(afe_priv->infracfg, @@ -2155,11 +2159,6 @@ static const dai_register_cb dai_register_cbs[] = { mt8192_dai_memif_register, }; -static void mt8192_afe_release_reserved_mem(void *data) -{ - of_reserved_mem_device_release(data); -} - static int mt8192_afe_pcm_dev_probe(struct platform_device *pdev) { struct mtk_base_afe *afe; @@ -2185,22 +2184,16 @@ static int mt8192_afe_pcm_dev_probe(struct platform_device *pdev) afe->dev = dev; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_info(dev, "no reserved memory found, pre-allocating buffers instead\n"); afe->preallocate_buffers = true; - } else { - ret = devm_add_action_or_reset(dev, mt8192_afe_release_reserved_mem, dev); - if (ret) - return ret; } /* init audio related clock */ ret = mt8192_init_clock(afe); - if (ret) { - dev_err(dev, "init clock error\n"); + if (ret) return ret; - } /* reset controller to reset audio regs before regmap cache */ rstc = devm_reset_control_get_exclusive(dev, "audiosys"); @@ -2276,7 +2269,7 @@ static int mt8192_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8192_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); diff --git a/sound/soc/mediatek/mt8192/mt8192-dai-adda.c b/sound/soc/mediatek/mt8192/mt8192-dai-adda.c index f8cb84621d38..c5c726a6e423 100644 --- a/sound/soc/mediatek/mt8192/mt8192-dai-adda.c +++ b/sound/soc/mediatek/mt8192/mt8192-dai-adda.c @@ -218,10 +218,13 @@ static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mt8192_afe_private *afe_priv = afe->platform_priv; int mtkaif_dmic = afe_priv->mtkaif_dmic; + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); + if (ret) + return ret; /* update setting to dmic */ if (mtkaif_dmic) { @@ -239,7 +242,9 @@ static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); + if (ret) + return ret; break; default: break; @@ -257,11 +262,14 @@ static int mtk_adda_ch34_ul_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int mtkaif_dmic = afe_priv->mtkaif_dmic_ch34; int mtkaif_adda6_only = afe_priv->mtkaif_adda6_only; + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, - 1); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, + 1); + if (ret) + return ret; /* update setting to dmic */ if (mtkaif_dmic) { @@ -291,8 +299,10 @@ static int mtk_adda_ch34_ul_event(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, - 1); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, + 1); + if (ret) + return ret; /* reset dmic */ afe_priv->mtkaif_dmic_ch34 = 0; @@ -446,15 +456,20 @@ static int mtk_adda_dl_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); + if (ret) + return ret; break; default: break; @@ -469,17 +484,22 @@ static int mtk_adda_ch34_dl_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, - 0); + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, + 0); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: /* should delayed 1/fs(smallest is 8k) = 125us before afe off */ usleep_range(125, 135); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, - 0); + ret = mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, + 0); + if (ret) + return ret; break; default: break; diff --git a/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c b/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c index 1632fc94776d..5608b534af0d 100644 --- a/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c +++ b/sound/soc/mediatek/mt8192/mt8192-dai-i2s.c @@ -586,6 +586,7 @@ static int mtk_i2s_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; i2s_priv = get_i2s_priv_by_name(afe, w->name); @@ -599,16 +600,17 @@ static int mtk_i2s_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, i2s_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, true, i2s_priv->id, 0); break; case SND_SOC_DAPM_POST_PMD: - mt8192_afe_gpio_request(afe->dev, false, i2s_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, false, i2s_priv->id, 0); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_apll_event(struct snd_soc_dapm_widget *w, @@ -617,6 +619,7 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -624,9 +627,11 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: if (snd_soc_dapm_widget_name_cmp(w, APLL1_W_NAME) == 0) - mt8192_apll1_enable(afe); + ret = mt8192_apll1_enable(afe); else - mt8192_apll2_enable(afe); + ret = mt8192_apll2_enable(afe); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: if (snd_soc_dapm_widget_name_cmp(w, APLL1_W_NAME) == 0) @@ -704,6 +709,7 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", __func__, w->name, event); @@ -716,7 +722,9 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + ret = mt8192_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: i2s_priv->mclk_rate = 0; diff --git a/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c b/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c index 49440db370af..36fa0f32ed1c 100644 --- a/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c +++ b/sound/soc/mediatek/mt8192/mt8192-dai-tdm.c @@ -250,6 +250,7 @@ static int mtk_tdm_en_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; if (!tdm_priv) { dev_warn(afe->dev, "%s(), tdm_priv == NULL", __func__); @@ -261,16 +262,17 @@ static int mtk_tdm_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_afe_gpio_request(afe->dev, true, tdm_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, true, tdm_priv->id, 0); break; case SND_SOC_DAPM_POST_PMD: - mt8192_afe_gpio_request(afe->dev, false, tdm_priv->id, 0); + ret = mt8192_afe_gpio_request(afe->dev, false, tdm_priv->id, 0); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, @@ -282,6 +284,7 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; if (!tdm_priv) { dev_warn(afe->dev, "%s(), tdm_priv == NULL", __func__); @@ -293,7 +296,9 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + ret = mt8192_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: mt8192_mck_disable(afe, tdm_priv->bck_id); @@ -314,6 +319,7 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, struct mt8192_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; if (!tdm_priv) { dev_warn(afe->dev, "%s(), tdm_priv == NULL", __func__); @@ -325,7 +331,9 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8192_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + ret = mt8192_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + if (ret) + return ret; break; case SND_SOC_DAPM_POST_PMD: tdm_priv->mclk_rate = 0; diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c index c86348691641..b010726e9b57 100644 --- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c @@ -163,12 +163,23 @@ static int mt8192_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) int chosen_phase_1, chosen_phase_2, chosen_phase_3; int counter; int mtkaif_calib_ok; + int ret = 0; - pm_runtime_get_sync(afe->dev); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 1); - mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 0); + ret = pm_runtime_resume_and_get(afe->dev); + if (ret < 0) + return ret; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 1); + if (ret) + goto err_pm_put; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA, 0); + if (ret) + goto err_disable_adda_1; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 1); + if (ret) + goto err_disable_adda_0; + ret = mt8192_afe_gpio_request(afe->dev, true, MT8192_DAI_ADDA_CH34, 0); + if (ret) + goto err_disable_adda_ch34_1; mt6359_mtkaif_calibration_enable(cmpnt_codec); @@ -289,10 +300,14 @@ static int mt8192_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) mt6359_mtkaif_calibration_disable(cmpnt_codec); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); - mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 1); mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 0); +err_disable_adda_ch34_1: + mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA_CH34, 1); +err_disable_adda_0: + mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 0); +err_disable_adda_1: + mt8192_afe_gpio_request(afe->dev, false, MT8192_DAI_ADDA, 1); +err_pm_put: pm_runtime_put(afe->dev); dev_dbg(afe->dev, "%s(), mtkaif_chosen_phase[0/1/2]:%d/%d/%d\n", @@ -301,7 +316,7 @@ static int mt8192_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) afe_priv->mtkaif_chosen_phase[1], afe_priv->mtkaif_chosen_phase[2]); - return 0; + return ret; } static int mt8192_mt6359_init(struct snd_soc_pcm_runtime *rtd) @@ -319,9 +334,7 @@ static int mt8192_mt6359_init(struct snd_soc_pcm_runtime *rtd) afe_priv->mtkaif_protocol = MTKAIF_PROTOCOL_2_CLK_P2; /* mtkaif calibration */ - mt8192_mt6359_mtkaif_calibration(rtd); - - return 0; + return mt8192_mt6359_mtkaif_calibration(rtd); } static int mt8192_rt5682_init(struct snd_soc_pcm_runtime *rtd) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c index 618d8400913a..44cddc2b1564 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-clk.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-clk.c @@ -241,21 +241,35 @@ static int mt8195_afe_enable_tuner_clk(struct mtk_base_afe *afe, unsigned int id) { struct mt8195_afe_private *afe_priv = afe->platform_priv; + int ret; switch (id) { case MT8195_AUD_PLL1: - mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL]); - mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL1_TUNER]); + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL]); + if (ret) + return ret; + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL1_TUNER]); + if (ret) { + mt8195_afe_disable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL]); + return ret; + } break; case MT8195_AUD_PLL2: - mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2]); - mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2_TUNER]); + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2]); + if (ret) + return ret; + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2_TUNER]); + if (ret) { + mt8195_afe_disable_clk(afe, afe_priv->clk[MT8195_CLK_AUD_APLL2]); + return ret; + } break; default: + ret = 0; break; } - return 0; + return ret; } static int mt8195_afe_disable_tuner_clk(struct mtk_base_afe *afe, @@ -383,12 +397,9 @@ int mt8195_afe_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8195_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_dbg(afe->dev, "%s(), devm_clk_get %s fail, ret %ld\n", - __func__, aud_clks[i], - PTR_ERR(afe_priv->clk[i])); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } /* initial tuner */ @@ -588,7 +599,7 @@ static int mt8195_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_t int mt8195_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) { struct mt8195_afe_private *afe_priv = afe->platform_priv; - int i; + int i, ret; static const unsigned int clk_array[] = { MT8195_CLK_SCP_ADSP_AUDIODSP, /* bus clock for infra */ MT8195_CLK_TOP_AUDIO_H_SEL, /* clock for ADSP bus */ @@ -600,10 +611,19 @@ int mt8195_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) MT8195_CLK_AUD_A1SYS, /* AFE HW clock */ }; - for (i = 0; i < ARRAY_SIZE(clk_array); i++) - mt8195_afe_enable_clk(afe, afe_priv->clk[clk_array[i]]); + for (i = 0; i < ARRAY_SIZE(clk_array); i++) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[clk_array[i]]); + if (ret) + goto err_disable_clk; + } return 0; + +err_disable_clk: + while (--i >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[clk_array[i]]); + + return ret; } int mt8195_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) @@ -642,7 +662,7 @@ static int mt8195_afe_disable_afe_on(struct mtk_base_afe *afe) static int mt8195_afe_enable_timing_sys(struct mtk_base_afe *afe) { struct mt8195_afe_private *afe_priv = afe->platform_priv; - int i; + int i, ret; static const unsigned int clk_array[] = { MT8195_CLK_AUD_A1SYS, MT8195_CLK_AUD_A2SYS, @@ -653,13 +673,22 @@ static int mt8195_afe_enable_timing_sys(struct mtk_base_afe *afe) MT8195_TOP_CG_26M_TIMING, }; - for (i = 0; i < ARRAY_SIZE(clk_array); i++) - mt8195_afe_enable_clk(afe, afe_priv->clk[clk_array[i]]); + for (i = 0; i < ARRAY_SIZE(clk_array); i++) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[clk_array[i]]); + if (ret) + goto err_disable_clk; + } for (i = 0; i < ARRAY_SIZE(cg_array); i++) mt8195_afe_enable_top_cg(afe, cg_array[i]); return 0; + +err_disable_clk: + while (--i >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[clk_array[i]]); + + return ret; } static int mt8195_afe_disable_timing_sys(struct mtk_base_afe *afe) @@ -687,14 +716,31 @@ static int mt8195_afe_disable_timing_sys(struct mtk_base_afe *afe) int mt8195_afe_enable_main_clock(struct mtk_base_afe *afe) { - mt8195_afe_enable_timing_sys(afe); + int ret; + + ret = mt8195_afe_enable_timing_sys(afe); + if (ret) + return ret; mt8195_afe_enable_afe_on(afe); - mt8195_afe_enable_apll_tuner(afe, MT8195_AUD_PLL1); - mt8195_afe_enable_apll_tuner(afe, MT8195_AUD_PLL2); + ret = mt8195_afe_enable_apll_tuner(afe, MT8195_AUD_PLL1); + if (ret) + goto err_disable_afe_on; + + ret = mt8195_afe_enable_apll_tuner(afe, MT8195_AUD_PLL2); + if (ret) + goto err_disable_apll1_tuner; return 0; + +err_disable_apll1_tuner: + mt8195_afe_disable_apll_tuner(afe, MT8195_AUD_PLL1); +err_disable_afe_on: + mt8195_afe_disable_afe_on(afe); + mt8195_afe_disable_timing_sys(afe); + + return ret; } int mt8195_afe_disable_main_clock(struct mtk_base_afe *afe) diff --git a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index bc0a63457cd7..13e5bc02fac5 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -286,15 +286,23 @@ mt8195_afe_paired_memif_clk_prepare(struct snd_pcm_substream *substream, struct mt8195_afe_private *afe_priv = afe->platform_priv; int id = snd_soc_rtd_to_cpu(rtd, 0)->id; int clk_id; + int ret; if (id != MT8195_AFE_MEMIF_DL8 && id != MT8195_AFE_MEMIF_DL10) return 0; if (enable) { clk_id = MT8195_CLK_AUD_MEMIF_DL10; - mt8195_afe_prepare_clk(afe, afe_priv->clk[clk_id]); + ret = mt8195_afe_prepare_clk(afe, afe_priv->clk[clk_id]); + if (ret) + return ret; clk_id = MT8195_CLK_AUD_MEMIF_DL8; - mt8195_afe_prepare_clk(afe, afe_priv->clk[clk_id]); + ret = mt8195_afe_prepare_clk(afe, afe_priv->clk[clk_id]); + if (ret) { + clk_id = MT8195_CLK_AUD_MEMIF_DL10; + mt8195_afe_unprepare_clk(afe, afe_priv->clk[clk_id]); + return ret; + } } else { clk_id = MT8195_CLK_AUD_MEMIF_DL8; mt8195_afe_unprepare_clk(afe, afe_priv->clk[clk_id]); @@ -315,6 +323,7 @@ mt8195_afe_paired_memif_clk_enable(struct snd_pcm_substream *substream, struct mt8195_afe_private *afe_priv = afe->platform_priv; int id = snd_soc_rtd_to_cpu(rtd, 0)->id; int clk_id; + int ret; if (id != MT8195_AFE_MEMIF_DL8 && id != MT8195_AFE_MEMIF_DL10) return 0; @@ -322,11 +331,18 @@ mt8195_afe_paired_memif_clk_enable(struct snd_pcm_substream *substream, if (enable) { /* DL8_DL10_MEM */ clk_id = MT8195_CLK_AUD_MEMIF_DL10; - mt8195_afe_enable_clk_atomic(afe, afe_priv->clk[clk_id]); + ret = mt8195_afe_enable_clk_atomic(afe, afe_priv->clk[clk_id]); + if (ret) + return ret; udelay(1); /* DL8_DL10_AGENT */ clk_id = MT8195_CLK_AUD_MEMIF_DL8; - mt8195_afe_enable_clk_atomic(afe, afe_priv->clk[clk_id]); + ret = mt8195_afe_enable_clk_atomic(afe, afe_priv->clk[clk_id]); + if (ret) { + clk_id = MT8195_CLK_AUD_MEMIF_DL10; + mt8195_afe_disable_clk_atomic(afe, afe_priv->clk[clk_id]); + return ret; + } } else { /* DL8_DL10_AGENT */ clk_id = MT8195_CLK_AUD_MEMIF_DL8; @@ -348,24 +364,38 @@ static int mt8195_afe_fe_startup(struct snd_pcm_substream *substream, int id = snd_soc_rtd_to_cpu(rtd, 0)->id; int ret = 0; - mt8195_afe_paired_memif_clk_prepare(substream, dai, 1); + ret = mt8195_afe_paired_memif_clk_prepare(substream, dai, 1); + if (ret) + return ret; ret = mtk_afe_fe_startup(substream, dai); - - snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_BUFFER_BYTES, - MT8195_MEMIF_BUFFER_BYTES_ALIGN); - - if (id != MT8195_AFE_MEMIF_DL7) - goto out; - - ret = snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_PERIOD_SIZE, - 1, - MT8195_MEMIF_DL7_MAX_PERIOD_SIZE); if (ret < 0) - dev_dbg(afe->dev, "hw_constraint_minmax failed\n"); -out: + goto err_clk_unprepare; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + MT8195_MEMIF_BUFFER_BYTES_ALIGN); + if (ret) + goto err_fe_shutdow; + + if (id == MT8195_AFE_MEMIF_DL7) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 1, + MT8195_MEMIF_DL7_MAX_PERIOD_SIZE); + if (ret < 0) { + dev_dbg(afe->dev, "hw_constraint_minmax failed\n"); + goto err_fe_shutdow; + } + } + + return 0; + +err_fe_shutdow: + mtk_afe_fe_shutdown(substream, dai); +err_clk_unprepare: + mt8195_afe_paired_memif_clk_prepare(substream, dai, 0); + return ret; } @@ -436,7 +466,14 @@ static int mt8195_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: - mt8195_afe_paired_memif_clk_enable(substream, dai, 1); + if (ret) + break; + + ret = mt8195_afe_paired_memif_clk_enable(substream, dai, 1); + if (ret) { + mtk_afe_fe_trigger(substream, SNDRV_PCM_TRIGGER_STOP, dai); + mt8195_afe_enable_cm(afe, cm, false); + } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: @@ -2928,18 +2965,31 @@ static int mt8195_afe_runtime_resume(struct device *dev) { struct mtk_base_afe *afe = dev_get_drvdata(dev); struct mt8195_afe_private *afe_priv = afe->platform_priv; + int ret; - mt8195_afe_enable_reg_rw_clk(afe); + ret = mt8195_afe_enable_reg_rw_clk(afe); + if (ret) + return ret; if (!afe->regmap || afe_priv->pm_runtime_bypass_reg_ctl) - goto skip_regmap; + return 0; regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err_restore_cache; + + ret = mt8195_afe_enable_main_clock(afe); + if (ret) + goto err_restore_cache; - mt8195_afe_enable_main_clock(afe); -skip_regmap: return 0; + +err_restore_cache: + regcache_cache_only(afe->regmap, true); + mt8195_afe_disable_reg_rw_clk(afe); + + return ret; } static int init_memif_priv_data(struct mtk_base_afe *afe) @@ -3013,7 +3063,7 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) struct reset_control *rstc; int i, irq_id, ret; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) return dev_err_probe(dev, ret, "failed to assign memory region\n"); @@ -3040,7 +3090,7 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) /* initial audio related clock */ ret = mt8195_afe_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error\n"); + return ret; /* reset controller to reset audio regs before regmap cache */ rstc = devm_reset_control_get_exclusive(dev, "audiosys"); @@ -3082,12 +3132,12 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return -ENXIO; + return irq_id; ret = devm_request_irq(dev, irq_id, mt8195_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for asys-isr\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -3145,10 +3195,9 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) /* register component */ ret = devm_snd_soc_register_component(dev, &mtk_afe_pcm_platform, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_warn(dev, "err_platform\n"); + if (ret) goto err_pm_put; - } + ret = regmap_multi_reg_write(afe->regmap, mt8195_afe_reg_defaults, ARRAY_SIZE(mt8195_afe_reg_defaults)); diff --git a/sound/soc/mediatek/mt8195/mt8195-dai-adda.c b/sound/soc/mediatek/mt8195/mt8195-dai-adda.c index 94abde15ea09..8a58a142064d 100644 --- a/sound/soc/mediatek/mt8195/mt8195-dai-adda.c +++ b/sound/soc/mediatek/mt8195/mt8195-dai-adda.c @@ -262,9 +262,8 @@ static int mtk_audio_hires_event(struct snd_soc_dapm_widget *w, default: return 0; } - mt8195_afe_set_clk_parent(afe, clk, clk_parent); - return 0; + return mt8195_afe_set_clk_parent(afe, clk, clk_parent); } static struct mtk_dai_adda_priv *get_adda_priv_by_name(struct mtk_base_afe *afe, diff --git a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c index 1a20adb2cbf5..8fa830a57502 100644 --- a/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c +++ b/sound/soc/mediatek/mt8195/mt8195-dai-etdm.c @@ -1549,9 +1549,7 @@ static int mtk_dai_etdm_enable_mclk(struct mtk_base_afe *afe, int dai_id) if (clkdiv_id < 0) return -EINVAL; - mt8195_afe_enable_clk(afe, afe_priv->clk[clkdiv_id]); - - return 0; + return mt8195_afe_enable_clk(afe, afe_priv->clk[clkdiv_id]); } static int mtk_dai_etdm_disable_mclk(struct mtk_base_afe *afe, int dai_id) @@ -1577,36 +1575,66 @@ static int mtk_dai_etdm_startup(struct snd_pcm_substream *substream, int cg_id; int mst_dai_id; int slv_dai_id; - int i; + int i, ret; if (is_cowork_mode(dai)) { mst_dai_id = get_etdm_cowork_master_id(dai); if (!mt8195_afe_etdm_is_valid(mst_dai_id)) return -EINVAL; - mtk_dai_etdm_enable_mclk(afe, mst_dai_id); + ret = mtk_dai_etdm_enable_mclk(afe, mst_dai_id); + if (ret) + return ret; cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(mst_dai_id); - if (cg_id >= 0) - mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (cg_id >= 0) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (ret) + goto err_disable_mclk; + } mst_etdm_data = afe_priv->dai_priv[mst_dai_id]; for (i = 0; i < mst_etdm_data->cowork_slv_count; i++) { slv_dai_id = mst_etdm_data->cowork_slv_id[i]; cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(slv_dai_id); - if (cg_id >= 0) - mt8195_afe_enable_clk(afe, - afe_priv->clk[cg_id]); + if (cg_id >= 0) { + ret = mt8195_afe_enable_clk(afe, + afe_priv->clk[cg_id]); + if (ret) + goto err_disable_slv_clk; + } } } else { - mtk_dai_etdm_enable_mclk(afe, dai->id); + ret = mtk_dai_etdm_enable_mclk(afe, dai->id); + if (ret) + return ret; cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(dai->id); - if (cg_id >= 0) - mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (cg_id >= 0) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (ret) + goto err_disable_mclk; + } } return 0; + +err_disable_slv_clk: + while (--i >= 0) { + slv_dai_id = mst_etdm_data->cowork_slv_id[i]; + cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(slv_dai_id); + if (cg_id >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[cg_id]); + } + + cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(mst_dai_id); + if (cg_id >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[cg_id]); + +err_disable_mclk: + mtk_dai_etdm_disable_mclk(afe, is_cowork_mode(dai) ? mst_dai_id : dai->id); + + return ret; } static void mtk_dai_etdm_shutdown(struct snd_pcm_substream *substream, @@ -2293,13 +2321,25 @@ static int mtk_dai_hdmitx_dptx_startup(struct snd_pcm_substream *substream, struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); struct mt8195_afe_private *afe_priv = afe->platform_priv; int cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(dai->id); + int ret; - if (cg_id >= 0) - mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (cg_id >= 0) { + ret = mt8195_afe_enable_clk(afe, afe_priv->clk[cg_id]); + if (ret) + return ret; + } - mtk_dai_etdm_enable_mclk(afe, dai->id); + ret = mtk_dai_etdm_enable_mclk(afe, dai->id); + if (ret) + goto err_disable_cg_clk; return 0; + +err_disable_cg_clk: + if (cg_id >= 0) + mt8195_afe_disable_clk(afe, afe_priv->clk[cg_id]); + + return ret; } static void mtk_dai_hdmitx_dptx_shutdown(struct snd_pcm_substream *substream, @@ -2463,6 +2503,7 @@ static int mtk_dai_etdm_probe(struct snd_soc_dai *dai) struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); struct mt8195_afe_private *afe_priv = afe->platform_priv; struct mtk_dai_etdm_priv *etdm_data; + int ret; dev_dbg(dai->dev, "%s id %d\n", __func__, dai->id); @@ -2470,15 +2511,23 @@ static int mtk_dai_etdm_probe(struct snd_soc_dai *dai) return -EINVAL; etdm_data = afe_priv->dai_priv[dai->id]; - if (etdm_data->mclk_freq) { - dev_dbg(afe->dev, "MCLK always on, rate %d\n", - etdm_data->mclk_freq); - pm_runtime_get_sync(afe->dev); - mtk_dai_etdm_mclk_configure(afe, dai->id); - mtk_dai_etdm_enable_mclk(afe, dai->id); - pm_runtime_put_sync(afe->dev); - } - return 0; + if (!etdm_data->mclk_freq) + return 0; + + dev_dbg(afe->dev, "MCLK always on, rate %d\n", + etdm_data->mclk_freq); + ret = pm_runtime_resume_and_get(afe->dev); + if (ret) + return ret; + ret = mtk_dai_etdm_mclk_configure(afe, dai->id); + if (ret) + goto err_put_pm; + ret = mtk_dai_etdm_enable_mclk(afe, dai->id); + +err_put_pm: + pm_runtime_put_sync(afe->dev); + + return ret; } static const struct snd_soc_dai_ops mtk_dai_hdmitx_dptx_ops = { diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c index 286e39f53ae0..91a05ea6b8b6 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c @@ -220,33 +220,39 @@ static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable) ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], MT8196_AUD_ENG1_CLK); if (ret) - return ret; + goto err_disable_eng1; ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); if (ret) - return ret; + goto err_restore_eng1; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], apll_rate); if (ret) - return ret; + goto err_disable_audio_h; } else { - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1]); - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); } return 0; + +err_disable_audio_h: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); +err_restore_eng1: + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1], + MT8196_AFE_26M); +err_disable_eng1: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1]); + + return ret; } static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) @@ -268,33 +274,39 @@ static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable) ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], MT8196_AUD_ENG2_CLK); if (ret) - return ret; + goto err_disable_eng2; ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); if (ret) - return ret; + goto err_restore_eng2; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], apll_rate); if (ret) - return ret; + goto err_disable_audio_h; } else { - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2]); - ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], - MT8196_AFE_26M); - if (ret) - return ret; + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], + MT8196_AFE_26M); mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); } return 0; + +err_disable_audio_h: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); +err_restore_eng2: + mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2], + MT8196_AFE_26M); +err_disable_eng2: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2]); + + return ret; } int mt8196_apll1_enable(struct mtk_base_afe *afe) @@ -302,15 +314,17 @@ int mt8196_apll1_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll1_mux_setting(afe, true); + ret = apll1_mux_setting(afe, true); + if (ret) + return ret; ret = mt8196_afe_enable_top_cg(afe, MT8196_CG_APLL1_CK); if (ret) - goto err_clk_apll1; + goto err_disable_mux; ret = mt8196_afe_enable_top_cg(afe, MT8196_PDN_APLL_TUNER1); if (ret) - goto err_clk_apll1_tuner; + goto err_disable_apll1_ck; /* sel 44.1kHz:1, apll_div:7, upper bound:3 */ regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, @@ -324,14 +338,22 @@ int mt8196_apll1_enable(struct mtk_base_afe *afe) 0x1 << FREQ_TUNER_EN_SFT); /* audio apll1 on */ - mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL1_EN_ON); + ret = mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL1_EN_ON); + if (ret) + goto err_disable_tuner; return 0; -err_clk_apll1_tuner: +err_disable_tuner: + regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER1); -err_clk_apll1: +err_disable_apll1_ck: mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL1_CK); +err_disable_mux: + apll1_mux_setting(afe, false); + return ret; } @@ -355,15 +377,17 @@ int mt8196_apll2_enable(struct mtk_base_afe *afe) int ret; /* setting for APLL */ - apll2_mux_setting(afe, true); + ret = apll2_mux_setting(afe, true); + if (ret) + return ret; ret = mt8196_afe_enable_top_cg(afe, MT8196_CG_APLL2_CK); if (ret) - goto err_clk_apll2; + goto err_disable_mux; ret = mt8196_afe_enable_top_cg(afe, MT8196_PDN_APLL_TUNER2); if (ret) - goto err_clk_apll2_tuner; + goto err_disable_apll2_ck; /* sel 48kHz: 2, apll_div: 7, upper bound: 3*/ regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, @@ -377,14 +401,23 @@ int mt8196_apll2_enable(struct mtk_base_afe *afe) 0x1 << FREQ_TUNER_EN_SFT); /* audio apll2 on */ - mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL2_EN_ON); + ret = mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL2_EN_ON); + if (ret) + goto err_disable_tuner2; + return 0; -err_clk_apll2_tuner: +err_disable_tuner2: + regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG, + FREQ_TUNER_EN_MASK_SFT, + 0x0); mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER2); -err_clk_apll2: +err_disable_apll2_ck: mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL2_CK); - return 0; +err_disable_mux: + apll2_mux_setting(afe, false); + + return ret; } void mt8196_apll2_disable(struct mtk_base_afe *afe) @@ -469,8 +502,10 @@ int mt8196_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate) return ret; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[div_clk_id], rate); - if (ret) + if (ret) { + mt8196_afe_disable_clk(afe, afe_priv->clk[div_clk_id]); return ret; + } return 0; } @@ -511,26 +546,41 @@ int mt8196_afe_enable_reg_rw_clk(struct mtk_base_afe *afe) int ret; /* bus clock for AFE external access, like DRAM */ - mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]); + ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]); + if (ret) + return ret; /* bus clock for AFE internal access, like AFE SRAM */ - mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]); + ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]); + if (ret) + goto err_disable_adsp_sel; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS], MT8196_AFE_26M); if (ret) - return ret; + goto err_disable_audiointbus; /* enable audio h clock */ - mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); + ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); + if (ret) + goto err_disable_audiointbus; ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H], MT8196_AFE_26M); if (ret) - return ret; + goto err_disable_audio_h; /* AFE hw clock */ /* IPM2.0: USE HOPPING & 26M */ /* set in the regmap_register_patch */ return 0; + +err_disable_audio_h: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]); +err_disable_audiointbus: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]); +err_disable_adsp_sel: + mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]); + + return ret; } int mt8196_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) @@ -548,14 +598,12 @@ int mt8196_afe_disable_reg_rw_clk(struct mtk_base_afe *afe) int mt8196_afe_enable_main_clock(struct mtk_base_afe *afe) { - mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); - return 0; + return mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); } int mt8196_afe_disable_main_clock(struct mtk_base_afe *afe) { - mt8196_afe_disable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); - return 0; + return mt8196_afe_disable_top_cg(afe, MT8196_AUDIO_26M_EN_ON); } int mt8196_init_clock(struct mtk_base_afe *afe) @@ -570,10 +618,9 @@ int mt8196_init_clock(struct mtk_base_afe *afe) for (i = 0; i < MT8196_CLK_NUM; i++) { afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe_priv->clk[i])) { - dev_err(afe->dev, "devm_clk_get %s fail\n", aud_clks[i]); - return PTR_ERR(afe_priv->clk[i]); - } + if (IS_ERR(afe_priv->clk[i])) + return dev_err_probe(afe->dev, PTR_ERR(afe_priv->clk[i]), + "failed to get clock %s\n", aud_clks[i]); } return 0; diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c index a1ae8322d8b6..fbeba01b086c 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c @@ -2211,7 +2211,7 @@ static int mt8196_afe_runtime_suspend(struct device *dev) static int mt8196_afe_runtime_resume(struct device *dev) { struct mtk_base_afe *afe = dev_get_drvdata(dev); - int ret = 0; + int ret; ret = mt8196_afe_enable_reg_rw_clk(afe); if (ret) @@ -2222,7 +2222,9 @@ static int mt8196_afe_runtime_resume(struct device *dev) goto skip_regmap; } regcache_cache_only(afe->regmap, false); - regcache_sync(afe->regmap); + ret = regcache_sync(afe->regmap); + if (ret) + goto err_set_cache_only; /* set audio 26M request */ regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1); @@ -2233,10 +2235,20 @@ static int mt8196_afe_runtime_resume(struct device *dev) CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT); /* enable AFE */ - mt8196_afe_enable_main_clock(afe); + ret = mt8196_afe_enable_main_clock(afe); + if (ret) + goto err_reset_26m_req; skip_regmap: return 0; + +err_reset_26m_req: + regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x0); +err_set_cache_only: + regcache_cache_only(afe->regmap, true); + mt8196_afe_disable_reg_rw_clk(afe); + + return ret; } static int mt8196_afe_component_probe(struct snd_soc_component *component) @@ -2309,11 +2321,6 @@ static const struct reg_sequence mt8196_cg_patch[] = { { AUDIO_TOP_CON4, 0x361c }, }; -static void mt8196_afe_release_reserved_mem(void *data) -{ - of_reserved_mem_device_release(data); -} - static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) { int ret, i; @@ -2327,14 +2334,9 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) if (ret) return ret; - ret = of_reserved_mem_device_init(dev); - if (ret) { + ret = devm_of_reserved_mem_device_init(dev); + if (ret) dev_err(dev, "failed to assign memory region: %d\n", ret); - } else { - ret = devm_add_action_or_reset(dev, mt8196_afe_release_reserved_mem, dev); - if (ret) - return ret; - } afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); if (!afe) @@ -2358,7 +2360,7 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) /* init audio related clock */ ret = mt8196_init_clock(afe); if (ret) - return dev_err_probe(dev, ret, "init clock error.\n"); + return ret; /* init memif */ /* IPM2.0 no need banding */ @@ -2392,13 +2394,13 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) /* request irq */ irq_id = platform_get_irq(pdev, 0); if (irq_id < 0) - return dev_err_probe(dev, irq_id, "no irq found"); + return irq_id; ret = devm_request_irq(dev, irq_id, mt8196_afe_irq_handler, IRQF_TRIGGER_NONE, "Afe_ISR_Handle", afe); if (ret) - return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n"); + return ret; /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); @@ -2467,10 +2469,8 @@ static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev) &mt8196_afe_component, afe->dai_drivers, afe->num_dai_drivers); - if (ret) { - dev_err(dev, "afe component err\n"); + if (ret) return ret; - } return 0; diff --git a/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c b/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c index ef5cde0ba829..adebf75629be 100644 --- a/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c +++ b/sound/soc/mediatek/mt8196/mt8196-dai-i2s.c @@ -985,27 +985,30 @@ static int mtk_apll_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x\n", w->name, event); switch (event) { case SND_SOC_DAPM_PRE_PMU: if (strcmp(w->name, APLL1_W_NAME) == 0) - mt8196_apll1_enable(afe); + ret = mt8196_apll1_enable(afe); else - mt8196_apll2_enable(afe); + ret = mt8196_apll2_enable(afe); break; case SND_SOC_DAPM_POST_PMD: if (strcmp(w->name, APLL1_W_NAME) == 0) mt8196_apll1_disable(afe); else mt8196_apll2_disable(afe); + ret = 0; break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, @@ -1015,6 +1018,7 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mtk_afe_i2s_priv *i2s_priv; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x\n", w->name, event); @@ -1025,17 +1029,18 @@ static int mtk_mclk_en_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8196_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); + ret = mt8196_mck_enable(afe, i2s_priv->mclk_id, i2s_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: i2s_priv->mclk_rate = 0; - mt8196_mck_disable(afe, i2s_priv->mclk_id); + ret = mt8196_mck_disable(afe, i2s_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_i2s_widgets[] = { diff --git a/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c b/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c index b7aeee939d88..44bd08f5c1fb 100644 --- a/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c +++ b/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c @@ -256,22 +256,24 @@ static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w, struct mt8196_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d\n", w->name, event, dai_id); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8196_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); + ret = mt8196_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate); break; case SND_SOC_DAPM_POST_PMD: - mt8196_mck_disable(afe, tdm_priv->bck_id); + ret = mt8196_mck_disable(afe, tdm_priv->bck_id); break; default: + ret = 0; break; } - return 0; + return ret; } static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, @@ -283,23 +285,25 @@ static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w, struct mt8196_afe_private *afe_priv = afe->platform_priv; int dai_id = get_tdm_id_by_name(w->name); struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id]; + int ret; dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d\n", w->name, event, dai_id); switch (event) { case SND_SOC_DAPM_PRE_PMU: - mt8196_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); + ret = mt8196_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate); break; case SND_SOC_DAPM_POST_PMD: tdm_priv->mclk_rate = 0; - mt8196_mck_disable(afe, tdm_priv->mclk_id); + ret = mt8196_mck_disable(afe, tdm_priv->mclk_id); break; default: + ret = 0; break; } - return 0; + return ret; } static const struct snd_soc_dapm_widget mtk_dai_tdm_widgets[] = { diff --git a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c index e9497246f37c..314235557fdf 100644 --- a/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c +++ b/sound/soc/mediatek/mt8365/mt8365-afe-pcm.c @@ -514,11 +514,14 @@ static int mt8365_afe_fe_startup(struct snd_pcm_substream *substream, snd_soc_set_runtime_hwparams(substream, afe->mtk_afe_hardware); ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) + if (ret < 0) { dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + return ret; + } mt8365_afe_enable_main_clk(afe); - return ret; + + return 0; } static void mt8365_afe_fe_shutdown(struct snd_pcm_substream *substream, diff --git a/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c b/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c index a058973662b3..2e678f4aa4b1 100644 --- a/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c +++ b/sound/soc/mediatek/mt8365/mt8365-dai-i2s.c @@ -267,7 +267,7 @@ static const u32 *get_iir_coef(unsigned int input_fs, if (k >= IIR_NO_NEED) { } else if (k == IIR_RATIOVER) { } else if (k == IIR_INV_COEF) { - } else { + } else if (k < ARRAY_SIZE(iir_coef_tbl_list)) { coef = iir_coef_tbl_list[k].coef; cnt = iir_coef_tbl_list[k].cnt; } diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c index a77cd516a1bf..e46896ac9669 100644 --- a/sound/soc/mxs/mxs-saif.c +++ b/sound/soc/mxs/mxs-saif.c @@ -691,6 +691,14 @@ static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd, (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) +static const u64 mxs_saif_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops mxs_saif_dai_ops = { .startup = mxs_saif_startup, .shutdown = mxs_saif_shutdown, @@ -699,6 +707,8 @@ static const struct snd_soc_dai_ops mxs_saif_dai_ops = { .hw_params = mxs_saif_hw_params, .set_sysclk = mxs_saif_set_dai_sysclk, .set_fmt = mxs_saif_set_dai_fmt, + .auto_selectable_formats = &mxs_saif_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver mxs_saif_dai = { diff --git a/sound/soc/pxa/mmp-sspa.c b/sound/soc/pxa/mmp-sspa.c index fbbce81680cf..f9066fbdd571 100644 --- a/sound/soc/pxa/mmp-sspa.c +++ b/sound/soc/pxa/mmp-sspa.c @@ -339,6 +339,10 @@ static int mmp_sspa_probe(struct snd_soc_dai *dai) SNDRV_PCM_FMTBIT_S24_3LE | \ SNDRV_PCM_FMTBIT_S32_LE) +static const u64 mmp_sspa_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops mmp_sspa_dai_ops = { .probe = mmp_sspa_probe, .startup = mmp_sspa_startup, @@ -348,6 +352,8 @@ static const struct snd_soc_dai_ops mmp_sspa_dai_ops = { .set_sysclk = mmp_sspa_set_dai_sysclk, .set_pll = mmp_sspa_set_dai_pll, .set_fmt = mmp_sspa_set_dai_fmt, + .auto_selectable_formats = &mmp_sspa_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver mmp_sspa_dai = { diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 3a0abcb0bfbd..9a98246ad19c 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -809,6 +809,15 @@ static int pxa_ssp_remove(struct snd_soc_dai *dai) #define PXA_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) +static const u64 pxa_ssp_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops pxa_ssp_dai_ops = { .probe = pxa_ssp_probe, .remove = pxa_ssp_remove, @@ -820,6 +829,8 @@ static const struct snd_soc_dai_ops pxa_ssp_dai_ops = { .set_fmt = pxa_ssp_set_dai_fmt, .set_tdm_slot = pxa_ssp_set_dai_tdm_slot, .set_tristate = pxa_ssp_set_dai_tristate, + .auto_selectable_formats = &pxa_ssp_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver pxa_ssp_dai = { diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index fe1df78926f5..008cf5ae2607 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -328,6 +328,10 @@ static int pxa2xx_i2s_remove(struct snd_soc_dai *dai) SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +static const u64 pxa_i2s_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops pxa_i2s_dai_ops = { .probe = pxa2xx_i2s_probe, .remove = pxa2xx_i2s_remove, @@ -337,6 +341,8 @@ static const struct snd_soc_dai_ops pxa_i2s_dai_ops = { .hw_params = pxa2xx_i2s_hw_params, .set_fmt = pxa2xx_i2s_set_dai_fmt, .set_sysclk = pxa2xx_i2s_set_dai_sysclk, + .auto_selectable_formats = &pxa_i2s_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver pxa_i2s_dai = { diff --git a/sound/soc/pxa/pxa2xx-pcm-lib.c b/sound/soc/pxa/pxa2xx-pcm-lib.c index 88a9d3226302..4b5b07f0c444 100644 --- a/sound/soc/pxa/pxa2xx-pcm-lib.c +++ b/sound/soc/pxa/pxa2xx-pcm-lib.c @@ -79,6 +79,7 @@ static int pxa2xx_pcm_open(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dmaengine_dai_dma_data *dma_params; + struct dma_chan *chan; int ret; runtime->hw = pxa2xx_pcm_hardware; @@ -107,9 +108,11 @@ static int pxa2xx_pcm_open(struct snd_pcm_substream *substream) if (ret < 0) return ret; - return snd_dmaengine_pcm_open( - substream, dma_request_slave_channel(snd_soc_rtd_to_cpu(rtd, 0)->dev, - dma_params->chan_name)); + chan = dma_request_chan(snd_soc_rtd_to_cpu(rtd, 0)->dev, dma_params->chan_name); + if (IS_ERR(chan)) + return -ENXIO; + + return snd_dmaengine_pcm_open(substream, chan); } static int pxa2xx_pcm_close(struct snd_pcm_substream *substream) diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index e6e24f3b9922..f6d810931a9a 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -71,6 +71,16 @@ config SND_SOC_QCOM_COMMON config SND_SOC_QCOM_SDW tristate +config SND_SOC_QCOM_QAIF + tristate "Qualcomm QAIF audio interface support" + depends on COMMON_CLK + select REGMAP_MMIO + help + Say Y or M to enable the Qualcomm Audio Interface (QAIF) driver + for the Shikra audio platform. QAIF is a DMA-based audio + subsystem that moves PCM data between memory and external serial + audio interfaces (AIF) and internal codec interfaces (CIF). + config SND_SOC_QDSP6_COMMON tristate @@ -102,6 +112,7 @@ config SND_SOC_QDSP6_ASM_DAI config SND_SOC_QDSP6_APM_DAI tristate select SND_SOC_COMPRESS + select QCOM_SCM config SND_SOC_QDSP6_APM_LPASS_DAI tristate diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index 985ce2ae286b..cf50b7a24bcf 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -47,3 +47,6 @@ obj-$(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) += snd-soc-qcom-offload-utils.o #DSP lib obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/ + +snd-soc-qcom-qaif-y := qaif-cpu.o qaif-platform.o qaif-shikra.o +obj-$(CONFIG_SND_SOC_QCOM_QAIF) += snd-soc-qcom-qaif.o diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index d9f256d51973..83b745f617a8 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -339,13 +339,11 @@ static struct snd_soc_jack_pin qcom_headset_jack_pins[] = { }, }; -int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, - struct snd_soc_jack *jack, bool *jack_setup) +static int qcom_snd_headset_jack_init(struct snd_soc_card *card, + struct snd_soc_jack *jack, + bool *jack_setup) { - struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); - struct snd_soc_card *card = rtd->card; - int rval, i; + int rval; if (!*jack_setup) { rval = snd_soc_card_jack_new_pins(card, "Headset Jack", @@ -369,6 +367,55 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, *jack_setup = true; } + return 0; +} + +int qcom_snd_headset_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup) +{ + struct snd_soc_dai *codec_dai; + struct snd_soc_card *card = rtd->card; + int rval, i; + + rval = qcom_snd_headset_jack_init(card, jack, jack_setup); + if (rval) + return rval; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + rval = snd_soc_component_set_jack(codec_dai->component, jack, NULL); + if (rval != 0 && rval != -ENOTSUPP) { + dev_warn(card->dev, "Failed to set jack: %d\n", rval); + qcom_snd_headset_jack_cleanup(rtd); + return rval; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_snd_headset_jack_setup); + +void qcom_snd_headset_jack_cleanup(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai; + int i; + + for_each_rtd_codec_dais(rtd, i, codec_dai) + snd_soc_component_set_jack(codec_dai->component, NULL, NULL); +} +EXPORT_SYMBOL_GPL(qcom_snd_headset_jack_cleanup); + +int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup) +{ + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + int rval, i; + + rval = qcom_snd_headset_jack_init(card, jack, jack_setup); + if (rval) + return rval; + switch (cpu_dai->id) { case LPI_MI2S_RX_0: case TX_CODEC_DMA_TX_0: @@ -388,8 +435,6 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, default: break; } - - return 0; } EXPORT_SYMBOL_GPL(qcom_snd_wcd_jack_setup); diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h index c1deac109f24..37b185a4968f 100644 --- a/sound/soc/qcom/common.h +++ b/sound/soc/qcom/common.h @@ -7,7 +7,7 @@ #include #include -#define LPASS_MAX_PORT (LPI_MI2S_TX_6 + 1) +#define LPASS_MAX_PORT (AIF_TDM_TX_12 + 1) struct qcom_snd_tdm_slot_cfg { unsigned int tx_mask; @@ -24,6 +24,9 @@ int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, const struct qcom_snd_tdm_slot_cfg *cpu_cfg, const struct qcom_snd_tdm_slot_cfg *codec_cfg); int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd); +int qcom_snd_headset_jack_setup(struct snd_soc_pcm_runtime *rtd, + struct snd_soc_jack *jack, bool *jack_setup); +void qcom_snd_headset_jack_cleanup(struct snd_soc_pcm_runtime *rtd); int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, struct snd_soc_jack *jack, bool *jack_setup); int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index 079177a6d81e..ee187eeb7392 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -458,30 +458,9 @@ const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops2 = { }; EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops2); -static int asoc_qcom_of_xlate_dai_name(struct snd_soc_component *component, - const struct of_phandle_args *args, - const char **dai_name) -{ - struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); - const struct lpass_variant *variant = drvdata->variant; - int id = args->args[0]; - int ret = -EINVAL; - int i; - - for (i = 0; i < variant->num_dai; i++) { - if (variant->dai_driver[i].id == id) { - *dai_name = variant->dai_driver[i].name; - ret = 0; - break; - } - } - - return ret; -} - static const struct snd_soc_component_driver lpass_cpu_comp_driver = { .name = "lpass-cpu", - .of_xlate_dai_name = asoc_qcom_of_xlate_dai_name, + .of_xlate_dai_name = snd_soc_of_xlate_dai_name, .legacy_dai_naming = 1, }; diff --git a/sound/soc/qcom/qaif-cpu.c b/sound/soc/qcom/qaif-cpu.c new file mode 100644 index 000000000000..d34b8df83f94 --- /dev/null +++ b/sound/soc/qcom/qaif-cpu.c @@ -0,0 +1,887 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-cpu.c -- ALSA SoC CPU-Platform DAI driver for QTi QAIF + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qaif-reg.h" +#include "qaif.h" + +/* CIF INTF CFG register bit positions */ +#define QAIF_CIF_INTF_16BIT_UNPACK_BIT BIT(0) +#define QAIF_CIF_INTF_DYNCLK_BIT BIT(2) +#define QAIF_CIF_INTF_FS_SEL_MASK GENMASK(7, 4) +#define QAIF_CIF_INTF_ACTIVE_CH_MASK GENMASK(27, 12) +#define QAIF_CIF_INTF_ACTIVE_CH_SHIFT 12 + +/* AIF CTL register (QAIF_AUD_INTF_CTL_REG) bit positions */ +#define QAIF_AIF_CTL_ENABLE_BIT BIT(0) +#define QAIF_AIF_CTL_ENABLE_TX_BIT BIT(4) +#define QAIF_AIF_CTL_ENABLE_RX_BIT BIT(8) + +/* AIF SYNC CFG register (QAIF_AUD_INTF_SYNC_CFG_REG) bit positions */ +#define QAIF_AIF_SYNC_SRC_BIT BIT(0) +#define QAIF_AIF_SYNC_MODE_MASK GENMASK(5, 4) +#define QAIF_AIF_SYNC_MODE_SHIFT 4 +#define QAIF_AIF_SYNC_DELAY_MASK GENMASK(9, 8) +#define QAIF_AIF_SYNC_DELAY_SHIFT 8 +#define QAIF_AIF_SYNC_INV_BIT BIT(12) + +/* SYNC_MODE field values */ +#define QAIF_AIF_SYNC_MODE_SHORT 0 +#define QAIF_AIF_SYNC_MODE_ONE_SLOT 1 +#define QAIF_AIF_SYNC_MODE_LONG 2 + +/* SYNC_DELAY field values */ +#define QAIF_AIF_SYNC_DELAY_SAME 0 +#define QAIF_AIF_SYNC_DELAY_ONE 1 + +/* AIF BIT WIDTH CFG register (QAIF_AUD_INTF_BIT_WIDTH_CFG_REG) bit positions */ +#define QAIF_AIF_SLOT_WIDTH_TX_MASK GENMASK(4, 0) +#define QAIF_AIF_SLOT_WIDTH_RX_MASK GENMASK(12, 8) +#define QAIF_AIF_SLOT_WIDTH_RX_SHIFT 8 +#define QAIF_AIF_SAMPLE_WIDTH_TX_MASK GENMASK(20, 16) +#define QAIF_AIF_SAMPLE_WIDTH_TX_SHIFT 16 +#define QAIF_AIF_SAMPLE_WIDTH_RX_MASK GENMASK(28, 24) +#define QAIF_AIF_SAMPLE_WIDTH_RX_SHIFT 24 + +/* + * slot_width is programmed 0-based into the 5-bit SLOT_WIDTH field and the + * frame length (slots * slot_width) 0-based into the 10-bit BITS_PER_LANE + * field, so these are the largest values the registers can represent. + */ +#define QAIF_AIF_SLOT_WIDTH_MAX 32 +#define QAIF_AIF_FRAME_BITS_MAX 1024 + +/* AIF FRAME CFG register (QAIF_AUD_INTF_FRAME_CFG_REG) bit positions */ +#define QAIF_AIF_BITS_PER_LANE_MASK GENMASK(9, 0) + +/* AIF LANE CFG register (QAIF_AUD_INTF_LANE_CFG_REG) bit positions */ +#define QAIF_AIF_LANE_DIR_MASK GENMASK(7, 0) +#define QAIF_AIF_LANE_EN_MASK GENMASK(15, 8) +#define QAIF_AIF_LANE_EN_SHIFT 8 +#define QAIF_AIF_CTRL_DATA_OE_BIT BIT(16) +#define QAIF_AIF_LOOPBACK_EN_BIT BIT(31) + +/* AIF MI2S CFG register (QAIF_AUD_INTF_MI2S_CFG_REG) bit positions */ +#define QAIF_AIF_MONO_MODE_TX_BIT BIT(0) +#define QAIF_AIF_MONO_MODE_RX_BIT BIT(1) + +/* AIF CFG register (QAIF_AUD_INTF_CFG_REG) bit positions */ +#define QAIF_AIF_FULL_CYCLE_EN_BIT BIT(0) + +static int qaif_cif_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + unsigned int dai_id = dai->driver->id; + unsigned int regval; + u32 reg; + int idx; + + switch (params_channels(params)) { + case 1: + regval = QAIF_CIF_DMA_INTF_ONE_CHANNEL; + break; + case 2: + regval = QAIF_CIF_DMA_INTF_TWO_CHANNEL; + break; + case 4: + regval = QAIF_CIF_DMA_INTF_FOUR_CHANNEL; + break; + case 6: + regval = QAIF_CIF_DMA_INTF_SIX_CHANNEL; + break; + case 8: + regval = QAIF_CIF_DMA_INTF_EIGHT_CHANNEL; + break; + default: + dev_dbg(rtd->dev, "unsupported channel count %u\n", + params_channels(params)); + return -EINVAL; + } + + idx = v->get_dma_idx(dai_id); + if (idx < 0) { + dev_err(rtd->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + reg = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_INTF_CFG_REG(v, idx) : + QAIF_CDC_WRDMA_INTF_CFG_REG(v, idx); + + return regmap_update_bits(drvdata->audio_qaif_map, reg, + QAIF_CIF_INTF_ACTIVE_CH_MASK, + regval << QAIF_CIF_INTF_ACTIVE_CH_SHIFT); +} + +static int qaif_cif_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + unsigned int dai_id = dai->driver->id; + struct regmap *map = drvdata->audio_qaif_map; + u32 reg; + int idx, ret; + + idx = v->get_dma_idx(dai_id); + if (idx < 0) { + dev_err(rtd->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + reg = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_INTF_CFG_REG(v, idx) : + QAIF_CDC_WRDMA_INTF_CFG_REG(v, idx); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_update_bits(map, reg, + QAIF_CIF_INTF_DYNCLK_BIT, + QAIF_CIF_INTF_DYNCLK_BIT); + if (ret) + return ret; + ret = regmap_update_bits(map, reg, QAIF_CIF_INTF_FS_SEL_MASK, 0); + if (ret) + return ret; + return regmap_update_bits(map, reg, + QAIF_CIF_INTF_16BIT_UNPACK_BIT, + QAIF_CIF_INTF_16BIT_UNPACK_BIT); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(map, reg, QAIF_CIF_INTF_DYNCLK_BIT, 0); + if (ret) + return ret; + return regmap_update_bits(map, reg, + QAIF_CIF_INTF_16BIT_UNPACK_BIT, 0); + default: + dev_err(rtd->dev, "invalid trigger cmd %d\n", cmd); + return -EINVAL; + } +} + +static int qaif_cif_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_prepare_enable(drvdata->aud_dma_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->aud_dma_mem_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma_mem clk: %d\n", ret); + clk_disable_unprepare(drvdata->aud_dma_clk); + return ret; + } + return 0; +} + +static void qaif_cif_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(drvdata->aud_dma_mem_clk); + clk_disable_unprepare(drvdata->aud_dma_clk); +} + +/* + * asoc_qcom_qaif_cif_dai_ops - DAI ops for QAIF CIF (CDC DMA) paths + * + * Provides startup, shutdown, hw_params and trigger callbacks for the + * Codec Interface DMA (CIF) DAI. startup/shutdown enable and disable the + * audio DMA clocks. Used by Shikra machine drivers for Bolero CDC DMA + * RX, TX and VA TX paths. + */ +const struct snd_soc_dai_ops asoc_qcom_qaif_cif_dai_ops = { + .startup = qaif_cif_daiops_startup, + .shutdown = qaif_cif_daiops_shutdown, + .hw_params = qaif_cif_daiops_hw_params, + .trigger = qaif_cif_daiops_trigger, +}; +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_cif_dai_ops); + +static int qaif_aif_cpu_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx, ret; + + idx = v->get_dma_idx(dai->driver->id); + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + ret = clk_prepare_enable(drvdata->aud_dma_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->aud_dma_mem_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma_mem clk: %d\n", ret); + clk_disable_unprepare(drvdata->aud_dma_clk); + return ret; + } + + ret = clk_prepare_enable(drvdata->mi2s_bit_clk[idx]); + if (ret) { + dev_err(dai->dev, "error enabling mi2s bit clk: %d\n", ret); + clk_disable_unprepare(drvdata->aud_dma_mem_clk); + clk_disable_unprepare(drvdata->aud_dma_clk); + } + return ret; +} + +static void qaif_aif_cpu_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + struct qaif_aif_config *cfg; + unsigned int enable_bit; + int idx = v->get_dma_idx(dai->driver->id); + int ret; + + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return; + } + + cfg = &drvdata->aif_intf_cfg[idx]; + if (cfg->loopback_en) + enable_bit = QAIF_AIF_CTL_ENABLE_BIT; + else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + enable_bit = QAIF_AIF_CTL_ENABLE_TX_BIT; + else + enable_bit = QAIF_AIF_CTL_ENABLE_RX_BIT; + + ret = regmap_update_bits(drvdata->audio_qaif_map, + QAIF_AUD_INTF_CTL_REG(idx), enable_bit, 0); + if (ret) + dev_err(dai->dev, "error clearing AIF enable bit: %d\n", ret); + + clk_disable_unprepare(drvdata->mi2s_bit_clk[idx]); + clk_disable_unprepare(drvdata->aud_dma_mem_clk); + clk_disable_unprepare(drvdata->aud_dma_clk); +} + +static int qaif_aif_cpu_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + const struct qaif_aif_config *cfg; + struct regmap *map = drvdata->audio_qaif_map; + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int slot_width; + int bitwidth, idx, ret; + + idx = v->get_dma_idx(dai->driver->id); + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + cfg = &drvdata->aif_intf_cfg[idx]; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "invalid bit width given: %d\n", bitwidth); + return bitwidth; + } + + /* + * The sync, lane and frame configuration are shared by both directions + * of an AIF and are taken from aif_intf_cfg[idx], which is fixed at + * probe and via set_fmt()/set_tdm_slot(). Both directions program + * identical values here, so concurrent playback and capture hw_params() + * calls are idempotent. + */ + ret = regmap_update_bits(map, QAIF_AUD_INTF_SYNC_CFG_REG(idx), + QAIF_AIF_SYNC_SRC_BIT | + QAIF_AIF_SYNC_MODE_MASK | + QAIF_AIF_SYNC_DELAY_MASK | + QAIF_AIF_SYNC_INV_BIT, + (cfg->sync_src ? QAIF_AIF_SYNC_SRC_BIT : 0) | + (cfg->sync_mode << QAIF_AIF_SYNC_MODE_SHIFT) | + (cfg->sync_delay << QAIF_AIF_SYNC_DELAY_SHIFT) | + (cfg->invert_sync ? QAIF_AIF_SYNC_INV_BIT : 0)); + if (ret) + return ret; + + ret = regmap_update_bits(map, QAIF_AUD_INTF_LANE_CFG_REG(idx), + QAIF_AIF_LANE_DIR_MASK | + QAIF_AIF_LANE_EN_MASK | + QAIF_AIF_CTRL_DATA_OE_BIT | + QAIF_AIF_LOOPBACK_EN_BIT, + cfg->lane_dir_mask | + (cfg->lane_en_mask << QAIF_AIF_LANE_EN_SHIFT) | + (cfg->ctrl_data_oe ? QAIF_AIF_CTRL_DATA_OE_BIT : 0) | + (cfg->loopback_en ? QAIF_AIF_LOOPBACK_EN_BIT : 0)); + if (ret) + return ret; + + ret = regmap_update_bits(map, QAIF_AUD_INTF_CFG_REG(idx), + QAIF_AIF_FULL_CYCLE_EN_BIT, + cfg->full_cycle_en ? QAIF_AIF_FULL_CYCLE_EN_BIT : 0); + if (ret) + return ret; + + if (qaif_is_aif_mi2s_port(dai->driver->id) && !cfg->slot_width) { + slot_width = bitwidth; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + drvdata->aif_intf_cfg[idx].slot_en_tx_mask = + GENMASK(channels - 1, 0); + else + drvdata->aif_intf_cfg[idx].slot_en_rx_mask = + GENMASK(channels - 1, 0); + drvdata->aif_intf_cfg[idx].slot_num = QAIF_MI2S_SLOTS; + } else { + slot_width = cfg->slot_width; + if (!slot_width || !cfg->slot_num) { + dev_err(dai->dev, "invalid TDM slot config: width=%u num=%u\n", + slot_width, cfg->slot_num); + return -EINVAL; + } + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = regmap_update_bits(map, QAIF_AUD_INTF_BIT_WIDTH_CFG_REG(idx), + QAIF_AIF_SLOT_WIDTH_TX_MASK | + QAIF_AIF_SAMPLE_WIDTH_TX_MASK, + QAIF_AIF_SLOT_WIDTH(slot_width) | + (QAIF_AIF_SAMPLE_WIDTH(bitwidth) << + QAIF_AIF_SAMPLE_WIDTH_TX_SHIFT)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_AUD_INTF_ACTV_SLOT_EN_TX_REG(idx), + cfg->slot_en_tx_mask); + if (ret) + return ret; + ret = regmap_update_bits(map, QAIF_AUD_INTF_MI2S_CFG_REG(idx), + QAIF_AIF_MONO_MODE_TX_BIT, + (channels < 2) ? QAIF_AIF_MONO_MODE_TX_BIT : 0); + } else { + ret = regmap_update_bits(map, QAIF_AUD_INTF_BIT_WIDTH_CFG_REG(idx), + QAIF_AIF_SLOT_WIDTH_RX_MASK | + QAIF_AIF_SAMPLE_WIDTH_RX_MASK, + (QAIF_AIF_SLOT_WIDTH(slot_width) << + QAIF_AIF_SLOT_WIDTH_RX_SHIFT) | + (QAIF_AIF_SAMPLE_WIDTH(bitwidth) << + QAIF_AIF_SAMPLE_WIDTH_RX_SHIFT)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_AUD_INTF_ACTV_SLOT_EN_RX_REG(idx), + cfg->slot_en_rx_mask); + if (ret) + return ret; + ret = regmap_update_bits(map, QAIF_AUD_INTF_MI2S_CFG_REG(idx), + QAIF_AIF_MONO_MODE_RX_BIT, + (channels < 2) ? QAIF_AIF_MONO_MODE_RX_BIT : 0); + } + if (ret) + return ret; + + ret = regmap_update_bits(map, QAIF_AUD_INTF_FRAME_CFG_REG(idx), + QAIF_AIF_BITS_PER_LANE_MASK, + (slot_width * cfg->slot_num) - 1); + if (ret) + return ret; + + return 0; +} + +static int qaif_aif_cpu_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + const struct qaif_aif_config *cfg; + unsigned int enable_bit; + int idx, ret; + + idx = v->get_dma_idx(dai->driver->id); + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + cfg = &drvdata->aif_intf_cfg[idx]; + if (cfg->loopback_en) + enable_bit = QAIF_AIF_CTL_ENABLE_BIT; + else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + enable_bit = QAIF_AIF_CTL_ENABLE_TX_BIT; + else + enable_bit = QAIF_AIF_CTL_ENABLE_RX_BIT; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_update_bits(drvdata->audio_qaif_map, + QAIF_AUD_INTF_CTL_REG(idx), enable_bit, enable_bit); + if (ret) + dev_err(dai->dev, "error setting AIF enable bit: %d\n", ret); + return ret; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(drvdata->audio_qaif_map, + QAIF_AUD_INTF_CTL_REG(idx), enable_bit, 0); + if (ret) + dev_err(dai->dev, "error clearing AIF enable bit: %d\n", ret); + return ret; + default: + dev_err(dai->dev, "invalid trigger cmd %d\n", cmd); + return -EINVAL; + } +} + +static int qaif_aif_cpu_daiops_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx = v->get_dma_idx(dai->driver->id); + struct qaif_aif_config *cfg; + + if (idx < 0) + return -EINVAL; + + if (slots <= 0 || slot_width <= 0) + return -EINVAL; + + if (slot_width > QAIF_AIF_SLOT_WIDTH_MAX || + slots * slot_width > QAIF_AIF_FRAME_BITS_MAX) + return -EINVAL; + + cfg = &drvdata->aif_intf_cfg[idx]; + cfg->slot_num = slots; + cfg->slot_width = slot_width; + cfg->slot_en_tx_mask = tx_mask; + cfg->slot_en_rx_mask = rx_mask; + + return 0; +} + +static int qaif_aif_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, + int dir) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx = v->get_dma_idx(dai->driver->id); + + if (idx < 0) + return -EINVAL; + + /* + * The bit clock is prepared and enabled for the lifetime of the + * stream by startup()/shutdown(); only the rate is set here. + */ + return clk_set_rate(drvdata->mi2s_bit_clk[idx], freq); +} + +static int qaif_aif_cpu_daiops_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx = v->get_dma_idx(dai->driver->id); + struct qaif_aif_config *cfg; + + if (idx < 0) + return -EINVAL; + + cfg = &drvdata->aif_intf_cfg[idx]; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_LONG; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + cfg->invert_sync = true; + break; + case SND_SOC_DAIFMT_DSP_A: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_ONE_SLOT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + break; + case SND_SOC_DAIFMT_DSP_B: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_SHORT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_SAME; + break; + default: + dev_err(dai->dev, "unsupported format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + cfg->sync_src = 1; + break; + case SND_SOC_DAIFMT_CBC_CFC: + cfg->sync_src = 0; + break; + default: + dev_err(dai->dev, "unsupported clock provider mode\n"); + return -EINVAL; + } + + /* + * For DSP_A/DSP_B (TDM/PCM) modes, sync inversion is board-configurable + * via the NB_IF flag. For I2S, invert_sync is fixed above and must + * not be overridden here. + */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) { + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + cfg->invert_sync = (fmt & SND_SOC_DAIFMT_INV_MASK) == + SND_SOC_DAIFMT_NB_IF; + break; + default: + dev_err(dai->dev, "bit clock inversion is not supported\n"); + return -EINVAL; + } + } + + return 0; +} + +/* + * asoc_qcom_qaif_aif_cpu_dai_ops - DAI ops for QAIF AIF (MI2S/TDM/PCM) paths + * + * Provides startup, shutdown, hw_params and trigger callbacks + * for the Unified Audio Interface (AIF) DAI. Used by Shikra machine + * drivers for MI2S, TDM and PCM serial audio paths. + */ +const struct snd_soc_dai_ops asoc_qcom_qaif_aif_cpu_dai_ops = { + .set_fmt = qaif_aif_cpu_daiops_set_fmt, + .set_tdm_slot = qaif_aif_cpu_daiops_set_tdm_slot, + .set_sysclk = qaif_aif_cpu_daiops_set_sysclk, + .startup = qaif_aif_cpu_daiops_startup, + .shutdown = qaif_aif_cpu_daiops_shutdown, + .hw_params = qaif_aif_cpu_daiops_hw_params, + .trigger = qaif_aif_cpu_daiops_trigger, +}; +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_aif_cpu_dai_ops); + +static const struct snd_soc_component_driver qaif_cpu_comp_driver = { + .name = "qaif-cpu", + .of_xlate_dai_name = snd_soc_of_xlate_dai_name, +}; + +static const struct regmap_config audio_qaif_regmap_config = { + .name = "audio_qaif_cpu", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .cache_type = REGCACHE_NONE, +}; + +static int of_qaif_parse_aif_intf_cfg(struct device *dev, + struct qaif_drv_data *data) +{ + const struct qaif_variant *v = data->variant; + struct device_node *np = dev->of_node; + struct device_node *intf_np; + struct qaif_aif_config *cfg; + int ret; + u32 dai_id; + int intf_idx; + int num_interfaces = 0; + u32 val_buf[QAIF_MAX_LANES]; + int j, n; + unsigned long configured_intf_mask = 0; + + for_each_child_of_node(np, intf_np) { + if (!of_property_present(intf_np, "reg")) + continue; + + if (num_interfaces >= QAIF_MAX_AIF_CFG_CNT) { + dev_warn(dev, "Too many AIF interfaces, limiting to %d\n", + QAIF_MAX_AIF_CFG_CNT); + of_node_put(intf_np); + break; + } + + ret = of_property_read_u32(intf_np, "reg", &dai_id); + if (ret) { + dev_err(dev, "Missing reg for interface %pOFn\n", intf_np); + of_node_put(intf_np); + return ret; + } + + intf_idx = v->get_dma_idx(dai_id); + if (intf_idx < 0) { + dev_err(dev, "Invalid DAI ID %d for interface %pOFn\n", + dai_id, intf_np); + of_node_put(intf_np); + return -EINVAL; + } + if (intf_idx >= ARRAY_SIZE(data->aif_intf_cfg)) { + dev_err(dev, "DAI ID %d maps to out-of-range intf_idx %d\n", + dai_id, intf_idx); + of_node_put(intf_np); + return -EINVAL; + } + + if (configured_intf_mask & BIT(intf_idx)) { + dev_err(dev, "Duplicate reg %d for interface %pOFn\n", + dai_id, intf_np); + of_node_put(intf_np); + return -EINVAL; + } + configured_intf_mask |= BIT(intf_idx); + + cfg = &data->aif_intf_cfg[intf_idx]; + if (v->aif_full_cycle_en) + cfg->full_cycle_en = v->aif_full_cycle_en[intf_idx]; + if (v->aif_ctrl_data_oe) + cfg->ctrl_data_oe = v->aif_ctrl_data_oe[intf_idx]; + if (v->aif_loopback_en) + cfg->loopback_en = v->aif_loopback_en[intf_idx]; + + switch (snd_soc_daifmt_parse_format(intf_np, NULL) & + SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_LONG; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + cfg->invert_sync = true; + break; + case SND_SOC_DAIFMT_DSP_A: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_ONE_SLOT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + break; + case SND_SOC_DAIFMT_DSP_B: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_SHORT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_SAME; + break; + default: + break; + } + + if (!qaif_is_aif_mi2s_port(dai_id)) { + ret = of_property_read_u32(intf_np, "dai-tdm-slot-width", + &cfg->slot_width); + if (ret || !cfg->slot_width) { + dev_err(dev, "Missing/invalid dai-tdm-slot-width for %pOFn\n", + intf_np); + of_node_put(intf_np); + return ret ? ret : -EINVAL; + } + ret = of_property_read_u32(intf_np, "dai-tdm-slot-num", + &cfg->slot_num); + if (ret || !cfg->slot_num) { + dev_err(dev, "Missing/invalid dai-tdm-slot-num for %pOFn\n", + intf_np); + of_node_put(intf_np); + return ret ? ret : -EINVAL; + } + snd_soc_of_get_slot_mask(intf_np, "dai-tdm-slot-rx-mask", + &cfg->slot_en_rx_mask); + snd_soc_of_get_slot_mask(intf_np, "dai-tdm-slot-tx-mask", + &cfg->slot_en_tx_mask); + if (!cfg->slot_en_rx_mask && !cfg->slot_en_tx_mask) { + dev_err(dev, "No active TDM slots for %pOFn\n", + intf_np); + of_node_put(intf_np); + return -EINVAL; + } + } + + n = of_property_read_variable_u32_array(intf_np, + "qcom,qaif-aif-lane-map", + val_buf, 1, QAIF_MAX_LANES); + if (n < 0) { + dev_err(dev, "Missing qcom,qaif-aif-lane-map for %pOFn\n", + intf_np); + of_node_put(intf_np); + return n; + } + for (j = 0; j < n; j++) { + if (val_buf[j] > 1) { + dev_err(dev, "Invalid lane-map value %u for %pOFn\n", + val_buf[j], intf_np); + of_node_put(intf_np); + return -EINVAL; + } + cfg->lane_en_mask |= BIT(j); + if (val_buf[j]) + cfg->lane_dir_mask |= BIT(j); + } + + num_interfaces++; + } + + return 0; +} + +/** + * asoc_qcom_qaif_cpu_platform_probe - Probe the QAIF CPU and platform driver + * @pdev: Platform device + * + * Initialises the QAIF regmap, parses DT, sets up clocks and registers + * the CPU DAI component and PCM platform. + * + * Return: 0 on success, negative error code on failure. + */ +int asoc_qcom_qaif_cpu_platform_probe(struct platform_device *pdev) +{ + struct regmap_config regmap_cfg = audio_qaif_regmap_config; + struct qaif_drv_data *drvdata; + struct resource *res; + const struct qaif_variant *variant; + struct device *dev = &pdev->dev; + const struct of_device_id *match; + int ret, i, dai_id, idx; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return dev_err_probe(dev, -ENOMEM, "Failed to allocate driver data\n"); + platform_set_drvdata(pdev, drvdata); + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) + return dev_err_probe(dev, -EINVAL, "No matching device data\n"); + + drvdata->variant = match->data; + variant = drvdata->variant; + + if (!variant->get_dma_idx) + return dev_err_probe(dev, -EINVAL, + "variant is missing get_dma_idx callback\n"); + + ret = of_qaif_parse_aif_intf_cfg(dev, drvdata); + if (ret) + return dev_err_probe(dev, ret, "Failed to parse aif interfaces\n"); + + drvdata->audio_qaif = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(drvdata->audio_qaif)) + return dev_err_probe(dev, PTR_ERR(drvdata->audio_qaif), + "Failed to ioremap QAIF registers\n"); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return dev_err_probe(dev, -EINVAL, "Failed to get MMIO resource\n"); + + regmap_cfg.max_register = resource_size(res) - regmap_cfg.reg_stride; + drvdata->audio_qaif_map = devm_regmap_init_mmio(dev, drvdata->audio_qaif, + ®map_cfg); + if (IS_ERR(drvdata->audio_qaif_map)) + return dev_err_probe(dev, PTR_ERR(drvdata->audio_qaif_map), + "Failed to init regmap\n"); + + drvdata->aud_dma_clk = devm_clk_get(dev, "aud_dma"); + if (IS_ERR(drvdata->aud_dma_clk)) + return dev_err_probe(dev, PTR_ERR(drvdata->aud_dma_clk), + "Failed to get aud_dma clock\n"); + + drvdata->aud_dma_mem_clk = devm_clk_get(dev, "aud_dma_mem"); + if (IS_ERR(drvdata->aud_dma_mem_clk)) + return dev_err_probe(dev, PTR_ERR(drvdata->aud_dma_mem_clk), + "Failed to get aud_dma_mem clock\n"); + + for (i = 0; i < variant->num_dai; i++) { + dai_id = variant->dai_driver[i].id; + if (qaif_is_cif_port(dai_id)) + continue; + idx = variant->get_dma_idx(dai_id); + if (idx < 0) + continue; + + if (drvdata->mi2s_bit_clk[idx]) + continue; + + drvdata->mi2s_bit_clk[idx] = + devm_clk_get(dev, variant->dai_bit_clk_names[idx]); + if (IS_ERR(drvdata->mi2s_bit_clk[idx])) + return dev_err_probe(dev, PTR_ERR(drvdata->mi2s_bit_clk[idx]), + "Failed to get bit clock %d\n", idx); + } + + drvdata->num_clks = variant->num_clks; + drvdata->clks = devm_kcalloc(dev, drvdata->num_clks, + sizeof(*drvdata->clks), GFP_KERNEL); + if (!drvdata->clks) + return dev_err_probe(dev, -ENOMEM, "Failed to allocate clocks\n"); + + for (i = 0; i < drvdata->num_clks; i++) + drvdata->clks[i].id = variant->clk_name[i]; + + ret = devm_clk_bulk_get(dev, drvdata->num_clks, drvdata->clks); + if (ret) + return dev_err_probe(dev, ret, "Failed to get clocks\n"); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + ret = devm_snd_soc_register_component(dev, &qaif_cpu_comp_driver, + variant->dai_driver, variant->num_dai); + if (ret) + return dev_err_probe(dev, ret, "error registering cpu driver\n"); + + return asoc_qcom_qaif_platform_register(pdev); +} +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_cpu_platform_probe); + +static int asoc_qcom_qaif_runtime_suspend(struct device *dev) +{ + struct qaif_drv_data *drvdata = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks); + return 0; +} + +static int asoc_qcom_qaif_runtime_resume(struct device *dev) +{ + struct qaif_drv_data *drvdata = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks); +} + +EXPORT_GPL_DEV_PM_OPS(asoc_qcom_qaif_pm_ops) = { + RUNTIME_PM_OPS(asoc_qcom_qaif_runtime_suspend, + asoc_qcom_qaif_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) CPU DAI driver"); +MODULE_AUTHOR("Harendra Gautam "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qaif-platform.c b/sound/soc/qcom/qaif-platform.c new file mode 100644 index 000000000000..facba3be2d8e --- /dev/null +++ b/sound/soc/qcom/qaif-platform.c @@ -0,0 +1,885 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-platform.c -- ALSA SoC PCM platform driver for the Qualcomm Audio Interface (QAIF) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qaif-reg.h" +#include "qaif.h" + +#define DRV_NAME "qaif-platform" + +/* period: 5ms @ 48kHz/1ch/S16 min, 20ms @ 48kHz/8ch/S32 max */ +#define QAIF_PLATFORM_PERIOD_BYTES_MIN (48 * 5 * 1 * 2) /* 480 B */ +#define QAIF_PLATFORM_PERIOD_BYTES_MAX (48 * 20 * 8 * 4) /* 30720 B */ +#define QAIF_PLATFORM_PERIODS_MIN 2 +#define QAIF_PLATFORM_PERIODS_MAX 4 +/* QAIF_PLATFORM_PERIODS_MAX * QAIF_PLATFORM_PERIOD_BYTES_MAX = 122880 B */ +#define QAIF_PLATFORM_BUFFER_BYTES_MAX \ + (QAIF_PLATFORM_PERIODS_MAX * QAIF_PLATFORM_PERIOD_BYTES_MAX) + +/* DMA CFG register bit positions */ +#define QAIF_DMACFG_SHRAM_WM_MASK GENMASK(11, 0) +#define QAIF_DMACFG_BURST4_BIT BIT(18) +#define QAIF_DMACFG_DYNCLK_BIT BIT(24) + +/* DMA CTL register bit positions */ +#define QAIF_DMACTL_ENABLE_BIT BIT(0) + +/* DMA SHUTDOWN_STATUS register bit positions */ +#define QAIF_DMA_SHUTDOWN_IN_PROGRESS_BIT BIT(0) + +/* DMA shutdown takes ~10 bit clocks (~100us worst case at 8 kHz). */ +#define QAIF_DMA_SHUTDOWN_POLL_US 10 +#define QAIF_DMA_SHUTDOWN_TIMEOUT_US 1000 + +#define QAIF_ALL_CLIENTS_MASK (QAIF_BITMASK_AIF_RDDMA_WRDMA | \ + QAIF_BITMASK_CIF_RDDMA_WRDMA) + +static const struct snd_pcm_hardware qaif_platform_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = QAIF_PLATFORM_BUFFER_BYTES_MAX, + .period_bytes_min = QAIF_PLATFORM_PERIOD_BYTES_MIN, + .period_bytes_max = QAIF_PLATFORM_PERIOD_BYTES_MAX, + .periods_min = QAIF_PLATFORM_PERIODS_MIN, + .periods_max = QAIF_PLATFORM_PERIODS_MAX, + .fifo_size = 0, +}; + +static int qaif_map_ee_resource(struct qaif_drv_data *drvdata) +{ + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + int ret; + + ret = regmap_write(map, QAIF_EE_RDDMA_MAP_REG(v), GENMASK(v->num_rddma - 1, 0)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_EE_WRDMA_MAP_REG(v), GENMASK(v->num_wrdma - 1, 0)); + if (ret) + return ret; + if (v->num_intf > 0) { + ret = regmap_write(map, QAIF_EE_INTF_MAP_REG(v), GENMASK(v->num_intf - 1, 0)); + if (ret) + return ret; + } + ret = regmap_write(map, QAIF_EE_CODEC_RDDMA_MAP_REG(v), + GENMASK(v->num_codec_rddma - 1, 0)); + if (ret) + return ret; + return regmap_write(map, QAIF_EE_CODEC_WRDMA_MAP_REG(v), + GENMASK(v->num_codec_wrdma - 1, 0)); +} + +static int qaif_map_dma_path(struct qaif_drv_data *drvdata) +{ + struct regmap *map = drvdata->audio_qaif_map; + int ret; + + if (drvdata->variant->qxm_type != QAIF_QXM0) { + dev_err(regmap_get_device(map), + "only QXM0 is supported, qxm_type=%d\n", + drvdata->variant->qxm_type); + return -EINVAL; + } + + ret = regmap_write(map, QAIF_RDDMA_MAP_QXM, QAIF_QXM0); + if (ret) + return ret; + ret = regmap_write(map, QAIF_WRDMA_MAP_QXM, QAIF_QXM0); + if (ret) + return ret; + ret = regmap_write(map, QAIF_CDC_RDDMA_MAP_QXM, QAIF_QXM0); + if (ret) + return ret; + return regmap_write(map, QAIF_CDC_WRDMA_MAP_QXM, QAIF_QXM0); +} + +static int qaif_config_shram(struct qaif_drv_data *drvdata) +{ + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + u32 start_addr, shram_len; + int ret, i; + + if (v->qxm_type != QAIF_QXM0) { + dev_err(regmap_get_device(map), + "only QXM0 is supported, qxm_type=%d\n", + v->qxm_type); + return -EINVAL; + } + + start_addr = v->rddma_shram_start_addr[QAIF_AIF_DMA]; + shram_len = v->rddma_shram_len; + for (i = 0; i < v->num_rddma; i++) { + ret = regmap_write(map, QAIF_RDDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_RDDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + + start_addr = v->wrdma_shram_start_addr[QAIF_AIF_DMA]; + shram_len = v->wrdma_shram_len; + for (i = 0; i < v->num_wrdma; i++) { + ret = regmap_write(map, QAIF_WRDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_WRDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + + start_addr = v->rddma_shram_start_addr[QAIF_CIF_DMA]; + shram_len = v->rddma_shram_len; + for (i = 0; i < v->num_codec_rddma; i++) { + ret = regmap_write(map, QAIF_CDC_RDDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_CDC_RDDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + + start_addr = v->wrdma_shram_start_addr[QAIF_CIF_DMA]; + shram_len = v->wrdma_shram_len; + for (i = 0; i < v->num_codec_wrdma; i++) { + ret = regmap_write(map, QAIF_CDC_WRDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_CDC_WRDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + return 0; +} + +static int qaif_init(struct snd_soc_component *component) +{ + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + int ret; + + if (drvdata->qaif_hw_configured) + return 0; + + ret = qaif_config_shram(drvdata); + if (ret) { + dev_err(component->dev, "failed to config shram: %d\n", ret); + return ret; + } + + ret = qaif_map_ee_resource(drvdata); + if (ret) { + dev_err(component->dev, "failed to map EE resources: %d\n", ret); + return ret; + } + + ret = qaif_map_dma_path(drvdata); + if (ret) + return ret; + + drvdata->qaif_hw_configured = true; + return 0; +} + +static int qaif_platform_pcmops_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + unsigned int dai_id = cpu_dai->driver->id; + struct qaif_pcm_data *data; + int ret, stream_dma_idx, dma_reg_idx, dir = substream->stream; + + dma_reg_idx = v->get_dma_idx(dai_id); + if (dma_reg_idx < 0) { + dev_err(component->dev, "invalid DMA register index for DAI %u: %d\n", + dai_id, dma_reg_idx); + return dma_reg_idx; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + runtime->private_data = data; + + mutex_lock(&drvdata->stream_lock); + stream_dma_idx = v->alloc_stream_dma_idx(drvdata, dir, dai_id); + mutex_unlock(&drvdata->stream_lock); + if (stream_dma_idx < 0) { + ret = stream_dma_idx; + goto err_free_data; + } + + data->stream_dma_idx = stream_dma_idx; + data->dma_reg_idx = dma_reg_idx; + + if (qaif_is_cif_port(dai_id)) + WRITE_ONCE(drvdata->cif_substream[stream_dma_idx], substream); + else + WRITE_ONCE(drvdata->aif_substream[stream_dma_idx], substream); + + snd_soc_set_runtime_hwparams(substream, &qaif_platform_hardware); + + /* + * The DMA buffer/period length registers are programmed in 64-bit + * (8-byte) words, so constrain buffer and period sizes to that step + * to keep the ALSA and hardware sizes identical. + */ + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + BIT(QAIF_DMA_BYTES_TO_WORDS_SHIFT)); + if (ret >= 0) + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + BIT(QAIF_DMA_BYTES_TO_WORDS_SHIFT)); + if (ret >= 0) + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(soc_runtime->dev, "setting constraints failed: %d\n", ret); + if (qaif_is_cif_port(dai_id)) + WRITE_ONCE(drvdata->cif_substream[stream_dma_idx], NULL); + else + WRITE_ONCE(drvdata->aif_substream[stream_dma_idx], NULL); + mutex_lock(&drvdata->stream_lock); + v->free_stream_dma_idx(drvdata, stream_dma_idx, dai_id); + mutex_unlock(&drvdata->stream_lock); + goto err_free_data; + } + return 0; + +err_free_data: + kfree(data); + return ret; +} + +static int qaif_platform_pcmops_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + + if (qaif_is_cif_port(dai_id)) + WRITE_ONCE(drvdata->cif_substream[data->stream_dma_idx], NULL); + else + WRITE_ONCE(drvdata->aif_substream[data->stream_dma_idx], NULL); + + mutex_lock(&drvdata->stream_lock); + v->free_stream_dma_idx(drvdata, data->stream_dma_idx, dai_id); + mutex_unlock(&drvdata->stream_lock); + + substream->runtime->private_data = NULL; + kfree(data); + return 0; +} + +static int qaif_platform_pcmops_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + int idx = data->dma_reg_idx; + int ret; + + mutex_lock(&drvdata->stream_lock); + ret = qaif_init(component); + mutex_unlock(&drvdata->stream_lock); + if (ret) { + dev_err(soc_runtime->dev, "qaif_init failed: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(drvdata->audio_qaif_map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), + QAIF_DMACFG_BURST4_BIT | QAIF_DMACFG_SHRAM_WM_MASK, + QAIF_DMACFG_BURST4_BIT | QAIF_DMACTL_WM_5); + if (ret) + dev_err(soc_runtime->dev, "error updating DMA CFG: %d\n", ret); + return ret; +} + +static int qaif_platform_pcmops_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + int idx = data->dma_reg_idx; + int ret; + + ret = regmap_update_bits(drvdata->audio_qaif_map, + qaif_dmactl_reg(v, idx, substream->stream, dai_id), + QAIF_DMACTL_ENABLE_BIT, 0); + if (ret) { + dev_err(soc_runtime->dev, "error disabling DMA: %d\n", ret); + return ret; + } + + ret = regmap_write(drvdata->audio_qaif_map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), 0); + if (ret) + dev_err(soc_runtime->dev, "error clearing DMA CFG: %d\n", ret); + return ret; +} + +static int qaif_platform_pcmops_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + struct qaif_pcm_data *data = runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + int idx = data->dma_reg_idx; + int ret, dir = substream->stream; + + ret = clk_set_rate(drvdata->aud_dma_clk, QAIF_DMA_CLOCK_FREQ); + if (ret) { + dev_err(soc_runtime->dev, "error setting aud_dma_clk rate: %d\n", ret); + return ret; + } + ret = clk_set_rate(drvdata->aud_dma_mem_clk, QAIF_DMA_CLOCK_FREQ); + if (ret) { + dev_err(soc_runtime->dev, "error setting aud_dma_mem_clk rate: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_sid_map_reg(dir, dai_id), drvdata->smmu_csid_bits); + if (ret) { + dev_err(soc_runtime->dev, "error writing to SID MAP reg: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_dmabase_reg(v, idx, dir, dai_id), + lower_32_bits(runtime->dma_addr)); + if (ret) { + dev_err(soc_runtime->dev, "error writing to dma base reg: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_dmabuff_reg(v, idx, dir, dai_id), + (snd_pcm_lib_buffer_bytes(substream) >> + QAIF_DMA_BYTES_TO_WORDS_SHIFT) - 1); + if (ret) { + dev_err(soc_runtime->dev, "error writing to dma buf reg: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_dmaper_len_reg(v, idx, dir, dai_id), + (snd_pcm_lib_period_bytes(substream) >> + QAIF_DMA_BYTES_TO_WORDS_SHIFT) - 1); + if (ret) + dev_err(soc_runtime->dev, "error writing to dma period reg: %d\n", ret); + return ret; +} + +static snd_pcm_uframes_t qaif_platform_pcmops_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + unsigned int base_addr, curr_addr; + int idx = data->dma_reg_idx; + int ret, dir = substream->stream; + + ret = regmap_read(map, qaif_dmabase_reg(v, idx, dir, dai_id), &base_addr); + if (ret) { + dev_err(soc_runtime->dev, "error reading from rdmabase reg: %d\n", ret); + return SNDRV_PCM_POS_XRUN; + } + + ret = regmap_read(map, qaif_dmacurr_reg(v, idx, dir, dai_id), &curr_addr); + if (ret) { + dev_err(soc_runtime->dev, "error reading from rdmacurr reg: %d\n", ret); + return SNDRV_PCM_POS_XRUN; + } + + return bytes_to_frames(substream->runtime, + (curr_addr - base_addr) % + snd_pcm_lib_buffer_bytes(substream)); +} + +static int qaif_platform_pcmops_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(component->dev, vma, + runtime->dma_area, runtime->dma_addr, + runtime->dma_bytes); +} + +static int qaif_platform_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + + return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + component->dev, + QAIF_PLATFORM_BUFFER_BYTES_MAX); +} + +static int qaif_platform_irq_op(struct qaif_drv_data *drvdata, int dir, + enum qaif_irq_type irq_type, int idx, + enum qaif_irq_op op) +{ + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + unsigned int bit = BIT(idx); + unsigned int val = op == QAIF_IRQ_ENABLE ? bit : 0; + u32 per_reg, err_reg, xrun_reg; + int ret; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + per_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_RDDMA_PERIOD_IRQ_EN_REG(v, irq_type); + xrun_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_RDDMA_UF_IRQ_EN_REG(v, irq_type); + err_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_RDDMA_ERR_RSP_IRQ_EN_REG(v, irq_type); + } else { + per_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_WRDMA_PERIOD_IRQ_EN_REG(v, irq_type); + xrun_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_WRDMA_OF_IRQ_EN_REG(v, irq_type); + err_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_WRDMA_ERR_RSP_IRQ_EN_REG(v, irq_type); + } + + if (op == QAIF_IRQ_CLEAR) { + ret = regmap_write(map, per_reg, bit); + if (ret) + return ret; + ret = regmap_write(map, xrun_reg, bit); + if (ret) + return ret; + return regmap_write(map, err_reg, bit); + } + + ret = regmap_write_bits(map, per_reg, bit, val); + if (ret) + return ret; + ret = regmap_write_bits(map, xrun_reg, bit, val); + if (ret) + return ret; + return regmap_write_bits(map, err_reg, bit, val); +} + +static int qaif_platform_pcmops_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + enum qaif_irq_type irq_type = qaif_is_cif_port(dai_id) ? QAIF_CIF_IRQ : QAIF_AIF_IRQ; + struct regmap *map = drvdata->audio_qaif_map; + int idx = data->dma_reg_idx; + int ret = 0, irq_op_err = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + irq_op_err = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_CLEAR); + if (irq_op_err) { + dev_err(soc_runtime->dev, "error clearing irq regs: %d\n", irq_op_err); + break; + } + ret = regmap_update_bits(map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), + QAIF_DMACFG_DYNCLK_BIT, QAIF_DMACFG_DYNCLK_BIT); + if (ret) { + dev_err(soc_runtime->dev, "error enabling dma_dynclk: %d\n", ret); + break; + } + ret = regmap_update_bits(map, + qaif_dmactl_reg(v, idx, substream->stream, dai_id), + QAIF_DMACTL_ENABLE_BIT, QAIF_DMACTL_ENABLE_BIT); + if (ret) { + dev_err(soc_runtime->dev, "error enabling DMA: %d\n", ret); + break; + } + irq_op_err = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_ENABLE); + if (irq_op_err) + dev_err(soc_runtime->dev, "error enabling irq regs: %d\n", irq_op_err); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(map, + qaif_dmactl_reg(v, idx, substream->stream, dai_id), + QAIF_DMACTL_ENABLE_BIT, 0); + if (ret) { + dev_err(soc_runtime->dev, "error disabling DMA: %d\n", ret); + break; + } + ret = regmap_update_bits(map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), + QAIF_DMACFG_DYNCLK_BIT, 0); + if (ret) + dev_err(soc_runtime->dev, "error disabling dma_dynclk: %d\n", ret); + irq_op_err = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_DISABLE); + if (irq_op_err) + dev_err(soc_runtime->dev, "error disabling irq regs: %d\n", irq_op_err); + break; + default: + ret = -EINVAL; + break; + } + + return ret ? : irq_op_err; +} + +static int qaif_platform_pcmops_sync_stop(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + struct qaif_pcm_data *data = substream->runtime->private_data; + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + unsigned int dai_id = cpu_dai->driver->id; + enum qaif_irq_type irq_type = qaif_is_cif_port(dai_id) ? QAIF_CIF_IRQ : QAIF_AIF_IRQ; + int idx = data->dma_reg_idx; + unsigned int val; + int ret; + + /* + * Wait for the DMA channel to report shutdown complete before + * clearing its interrupts, so a fast STOP->START or suspend/resume + * does not race a channel still shutting down. + */ + ret = regmap_read_poll_timeout(map, + qaif_dma_shutdown_stat_reg(v, idx, + substream->stream, + dai_id), + val, + !(val & QAIF_DMA_SHUTDOWN_IN_PROGRESS_BIT), + QAIF_DMA_SHUTDOWN_POLL_US, + QAIF_DMA_SHUTDOWN_TIMEOUT_US); + if (ret) + dev_err(soc_runtime->dev, "timeout waiting for DMA shutdown: %d\n", ret); + + synchronize_irq(drvdata->audio_qaif_irq); + ret = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_CLEAR); + if (ret) + dev_err(soc_runtime->dev, "error clearing irq regs: %d\n", ret); + return ret; +} + +static irqreturn_t qaif_process_dma_irq(struct qaif_drv_data *drvdata, + u32 stat_reg_addr, + u32 clr_reg_addr, + enum qaif_irq_type irq_type, + enum qaif_dma_dir dma_type, + enum qaif_irq irq, + struct snd_pcm_substream **substream) +{ + const struct qaif_variant *v = drvdata->variant; + unsigned int reg = 0; + int dma_idx, stream_dma_idx, rv, num_dma, stream_offset, array_size; + u32 mask; + struct snd_pcm_substream *ss; + + stream_offset = (dma_type == QAIF_DMA_WRDMA) ? + ((irq_type == QAIF_AIF_IRQ) ? v->wrdma_start : v->codec_wrdma_start) : 0; + num_dma = (dma_type == QAIF_DMA_WRDMA) ? + ((irq_type == QAIF_AIF_IRQ) ? v->num_wrdma : v->num_codec_wrdma) : + ((irq_type == QAIF_AIF_IRQ) ? v->num_rddma : v->num_codec_rddma); + if (!num_dma) + return IRQ_NONE; + + array_size = (irq_type == QAIF_AIF_IRQ) ? + QAIF_MAX_AIF_DMA_IDX : QAIF_MAX_CIF_DMA_IDX; + if (stream_offset + num_dma > array_size) { + dev_err(regmap_get_device(drvdata->audio_qaif_map), + "DMA index range %d+%d exceeds substream table %d\n", + stream_offset, num_dma, array_size); + return IRQ_NONE; + } + mask = GENMASK(num_dma - 1, 0); + + rv = regmap_read(drvdata->audio_qaif_map, stat_reg_addr, ®); + if (rv) { + dev_err_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "error reading stat reg 0x%x: %d\n", stat_reg_addr, rv); + return IRQ_NONE; + } + + if (!(reg & mask)) { + dev_warn_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "spurious IRQ: stat reg 0x%x status 0x%x mask 0x%x\n", + stat_reg_addr, reg, mask); + return IRQ_NONE; + } + + if (regmap_write(drvdata->audio_qaif_map, clr_reg_addr, reg & mask)) + return IRQ_NONE; + + for (dma_idx = 0; dma_idx < num_dma; dma_idx++) { + stream_dma_idx = dma_idx + stream_offset; + ss = READ_ONCE(substream[stream_dma_idx]); + if (!(reg & BIT(dma_idx)) || !ss) + continue; + switch (irq) { + case QAIF_IRQ_PERIOD: + snd_pcm_period_elapsed(ss); + break; + case QAIF_IRQ_OVERFLOW: + case QAIF_IRQ_UNDERFLOW: + dev_warn_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "QAIF DMA xRun\n"); + snd_pcm_stop_xrun(ss); + break; + case QAIF_IRQ_ERROR: + snd_pcm_stop_xrun(ss); + dev_err_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "QAIF bus error\n"); + break; + } + } + return IRQ_HANDLED; +} + +static irqreturn_t qaif_aif_irq_handler(struct qaif_drv_data *drvdata, u32 status) +{ + const struct qaif_variant *v = drvdata->variant; + struct snd_pcm_substream **ss = drvdata->aif_substream; + irqreturn_t ret = IRQ_NONE; + + if (status & QAIF_SUMMARY_BITMASK_AIF_PERIOD_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_PERIOD_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_OVERFLOW_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_OVERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_UNDERFLOW_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_UNDERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_ERROR, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_ERROR, ss); + return ret; +} + +static irqreturn_t qaif_cif_irq_handler(struct qaif_drv_data *drvdata, u32 status) +{ + const struct qaif_variant *v = drvdata->variant; + struct snd_pcm_substream **ss = drvdata->cif_substream; + irqreturn_t ret = IRQ_NONE; + + if (status & QAIF_SUMMARY_BITMASK_CIF_PERIOD_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_PERIOD_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_OVERFLOW_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_OVERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_UNDERFLOW_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_UNDERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_ERROR, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_ERROR, ss); + return ret; +} + +static const struct qaif_irq_map qaif_irq_clients[] = { + { QAIF_CLIENT_ID_AIF_DMA, QAIF_BITMASK_AIF_RDDMA_WRDMA, qaif_aif_irq_handler }, + { QAIF_CLIENT_ID_CIF_DMA, QAIF_BITMASK_CIF_RDDMA_WRDMA, qaif_cif_irq_handler }, +}; + +static irqreturn_t asoc_platform_qaif_irq(int irq, void *data) +{ + struct qaif_drv_data *drvdata = data; + const struct qaif_variant *v = drvdata->variant; + u32 summary_irq_status; + int rv, client; + irqreturn_t ret = IRQ_NONE; + + rv = regmap_read(drvdata->audio_qaif_map, QAIF_SUMMARY_IRQSTAT_REG(v), + &summary_irq_status); + if (rv) { + dev_err(regmap_get_device(drvdata->audio_qaif_map), + "error reading from irqstat reg: %d\n", rv); + return IRQ_NONE; + } + + if (!(summary_irq_status & QAIF_ALL_CLIENTS_MASK)) + return IRQ_NONE; + + for (client = 0; client < ARRAY_SIZE(qaif_irq_clients); client++) { + if (summary_irq_status & qaif_irq_clients[client].mask) + ret |= qaif_irq_clients[client].client_irq_handler(drvdata, + summary_irq_status); + } + return ret; +} + +static const struct snd_soc_component_driver qaif_component_driver = { + .name = DRV_NAME, + .pcm_new = qaif_platform_pcm_new, + .open = qaif_platform_pcmops_open, + .close = qaif_platform_pcmops_close, + .hw_params = qaif_platform_pcmops_hw_params, + .hw_free = qaif_platform_pcmops_hw_free, + .prepare = qaif_platform_pcmops_prepare, + .trigger = qaif_platform_pcmops_trigger, + .sync_stop = qaif_platform_pcmops_sync_stop, + .pointer = qaif_platform_pcmops_pointer, + .mmap = qaif_platform_pcmops_mmap, +}; + +int asoc_qcom_qaif_platform_register(struct platform_device *pdev) +{ + struct qaif_drv_data *drvdata = platform_get_drvdata(pdev); + struct of_phandle_args iommu_spec; + u32 sid; + int ret; + + if (!drvdata->variant->alloc_stream_dma_idx || + !drvdata->variant->free_stream_dma_idx) + return dev_err_probe(&pdev->dev, -EINVAL, + "Variant missing mandatory callbacks\n"); + + ret = of_parse_phandle_with_args(pdev->dev.of_node, + "iommus", "#iommu-cells", 0, + &iommu_spec); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to parse iommus property\n"); + sid = iommu_spec.args[0]; + of_node_put(iommu_spec.np); + drvdata->smmu_csid_bits = sid & QAIF_CSID_MASK; + + /* + * The DMA base register only holds the low 32 bits of the buffer + * address, so the coherent buffer must come from the low 32-bit + * address space. + */ + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to set 32-bit DMA mask\n"); + + drvdata->audio_qaif_irq = platform_get_irq(pdev, 0); + if (drvdata->audio_qaif_irq < 0) + return dev_err_probe(&pdev->dev, drvdata->audio_qaif_irq, + "Failed to get IRQ\n"); + + drvdata->qaif_hw_configured = false; + mutex_init(&drvdata->stream_lock); + + ret = devm_request_irq(&pdev->dev, drvdata->audio_qaif_irq, + asoc_platform_qaif_irq, 0, + "qaif-irq-audio-core", drvdata); + if (ret) + return dev_err_probe(&pdev->dev, ret, "irq request failed\n"); + + ret = devm_snd_soc_register_component(&pdev->dev, &qaif_component_driver, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register platform component\n"); + return 0; +} +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_platform_register); + +MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) PCM platform driver"); +MODULE_AUTHOR("Harendra Gautam "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qaif-reg.h b/sound/soc/qcom/qaif-reg.h new file mode 100644 index 000000000000..2ae26cf24c59 --- /dev/null +++ b/sound/soc/qcom/qaif-reg.h @@ -0,0 +1,470 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-reg.h -- QAIF ALSA SoC CPU-Platform DAI driver register header + */ +#ifndef SND_SOC_QCOM_QAIF_REG_H +#define SND_SOC_QCOM_QAIF_REG_H + +#include "qaif.h" + +#define QAIF_SUMMARY_IRQSTAT_REG(v) (0x19188 + (0x1000 * ((v)->ee))) + +/* Core HW info */ +#define QAIF_HW_VERSION_REG (0x0000) +#define QAIF_HW_INFO_REG (0x0004) +#define QAIF_HW_INFO2_REG (0x0008) + +/* Interface lane and channel info */ +#define QAIF_AUD_INTF_LANE_INFO_REG (0x0020) +#define QAIF_AUD_INTF_LANE_INFO2_REG (0x0024) +#define QAIF_CDC_TX_INTF_CH_INFO_REG(n) (0x0028 + (0x4 * (n))) +#define QAIF_CDC_RX_INTF_CH_INFO_REG(n) (0x0068 + (0x4 * (n))) +#define QAIF_QXM1_SHRAM_LENGTH_INFO_REG (0x0088) +#define QAIF_QXM0_SHRAM_LENGTH_INFO_REG (0x008C) +#define QAIF_NUM_AUD_INTF_TO_RAIL_INFO_REG (0x0090) + +/* Debug/control and status */ +#define QAIF_DEBUG_CTL_REG (0x0200) +#define QAIF_WRDMA_LOOPBACK_EN_REG (0x0204) +#define QAIF_WRDMA_LOOPBACK_SEL_REG (0x0208) +#define QAIF_SHRAM_DYNAMIC_CLK_GATING_EN_REG (0x0300) +#define QAIF_AXI_STATUS_REG (0x0304) +#define QAIF_QSB_DYNAMIC_CLK_GATING_EN_REG (0x0308) +#define QAIF_START_STOP_CTRL_BYPASS_EN_REG (0x030C) +#define QAIF_QXM0_AXI_ATTR_CFG_REG (0x040C) + +/* QXM request/grant debug */ +#define QAIF_QXM0_AUD_WR_REQ_GNT_DBG_STAT_REG (0x0500) +#define QAIF_QXM1_AUD_WR_REQ_GNT_DBG_STAT_REG (0x0504) +#define QAIF_QXM0_CODEC_RX_WR_REQ_DBG_STAT_REG (0x0508) +#define QAIF_QXM0_CODEC_RX_WR_GNT_DBG_STAT_REG (0x050C) +#define QAIF_QXM1_CODEC_RX_WR_REQ_DBG_STAT_REG (0x0510) +#define QAIF_QXM1_CODEC_RX_WR_GNT_DBG_STAT_REG (0x0514) +#define QAIF_QXM0_AUD_RD_REQ_GNT_DBG_STAT_REG (0x0518) +#define QAIF_QXM1_AUD_RD_REQ_GNT_DBG_STAT_REG (0x051C) +#define QAIF_QXM0_CODEC_TX_RD_REQ_DBG_STAT_REG (0x0520) +#define QAIF_QXM0_CODEC_TX_RD_GNT_DBG_STAT_REG (0x0524) +#define QAIF_QXM1_CODEC_TX_RD_REQ_DBG_STAT_REG (0x0528) +#define QAIF_QXM1_CODEC_TX_RD_GNT_DBG_STAT_REG (0x052C) +#define QAIF_QXM0_EXT_RDDMA_RD_REQ_GNT_DBG_STAT_REG (0x0530) +#define QAIF_QXM1_EXT_RDDMA_RD_REQ_GNT_DBG_STAT_REG (0x0534) + +/* QSB transaction debug */ +#define QAIF_QSB_AUD_WR_TXN_DBG_STAT_REG (0x0538) +#define QAIF_QSB_CODEC_RX_WR_TXN_ERR_DBG_STAT_REG (0x053C) +#define QAIF_QSB_CODEC_RX_WR_TXN_OKAY_DBG_STAT_REG (0x0540) +#define QAIF_QSB_AUD_ADDR_SENT_DBG_STAT_REG (0x0544) +#define QAIF_QSB_CODEC_TX_RD_ADDR_SENT_DBG_STAT_REG (0x0548) +#define QAIF_QSB_EXT_RDDMA_RD_ADDR_SENT_DBG_STAT_REG (0x054C) +#define QAIF_QSB_CODEC_RX_WR_ADDR_SENT_DBG_STAT_REG (0x0550) +#define QAIF_QSB_AUD_RD_TXN_DBG_STAT_REG (0x0554) +#define QAIF_QSB_CODEC_TX_RD_TXN_ERR_DBG_STAT_REG (0x0558) +#define QAIF_QSB_CODEC_TX_RD_TXN_RCVD_DBG_STAT_REG (0x055C) +#define QAIF_QSB_EXT_RDDMA_RD_TXN_DBG_STAT_REG (0x0560) +#define QAIF_QSB_MISC_DBG_STATUS_REG (0x0564) + +/* Global spare and HWE */ +#define QAIF_GLOBAL_SPARE_IN_REG (0x0B00) +#define QAIF_GLOBAL_SPARE_OUT_REG (0x0B04) +#define QAIF_HWE_CFG_REG (0x0B08) + +/* SID maps */ +#define QAIF_WRDMA_SID_MAP_REG (0x1B00) +#define QAIF_CDC_WRDMA_SID_MAP_REG (0x1B40) +#define QAIF_RDDMA_SID_MAP_REG (0x1C00) +#define QAIF_CDC_RDDMA_SID_MAP_REG (0x1C40) + +/* EE overlap interrupts */ +#define QAIF_EE_OVERLAP_IRQ_EN_REG (0x1D00) +#define QAIF_EE_OVERLAP_IRQ_RAW_STATUS_REG (0x1D04) +#define QAIF_EE_OVERLAP_IRQ_CLEAR_REG (0x1D08) +#define QAIF_EE_OVERLAP_IRQ_FORCE_REG (0x1D0C) + +/* + * EE (Execution Engine) assignment and map registers. + * + * The EE offset (v->ee) is an address/index offset relative to the + * platform-defined QAIF/EE base and lets the hardware route the AIF + * operation to the correct EE context. It is hardware/platform-specific and + * must match the mapping defined for the selected QAIF AIF/lane in the + * hardware programming guide. It is not a runtime data offset and must not + * be changed based on the stream configuration. + */ +#define QAIF_EE_RDDMA_ASSIGNMENT_REG(v) (0x19148 + (0x1000 * ((v)->ee))) +#define QAIF_EE_WRDMA_ASSIGNMENT_REG(v) (0x19150 + (0x1000 * ((v)->ee))) +#define QAIF_EE_INTF_ASSIGNMENT_REG(v) (0x19158 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_RDDMA_ASSIGN_REG(v) (0x19308 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_WRDMA_ASSIGN_REG(v) (0x19318 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RDDMA_MAP_REG(v) (0x1920 + (0x1000 * ((v)->ee))) +#define QAIF_EE_WRDMA_MAP_REG(v) (0x1940 + (0x1000 * ((v)->ee))) +#define QAIF_EE_INTF_MAP_REG(v) (0x1960 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_RDDMA_MAP_REG(v) (0x1980 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_WRDMA_MAP_REG(v) (0x1A00 + (0x1000 * ((v)->ee))) + +/* EE rate-detection and VFR interrupts */ +#define QAIF_EE_RATE_DET_IRQ_EN_REG(v) (0x190F0 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_STAT_REG(v) (0x190F4 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_RAW_STAT_REG(v) (0x190F8 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_CLEAR_REG(v) (0x190FC + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_FORCE_REG(v) (0x19100 + (0x1000 * ((v)->ee))) + +#define QAIF_EE_VFR_IRQ_EN_REG(v) (0x19104 + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_STATUS_REG(v) (0x19108 + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_RAW_STATUS_REG(v) (0x1910C + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_CLEAR_REG(v) (0x19110 + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_FORCE_REG(v) (0x19114 + (0x1000 * ((v)->ee))) + +/* EE AUD_INTF underflow/overflow interrupts */ +#define QAIF_EE_AUD_INTF_UF_IRQ_EN_REG(v) (0x19160 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_STAT_REG(v) (0x19164 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_RAW_STAT_REG(v) (0x19168 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_CLEAR_REG(v) (0x1916C + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_FORCE_REG(v) (0x19170 + (0x1000 * ((v)->ee))) + +#define QAIF_EE_AUD_INTF_OF_IRQ_EN_REG(v) (0x19174 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_STAT_REG(v) (0x19178 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_RAW_STAT_REG(v) (0x1917C + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_CLEAR_REG(v) (0x19180 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_FORCE_REG(v) (0x19184 + (0x1000 * ((v)->ee))) + +/* EE L2 Period IRQ mux selection */ +#define QAIF_EE_L2_PERIOD_IRQ_0_3_MUX_SEL_REG(v) (0x19F00 + (0x1000 * ((v)->ee))) +#define QAIF_EE_L2_PERIOD_IRQ_4_7_MUX_SEL_REG(v) (0x19F04 + (0x1000 * ((v)->ee))) + +/* AUD_INTF block (per interface, stride 0x1000 starting at 0x4000) */ +#define QAIF_AUD_INTF_REG_ADDR(offset, i) (0x4000 + (offset) + (0x1000 * (i))) + +#define QAIF_AUD_INTF_CTL_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0000, (i)) +#define QAIF_AUD_INTF_SYNC_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0004, (i)) +#define QAIF_AUD_INTF_BIT_WIDTH_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0008, (i)) +#define QAIF_AUD_INTF_FRAME_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x000C, (i)) +#define QAIF_AUD_INTF_ACTV_SLOT_EN_TX_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0010, (i)) +#define QAIF_AUD_INTF_ACTV_SLOT_EN_RX_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0030, (i)) +#define QAIF_AUD_INTF_LANE_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0050, (i)) +#define QAIF_AUD_INTF_MI2S_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0054, (i)) +#define QAIF_AUD_INTF_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0058, (i)) +#define QAIF_AUD_INTF_CHAR_CTL_REG(i) QAIF_AUD_INTF_REG_ADDR(0x005C, (i)) +#define QAIF_AUD_INTF_CHAR_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0060, (i)) +#define QAIF_AUD_INTF_CHAR_DATA_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0064, (i)) +#define QAIF_AUD_INTF_CHAR_DATA_EXT_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0068, (i)) +#define QAIF_AUD_INTF_CHAR_SYNC_REG(i) QAIF_AUD_INTF_REG_ADDR(0x006C, (i)) +#define QAIF_AUD_INTF_INIT_DBG_STATUS_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0FF0, (i)) +#define QAIF_AUD_INTF_TX_DBG_STATUS_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0FF4, (i)) +#define QAIF_AUD_INTF_RX_DBG_STATUS_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0FF8, (i)) + +/* RATE_DET block (per detector, stride 0x1000 starting at 0x1E000) */ +#define QAIF_RATE_DET_REG_ADDR(offset, det) (0x1E000 + (offset) + (0x1000 * (det))) + +#define QAIF_RATE_DET_CONFIG_REG(det) QAIF_RATE_DET_REG_ADDR(0x0000, (det)) +#define QAIF_RATE_DET_TARGET1_CONF_REG(det) QAIF_RATE_DET_REG_ADDR(0x0004, (det)) +#define QAIF_RATE_DET_TARGET2_CONF_REG(det) QAIF_RATE_DET_REG_ADDR(0x0008, (det)) +#define QAIF_RATE_DET_BIN_REG(det) QAIF_RATE_DET_REG_ADDR(0x000C, (det)) +#define QAIF_RATE_DET_STC_DIFF_REG(det) QAIF_RATE_DET_REG_ADDR(0x0010, (det)) +#define QAIF_RATE_DET_SEL_REG(det) QAIF_RATE_DET_REG_ADDR(0x0014, (det)) +#define QAIF_RATE_DET_TIMEOUT_CFG_REG(det) QAIF_RATE_DET_REG_ADDR(0x0018, (det)) + +#define QAIF_WRDMA_MAP_QXM (0x1000) +#define QAIF_CDC_WRDMA_MAP_QXM (0x1004) +#define QAIF_RDDMA_MAP_QXM (0x1010) +#define QAIF_CDC_RDDMA_MAP_QXM (0x1014) +#define QAIF_RDDMA_QXM1_SHRAM_ST_ADDR(i) (0x1100 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM1_SHRAM_ST_ADDR(i) (0x1140 + (0x4 * (i))) +#define QAIF_RDDMA_QXM0_SHRAM_ST_ADDR(i) (0x1200 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM0_SHRAM_ST_ADDR(i) (0x1240 + (0x4 * (i))) +#define QAIF_RDDMA_QXM1_SHRAM_LEN(i) (0x1300 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM1_SHRAM_LEN(i) (0x1340 + (0x4 * (i))) +#define QAIF_RDDMA_QXM0_SHRAM_LEN(i) (0x1400 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM0_SHRAM_LEN(i) (0x1440 + (0x4 * (i))) +#define QAIF_WRDMA_QXM1_SHRAM_ST_ADDR(i) (0x1500 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM1_SHRAM_ST_ADDR(i) (0x1540 + (0x4 * (i))) +#define QAIF_WRDMA_QXM0_SHRAM_ST_ADDR(i) (0x1600 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM0_SHRAM_ST_ADDR(i) (0x1640 + (0x4 * (i))) +#define QAIF_WRDMA_QXM1_SHRAM_LEN(i) (0x1700 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM1_SHRAM_LEN(i) (0x1740 + (0x4 * (i))) +#define QAIF_WRDMA_QXM0_SHRAM_LEN(i) (0x1800 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM0_SHRAM_LEN(i) (0x1840 + (0x4 * (i))) + +/* RDDMA */ +static inline u32 qaif_rddma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->rddma_reg_base + offset + v->rddma_stride * i; +} + +#define QAIF_RDDMA_CTL_REG(v, i) qaif_rddma_reg_addr(v, 0x00, (i)) +#define QAIF_RDDMA_CFG_REG(v, i) qaif_rddma_reg_addr(v, 0x04, (i)) +#define QAIF_RDDMA_BASE_ADDR_REG(v, i) qaif_rddma_reg_addr(v, 0x08, (i)) +#define QAIF_RDDMA_BUFF_LEN_REG(v, i) qaif_rddma_reg_addr(v, 0x10, (i)) +#define QAIF_RDDMA_CURR_ADDR_REG(v, i) qaif_rddma_reg_addr(v, 0x14, (i)) +#define QAIF_RDDMA_PERIOD_LEN_REG(v, i) qaif_rddma_reg_addr(v, 0x1C, (i)) +#define QAIF_RDDMA_PERIOD_CNT_REG(v, i) qaif_rddma_reg_addr(v, 0x20, (i)) +#define QAIF_RDDMA_SHRAM_WORDCNT_REG(v, i) qaif_rddma_reg_addr(v, 0x24, (i)) +#define QAIF_RDDMA_FRAME_STATUS_REG(v, i) qaif_rddma_reg_addr(v, 0x28, (i)) +#define QAIF_RDDMA_FRAME_STATUS_EXTN_REG(v, i) qaif_rddma_reg_addr(v, 0x2C, (i)) +#define QAIF_RDDMA_FRAME_STATUS_CLR_REG(v, i) qaif_rddma_reg_addr(v, 0x30, (i)) +#define QAIF_RDDMA_SET_BUFF_CNT_REG(v, i) qaif_rddma_reg_addr(v, 0x34, (i)) +#define QAIF_RDDMA_SET_PERIOD_CNT_REG(v, i) qaif_rddma_reg_addr(v, 0x38, (i)) +#define QAIF_RDDMA_STC_LSB_REG(v, i) qaif_rddma_reg_addr(v, 0x3C, (i)) +#define QAIF_RDDMA_STC_MSB_REG(v, i) qaif_rddma_reg_addr(v, 0x40, (i)) +#define QAIF_RDDMA_PERIOD_DET_STAT_REG(v, i) qaif_rddma_reg_addr(v, 0x44, (i)) +#define QAIF_RDDMA_PERIOD_DET_CLR_REG(v, i) qaif_rddma_reg_addr(v, 0x48, (i)) +#define QAIF_RDDMA_FORMAT_ERR_REG(v, i) qaif_rddma_reg_addr(v, 0x4C, (i)) +#define QAIF_RDDMA_AHB_BYPASS_REG(v, i) qaif_rddma_reg_addr(v, 0x50, (i)) +#define QAIF_RDDMA_SHUTDOWN_STAT_REG(v, i) qaif_rddma_reg_addr(v, 0x54, (i)) +#define QAIF_RDDMA_PADDING_CFG_REG(v, i) qaif_rddma_reg_addr(v, 0x58, (i)) +#define QAIF_RDDMA_STATUS_REG(v, i) qaif_rddma_reg_addr(v, 0x60, (i)) +#define QAIF_RDDMA_DBG_STATUS_REG(v, i) qaif_rddma_reg_addr(v, 0xFF0, (i)) + +static inline u32 qaif_codec_rddma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->codec_rddma_reg_base + offset + v->codec_rddma_stride * i; +} + +#define QAIF_CDC_RDDMA_CTL_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x00, (i)) +#define QAIF_CDC_RDDMA_CFG_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x04, (i)) +#define QAIF_CDC_RDDMA_BASE_ADDR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x08, (i)) +#define QAIF_CDC_RDDMA_BUFF_LEN_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x10, (i)) +#define QAIF_CDC_RDDMA_CURR_ADDR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x14, (i)) +#define QAIF_CDC_RDDMA_PERIOD_LEN_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x1C, (i)) +#define QAIF_CDC_RDDMA_PERIOD_CNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x20, (i)) +#define QAIF_CDC_RDDMA_SHRAM_WORDCNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x24, (i)) +#define QAIF_CDC_RDDMA_FRAME_STAT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x28, (i)) +#define QAIF_CDC_RDDMA_FRAME_STAT_EXTN_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x2C, (i)) +#define QAIF_CDC_RDDMA_FRAME_STAT_CLR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x30, (i)) +#define QAIF_CDC_RDDMA_SET_BUFF_CNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x34, (i)) +#define QAIF_CDC_RDDMA_SET_PERIOD_CNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x38, (i)) +#define QAIF_CDC_RDDMA_STC_LSB_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x3C, (i)) +#define QAIF_CDC_RDDMA_STC_MSB_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x40, (i)) +#define QAIF_CDC_RDDMA_PERIOD_DET_STAT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x44, (i)) +#define QAIF_CDC_RDDMA_PERIOD_DET_CLR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x48, (i)) +#define QAIF_CDC_RDDMA_FORMAT_ERR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x4C, (i)) +#define QAIF_CDC_RDDMA_AHB_BYPASS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x50, (i)) +#define QAIF_CDC_RDDMA_SHUTDOWN_STAT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x54, (i)) +#define QAIF_CDC_RDDMA_PADDING_CFG_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x58, (i)) +#define QAIF_CDC_RDDMA_INTF_CFG_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x5C, (i)) +#define QAIF_CDC_RDDMA_STATUS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x60, (i)) +#define QAIF_CDC_RDDMA_DBG_STATUS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0xFF0, (i)) +#define QAIF_CDC_RDDMA_INTF_DBG_STATUS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0xFF4, (i)) + +/* WRDMA */ +static inline u32 qaif_wrdma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->wrdma_reg_base + offset + v->wrdma_stride * i; +} + +#define QAIF_WRDMA_CTL_REG(v, i) qaif_wrdma_reg_addr(v, 0x00, (i)) +#define QAIF_WRDMA_CFG_REG(v, i) qaif_wrdma_reg_addr(v, 0x04, (i)) +#define QAIF_WRDMA_BASE_ADDR_REG(v, i) qaif_wrdma_reg_addr(v, 0x08, (i)) +#define QAIF_WRDMA_BUFF_LEN_REG(v, i) qaif_wrdma_reg_addr(v, 0x10, (i)) +#define QAIF_WRDMA_CURR_ADDR_REG(v, i) qaif_wrdma_reg_addr(v, 0x14, (i)) +#define QAIF_WRDMA_PERIOD_LEN_REG(v, i) qaif_wrdma_reg_addr(v, 0x1C, (i)) +#define QAIF_WRDMA_PERIOD_CNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x20, (i)) +#define QAIF_WRDMA_SHRAM_WORDCNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x24, (i)) +#define QAIF_WRDMA_FRAME_STATUS_REG(v, i) qaif_wrdma_reg_addr(v, 0x28, (i)) +#define QAIF_WRDMA_FRAME_STATUS_EXTN_REG(v, i) qaif_wrdma_reg_addr(v, 0x2C, (i)) +#define QAIF_WRDMA_FRAME_STATUS_CLR_REG(v, i) qaif_wrdma_reg_addr(v, 0x30, (i)) +#define QAIF_WRDMA_SET_BUFF_CNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x34, (i)) +#define QAIF_WRDMA_SET_PERIOD_CNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x38, (i)) +#define QAIF_WRDMA_STC_LSB_REG(v, i) qaif_wrdma_reg_addr(v, 0x3C, (i)) +#define QAIF_WRDMA_STC_MSB_REG(v, i) qaif_wrdma_reg_addr(v, 0x40, (i)) +#define QAIF_WRDMA_PERIOD_DET_STAT_REG(v, i) qaif_wrdma_reg_addr(v, 0x44, (i)) +#define QAIF_WRDMA_PERIOD_DET_CLR_REG(v, i) qaif_wrdma_reg_addr(v, 0x48, (i)) +#define QAIF_WRDMA_FORMAT_ERR_REG(v, i) qaif_wrdma_reg_addr(v, 0x4C, (i)) +#define QAIF_WRDMA_AHB_BYPASS_REG(v, i) qaif_wrdma_reg_addr(v, 0x50, (i)) +#define QAIF_WRDMA_SHUTDOWN_STAT_REG(v, i) qaif_wrdma_reg_addr(v, 0x54, (i)) +#define QAIF_WRDMA_DBG_STATUS_REG(v, i) qaif_wrdma_reg_addr(v, 0xFF0, (i)) + +static inline u32 qaif_codec_wrdma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->codec_wrdma_reg_base + offset + v->codec_wrdma_stride * i; +} + +#define QAIF_CDC_WRDMA_CTL_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x00, (i)) +#define QAIF_CDC_WRDMA_CFG_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x04, (i)) +#define QAIF_CDC_WRDMA_BASE_ADDR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x08, (i)) +#define QAIF_CDC_WRDMA_BUFF_LEN_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x10, (i)) +#define QAIF_CDC_WRDMA_CURR_ADDR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x14, (i)) +#define QAIF_CDC_WRDMA_PERIOD_LEN_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x1C, (i)) +#define QAIF_CDC_WRDMA_PERIOD_CNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x20, (i)) +#define QAIF_CDC_WRDMA_SHRAM_WORDCNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x24, (i)) +#define QAIF_CDC_WRDMA_FRAME_STAT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x28, (i)) +#define QAIF_CDC_WRDMA_FRAME_STAT_EXTN_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x2C, (i)) +#define QAIF_CDC_WRDMA_FRAME_STAT_CLR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x30, (i)) +#define QAIF_CDC_WRDMA_SET_BUFF_CNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x34, (i)) +#define QAIF_CDC_WRDMA_SET_PERIOD_CNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x38, (i)) +#define QAIF_CDC_WRDMA_STC_LSB_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x3C, (i)) +#define QAIF_CDC_WRDMA_STC_MSB_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x40, (i)) +#define QAIF_CDC_WRDMA_PERIOD_DET_STAT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x44, (i)) +#define QAIF_CDC_WRDMA_PERIOD_DET_CLR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x48, (i)) +#define QAIF_CDC_WRDMA_FORMAT_ERR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x4C, (i)) +#define QAIF_CDC_WRDMA_AHB_BYPASS_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x50, (i)) +#define QAIF_CDC_WRDMA_SHUTDOWN_STAT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x54, (i)) +#define QAIF_CDC_WRDMA_INTF_CFG_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x58, (i)) +#define QAIF_CDC_WRDMA_DBG_STATUS_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0xFF0, (i)) +#define QAIF_CDC_WRDMA_INTF_DBG_STATUS_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0xFF4, (i)) + +static inline u32 qaif_rddma_irq_reg_addr(const struct qaif_variant *v, + enum qaif_irq_type dma_type, + u32 offset) +{ + if (dma_type == QAIF_AIF_IRQ) + return v->rddma_irq_reg_base + offset + + v->rddma_irq_stride * v->ee; + return v->codec_rddma_irq_reg_base + offset + + v->codec_rddma_irq_stride * v->ee; +} + +/* RDDMA Period Interrupts */ +#define QAIF_EE_RDDMA_PERIOD_IRQ_EN_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x00) +#define QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x08) +#define QAIF_EE_RDDMA_PERIOD_IRQ_RAW_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x10) +#define QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x18) +#define QAIF_EE_RDDMA_PERIOD_IRQ_FORCE_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x20) +/* RDDMA Underflow Interrupts */ +#define QAIF_EE_RDDMA_UF_IRQ_EN_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x28) +#define QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x30) +#define QAIF_EE_RDDMA_UF_IRQ_RAW_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x38) +#define QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x40) +#define QAIF_EE_RDDMA_UF_IRQ_FORCE_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x48) +/* RDDMA Error Response Interrupts */ +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_EN_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x50) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x58) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_RAW_STAT(v, i) qaif_rddma_irq_reg_addr(v, i, 0x60) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x68) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_FORCE_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x70) + +static inline u32 qaif_wrdma_irq_reg_addr(const struct qaif_variant *v, + enum qaif_irq_type dma_type, + u32 offset) +{ + if (dma_type == QAIF_AIF_IRQ) + return v->wrdma_irq_reg_base + offset + + v->wrdma_irq_stride * v->ee; + return v->codec_wrdma_irq_reg_base + offset + + v->codec_wrdma_irq_stride * v->ee; +} + +/* WRDMA Period Interrupts */ +#define QAIF_EE_WRDMA_PERIOD_IRQ_EN_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x00) +#define QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x08) +#define QAIF_EE_WRDMA_PERIOD_IRQ_RAW_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x10) +#define QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x18) +#define QAIF_EE_WRDMA_PERIOD_IRQ_FORCE_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x20) +/* WRDMA Overflow Interrupts */ +#define QAIF_EE_WRDMA_OF_IRQ_EN_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x28) +#define QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x30) +#define QAIF_EE_WRDMA_OF_IRQ_RAW_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x38) +#define QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x40) +#define QAIF_EE_WRDMA_OF_IRQ_FORCE_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x48) +/* WRDMA Error Response Interrupts */ +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_EN_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x50) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x58) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_RAW_STAT(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x60) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x68) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_FORCE_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x70) + +static inline u32 qaif_dmacfg_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_CFG_REG(v, i) : + QAIF_CDC_WRDMA_CFG_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_CFG_REG(v, i) : QAIF_WRDMA_CFG_REG(v, i); +} + +static inline u32 qaif_dmactl_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_CTL_REG(v, i) : + QAIF_CDC_WRDMA_CTL_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_CTL_REG(v, i) : QAIF_WRDMA_CTL_REG(v, i); +} + +static inline u32 qaif_dma_shutdown_stat_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_SHUTDOWN_STAT_REG(v, i) : + QAIF_CDC_WRDMA_SHUTDOWN_STAT_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_SHUTDOWN_STAT_REG(v, i) : + QAIF_WRDMA_SHUTDOWN_STAT_REG(v, i); +} + +static inline u32 qaif_dmabuff_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_BUFF_LEN_REG(v, i) : + QAIF_CDC_WRDMA_BUFF_LEN_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_BUFF_LEN_REG(v, i) : QAIF_WRDMA_BUFF_LEN_REG(v, i); +} + +static inline u32 qaif_dmacurr_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_CURR_ADDR_REG(v, i) : + QAIF_CDC_WRDMA_CURR_ADDR_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_CURR_ADDR_REG(v, i) : QAIF_WRDMA_CURR_ADDR_REG(v, i); +} + +static inline u32 qaif_dmaper_cnt_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_PERIOD_CNT_REG(v, i) : + QAIF_CDC_WRDMA_PERIOD_CNT_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_PERIOD_CNT_REG(v, i) : QAIF_WRDMA_PERIOD_CNT_REG(v, i); +} + +static inline u32 qaif_dmaper_len_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_PERIOD_LEN_REG(v, i) : + QAIF_CDC_WRDMA_PERIOD_LEN_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_PERIOD_LEN_REG(v, i) : QAIF_WRDMA_PERIOD_LEN_REG(v, i); +} + +static inline u32 qaif_dmabase_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_BASE_ADDR_REG(v, i) : + QAIF_CDC_WRDMA_BASE_ADDR_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_BASE_ADDR_REG(v, i) : QAIF_WRDMA_BASE_ADDR_REG(v, i); +} + +static inline u32 qaif_sid_map_reg(int dir, unsigned int dai_id) +{ + if (qaif_is_cif_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_SID_MAP_REG : + QAIF_CDC_WRDMA_SID_MAP_REG; + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_SID_MAP_REG : QAIF_WRDMA_SID_MAP_REG; +} + +#endif /* SND_SOC_QCOM_QAIF_REG_H */ diff --git a/sound/soc/qcom/qaif-shikra.c b/sound/soc/qcom/qaif-shikra.c new file mode 100644 index 000000000000..2db3995a2de0 --- /dev/null +++ b/sound/soc/qcom/qaif-shikra.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-shikra.c -- ALSA SoC CPU-Platform DAI driver for QTi QAIF + */ + +#include +#include +#include +#include +#include "qaif.h" + +static const struct qaif_dmaidx_dai_map shikra_mi2s_dma_dai_map[] = { + { AIF_MI2S_RX_0, QAIF_DMA_IDX0 }, + { AIF_MI2S_RX_1, QAIF_DMA_IDX1 }, + { AIF_MI2S_RX_2, QAIF_DMA_IDX2 }, + { AIF_MI2S_RX_3, QAIF_DMA_IDX3 }, + { AIF_MI2S_TX_0, QAIF_DMA_IDX0 }, + { AIF_MI2S_TX_1, QAIF_DMA_IDX1 }, + { AIF_MI2S_TX_2, QAIF_DMA_IDX2 }, + { AIF_MI2S_TX_3, QAIF_DMA_IDX3 }, +}; + +static const struct qaif_dmaidx_dai_map shikra_tdm_dma_dai_map[] = { + { AIF_TDM_RX_0, QAIF_DMA_IDX0 }, + { AIF_TDM_RX_1, QAIF_DMA_IDX1 }, + { AIF_TDM_RX_2, QAIF_DMA_IDX2 }, + { AIF_TDM_RX_3, QAIF_DMA_IDX3 }, + { AIF_TDM_TX_0, QAIF_DMA_IDX0 }, + { AIF_TDM_TX_1, QAIF_DMA_IDX1 }, + { AIF_TDM_TX_2, QAIF_DMA_IDX2 }, + { AIF_TDM_TX_3, QAIF_DMA_IDX3 }, +}; + +static const struct qaif_dmaidx_dai_map shikra_cif_dma_dai_map[] = { + { RX_CODEC_DMA_RX_0, QAIF_DMA_IDX0 }, + { RX_CODEC_DMA_RX_1, QAIF_DMA_IDX1 }, + { VA_CODEC_DMA_TX_0, QAIF_DMA_IDX0 }, + { VA_CODEC_DMA_TX_1, QAIF_DMA_IDX1 }, +}; + +static struct snd_soc_dai_driver shikra_qaif_cpu_dai_driver[] = { + { + .id = AIF_MI2S_RX_0, + .name = "MI2S AIF RX Zero", + .playback = { + .stream_name = "MI2S AIF RX Zero Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_TX_0, + .name = "MI2S AIF TX Zero", + .capture = { + .stream_name = "MI2S AIF TX Zero Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_RX_0, + .name = "TDM AIF RX Zero", + .playback = { + .stream_name = "TDM AIF RX Zero Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_TX_0, + .name = "TDM AIF TX Zero", + .capture = { + .stream_name = "TDM AIF TX Zero Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_RX_1, + .name = "MI2S AIF RX One", + .playback = { + .stream_name = "MI2S AIF RX One Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_TX_1, + .name = "MI2S AIF TX One", + .capture = { + .stream_name = "MI2S AIF TX One Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_RX_1, + .name = "TDM AIF RX One", + .playback = { + .stream_name = "TDM AIF RX One Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_TX_1, + .name = "TDM AIF TX One", + .capture = { + .stream_name = "TDM AIF TX One Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_RX_2, + .name = "MI2S AIF RX Two", + .playback = { + .stream_name = "MI2S AIF RX Two Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_TX_2, + .name = "MI2S AIF TX Two", + .capture = { + .stream_name = "MI2S AIF TX Two Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_RX_2, + .name = "TDM AIF RX Two", + .playback = { + .stream_name = "TDM AIF RX Two Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_TX_2, + .name = "TDM AIF TX Two", + .capture = { + .stream_name = "TDM AIF TX Two Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_RX_3, + .name = "MI2S AIF RX Three", + .playback = { + .stream_name = "MI2S AIF RX Three Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_MI2S_TX_3, + .name = "MI2S AIF TX Three", + .capture = { + .stream_name = "MI2S AIF TX Three Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_RX_3, + .name = "TDM AIF RX Three", + .playback = { + .stream_name = "TDM AIF RX Three Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = AIF_TDM_TX_3, + .name = "TDM AIF TX Three", + .capture = { + .stream_name = "TDM AIF TX Three Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = RX_CODEC_DMA_RX_0, + .name = "RX CDC DMA RX0", + .playback = { + .stream_name = "RX CDC DMA RX0 Playback", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, { + .id = RX_CODEC_DMA_RX_1, + .name = "RX CDC DMA RX1", + .playback = { + .stream_name = "RX CDC DMA RX1 Playback", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, { + .id = VA_CODEC_DMA_TX_0, + .name = "VA CDC DMA TX0", + .capture = { + .stream_name = "VA CDC DMA TX0 Capture", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, { + .id = VA_CODEC_DMA_TX_1, + .name = "VA CDC DMA TX1", + .capture = { + .stream_name = "VA CDC DMA TX1 Capture", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, +}; + +static int shikra_qaif_get_dma_idx(unsigned int dai_id) +{ + const struct qaif_dmaidx_dai_map *map; + int i, size; + + if (dai_id >= AIF_MI2S_RX_0 && dai_id <= AIF_MI2S_TX_12) { + map = shikra_mi2s_dma_dai_map; + size = ARRAY_SIZE(shikra_mi2s_dma_dai_map); + } else if (dai_id >= AIF_TDM_RX_0 && dai_id <= AIF_TDM_TX_12) { + map = shikra_tdm_dma_dai_map; + size = ARRAY_SIZE(shikra_tdm_dma_dai_map); + } else { + map = shikra_cif_dma_dai_map; + size = ARRAY_SIZE(shikra_cif_dma_dai_map); + } + + for (i = 0; i < size; i++) { + if (map[i].dai_id == dai_id) + return map[i].dma_idx; + } + return -EINVAL; +} + +static int shikra_qaif_alloc_stream_dma_idx(struct qaif_drv_data *drvdata, + int direction, + unsigned int dai_id) +{ + const struct qaif_variant *v = drvdata->variant; + int dma_idx, index; + + dma_idx = shikra_qaif_get_dma_idx(dai_id); + if (dma_idx < 0) + return dma_idx; + + if (qaif_is_aif_port(dai_id)) { + index = (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + dma_idx : v->wrdma_start + dma_idx; + if (index >= QAIF_MAX_AIF_DMA_IDX) + return -EINVAL; + if (test_and_set_bit(index, &drvdata->aif_dma_idx_bit_map)) + return -EBUSY; + } else if (qaif_is_cif_port(dai_id)) { + index = (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + dma_idx : v->codec_wrdma_start + dma_idx; + if (index >= QAIF_MAX_CIF_DMA_IDX) + return -EINVAL; + if (test_and_set_bit(index, &drvdata->cif_dma_idx_bit_map)) + return -EBUSY; + } else { + return -EINVAL; + } + + return index; +} + +static int shikra_qaif_free_stream_dma_idx(struct qaif_drv_data *drvdata, + int index, + unsigned int dai_id) +{ + if (qaif_is_aif_port(dai_id)) + clear_bit(index, &drvdata->aif_dma_idx_bit_map); + else + clear_bit(index, &drvdata->cif_dma_idx_bit_map); + return 0; +} + +static const struct qaif_variant shikra_qaif_data = { + .ee = 0, + + .num_rddma = 4, + .num_wrdma = 4, + .wrdma_start = 4, + + .num_codec_rddma = 4, + .num_codec_wrdma = 4, + .codec_wrdma_start = 4, + .num_intf = 4, + + .rddma_reg_base = 0x8000, + .rddma_stride = 0x1000, + .codec_rddma_reg_base = 0xc000, + .codec_rddma_stride = 0x1000, + + .wrdma_reg_base = 0x11000, + .wrdma_stride = 0x1000, + .codec_wrdma_reg_base = 0x15000, + .codec_wrdma_stride = 0x1000, + + .rddma_irq_reg_base = 0x19000, + .rddma_irq_stride = 0x1000, + .codec_rddma_irq_reg_base = 0x191A0, + .codec_rddma_irq_stride = 0x1000, + + .wrdma_irq_reg_base = 0x19078, + .wrdma_irq_stride = 0x1000, + .codec_wrdma_irq_reg_base = 0x19290, + .codec_wrdma_irq_stride = 0x1000, + + .qxm_type = QAIF_QXM0, + .rddma_shram_len = 64, + .rddma_shram_start_addr = {0, 256}, + .wrdma_shram_len = 64, + .wrdma_shram_start_addr = {0, 256}, + + .clk_name = (const char * const []) { + "lpass_config", + "lpass_core_axim", + "bus", + }, + .num_clks = 3, + + .dai_driver = shikra_qaif_cpu_dai_driver, + .num_dai = ARRAY_SIZE(shikra_qaif_cpu_dai_driver), + + .dai_bit_clk_names = (const char * const []) { + "aif_if0_ibit", + "aif_if1_ibit", + "aif_if2_ibit", + "aif_if3_ibit", + }, + .aif_full_cycle_en = (const bool []) { false, false, false, false }, + .aif_ctrl_data_oe = (const bool []) { false, false, false, false }, + .aif_loopback_en = (const bool []) { false, false, false, false }, + .alloc_stream_dma_idx = shikra_qaif_alloc_stream_dma_idx, + .free_stream_dma_idx = shikra_qaif_free_stream_dma_idx, + .get_dma_idx = shikra_qaif_get_dma_idx, +}; + +static const struct of_device_id shikra_qaif_cpu_device_id[] = { + { .compatible = "qcom,shikra-qaif-cpu", .data = &shikra_qaif_data }, + {} +}; +MODULE_DEVICE_TABLE(of, shikra_qaif_cpu_device_id); + +static struct platform_driver shikra_qaif_cpu_platform_driver = { + .driver = { + .name = "shikra-qaif-cpu", + .of_match_table = shikra_qaif_cpu_device_id, + .pm = pm_ptr(&asoc_qcom_qaif_pm_ops), + }, + .probe = asoc_qcom_qaif_cpu_platform_probe, +}; +module_platform_driver(shikra_qaif_cpu_platform_driver); + +MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) Shikra variant driver"); +MODULE_AUTHOR("Harendra Gautam "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qaif.h b/sound/soc/qcom/qaif.h new file mode 100644 index 000000000000..794d02ef128b --- /dev/null +++ b/sound/soc/qcom/qaif.h @@ -0,0 +1,292 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef SND_SOC_QCOM_QAIF_H +#define SND_SOC_QCOM_QAIF_H + +#include +#include +#include +#include +#include +#include +#include +#include + +struct snd_pcm_substream; +struct snd_soc_dai_driver; +struct snd_soc_dai_ops; +struct platform_device; + +#define QAIF_CIF_DMA_INTF_ONE_CHANNEL 0x01 +#define QAIF_CIF_DMA_INTF_TWO_CHANNEL 0x03 +#define QAIF_CIF_DMA_INTF_FOUR_CHANNEL 0x0f +#define QAIF_CIF_DMA_INTF_SIX_CHANNEL 0x3f +#define QAIF_CIF_DMA_INTF_EIGHT_CHANNEL 0xff + +#define QAIF_AIF_SLOT_WIDTH(bits) ((bits) - 1) +#define QAIF_AIF_SAMPLE_WIDTH(bits) ((bits) - 1) + +/* Watermark level 5 encoded as N-1: hardware field value = watermark - 1 */ +#define QAIF_DMACTL_WM_5 4 + +#define QAIF_DMA_BYTES_TO_WORDS_SHIFT 3 +#define QAIF_DMA_CLOCK_FREQ 38400000 + +#define QAIF_MAX_AIF_CFG_CNT 4 +#define QAIF_MAX_LANES 8 + +#define QAIF_MAX_MI2S_PORTS 8 +#define QAIF_MI2S_SLOTS 2 +#define QAIF_MAX_AIF_DMA_IDX 8 +#define QAIF_MAX_CIF_DMA_IDX 8 + +/* SID map register CSID field (WRDMA_SID/RDDMA_SID), bits [4:0] */ +#define QAIF_CSID_MASK GENMASK(4, 0) + +static inline bool qaif_is_aif_mi2s_port(unsigned int dai_id) +{ + return (dai_id >= AIF_MI2S_RX_0 && dai_id <= AIF_MI2S_RX_12) || + (dai_id >= AIF_MI2S_TX_0 && dai_id <= AIF_MI2S_TX_12); +} + +static inline bool qaif_is_aif_tdm_port(unsigned int dai_id) +{ + return (dai_id >= AIF_TDM_RX_0 && dai_id <= AIF_TDM_RX_12) || + (dai_id >= AIF_TDM_TX_0 && dai_id <= AIF_TDM_TX_12); +} + +static inline bool qaif_is_aif_port(unsigned int dai_id) +{ + return qaif_is_aif_mi2s_port(dai_id) || qaif_is_aif_tdm_port(dai_id); +} + +static inline bool qaif_is_cif_port(unsigned int dai_id) +{ + return (dai_id >= WSA_CODEC_DMA_RX_0 && dai_id <= RX_CODEC_DMA_RX_7); +} + +enum qxm_sel { + QAIF_QXM0 = 0, + QAIF_QXM1 = 1, +}; + +enum qaif_irq_type { + QAIF_AIF_IRQ = 0, + QAIF_CIF_IRQ = 1, + QAIF_IRQ_TYPE_MAX, +}; + +enum qaif_dma_type { + QAIF_AIF_DMA = 0, + QAIF_CIF_DMA = 1, +}; + +struct qaif_aif_config { + u32 sync_mode; + u32 sync_src; + bool invert_sync; + u32 sync_delay; + u32 slot_width; + u32 slot_en_rx_mask; + u32 slot_en_tx_mask; + bool loopback_en; + bool ctrl_data_oe; + u32 lane_en_mask; + u32 lane_dir_mask; + bool full_cycle_en; + u32 slot_num; +}; + +struct qaif_pcm_data { + /* Flat slot in aif_substream[] (or cif_substream[]) used to track + * this stream. Direction-encoded: playback uses the raw DMA channel + * number (0..num_rddma-1), capture adds wrdma_start so playback and + * capture of the same DAI occupy distinct slots. + * Set by alloc_stream_dma_idx(); bounded by QAIF_MAX_AIF/CIF_DMA_IDX. + */ + int stream_dma_idx; + /* Hardware channel number (0-based within its DMA bank) passed to + * register address helpers (e.g. QAIF_RDDMA_CFG_REG(v, chan)). + * Direction-agnostic: the register helpers select RDDMA or WRDMA bank + * based on stream direction, so both directions of the same DAI share + * the same value. Set by get_dma_idx(). + */ + int dma_reg_idx; +}; + +/* Named 0-based DMA channel indices used as the dma_idx of a + * qaif_dmaidx_dai_map entry, so board tables reference channels by name. + */ +enum qaif_dma_idx { + QAIF_DMA_IDX0 = 0, + QAIF_DMA_IDX1, + QAIF_DMA_IDX2, + QAIF_DMA_IDX3, + QAIF_DMA_IDX4, + QAIF_DMA_IDX5, + QAIF_DMA_IDX6, + QAIF_DMA_IDX7, +}; + +/* Associates a DAI ID with its physical DMA channel index on a given + * board/variant. @dai_id is the DAI to match; @dma_idx (a qaif_dma_idx value) + * is the 0-based hardware channel that get_dma_idx() returns for it. + */ +struct qaif_dmaidx_dai_map { + unsigned int dai_id; + int dma_idx; +}; + +struct qaif_drv_data { + struct clk *mi2s_bit_clk[QAIF_MAX_MI2S_PORTS]; + struct clk_bulk_data *clks; + int num_clks; + struct clk *aud_dma_clk; + struct clk *aud_dma_mem_clk; + void __iomem *audio_qaif; + struct regmap *audio_qaif_map; + int audio_qaif_irq; + struct mutex stream_lock; /* serializes stream open/close */ + bool qaif_hw_configured; + const struct qaif_variant *variant; + struct qaif_aif_config aif_intf_cfg[QAIF_MAX_AIF_CFG_CNT]; + unsigned long aif_dma_idx_bit_map; + unsigned long cif_dma_idx_bit_map; + struct snd_pcm_substream *aif_substream[QAIF_MAX_AIF_DMA_IDX]; + struct snd_pcm_substream *cif_substream[QAIF_MAX_CIF_DMA_IDX]; + u32 smmu_csid_bits; /* SMMU stream ID bits from DT iommus cell */ +}; + +enum qaif_summary_irq_bitmask { + QAIF_SUMMARY_BITMASK_AIF_PERIOD_RDDMA = BIT(0), + QAIF_SUMMARY_BITMASK_AIF_UNDERFLOW_RDDMA = BIT(1), + QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_RDDMA = BIT(2), + QAIF_SUMMARY_BITMASK_AIF_PERIOD_WRDMA = BIT(3), + QAIF_SUMMARY_BITMASK_AIF_OVERFLOW_WRDMA = BIT(4), + QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_WRDMA = BIT(5), + QAIF_SUMMARY_BITMASK_CIF_PERIOD_RDDMA = BIT(18), + QAIF_SUMMARY_BITMASK_CIF_UNDERFLOW_RDDMA = BIT(19), + QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_RDDMA = BIT(20), + QAIF_SUMMARY_BITMASK_CIF_PERIOD_WRDMA = BIT(24), + QAIF_SUMMARY_BITMASK_CIF_OVERFLOW_WRDMA = BIT(25), + QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_WRDMA = BIT(26), +}; + +enum qaif_client_status_register_bitmask_info { + QAIF_BITMASK_AIF_RDDMA_WRDMA = GENMASK(5, 0), + QAIF_BITMASK_CIF_RDDMA_WRDMA = GENMASK(26, 18), +}; + +struct qaif_irq_map { + u32 client_id; + u32 mask; + /* + * Called from the hard IRQ handler (asoc_platform_qaif_irq()) with + * @status holding the summary interrupt status. Must not sleep. + */ + irqreturn_t (*client_irq_handler)(struct qaif_drv_data *drvdata, u32 status); +}; + +enum qaif_dma_dir { + QAIF_DMA_RDDMA = 0, + QAIF_DMA_WRDMA = 1, +}; + +enum qaif_irq { + QAIF_IRQ_PERIOD = 0, + QAIF_IRQ_OVERFLOW, + QAIF_IRQ_UNDERFLOW, + QAIF_IRQ_ERROR, +}; + +enum qaif_irq_op { + QAIF_IRQ_CLEAR = 0, + QAIF_IRQ_ENABLE, + QAIF_IRQ_DISABLE, +}; + +enum qaif_client_info { + QAIF_CLIENT_ID_AIF_DMA = 0, + QAIF_CLIENT_ID_CIF_DMA, +}; + +struct qaif_variant { + u32 ee; /* Hardware EE index assigned to this driver instance */ + + u32 num_rddma; + u32 num_wrdma; + u32 wrdma_start; + u32 num_codec_rddma; + u32 num_codec_wrdma; + u32 codec_wrdma_start; + u32 num_intf; + + u32 rddma_reg_base; + u32 rddma_stride; + u32 codec_rddma_reg_base; + u32 codec_rddma_stride; + u32 wrdma_reg_base; + u32 wrdma_stride; + u32 codec_wrdma_reg_base; + u32 codec_wrdma_stride; + + u32 rddma_irq_reg_base; + u32 rddma_irq_stride; + u32 codec_rddma_irq_reg_base; + u32 codec_rddma_irq_stride; + u32 wrdma_irq_reg_base; + u32 wrdma_irq_stride; + u32 codec_wrdma_irq_reg_base; + u32 codec_wrdma_irq_stride; + + /* QSB master port selector; only QAIF_QXM0 is supported today */ + enum qxm_sel qxm_type; + /* SHRAM sizes in 64-bit words */ + u32 rddma_shram_len; + /* SHRAM start offsets (in 64-bit words), indexed by enum qaif_dma_type */ + u32 rddma_shram_start_addr[2]; + u32 wrdma_shram_len; + u32 wrdma_shram_start_addr[2]; + + const char * const *clk_name; + int num_clks; + struct snd_soc_dai_driver *dai_driver; + int num_dai; + const char * const *dai_bit_clk_names; + /* + * Optional per-AIF configuration tables, indexed by AIF interface + * index (0..num_intf-1). NULL if the variant does not use them. + */ + const bool *aif_full_cycle_en; + const bool *aif_ctrl_data_oe; + const bool *aif_loopback_en; + + /* Returns a direction-encoded flat slot index for use in + * aif_substream[]/cif_substream[] and the dma_idx_bit_map. + * Playback returns the raw channel number; capture adds wrdma_start (or + * codec_wrdma_start) so both directions of the same DAI get distinct + * slots. Sets the corresponding bit atomically; returns -EBUSY if + * already open, -EINVAL if @dai_id or the computed index is out of range. + */ + int (*alloc_stream_dma_idx)(struct qaif_drv_data *data, int direction, + unsigned int dai_id); + /* Clears the bit set by alloc_stream_dma_idx. */ + int (*free_stream_dma_idx)(struct qaif_drv_data *data, int chan, + unsigned int dai_id); + /* Returns the hardware channel number (0-based within its DMA bank) + * for @dai_id, direction-agnostic. Used as dma_reg_idx. + */ + int (*get_dma_idx)(unsigned int dai_id); +}; + +extern const struct snd_soc_dai_ops asoc_qcom_qaif_cif_dai_ops; +extern const struct snd_soc_dai_ops asoc_qcom_qaif_aif_cpu_dai_ops; + +int asoc_qcom_qaif_cpu_platform_probe(struct platform_device *pdev); +int asoc_qcom_qaif_platform_register(struct platform_device *pdev); +extern const struct dev_pm_ops asoc_qcom_qaif_pm_ops; + +#endif /* SND_SOC_QCOM_QAIF_H */ diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index cce0ad31ff0c..d2dc5d37bccf 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -593,10 +593,10 @@ EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt); int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, - const struct gpr_pkt *pkt, uint32_t rsp_opcode) + struct gpr_pkt *pkt, uint32_t rsp_opcode) { - const struct gpr_hdr *hdr = &pkt->hdr; + struct gpr_hdr *hdr = &pkt->hdr; int rc; mutex_lock(cmd_lock); @@ -636,10 +636,12 @@ int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, } EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return audioreach_send_cmd_sync(graph->dev, NULL, &graph->result, &graph->lock, graph->port, &graph->cmd_wait, pkt, rsp_opcode); } @@ -994,6 +996,8 @@ int audioreach_compr_set_param(struct q6apm_graph *graph, if (rc) return rc; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(audioreach_compr_set_param); @@ -1609,6 +1613,8 @@ int audioreach_shared_memory_send_eos(struct q6apm_graph *graph) eos->policy = WR_SH_MEM_EP_EOS_POLICY_LAST; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(audioreach_shared_memory_send_eos); diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 35541b2d8c99..587f14ce71d2 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -1002,14 +1002,19 @@ struct audioreach_module_config { }; /* Packet Allocation routines */ -void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t - token); +static inline u16 audioreach_gpr_dest_domain(gpr_device_t *gdev) +{ + return gdev && gdev->domain_id ? gdev->domain_id : GPR_DOMAIN_ID_ADSP; +} + +void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, + uint32_t token); void audioreach_set_default_channel_mapping(u8 *ch_map, int num_channels); void *audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode, uint32_t token, uint32_t src_port, uint32_t dest_port); void *audioreach_alloc_apm_pkt(int pkt_size, uint32_t opcode, uint32_t token, - uint32_t src_port); + uint32_t src_port); void *audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token, uint32_t src_port, uint32_t dest_port); @@ -1020,10 +1025,13 @@ int audioreach_tplg_init(struct snd_soc_component *component); /* Module specific */ void audioreach_graph_free_buf(struct q6apm_graph *graph); -int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, - struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, - const struct gpr_pkt *pkt, uint32_t rsp_opcode); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, + struct gpr_ibasic_rsp_result_t *result, + struct mutex *cmd_lock, gpr_port_t *port, + wait_queue_head_t *cmd_wait, + struct gpr_pkt *pkt, uint32_t rsp_opcode); +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, + struct gpr_pkt *pkt, uint32_t rsp_opcode); int audioreach_set_media_format(struct q6apm_graph *graph, const struct audioreach_module *module, diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 033197bdd870..58beaf906b9f 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -1,20 +1,25 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2021, Linaro Limited -#include +#include +#include +#include #include +#include +#include #include +#include #include +#include #include #include -#include -#include #include #include +#include +#include +#include #include #include -#include -#include #include "q6apm.h" #define DRV_NAME "q6apm-dai" @@ -36,6 +41,16 @@ #define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4) #define SID_MASK_DEFAULT 0xF +#define Q6APM_MAX_SCM_REGIONS 16 +#define Q6APM_POOL_MAX_STREAMS 8 + +struct q6apm_scm_region { + phys_addr_t addr; + size_t size; + u64 src_perms; + bool assigned; +}; + static const struct snd_compr_codec_caps q6apm_compr_caps = { .num_descriptors = 1, .descriptor[0].max_ch = 2, @@ -84,9 +99,103 @@ struct q6apm_dai_rtd { }; struct q6apm_dai_data { + struct device *dev; long long sid; + bool use_scm_assign; + bool has_reserved_mem; + size_t reserved_buf_size; + struct mutex scm_lock; /* protects scm_regions and num_scm_regions */ + struct q6apm_scm_region scm_regions[Q6APM_MAX_SCM_REGIONS]; + int num_scm_regions; }; +static int q6apm_dai_scm_assign(struct q6apm_dai_data *pdata, + phys_addr_t addr, size_t size) +{ + struct qcom_scm_vmperm dst[] = { + { .vmid = QCOM_SCM_VMID_HLOS, .perm = QCOM_SCM_PERM_RW }, + { .vmid = QCOM_SCM_VMID_MSS_MSA, .perm = QCOM_SCM_PERM_RW }, + { .vmid = QCOM_SCM_VMID_LPASS, .perm = QCOM_SCM_PERM_RW }, + }; + struct q6apm_scm_region *r; + u64 src = BIT(QCOM_SCM_VMID_HLOS); + int ret; + + mutex_lock(&pdata->scm_lock); + + if (pdata->num_scm_regions >= Q6APM_MAX_SCM_REGIONS) { + mutex_unlock(&pdata->scm_lock); + return -ENOSPC; + } + + ret = qcom_scm_assign_mem(addr, size, &src, dst, ARRAY_SIZE(dst)); + if (ret) { + mutex_unlock(&pdata->scm_lock); + return ret; + } + + r = &pdata->scm_regions[pdata->num_scm_regions++]; + r->addr = addr; + r->size = size; + r->src_perms = src; + r->assigned = true; + + mutex_unlock(&pdata->scm_lock); + + return 0; +} + +static void q6apm_dai_scm_unassign(struct q6apm_dai_data *pdata, + phys_addr_t addr) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + int i; + + mutex_lock(&pdata->scm_lock); + + for (i = 0; i < pdata->num_scm_regions; i++) { + if (pdata->scm_regions[i].addr != addr || + !pdata->scm_regions[i].assigned) + continue; + + if (qcom_scm_assign_mem(addr, pdata->scm_regions[i].size, + &pdata->scm_regions[i].src_perms, + &hlos, 1)) { + dev_err(pdata->dev, "SCM unassign %pa failed\n", &addr); + mutex_unlock(&pdata->scm_lock); + return; + } + + pdata->scm_regions[i].assigned = false; + pdata->num_scm_regions--; + pdata->scm_regions[i] = pdata->scm_regions[pdata->num_scm_regions]; + mutex_unlock(&pdata->scm_lock); + return; + } + + mutex_unlock(&pdata->scm_lock); +} + +static void q6apm_dai_scm_cleanup(void *data) +{ + struct q6apm_dai_data *pdata = data; + int i; + + for (i = pdata->num_scm_regions - 1; i >= 0; i--) { + if (pdata->scm_regions[i].assigned) + q6apm_dai_scm_unassign(pdata, + pdata->scm_regions[i].addr); + } +} + +static void q6apm_dai_reserved_mem_release(void *data) +{ + of_reserved_mem_device_release(data); +} + static const struct snd_pcm_hardware q6apm_dai_hardware_capture = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | @@ -409,8 +518,11 @@ static int q6apm_dai_open(struct snd_soc_component *component, } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + size_t buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, - BUFFER_BYTES_MIN, BUFFER_BYTES_MAX); + BUFFER_BYTES_MIN, buf_max); if (ret < 0) { dev_err(dev, "constraint for buffer bytes min max ret = %d\n", ret); goto err; @@ -431,17 +543,20 @@ static int q6apm_dai_open(struct snd_soc_component *component, } runtime->private_data = prtd; - runtime->dma_bytes = BUFFER_BYTES_MAX; + runtime->dma_bytes = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; if (pdata->sid < 0) prtd->phys = substream->dma_buffer.addr; else prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32); if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) { + size_t buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; void *pos_buffer; - prtd->pos_phys = prtd->phys + BUFFER_BYTES_MAX; - pos_buffer = (void *)(substream->dma_buffer.area + BUFFER_BYTES_MAX); + prtd->pos_phys = prtd->phys + buf_max; + pos_buffer = (void *)(substream->dma_buffer.area + buf_max); prtd->pos_buffer = (struct sh_mem_pull_push_mode_position_buffer *)(pos_buffer); } @@ -498,11 +613,15 @@ static snd_pcm_uframes_t q6apm_dai_pointer(struct snd_soc_component *component, ptr = bytes_to_frames(runtime, index); return ptr; } - ptr = q6apm_get_hw_pointer(prtd->graph, substream->stream) * runtime->period_size; - if (ptr) - return ptr - 1; + /* + * The hw pointer counts the period buffers the DSP has returned in + * this lap, 1..periods. A full lap is the start of the ring again: + * report 0 rather than one frame less, so that the position of a + * drained buffer reaches appl_ptr and ALSA sees the underrun. + */ + ptr = q6apm_get_hw_pointer(prtd->graph, substream->stream) % runtime->periods; - return 0; + return ptr * runtime->period_size; } static int q6apm_dai_hw_params(struct snd_soc_component *component, @@ -535,6 +654,7 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, { struct q6apm_dai_data *pdata; struct device *dev = component->dev; + size_t buf_max; phys_addr_t phys; int ret; @@ -544,20 +664,23 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, return -EINVAL; } + buf_max = pdata->has_reserved_mem ? pdata->reserved_buf_size : + BUFFER_BYTES_MAX; + if (pdata->sid < 0) phys = substream->dma_buffer.addr; else phys = substream->dma_buffer.addr | (pdata->sid << 32); - ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, BUFFER_BYTES_MAX); + ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, buf_max); if (ret < 0) dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); if (is_push_pull) { if (pdata->sid < 0) - phys = substream->dma_buffer.addr + BUFFER_BYTES_MAX; + phys = substream->dma_buffer.addr + buf_max; else - phys = (substream->dma_buffer.addr + BUFFER_BYTES_MAX) | (pdata->sid << 32); + phys = (substream->dma_buffer.addr + buf_max) | (pdata->sid << 32); ret = q6apm_map_pos_buffer(dev, graph_id, phys, POS_BUFFER_BYTES); if (ret < 0) @@ -572,20 +695,22 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct q6apm_dai_data *pdata; struct snd_pcm *pcm = rtd->pcm; - /* - * Allocate one extra page as a workaround for a DSP bug where 32-bit - * address arithmetic can overflow when the buffer is placed near the - * end of the addressable range. - */ int size = BUFFER_BYTES_MAX + PAGE_SIZE; int graph_id, ret; bool is_push_pull; struct snd_pcm_substream *substream = NULL; + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + if (pdata->has_reserved_mem) + size = pdata->reserved_buf_size + PAGE_SIZE; + graph_id = cpu_dai->driver->id; - /* Note: DSP backend dais are uni-directional ONLY(either playback or capture) */ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; else if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) @@ -603,9 +728,24 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc if (ret) return ret; + if (pdata->use_scm_assign && !pdata->has_reserved_mem) { + ret = q6apm_dai_scm_assign(pdata, + substream->dma_buffer.addr, + ALIGN(size, PAGE_SIZE)); + if (ret) { + dev_err(component->dev, + "SCM assign buffer failed: %d\n", ret); + return ret; + } + } + ret = q6apm_dai_memory_map(component, substream, graph_id, is_push_pull); - if (ret) + if (ret) { + if (pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, + substream->dma_buffer.addr); return ret; + } } return 0; @@ -635,15 +775,26 @@ static void q6apm_dai_memory_unmap(struct snd_soc_component *component, static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm) { + struct q6apm_dai_data *pdata; struct snd_pcm_substream *substream; + pdata = snd_soc_component_get_drvdata(component); + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; - if (substream) + if (substream) { q6apm_dai_memory_unmap(component, substream); + if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, + substream->dma_buffer.addr); + } substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; - if (substream) + if (substream) { q6apm_dai_memory_unmap(component, substream); + if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, + substream->dma_buffer.addr); + } } static int q6apm_dai_compr_open(struct snd_soc_component *component, @@ -683,6 +834,17 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, if (ret) return ret; + if (pdata->use_scm_assign && !pdata->has_reserved_mem) { + ret = q6apm_dai_scm_assign(pdata, prtd->dma_buffer.addr, + ALIGN(size, PAGE_SIZE)); + if (ret) { + dev_err(dev, "SCM assign compr buffer failed: %d\n", + ret); + snd_dma_free_pages(&prtd->dma_buffer); + return ret; + } + } + if (pdata->sid < 0) prtd->phys = prtd->dma_buffer.addr; else @@ -700,11 +862,16 @@ static int q6apm_dai_compr_free(struct snd_soc_component *component, { struct snd_compr_runtime *runtime = stream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; + struct q6apm_dai_data *pdata; + + pdata = snd_soc_component_get_drvdata(component); q6apm_graph_stop(prtd->graph); q6apm_free_fragments(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK); q6apm_unmap_memory_fixed_region(component->dev, prtd->graph->id); q6apm_graph_close(prtd->graph); + if (pdata && pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign(pdata, prtd->dma_buffer.addr); snd_dma_free_pages(&prtd->dma_buffer); prtd->graph = NULL; kfree(prtd); @@ -1021,6 +1188,7 @@ static int q6apm_dai_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; + struct q6apm *apm = dev_get_drvdata(dev->parent); struct q6apm_dai_data *pdata; struct of_phandle_args args; int rc; @@ -1029,12 +1197,123 @@ static int q6apm_dai_probe(struct platform_device *pdev) if (!pdata) return -ENOMEM; + pdata->dev = dev; + mutex_init(&pdata->scm_lock); + rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); if (rc < 0) pdata->sid = -1; else pdata->sid = args.args[0] & SID_MASK_DEFAULT; + if (apm && apm->gdev && + apm->gdev->domain_id == GPR_DOMAIN_ID_MODEM) { + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + if (pdata->sid >= 0) { + dev_err(dev, + "iommus and mDSP SCM path are mutually exclusive\n"); + return -EINVAL; + } + + pdata->use_scm_assign = true; + + rc = devm_add_action_or_reset(dev, q6apm_dai_scm_cleanup, + pdata); + if (rc) + return rc; + } + + if (pdata->use_scm_assign) { + int mem_count; + + mem_count = of_count_phandle_with_args(node, "memory-region", + NULL); + if (mem_count >= 1) { + struct device_node *mem_node; + struct reserved_mem *rmem; + + mem_node = of_parse_phandle(node, "memory-region", 0); + rmem = of_reserved_mem_lookup(mem_node); + of_node_put(mem_node); + if (!rmem) { + dev_err(dev, + "memory-region[0]: lookup failed\n"); + return -ENODEV; + } + + rc = q6apm_dai_scm_assign(pdata, rmem->base, + ALIGN(rmem->size, PAGE_SIZE)); + if (rc) { + dev_err(dev, + "SCM assign memory-region[0] failed: %d\n", + rc); + return rc; + } + } + + if (mem_count >= 2) { + struct device_node *mem_node; + struct reserved_mem *rmem; + size_t per_stream; + + mem_node = of_parse_phandle(node, "memory-region", 1); + rmem = of_reserved_mem_lookup(mem_node); + of_node_put(mem_node); + if (!rmem) { + dev_err(dev, + "memory-region[1]: lookup failed\n"); + return -ENODEV; + } + + per_stream = rmem->size / Q6APM_POOL_MAX_STREAMS; + if (per_stream <= POS_BUFFER_BYTES + PAGE_SIZE) { + dev_err(dev, + "reserved-memory pool too small: %pa bytes\n", + &rmem->size); + return -EINVAL; + } + + /* + * Assign the whole pool to the consumer VMIDs once here, + * instead of per-stream in pcm_new()/compr_open(). Each + * qcom_scm_assign_mem() call consumes an entry in a small + * fixed-size TZ memory-protection table shared platform-wide; + * assigning per-stream-slice (up to Q6APM_POOL_MAX_STREAMS + * times) exhausts that table and hangs the SMC call. + */ + rc = q6apm_dai_scm_assign(pdata, rmem->base, + ALIGN(rmem->size, PAGE_SIZE)); + if (rc) { + dev_err(dev, + "SCM assign memory-region[1] failed: %d\n", + rc); + return rc; + } + + rc = of_reserved_mem_device_init_by_idx(dev, node, 1); + if (rc) { + dev_err(dev, + "reserved-memory pool init failed: %d\n", + rc); + return rc; + } + + rc = devm_add_action_or_reset(dev, + q6apm_dai_reserved_mem_release, + dev); + if (rc) + return rc; + + pdata->reserved_buf_size = + min_t(size_t, + per_stream - POS_BUFFER_BYTES - PAGE_SIZE, + BUFFER_BYTES_MAX); + pdata->has_reserved_mem = true; + } + } + dev_set_drvdata(dev, pdata); return devm_snd_soc_register_component(dev, &q6apm_fe_dai_component, NULL, 0); diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index e204fd59e512..83f2711bceea 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -386,6 +386,7 @@ static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, switch (dai->id) { case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_TDM_RX_0 ... AIF_TDM_TX_12: slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask; if (slot_mask & ~cap_mask) { dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n", @@ -474,6 +475,7 @@ static int of_q6apm_parse_dai_data(struct device *dev, case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case SENARY_MI2S_RX ... SENARY_MI2S_TX: case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_MI2S_RX_0 ... AIF_TDM_TX_12: priv = &data->priv[id]; priv->mclk = of_clk_get_by_name(node, "mclk"); if (IS_ERR(priv->mclk)) { diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index f167b9dae3fa..b6b1ef6833b3 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -29,11 +29,13 @@ struct apm_graph_mgmt_cmd { static struct q6apm *g_apm; -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode) { gpr_device_t *gdev = apm->gdev; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(gdev); + return audioreach_send_cmd_sync(&gdev->dev, gdev, &apm->result, &apm->lock, NULL, &apm->wait, pkt, rsp_opcode); } @@ -283,6 +285,7 @@ int q6apm_alloc_fragments(struct q6apm_graph *graph, unsigned int dir, phys_addr mutex_lock(&graph->lock); data->dsp_buf = 0; + atomic_set(&data->hw_ptr, 0); if (data->buf) { mutex_unlock(&graph->lock); @@ -502,6 +505,8 @@ int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, mutex_unlock(&graph->lock); + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(q6apm_write_async); @@ -536,6 +541,8 @@ int q6apm_read(struct q6apm_graph *graph) mutex_unlock(&graph->lock); + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(q6apm_read); @@ -900,6 +907,7 @@ static int apm_probe(gpr_device_t *gdev) static void apm_remove(gpr_device_t *gdev) { + g_apm = NULL; of_platform_depopulate(&gdev->dev); snd_soc_unregister_component(&gdev->dev); } diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 5cb51ca491dc..9092359ccf90 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -147,7 +147,7 @@ int q6apm_alloc_fragments(struct q6apm_graph *graph, int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir); int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id); /* Helpers */ -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode); /* Callback for graph specific */ diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index 4f09fdd40905..4261c3048c36 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -485,11 +485,10 @@ static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_soc_component *component, struct q6asm_dai_rtd *prtd = runtime->private_data; snd_pcm_uframes_t ptr; - ptr = q6asm_get_hw_pointer(prtd->audio_client, substream->stream) * runtime->period_size; - if (ptr) - return ptr - 1; + /* a full lap of period buffers is the start of the ring again */ + ptr = q6asm_get_hw_pointer(prtd->audio_client, substream->stream) % runtime->periods; - return 0; + return ptr * runtime->period_size; } static int q6asm_dai_hw_params(struct snd_soc_component *component, @@ -551,7 +550,7 @@ static void compress_event_handler(uint32_t opcode, uint32_t token, prtd->stream_id, CMD_CLOSE); /* - * vaild stream ids start from 1, So we are + * valid stream ids start from 1, So we are * toggling this between 1 and 2. */ prtd->stream_id = (prtd->stream_id == 1 ? 2 : 1); diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c index c3d8116ad503..f422346a50c6 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c @@ -548,6 +548,32 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { .id = SENARY_MI2S_TX, .name = "SEN_MI2S_TX", }, + Q6AFE_MI2S_RX_DAI("AIF RX0", AIF_MI2S_RX_0), + Q6AFE_MI2S_RX_DAI("AIF RX1", AIF_MI2S_RX_1), + Q6AFE_MI2S_RX_DAI("AIF RX2", AIF_MI2S_RX_2), + Q6AFE_MI2S_RX_DAI("AIF RX3", AIF_MI2S_RX_3), + Q6AFE_MI2S_RX_DAI("AIF RX4", AIF_MI2S_RX_4), + Q6AFE_MI2S_RX_DAI("AIF RX5", AIF_MI2S_RX_5), + Q6AFE_MI2S_RX_DAI("AIF RX6", AIF_MI2S_RX_6), + Q6AFE_MI2S_RX_DAI("AIF RX7", AIF_MI2S_RX_7), + Q6AFE_MI2S_RX_DAI("AIF RX8", AIF_MI2S_RX_8), + Q6AFE_MI2S_RX_DAI("AIF RX9", AIF_MI2S_RX_9), + Q6AFE_MI2S_RX_DAI("AIF RX10", AIF_MI2S_RX_10), + Q6AFE_MI2S_RX_DAI("AIF RX11", AIF_MI2S_RX_11), + Q6AFE_MI2S_RX_DAI("AIF RX12", AIF_MI2S_RX_12), + Q6AFE_MI2S_TX_DAI("AIF TX0", AIF_MI2S_TX_0), + Q6AFE_MI2S_TX_DAI("AIF TX1", AIF_MI2S_TX_1), + Q6AFE_MI2S_TX_DAI("AIF TX2", AIF_MI2S_TX_2), + Q6AFE_MI2S_TX_DAI("AIF TX3", AIF_MI2S_TX_3), + Q6AFE_MI2S_TX_DAI("AIF TX4", AIF_MI2S_TX_4), + Q6AFE_MI2S_TX_DAI("AIF TX5", AIF_MI2S_TX_5), + Q6AFE_MI2S_TX_DAI("AIF TX6", AIF_MI2S_TX_6), + Q6AFE_MI2S_TX_DAI("AIF TX7", AIF_MI2S_TX_7), + Q6AFE_MI2S_TX_DAI("AIF TX8", AIF_MI2S_TX_8), + Q6AFE_MI2S_TX_DAI("AIF TX9", AIF_MI2S_TX_9), + Q6AFE_MI2S_TX_DAI("AIF TX10", AIF_MI2S_TX_10), + Q6AFE_MI2S_TX_DAI("AIF TX11", AIF_MI2S_TX_11), + Q6AFE_MI2S_TX_DAI("AIF TX12", AIF_MI2S_TX_12), Q6AFE_MI2S_RX_DAI("LPI RX0", LPI_MI2S_RX_0), Q6AFE_MI2S_RX_DAI("LPI RX1", LPI_MI2S_RX_1), Q6AFE_MI2S_RX_DAI("LPI RX2", LPI_MI2S_RX_2), @@ -562,6 +588,32 @@ static struct snd_soc_dai_driver q6dsp_audio_fe_dais[] = { Q6AFE_MI2S_TX_DAI("LPI TX4", LPI_MI2S_TX_4), Q6AFE_MI2S_TX_DAI("LPI TX5", LPI_MI2S_TX_5), Q6AFE_MI2S_TX_DAI("LPI TX6", LPI_MI2S_TX_6), + Q6AFE_TDM_PB_DAI("AIF RX0", 0, AIF_TDM_RX_0), + Q6AFE_TDM_PB_DAI("AIF RX1", 1, AIF_TDM_RX_1), + Q6AFE_TDM_PB_DAI("AIF RX2", 2, AIF_TDM_RX_2), + Q6AFE_TDM_PB_DAI("AIF RX3", 3, AIF_TDM_RX_3), + Q6AFE_TDM_PB_DAI("AIF RX4", 4, AIF_TDM_RX_4), + Q6AFE_TDM_PB_DAI("AIF RX5", 5, AIF_TDM_RX_5), + Q6AFE_TDM_PB_DAI("AIF RX6", 6, AIF_TDM_RX_6), + Q6AFE_TDM_PB_DAI("AIF RX7", 7, AIF_TDM_RX_7), + Q6AFE_TDM_PB_DAI("AIF RX8", 8, AIF_TDM_RX_8), + Q6AFE_TDM_PB_DAI("AIF RX9", 9, AIF_TDM_RX_9), + Q6AFE_TDM_PB_DAI("AIF RX10", 10, AIF_TDM_RX_10), + Q6AFE_TDM_PB_DAI("AIF RX11", 11, AIF_TDM_RX_11), + Q6AFE_TDM_PB_DAI("AIF RX12", 12, AIF_TDM_RX_12), + Q6AFE_TDM_CAP_DAI("AIF TX0", 0, AIF_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("AIF TX1", 1, AIF_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("AIF TX2", 2, AIF_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("AIF TX3", 3, AIF_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("AIF TX4", 4, AIF_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("AIF TX5", 5, AIF_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("AIF TX6", 6, AIF_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("AIF TX7", 7, AIF_TDM_TX_7), + Q6AFE_TDM_CAP_DAI("AIF TX8", 8, AIF_TDM_TX_8), + Q6AFE_TDM_CAP_DAI("AIF TX9", 9, AIF_TDM_TX_9), + Q6AFE_TDM_CAP_DAI("AIF TX10", 10, AIF_TDM_TX_10), + Q6AFE_TDM_CAP_DAI("AIF TX11", 11, AIF_TDM_TX_11), + Q6AFE_TDM_CAP_DAI("AIF TX12", 12, AIF_TDM_TX_12), Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), @@ -717,9 +769,11 @@ struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: case LPI_MI2S_RX_5 ... LPI_MI2S_TX_6: + case AIF_MI2S_RX_0 ... AIF_MI2S_TX_12: q6dsp_audio_fe_dais[i].ops = cfg->q6i2s_ops; break; case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_TDM_RX_0 ... AIF_TDM_TX_12: q6dsp_audio_fe_dais[i].ops = cfg->q6tdm_ops; break; case WSA_CODEC_DMA_RX_0 ... RX_CODEC_DMA_RX_7: diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c index 1f3ce4cc0837..2042ff1e8ff1 100644 --- a/sound/soc/qcom/qdsp6/q6prm.c +++ b/sound/soc/qcom/qdsp6/q6prm.c @@ -57,6 +57,8 @@ struct prm_cmd_release_rsc { static int q6prm_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(prm->gdev); + return audioreach_send_cmd_sync(prm->dev, prm->gdev, &prm->result, &prm->lock, NULL, &prm->wait, pkt, rsp_opcode); } diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index d076c99f197f..ea511200c8d7 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -68,6 +68,7 @@ { mix_name, "SEC_MI2S_TX", "SEC_MI2S_TX" }, \ { mix_name, "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, \ { mix_name, "QUIN_MI2S_TX", "QUIN_MI2S_TX" }, \ + { mix_name, "SEN_MI2S_TX", "SEN_MI2S_TX" }, \ { mix_name, "TERT_MI2S_TX", "TERT_MI2S_TX" }, \ { mix_name, "SLIMBUS_0_TX", "SLIMBUS_0_TX" }, \ { mix_name, "SLIMBUS_1_TX", "SLIMBUS_1_TX" }, \ @@ -152,6 +153,9 @@ SOC_SINGLE_EXT("QUIN_MI2S_TX", QUINARY_MI2S_TX, \ id, 1, 0, msm_routing_get_audio_mixer, \ msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEN_MI2S_TX", SENARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ SOC_SINGLE_EXT("SLIMBUS_0_TX", SLIMBUS_0_TX, \ id, 1, 0, msm_routing_get_audio_mixer, \ msm_routing_put_audio_mixer), \ @@ -563,6 +567,9 @@ static const struct snd_kcontrol_new quaternary_mi2s_rx_mixer_controls[] = { static const struct snd_kcontrol_new quinary_mi2s_rx_mixer_controls[] = { Q6ROUTING_RX_MIXERS(QUINARY_MI2S_RX) }; +static const struct snd_kcontrol_new senary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SENARY_MI2S_RX) }; + static const struct snd_kcontrol_new tertiary_mi2s_rx_mixer_controls[] = { Q6ROUTING_RX_MIXERS(TERTIARY_MI2S_RX) }; @@ -825,6 +832,9 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { SND_SOC_DAPM_MIXER("QUIN_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, quinary_mi2s_rx_mixer_controls, ARRAY_SIZE(quinary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEN_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + senary_mi2s_rx_mixer_controls, + ARRAY_SIZE(senary_mi2s_rx_mixer_controls)), SND_SOC_DAPM_MIXER("TERT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, tertiary_mi2s_rx_mixer_controls, ARRAY_SIZE(tertiary_mi2s_rx_mixer_controls)), @@ -1038,6 +1048,7 @@ static const struct snd_soc_dapm_route intercon[] = { Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_6_RX Audio Mixer", "SLIMBUS_6_RX"), Q6ROUTING_RX_DAPM_ROUTE("QUAT_MI2S_RX Audio Mixer", "QUAT_MI2S_RX"), Q6ROUTING_RX_DAPM_ROUTE("QUIN_MI2S_RX Audio Mixer", "QUIN_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SEN_MI2S_RX Audio Mixer", "SEN_MI2S_RX"), Q6ROUTING_RX_DAPM_ROUTE("TERT_MI2S_RX Audio Mixer", "TERT_MI2S_RX"), Q6ROUTING_RX_DAPM_ROUTE("SEC_MI2S_RX Audio Mixer", "SEC_MI2S_RX"), Q6ROUTING_RX_DAPM_ROUTE("PRI_MI2S_RX Audio Mixer", "PRI_MI2S_RX"), diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 4d48e1012cd4..9aae6aadd43d 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -25,6 +25,10 @@ #define I2S_BIT_RATE(rate, channels, format) \ ((rate) * (channels) * (format)) +#define ADAU1977_SYSCLK 0 +#define ADAU1977_SYSCLK_SRC_MCLK 0 +#define ADAU1977_SYSCLK_SRC_LRCLK 1 + static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Mic Jack", NULL), @@ -58,6 +62,25 @@ static const struct snd_soc_dapm_widget max98090_dapm_widgets[] = { SND_SOC_DAPM_SPK("Speaker", NULL), }; +enum sc8280xp_jack_setup { + SC8280XP_JACK_SETUP_NONE, + SC8280XP_JACK_SETUP_CODEC, + SC8280XP_JACK_SETUP_WCD, +}; + +struct sc8280xp_dai_data { + unsigned int id; + unsigned int mclk_rate; + bool codec_sysclk_set; + bool mi2s_mclk_enable; + bool mi2s_bclk_enable; + enum sc8280xp_jack_setup jack_setup; +}; + +#define SC8280XP_DAI_DATA(...) \ + .dai_data = (const struct sc8280xp_dai_data[]) { __VA_ARGS__ }, \ + .num_dai_data = ARRAY_SIZE(((const struct sc8280xp_dai_data[]) { __VA_ARGS__ })) + struct qcom_snd_soc_common { const char *driver_name; const struct snd_soc_dapm_widget *dapm_widgets; @@ -71,6 +94,9 @@ struct qcom_snd_soc_common { bool mi2s_mclk_enable; bool mi2s_bclk_enable; bool wcd_jack; + const struct sc8280xp_dai_data *dai_data; + size_t num_dai_data; + int (*snd_init)(struct snd_soc_pcm_runtime *rtd); int (*snd_prepare)(struct snd_pcm_substream *substream); }; @@ -83,6 +109,20 @@ struct sc8280xp_snd_data { bool jack_setup; }; +static const struct sc8280xp_dai_data * +sc8280xp_get_dai_data(const struct qcom_snd_soc_common *common, + unsigned int id) +{ + size_t i; + + for (i = 0; i < common->num_dai_data; i++) { + if (common->dai_data[i].id == id) + return &common->dai_data[i]; + } + + return NULL; +} + static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) { int rate = params_rate(params); @@ -112,13 +152,22 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + const struct sc8280xp_dai_data *dai_data; struct snd_soc_dai *codec_dai; struct qcom_snd_tdm_slot_cfg cpu_cfg; struct qcom_snd_tdm_slot_cfg codec_cfg; + bool codec_sysclk_set = data->priv->codec_sysclk_set; + bool mi2s_bclk_enable = data->priv->mi2s_bclk_enable; int bclk_freq; int ret; int i; + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + if (dai_data) { + codec_sysclk_set = dai_data->codec_sysclk_set; + mi2s_bclk_enable = dai_data->mi2s_bclk_enable; + } + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); if (ret) return ret == -ENOENT ? 0 : ret; @@ -147,7 +196,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, if (bclk_freq <= 0) return -EINVAL; - if (data->priv->mi2s_bclk_enable) { + if (mi2s_bclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, SND_SOC_CLOCK_IN); if (ret && ret != -ENOTSUPP) { @@ -157,7 +206,7 @@ static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, } } - if (data->priv->codec_sysclk_set) { + if (codec_sysclk_set) { for_each_rtd_codec_dais(rtd, i, codec_dai) { ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, SND_SOC_CLOCK_IN); @@ -176,9 +225,19 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) { struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + const struct sc8280xp_dai_data *dai_data; struct snd_soc_card *card = rtd->card; struct snd_soc_jack *dp_jack = NULL; int dp_pcm_id = 0; + int ret; + + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + + if (data->priv->snd_init) { + ret = data->priv->snd_init(rtd); + if (ret) + return ret; + } switch (cpu_dai->id) { case WSA_CODEC_DMA_RX_0: @@ -209,10 +268,24 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) if (dp_jack) return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); - if (data->priv->wcd_jack) - return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + if (!dai_data) { + if (data->priv->wcd_jack) + return qcom_snd_wcd_jack_setup(rtd, &data->jack, + &data->jack_setup); - return 0; + return 0; + } + + switch (dai_data->jack_setup) { + case SC8280XP_JACK_SETUP_CODEC: + return qcom_snd_headset_jack_setup(rtd, &data->jack, + &data->jack_setup); + case SC8280XP_JACK_SETUP_WCD: + return qcom_snd_wcd_jack_setup(rtd, &data->jack, + &data->jack_setup); + default: + return 0; + } } static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, @@ -244,6 +317,35 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, return 0; } +static int nord_snd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int ret; + + switch (cpu_dai->id) { + case AIF_TDM_TX_8: + /* + * adau1979 PLL clocked from LRCLK, no external MCLK. Selecting + * the source also installs the PLL rate constraint, which the + * codec applies from its startup() callback, so it has to be + * in place before the first stream is opened. The frequency is + * unused for an LRCLK-sourced PLL. + */ + ret = snd_soc_component_set_sysclk(codec_dai->component, + ADAU1977_SYSCLK, + ADAU1977_SYSCLK_SRC_LRCLK, + 48000, SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + return ret; + break; + default: + break; + } + + return 0; +} + static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -251,15 +353,30 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); - int mclk_freq = sc8280xp_get_mclk_freq(params); + const struct sc8280xp_dai_data *dai_data; + bool codec_sysclk_set = data->priv->codec_sysclk_set; + bool mi2s_mclk_enable = data->priv->mi2s_mclk_enable; + bool mi2s_bclk_enable = data->priv->mi2s_bclk_enable; + int mclk_freq; int bclk_freq = sc8280xp_get_bclk_freq(params); int ret; + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + mclk_freq = sc8280xp_get_mclk_freq(params); + if (dai_data) { + codec_sysclk_set = dai_data->codec_sysclk_set; + mi2s_mclk_enable = dai_data->mi2s_mclk_enable; + mi2s_bclk_enable = dai_data->mi2s_bclk_enable; + if (dai_data->mclk_rate) + mclk_freq = dai_data->mclk_rate; + } + switch (cpu_dai->id) { case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: case SENARY_MI2S_RX ... SENARY_MI2S_TX: case LPI_MI2S_RX_0 ... LPI_MI2S_TX_4: + case AIF_MI2S_RX_0 ... AIF_MI2S_TX_12: ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); if (ret && ret != -ENOTSUPP) return ret; @@ -271,7 +388,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, return ret; } - if (data->priv->mi2s_mclk_enable) { + if (mi2s_mclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_MCLK, mclk_freq, SND_SOC_CLOCK_OUT); @@ -279,7 +396,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, return ret; } - if (data->priv->mi2s_bclk_enable) { + if (mi2s_bclk_enable) { ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, SND_SOC_CLOCK_OUT); @@ -287,7 +404,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, return ret; } - if (data->priv->codec_sysclk_set) { + if (codec_sysclk_set) { ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_freq, SND_SOC_CLOCK_IN); @@ -296,6 +413,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, } break; case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + case AIF_TDM_RX_0 ... AIF_TDM_TX_12: return sc8280xp_tdm_hw_params(substream, params); default: break; @@ -368,6 +486,19 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) return qcom_snd_sdw_hw_free(substream, &data->stream_prepared[cpu_dai->id]); } +static void sc8280xp_snd_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + const struct sc8280xp_dai_data *dai_data; + + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + if (dai_data && dai_data->jack_setup == SC8280XP_JACK_SETUP_CODEC) { + qcom_snd_headset_jack_cleanup(rtd); + data->jack_setup = false; + } +} + static const struct snd_soc_ops sc8280xp_be_ops = { .startup = qcom_snd_sdw_startup, .shutdown = qcom_snd_sdw_shutdown, @@ -384,6 +515,7 @@ static void sc8280xp_add_be_ops(struct snd_soc_card *card) for_each_card_prelinks(card, i, link) { if (link->no_pcm == 1) { link->init = sc8280xp_snd_init; + link->exit = sc8280xp_snd_exit; link->be_hw_params_fixup = sc8280xp_be_hw_params_fixup; link->ops = &sc8280xp_be_ops; } @@ -461,6 +593,24 @@ static const struct qcom_snd_soc_common kaanapali_priv_data = { .wcd_jack = true, }; +static const struct qcom_snd_soc_common maili_priv_data = { + .driver_name = "maili", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + .wcd_jack = true, +}; + +static const struct qcom_snd_soc_common nord_priv_data = { + .driver_name = "nord", + .mi2s_bclk_enable = true, + .codec_dai_fmt = SND_SOC_DAIFMT_CBC_CFC | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_DSP_A, + .snd_init = nord_snd_init, +}; + static const struct qcom_snd_soc_common qcs9100_priv_data = { .driver_name = "sa8775p", .dapm_widgets = sc8280xp_dapm_widgets, @@ -488,6 +638,28 @@ static const struct qcom_snd_soc_common qcs6490_priv_data = { .wcd_jack = true, }; +static const struct qcom_snd_soc_common rubikpi3_priv_data = { + .driver_name = "qcs6490", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .codec_dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_BC_FC, + SC8280XP_DAI_DATA({ + .id = PRIMARY_MI2S_RX, + .mclk_rate = 19200000, + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + .jack_setup = SC8280XP_JACK_SETUP_CODEC, + }, { + .id = PRIMARY_MI2S_TX, + .mclk_rate = 19200000, + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, + } + ), +}; + static const struct qcom_snd_soc_common qcs8275_priv_data = { .driver_name = "qcs8300", .dapm_widgets = max98090_dapm_widgets, @@ -563,10 +735,12 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, { .compatible = "qcom,hawi-sndcard", .data = &hawi_priv_data }, { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, - { .compatible = "qcom,maili-sndcard", .data = &hawi_priv_data }, + { .compatible = "qcom,maili-sndcard", .data = &maili_priv_data }, + { .compatible = "qcom,nord-sndcard", .data = &nord_priv_data }, { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, + { .compatible = "thundercomm,qcs6490-rubikpi3-sndcard", .data = &rubikpi3_priv_data }, { .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data }, { .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data }, { .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data }, diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c index 6843ab8ba017..85951397cb2b 100644 --- a/sound/soc/qcom/sdm845.c +++ b/sound/soc/qcom/sdm845.c @@ -23,9 +23,14 @@ #define DEFAULT_MCLK_RATE 24576000 #define TDM_BCLK_RATE 6144000 #define MI2S_BCLK_RATE 1536000 -#define LEFT_SPK_TDM_TX_MASK 0x30 -#define RIGHT_SPK_TDM_TX_MASK 0xC0 -#define SPK_TDM_RX_MASK 0x03 +#define LEFT_SPK_TDM_RX_MASK BIT(0) +#define RIGHT_SPK_TDM_RX_MASK BIT(1) +#define SPK_TDM_RX_MASK (LEFT_SPK_TDM_RX_MASK | RIGHT_SPK_TDM_RX_MASK) +#define MIC1_TDM_RX_MASK BIT(2) +#define MIC2_TDM_RX_MASK BIT(3) +#define MIC_TDM_RX_MASK (MIC1_TDM_RX_MASK | MIC2_TDM_RX_MASK) +#define LEFT_SPK_TDM_TX_MASK (BIT(4) | BIT(5)) +#define RIGHT_SPK_TDM_TX_MASK (BIT(6) | BIT(7)) #define NUM_TDM_SLOTS 8 #define SLIM_MAX_TX_PORTS 16 #define SLIM_MAX_RX_PORTS 13 @@ -38,6 +43,7 @@ struct sdm845_snd_data { bool stream_prepared[AFE_PORT_MAX]; uint32_t pri_mi2s_clk_count; uint32_t sec_mi2s_clk_count; + uint32_t quat_mi2s_clk_count; uint32_t quat_tdm_clk_count; }; @@ -112,8 +118,8 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, channels = params_channels(params); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0x3, - 8, slot_width); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, SPK_TDM_RX_MASK, + NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", __func__, ret); @@ -128,8 +134,10 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, goto end; } } else { - ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0, - 8, slot_width); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, + SPK_TDM_RX_MASK | + MIC_TDM_RX_MASK, 0, + NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", __func__, ret); @@ -150,7 +158,7 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, if (!strcmp(codec_dai->component->name_prefix, "Left")) { ret = snd_soc_dai_set_tdm_slot( codec_dai, LEFT_SPK_TDM_TX_MASK, - SPK_TDM_RX_MASK, NUM_TDM_SLOTS, + LEFT_SPK_TDM_RX_MASK, NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, @@ -162,7 +170,7 @@ static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, if (!strcmp(codec_dai->component->name_prefix, "Right")) { ret = snd_soc_dai_set_tdm_slot( codec_dai, RIGHT_SPK_TDM_TX_MASK, - SPK_TDM_RX_MASK, NUM_TDM_SLOTS, + RIGHT_SPK_TDM_RX_MASK, NUM_TDM_SLOTS, slot_width); if (ret < 0) { dev_err(rtd->dev, @@ -365,9 +373,11 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream) break; case QUATERNARY_MI2S_RX: codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; - snd_soc_dai_set_sysclk(cpu_dai, - Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT, - MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + if (++(data->quat_mi2s_clk_count) == 1) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT, + MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + } snd_soc_dai_set_fmt(cpu_dai, fmt); snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); break; @@ -380,7 +390,7 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream) TDM_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); } - codec_dai_fmt |= SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_B; + codec_dai_fmt |= SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_A; for_each_rtd_codec_dais(rtd, j, codec_dai) { @@ -405,6 +415,28 @@ static int sdm845_snd_startup(struct snd_pcm_substream *substream) return ret; } } + + /* Set codec sysclk needed by codecs like cs35l36. */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + TDM_BCLK_RATE, + SND_SOC_CLOCK_IN); + if (ret < 0 && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, + "Failed to set codec dai sysclk: %d\n", + ret); + return ret; + } + + ret = snd_soc_component_set_sysclk(codec_dai->component, + 0, 0, + TDM_BCLK_RATE, + SND_SOC_CLOCK_IN); + if (ret < 0 && ret != -ENOTSUPP) { + dev_err(codec_dai->dev, + "Failed to set codec component sysclk: %d\n", + ret); + return ret; + } } break; case SLIMBUS_0_RX...SLIMBUS_6_TX: @@ -454,8 +486,16 @@ static void sdm845_snd_shutdown(struct snd_pcm_substream *substream) 0, SNDRV_PCM_STREAM_PLAYBACK); } break; - case SLIMBUS_0_RX...SLIMBUS_6_TX: + case QUATERNARY_MI2S_RX: + if (--(data->quat_mi2s_clk_count) == 0) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT, + 0, SNDRV_PCM_STREAM_PLAYBACK); + } + break; + + case SLIMBUS_0_RX...SLIMBUS_6_TX: break; default: diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c index 76dc3a3f4a31..5866f1f34b3d 100644 --- a/sound/soc/qcom/sm8250.c +++ b/sound/soc/qcom/sm8250.c @@ -112,6 +112,14 @@ static int sm8250_snd_startup(struct snd_pcm_substream *substream) snd_soc_dai_set_fmt(cpu_dai, fmt); snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); break; + case SENARY_MI2S_RX: + codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_SEN_MI2S_IBIT, + MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_fmt(cpu_dai, fmt); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; case LPI_MI2S_RX_0: codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; snd_soc_dai_set_sysclk(cpu_dai, @@ -207,6 +215,7 @@ static int sm8250_platform_probe(struct platform_device *pdev) static const struct of_device_id snd_sm8250_dt_match[] = { { .compatible = "fairphone,fp4-sndcard", .data = "sm7225" }, { .compatible = "fairphone,fp5-sndcard", .data = "qcm6490" }, + { .compatible = "motorola,dubai-sndcard", .data = "sm7325" }, { .compatible = "qcom,qrb2210-sndcard", .data = "qcm2290" }, { .compatible = "qcom,qrb4210-rb2-sndcard", .data = "sm4250" }, { .compatible = "qcom,qrb5165-rb5-sndcard", .data = "sm8250" }, diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c index 23b7e63596d3..74e2ea002fbb 100644 --- a/sound/soc/renesas/fsi.c +++ b/sound/soc/renesas/fsi.c @@ -1811,14 +1811,21 @@ static int fsi_pcm_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - int ret = 0; + int ret; snd_soc_set_runtime_hwparams(substream, &fsi_pcm_hardware); ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; - return ret; + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure the component is correctly marked as opened. + */ + return 0; } static snd_pcm_uframes_t fsi_pointer(struct snd_soc_component *component, diff --git a/sound/soc/renesas/rcar/msiof.c b/sound/soc/renesas/rcar/msiof.c index 128543fc4fc9..11a6f22ec0ad 100644 --- a/sound/soc/renesas/rcar/msiof.c +++ b/sound/soc/renesas/rcar/msiof.c @@ -419,18 +419,14 @@ static int msiof_open(struct snd_soc_component *component, return PTR_ERR(chan); ret = snd_dmaengine_pcm_open(substream, chan); - if (ret < 0) - goto open_err_dma; + if (ret < 0) { + dma_release_channel(chan); + return ret; + } snd_soc_set_runtime_hwparams(substream, &msiof_pcm_hardware); - ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); - -open_err_dma: - if (ret < 0) - dma_release_channel(chan); - - return ret; + return 0; } static int msiof_close(struct snd_soc_component *component, diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index 9fe8a639c47c..9a0781bb072d 100644 --- a/sound/soc/renesas/rz-ssi.c +++ b/sound/soc/renesas/rz-ssi.c @@ -900,6 +900,13 @@ static int rz_ssi_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 rz_ssi_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rz_ssi_dai_ops = { .probe = rz_ssi_dai_probe, .startup = rz_ssi_startup, @@ -907,6 +914,8 @@ static const struct snd_soc_dai_ops rz_ssi_dai_ops = { .trigger = rz_ssi_dai_trigger, .set_fmt = rz_ssi_dai_set_fmt, .hw_params = rz_ssi_dai_hw_params, + .auto_selectable_formats = &rz_ssi_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_pcm_hardware rz_ssi_pcm_hardware = { @@ -928,17 +937,21 @@ static const struct snd_pcm_hardware rz_ssi_pcm_hardware = { static int rz_ssi_pcm_open_pio(struct snd_soc_component *component, struct snd_pcm_substream *substream) { + int ret; + snd_soc_set_runtime_hwparams(substream, &rz_ssi_pcm_hardware); - return snd_pcm_hw_constraint_integer(substream->runtime, + ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); -} + if (ret < 0) + return ret; -static int rz_ssi_pcm_open_dma(struct snd_soc_component *component, - struct snd_pcm_substream *substream) -{ - return snd_pcm_hw_constraint_integer(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIODS); + /* + * ASoC only marks the component as opened when .open() returns 0. + * Since snd_pcm_hw_constraint_integer() may return 1, explicitly + * return 0 to ensure the component is correctly marked as opened. + */ + return 0; } static snd_pcm_uframes_t rz_ssi_pcm_pointer(struct snd_soc_component *component, @@ -991,7 +1004,6 @@ static const struct snd_soc_component_driver rz_ssi_soc_component_pio = { static const struct snd_soc_component_driver rz_ssi_soc_component_dma = { .name = "rz-ssi", - .open = rz_ssi_pcm_open_dma, .legacy_dai_naming = 1, }; diff --git a/sound/soc/renesas/siu_dai.c b/sound/soc/renesas/siu_dai.c index 85dee9025710..fff6ef870f0d 100644 --- a/sound/soc/renesas/siu_dai.c +++ b/sound/soc/renesas/siu_dai.c @@ -688,12 +688,18 @@ static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, return ret; } +static const u64 siu_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops siu_dai_ops = { .startup = siu_dai_startup, .shutdown = siu_dai_shutdown, .prepare = siu_dai_prepare, .set_sysclk = siu_dai_set_sysclk, .set_fmt = siu_dai_set_fmt, + .auto_selectable_formats = &siu_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver siu_i2s_dai = { diff --git a/sound/soc/renesas/ssi.c b/sound/soc/renesas/ssi.c index 96cf523c2273..f00067bafe8d 100644 --- a/sound/soc/renesas/ssi.c +++ b/sound/soc/renesas/ssi.c @@ -329,6 +329,17 @@ static int ssi_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) +static const u64 ssi_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_GATED | + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops ssi_dai_ops = { .startup = ssi_startup, .shutdown = ssi_shutdown, @@ -337,6 +348,8 @@ static const struct snd_soc_dai_ops ssi_dai_ops = { .set_sysclk = ssi_set_sysclk, .set_clkdiv = ssi_set_clkdiv, .set_fmt = ssi_set_fmt, + .auto_selectable_formats = &ssi_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sh4_ssi_dai[] = { diff --git a/sound/soc/rockchip/rk3288_hdmi_analog.c b/sound/soc/rockchip/rk3288_hdmi_analog.c index 541163ed56fc..9bb02d41d901 100644 --- a/sound/soc/rockchip/rk3288_hdmi_analog.c +++ b/sound/soc/rockchip/rk3288_hdmi_analog.c @@ -201,36 +201,59 @@ static int snd_rk_mc_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "Unable to parse property 'rockchip,audio-codec'\n"); - return ret; + goto put_codec_of_node; } ret = snd_soc_get_dai_name(&args, &rk_dailink.codecs[0].dai_name); - if (ret) - return dev_err_probe(&pdev->dev, ret, - "Unable to get codec_dai_name\n"); + of_node_put(args.np); + if (ret) { + dev_err_probe(&pdev->dev, ret, + "Unable to get codec_dai_name\n"); + goto put_codec_of_node; + } rk_dailink.cpus->of_node = of_parse_phandle(np, "rockchip,i2s-controller", 0); if (!rk_dailink.cpus->of_node) { dev_err(&pdev->dev, "Property 'rockchip,i2s-controller' missing or invalid\n"); - return -EINVAL; + ret = -EINVAL; + goto put_codec_of_node; } rk_dailink.platforms->of_node = rk_dailink.cpus->of_node; ret = snd_soc_of_parse_audio_routing(card, "rockchip,routing"); if (ret) - return ret; + goto put_cpu_of_node; snd_soc_card_set_drvdata(card, machine); ret = devm_snd_soc_register_card(&pdev->dev, card); - if (ret) - return dev_err_probe(&pdev->dev, ret, - "Soc register card failed\n"); + if (ret) { + dev_err_probe(&pdev->dev, ret, + "Soc register card failed\n"); + goto put_cpu_of_node; + } return 0; + +put_cpu_of_node: + of_node_put(rk_dailink.cpus->of_node); + rk_dailink.cpus->of_node = NULL; +put_codec_of_node: + of_node_put(rk_dailink.codecs[0].of_node); + rk_dailink.codecs[0].of_node = NULL; + + return ret; +} + +static void snd_rk_mc_remove(struct platform_device *pdev) +{ + of_node_put(rk_dailink.cpus->of_node); + rk_dailink.cpus->of_node = NULL; + of_node_put(rk_dailink.codecs[0].of_node); + rk_dailink.codecs[0].of_node = NULL; } static const struct of_device_id rockchip_sound_of_match[] = { @@ -242,6 +265,7 @@ MODULE_DEVICE_TABLE(of, rockchip_sound_of_match); static struct platform_driver rockchip_sound_driver = { .probe = snd_rk_mc_probe, + .remove = snd_rk_mc_remove, .driver = { .name = DRV_NAME, .pm = &snd_soc_pm_ops, diff --git a/sound/soc/rockchip/rk3399_gru_sound.c b/sound/soc/rockchip/rk3399_gru_sound.c index b80acb221d24..d4a0daabfb42 100644 --- a/sound/soc/rockchip/rk3399_gru_sound.c +++ b/sound/soc/rockchip/rk3399_gru_sound.c @@ -258,10 +258,20 @@ static int rockchip_sound_dmic_hw_params(struct snd_pcm_substream *substream, static int rockchip_sound_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; + int ret; runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - return snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, - 8000, 96000); + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + 8000, 96000); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI link as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI link is correctly marked as started. + */ + return 0; } static const struct snd_soc_ops rockchip_sound_max98357a_ops = { @@ -505,8 +515,8 @@ static int rockchip_sound_of_parse_dais(struct device *dev, int i, index; int num_routes; - card->dai_link = devm_kzalloc(dev, sizeof(rockchip_dais), - GFP_KERNEL); + card->dai_link = devm_kcalloc(dev, ARRAY_SIZE(rockchip_dais), + sizeof(*card->dai_link), GFP_KERNEL); if (!card->dai_link) return -ENOMEM; diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index 261f36d4c2fd..0a67632e68ed 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -244,7 +244,9 @@ static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int mask = 0, val = 0; int ret = 0; - pm_runtime_get_sync(cpu_dai->dev); + ret = pm_runtime_resume_and_get(cpu_dai->dev); + if (ret) + return ret; mask = I2S_CKR_MSS_MASK; switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_BP_FP: @@ -537,13 +539,26 @@ static int rockchip_i2s_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 rockchip_i2s_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rockchip_i2s_dai_ops = { - .probe = rockchip_i2s_dai_probe, - .hw_params = rockchip_i2s_hw_params, - .set_bclk_ratio = rockchip_i2s_set_bclk_ratio, - .set_sysclk = rockchip_i2s_set_sysclk, - .set_fmt = rockchip_i2s_set_fmt, - .trigger = rockchip_i2s_trigger, + .probe = rockchip_i2s_dai_probe, + .hw_params = rockchip_i2s_hw_params, + .set_bclk_ratio = rockchip_i2s_set_bclk_ratio, + .set_sysclk = rockchip_i2s_set_sysclk, + .set_fmt = rockchip_i2s_set_fmt, + .trigger = rockchip_i2s_trigger, + .auto_selectable_formats = &rockchip_i2s_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver rockchip_i2s_dai = { diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c index 5ef7f109706d..983319a538b1 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.c +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -826,14 +826,27 @@ static int rockchip_i2s_tdm_set_bclk_ratio(struct snd_soc_dai *dai, return 0; } +static const u64 rockchip_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rockchip_i2s_tdm_dai_ops = { - .probe = rockchip_i2s_tdm_dai_probe, - .hw_params = rockchip_i2s_tdm_hw_params, - .set_bclk_ratio = rockchip_i2s_tdm_set_bclk_ratio, - .set_fmt = rockchip_i2s_tdm_set_fmt, - .set_sysclk = rockchip_i2s_tdm_set_sysclk, - .set_tdm_slot = rockchip_dai_tdm_slot, - .trigger = rockchip_i2s_tdm_trigger, + .probe = rockchip_i2s_tdm_dai_probe, + .hw_params = rockchip_i2s_tdm_hw_params, + .set_bclk_ratio = rockchip_i2s_tdm_set_bclk_ratio, + .set_fmt = rockchip_i2s_tdm_set_fmt, + .set_sysclk = rockchip_i2s_tdm_set_sysclk, + .set_tdm_slot = rockchip_dai_tdm_slot, + .trigger = rockchip_i2s_tdm_trigger, + .auto_selectable_formats = &rockchip_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_component_driver rockchip_i2s_tdm_component = { diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c index 426506a8e18e..de392b420ebd 100644 --- a/sound/soc/rockchip/rockchip_max98090.c +++ b/sound/soc/rockchip/rockchip_max98090.c @@ -191,12 +191,23 @@ static int rk_aif1_hw_params(struct snd_pcm_substream *substream, static int rk_aif1_startup(struct snd_pcm_substream *substream) { + int ret; /* * Set period size to 240 because pl330 has issue * dealing with larger period in stress testing. */ - return snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 240, 240); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + 240, 240); + if (ret < 0) + return ret; + + /* + * ASoC only marks the DAI link as started when .startup() returns 0. + * Since snd_pcm_hw_constraint_minmax() may return 1, explicitly + * return 0 to ensure the DAI link is correctly marked as started. + */ + return 0; } static const struct snd_soc_ops rk_aif1_ops = { diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c index 343fda478901..0720748c7c8d 100644 --- a/sound/soc/rockchip/rockchip_pdm.c +++ b/sound/soc/rockchip/rockchip_pdm.c @@ -381,11 +381,17 @@ static int rockchip_pdm_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 rockchip_pdm_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF; + static const struct snd_soc_dai_ops rockchip_pdm_dai_ops = { - .probe = rockchip_pdm_dai_probe, - .set_fmt = rockchip_pdm_set_fmt, - .trigger = rockchip_pdm_trigger, - .hw_params = rockchip_pdm_hw_params, + .probe = rockchip_pdm_dai_probe, + .set_fmt = rockchip_pdm_set_fmt, + .trigger = rockchip_pdm_trigger, + .hw_params = rockchip_pdm_hw_params, + .auto_selectable_formats = &rockchip_pdm_selectable_formats, + .num_auto_selectable_formats = 1, }; #define ROCKCHIP_PDM_RATES SNDRV_PCM_RATE_8000_192000 diff --git a/sound/soc/rockchip/rockchip_sai.c b/sound/soc/rockchip/rockchip_sai.c index 30b5e71d0937..dab96df8bf96 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -487,7 +487,9 @@ static int rockchip_sai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) unsigned long flags; int ret = 0; - pm_runtime_get_sync(dai->dev); + ret = pm_runtime_resume_and_get(dai->dev); + if (ret) + return ret; mask = SAI_CKR_MSS_MASK; switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { @@ -957,16 +959,29 @@ static int rockchip_sai_set_sysclk(struct snd_soc_dai *dai, int stream, return 0; } +static const u64 rockchip_sai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops rockchip_sai_dai_ops = { - .probe = rockchip_sai_dai_probe, - .startup = rockchip_sai_startup, - .shutdown = rockchip_sai_shutdown, - .hw_params = rockchip_sai_hw_params, - .set_fmt = rockchip_sai_set_fmt, - .set_sysclk = rockchip_sai_set_sysclk, - .prepare = rockchip_sai_prepare, - .trigger = rockchip_sai_trigger, - .set_tdm_slot = rockchip_sai_set_tdm_slot, + .probe = rockchip_sai_dai_probe, + .startup = rockchip_sai_startup, + .shutdown = rockchip_sai_shutdown, + .hw_params = rockchip_sai_hw_params, + .set_fmt = rockchip_sai_set_fmt, + .set_sysclk = rockchip_sai_set_sysclk, + .prepare = rockchip_sai_prepare, + .trigger = rockchip_sai_trigger, + .set_tdm_slot = rockchip_sai_set_tdm_slot, + .auto_selectable_formats = &rockchip_sai_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver rockchip_sai_dai = { diff --git a/sound/soc/samsung/aries_wm8994.c b/sound/soc/samsung/aries_wm8994.c index d8659973540f..1a5b53f47571 100644 --- a/sound/soc/samsung/aries_wm8994.c +++ b/sound/soc/samsung/aries_wm8994.c @@ -374,17 +374,15 @@ static int aries_late_probe(struct snd_soc_card *card) irq = gpiod_to_irq(priv->gpio_headset_detect); if (irq < 0) { dev_err(card->dev, "Failed to map headset detect gpio to irq"); - return -EINVAL; + return irq; } ret = devm_request_threaded_irq(card->dev, irq, NULL, headset_det_irq_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "headset_detect", priv); - if (ret) { - dev_err(card->dev, "Failed to request headset detect irq"); + if (ret) return ret; - } headset_button_gpio[0].data = priv; headset_button_gpio[0].desc = priv->gpio_headset_key; @@ -623,10 +621,8 @@ static int aries_audio_probe(struct platform_device *pdev) if (ret < 0) { /* Backwards compatible way */ ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing"); - if (ret < 0) { - dev_err(dev, "Audio routing invalid/unspecified\n"); + if (ret < 0) return ret; - } } aries_dai[1].dai_fmt = priv->variant->modem_dai_fmt; @@ -671,10 +667,8 @@ static int aries_audio_probe(struct platform_device *pdev) ret = devm_snd_soc_register_component(dev, &aries_component, aries_ext_dai, ARRAY_SIZE(aries_ext_dai)); - if (ret < 0) { - dev_err(dev, "Failed to register component: %d\n", ret); + if (ret < 0) goto out; - } ret = devm_snd_soc_register_card(dev, card); if (ret) diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index 309f024bf2a4..8430686760d7 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -499,10 +499,9 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev) return PTR_ERR(pcm->regs); pcm->cclk = devm_clk_get(&pdev->dev, "audio-bus"); - if (IS_ERR(pcm->cclk)) { - dev_err(&pdev->dev, "failed to get audio-bus clock\n"); - return PTR_ERR(pcm->cclk); - } + if (IS_ERR(pcm->cclk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pcm->cclk), + "failed to get audio-bus clock\n"); ret = clk_prepare_enable(pcm->cclk); if (ret) return ret; @@ -512,8 +511,8 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev) pcm->pclk = devm_clk_get(&pdev->dev, "pcm"); if (IS_ERR(pcm->pclk)) { - dev_err(&pdev->dev, "failed to get pcm clock\n"); - ret = PTR_ERR(pcm->pclk); + ret = dev_err_probe(&pdev->dev, PTR_ERR(pcm->pclk), + "failed to get pcm clock\n"); goto err_dis_cclk; } ret = clk_prepare_enable(pcm->pclk); @@ -535,19 +534,15 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev) ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, NULL, NULL, NULL); - if (ret) { - dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); + if (ret) goto err_dis_pclk; - } pm_runtime_enable(&pdev->dev); ret = devm_snd_soc_register_component(&pdev->dev, &s3c_pcm_component, &s3c_pcm_dai[pdev->id], 1); - if (ret != 0) { - dev_err(&pdev->dev, "failed to get register DAI: %d\n", ret); + if (ret != 0) goto err_dis_pm; - } return 0; diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c index 53eaabaf8956..719083e8e89d 100644 --- a/sound/soc/samsung/spdif.c +++ b/sound/soc/samsung/spdif.c @@ -415,19 +415,15 @@ static int spdif_probe(struct platform_device *pdev) ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, NULL, NULL, NULL); - if (ret) { - dev_err(&pdev->dev, "failed to register DMA: %d\n", ret); + if (ret) goto err2; - } dev_set_drvdata(&pdev->dev, spdif); ret = devm_snd_soc_register_component(&pdev->dev, &samsung_spdif_component, &samsung_spdif_dai, 1); - if (ret != 0) { - dev_err(&pdev->dev, "fail to register dai\n"); + if (ret != 0) goto err2; - } return 0; err2: diff --git a/sound/soc/samsung/tm2_wm5110.c b/sound/soc/samsung/tm2_wm5110.c index f1f59e059f5d..f4912826dfd4 100644 --- a/sound/soc/samsung/tm2_wm5110.c +++ b/sound/soc/samsung/tm2_wm5110.c @@ -518,19 +518,15 @@ static int tm2_probe(struct platform_device *pdev) } ret = snd_soc_of_parse_card_name(card, "model"); - if (ret < 0) { - dev_err(dev, "Card name is not specified\n"); + if (ret < 0) return ret; - } ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); if (ret < 0) { /* Backwards compatible way */ ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing"); - if (ret < 0) { - dev_err(dev, "Audio routing is not specified or invalid\n"); + if (ret < 0) return ret; - } } card->aux_dev[0].dlc.of_node = of_parse_phandle(dev->of_node, @@ -609,10 +605,8 @@ static int tm2_probe(struct platform_device *pdev) ret = devm_snd_soc_register_component(dev, &tm2_component, tm2_ext_dai, ARRAY_SIZE(tm2_ext_dai)); - if (ret < 0) { - dev_err(dev, "Failed to register component: %d\n", ret); + if (ret < 0) goto dai_node_put; - } ret = devm_snd_soc_register_card(dev, card); if (ret < 0) { diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig index 4c0dcb9ff3b9..c371323cc36e 100644 --- a/sound/soc/sdca/Kconfig +++ b/sound/soc/sdca/Kconfig @@ -3,11 +3,13 @@ menu "SoundWire (SDCA)" config SND_SOC_SDCA tristate "SDCA core support" - depends on ACPI select AUXILIARY_BUS help This option enables support for the MIPI SoundWire Device - Class for Audio (SDCA). + Class for Audio (SDCA). The ACPI-specific enumeration + helpers are only built when CONFIG_ACPI is enabled; on + non-ACPI platforms the SDCA function data is supplied + statically by the codec driver. config SND_SOC_SDCA_HID bool "SDCA HID support" diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index ce2e7c270765..ccc579f878ff 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -1507,7 +1507,7 @@ int sdca_asoc_set_constraints(struct device *dev, struct regmap *regmap, return ret; } - dai->priv = constraint; + snd_soc_dai_set_priv(dai, constraint); return 0; } @@ -1523,7 +1523,7 @@ EXPORT_SYMBOL_NS(sdca_asoc_set_constraints, "SND_SOC_SDCA"); void sdca_asoc_free_constraints(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_pcm_hw_constraint_list *constraint = dai->priv; + struct snd_pcm_hw_constraint_list *constraint = snd_soc_dai_to_priv(dai); kfree(constraint); } diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c index d7444f442c71..79fdb672be1d 100644 --- a/sound/soc/sdca/sdca_class.c +++ b/sound/soc/sdca/sdca_class.c @@ -20,7 +20,7 @@ #include #include #include -#include "sdca_class.h" +#include #define CLASS_SDW_ATTACH_TIMEOUT_MS 5000 @@ -136,18 +136,35 @@ static void class_boot_work(struct work_struct *work) pm_runtime_put_sync(drv->dev); } -static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id) +/** + * sdca_class_probe - SDCA class SoundWire slave probe helper + * @sdw: SoundWire slave + * @drv: caller-allocated sdca_class_drv storage. The caller (a codec + * driver, or the built-in class_sdw_driver in this file) owns the + * allocation and sets its own dev_set_drvdata() -- the framework + * does not touch drvdata. Typically embedded in the codec's own + * priv struct so codec drivers can keep per-slave state. + * @ops: optional codec-provided class callbacks (may be NULL for + * pure-generic SDCA parts that need no quirks) + * + * Codec-specific SoundWire drivers call this from their .probe after + * allocating a struct sdca_class_drv (usually embedded in their own + * priv) and setting drvdata to their priv. The framework fills in the + * sdca_class_drv fields, sets up the class regmap, and queues the + * deferred boot work. + */ +int sdca_class_probe(struct sdw_slave *sdw, + struct sdca_class_drv *drv, + const struct sdca_class_ops *ops) { struct device *dev = &sdw->dev; struct regmap_config *dev_config; - struct sdca_class_drv *drv; int ret; sdca_lookup_swft(sdw); - drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); if (!drv) - return -ENOMEM; + return -EINVAL; dev_config = devm_kmemdup(dev, &class_dev_regmap_config, sizeof(*dev_config), GFP_KERNEL); @@ -156,11 +173,10 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id drv->dev = dev; drv->sdw = sdw; + drv->ops = ops; mutex_init(&drv->regmap_lock); mutex_init(&drv->init_lock); - dev_set_drvdata(drv->dev, drv); - INIT_WORK(&drv->boot_work, class_boot_work); dev_config->lock_arg = &drv->regmap_lock; @@ -185,39 +201,83 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_probe, "SND_SOC_SDCA_CLASS"); + +static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id) +{ + struct sdca_class_drv *drv; + + /* + * Pure-generic SDCA parts: no codec priv to embed, so allocate a + * bare sdca_class_drv here and stash it in drvdata for the + * built-in PM ops to fetch. + */ + drv = devm_kzalloc(&sdw->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + dev_set_drvdata(&sdw->dev, drv); + + return sdca_class_probe(sdw, drv, NULL); +} + +/** + * sdca_class_remove - SDCA class SoundWire slave remove helper + * @drv: caller-owned sdca_class_drv (the one handed to sdca_class_probe()). + * + * Cancels the deferred boot work so devres can safely free @drv and the + * embedding codec priv without racing class_boot_work. Codec-specific + * SoundWire drivers that call sdca_class_probe() must call this from + * their .remove with the same drv pointer they passed to probe. + */ +void sdca_class_remove(struct sdca_class_drv *drv) +{ + cancel_work_sync(&drv->boot_work); +} +EXPORT_SYMBOL_NS_GPL(sdca_class_remove, "SND_SOC_SDCA_CLASS"); static void class_sdw_remove(struct sdw_slave *sdw) { - struct device *dev = &sdw->dev; - struct sdca_class_drv *drv = dev_get_drvdata(dev); + struct sdca_class_drv *drv = dev_get_drvdata(&sdw->dev); - cancel_work_sync(&drv->boot_work); + sdca_class_remove(drv); } -static int class_suspend(struct device *dev) +/** + * sdca_class_system_suspend - SDCA class system suspend helper + * @drv: caller-owned sdca_class_drv. + * + * Codec drivers compose this into their own dev_pm_ops. Disables the + * SoundWire interrupt and forces runtime suspend of the underlying + * class regmap. + */ +int sdca_class_system_suspend(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); int ret; disable_irq(drv->sdw->irq); - ret = pm_runtime_force_suspend(dev); + ret = pm_runtime_force_suspend(drv->dev); if (ret) { - dev_err(dev, "failed to force suspend: %d\n", ret); + dev_err(drv->dev, "failed to force suspend: %d\n", ret); return ret; } return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_system_suspend, "SND_SOC_SDCA_CLASS"); -static int class_resume(struct device *dev) +/** + * sdca_class_system_resume - SDCA class system resume helper + * @drv: caller-owned sdca_class_drv. + */ +int sdca_class_system_resume(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); int ret; - ret = pm_runtime_force_resume(dev); + ret = pm_runtime_force_resume(drv->dev); if (ret) { - dev_err(dev, "failed to force resume: %d\n", ret); + dev_err(drv->dev, "failed to force resume: %d\n", ret); return ret; } @@ -225,11 +285,14 @@ static int class_resume(struct device *dev) return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_system_resume, "SND_SOC_SDCA_CLASS"); -static int class_runtime_suspend(struct device *dev) +/** + * sdca_class_runtime_suspend - SDCA class runtime suspend helper + * @drv: caller-owned sdca_class_drv. + */ +int sdca_class_runtime_suspend(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); - /* * Whilst the driver doesn't power the chip down here, going into runtime * suspend lets the SoundWire bus power down, which means the driver @@ -239,10 +302,14 @@ static int class_runtime_suspend(struct device *dev) return 0; } +EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_suspend, "SND_SOC_SDCA_CLASS"); -static int class_runtime_resume(struct device *dev) +/** + * sdca_class_runtime_resume - SDCA class runtime resume helper + * @drv: caller-owned sdca_class_drv. + */ +int sdca_class_runtime_resume(struct sdca_class_drv *drv) { - struct sdca_class_drv *drv = dev_get_drvdata(dev); int ret; ret = sdw_slave_wait_for_init(drv->sdw, CLASS_SDW_ATTACH_TIMEOUT_MS); @@ -265,10 +332,37 @@ static int class_runtime_resume(struct device *dev) return ret; } +EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_resume, "SND_SOC_SDCA_CLASS"); -static const struct dev_pm_ops class_pm_ops = { - SYSTEM_SLEEP_PM_OPS(class_suspend, class_resume) - RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL) +/* + * Convenience dev_pm_ops used by the built-in class_sdw_driver, which + * stashes its sdca_class_drv in drvdata directly. Codec drivers that + * embed sdca_class_drv in their own priv compose their own dev_pm_ops + * using the sdca_class_*_suspend/resume helpers above. + */ +static int class_pm_system_suspend(struct device *dev) +{ + return sdca_class_system_suspend(dev_get_drvdata(dev)); +} + +static int class_pm_system_resume(struct device *dev) +{ + return sdca_class_system_resume(dev_get_drvdata(dev)); +} + +static int class_pm_runtime_suspend(struct device *dev) +{ + return sdca_class_runtime_suspend(dev_get_drvdata(dev)); +} + +static int class_pm_runtime_resume(struct device *dev) +{ + return sdca_class_runtime_resume(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops sdca_class_pm_ops = { + SYSTEM_SLEEP_PM_OPS(class_pm_system_suspend, class_pm_system_resume) + RUNTIME_PM_OPS(class_pm_runtime_suspend, class_pm_runtime_resume, NULL) }; static const struct sdw_device_id class_sdw_id[] = { @@ -282,7 +376,7 @@ MODULE_DEVICE_TABLE(sdw, class_sdw_id); static struct sdw_driver class_sdw_driver = { .driver = { .name = "sdca_class", - .pm = pm_ptr(&class_pm_ops), + .pm = pm_ptr(&sdca_class_pm_ops), }, .probe = class_sdw_probe, diff --git a/sound/soc/sdca/sdca_class.h b/sound/soc/sdca/sdca_class.h deleted file mode 100644 index 57f7f8d08f49..000000000000 --- a/sound/soc/sdca/sdca_class.h +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * The MIPI SDCA specification is available for public downloads at - * https://www.mipi.org/mipi-sdca-v1-0-download - * - * Copyright (C) 2025 Cirrus Logic, Inc. and - * Cirrus Logic International Semiconductor Ltd. - */ - -#ifndef __SDCA_CLASS_H__ -#define __SDCA_CLASS_H__ - -#include -#include -#include - -struct device; -struct regmap; -struct sdw_slave; -struct sdca_function_data; - -struct sdca_class_drv { - struct device *dev; - struct regmap *dev_regmap; - struct sdw_slave *sdw; - - struct sdca_interrupt_info *irq_info; - - struct mutex regmap_lock; - /* Serialise function initialisations */ - struct mutex init_lock; - struct work_struct boot_work; -}; - -#endif /* __SDCA_CLASS_H__ */ diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c index 610931ae5531..13b70f0f0dbf 100644 --- a/sound/soc/sdca/sdca_class_function.c +++ b/sound/soc/sdca/sdca_class_function.c @@ -26,7 +26,7 @@ #include #include #include -#include "sdca_class.h" +#include #include "sdca_function_device.h" struct class_function_drv { @@ -216,9 +216,43 @@ static int class_function_set_jack(struct snd_soc_component *component, return sdca_jack_set_jack(core->irq_info, jack); } +/* + * DT phandle cell is the SDCA entity index (matches dais[].id), not the + * positional DAI index the default xlate assumes. + */ +static int class_function_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + struct class_function_drv *drv = snd_soc_component_get_drvdata(component); + struct sdca_function_data *function = drv->function; + struct sdca_entity *entity; + u32 target; + + if (args->args_count != 1) + return -EINVAL; + + target = args->args[0]; + if (target >= function->num_entities) + goto err; + + entity = &function->entities[target]; + if ((entity->type != SDCA_ENTITY_TYPE_IT && + entity->type != SDCA_ENTITY_TYPE_OT) || !entity->iot.is_dataport) + goto err; + + *dai_name = entity->label; + return 0; +err: + dev_err(component->dev, "xlate: no dataport entity at index %u (num_entities=%d)\n", + target, function->num_entities); + return -EINVAL; +} + static const struct snd_soc_component_driver class_function_component_drv = { .fixup_controls = class_function_component_fixup_controls, .remove = class_function_component_remove, + .of_xlate_dai_name = class_function_of_xlate_dai_name, .endianness = 1, }; @@ -329,7 +363,14 @@ static int class_function_probe(struct auxiliary_device *auxdev, drv->core = core; drv->function = &sdev->function; - ret = sdca_parse_function(dev, drv->function); + if (core->ops && core->ops->populate_function) { + ret = core->ops->populate_function(dev, drv->function); + } else if (drv->function->desc->node) { + ret = sdca_parse_function(dev, drv->function); + } else { + dev_err(dev, "no firmware node and no populate_function hook\n"); + return -ENOENT; + } if (ret) return ret; @@ -367,6 +408,7 @@ static int class_function_probe(struct auxiliary_device *auxdev, switch (drv->function->desc->type) { case SDCA_FUNCTION_TYPE_UAJ: case SDCA_FUNCTION_TYPE_RJ: + case SDCA_FUNCTION_TYPE_SIMPLE_JACK: cmp_drv->set_jack = class_function_set_jack; break; default: @@ -559,6 +601,10 @@ static const struct auxiliary_device_id class_function_id_table[] = { .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_RJ_NAME, .driver_data = SDCA_FUNCTION_TYPE_RJ, }, + { + .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_SIMPLE_NAME, + .driver_data = SDCA_FUNCTION_TYPE_SIMPLE_JACK, + }, {}, }; MODULE_DEVICE_TABLE(auxiliary, class_function_id_table); diff --git a/sound/soc/sdca/sdca_device.c b/sound/soc/sdca/sdca_device.c index 4bcd8d1fdff8..3f302bfa5457 100644 --- a/sound/soc/sdca/sdca_device.c +++ b/sound/soc/sdca/sdca_device.c @@ -15,6 +15,7 @@ #include #include +#if IS_ENABLED(CONFIG_ACPI) void sdca_lookup_interface_revision(struct sdw_slave *slave) { struct fwnode_handle *fwnode = slave->dev.fwnode; @@ -45,6 +46,9 @@ void sdca_lookup_swft(struct sdw_slave *slave) devm_add_action_or_reset(&slave->dev, devm_acpi_table_put, slave->sdca_data.swft); } +#else +void sdca_lookup_swft(struct sdw_slave *slave) { } +#endif EXPORT_SYMBOL_NS(sdca_lookup_swft, "SND_SOC_SDCA"); static bool sdca_device_quirk_rt712_vb(struct sdw_slave *slave) diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c index 150e36ed24bc..7fc809707944 100644 --- a/sound/soc/sdca/sdca_fdl.c +++ b/sound/soc/sdca/sdca_fdl.c @@ -199,7 +199,6 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, struct sdca_fdl_file *fdl_file; char *disk_filename; int ret; - int i; if (!set) { dev_err(dev, "request to load SWF with no set\n"); @@ -209,9 +208,18 @@ static int fdl_load_file(struct sdca_interrupt *interrupt, fdl_file = &set->files[file_index]; if (fdl_data->swft) { - tmp = fdl_data->swft->files; - for (i = 0; i < fdl_data->swft->header.length; i += tmp->file_length, - tmp = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length)) { + struct acpi_sw_file *table_end, *next; + + table_end = ACPI_ADD_PTR(struct acpi_sw_file, fdl_data->swft, + fdl_data->swft->header.length); + for (tmp = fdl_data->swft->files; tmp + 1 <= table_end; tmp = next) { + next = ACPI_ADD_PTR(struct acpi_sw_file, tmp, tmp->file_length); + + if (tmp->file_length < sizeof(*tmp) || next > table_end) { + dev_err(dev, "bad file length in SWFT: %u\n", tmp->file_length); + break; + } + if (tmp->vendor_id == fdl_file->vendor_id && tmp->file_id == fdl_file->file_id) { dev_dbg(dev, "located SWF in ACPI: %x-%x-%x\n", @@ -293,6 +301,8 @@ static struct sdca_fdl_set *fdl_get_set(struct sdca_interrupt *interrupt) range = sdca_selector_find_range(dev, xu, SDCA_CTL_XU_FDL_SET_INDEX, SDCA_FDL_SET_INDEX_NCOLS, 0); + if (!range) + return NULL; val = sdca_range_search(range, SDCA_FDL_SET_INDEX_SET_NUMBER, val, SDCA_FDL_SET_INDEX_FILE_SET_ID); diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c index 32d9b7f30a4f..1eb6418e132d 100644 --- a/sound/soc/sdca/sdca_functions.c +++ b/sound/soc/sdca/sdca_functions.c @@ -25,6 +25,7 @@ */ #define SDCA_PROPERTY_LENGTH 64 +#if IS_ENABLED(CONFIG_ACPI) static int patch_sdca_function_type(u32 interface_revision, u32 *function_type) { /* @@ -78,6 +79,8 @@ static const char *get_sdca_function_name(u32 function_type) return SDCA_FUNCTION_TYPE_SPEAKER_MIC_NAME; case SDCA_FUNCTION_TYPE_RJ: return SDCA_FUNCTION_TYPE_RJ_NAME; + case SDCA_FUNCTION_TYPE_SIMPLE_JACK: + return SDCA_FUNCTION_TYPE_SIMPLE_NAME; case SDCA_FUNCTION_TYPE_COMPANION_AMP: return SDCA_FUNCTION_TYPE_COMPANION_AMP_NAME; case SDCA_FUNCTION_TYPE_IMP_DEF: @@ -199,6 +202,7 @@ void sdca_lookup_functions(struct sdw_slave *slave) acpi_dev_for_each_child(adev, find_sdca_function, &slave->sdca_data); } EXPORT_SYMBOL_NS(sdca_lookup_functions, "SND_SOC_SDCA"); +#endif struct raw_init_write { __le32 addr; @@ -918,8 +922,8 @@ static int find_sdca_control_value(struct device *dev, struct sdca_entity *entit return 0; } -static int find_sdca_control_reset(const struct sdca_entity *entity, - struct sdca_control *control) +static void find_sdca_control_reset(const struct sdca_entity *entity, + struct sdca_control *control) { switch (SDCA_CTL_TYPE(entity->type, control->sel)) { case SDCA_CTL_TYPE_S(FU, AGC): @@ -946,8 +950,6 @@ static int find_sdca_control_reset(const struct sdca_entity *entity, default: break; } - - return 0; } static int find_sdca_entity_control(struct device *dev, struct sdca_entity *entity, @@ -1028,9 +1030,7 @@ static int find_sdca_entity_control(struct device *dev, struct sdca_entity *enti control->is_volatile = find_sdca_control_volatile(entity, control); - ret = find_sdca_control_reset(entity, control); - if (ret) - return ret; + find_sdca_control_reset(entity, control); ret = find_sdca_control_range(dev, control_node, &control->range); if (ret) { @@ -1985,7 +1985,6 @@ static int find_sdca_cluster_channels(struct device *dev, char channel_property[SDCA_PROPERTY_LENGTH]; struct fwnode_handle *channel_node; - /* DisCo uses upper-case for hex numbers */ snprintf(channel_property, sizeof(channel_property), "mipi-sdca-channel-%d-subproperties", i + 1); diff --git a/sound/soc/sdw_utils/Makefile b/sound/soc/sdw_utils/Makefile index 5ae8c69b8b98..1dbc00a2d64a 100644 --- a/sound/soc/sdw_utils/Makefile +++ b/sound/soc/sdw_utils/Makefile @@ -10,5 +10,7 @@ snd-soc-sdw-utils-y := soc_sdw_utils.o soc_sdw_dmic.o soc_sdw_rt_dmic.o \ soc_sdw_cs_amp.o \ soc_sdw_es9356.o \ soc_sdw_maxim.o \ - soc_sdw_ti_amp.o + soc_sdw_ti_amp.o \ + soc_sdw_senary_sdca.o soc_sdw_senary_sdca_jack_common.o \ + soc_sdw_senary_amp.o soc_sdw_senary_dmic.o obj-$(CONFIG_SND_SOC_SDW_UTILS) += snd-soc-sdw-utils.o diff --git a/sound/soc/sdw_utils/soc_sdw_rt711.c b/sound/soc/sdw_utils/soc_sdw_rt711.c index 3a3a66b4b737..4707190e160c 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt711.c +++ b/sound/soc/sdw_utils/soc_sdw_rt711.c @@ -124,6 +124,7 @@ int asoc_sdw_rt711_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_ device_remove_software_node(ctx->headset_codec_dev); put_device(ctx->headset_codec_dev); + ctx->headset_codec_dev = NULL; return 0; } diff --git a/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c b/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c index 5bf3627a97a0..6e5fa93b5fc2 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_mf_sdca.c @@ -33,6 +33,11 @@ static const struct snd_soc_dapm_route rt722_spk_map[] = { { "Speaker", NULL, "rt722 SPK" }, }; +static const struct snd_soc_dapm_route rt766_spk_map[] = { + { "Speaker", NULL, "rt766 SPOL" }, + { "Speaker", NULL, "rt766 SPOR" }, +}; + /* Structure to map codec names to respective route arrays and sizes */ struct codec_route_map { const char *codec_name; @@ -45,6 +50,7 @@ static const struct codec_route_map codec_routes[] = { { "rt712", rt712_spk_map, ARRAY_SIZE(rt712_spk_map) }, { "rt721", rt721_spk_map, ARRAY_SIZE(rt721_spk_map) }, { "rt722", rt722_spk_map, ARRAY_SIZE(rt722_spk_map) }, + { "rt766", rt766_spk_map, ARRAY_SIZE(rt766_spk_map) }, }; static const struct codec_route_map *get_codec_route_map(const char *codec_name) diff --git a/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c b/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c index 2547b5b3fdd7..b4e427d55130 100644 --- a/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c +++ b/sound/soc/sdw_utils/soc_sdw_rt_sdca_jack_common.c @@ -70,6 +70,11 @@ static const struct snd_soc_dapm_route rt722_sdca_map[] = { { "rt722 MIC2", NULL, "Headset Mic" }, }; +static const struct snd_soc_dapm_route rt766_sdca_map[] = { + { "Headphone", NULL, "rt766 HP" }, + { "rt766 MIC2", NULL, "Headset Mic" }, +}; + static struct snd_soc_jack_pin rt_sdca_jack_pins[] = { { .pin = "Headphone", @@ -133,6 +138,9 @@ int asoc_sdw_rt_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_s } else if (strstr(component->name_prefix, "rt722")) { ret = snd_soc_dapm_add_routes(dapm, rt722_sdca_map, ARRAY_SIZE(rt722_sdca_map)); + } else if (strstr(component->name_prefix, "rt766")) { + ret = snd_soc_dapm_add_routes(dapm, rt766_sdca_map, + ARRAY_SIZE(rt766_sdca_map)); } else { dev_err(card->dev, "%s is not supported\n", component->name_prefix); return -EINVAL; diff --git a/sound/soc/sdw_utils/soc_sdw_senary_amp.c b/sound/soc/sdw_utils/soc_sdw_senary_amp.c new file mode 100644 index 000000000000..9370402ab4c7 --- /dev/null +++ b/sound/soc/sdw_utils/soc_sdw_senary_amp.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +// This file incorporates work covered by the following copyright notice: +// Copyright (c) 2022 Intel Corporation +// Copyright (c) 2024 Advanced Micro Devices, Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int senary_amp_add_device_props(struct device *sdw_dev) +{ + return 0; +} + +int asoc_sdw_senary_amp_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) +{ + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + + if (ctx->amp_dev1) { + device_remove_software_node(ctx->amp_dev1); + put_device(ctx->amp_dev1); + } + + if (ctx->amp_dev2) { + device_remove_software_node(ctx->amp_dev2); + put_device(ctx->amp_dev2); + } + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_amp_exit, "SND_SOC_SDW_UTILS"); + +int asoc_sdw_senary_amp_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback) +{ + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct device *sdw_dev1, *sdw_dev2; + int ret; + + /* Count amp number and do init on playback link only. */ + if (!playback) + return 0; + + info->amp_num++; + + if (info->amp_num == 2) { + sdw_dev1 = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name); + if (!sdw_dev1) + return -EPROBE_DEFER; + + ret = senary_amp_add_device_props(sdw_dev1); + if (ret < 0) { + put_device(sdw_dev1); + return ret; + } + ctx->amp_dev1 = sdw_dev1; + + sdw_dev2 = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[1].name); + if (!sdw_dev2) + return -EPROBE_DEFER; + + ret = senary_amp_add_device_props(sdw_dev2); + if (ret < 0) { + put_device(sdw_dev2); + return ret; + } + ctx->amp_dev2 = sdw_dev2; + } + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_amp_init, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/sdw_utils/soc_sdw_senary_dmic.c b/sound/soc/sdw_utils/soc_sdw_senary_dmic.c new file mode 100644 index 000000000000..7481706561f9 --- /dev/null +++ b/sound/soc/sdw_utils/soc_sdw_senary_dmic.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only +// This file incorporates work covered by the following copyright notice: +// Copyright (c) 2024 Intel Corporation +// Copyright (c) 2024 Advanced Micro Devices, Inc. + +#include +#include +#include +#include +#include +#include + +/* Card pin "Dmic" → codec input (name_prefix + " DMIC") */ +static const struct snd_soc_dapm_route senary_dmic_map[] = { + { "sn624x DMIC", NULL, "Dmic" }, +}; + +int asoc_sdw_senary_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + struct snd_soc_component *component; + char *mic_name; + int ret; + + component = dai->component; + + ret = snd_soc_dapm_add_routes(dapm, senary_dmic_map, + ARRAY_SIZE(senary_dmic_map)); + if (ret) { + dev_err(card->dev, "senary dmic map addition failed: %d\n", ret); + return ret; + } + + mic_name = devm_kasprintf(card->dev, GFP_KERNEL, "%s", component->name_prefix); + if (!mic_name) + return -ENOMEM; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s mic:%s-dmic", card->components, + mic_name); + if (!card->components) + return -ENOMEM; + + dev_dbg(card->dev, "card->components: %s\n", card->components); + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_dmic_rtd_init, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/sdw_utils/soc_sdw_senary_sdca.c b/sound/soc/sdw_utils/soc_sdw_senary_sdca.c new file mode 100644 index 000000000000..5c2cd75307c9 --- /dev/null +++ b/sound/soc/sdw_utils/soc_sdw_senary_sdca.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-only +// This file incorporates work covered by the following copyright notice: +// Copyright (c) 2024 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* dapm routes for SPK will be registered dynamically */ +static const struct snd_soc_dapm_route sn624x_spk_map[] = { + { "Speaker", NULL, "sn624x SPK" }, +}; + +/* Structure to map codec names to respective route arrays and sizes */ +struct codec_route_map { + const char *codec_name; + const struct snd_soc_dapm_route *route_map; + size_t route_size; +}; + +/* Codec route maps array */ +static const struct codec_route_map codec_routes[] = { + { "sn624x", sn624x_spk_map, ARRAY_SIZE(sn624x_spk_map) }, +}; + +static const struct codec_route_map *get_codec_route_map(const char *dai_name) +{ + for (size_t i = 0; i < ARRAY_SIZE(codec_routes); i++) { + if (str_has_prefix(dai_name, codec_routes[i].codec_name)) + return &codec_routes[i]; + } + return NULL; +} + +int asoc_sdw_senary_sdca_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + int ret; + + /* Match by DAI name prefix (e.g. sn624x-sdca-aif2). */ + const struct codec_route_map *route_map = get_codec_route_map(dai->name); + + if (!route_map) { + dev_err(rtd->dev, "failed to get codec name and route map\n"); + return -EINVAL; + } + + /* Add routes */ + ret = snd_soc_dapm_add_routes(dapm, route_map->route_map, route_map->route_size); + if (ret) + dev_err(rtd->dev, "failed to add rt sdca spk map: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_spk_rtd_init, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c b/sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c new file mode 100644 index 000000000000..1dab6f99877d --- /dev/null +++ b/sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +// This file incorporates work covered by the following copyright notice: +// Copyright (c) 2020 Intel Corporation +// Copyright (c) 2024 Advanced Micro Devices, Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * jack_init / jack_exit / most of rtd_init follow soc_sdw_rt_sdca_jack_common.c + * (same SDCA headset machine-helper pattern). Vendor-specific pieces here are + * mainly the "senarytech,jd-src" property name and senary_sdca_jack_map[]. + * + * Keep a Senary-local file for now to mirror the existing RT layout. Sharing + * parameterised SDCA jack helpers in snd-soc-sdw-utils (jd-src key + common + * init/exit/rtd_init boilerplate) is intentional follow-up work, out of scope + * for the sn624x codec series. + */ + +/* + * Note this MUST be called before snd_soc_register_card(), so that the props + * are in place before the codec component driver's probe function parses them. + */ +static int senary_sdca_jack_add_codec_device_props(struct device *sdw_dev, unsigned long quirk) +{ + struct property_entry props[SOC_SDW_MAX_NO_PROPS] = {}; + struct fwnode_handle *fwnode; + int ret; + + if (!SOC_SDW_JACK_JDSRC(quirk)) + return 0; + + props[0] = PROPERTY_ENTRY_U32("senarytech,jd-src", SOC_SDW_JACK_JDSRC(quirk)); + + fwnode = fwnode_create_software_node(props, NULL); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); + + ret = device_add_software_node(sdw_dev, to_software_node(fwnode)); + + fwnode_handle_put(fwnode); + + return ret; +} + +static const struct snd_soc_dapm_route senary_sdca_jack_map[] = { + { "Headphone", NULL, "sn624x HP" }, + { "sn624x MIC2", NULL, "Headset Mic" }, +}; + +static struct snd_soc_jack_pin senary_sdca_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static const char * const need_sdca_suffix[] = { + "sn624x" +}; + +int asoc_sdw_senary_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component; + struct snd_soc_jack *jack; + int ret; + int i; + + component = dai->component; + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s hs:%s", + card->components, component->name_prefix); + if (!card->components) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(need_sdca_suffix); i++) { + if (!strstr(component->name_prefix, need_sdca_suffix[i])) + continue; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s-sdca", card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_dapm_add_routes(dapm, senary_sdca_jack_map, + ARRAY_SIZE(senary_sdca_jack_map)); + if (ret) { + dev_err(card->dev, + "senary sdca jack map addition failed: %d\n", ret); + return ret; + } + break; + } + + if (i == ARRAY_SIZE(need_sdca_suffix)) { + dev_err(card->dev, "%s is not supported\n", component->name_prefix); + return -EINVAL; + } + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + senary_sdca_jack_pins, + ARRAY_SIZE(senary_sdca_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_jack_rtd_init, "SND_SOC_SDW_UTILS"); + +int asoc_sdw_senary_sdca_jack_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) +{ + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + + if (!ctx->headset_codec_dev) + return 0; + + if (!SOC_SDW_JACK_JDSRC(ctx->mc_quirk)) + return 0; + + device_remove_software_node(ctx->headset_codec_dev); + put_device(ctx->headset_codec_dev); + ctx->headset_codec_dev = NULL; + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_jack_exit, "SND_SOC_SDW_UTILS"); + +/* + * Same control flow as asoc_sdw_rt_sdca_jack_init(); see file comment above + * about deferred helper sharing in snd-soc-sdw-utils. + */ +int asoc_sdw_senary_sdca_jack_init(struct snd_soc_card *card, + struct snd_soc_dai_link *dai_links, + struct asoc_sdw_codec_info *info, + bool playback) +{ + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct device *sdw_dev; + int ret; + + /* + * Jack detection should be only initialized once for headsets since + * the playback/capture is sharing the same jack + */ + if (ctx->headset_codec_dev) + return 0; + + sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name); + if (!sdw_dev) + return -EPROBE_DEFER; + + ret = senary_sdca_jack_add_codec_device_props(sdw_dev, ctx->mc_quirk); + if (ret < 0) { + put_device(sdw_dev); + return ret; + } + ctx->headset_codec_dev = sdw_dev; + + return 0; +} +EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_jack_init, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c index eda7c567f71e..7a19de75f32b 100644 --- a/sound/soc/sdw_utils/soc_sdw_utils.c +++ b/sound/soc/sdw_utils/soc_sdw_utils.c @@ -17,6 +17,15 @@ static const struct snd_soc_dapm_widget generic_dmic_widgets[] = { SND_SOC_DAPM_MIC("DMIC", NULL), }; +/* Senary SN624x UCM uses CaptureMixerElem "Dmic"; keep separate from generic DMIC. */ +static const struct snd_soc_dapm_widget sn624x_dmic_widgets[] = { + SND_SOC_DAPM_MIC("Dmic", NULL), +}; + +static const struct snd_kcontrol_new sn624x_dmic_controls[] = { + SOC_DAPM_PIN_SWITCH("Dmic"), +}; + static const struct snd_soc_dapm_widget generic_jack_widgets[] = { SND_SOC_DAPM_HP("Headphone", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), @@ -676,6 +685,52 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .dai_num = 3, }, + { + .vendor_id = 0x025d, + .part_id = 0x718, + .name_prefix = "rt721", + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt721-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt721-sdca-aif2", + .component_name = "rt721", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + /* No feedback capability is provided by rt721-sdca codec driver*/ + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt721-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + }, + .dai_num = 3, + }, { .vendor_id = 0x025d, .part_id = 0x722, @@ -724,6 +779,178 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .dai_num = 3, }, + { + .vendor_id = 0x025d, + .part_id = 0x767, + .name_prefix = "rt766", + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x025d, + .part_id = 0x767, + .name_prefix = "rt766", + .version_id = 4, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x025d, + .part_id = 0x766, + .name_prefix = "rt766", + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x025d, + .part_id = 0x766, + .name_prefix = "rt766", + .version_id = 4, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt766-sdca-aif1", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_rt_sdca_jack_init, + .exit = asoc_sdw_rt_sdca_jack_exit, + .rtd_init = asoc_sdw_rt_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "rt766-sdca-aif2", + .component_name = "rt766", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_rt_amp_init, + .exit = asoc_sdw_rt_amp_exit, + .rtd_init = asoc_sdw_rt_mf_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + }, + { + .direction = {false, true}, + .dai_name = "rt766-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_rt_dmic_rtd_init, + }, + }, + .dai_num = 3, + }, { .vendor_id = 0x019f, .part_id = 0x8373, @@ -1264,6 +1491,163 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, .dai_num = 1, }, + /* + * Senary SN6242/6244/6247: same SDCA jack/amp/mic topology; only part_id + * differs. name_prefix stays "sn624x" for UCM/DAI naming. + */ + { + .vendor_id = 0x0496, + .part_id = 0x6244, + .name_prefix = "sn624x", + .ignore_internal_dmic = true, + .dais = { + { + .direction = {true, true}, + .dai_name = "sn624x-sdca-aif", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_senary_sdca_jack_init, + .exit = asoc_sdw_senary_sdca_jack_exit, + .rtd_init = asoc_sdw_senary_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "sn624x-sdca-aif2", + .component_name = "sn624x", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_senary_amp_init, + .exit = asoc_sdw_senary_amp_exit, + .rtd_init = asoc_sdw_senary_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + .quirk = SOC_SDW_CODEC_SPKR, + .quirk_exclude = true, + }, + { + .direction = {false, true}, + .dai_name = "sn624x-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_senary_dmic_rtd_init, + .widgets = sn624x_dmic_widgets, + .num_widgets = ARRAY_SIZE(sn624x_dmic_widgets), + .controls = sn624x_dmic_controls, + .num_controls = ARRAY_SIZE(sn624x_dmic_controls), + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x0496, + .part_id = 0x6247, + .name_prefix = "sn624x", + .ignore_internal_dmic = true, + .dais = { + { + .direction = {true, true}, + .dai_name = "sn624x-sdca-aif", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_senary_sdca_jack_init, + .exit = asoc_sdw_senary_sdca_jack_exit, + .rtd_init = asoc_sdw_senary_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "sn624x-sdca-aif2", + .component_name = "sn624x", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_senary_amp_init, + .exit = asoc_sdw_senary_amp_exit, + .rtd_init = asoc_sdw_senary_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + .quirk = SOC_SDW_CODEC_SPKR, + .quirk_exclude = true, + }, + { + .direction = {false, true}, + .dai_name = "sn624x-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_senary_dmic_rtd_init, + .widgets = sn624x_dmic_widgets, + .num_widgets = ARRAY_SIZE(sn624x_dmic_widgets), + .controls = sn624x_dmic_controls, + .num_controls = ARRAY_SIZE(sn624x_dmic_controls), + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + }, + .dai_num = 3, + }, + { + .vendor_id = 0x0496, + .part_id = 0x6242, + .name_prefix = "sn624x", + .ignore_internal_dmic = true, + .dais = { + { + .direction = {true, true}, + .dai_name = "sn624x-sdca-aif", + .dai_type = SOC_SDW_DAI_TYPE_JACK, + .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID}, + .init = asoc_sdw_senary_sdca_jack_init, + .exit = asoc_sdw_senary_sdca_jack_exit, + .rtd_init = asoc_sdw_senary_sdca_jack_rtd_init, + .controls = generic_jack_controls, + .num_controls = ARRAY_SIZE(generic_jack_controls), + .widgets = generic_jack_widgets, + .num_widgets = ARRAY_SIZE(generic_jack_widgets), + }, + { + .direction = {true, false}, + .dai_name = "sn624x-sdca-aif2", + .component_name = "sn624x", + .dai_type = SOC_SDW_DAI_TYPE_AMP, + .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID}, + .init = asoc_sdw_senary_amp_init, + .exit = asoc_sdw_senary_amp_exit, + .rtd_init = asoc_sdw_senary_sdca_spk_rtd_init, + .controls = generic_spk_controls, + .num_controls = ARRAY_SIZE(generic_spk_controls), + .widgets = generic_spk_widgets, + .num_widgets = ARRAY_SIZE(generic_spk_widgets), + .quirk = SOC_SDW_CODEC_SPKR, + .quirk_exclude = true, + }, + { + .direction = {false, true}, + .dai_name = "sn624x-sdca-aif3", + .dai_type = SOC_SDW_DAI_TYPE_MIC, + .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID}, + .rtd_init = asoc_sdw_senary_dmic_rtd_init, + .widgets = sn624x_dmic_widgets, + .num_widgets = ARRAY_SIZE(sn624x_dmic_widgets), + .controls = sn624x_dmic_controls, + .num_controls = ARRAY_SIZE(sn624x_dmic_controls), + .quirk = SOC_SDW_CODEC_MIC, + .quirk_exclude = true, + }, + }, + .dai_num = 3, + }, }; EXPORT_SYMBOL_NS(codec_info_list, "SND_SOC_SDW_UTILS"); diff --git a/sound/soc/soc-ac97.c b/sound/soc/soc-ac97.c index 5f81e3606655..9d38b38d88df 100644 --- a/sound/soc/soc-ac97.c +++ b/sound/soc/soc-ac97.c @@ -69,7 +69,7 @@ static int snd_soc_ac97_gpio_direction_in(struct gpio_chip *chip, { struct snd_soc_component *component = gpio_to_component(chip); - dev_dbg(component->dev, "set gpio %d to output\n", offset); + dev_dbg(component->dev, "set gpio %d to input\n", offset); return snd_soc_component_update_bits(component, AC97_GPIO_CFG, 1 << offset, 1 << offset); } @@ -241,10 +241,12 @@ struct snd_ac97 *snd_soc_new_ac97_component(struct snd_soc_component *component, ret = snd_soc_ac97_init_gpio(ac97, component); if (ret) - goto err_put_device; + goto err_del_device; return ac97; +err_del_device: + device_del(&ac97->dev); err_put_device: put_device(&ac97->dev); return ERR_PTR(ret); diff --git a/sound/soc/soc-card-test.c b/sound/soc/soc-card-test.c index 47dfd8b1babc..610b8220786d 100644 --- a/sound/soc/soc-card-test.c +++ b/sound/soc/soc-card-test.c @@ -70,6 +70,7 @@ static void test_snd_soc_card_get_kcontrol(struct kunit *test) static int soc_card_test_case_init(struct kunit *test) { struct soc_card_test_priv *priv; + struct device *dev; int ret; priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); @@ -82,8 +83,10 @@ static int soc_card_test_case_init(struct kunit *test) if (!priv->card) return -ENOMEM; - priv->card_dev = kunit_device_register(test, "sound-soc-card-test"); - priv->card_dev = get_device(priv->card_dev); + dev = kunit_device_register(test, "sound-soc-card-test"); + if (IS_ERR(dev)) + return PTR_ERR(dev); + priv->card_dev = get_device(dev); if (!priv->card_dev) return -ENODEV; diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c index dc7d203cb76a..a6e280c798f3 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -8,10 +8,12 @@ // Mark Brown // Kuninori Morimoto // +#include #include #include #include #include +#include "soc-internal.h" #define soc_component_ret(dai, ret) _soc_component_ret(dai, __func__, ret) static inline int _soc_component_ret(struct snd_soc_component *component, const char *func, int ret) @@ -29,24 +31,11 @@ static inline int _soc_component_ret_reg_rw(struct snd_soc_component *component, func, component->name, reg); } -struct snd_soc_component *snd_soc_component_alloc(struct device *dev) +static LIST_HEAD(component_list); +struct list_head *snd_soc_component_get_list_head(void) { - struct snd_soc_component *component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); - - if (!component) - return NULL; - - component->dev = dev; - - return component; + return &component_list; } -EXPORT_SYMBOL_GPL(snd_soc_component_alloc); - -void snd_soc_component_set_name(struct snd_soc_component *component, const char *name) -{ - component->name = name; -} -EXPORT_SYMBOL_GPL(snd_soc_component_set_name); const char *snd_soc_component_name(struct snd_soc_component *component) { @@ -66,6 +55,18 @@ void *snd_soc_component_to_priv(struct snd_soc_component *component) } EXPORT_SYMBOL_GPL(snd_soc_component_to_priv); +struct snd_soc_dapm_context *snd_soc_component_to_dapm(struct snd_soc_component *component) +{ + return component->dapm; +} +EXPORT_SYMBOL_GPL(snd_soc_component_to_dapm); + +unsigned int snd_soc_component_active(struct snd_soc_component *component) +{ + return component->active; +} +EXPORT_SYMBOL_GPL(snd_soc_component_active); + /* * We might want to check substream by using list. * In such case, we can update these macros. @@ -315,8 +316,12 @@ void snd_soc_component_suspend(struct snd_soc_component *component) void snd_soc_component_resume(struct snd_soc_component *component) { - if (component->driver->resume) - component->driver->resume(component); + int ret; + + if (component->driver->resume) { + ret = component->driver->resume(component); + soc_component_ret(component, ret); + } component->suspended = 0; } @@ -325,15 +330,25 @@ int snd_soc_component_is_suspended(struct snd_soc_component *component) return component->suspended; } -int snd_soc_component_probe(struct snd_soc_component *component) +/** + * snd_soc_component_add_controls - Add an array of controls to a component. + * + * @component: Component 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_component_add_controls(struct snd_soc_component *component, + const struct snd_kcontrol_new *controls, + unsigned int num_controls) { - int ret = 0; + struct snd_card *card = component->card->snd_card; - if (component->driver->probe) - ret = component->driver->probe(component); - - return soc_component_ret(component, ret); + return snd_soc_add_controls(card, component->dev, controls, + num_controls, component->name_prefix, component); } +EXPORT_SYMBOL_GPL(snd_soc_component_add_controls); int snd_soc_component_fixup_controls(struct snd_soc_component *component) { @@ -345,12 +360,6 @@ int snd_soc_component_fixup_controls(struct snd_soc_component *component) return soc_component_ret(component, ret); } -void snd_soc_component_remove(struct snd_soc_component *component) -{ - if (component->driver->remove) - component->driver->remove(component); -} - int snd_soc_component_of_xlate_dai_id(struct snd_soc_component *component, struct device_node *ep) { @@ -394,6 +403,35 @@ int snd_soc_component_regmap_val_bytes(struct snd_soc_component *component) } EXPORT_SYMBOL_GPL(snd_soc_component_regmap_val_bytes); +/** + * snd_soc_component_regmap_cache_sync() - Sync the register cache with the hardware + * @component: COMPONENT to sync + * + * Note: This function will call regcache_sync() + */ +int snd_soc_component_regmap_cache_sync(struct snd_soc_component *component) +{ + if (!component->regmap) + return 0; + + return regcache_sync(component->regmap); +} +EXPORT_SYMBOL_GPL(snd_soc_component_regmap_cache_sync); + +/** + * snd_soc_component_regmap_async_complete() - Ensure asynchronous I/O has completed + * @component: Component for which to wait + * + * This function blocks until all asynchronous I/O which has previously been + * scheduled using snd_soc_component_update_bits_async() has completed. + */ +void snd_soc_component_regmap_async_complete(struct snd_soc_component *component) +{ + if (component->regmap) + regmap_async_complete(component->regmap); +} +EXPORT_SYMBOL_GPL(snd_soc_component_regmap_async_complete); + #ifdef CONFIG_REGMAP /** @@ -898,20 +936,6 @@ int snd_soc_component_write_field(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(snd_soc_component_write_field); -/** - * snd_soc_component_async_complete() - Ensure asynchronous I/O has completed - * @component: Component for which to wait - * - * This function blocks until all asynchronous I/O which has previously been - * scheduled using snd_soc_component_update_bits_async() has completed. - */ -void snd_soc_component_async_complete(struct snd_soc_component *component) -{ - if (component->regmap) - regmap_async_complete(component->regmap); -} -EXPORT_SYMBOL_GPL(snd_soc_component_async_complete); - /** * snd_soc_component_test_bits - Test register for change * @component: component @@ -935,7 +959,7 @@ int snd_soc_component_test_bits(struct snd_soc_component *component, } EXPORT_SYMBOL_GPL(snd_soc_component_test_bits); -int snd_soc_pcm_component_pointer(struct snd_pcm_substream *substream) +snd_pcm_uframes_t snd_soc_pcm_component_pointer(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_component *component; @@ -1270,3 +1294,262 @@ int snd_soc_pcm_component_ack(struct snd_pcm_substream *substream) return 0; } + +struct device_node *snd_soc_component_to_node(struct snd_soc_component *component) +{ + struct device_node *of_node; + + of_node = component->dev->of_node; + if (!of_node && component->dev->parent) + of_node = component->dev->parent->of_node; + + return of_node; +} + +bool snd_soc_component_matches_dlc(struct snd_soc_component *component, + const struct snd_soc_dai_link_component *dlc) +{ + struct device_node *component_of_node; + + if (!dlc) + return false; + + if (dlc->dai_args) { + struct snd_soc_dai *dai; + + for_each_component_dais(component, dai) + if (snd_soc_dai_matches_dlc(dai, dlc)) + return true; + return false; + } + + component_of_node = snd_soc_component_to_node(component); + + if (dlc->of_node && component_of_node != dlc->of_node) + return false; + if (dlc->name && strcmp(component->name, dlc->name)) + return false; + + return true; +} + +#ifdef CONFIG_DEBUG_FS +static void snd_soc_component_debugfs_init(struct snd_soc_component *component) +{ + if (!component->card->debugfs_card_root) + return; + + if (component->driver->debugfs_prefix) { + char *name; + + name = kasprintf(GFP_KERNEL, "%s:%s", + component->driver->debugfs_prefix, component->name); + if (name) { + component->debugfs_root = debugfs_create_dir(name, + component->card->debugfs_card_root); + kfree(name); + } + } else { + component->debugfs_root = debugfs_create_dir(component->name, + component->card->debugfs_card_root); + } + + snd_soc_dapm_debugfs_init(snd_soc_component_to_dapm(component), + component->debugfs_root); +} + +static void snd_soc_component_debugfs_cleanup(struct snd_soc_component *component) +{ + if (!component->debugfs_root) + return; + debugfs_remove_recursive(component->debugfs_root); + component->debugfs_root = NULL; +} +#else +static inline void snd_soc_component_debugfs_init(struct snd_soc_component *component) { } +static inline void snd_soc_component_debugfs_cleanup(struct snd_soc_component *component) { } +#endif /* CONFIG_DEBUG_FS */ + +static void snd_soc_component_set_name_prefix(struct snd_soc_component *component) +{ + struct snd_soc_card *card = component->card; + struct device_node *of_node = snd_soc_component_to_node(component); + const char *str; + int ret, i; + + for (i = 0; i < card->num_configs; i++) { + struct snd_soc_codec_conf *map = &card->codec_conf[i]; + + if (snd_soc_component_matches_dlc(component, &map->dlc) && + map->name_prefix) { + component->name_prefix = map->name_prefix; + return; + } + } + + /* + * If there is no configuration table or no match in the table, + * check if a prefix is provided in the node + */ + ret = of_property_read_string(of_node, "sound-name-prefix", &str); + if (ret < 0) + return; + + component->name_prefix = str; +} + +void snd_soc_component_remove(struct snd_soc_component *component, int probed) +{ + if (!component->card) + return; + + if (probed && component->driver->remove) + component->driver->remove(component); + + list_del_init(&component->card_list); + snd_soc_dapm_free(snd_soc_component_to_dapm(component)); + snd_soc_component_debugfs_cleanup(component); + component->card = NULL; + snd_soc_component_module_put_when_remove(component); +} + +int snd_soc_component_probe(struct snd_soc_component *component, struct snd_soc_card *card) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + struct snd_soc_dai *dai; + int probed = 0; + int ret; + + if (snd_soc_component_is_dummy(component)) + return 0; + + if (component->card) { + if (component->card != card) { + dev_err(component->dev, + "Trying to bind component \"%s\" to card \"%s\" but is already bound to card \"%s\"\n", + component->name, card->name, component->card->name); + return -ENODEV; + } + return 0; + } + + ret = snd_soc_component_module_get_when_probe(component); + if (ret < 0) + return ret; + + component->card = card; + snd_soc_component_set_name_prefix(component); + + snd_soc_component_debugfs_init(component); + + snd_soc_dapm_init(dapm, card, component); + + ret = snd_soc_dapm_new_controls(dapm, + component->driver->dapm_widgets, + component->driver->num_dapm_widgets); + + if (ret != 0) { + dev_err(component->dev, + "Failed to create new controls %d\n", ret); + goto err_probe; + } + + for_each_component_dais(component, dai) { + ret = snd_soc_dapm_new_dai_widgets(dapm, dai); + if (ret != 0) { + dev_err(component->dev, + "Failed to create DAI widgets %d\n", ret); + goto err_probe; + } + } + + if (component->driver->probe) { + ret = component->driver->probe(component); + if (ret < 0) + goto err_probe; + } + + WARN(!snd_soc_dapm_get_idle_bias(dapm) && + snd_soc_dapm_get_bias_level(dapm) != SND_SOC_BIAS_OFF, + "codec %s can not start from non-off bias with idle_bias_off==1\n", + component->name); + probed = 1; + + /* + * machine specific init + * see + * snd_soc_component_set_aux() + */ + ret = snd_soc_component_init(component); + if (ret < 0) + goto err_probe; + + ret = snd_soc_component_add_controls(component, + component->driver->controls, + component->driver->num_controls); + if (ret < 0) + goto err_probe; + + ret = snd_soc_dapm_add_routes(dapm, + component->driver->dapm_routes, + component->driver->num_dapm_routes); + if (ret < 0) + goto err_probe; + + /* see for_each_card_components */ + list_add(&component->card_list, &card->component_dev_list); + +err_probe: + if (ret < 0) + snd_soc_component_remove(component, probed); + + return ret; +} + +struct snd_soc_component +*snd_soc_component_lookup_nolock(struct device *dev, const char *driver_name) +{ + struct snd_soc_component *component; + + for_each_component(component) { + if (dev != component->dev) + continue; + + if (!driver_name) + return component; + + if (!component->driver->name) + continue; + + if (component->driver->name == driver_name) + return component; + + if (strcmp(component->driver->name, driver_name) == 0) + return component; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_component_lookup_nolock); + +struct snd_soc_component *snd_soc_component_lookup(struct device *dev, + const char *driver_name) +{ + guard(mutex)(&client_mutex); + + return snd_soc_component_lookup_nolock(dev, driver_name); +} +EXPORT_SYMBOL_GPL(snd_soc_component_lookup); + +struct snd_soc_component *snd_soc_component_lookup_by_name(const char *component_name) +{ + struct snd_soc_component *component; + + guard(mutex)(&client_mutex); + for_each_component(component) + if (strstr(component->name, component_name)) + return component; + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_component_lookup_by_name); diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index b8402802ae78..a941ed0908c6 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -16,10 +16,12 @@ #include #include #include +#include #include #include #include #include +#include "soc-internal.h" static int snd_soc_compr_components_open(struct snd_compr_stream *cstream) { @@ -69,10 +71,10 @@ static int soc_compr_clean(struct snd_compr_stream *cstream, int rollback) snd_soc_dai_digital_mute(codec_dai, 1, stream); if (!snd_soc_dai_active(cpu_dai)) - soc_pcm_set_dai_params(cpu_dai, NULL); + snd_soc_dai_symmetric_set_params(cpu_dai, NULL); if (!snd_soc_dai_active(codec_dai)) - soc_pcm_set_dai_params(codec_dai, NULL); + snd_soc_dai_symmetric_set_params(codec_dai, NULL); snd_soc_link_compr_shutdown(cstream, rollback); @@ -130,6 +132,104 @@ static int soc_compr_open(struct snd_compr_stream *cstream) return ret; } +/* + * The internal PCM of a compressed FE is never opened via the PCM API, so its + * substream has no runtime attached to it. + * + * DPCM lends the runtime of the FE to every BE it opens and re-points it at + * another FE when the lending one goes away, so the compressed FE must provide + * one as well. Without it the BEs are left with a NULL runtime, which oopses + * in BE DAI and CODEC drivers looking at substream->runtime, and a BE can not + * be shared between a compressed and a PCM FE at all. + * + * The runtime is owned by the FE for as long as the compressed stream is open. + */ +static int soc_compr_alloc_fe_runtime(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream); + struct snd_pcm_hw_constraints *constrs; + int i; + + if (!fe_substream || fe_substream->runtime) + return 0; + + fe_substream->runtime = kzalloc_obj(*fe_substream->runtime); + if (!fe_substream->runtime) + return -ENOMEM; + + /* + * snd_pcm_open() initializes the constraints of a PCM runtime to + * 'anything goes'. A zeroed one means an empty mask and a [0, 0] + * interval instead, which the constraint helpers refine against and + * reject, so initialize them the same way the PCM core does. + * + * The hw rules snd_pcm_hw_constraints_init() installs on top are only + * evaluated by snd_pcm_hw_refine(), which never runs for this + * substream, so they are not needed here. + */ + constrs = &fe_substream->runtime->hw_constraints; + for (i = SNDRV_PCM_HW_PARAM_FIRST_MASK; i <= SNDRV_PCM_HW_PARAM_LAST_MASK; i++) + snd_mask_any(constrs_mask(constrs, i)); + + for (i = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; i <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; i++) + snd_interval_any(constrs_interval(constrs, i)); + + return 0; +} + +static void soc_compr_free_fe_runtime(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream); + + if (!fe_substream || !fe_substream->runtime) + return; + + /* BE startup callbacks may have added hw constraint rules */ + kfree(fe_substream->runtime->hw_constraints.rules); + kfree(fe_substream->runtime); + fe_substream->runtime = NULL; +} + +/* + * The BE parameters of a compressed FE are set up by the machine level + * be_hw_params_fixup(), which is mandatory for a compressed BE, see + * soc_compr_set_params_fe(). Once they are fixed up, use them to fill in the + * runtime the BEs have been lent, so that a BE DAI or CODEC driver sees the + * format it is being configured for. + */ +static void soc_compr_set_fe_runtime(struct snd_soc_pcm_runtime *fe, int stream) +{ + struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream); + struct snd_pcm_runtime *runtime; + struct snd_soc_dpcm *dpcm; + + snd_soc_dpcm_mutex_assert_held(fe); + + if (!fe_substream || !fe_substream->runtime) + return; + + runtime = fe_substream->runtime; + + for_each_dpcm_be(fe, stream, dpcm) { + struct snd_pcm_hw_params *params = &dpcm->be->dpcm[stream].hw_params; + int bits = snd_pcm_format_physical_width(params_format(params)); + + /* skip a BE which has not been fixed up */ + if (bits <= 0) + continue; + + runtime->access = params_access(params); + runtime->format = params_format(params); + runtime->subformat = params_subformat(params); + runtime->channels = params_channels(params); + runtime->rate = params_rate(params); + + runtime->sample_bits = bits; + runtime->frame_bits = bits * runtime->channels; + break; + } +} + static int soc_compr_open_fe(struct snd_compr_stream *cstream) { struct snd_soc_pcm_runtime *fe = cstream->private_data; @@ -141,6 +241,10 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) snd_soc_card_mutex_lock(fe->card); + ret = soc_compr_alloc_fe_runtime(fe, stream); + if (ret < 0) + goto be_err; + ret = dpcm_path_get(fe, stream, &list); if (ret < 0) goto be_err; @@ -195,6 +299,7 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) dpcm_path_put(&list); snd_soc_dpcm_mutex_unlock(fe); be_err: + soc_compr_free_fe_runtime(fe, stream); fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; snd_soc_card_mutex_unlock(fe->card); return ret; @@ -207,6 +312,16 @@ static int soc_compr_free_fe(struct snd_compr_stream *cstream) struct snd_soc_dpcm *dpcm; int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */ + /* + * The core will not send a STOP trigger on free if the device is in + * DRAIN state, but we need to stop BE and FE before we can proceed to + * free the stream. + * Run the STOP trigger if the DPCM state is START (DRAIN is not + * changing the DPCM state). + */ + if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_START) + cstream->ops->trigger(cstream, SNDRV_PCM_TRIGGER_STOP); + snd_soc_card_mutex_lock(fe->card); snd_soc_dpcm_mutex_lock(fe); @@ -237,6 +352,9 @@ static int soc_compr_free_fe(struct snd_compr_stream *cstream) snd_soc_dai_compr_shutdown(cpu_dai, cstream, 0); + /* all BEs are shut down and disconnected, the runtime is unused now */ + soc_compr_free_fe_runtime(fe, stream); + snd_soc_card_mutex_unlock(fe->card); return 0; } @@ -273,30 +391,89 @@ static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd) return ret; } -static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd) +static int soc_compr_trigger_fe_be(struct snd_compr_stream *cstream, int cmd, + bool fe_first) { struct snd_soc_pcm_runtime *fe = cstream->private_data; struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(fe, 0); - int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */ int ret; - if (cmd == SND_COMPR_TRIGGER_PARTIAL_DRAIN || - cmd == SND_COMPR_TRIGGER_DRAIN) + if (fe_first) { + dev_dbg(fe->dev, "ASoC: pre trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); + if (ret < 0) + goto out; + + ret = snd_soc_component_compr_trigger(cstream, cmd); + if (ret < 0) + goto out; + + ret = dpcm_be_dai_trigger(fe, cstream->direction, cmd); + } else { + dev_dbg(fe->dev, "ASoC: post trigger FE %s cmd %d\n", + fe->dai_link->name, cmd); + + ret = dpcm_be_dai_trigger(fe, cstream->direction, cmd); + if (ret < 0) + goto out; + + ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); + if (ret < 0) + goto out; + + ret = snd_soc_component_compr_trigger(cstream, cmd); + } + +out: + return snd_soc_ret(fe->dev, ret, "trigger FE cmd: %d failed\n", cmd); +} + +static int soc_compr_trigger_fe(struct snd_compr_stream *cstream, int cmd) +{ + struct snd_soc_pcm_runtime *fe = cstream->private_data; + int stream = cstream->direction; /* SND_COMPRESS_xxx is same as SNDRV_PCM_STREAM_xxx */ + bool fe_first; + int ret; + + /* FE only triggers */ + switch (cmd) { + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + case SND_COMPR_TRIGGER_NEXT_TRACK: + case SND_COMPR_TRIGGER_DRAIN: return snd_soc_component_compr_trigger(cstream, cmd); + default: + break; + } + + if (fe->dai_link->trigger[stream] == SND_SOC_DPCM_TRIGGER_POST) + fe_first = false; + else + fe_first = true; snd_soc_card_mutex_lock(fe->card); - ret = snd_soc_dai_compr_trigger(cpu_dai, cstream, cmd); - if (ret < 0) - goto out; - - ret = snd_soc_component_compr_trigger(cstream, cmd); - if (ret < 0) - goto out; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; - ret = dpcm_be_dai_trigger(fe, stream, cmd); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = soc_compr_trigger_fe_be(cstream, cmd, fe_first); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = soc_compr_trigger_fe_be(cstream, cmd, !fe_first); + break; + default: + ret = -EINVAL; + break; + } + + if (ret < 0) + goto out; switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -387,6 +564,8 @@ static int soc_compr_set_params_fe(struct snd_compr_stream *cstream, snd_soc_dpcm_mutex_lock(fe); ret = dpcm_be_dai_hw_params(fe, stream); + if (!ret) + soc_compr_set_fe_runtime(fe, stream); snd_soc_dpcm_mutex_unlock(fe); if (ret < 0) goto out; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index b9c70268d49f..97269b4ee4ca 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -41,17 +41,14 @@ #include #include #include +#include "soc-internal.h" #define CREATE_TRACE_POINTS #include -static DEFINE_MUTEX(client_mutex); -static LIST_HEAD(component_list); +DEFINE_MUTEX(client_mutex); static LIST_HEAD(unbind_card_list); -#define for_each_component(component) \ - list_for_each_entry(component, &component_list, list) - /* * This is used if driver don't need to have CPU/Codec/Platform * dai_link. see soc.h @@ -131,38 +128,6 @@ static const struct attribute_group *soc_dev_attr_groups[] = { struct dentry *snd_soc_debugfs_root; EXPORT_SYMBOL_GPL(snd_soc_debugfs_root); -static void soc_init_component_debugfs(struct snd_soc_component *component) -{ - if (!component->card->debugfs_card_root) - return; - - if (component->driver->debugfs_prefix) { - char *name; - - name = kasprintf(GFP_KERNEL, "%s:%s", - component->driver->debugfs_prefix, component->name); - if (name) { - component->debugfs_root = debugfs_create_dir(name, - component->card->debugfs_card_root); - kfree(name); - } - } else { - component->debugfs_root = debugfs_create_dir(component->name, - component->card->debugfs_card_root); - } - - snd_soc_dapm_debugfs_init(snd_soc_component_to_dapm(component), - component->debugfs_root); -} - -static void soc_cleanup_component_debugfs(struct snd_soc_component *component) -{ - if (!component->debugfs_root) - return; - debugfs_remove_recursive(component->debugfs_root); - component->debugfs_root = NULL; -} - static int dai_list_show(struct seq_file *m, void *v) { struct snd_soc_component *component; @@ -223,8 +188,6 @@ static void snd_soc_debugfs_exit(void) #else -static inline void soc_init_component_debugfs(struct snd_soc_component *component) { } -static inline void soc_cleanup_component_debugfs(struct snd_soc_component *component) { } 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) { } @@ -232,22 +195,6 @@ static inline void snd_soc_debugfs_exit(void) { } #endif -static int snd_soc_is_match_dai_args(const struct of_phandle_args *args1, - const struct of_phandle_args *args2) -{ - if (!args1 || !args2) - return 0; - - if (args1->np != args2->np) - return 0; - - for (int i = 0; i < args1->args_count; i++) - if (args1->args[i] != args2->args[i]) - return 0; - - return 1; -} - static inline int snd_soc_dlc_component_is_empty(struct snd_soc_dai_link_component *dlc) { return !(dlc->dai_args || dlc->name || dlc->of_node); @@ -263,50 +210,6 @@ static inline int snd_soc_dlc_dai_is_empty(struct snd_soc_dai_link_component *dl return !(dlc->dai_args || dlc->dai_name); } -static int snd_soc_is_matching_dai(const struct snd_soc_dai_link_component *dlc, - struct snd_soc_dai *dai) -{ - if (!dlc) - return 0; - - if (dlc->dai_args) - return snd_soc_is_match_dai_args(dai->driver->dai_args, dlc->dai_args); - - if (!dlc->dai_name) - return 1; - - /* see snd_soc_dai_name_get() */ - - if (dai->driver->name && - strcmp(dlc->dai_name, dai->driver->name) == 0) - return 1; - - if (strcmp(dlc->dai_name, dai->name) == 0) - return 1; - - if (dai->component->name && - strcmp(dlc->dai_name, dai->component->name) == 0) - return 1; - - return 0; -} - -const char *snd_soc_dai_name_get(const struct snd_soc_dai *dai) -{ - /* see snd_soc_is_matching_dai() */ - if (dai->driver->name) - return dai->driver->name; - - if (dai->name) - return dai->name; - - if (dai->component->name) - return dai->component->name; - - return NULL; -} -EXPORT_SYMBOL_GPL(snd_soc_dai_name_get); - static int snd_soc_rtd_add_component(struct snd_soc_pcm_runtime *rtd, struct snd_soc_component *component) { @@ -358,54 +261,6 @@ struct snd_soc_component *snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_rtdcom_lookup); -struct snd_soc_component -*snd_soc_lookup_component_nolocked(struct device *dev, const char *driver_name) -{ - struct snd_soc_component *component; - - for_each_component(component) { - if (dev != component->dev) - continue; - - if (!driver_name) - return component; - - if (!component->driver->name) - continue; - - if (component->driver->name == driver_name) - return component; - - if (strcmp(component->driver->name, driver_name) == 0) - return component; - } - - return NULL; -} -EXPORT_SYMBOL_GPL(snd_soc_lookup_component_nolocked); - -struct snd_soc_component *snd_soc_lookup_component(struct device *dev, - const char *driver_name) -{ - guard(mutex)(&client_mutex); - - return snd_soc_lookup_component_nolocked(dev, driver_name); -} -EXPORT_SYMBOL_GPL(snd_soc_lookup_component); - -struct snd_soc_component *snd_soc_lookup_component_by_name(const char *component_name) -{ - struct snd_soc_component *component; - - guard(mutex)(&client_mutex); - for_each_component(component) - if (strstr(component->name, component_name)) - return component; - - return NULL; -} -EXPORT_SYMBOL_GPL(snd_soc_lookup_component_by_name); - struct snd_soc_pcm_runtime *snd_soc_get_pcm_runtime(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) @@ -829,18 +684,6 @@ static void soc_resume_init(struct snd_soc_card *card) static inline void soc_resume_init(struct snd_soc_card *card) { } #endif -static struct device_node -*soc_component_to_node(struct snd_soc_component *component) -{ - struct device_node *of_node; - - of_node = component->dev->of_node; - if (!of_node && component->dev->parent) - of_node = component->dev->parent->of_node; - - return of_node; -} - struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, const struct of_phandle_args *args) { @@ -855,34 +698,6 @@ struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, } EXPORT_SYMBOL_GPL(snd_soc_copy_dai_args); -static int snd_soc_is_matching_component( - const struct snd_soc_dai_link_component *dlc, - struct snd_soc_component *component) -{ - struct device_node *component_of_node; - - if (!dlc) - return 0; - - if (dlc->dai_args) { - struct snd_soc_dai *dai; - - for_each_component_dais(component, dai) - if (snd_soc_is_matching_dai(dlc, dai)) - return 1; - return 0; - } - - component_of_node = soc_component_to_node(component); - - if (dlc->of_node && component_of_node != dlc->of_node) - return 0; - if (dlc->name && strcmp(component->name, dlc->name)) - return 0; - - return 1; -} - static struct snd_soc_component *soc_find_component( const struct snd_soc_dai_link_component *dlc) { @@ -899,7 +714,7 @@ static struct snd_soc_component *soc_find_component( * CPU component and generic DMAEngine component */ for_each_component(component) - if (snd_soc_is_matching_component(dlc, component)) + if (snd_soc_component_matches_dlc(component, dlc)) return component; return NULL; @@ -926,9 +741,9 @@ struct snd_soc_dai *snd_soc_find_dai( /* Find CPU DAI from registered DAIs */ for_each_component(component) - if (snd_soc_is_matching_component(dlc, component)) + if (snd_soc_component_matches_dlc(component, dlc)) for_each_component_dais(component, dai) - if (snd_soc_is_matching_dai(dlc, dai)) + if (snd_soc_dai_matches_dlc(dai, dlc)) return dai; return NULL; @@ -1225,7 +1040,7 @@ static int snd_soc_add_pcm_runtime(struct snd_soc_card *card, /* Find PLATFORM from registered PLATFORMs */ for_each_link_platforms(dai_link, i, platform) { for_each_component(component) { - if (!snd_soc_is_matching_component(platform, component)) + if (!snd_soc_component_matches_dlc(component, platform)) continue; if (snd_soc_component_is_dummy(component) && component->num_dai) @@ -1394,143 +1209,6 @@ static int soc_init_pcm_runtime(struct snd_soc_card *card, return ret; } -static void soc_set_name_prefix(struct snd_soc_card *card, - struct snd_soc_component *component) -{ - struct device_node *of_node = soc_component_to_node(component); - const char *str; - int ret, i; - - for (i = 0; i < card->num_configs; i++) { - struct snd_soc_codec_conf *map = &card->codec_conf[i]; - - if (snd_soc_is_matching_component(&map->dlc, component) && - map->name_prefix) { - component->name_prefix = map->name_prefix; - return; - } - } - - /* - * If there is no configuration table or no match in the table, - * check if a prefix is provided in the node - */ - ret = of_property_read_string(of_node, "sound-name-prefix", &str); - if (ret < 0) - return; - - component->name_prefix = str; -} - -static void soc_remove_component(struct snd_soc_component *component, - int probed) -{ - - if (!component->card) - return; - - if (probed) - snd_soc_component_remove(component); - - list_del_init(&component->card_list); - snd_soc_dapm_free(snd_soc_component_to_dapm(component)); - soc_cleanup_component_debugfs(component); - component->card = NULL; - snd_soc_component_module_put_when_remove(component); -} - -static int soc_probe_component(struct snd_soc_card *card, - struct snd_soc_component *component) -{ - struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); - struct snd_soc_dai *dai; - int probed = 0; - int ret; - - if (snd_soc_component_is_dummy(component)) - return 0; - - if (component->card) { - if (component->card != card) { - dev_err(component->dev, - "Trying to bind component \"%s\" to card \"%s\" but is already bound to card \"%s\"\n", - component->name, card->name, component->card->name); - return -ENODEV; - } - return 0; - } - - ret = snd_soc_component_module_get_when_probe(component); - if (ret < 0) - return ret; - - component->card = card; - soc_set_name_prefix(card, component); - - soc_init_component_debugfs(component); - - snd_soc_dapm_init(dapm, card, component); - - ret = snd_soc_dapm_new_controls(dapm, - component->driver->dapm_widgets, - component->driver->num_dapm_widgets); - - if (ret != 0) { - dev_err(component->dev, - "Failed to create new controls %d\n", ret); - goto err_probe; - } - - for_each_component_dais(component, dai) { - ret = snd_soc_dapm_new_dai_widgets(dapm, dai); - if (ret != 0) { - dev_err(component->dev, - "Failed to create DAI widgets %d\n", ret); - goto err_probe; - } - } - - ret = snd_soc_component_probe(component); - if (ret < 0) - goto err_probe; - - WARN(!snd_soc_dapm_get_idle_bias(dapm) && - snd_soc_dapm_get_bias_level(dapm) != SND_SOC_BIAS_OFF, - "codec %s can not start from non-off bias with idle_bias_off==1\n", - component->name); - probed = 1; - - /* - * machine specific init - * see - * snd_soc_component_set_aux() - */ - ret = snd_soc_component_init(component); - if (ret < 0) - goto err_probe; - - ret = snd_soc_add_component_controls(component, - component->driver->controls, - component->driver->num_controls); - if (ret < 0) - goto err_probe; - - ret = snd_soc_dapm_add_routes(dapm, - component->driver->dapm_routes, - component->driver->num_dapm_routes); - if (ret < 0) - goto err_probe; - - /* see for_each_card_components */ - list_add(&component->card_list, &card->component_dev_list); - -err_probe: - if (ret < 0) - soc_remove_component(component, probed); - - return ret; -} - static void soc_remove_link_dais(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; @@ -1573,7 +1251,7 @@ static void soc_remove_link_components(struct snd_soc_card *card) if (component->driver->remove_order != order) continue; - soc_remove_component(component, 1); + snd_soc_component_remove(component, 1); } } } @@ -1591,7 +1269,7 @@ static int soc_probe_link_components(struct snd_soc_card *card) if (component->driver->probe_order != order) continue; - ret = soc_probe_component(card, component); + ret = snd_soc_component_probe(component, card); if (ret < 0) return ret; } @@ -1643,7 +1321,7 @@ static int soc_probe_aux_devices(struct snd_soc_card *card) if (component->driver->probe_order != order) continue; - ret = soc_probe_component(card, component); + ret = snd_soc_component_probe(component, card); if (ret < 0) return ret; } @@ -1660,7 +1338,7 @@ static void soc_remove_aux_devices(struct snd_soc_card *card) for_each_comp_order(order) { for_each_card_auxs_safe(card, comp, _comp) { if (comp->driver->remove_order == order) - soc_remove_component(comp, 1); + snd_soc_component_remove(comp, 1); } } } @@ -1859,7 +1537,7 @@ static void soc_check_tplg_fes(struct snd_soc_card *card) } dev_dbg(card->dev, "info: override BE DAI link %s\n", - card->dai_link[i].name); + dai_link->name); /* override platform component */ if (!dai_link->platforms) { @@ -2343,9 +2021,9 @@ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, } EXPORT_SYMBOL_GPL(snd_soc_cnew); -static 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) +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) { int i; @@ -2363,25 +2041,6 @@ static int snd_soc_add_controls(struct snd_card *card, struct device *dev, return 0; } -/** - * snd_soc_add_component_controls - Add an array of controls to a component. - * - * @component: Component 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_component_controls(struct snd_soc_component *component, - const struct snd_kcontrol_new *controls, unsigned int num_controls) -{ - struct snd_card *card = component->card->snd_card; - - return snd_soc_add_controls(card, component->dev, controls, - num_controls, component->name_prefix, component); -} -EXPORT_SYMBOL_GPL(snd_soc_add_component_controls); - /** * snd_soc_add_card_controls - add an array of controls to a SoC card. * Convenience function to add a list of controls. @@ -2402,26 +2061,6 @@ int snd_soc_add_card_controls(struct snd_soc_card *soc_card, } EXPORT_SYMBOL_GPL(snd_soc_add_card_controls); -/** - * snd_soc_add_dai_controls - add an array of controls to a DAI. - * Convenience function to add a list of controls. - * - * @dai: DAI 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_dai_controls(struct snd_soc_dai *dai, - const struct snd_kcontrol_new *controls, int num_controls) -{ - struct snd_card *card = dai->component->card->snd_card; - - return snd_soc_add_controls(card, dai->dev, controls, num_controls, - NULL, dai); -} -EXPORT_SYMBOL_GPL(snd_soc_add_dai_controls); - /** * snd_soc_register_card - Register a card with the ASoC core * @@ -2480,7 +2119,7 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_card); * Simplify DAI link configuration by removing ".-1" from device names * and sanitizing names. */ -static char *fmt_single_name(struct device *dev, int *id) +char *snd_soc_fmt_single_name(struct device *dev, int *id) { const char *devname = dev_name(dev); char *found, *name; @@ -2529,8 +2168,7 @@ static char *fmt_single_name(struct device *dev, int *id) * Simplify DAI link naming for single devices with multiple DAIs by removing * any ".-1" and using the DAI name (instead of device name). */ -static inline char *fmt_multiple_name(struct device *dev, - struct snd_soc_dai_driver *dai_drv) +char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *dai_drv) { if (dai_drv->name == NULL) { dev_err(dev, @@ -2542,74 +2180,6 @@ static inline char *fmt_multiple_name(struct device *dev, return devm_kstrdup(dev, dai_drv->name, GFP_KERNEL); } -void snd_soc_unregister_dai(struct snd_soc_dai *dai) -{ - lockdep_assert_held(&client_mutex); - - dev_dbg(dai->dev, "ASoC: Unregistered DAI '%s'\n", dai->name); - list_del(&dai->list); -} -EXPORT_SYMBOL_GPL(snd_soc_unregister_dai); - -/** - * snd_soc_register_dai - Register a DAI dynamically & create its widgets - * - * @component: The component the DAIs are registered for - * @dai_drv: DAI driver to use for the DAI - * @legacy_dai_naming: if %true, use legacy single-name format; - * if %false, use multiple-name format; - * - * Topology can use this API to register DAIs when probing a component. - * These DAIs's widgets will be freed in the card cleanup and the DAIs - * will be freed in the component cleanup. - */ -struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component, - struct snd_soc_dai_driver *dai_drv, - bool legacy_dai_naming) -{ - struct device *dev = component->dev; - struct snd_soc_dai *dai; - - lockdep_assert_held(&client_mutex); - - dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL); - if (dai == NULL) - return NULL; - - /* - * Back in the old days when we still had component-less DAIs, - * instead of having a static name, component-less DAIs would - * inherit the name of the parent device so it is possible to - * register multiple instances of the DAI. We still need to keep - * the same naming style even though those DAIs are not - * component-less anymore. - */ - if (legacy_dai_naming && - (dai_drv->id == 0 || dai_drv->name == NULL)) { - dai->name = fmt_single_name(dev, &dai->id); - } else { - dai->name = fmt_multiple_name(dev, dai_drv); - if (dai_drv->id) - dai->id = dai_drv->id; - else - dai->id = component->num_dai; - } - if (!dai->name) - return NULL; - - dai->component = component; - dai->dev = dev; - dai->driver = dai_drv; - - /* see for_each_component_dais */ - list_add_tail(&dai->list, &component->dai_list); - component->num_dai++; - - dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); - return dai; -} -EXPORT_SYMBOL_GPL(snd_soc_register_dai); - /** * snd_soc_unregister_dais - Unregister DAIs from the ASoC core * @@ -2707,14 +2277,30 @@ static void snd_soc_del_component_unlocked(struct snd_soc_component *component) list_del(&component->list); } -static int soc_component_initialize(struct snd_soc_component *component, - const struct snd_soc_component_driver *driver) +struct snd_soc_component *snd_soc_component_new(struct device *dev, const char *name, + const struct snd_soc_component_driver *driver) { - struct device *dev = component->dev; + struct snd_soc_component *component; + + component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL); + if (!component) + return ERR_PTR(-ENOMEM); component->dapm = snd_soc_dapm_alloc(dev); if (!component->dapm) - return -ENOMEM; + return ERR_PTR(-ENOMEM); + + if (!name) { + name = snd_soc_fmt_single_name(dev, NULL); + if (!name) { + dev_err(dev, "ASoC: Failed to allocate name\n"); + return ERR_PTR(-ENOMEM); + } + } + + component->dev = dev; + component->name = name; + component->driver = driver; INIT_LIST_HEAD(&component->dai_list); INIT_LIST_HEAD(&component->dobj_list); @@ -2723,22 +2309,13 @@ static int soc_component_initialize(struct snd_soc_component *component, INIT_LIST_HEAD(&component->card_aux_list); mutex_init(&component->io_mutex); - if (!component->name) { - component->name = fmt_single_name(dev, NULL); - if (!component->name) { - dev_err(dev, "ASoC: Failed to allocate name\n"); - return -ENOMEM; - } - } - - component->driver = driver; - - return 0; + return component; } +EXPORT_SYMBOL_GPL(snd_soc_component_new); -static int soc_component_add(struct snd_soc_component *component, - struct snd_soc_dai_driver *dai_drv, - int num_dai) +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; @@ -2766,7 +2343,7 @@ static int soc_component_add(struct snd_soc_component *component, } /* see for_each_component */ - list_add(&component->list, &component_list); + 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); @@ -2777,36 +2354,22 @@ static int soc_component_add(struct snd_soc_component *component, return ret; } +EXPORT_SYMBOL_GPL(snd_soc_component_register); -int snd_soc_register_component_c(struct snd_soc_component *component, - const struct snd_soc_component_driver *component_driver, - struct snd_soc_dai_driver *dai_drv, - int num_dai) -{ - int ret; - - ret = soc_component_initialize(component, component_driver); - if (ret < 0) - return ret; - - return soc_component_add(component, dai_drv, num_dai); -} -EXPORT_SYMBOL_GPL(snd_soc_register_component_c); - -int snd_soc_register_component_d(struct device *dev, - const struct snd_soc_component_driver *component_driver, - struct snd_soc_dai_driver *dai_drv, - int num_dai) +int snd_soc_register_component(struct device *dev, + const struct snd_soc_component_driver *component_driver, + struct snd_soc_dai_driver *dai_drv, + int num_dai) { struct snd_soc_component *component; - component = snd_soc_component_alloc(dev); - if (!component) - return -ENOMEM; + component = snd_soc_component_new(dev, NULL, component_driver); + if (IS_ERR(component)) + return PTR_ERR(component); - return snd_soc_register_component_c(component, component_driver, dai_drv, num_dai); + return snd_soc_component_register(component, dai_drv, num_dai); } -EXPORT_SYMBOL_GPL(snd_soc_register_component_d); +EXPORT_SYMBOL_GPL(snd_soc_register_component); /** * snd_soc_unregister_component_by_driver - Unregister component using a given driver @@ -3053,6 +2616,39 @@ int snd_soc_of_parse_tdm_slot(struct device_node *np, } EXPORT_SYMBOL_GPL(snd_soc_of_parse_tdm_slot); +/** + * snd_soc_of_xlate_dai_name - Resolve a sound-dai phandle argument to a + * DAI name by searching the component DAI list. + * @component: ASoC component to search. + * @args: Phandle arguments from the sound-dai property; args[0] is the + * DAI ID. + * @dai_name: Output pointer set to the matched DAI name on success. + * + * Return: 0 on success, -EINVAL if args_count != 1 or no match is found. + */ +int snd_soc_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + struct snd_soc_dai *dai; + int id; + + if (args->args_count != 1) + return -EINVAL; + + id = args->args[0]; + + for_each_component_dais(component, dai) { + if (dai->driver->id == id) { + *dai_name = snd_soc_dai_name(dai); + return 0; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_of_xlate_dai_name); + void snd_soc_dlc_use_cpu_as_platform(struct snd_soc_dai_link_component *platforms, struct snd_soc_dai_link_component *cpus) { @@ -3441,7 +3037,7 @@ int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_ guard(mutex)(&client_mutex); for_each_component(pos) { - struct device_node *component_of_node = soc_component_to_node(pos); + struct device_node *component_of_node = snd_soc_component_to_node(pos); if (component_of_node != args->np || !pos->num_dai) continue; @@ -3477,7 +3073,7 @@ int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_ id--; } - dlc->dai_name = snd_soc_dai_name_get(dai); + dlc->dai_name = snd_soc_dai_name(dai); } else if (ret) { /* * if another error than ENOTSUPP is returned go on and @@ -3552,7 +3148,7 @@ struct snd_soc_dai *snd_soc_get_dai_via_args(const struct of_phandle_args *dai_a for_each_component(component) { for_each_component_dais(component, dai) - if (snd_soc_is_match_dai_args(dai->driver->dai_args, dai_args)) + if (snd_soc_dai_matches_args(dai, dai_args)) return dai; } return NULL; diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c index b098d1100b4b..d124b6afb1b8 100644 --- a/sound/soc/soc-dai.c +++ b/sound/soc/soc-dai.c @@ -6,9 +6,11 @@ // Kuninori Morimoto // +#include #include #include #include +#include "soc-internal.h" #define soc_dai_ret(dai, ret) _soc_dai_ret(dai, __func__, ret) static inline int _soc_dai_ret(const struct snd_soc_dai *dai, @@ -18,6 +20,42 @@ static inline int _soc_dai_ret(const struct snd_soc_dai *dai, "at %s() on %s\n", func, dai->name); } +struct snd_soc_component *snd_soc_dai_to_component(const struct snd_soc_dai *dai) +{ + return dai->component; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_component); + +struct snd_soc_dai_driver *snd_soc_dai_to_driver(const struct snd_soc_dai *dai) +{ + return dai->driver; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_driver); + +void snd_soc_dai_set_priv(struct snd_soc_dai *dai, void *priv) +{ + dai->priv = priv; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_priv); + +void *snd_soc_dai_to_priv(struct snd_soc_dai *dai) +{ + return dai->priv; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_priv); + +struct snd_soc_dai *snd_soc_dai_from_list(struct list_head *list) +{ + return list_entry(list, struct snd_soc_dai, list); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_from_list); + +struct list_head *snd_soc_dai_to_list(struct snd_soc_dai *dai) +{ + return &dai->list; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_to_list); + /* * We might want to check substream by using list. * In such case, we can update these macros. @@ -669,8 +707,7 @@ bool snd_soc_dai_stream_valid(const struct snd_soc_dai *dai, int dir) return stream->channels_min; } -void snd_soc_dai_action(struct snd_soc_dai *dai, - int stream, int action) +void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action) { /* see snd_soc_dai_stream_active() */ dai->stream[stream].active += action; @@ -678,7 +715,6 @@ void snd_soc_dai_action(struct snd_soc_dai *dai, /* see snd_soc_component_active() */ dai->component->active += action; } -EXPORT_SYMBOL_GPL(snd_soc_dai_action); int snd_soc_dai_active(const struct snd_soc_dai *dai) { @@ -686,7 +722,7 @@ int snd_soc_dai_active(const struct snd_soc_dai *dai) active = 0; for_each_pcm_streams(stream) - active += dai->stream[stream].active; + active += snd_soc_dai_stream_active(dai, stream); return active; } @@ -989,3 +1025,377 @@ int snd_soc_dai_compr_get_metadata(struct snd_soc_dai *dai, return soc_dai_ret(dai, ret); } EXPORT_SYMBOL_GPL(snd_soc_dai_compr_get_metadata); + +/** + * snd_soc_dai_add_controls - add an array of controls to a DAI. + * Convenience function to add a list of controls. + * + * @dai: DAI 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_dai_add_controls(struct snd_soc_dai *dai, + const struct snd_kcontrol_new *controls, int num_controls) +{ + struct snd_card *card = dai->component->card->snd_card; + + return snd_soc_add_controls(card, dai->dev, controls, num_controls, NULL, dai); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_add_controls); + +int snd_soc_dai_matches_args(const struct snd_soc_dai *dai, + const struct of_phandle_args *args2) +{ + const struct of_phandle_args *args1 = dai->driver->dai_args; + + if (!args1 || !args2) + return 0; + + if (args1->np != args2->np) + return 0; + + for (int i = 0; i < args1->args_count; i++) + if (args1->args[i] != args2->args[i]) + return 0; + + return 1; +} + +int snd_soc_dai_matches_dlc(struct snd_soc_dai *dai, + const struct snd_soc_dai_link_component *dlc) +{ + if (!dlc) + return 0; + + if (dlc->dai_args) + return snd_soc_dai_matches_args(dai, dlc->dai_args); + + if (!dlc->dai_name) + return 1; + + /* see snd_soc_dai_name() */ + + if (dai->driver->name && + strcmp(dlc->dai_name, dai->driver->name) == 0) + return 1; + + if (strcmp(dlc->dai_name, dai->name) == 0) + return 1; + + if (dai->component->name && + strcmp(dlc->dai_name, dai->component->name) == 0) + return 1; + + return 0; +} + +const char *snd_soc_dai_name(const struct snd_soc_dai *dai) +{ + /* see snd_soc_dai_matches_dlc() */ + if (dai->driver->name) + return dai->driver->name; + + if (dai->name) + return dai->name; + + if (dai->component->name) + return dai->component->name; + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_name); + +int snd_soc_dai_id(const struct snd_soc_dai *dai) +{ + return dai->id; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_id); + +const struct snd_soc_pcm_stream * +snd_soc_dai_pcm_stream_get_i(const struct snd_soc_dai *dai, int stream) +{ + return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? + &dai->driver->playback : &dai->driver->capture; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_pcm_stream_get_i); + +struct snd_soc_dapm_widget *snd_soc_dai_stream_widget_get(struct snd_soc_dai *dai, int stream) +{ + return dai->stream[stream].widget; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_widget_get); + +void snd_soc_dai_stream_widget_set(struct snd_soc_dai *dai, int stream, struct snd_soc_dapm_widget *widget) +{ + dai->stream[stream].widget = widget; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_widget_set); + +void *snd_soc_dai_stream_dma_data_get_i(const struct snd_soc_dai *dai, int stream) +{ + return dai->stream[stream].dma_data; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_dma_data_get_i); + +void snd_soc_dai_stream_dma_data_set_i(struct snd_soc_dai *dai, int stream, void *data) +{ + dai->stream[stream].dma_data = data; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_dma_data_set_i); + +unsigned int snd_soc_dai_stream_tdm_mask_get(const struct snd_soc_dai *dai, int stream) +{ + return dai->stream[stream].tdm_mask; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_tdm_mask_get); + +void snd_soc_dai_stream_tdm_mask_set(struct snd_soc_dai *dai, int stream, unsigned int tdm_mask) +{ + dai->stream[stream].tdm_mask = tdm_mask; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_tdm_mask_set); + +unsigned int snd_soc_dai_stream_active(const struct snd_soc_dai *dai, int stream) +{ + /* see snd_soc_dai_active_update() for setup */ + return dai->stream[stream].active; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_stream_active); + +/** + * snd_soc_dai_set_stream() - Configures a DAI for stream operation + * @dai: DAI + * @stream: STREAM (opaque structure depending on DAI type) + * @direction: Stream direction(Playback/Capture) + * Some subsystems, such as SoundWire, don't have a notion of direction and we reuse + * the ASoC stream direction to configure sink/source ports. + * Playback maps to source ports and Capture for sink ports. + * + * This should be invoked with NULL to clear the stream set previously. + * Returns 0 on success, a negative error code otherwise. + */ +int snd_soc_dai_set_stream(struct snd_soc_dai *dai, void *stream, int direction) +{ + if (dai->driver->ops->set_stream) + return dai->driver->ops->set_stream(dai, stream, direction); + else + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_stream); + +/** + * snd_soc_dai_get_stream() - Retrieves stream from DAI + * @dai: DAI + * @direction: Stream direction(Playback/Capture) + * + * This routine only retrieves that was previously configured + * with snd_soc_dai_get_stream() + * + * Returns pointer to stream or an ERR_PTR value, e.g. + * ERR_PTR(-ENOTSUPP) if callback is not supported; + */ +void *snd_soc_dai_get_stream(struct snd_soc_dai *dai, int direction) +{ + if (dai->driver->ops->get_stream) + return dai->driver->ops->get_stream(dai, direction); + else + return ERR_PTR(-ENOTSUPP); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_stream); + +void snd_soc_dai_unregister(struct snd_soc_dai *dai) +{ + lockdep_assert_held(&client_mutex); + + dev_dbg(dai->dev, "ASoC: Unregistered DAI '%s'\n", dai->name); + list_del(&dai->list); +} +EXPORT_SYMBOL_GPL(snd_soc_dai_unregister); + +/** + * snd_soc_dai_register - Register a DAI dynamically & create its widgets + * + * @component: The component the DAIs are registered for + * @dai_drv: DAI driver to use for the DAI + * @legacy_dai_naming: if %true, use legacy single-name format; + * if %false, use multiple-name format; + * + * Topology can use this API to register DAIs when probing a component. + * These DAIs's widgets will be freed in the card cleanup and the DAIs + * will be freed in the component cleanup. + */ +struct snd_soc_dai *snd_soc_dai_register(struct snd_soc_component *component, + struct snd_soc_dai_driver *dai_drv, + bool legacy_dai_naming) +{ + struct device *dev = component->dev; + struct snd_soc_dai *dai; + + lockdep_assert_held(&client_mutex); + + dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL); + if (dai == NULL) + return NULL; + + /* + * Back in the old days when we still had component-less DAIs, + * instead of having a static name, component-less DAIs would + * inherit the name of the parent device so it is possible to + * register multiple instances of the DAI. We still need to keep + * the same naming style even though those DAIs are not + * component-less anymore. + */ + if (legacy_dai_naming && + (dai_drv->id == 0 || dai_drv->name == NULL)) { + dai->name = snd_soc_fmt_single_name(dev, &dai->id); + } else { + dai->name = snd_soc_fmt_multiple_name(dev, dai_drv); + if (dai_drv->id) + dai->id = dai_drv->id; + else + dai->id = component->num_dai; + } + if (!dai->name) + return NULL; + + dai->component = component; + dai->dev = dev; + dai->driver = dai_drv; + + /* see for_each_component_dais */ + list_add_tail(&dai->list, &component->dai_list); + component->num_dai++; + + dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name); + return dai; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_register); + +unsigned int snd_soc_dai_get_symmetric_rate(struct snd_soc_dai *dai) +{ + return dai->symmetric_rate; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_symmetric_rate); + +unsigned int snd_soc_dai_get_symmetric_channels(struct snd_soc_dai *dai) +{ + return dai->symmetric_channels; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_symmetric_channels); + +unsigned int snd_soc_dai_get_symmetric_sample_bits(struct snd_soc_dai *dai) +{ + return dai->symmetric_sample_bits; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_get_symmetric_sample_bits); + +void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + if (params) { + dai->symmetric_rate = params_rate(params); + dai->symmetric_channels = params_channels(params); + dai->symmetric_sample_bits = snd_pcm_format_physical_width(params_format(params)); + } else { + dai->symmetric_rate = 0; + dai->symmetric_channels = 0; + dai->symmetric_sample_bits = 0; + } +} + +int snd_soc_dai_symmetric_apply(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + int ret; + + if (!snd_soc_dai_active(dai)) + return 0; + +#define __symmetric_apply(name, NAME) \ + if (dai->symmetric_##name && \ + (dai->driver->symmetric_##name || rtd->dai_link->symmetric_##name)) { \ + dev_dbg(dai->dev, "ASoC: Symmetry forces %s to %d\n", \ + #name, dai->symmetric_##name); \ + \ + ret = snd_pcm_hw_constraint_single(substream->runtime, \ + SNDRV_PCM_HW_PARAM_##NAME, \ + dai->symmetric_##name); \ + if (ret < 0) \ + return snd_soc_ret(dai->dev, ret, \ + "Unable to apply %s constraint\n", #name); \ + } + + __symmetric_apply(rate, RATE); + __symmetric_apply(channels, CHANNELS); + __symmetric_apply(sample_bits, SAMPLE_BITS); + + return 0; +} + +int snd_soc_dai_symmetric_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai d; + struct snd_soc_dai *dai; + struct snd_soc_dai *cpu_dai; + unsigned int symmetry, i; + + d.name = __func__; + snd_soc_dai_symmetric_set_params(&d, params); + +#define __symmetric_params(xxx) \ + symmetry = rtd->dai_link->symmetric_##xxx; \ + for_each_rtd_dais(rtd, i, dai) \ + symmetry |= dai->driver->symmetric_##xxx; \ + \ + if (symmetry) \ + for_each_rtd_cpu_dais(rtd, i, cpu_dai) \ + if (!snd_soc_dai_is_dummy(cpu_dai) && \ + cpu_dai->symmetric_##xxx && \ + cpu_dai->symmetric_##xxx != d.symmetric_##xxx) \ + return snd_soc_ret(rtd->dev, -EINVAL, \ + "unmatched %s symmetry: %s:%d - %s:%d\n", \ + #xxx, cpu_dai->name, cpu_dai->symmetric_##xxx, \ + d.name, d.symmetric_##xxx); + + /* reject unmatched parameters when applying symmetry */ + __symmetric_params(rate); + __symmetric_params(channels); + __symmetric_params(sample_bits); + + return 0; +} + +void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai_link *link = rtd->dai_link; + struct snd_soc_dai *dai; + unsigned int symmetry, i; + + symmetry = link->symmetric_rate || + link->symmetric_channels || + link->symmetric_sample_bits; + + for_each_rtd_dais(rtd, i, dai) + symmetry = symmetry || + dai->driver->symmetric_rate || + dai->driver->symmetric_channels || + dai->driver->symmetric_sample_bits; + + if (symmetry) + substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; +} + +struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai) +{ + return dai->bclk; +} + +unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai) +{ + return dai->bclk_ratio; +} diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 06b85313af12..7455160b11fe 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -37,6 +37,7 @@ #include #include #include +#include "soc-internal.h" #include @@ -2868,6 +2869,7 @@ static void dapm_free_path(struct snd_soc_dapm_path *path) */ void snd_soc_dapm_free_widget(struct snd_soc_dapm_widget *w) { + struct snd_soc_dapm_context *d; struct snd_soc_dapm_path *p, *next_p; enum snd_soc_dapm_direction dir; @@ -2888,6 +2890,22 @@ void snd_soc_dapm_free_widget(struct snd_soc_dapm_widget *w) dapm_debugfs_free_widget(w); + if (w->dapm) { + if (w->dapm->card) { + for_each_card_dapms(w->dapm->card, d) { + if (d->wcache_sink == w) + d->wcache_sink = NULL; + if (d->wcache_source == w) + d->wcache_source = NULL; + } + } else { + if (w->dapm->wcache_sink == w) + w->dapm->wcache_sink = NULL; + if (w->dapm->wcache_source == w) + w->dapm->wcache_source = NULL; + } + } + kfree(w->kcontrols); kfree_const(w->name); kfree_const(w->sname); @@ -2905,9 +2923,6 @@ static void dapm_free_widgets(struct snd_soc_dapm_context *dapm) continue; snd_soc_dapm_free_widget(w); } - - dapm->wcache_sink = NULL; - dapm->wcache_source = NULL; } static struct snd_soc_dapm_widget *dapm_find_widget( @@ -4010,7 +4025,7 @@ static int dapm_dai_link_event_pre_pmu(struct snd_soc_dapm_widget *w, if (ret < 0) return ret; - snd_soc_dai_activate(source, substream->stream); + snd_soc_dai_active_update(source, substream->stream, 1); } substream->stream = SNDRV_PCM_STREAM_PLAYBACK; @@ -4021,7 +4036,7 @@ static int dapm_dai_link_event_pre_pmu(struct snd_soc_dapm_widget *w, if (ret < 0) return ret; - snd_soc_dai_activate(sink, substream->stream); + snd_soc_dai_active_update(sink, substream->stream, 1); } substream->hw_opened = 1; @@ -4150,14 +4165,14 @@ static int dapm_dai_link_event(struct snd_soc_dapm_widget *w, substream->stream = SNDRV_PCM_STREAM_CAPTURE; snd_soc_dapm_widget_for_each_source_path(w, path) { source = path->source->priv; - snd_soc_dai_deactivate(source, substream->stream); + snd_soc_dai_active_update(source, substream->stream, -1); snd_soc_dai_shutdown(source, substream, 0); } substream->stream = SNDRV_PCM_STREAM_PLAYBACK; snd_soc_dapm_widget_for_each_sink_path(w, path) { sink = path->sink->priv; - snd_soc_dai_deactivate(sink, substream->stream); + snd_soc_dai_active_update(sink, substream->stream, -1); snd_soc_dai_shutdown(sink, substream, 0); } break; diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c index 65b7ec52a054..d750dc44981d 100644 --- a/sound/soc/soc-generic-dmaengine-pcm.c +++ b/sound/soc/soc-generic-dmaengine-pcm.c @@ -32,7 +32,7 @@ static struct device *dmaengine_dma_dev(struct dmaengine_pcm *pcm, if (!pcm->chan[substream->stream]) return NULL; - return pcm->chan[substream->stream]->device->dev; + return dmaengine_get_dma_device(pcm->chan[substream->stream]); } /** @@ -242,9 +242,11 @@ static int dmaengine_pcm_new(struct snd_soc_component *component, if (!substream) continue; - if (!pcm->chan[i] && config->chan_names[i]) - pcm->chan[i] = dma_request_slave_channel(dev, - config->chan_names[i]); + if (!pcm->chan[i] && config->chan_names[i]) { + pcm->chan[i] = dma_request_chan(dev, config->chan_names[i]); + if (IS_ERR(pcm->chan[i])) + pcm->chan[i] = NULL; + } if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) { pcm->chan[i] = dmaengine_pcm_compat_request_channel( @@ -452,10 +454,6 @@ int snd_dmaengine_pcm_register(struct device *dev, struct dmaengine_pcm *pcm; int ret; - component = snd_soc_component_alloc(dev); - if (!component) - return -ENOMEM; - pcm = kzalloc_obj(*pcm); if (!pcm) return -ENOMEM; @@ -465,10 +463,6 @@ int snd_dmaengine_pcm_register(struct device *dev, pcm->config = config; pcm->flags = flags; - if (config->name) - snd_soc_component_set_name(component, config->name); - snd_soc_component_set_priv(component, pcm); - ret = dmaengine_pcm_request_chan_of(pcm, dev, config); if (ret) goto err_free_dma; @@ -478,7 +472,15 @@ int snd_dmaengine_pcm_register(struct device *dev, else driver = &dmaengine_pcm_component; - ret = snd_soc_register_component(component, driver, NULL, 0); + component = snd_soc_component_new(dev, config->name, driver); + if (IS_ERR(component)) { + ret = PTR_ERR(component); + goto err_free_dma; + } + + snd_soc_component_set_priv(component, pcm); + + ret = snd_soc_component_register(component, NULL, 0); if (ret) goto err_free_dma; diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h new file mode 100644 index 000000000000..b844b27574c8 --- /dev/null +++ b/sound/soc/soc-internal.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * soc-internal.h + * + * Copyright (c) 2026 Kuninori Morimoto + */ +#ifndef __SOC_INTERNAL_H +#define __SOC_INTERNAL_H + +/* + * This header is for ALSA SoC Framework internal, not for Vender drivers. + * The ALSA SoC functions for Vender drivers are defined in linux/include/sound/xxx.h + * as snd_soc_xxx(); + */ + +/* + * In soc-core + */ +extern struct mutex client_mutex; + +char *snd_soc_fmt_single_name(struct device *dev, int *id); +char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *dai_drv); +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); + +/* + * In soc-dai + */ +void snd_soc_dai_symmetric_set_params(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params); +int snd_soc_dai_symmetric_apply(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); +int snd_soc_dai_symmetric_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); +void snd_soc_dai_symmetric_update(struct snd_pcm_substream *substream); +struct clk *snd_soc_dai_get_bclk(struct snd_soc_dai *dai); +unsigned int snd_soc_dai_get_bclk_ratio(struct snd_soc_dai *dai); +void snd_soc_dai_active_update(struct snd_soc_dai *dai, int stream, int action); + +/* + * In soc-component + */ +struct device_node *snd_soc_component_to_node(struct snd_soc_component *component); +bool snd_soc_component_matches_dlc(struct snd_soc_component *component, + const struct snd_soc_dai_link_component *dlc); +void snd_soc_component_remove(struct snd_soc_component *component, int probed); +int snd_soc_component_probe(struct snd_soc_component *component, struct snd_soc_card *card); +struct list_head *snd_soc_component_get_list_head(void); + +#define for_each_component(component) \ + list_for_each_entry(component, snd_soc_component_get_list_head(), list) + +#endif /* __SOC_INTERNAL_H */ diff --git a/sound/soc/soc-link.c b/sound/soc/soc-link.c index 02fd68f2e702..50ff9ba3ac87 100644 --- a/sound/soc/soc-link.c +++ b/sound/soc/soc-link.c @@ -161,7 +161,7 @@ int snd_soc_link_trigger(struct snd_pcm_substream *substream, int cmd, break; ret = soc_link_trigger(substream, cmd); - soc_link_mark_pop(rtd, startup); + soc_link_mark_pop(rtd, trigger); } return ret; diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index c935571d43e9..b3a719604ec7 100644 --- a/sound/soc/soc-ops.c +++ b/sound/soc/soc-ops.c @@ -305,8 +305,8 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw); * Callback to provide information about a single mixer control, or a double * mixer control that spans 2 registers of the SX TLV type. SX TLV controls * have a range that represents both positive and negative values either side - * of zero but without a sign bit. min is the minimum register value, max is - * the number of steps. + * of zero but without a sign bit. min is the register value representing the + * minimum volume, max is the number of volume steps. * * Returns 0 for success. */ @@ -406,7 +406,7 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw_sx); static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) { struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value; - struct snd_ctl_elem_value *uctl; + struct snd_ctl_elem_value *uctl __free(kfree) = NULL; int ret; if (!mc->platform_max) @@ -418,7 +418,7 @@ static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) ret = kctl->get(kctl, uctl); if (ret < 0) - goto out; + return ret; if (uctl->value.integer.value[0] > mc->platform_max) uctl->value.integer.value[0] = mc->platform_max; @@ -428,10 +428,10 @@ static int snd_soc_clip_to_platform_max(struct snd_kcontrol *kctl) uctl->value.integer.value[1] = mc->platform_max; ret = kctl->put(kctl, uctl); + if (ret < 0) + return ret; -out: - kfree(uctl); - return ret; + return 0; } /** diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 3137c091bdb8..ef8ccf52798e 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -26,6 +26,7 @@ #include #include #include +#include "soc-internal.h" DEFINE_GUARD(snd_soc_card_mutex, struct snd_soc_card *, @@ -367,7 +368,7 @@ void snd_soc_runtime_action(struct snd_soc_pcm_runtime *rtd, snd_soc_dpcm_mutex_assert_held(rtd); for_each_rtd_dais(rtd, i, dai) - snd_soc_dai_action(dai, stream, action); + snd_soc_dai_active_update(dai, stream, action); /* Increments/Decrements the active count for components without DAIs */ for_each_rtd_components(rtd, i, component) { @@ -427,50 +428,6 @@ void dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, int event) snd_soc_dapm_stream_event(fe, dir, event); } -void soc_pcm_set_dai_params(struct snd_soc_dai *dai, - struct snd_pcm_hw_params *params) -{ - if (params) { - dai->symmetric_rate = params_rate(params); - dai->symmetric_channels = params_channels(params); - dai->symmetric_sample_bits = snd_pcm_format_physical_width(params_format(params)); - } else { - dai->symmetric_rate = 0; - dai->symmetric_channels = 0; - dai->symmetric_sample_bits = 0; - } -} - -static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, - struct snd_soc_dai *soc_dai) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - int ret; - - if (!snd_soc_dai_active(soc_dai)) - return 0; - -#define __soc_pcm_apply_symmetry(name, NAME) \ - if (soc_dai->symmetric_##name && \ - (soc_dai->driver->symmetric_##name || rtd->dai_link->symmetric_##name)) { \ - dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %s to %d\n",\ - #name, soc_dai->symmetric_##name); \ - \ - ret = snd_pcm_hw_constraint_single(substream->runtime, \ - SNDRV_PCM_HW_PARAM_##NAME,\ - soc_dai->symmetric_##name); \ - if (ret < 0) \ - return snd_soc_ret(soc_dai->dev, ret, \ - "Unable to apply %s constraint\n", #name); \ - } - - __soc_pcm_apply_symmetry(rate, RATE); - __soc_pcm_apply_symmetry(channels, CHANNELS); - __soc_pcm_apply_symmetry(sample_bits, SAMPLE_BITS); - - return 0; -} - /* * Shared BCLK constraint: when multiple DAIs share the same physical BCLK, * constrain hw_params so that the BCLK rate (rate * channels * sample_bits, @@ -488,17 +445,24 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); struct snd_interval constraint = { .empty = 1 }; unsigned int target_rate; + struct clk *dai_bclk; + unsigned int dai_bclk_ratio; int i; /* Protect the rtd list traversal with the ASoC card mutex helper. */ guard(snd_soc_card_mutex)(card); + dai_bclk = snd_soc_dai_get_bclk(dai); + dai_bclk_ratio = snd_soc_dai_get_bclk_ratio(dai); + /* Scan all DAIs on the card for an active peer sharing the same BCLK */ for_each_card_rtds(card, rtd) { for_each_rtd_cpu_dais(rtd, i, other_dai) { + struct clk *other_dai_bclk = snd_soc_dai_get_bclk(other_dai); + if (other_dai == dai) continue; - if (!other_dai->bclk) + if (!other_dai_bclk) continue; if (!snd_soc_dai_active(other_dai)) continue; @@ -510,10 +474,10 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, */ if (!other_dai->symmetric_rate) continue; - if (!clk_is_match(dai->bclk, other_dai->bclk)) + if (!clk_is_match(dai_bclk, other_dai_bclk)) continue; - active_bclk_rate = clk_get_rate(other_dai->bclk); + active_bclk_rate = clk_get_rate(other_dai_bclk); if (active_bclk_rate) goto found; } @@ -522,13 +486,13 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, return 0; found: - if (dai->bclk_ratio) { + if (dai_bclk_ratio) { /* * Driver has set an explicit BCLK ratio (e.g. for TDM where * BCLK = rate * slots * slot_width). The only valid rate is * active_bclk_rate / bclk_ratio. */ - target_rate = active_bclk_rate / dai->bclk_ratio; + target_rate = active_bclk_rate / dai_bclk_ratio; constraint.min = target_rate; constraint.max = target_rate; @@ -565,7 +529,7 @@ static int soc_pcm_shared_bclk_rule_rate(struct snd_pcm_hw_params *params, static int soc_pcm_apply_shared_bclk(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - if (!dai->bclk) + if (!snd_soc_dai_get_bclk(dai)) return 0; dev_dbg(dai->dev, @@ -579,62 +543,6 @@ static int soc_pcm_apply_shared_bclk(struct snd_pcm_substream *substream, -1); } -static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct snd_soc_dai d; - struct snd_soc_dai *dai; - struct snd_soc_dai *cpu_dai; - unsigned int symmetry, i; - - d.name = __func__; - soc_pcm_set_dai_params(&d, params); - -#define __soc_pcm_params_symmetry(xxx) \ - symmetry = rtd->dai_link->symmetric_##xxx; \ - for_each_rtd_dais(rtd, i, dai) \ - symmetry |= dai->driver->symmetric_##xxx; \ - \ - if (symmetry) \ - for_each_rtd_cpu_dais(rtd, i, cpu_dai) \ - if (!snd_soc_dai_is_dummy(cpu_dai) && \ - cpu_dai->symmetric_##xxx && \ - cpu_dai->symmetric_##xxx != d.symmetric_##xxx) \ - return snd_soc_ret(rtd->dev, -EINVAL, \ - "unmatched %s symmetry: %s:%d - %s:%d\n", \ - #xxx, cpu_dai->name, cpu_dai->symmetric_##xxx, \ - d.name, d.symmetric_##xxx); - - /* reject unmatched parameters when applying symmetry */ - __soc_pcm_params_symmetry(rate); - __soc_pcm_params_symmetry(channels); - __soc_pcm_params_symmetry(sample_bits); - - return 0; -} - -static void soc_pcm_update_symmetry(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct snd_soc_dai_link *link = rtd->dai_link; - struct snd_soc_dai *dai; - unsigned int symmetry, i; - - symmetry = link->symmetric_rate || - link->symmetric_channels || - link->symmetric_sample_bits; - - for_each_rtd_dais(rtd, i, dai) - symmetry = symmetry || - dai->driver->symmetric_rate || - dai->driver->symmetric_channels || - dai->driver->symmetric_sample_bits; - - if (symmetry) - substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; -} - static void soc_pcm_set_msb(struct snd_pcm_substream *substream, int bits) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -879,7 +787,7 @@ static int soc_pcm_clean(struct snd_soc_pcm_runtime *rtd, /* Make sure DAI parameters cleared if the DAI becomes inactive */ for_each_rtd_dais(rtd, i, dai) { if (snd_soc_dai_active(dai) == 0) - soc_pcm_set_dai_params(dai, NULL); + snd_soc_dai_symmetric_set_params(dai, NULL); } } @@ -1001,7 +909,7 @@ static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd, /* Check that the codec and cpu DAIs are compatible */ soc_pcm_init_runtime_hw(substream); - soc_pcm_update_symmetry(substream); + snd_soc_dai_symmetric_update(substream); ret = soc_hw_sanity_check(substream); if (ret < 0) @@ -1011,7 +919,7 @@ static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd, /* Symmetry only applies if we've already got an active stream. */ for_each_rtd_dais(rtd, i, dai) { - ret = soc_pcm_apply_symmetry(substream, dai); + ret = snd_soc_dai_symmetric_apply(substream, dai); if (ret != 0) goto err; } @@ -1135,7 +1043,7 @@ static int soc_pcm_hw_clean(struct snd_soc_pcm_runtime *rtd, /* clear the corresponding DAIs parameters when going to be inactive */ for_each_rtd_dais(rtd, i, dai) { if (snd_soc_dai_active(dai) == 1) - soc_pcm_set_dai_params(dai, NULL); + snd_soc_dai_symmetric_set_params(dai, NULL); if (snd_soc_dai_stream_active(dai, substream->stream) == 1) { if (!snd_soc_dai_mute_is_ctrled_at_trigger(dai)) @@ -1197,7 +1105,7 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, snd_soc_dpcm_mutex_assert_held(rtd); - ret = soc_pcm_params_symmetry(substream, params); + ret = snd_soc_dai_symmetric_params(substream, params); if (ret) goto out; @@ -1245,7 +1153,7 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, if(ret < 0) goto out; - soc_pcm_set_dai_params(codec_dai, &tmp_params); + snd_soc_dai_symmetric_set_params(codec_dai, &tmp_params); snd_soc_dapm_update_dai(substream, &tmp_params, codec_dai); } @@ -1283,7 +1191,7 @@ static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, goto out; /* store the parameters for each DAI */ - soc_pcm_set_dai_params(cpu_dai, &tmp_params); + snd_soc_dai_symmetric_set_params(cpu_dai, &tmp_params); snd_soc_dapm_update_dai(substream, &tmp_params, cpu_dai); } @@ -2032,11 +1940,11 @@ static int dpcm_apply_symmetry(struct snd_pcm_substream *fe_substream, int i; /* apply symmetry for FE */ - soc_pcm_update_symmetry(fe_substream); + snd_soc_dai_symmetric_update(fe_substream); for_each_rtd_cpu_dais (fe, i, fe_cpu_dai) { /* Symmetry only applies if we've got an active stream. */ - err = soc_pcm_apply_symmetry(fe_substream, fe_cpu_dai); + err = snd_soc_dai_symmetric_apply(fe_substream, fe_cpu_dai); if (err < 0) goto error; } @@ -2057,11 +1965,11 @@ static int dpcm_apply_symmetry(struct snd_pcm_substream *fe_substream, if (rtd->dai_link->be_hw_params_fixup) continue; - soc_pcm_update_symmetry(be_substream); + snd_soc_dai_symmetric_update(be_substream); /* Symmetry only applies if we've got an active stream. */ for_each_rtd_dais(rtd, i, dai) { - err = soc_pcm_apply_symmetry(fe_substream, dai); + err = snd_soc_dai_symmetric_apply(fe_substream, dai); if (err < 0) goto error; } diff --git a/sound/soc/soc-topology-test.c b/sound/soc/soc-topology-test.c index 346a52d3006a..8df845a196fe 100644 --- a/sound/soc/soc-topology-test.c +++ b/sound/soc/soc-topology-test.c @@ -24,8 +24,12 @@ static struct device *test_dev; static int snd_soc_tplg_test_init(struct kunit *test) { - test_dev = kunit_device_register(test, "sound-soc-topology-test"); - test_dev = get_device(test_dev); + struct device *dev; + + dev = kunit_device_register(test, "sound-soc-topology-test"); + if (IS_ERR(dev)) + return PTR_ERR(dev); + test_dev = get_device(dev); if (!test_dev) return -ENODEV; @@ -246,8 +250,8 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component_null_comp); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -256,7 +260,7 @@ static void snd_soc_tplg_test_load_with_null_comp(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component_null_comp, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -291,8 +295,8 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -301,7 +305,7 @@ static void snd_soc_tplg_test_load_with_null_ops(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -355,8 +359,8 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component_null_fw); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -365,7 +369,7 @@ static void snd_soc_tplg_test_load_with_null_fw(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component_null_fw, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -406,8 +410,8 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -416,7 +420,7 @@ static void snd_soc_tplg_test_load_empty_tplg(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -464,8 +468,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -474,7 +478,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_magic(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -522,8 +526,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -532,7 +536,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_abi(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -580,8 +584,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -590,7 +594,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_size(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -639,8 +643,8 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -649,7 +653,7 @@ static void snd_soc_tplg_test_load_empty_tplg_bad_payload_size(struct kunit *tes if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); /* cleanup */ @@ -690,8 +694,8 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -700,7 +704,7 @@ static void snd_soc_tplg_test_load_pcm_tplg(struct kunit *test) if (ret != 0 && ret != -EPROBE_DEFER) KUNIT_FAIL(test, "Failed to register card"); - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); snd_soc_unregister_component(test_dev); @@ -743,8 +747,8 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); @@ -755,7 +759,7 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_comp(struct kunit *test) for (i = 0; i < 100; i++) { - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); snd_soc_unregister_component(test_dev); @@ -799,13 +803,13 @@ static void snd_soc_tplg_test_load_pcm_tplg_reload_card(struct kunit *test) kunit_comp->card.num_links = ARRAY_SIZE(kunit_dai_links); kunit_comp->card.fully_routed = true; - component = snd_soc_component_alloc(test_dev); - KUNIT_ASSERT_NOT_NULL(test, component); + component = snd_soc_component_new(test_dev, NULL, &test_component); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, component); snd_soc_component_set_priv(component, kunit_comp); /* run test */ - ret = snd_soc_register_component(component, &test_component, NULL, 0); + ret = snd_soc_component_register(component, NULL, 0); KUNIT_EXPECT_EQ(test, 0, ret); for (i = 0; i < 100; i++) { diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index b0b22e6ebc03..d5d31da5e937 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -8,10 +8,12 @@ snd-sof-y := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ ifneq ($(CONFIG_SND_SOC_SOF_IPC3),) snd-sof-y += ipc3.o ipc3-loader.o ipc3-topology.o ipc3-control.o ipc3-pcm.o\ ipc3-dtrace.o +snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += ipc3-compress.o endif ifneq ($(CONFIG_SND_SOC_SOF_IPC4),) snd-sof-y += ipc4.o ipc4-loader.o ipc4-topology.o ipc4-control.o ipc4-pcm.o\ ipc4-mtrace.o ipc4-telemetry.o +snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += ipc4-compress.o endif # SOF client support @@ -19,8 +21,6 @@ ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),) snd-sof-y += sof-client.o endif -snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o - snd-sof-pci-y := sof-pci-dev.o snd-sof-acpi-y := sof-acpi-dev.o snd-sof-of-y := sof-of-dev.o diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig index 903e7ec3b3ba..d64f682cfbea 100644 --- a/sound/soc/sof/amd/Kconfig +++ b/sound/soc/sof/amd/Kconfig @@ -109,6 +109,7 @@ config SND_SOC_SOF_AMD_ACP7X depends on SND_SOC_SOF_PCI depends on AMD_NODE select SND_SOC_SOF_AMD_COMMON + select SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE help Select this option for SOF support on AMD ACP7.B and ACP7.F PCI revision based platforms. diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile index bb1c0ddef69d..3a7d230a4c46 100644 --- a/sound/soc/sof/amd/Makefile +++ b/sound/soc/sof/amd/Makefile @@ -4,7 +4,7 @@ # # Copyright(c) 2021, 2023, 2024 Advanced Micro Devices, Inc. All rights reserved. -snd-sof-amd-acp-y := acp.o acp-loader.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o +snd-sof-amd-acp-y := acp.o acp-loader.o acp_asp_mailbox.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o snd-sof-amd-acp-$(CONFIG_SND_SOC_SOF_ACP_PROBES) += acp-probes.o snd-sof-amd-renoir-y := pci-rn.o renoir.o snd-sof-amd-rembrandt-y := pci-rmb.o rembrandt.o diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index df656cdc1527..3bb1de8c0376 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -16,6 +16,7 @@ #include "acp.h" #include "acp-dsp-offset.h" #include +#include "../../amd/acp/soc_amd_sdw_common.h" /** * amd_sof_ipc_dump() - This function is called when IPC tx times out. @@ -131,6 +132,7 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * struct snd_soc_acpi_mach *mach; const struct snd_soc_acpi_link_adr *link; struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; + struct amd_pdm_pdata *pdm_pdata; int ret, i; if (acp_data->info.count) { @@ -154,9 +156,25 @@ static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev * } if (mach && mach->link_mask) { mach->mach_params.subsystem_rev = acp_data->pci_rev; + mach->mach_params.subsystem_vendor = acp_data->subsystem_vendor; + mach->mach_params.subsystem_device = acp_data->subsystem_device; + mach->mach_params.subsystem_id_set = true; mach->mach_params.links = mach->links; mach->mach_params.link_mask = mach->link_mask; mach->mach_params.platform = dev_name(sdev->dev); + + if (acp_data->pdm_sel) { + pdm_pdata = devm_kzalloc(sdev->dev, + sizeof(*pdm_pdata), + GFP_KERNEL); + if (!pdm_pdata) + return NULL; + pdm_pdata->pdm_sel = acp_data->pdm_sel; + mach->pdata = pdm_pdata; + } + + dev_dbg(sdev->dev, "SSID %x%04x\n", mach->mach_params.subsystem_vendor, + mach->mach_params.subsystem_device); return mach; } } @@ -177,6 +195,7 @@ struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev) struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; const struct sof_dev_desc *desc = sof_pdata->desc; struct snd_soc_acpi_mach *mach = NULL; + struct amd_pdm_pdata *pdm_pdata; if (desc->machines) mach = snd_soc_acpi_find_machine(desc->machines); @@ -188,7 +207,22 @@ struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev) } } + mach = devm_kmemdup(sdev->dev, mach, sizeof(*mach), GFP_KERNEL); + if (!mach) { + dev_err(sdev->dev, "failed to allocate machine entry copy\n"); + return NULL; + } + mach->mach_params.subsystem_rev = acp_data->pci_rev; + + if (acp_data->pdm_sel) { + pdm_pdata = devm_kzalloc(sdev->dev, sizeof(*pdm_pdata), GFP_KERNEL); + if (!pdm_pdata) + return NULL; + pdm_pdata->pdm_sel = acp_data->pdm_sel; + mach->pdata = pdm_pdata; + } + sof_pdata->tplg_filename = mach->sof_tplg_filename; sof_pdata->fw_filename = mach->fw_filename; diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h index bea1bd3afa70..0bcccf27d133 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -37,6 +37,30 @@ /* Registers from ACP_DSP_0 block */ #define ACP_DSP0_RUNSTALL 0x414 +/* + * ATU group registers for groups 9 to 16. + * ACPAXI2AXI_ATU_CTRL (0xC40) sits between GRP_8 and GRP_9, + * so groups 9 to 16 are offset by 4 bytes relative to a simple stride. + */ +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_9 0xC44 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_9 0xC48 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_10 0xC4C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_10 0xC50 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_11 0xC54 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_11 0xC58 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_12 0xC5C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_12 0xC60 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_13 0xC64 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_13 0xC68 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_14 0xC6C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_14 0xC70 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_15 0xC74 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_15 0xC78 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_16 0xC7C +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_16 0xC80 + +/* ATU page size value for 2 MB pages - used for carveout groups */ +#define PAGE_SIZE_2M_ENABLE 0x0 /* Registers from ACP_AXI2AXIATU block */ #define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 0xC00 @@ -132,6 +156,40 @@ #define ACP_SHA_DMA_INCLUDE_HDR 0x1CCC #define ACP_SHA_PSP_ACK 0x1C74 +/* ACP7.B/7.F MALL/Carveout configuration registers (ACP MMIO offsets) */ +#define ACP7X_MALL_ADDR_VALID 0x114C +#define ACP7X_MALL_BASE_ADDR_LOW 0x1150 +#define ACP7X_MALL_BASE_ADDR_HIGH 0x1154 +#define ACP7X_MALL_SIZE 0x1158 +#define ACP7X_MALL_VALID 0x115C + +/* ASP MPASP mailbox SMN addresses for ACP7.B/7.F carveout firmware load */ +#define MPASP_C2PMSG_173_REG 0x03810BB4 +#define MPASP_C2PMSG_174_REG 0x03810BB8 +#define MPASP_C2PMSG_175_REG 0x03810BBC +#define MPASP_C2PMSG_73_REG 0x03810A24 + +/* ASP mailbox command IDs */ +#define ASP_MBOX_CMD_VALIDATE_IMAGE 0x0001 +#define ASP_MBOX_CMD_CALC_HMAC 0x0002 +#define ASP_MBOX_CMD_VALIDATE_HMAC 0x0003 +#define ASP_MBOX_CMD_GET_CARVEOUT_ADDR 0x0004 + +/* MPASP_C2PMSG_173 control register bit definitions */ +#define ASP_MBOX_READY_BIT BIT(31) +#define ASP_MBOX_CMD_ID_MASK 0xFFFF + +/* ASP mailbox timeout: 5 seconds per ASP firmware load specification */ +#define ASP_MBOX_TIMEOUT_US 5000000 +/* Short timeout for the initial GET_CARVEOUT_ADDR probe: fail fast when + * ASP does not support carveout rather than blocking probe for 5 seconds. + */ +#define ASP_MBOX_PROBE_TIMEOUT_US 50000 +#define ASP_MBOX_POLL_INTERVAL_US 1000 + +/* ASP mailbox payload cookie "ACPB" */ +#define ASP_MBOX_COOKIE 0x41435042 + #define ACP_SCRATCH_REG_0 0x10000 #define ACP6X_DSP_FUSION_RUNSTALL 0x0644 #define ACP70_DSP_FUSION_RUNSTALL ACP6X_DSP_FUSION_RUNSTALL @@ -156,5 +214,22 @@ #define ACP7X_IDMA_ERROR_MASK 0x1FF9FF #define ACP7X_ZSC_DSP_CTRL 0x001014 #define ACP7X_PME_EN ACP70_PME_EN +/* SW enable base for SDW0; manager N uses ACP7X_SW_EN + (N * 0x2000) */ +#define ACP7X_SW_EN 0x5200 + +/* ACP7X SoundWire IO registers (non-gsync, up to 4 managers SW0-SW3) */ +#define ACP7X_EXTERNAL_SW_INTR_STAT 0x001A24 +#define ACP7X_SW_WAKE_EN 0x001458 +#define ACP7X_SW_PME_STS 0x001474 +#define ACP7X_SDW_STAT BIT(23) +#define ACP7X_ERROR_IRQ BIT(29) +#define ACP7X_SW_WAKE_EN_MASK BIT(0) +#define ACP7X_SW_HOST_WAKE_MASK BIT(22) +#define ACP7X_SDW_HOST_WAKE_STAT BIT(24) +#define ACP7X_SW_FIFO_ERROR_REASON 0x50C4 +#define ACP7X_SW_ERROR_REASON1 0x50CC +#define ACP7X_SW_ERROR_REASON2 0x50D4 +#define ACP7X_EXTERNAL_SDW_STAT BIT(16) +#define ACP7X_SW_ERR_STAT_MASK BIT(11) #endif diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c index f1ec85694323..abd080484804 100644 --- a/sound/soc/sof/amd/acp-loader.c +++ b/sound/soc/sof/amd/acp-loader.c @@ -168,12 +168,76 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) struct pci_dev *pci = to_pci_dev(sdev->dev); const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); struct acp_dev_data *adata; + u64 carveout_offset; unsigned int src_addr, size_fw, dest_addr; u32 page_count, dma_size; + u32 acp_logical_addr; int ret; adata = sdev->pdata->hw_pdata; + if (adata->asp_carveout_base) { + size_fw = adata->fw_code_raw_size; + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; + + if (adata->fw_code_carveout_addr < adata->asp_carveout_base || + adata->fw_code_carveout_addr - adata->asp_carveout_base + + size_fw > adata->asp_carveout_size) { + dev_err(sdev->dev, + "carveout addr 0x%llx out of window [0x%llx+0x%x]\n", + adata->fw_code_carveout_addr, + adata->asp_carveout_base, + adata->asp_carveout_size); + return -EINVAL; + } + carveout_offset = adata->fw_code_carveout_addr - adata->asp_carveout_base; + acp_logical_addr = ACP_SYSTEM_MEMORY_WINDOW + + (adata->asp_carveout_group_start * 8 * SZ_1M) + + (u32)carveout_offset; + + ret = configure_and_run_sha_dma(adata, NULL, + acp_logical_addr, + ACP_IRAM_BASE_ADDRESS, size_fw); + if (ret < 0) { + dev_err(sdev->dev, "SHA DMA from carveout failed: %d\n", ret); + return ret; + } + + if (adata->is_sram_in_use) { + configure_pte_for_fw_loading(FW_SRAM_DATA_BIN, + ACP_SRAM_PAGE_COUNT, adata); + src_addr = ACP_SYSTEM_MEMORY_WINDOW + ACP_DEFAULT_SRAM_LENGTH + + (page_count * ACP_PAGE_SIZE); + dest_addr = ACP7X_SRAM_BASE_ADDRESS; + ret = configure_and_run_dma(adata, src_addr, dest_addr, + adata->fw_sram_data_bin_size); + if (ret < 0) { + dev_err(sdev->dev, "SRAM DMA failed: %d\n", ret); + dma_free_coherent(&pci->dev, ACP_DEFAULT_SRAM_LENGTH, + adata->sram_data_buf, adata->sram_dma_addr); + adata->sram_data_buf = NULL; + return ret; + } + ret = acp_dma_status(adata, 0); + if (ret < 0) + dev_err(sdev->dev, "SRAM DMA status error: %d\n", ret); + } + + /* Enable cache window */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_OFFSET0, + desc->sram_pte_offset); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_SIZE0, + SRAM1_SIZE | BIT(31)); + + if (adata->is_sram_in_use) { + dma_free_coherent(&pci->dev, ACP_DEFAULT_SRAM_LENGTH, + adata->sram_data_buf, adata->sram_dma_addr); + adata->sram_data_buf = NULL; + } + return ret; + } + if (adata->pci_rev >= ACP7B_PCI_ID) { if (adata->acp_sof_signed_firmware_image) { if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { diff --git a/sound/soc/sof/amd/acp-stream.c b/sound/soc/sof/amd/acp-stream.c index 9212a3137cfd..a751ffdb0adf 100644 --- a/sound/soc/sof/amd/acp-stream.c +++ b/sound/soc/sof/amd/acp-stream.c @@ -15,15 +15,6 @@ #include "acp-dsp-offset.h" #include "acp.h" -#define PTE_GRP1_OFFSET 0x00000000 -#define PTE_GRP2_OFFSET 0x00800000 -#define PTE_GRP3_OFFSET 0x01000000 -#define PTE_GRP4_OFFSET 0x01800000 -#define PTE_GRP5_OFFSET 0x02000000 -#define PTE_GRP6_OFFSET 0x02800000 -#define PTE_GRP7_OFFSET 0x03000000 -#define PTE_GRP8_OFFSET 0x03800000 - int acp_dsp_stream_config(struct snd_sof_dev *sdev, struct acp_dsp_stream *stream) { const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index f89ad86260b4..86e2402b8ffd 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -22,11 +22,20 @@ #include "../ops.h" #include "acp.h" #include "acp-dsp-offset.h" +#include "../../amd/acp/soc_amd_sdw_common.h" +#include "acp7x.h" static bool enable_fw_debug; module_param(enable_fw_debug, bool, 0444); MODULE_PARM_DESC(enable_fw_debug, "Enable Firmware debug"); +static const u32 acp7x_sof_sdw_ext_stat[ACP7X_SDW_MAX_MANAGER_COUNT] = { + ACP7X_SDW_STAT << 0, + ACP7X_SDW_STAT << 1, + ACP7X_SDW_STAT << 2, + ACP7X_SDW_STAT << 3, +}; + static struct acp_quirk_entry quirk_valve_galileo = { .signed_fw_image = true, .skip_iram_dram_size_mod = true, @@ -270,11 +279,18 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, unsigned int tx_count, fw_qualifier, val; int ret; - if (!image_addr) { + if (!image_addr && + !adata->asp_carveout_base) { dev_err(sdev->dev, "SHA DMA image address is NULL\n"); return -EINVAL; } + /* + * Flush the ATU cache before programming SHA DMA registers + * to guarantee coherency at SHA DMA start time. + */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD); if (val & ACP_SHA_RUN) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RESET); @@ -288,8 +304,13 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, } } - if ((adata->quirks && adata->quirks->signed_fw_image) || - adata->acp_sof_signed_firmware_image) + /* + * The signing header must NOT be included for the carveout path: ASP + * stripped it when depositing the raw binary during VALIDATE_IMAGE. + */ + if (((adata->quirks && adata->quirks->signed_fw_image) || + adata->acp_sof_signed_firmware_image) && + !adata->asp_carveout_base) snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); @@ -307,7 +328,17 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, if (ret) return ret; } - snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN); + /* + * SHA_IOC_En triggers an interrupt-on-completion to ASP for source + * address validation. Only set it on the carveout path; enabling it + * on the legacy ATU-window path breaks PSP validation. + */ + if (adata->asp_carveout_base) { + reinit_completion(&adata->sha_dma_complete); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN_WITH_IOC); + } else { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN); + } ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_TRANSFER_BYTE_CNT, tx_count, tx_count == image_length, @@ -324,6 +355,23 @@ int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, return ret; } + if (adata->asp_carveout_base) { + /* + * Wait for the SHA IOC interrupt which fires after ASP validates + * the source address. Use ASP_MBOX_TIMEOUT_US to match the + * budget used for all other ASP mailbox operations. + * usecs_to_jiffies() can round to 0 at low HZ so clamp to at + * least 1 jiffy. + */ + unsigned long timeout_jiffies = + max(1UL, usecs_to_jiffies(ASP_MBOX_TIMEOUT_US)); + + if (!wait_for_completion_timeout(&adata->sha_dma_complete, + timeout_jiffies)) + dev_warn(sdev->dev, + "SHA DMA interrupt not received within timeout, continuing\n"); + } + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_DSP_FW_QUALIFIER, fw_qualifier, fw_qualifier & DSP_FW_RUN_ENABLE, ACP_REG_POLL_INTERVAL, ACP_DMA_COMPLETE_TIMEOUT_US); @@ -581,30 +629,209 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) return IRQ_NONE; } +/* ACP7X SoundWire IO data tables */ + +const struct sof_amd_acp7x_sdw_err_regs acp7x_sdw_err_regs[ACP7X_SDW_MAX_MANAGER_COUNT] = { + { ACP7X_SW_ERR_STAT_MASK << 0, + ACP7X_SW_FIFO_ERROR_REASON + (0 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (0 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (0 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 1, + ACP7X_SW_FIFO_ERROR_REASON + (1 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (1 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (1 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 2, + ACP7X_SW_FIFO_ERROR_REASON + (2 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (2 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (2 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 3, + ACP7X_SW_FIFO_ERROR_REASON + (3 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (3 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (3 * 0x2000) }, +}; + +const struct acp7x_sdw_wake_src acp7x_sdw_wake_sources[] = { + { ACP7X_SDW_HOST_WAKE_STAT << 0, ACP7X_SW_PME_STS + (0 * 4), + ACP7X_SW_WAKE_EN_MASK << 0, 0 }, + { ACP7X_SDW_HOST_WAKE_STAT << 1, ACP7X_SW_PME_STS + (1 * 4), + ACP7X_SW_WAKE_EN_MASK << 1, 1 }, + { ACP7X_SDW_HOST_WAKE_STAT << 2, ACP7X_SW_PME_STS + (2 * 4), + ACP7X_SW_WAKE_EN_MASK << 2, 2 }, + { ACP7X_SDW_HOST_WAKE_STAT << 3, ACP7X_SW_PME_STS + (3 * 4), + ACP7X_SW_WAKE_EN_MASK << 3, 3 }, +}; + +/* ACP7X SoundWire IO interrupt and wake helpers */ + +static void sof_amd_acp7x_handle_one_sdw_err(struct snd_sof_dev *sdev, u32 *err_stat, + u32 acp_error_stat_reg, + const struct sof_amd_acp7x_sdw_err_regs *regs) +{ + if (!(*err_stat & regs->err_stat_mask)) + return; + + *err_stat &= ~regs->err_stat_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, acp_error_stat_reg, *err_stat); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->fifo_err_reason, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->err_reason1, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->err_reason2, 0); +} + +/* Clears SoundWire error registers for all managers; runs in hard IRQ context. */ +static void sof_amd_acp7x_clear_sdw_err_regs(struct snd_sof_dev *sdev) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 err_stat; + unsigned int instance; + + err_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->acp_error_stat); + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) + sof_amd_acp7x_handle_one_sdw_err(sdev, &err_stat, desc->acp_error_stat, + &acp7x_sdw_err_regs[instance]); +} + +static void sof_amd_handle_acp7x_sdw_wake_event(struct acp_dev_data *adata) +{ + struct amd_sdw_manager *amd_manager; + unsigned int instance; + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + if (!adata->acp7x_sdw_wake_event[instance]) + continue; + if (!adata->sdw->pdev[instance]) + continue; + + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + pm_request_resume(amd_manager->dev); + adata->acp7x_sdw_wake_event[instance] = false; + } +} + +static int sof_amd_check_and_handle_acp7x_sdw_wake_irq(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 ext_intr_stat1, sdw_pme_stat, sdw_wake_en; + u32 i; + bool sdw_wake_irq = false; + + ext_intr_stat1 = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat1); + + for (i = 0; i < ARRAY_SIZE(acp7x_sdw_wake_sources); i++) { + const struct acp7x_sdw_wake_src *src = &acp7x_sdw_wake_sources[i]; + + bool woke = false; + + if (ext_intr_stat1 & src->host_stat_mask) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat1, + src->host_stat_mask); + woke = true; + } + + sdw_pme_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, src->pme_sts_reg); + if (sdw_pme_stat) { + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + sdw_wake_en &= ~src->wake_en_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN, sdw_wake_en); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, src->pme_sts_reg, sdw_pme_stat); + woke = true; + } + + if (woke) { + adata->acp7x_sdw_wake_event[src->instance] = true; + sdw_wake_irq = true; + } + } + + if (sdw_wake_irq) { + sof_amd_handle_acp7x_sdw_wake_event(adata); + return WAKE_IRQ_HANDLED; + } + return WAKE_IRQ_NONE; +} + +static void sof_acp7x_handle_sdw_manager_irq(struct snd_sof_dev *sdev, + struct acp_dev_data *adata, + const struct sof_amd_acp_desc *desc, + unsigned int instance, + u32 ext_stat_mask) +{ + struct amd_sdw_manager *amd_manager; + unsigned int sdw_intr_stat; + u32 sw_intr_reg = ACP7X_EXTERNAL_SW_INTR_STAT + (instance * 4); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ext_stat_mask); + sdw_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, sw_intr_reg); + if (sdw_intr_stat & ACP7X_EXTERNAL_SDW_STAT) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, sw_intr_reg, ACP7X_EXTERNAL_SDW_STAT); + if (!adata->sdw->pdev[instance]) + return; + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + schedule_work(&amd_manager->amd_sdw_irq_thread); + } +} + static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) { struct snd_sof_dev *sdev = dev_id; + struct acp_dev_data *adata = sdev->pdata->hw_pdata; const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); unsigned int base = desc->dsp_intr_base; unsigned int val; unsigned int ext_intr_stat; - int irq_flag = 0; + unsigned int instance; + int irq_flag = 0, wake_irq_flag = 0; + bool dsp_irq = false; val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); if (val & ACP_DSP_TO_HOST_IRQ) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, ACP_DSP_TO_HOST_IRQ); - return IRQ_WAKE_THREAD; + dsp_irq = true; } ext_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); - if (ext_intr_stat & ACP_ERROR_IRQ_MASK) { - snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); - snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); + + if (adata->sdw) { + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + if (ext_intr_stat & acp7x_sof_sdw_ext_stat[instance]) { + sof_acp7x_handle_sdw_manager_irq(sdev, adata, desc, instance, + acp7x_sof_sdw_ext_stat[instance]); + irq_flag = 1; + } + } + } + + if (adata->sdw) + wake_irq_flag = sof_amd_check_and_handle_acp7x_sdw_wake_irq(sdev); + /* + * SHA DMA completion interrupt (ACP_SHA_STAT, bit 15 of ext_intr_stat). + * ACP_SHA_STAT has no dedicated mask bit in ACP_EXTERNAL_INTR_CNTL; + * it is always enabled by default and fires only when SHA DMA completes + * with SHA_IOC_En set. SHA DMA is triggered exclusively during firmware + * loading, so this interrupt is only asserted in that context. + * Acknowledge and signal sha_dma_complete so configure_and_run_sha_dma() + * can proceed to poll ACP_SHA_DSP_FW_QUALIFIER. + */ + if (ext_intr_stat & ACP_SHA_STAT) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_SHA_STAT); + complete(&adata->sha_dma_complete); irq_flag = 1; } - if (irq_flag) + if (ext_intr_stat & ACP7X_ERROR_IRQ) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP7X_ERROR_IRQ); + sof_amd_acp7x_clear_sdw_err_regs(sdev); + irq_flag = 1; + } + + if (dsp_irq) + return IRQ_WAKE_THREAD; + + if (irq_flag || wake_irq_flag) return IRQ_HANDLED; return IRQ_NONE; @@ -728,6 +955,8 @@ static int acp_init(struct snd_sof_dev *sdev) const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); struct acp_dev_data *acp_data; unsigned int sdw0_wake_en, sdw1_wake_en; + u32 sdw_wake_en, intr_mask; + unsigned int i; int ret; /* power on */ @@ -771,6 +1000,20 @@ static int acp_init(struct snd_sof_dev *sdev) snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_DSP0_IDMA_ERROR_MASK, ACP7X_IDMA_ERROR_MASK); + /* + * Enable host-wake interrupt per manager based on SW_WAKE_EN: + * SW_WAKE_EN bit i enables ACP7X_SW_HOST_WAKE_MASK << i in INTR_CNTL1. + */ + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + intr_mask = 0; + for (i = 0; i < ACP7X_SDW_MAX_MANAGER_COUNT; i++) { + if (sdw_wake_en & BIT(i)) + intr_mask |= ACP7X_SW_HOST_WAKE_MASK << i; + } + if (intr_mask) + snd_sof_dsp_update_bits(sdev, ACP_DSP_BAR, + ACP7X_EXTERNAL_INTR_CNTL1, + intr_mask, intr_mask); break; } return 0; @@ -779,15 +1022,30 @@ static int acp_init(struct snd_sof_dev *sdev) static bool check_acp_sdw_enable_status(struct snd_sof_dev *sdev) { struct acp_dev_data *acp_data; - u32 sdw0_en, sdw1_en; + u32 sdw0_en, sdw1_en, sdw2_en, sdw3_en; acp_data = sdev->pdata->hw_pdata; if (!acp_data->sdw) return false; - sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW0_EN); - sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW1_EN); - acp_data->sdw_en_stat = sdw0_en || sdw1_en; + switch (acp_data->pci_rev) { + case ACP63_PCI_ID: + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: + sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW0_EN); + sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW1_EN); + acp_data->sdw_en_stat = sdw0_en || sdw1_en; + break; + case ACP7B_PCI_ID: + case ACP7F_PCI_ID: + sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (0 * 0x2000)); + sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (1 * 0x2000)); + sdw2_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (2 * 0x2000)); + sdw3_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (3 * 0x2000)); + acp_data->sdw_en_stat = sdw0_en || sdw1_en || sdw2_en || sdw3_en; + break; + } return acp_data->sdw_en_stat; } @@ -853,6 +1111,31 @@ int amd_sof_acp_resume(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(amd_sof_acp_resume, "SND_SOC_SOF_AMD_COMMON"); +static void acp_sof_scan_pdm_devices(struct snd_sof_dev *sdev, + struct acpi_device *pdm_dev) +{ + struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; + struct fwnode_handle *fwnode, *child; + u32 ep_port_val; + + fwnode = acpi_fwnode_handle(pdm_dev); + child = fwnode_get_next_child_node(fwnode, NULL); + if (!child) + return; + + if (!fwnode_property_read_u32(child, "acp-audio-ep-port", &ep_port_val)) { + if (ep_port_val == ACP_DEV_PORT_PDM) + acp_data->pdm_sel = ACP7X_PDM_DMIC0; + else if (ep_port_val == ACP_DEV_PORT_PDM2) + acp_data->pdm_sel = ACP7X_PDM_DMIC1; + else + dev_warn(sdev->dev, + "acp-audio-ep-port: unrecognized value %u\n", + ep_port_val); + } + fwnode_handle_put(child); +} + #if IS_ENABLED(CONFIG_SND_SOC_SOF_AMD_SOUNDWIRE) static int acp_sof_scan_sdw_devices(struct snd_sof_dev *sdev, u64 addr) { @@ -967,7 +1250,10 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) adata->addr = addr; adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; adata->pci_rev = pci->revision; + adata->subsystem_vendor = pci->subsystem_vendor; + adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); + init_completion(&adata->sha_dma_complete); sdev->pdata->hw_pdata = adata; ret = acp_init(sdev); @@ -1038,6 +1324,8 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) free_ipc_irq: free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); unregister_dev: platform_device_unregister(adata->dmic_dev); return ret; @@ -1048,12 +1336,12 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev) { struct acp_dev_data *adata = sdev->pdata->hw_pdata; - if (adata->sdw) - amd_sof_sdw_exit(sdev); - if (sdev->ipc_irq) free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); + if (adata->dmic_dev) platform_device_unregister(adata->dmic_dev); @@ -1068,6 +1356,7 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) const struct sof_amd_acp_desc *chip; const union acpi_object *obj; struct acpi_device *adev; + struct acpi_device *pdm_dev; unsigned int addr; unsigned int irqflags; int ret; @@ -1101,7 +1390,10 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) adata->addr = addr; adata->reg_range = chip->reg_end_addr - chip->reg_start_addr; adata->pci_rev = pci->revision; + adata->subsystem_vendor = pci->subsystem_vendor; + adata->subsystem_device = pci->subsystem_device; mutex_init(&adata->acp_lock); + init_completion(&adata->sha_dma_complete); sdev->pdata->hw_pdata = adata; ret = acp_init(sdev); @@ -1120,7 +1412,27 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) goto unregister_dev; } + /* scan SoundWire capabilities exposed by DSDT */ + ret = acp_sof_scan_sdw_devices(sdev, chip->sdw_acpi_dev_addr); + if (ret < 0) { + dev_dbg(sdev->dev, "skipping SoundWire, not detected with ACPI scan\n"); + goto skip_soundwire; + } + ret = amd_sof_sdw_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: SoundWire probe error\n"); + goto free_ipc_irq; + } + if (adata->info.link_mask) + adata->is_sdw_dev = true; + +skip_soundwire: if (adev) { + /* DMIC ACPI child address is 2 on ACP7x platforms */ + pdm_dev = acpi_find_child_device(adev, ACP7X_DMIC_ADDR, 0); + if (pdm_dev) + acp_sof_scan_pdm_devices(sdev, pdm_dev); + if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ACPI_TYPE_INTEGER, &obj)) adata->acp_sof_signed_firmware_image = obj->integer.value; @@ -1154,10 +1466,26 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) acp_memory_init(sdev); acp_dsp_stream_init(sdev); + /* + * Carveout is the only supported signed firmware load path on + * ACP7.B/7.F. Query ASP now when signed firmware is enabled so + * probe fails cleanly if carveout is unavailable. + */ + if (adata->acp_sof_signed_firmware_image) { + ret = acp7x_query_asp_carveout(sdev); + if (ret) { + dev_err(sdev->dev, + "ASP carveout unavailable, cannot load firmware: %d\n", ret); + goto free_ipc_irq; + } + } + return 0; free_ipc_irq: free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); unregister_dev: platform_device_unregister(adata->dmic_dev); return ret; @@ -1171,6 +1499,9 @@ void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) if (sdev->ipc_irq) free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); + if (adata->dmic_dev) platform_device_unregister(adata->dmic_dev); @@ -1178,6 +1509,41 @@ void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(amd_sof_acp7x_remove, "SND_SOC_SOF_AMD_COMMON"); +static void handle_amd_sof_acp7x_sdw_pme_event(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata; + struct amd_sdw_manager *amd_manager; + u32 sdw_pme_stat, sdw_wake_en, pme_reg, wake_mask; + unsigned int instance; + + adata = sdev->pdata->hw_pdata; + if (!adata->sdw) + return; + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + pme_reg = ACP7X_SW_PME_STS + (instance * 4); + wake_mask = ACP7X_SW_WAKE_EN_MASK << instance; + + sdw_pme_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, pme_reg); + if (!sdw_pme_stat) + continue; + + mutex_lock(&adata->acp_lock); + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + sdw_wake_en &= ~wake_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN, sdw_wake_en); + mutex_unlock(&adata->acp_lock); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pme_reg, sdw_pme_stat); + + if (!adata->sdw->pdev[instance]) + continue; + + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + pm_request_resume(amd_manager->dev); + } +} + int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) { struct acp_dev_data *acp_data; @@ -1186,6 +1552,11 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) acp_data = sdev->pdata->hw_pdata; + if (acp_data->is_sdw_dev && check_acp_sdw_enable_status(sdev)) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); + return acp_dsp_reset(sdev); + } + ret = acp_reset(sdev); if (ret) { dev_err(sdev->dev, "ACP Reset failed\n"); @@ -1197,7 +1568,8 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) enable = true; break; default: - break; + dev_err(sdev->dev, "Unexpected PCI revision: 0x%x\n", acp_data->pci_rev); + return -EINVAL; } snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); @@ -1206,13 +1578,25 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) } EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend, "SND_SOC_SOF_AMD_COMMON"); -int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) +int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) +{ + return amd_sof_acp7x_suspend(sdev, 0); +} +EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); + +int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) { struct acp_dev_data *acp_data; int ret; acp_data = sdev->pdata->hw_pdata; + if (acp_data->sdw_en_stat) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); + return acp_dsp_reset(sdev); + } + ret = acp_init(sdev); if (ret) { dev_err(sdev->dev, "ACP Init failed\n"); @@ -1224,31 +1608,41 @@ int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) return ret; } - switch (acp_data->pci_rev) { - case ACP7B_PCI_ID: - case ACP7F_PCI_ID: + if (acp_data->is_sdw_dev) + handle_amd_sof_acp7x_sdw_pme_event(sdev); + + return 0; +} +EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); + +int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *acp_data; + int ret; + + acp_data = sdev->pdata->hw_pdata; + + if (acp_data->sdw_en_stat) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); - break; - default: - break; + return acp_dsp_reset(sdev); + } + + ret = acp_init(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Init failed\n"); + return ret; + } + ret = acp_memory_init(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Memory init failed\n"); + return ret; } return 0; } EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); -int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) -{ - return amd_sof_acp7x_suspend(sdev, 0); -} -EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); - -int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) -{ - return amd_sof_acp7x_resume(sdev); -} -EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); - MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("AMD ACP sof driver"); MODULE_IMPORT_NS("SOUNDWIRE_AMD_INIT"); diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 16d66b2eaa70..b17a36b31ee0 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -49,10 +49,14 @@ #define ACP_PAGE_SIZE 0x1000 #define ACP_DMA_CH_RUN 0x02 #define ACP_MAX_DESC_CNT 0x02 +#define ACP_DSP_RUN 0x00 #define DSP_FW_RUN_ENABLE 0x01 #define ACP_SHA_RUN 0x01 #define ACP_SHA_RESET 0x02 #define ACP_SHA_HEADER 0x01 +/* SHA_IOC_En (bit 2): interrupt-on-completion to ASP; must be set alongside sha_run */ +#define ACP_SHA_IOC_EN 0x04 +#define ACP_SHA_RUN_WITH_IOC (ACP_SHA_RUN | ACP_SHA_IOC_EN) #define ACP_DMA_CH_RST 0x01 #define ACP_DMA_CH_GRACEFUL_RST_EN 0x10 #define ACP_ATU_CACHE_INVALID 0x01 @@ -115,6 +119,28 @@ #define ACP_FIRMWARE_SIGNATURE 0x100 #define ACP_IMAGE_HEADER_SIZE ACP_FIRMWARE_SIGNATURE #define ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF 0x14 +#define ACP_ASP_SIGNATURE_LENGTH 512 + +/* + * ACP7.B/7.F carveout: 32 MB region mapped at 2 MB page granularity. + * Use AXI2AXIATU_PAGE_SIZE_Mask2MB (value 0x0) for carveout ATU + * groups giving 32MB / 2MB = 16 PTEs. 4KB pages would require 8192 PTEs and + * is not how the hardware carveout path is configured. + */ +#define ACP7X_CARVEOUT_MAX_SIZE (32 * SZ_1M) +#define ACP7X_CARVEOUT_PAGE_SIZE (2 * SZ_1M) +#define ACP7X_CARVEOUT_PAGE_COUNT (ACP7X_CARVEOUT_MAX_SIZE / ACP7X_CARVEOUT_PAGE_SIZE) +/* + * PTE high-word flags for ATU entries: + * bit[31]: PAGE_Enable - entry is valid + * bit[30]: MALL_Enable - route accesses through carveout (MALL) memory, + * not standard DRAM. Required for ASP to recognise the SHA DMA + * source as within the carveout and grant ACP_SHA_DSP_FW_QUALIFIER. + * (PAGE_Enable | MALL_Enable ATU entry flags) + */ +#define ACP_ATU_PTE_PAGE_ENABLE BIT(31) +#define ACP_ATU_PTE_MALL_ENABLE BIT(30) +#define ACP_ATU_PTE_CARVEOUT_FLAGS (ACP_ATU_PTE_PAGE_ENABLE | ACP_ATU_PTE_MALL_ENABLE) #define ACP_ERROR_IRQ_MASK BIT(29) #define ACP_SDW0_IRQ_MASK BIT(21) @@ -125,9 +151,41 @@ #define ACP_SRAM_PAGE_COUNT 128 #define ACP6X_SDW_MAX_MANAGER_COUNT 2 #define ACP70_SDW_MAX_MANAGER_COUNT ACP6X_SDW_MAX_MANAGER_COUNT +/* ACPI _DSD acp-audio-ep-port values for PDM controller selection */ +#define ACP_DEV_PORT_PDM 4 +#define ACP_DEV_PORT_PDM2 5 +#define SDW_ACPI_ADDR_ACP7X SDW_ACPI_ADDR_ACP63 +#define ACP7X_SDW_MAX_MANAGER_COUNT 4 + +/* ACPI child device address for the ACP7x PDM/DMIC device */ +#define ACP7X_DMIC_ADDR 2 #define ACP_DSP_MSG_SET 1 #define ACP_DSP_ACK_SET 1 +/* + * ATU PTE group ACP logical address offsets. + * Each group covers 8 MB (0x00800000). Groups 1-8 are used for PCM + * streams; groups 9-16 are extended groups. + * + * GRP_N_OFFSET = (N - 1) * 8 MB + */ +#define PTE_GRP1_OFFSET 0x00000000 +#define PTE_GRP2_OFFSET 0x00800000 +#define PTE_GRP3_OFFSET 0x01000000 +#define PTE_GRP4_OFFSET 0x01800000 +#define PTE_GRP5_OFFSET 0x02000000 +#define PTE_GRP6_OFFSET 0x02800000 +#define PTE_GRP7_OFFSET 0x03000000 +#define PTE_GRP8_OFFSET 0x03800000 +#define PTE_GRP9_OFFSET 0x04000000 +#define PTE_GRP10_OFFSET 0x04800000 +#define PTE_GRP11_OFFSET 0x05000000 +#define PTE_GRP12_OFFSET 0x05800000 +#define PTE_GRP13_OFFSET 0x06000000 +#define PTE_GRP14_OFFSET 0x06800000 +#define PTE_GRP15_OFFSET 0x07000000 +#define PTE_GRP16_OFFSET 0x07800000 + enum clock_source { ACP_CLOCK_96M = 0, ACP_CLOCK_48M, @@ -141,6 +199,29 @@ struct acp_atu_grp_pte { u32 high; }; +/* ASP mailbox payload for GET_CARVEOUT_ADDR command (0x04) */ +struct asp_get_carveout_payload { + u32 cookie; /* [0x00] Driver: ASP_MBOX_COOKIE */ + u32 status; /* [0x04] ASP: 0 = success */ + u64 carveout_addr; /* [0x08] ASP: carveout physical base address */ + u32 carveout_size; /* [0x10] ASP: ACP_MALL_SIZE register value */ + u32 mall_addr_valid; /* [0x14] ASP: ACP_MALL_ADDR_VALID register value */ + u32 mall_valid; /* [0x18] ASP: ACP_MALL_VALID register value */ +}; + +/* + * ASP mailbox payload for VALIDATE_IMAGE command (0x01). + * Driver fills src/dest fields; ASP fills carveout_dest_addr and status. + */ +struct asp_validate_image_payload { + u32 cookie; /* [0x00] Driver: ASP_MBOX_COOKIE */ + u32 status; /* [0x04] ASP: 0 = success */ + u64 carveout_dest_addr; /* [0x08] ASP: physical addr of raw binary in carveout */ + u64 dest_offset; /* [0x10] Driver: destination offset within carveout */ + u64 src_phys_addr; /* [0x18] Driver: signed FW source physical addr in DDR */ + u32 fw_image_size; /* [0x20] Driver: total signed image size in bytes */ +}; + union dma_tx_cnt { struct { unsigned int count : 19; @@ -279,8 +360,42 @@ struct acp_dev_data { bool acp70_sdw0_wake_event; /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ bool acp70_sdw1_wake_event; + /* per-manager wake event flags; indexed by SoundWire manager instance (0-3) */ + bool acp7x_sdw_wake_event[ACP7X_SDW_MAX_MANAGER_COUNT]; + /* PDM controller index selected from ACPI acp-audio-ep-port; passed to machine driver */ + unsigned int pdm_sel; + bool is_sdw_dev; unsigned int pci_rev; + u32 subsystem_vendor; + u32 subsystem_device; int acp_sof_signed_firmware_image; + /* ACP7.F ASP carveout firmware load state */ + u64 asp_carveout_base; + u32 asp_carveout_size; + u32 asp_carveout_group_start; /* first ATU group index for carveout (from ASP) */ + u32 asp_carveout_group_count; /* number of consecutive ATU groups for carveout */ + /* + * Physical address and raw size of each validated DSP code binary + * within the carveout region (returned by ASP VALIDATE_IMAGE). + * Preserved across suspend/resume so that SHA DMA can reload DSP IRAM + * from the carveout without re-authenticating from disk. + */ + u64 fw_code_carveout_addr; + u32 fw_code_raw_size; + /* Real CPU physical address of the ASP MPASP mailbox payload page */ + phys_addr_t asp_mbox_buf_phys; + void *asp_mbox_buf; + /* + * SHA DMA completion for the carveout path. + * ACP_SHA_STAT (bit 15 of ACP_EXTERNAL_INTR_STAT) has no dedicated + * mask bit in ACP_EXTERNAL_INTR_CNTL - it is always enabled by default. + * SHA DMA is triggered exclusively during firmware loading, so no + * spurious ACP_SHA_STAT interrupts are expected outside that window. + * acp7x_irq_handler signals sha_dma_complete when ACP_SHA_STAT fires; + * configure_and_run_sha_dma() waits on it before polling + * ACP_SHA_DSP_FW_QUALIFIER to ensure ASP has validated the source. + */ + struct completion sha_dma_complete; }; void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); @@ -303,6 +418,11 @@ int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev); int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev); int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type); +/* ACP7.B/7.F ASP carveout firmware load helper functions */ +int acp7x_query_asp_carveout(struct snd_sof_dev *sdev); +int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev); +int acp7x_configure_carveout_pte(struct snd_sof_dev *sdev); + /* Block IO callbacks */ int acp_dsp_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, u32 offset, void *src, size_t size); diff --git a/sound/soc/sof/amd/acp7x.c b/sound/soc/sof/amd/acp7x.c index b6722d11168c..521b7c00c761 100644 --- a/sound/soc/sof/amd/acp7x.c +++ b/sound/soc/sof/amd/acp7x.c @@ -170,8 +170,15 @@ int sof_acp7x_ops_init(struct snd_sof_dev *sdev) acp_sof_post_fw_run_delay = obj->integer.value; } - if (acp_sof_signed_firmware_image) - sof_acp7x_ops.load_firmware = acp_sof_load_signed_firmware; + if (acp_sof_signed_firmware_image) { + /* + * This driver handles only ACP7.B/7.F which both use the ASP + * carveout firmware load path. Assign unconditionally rather + * than rechecking the revision so the correct loader is always + * selected when signed firmware is enabled. + */ + sof_acp7x_ops.load_firmware = acp7x_load_firmware_carveout; + } if (acp_sof_post_fw_run_delay) sof_acp7x_ops.post_fw_run = sof_acp7x_post_fw_run_delay; diff --git a/sound/soc/sof/amd/acp7x.h b/sound/soc/sof/amd/acp7x.h new file mode 100644 index 000000000000..5151d8757c12 --- /dev/null +++ b/sound/soc/sof/amd/acp7x.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2026 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Vijendar Mukunda + */ + +#ifndef __SOF_AMD_ACP7X_H +#define __SOF_AMD_ACP7X_H + +/* Return values for sof_amd_check_and_handle_acp7x_sdw_wake_irq() */ +#define WAKE_IRQ_HANDLED 1 +#define WAKE_IRQ_NONE 0 + +/* ACP7X SoundWire IO structures */ + +struct sof_amd_acp7x_sdw_err_regs { + u32 err_stat_mask; + u32 fifo_err_reason; + u32 err_reason1; + u32 err_reason2; +}; + +struct acp7x_sdw_wake_src { + u32 host_stat_mask; + u32 pme_sts_reg; + u32 wake_en_mask; + u8 instance; +}; + +extern const struct sof_amd_acp7x_sdw_err_regs acp7x_sdw_err_regs[ACP7X_SDW_MAX_MANAGER_COUNT]; +extern const struct acp7x_sdw_wake_src acp7x_sdw_wake_sources[]; + +#endif /* __SOF_AMD_ACP7X_H */ diff --git a/sound/soc/sof/amd/acp_asp_mailbox.c b/sound/soc/sof/amd/acp_asp_mailbox.c new file mode 100644 index 000000000000..bc183814b973 --- /dev/null +++ b/sound/soc/sof/amd/acp_asp_mailbox.c @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2026 Advanced Micro Devices, Inc. All rights reserved. +// +// Authors: Vijendar Mukunda + +/* + * ASP (AMD Security Processor) mailbox communication and ACP7.B/7.F carveout + * memory firmware load/restore for the ACP7.B/7.F platform. + */ + +#include +#include +#include +#include +#include +#include + +#include "../ops.h" +#include "acp-dsp-offset.h" +#include "acp.h" + +/* + * acp7x_asp_send_cmd - send one ASP mailbox command via the ACPI AMSG method. + * + * The ACPI method "AMSG" (stored as 'GSMA' LE) is used to + * proxy commands to ASP. Direct SMN writes to MPASP_C2PMSG_* do not work + * because the BIOS gates host access to those registers and requires all + * ASP mailbox traffic to go through this ACPI method. + * + * The caller must fill adata->asp_mbox_buf with the payload before calling. + * On return the same buffer contains ASP's response fields. + * + * ACPI method signature: AMSG(CommandId, PayloadPhysAddrHi, PayloadPhysAddrLo) + * GET_CARVEOUT_ADDR uses a short 50 ms probe timeout; other commands use 5 s. + */ +static int acp7x_asp_send_cmd(struct snd_sof_dev *sdev, u16 cmd_id) +{ + struct pci_dev *pci = to_pci_dev(sdev->dev); + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct acpi_device *adev = ACPI_COMPANION(&pci->dev); + union acpi_object args[3]; + struct acpi_object_list arg_list = { ARRAY_SIZE(args), args }; + unsigned long timeout; + acpi_handle handle; + acpi_status status; + unsigned int ready_timeout; + u32 ctrl; + int ret, smn_ret; + + if (!adev) { + dev_err(sdev->dev, "ASP cmd 0x%04x: no ACPI companion device\n", cmd_id); + return -ENODEV; + } + + handle = adev->handle; + + /* Short timeout for GET_CARVEOUT_ADDR to fail fast if ASP lacks carveout support. */ + ready_timeout = (cmd_id == ASP_MBOX_CMD_GET_CARVEOUT_ADDR) ? + ASP_MBOX_PROBE_TIMEOUT_US : ASP_MBOX_TIMEOUT_US; + + timeout = jiffies + usecs_to_jiffies(ready_timeout); + ret = -ETIMEDOUT; + do { + smn_ret = amd_smn_read(0, MPASP_C2PMSG_173_REG, &ctrl); + if (smn_ret) { + dev_err(sdev->dev, "ASP mailbox SMN read failed: %d\n", smn_ret); + return smn_ret; + } + if (ctrl & ASP_MBOX_READY_BIT) { + ret = 0; + break; + } + usleep_range(ASP_MBOX_POLL_INTERVAL_US, ASP_MBOX_POLL_INTERVAL_US + 100); + } while (!time_after(jiffies, timeout)); + + if (ret) { + dev_err(sdev->dev, "ASP mailbox not ready before cmd 0x%04x\n", cmd_id); + return -ETIMEDOUT; + } + + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = cmd_id; + args[1].type = ACPI_TYPE_INTEGER; + args[1].integer.value = upper_32_bits(adata->asp_mbox_buf_phys); + args[2].type = ACPI_TYPE_INTEGER; + args[2].integer.value = lower_32_bits(adata->asp_mbox_buf_phys); + + status = acpi_evaluate_object(handle, "AMSG", &arg_list, NULL); + if (ACPI_FAILURE(status)) { + dev_err(sdev->dev, "ASP AMSG cmd 0x%04x: ACPI method failed: %s\n", + cmd_id, acpi_format_exception(status)); + return -EIO; + } + return 0; +} + +/* + * acp7x_configure_carveout_pte - program ATU groups for ACP7.B/7.F carveout. + * + * Programs PAGE_SIZE, BASE_ADDR and PTE entries for each ATU group covering + * the carveout region using 2 MB pages with PAGE_Enable | MALL_Enable flags. + * Must be called after GET_CARVEOUT_ADDR and on every D0 resume since + * acp_init() resets the scratch SRAM and ATU registers. + */ +int acp7x_configure_carveout_pte(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u64 phys_addr; + u32 grp_start = adata->asp_carveout_group_start; + u32 grp_count = adata->asp_carveout_group_count; + u32 total_pages, pages_per_group; + u32 grp, page_in_grp, page_idx; + u32 pte_scratch_offset, page_size_reg, base_addr_reg, reg_val; + u32 pte_lo_addr, pte_hi_addr; + u32 low, high; + u32 abs_grp; + + if (!adata->asp_carveout_base) { + dev_err(sdev->dev, "carveout PTE: no valid carveout address\n"); + return -EINVAL; + } + + if (!grp_count || grp_start < 8 || grp_start + grp_count > 16) { + dev_err(sdev->dev, "carveout PTE: invalid ATU group range %u+%u\n", + grp_start, grp_count); + return -EINVAL; + } + + /* + * ASP is expected to allocate exactly four contiguous 8 MB ATU groups + * for the fixed 32 MB carveout. Warn if the response differs so that + * unexpected firmware behaviour is clearly visible in the log. + */ + if (grp_count != 4) + dev_warn(sdev->dev, "carveout PTE: expected 4 ATU groups, got %u\n", + grp_count); + + total_pages = adata->asp_carveout_size / ACP7X_CARVEOUT_PAGE_SIZE; + pages_per_group = total_pages / grp_count; + page_idx = 0; + + for (grp = 0; grp < grp_count; grp++) { + abs_grp = grp_start + grp; + + /* + * asp_carveout_group_start is the 0-based index from + * __ffs(mall_addr_valid): bit N in MallGroupEntries represents + * hardware GRP_(N+1), so abs_grp is 0-based and GRP number is + * (abs_grp + 1). Example: bits 11-14 set -> abs_grp 11..14 -> + * programs GRP_12..GRP_15 (registers 0xC5C..0xC74). + * ATU_CTRL (0xC40) sits between GRP_8 and GRP_9, adding a + * 4-byte gap for abs_grp >= 8 (i.e. GRP_9 and above). + */ + if (abs_grp < 8) + page_size_reg = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 + abs_grp * 8; + else + page_size_reg = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 + abs_grp * 8 + 4; + + base_addr_reg = page_size_reg + 4; + + pte_scratch_offset = abs_grp * 0x20; + reg_val = desc->sram_pte_offset + pte_scratch_offset; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, page_size_reg, PAGE_SIZE_2M_ENABLE); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base_addr_reg, reg_val | BIT(31)); + + dev_dbg(sdev->dev, + "carveout ATU: abs_grp=%u PAGE_SIZE_reg=0x%x BASE_ADDR_reg=0x%x val=0x%x\n", + abs_grp, page_size_reg, base_addr_reg, + (u32)(reg_val | BIT(31))); + + for (page_in_grp = 0; page_in_grp < pages_per_group; + page_in_grp++, page_idx++) { + pte_lo_addr = ACP_SCRATCH_REG_0 + pte_scratch_offset + (page_in_grp * 8); + pte_hi_addr = pte_lo_addr + 4; + phys_addr = adata->asp_carveout_base + + ((u64)page_idx * ACP7X_CARVEOUT_PAGE_SIZE); + low = lower_32_bits(phys_addr); + high = upper_32_bits(phys_addr) | ACP_ATU_PTE_CARVEOUT_FLAGS; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_lo_addr, low); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_hi_addr, high); + } + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); + + dev_dbg(sdev->dev, + "carveout PTE done: %u groups [%u..%u] covering 0x%llx..0x%llx\n", + grp_count, grp_start, grp_start + grp_count - 1, + adata->asp_carveout_base, + adata->asp_carveout_base + (u64)adata->asp_carveout_size); + return 0; +} +EXPORT_SYMBOL_NS(acp7x_configure_carveout_pte, "SND_SOC_SOF_AMD_COMMON"); + +/* + * acp7x_alloc_asp_payload - allocate a physically contiguous buffer below 4 GB. + * + * ASP accesses memory via the SoC fabric, bypassing the IOMMU, so the buffer + * must be below 4 GB and page_to_phys() must return the real CPU physical + * address. Use __GFP_DMA32/__GFP_DMA as hard zone constraints (not hints) + * to guarantee the physical address fits in 32 bits. + */ +static void *acp7x_alloc_asp_payload(struct snd_sof_dev *sdev, phys_addr_t *phys_out) +{ + struct page *page; + void *vaddr; + + page = alloc_page(GFP_KERNEL | __GFP_DMA32 | __GFP_ZERO); + if (!page) + page = alloc_page(GFP_KERNEL | __GFP_DMA | __GFP_ZERO); + if (!page) { + dev_err(sdev->dev, "ASP mbox: failed to allocate below-4GB payload page\n"); + return NULL; + } + + vaddr = page_address(page); + *phys_out = page_to_phys(page); + + if (*phys_out > 0xFFFFFFFFULL) { + dev_err(sdev->dev, "ASP mbox: page at phys=0x%llx still exceeds 4 GB\n", + (u64)*phys_out); + __free_page(page); + return NULL; + } + + return vaddr; +} + +static void acp7x_free_asp_payload(void *vaddr) +{ + if (vaddr) + __free_page(virt_to_page(vaddr)); +} + +int acp7x_query_asp_carveout(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct asp_get_carveout_payload *co_payload; + phys_addr_t phys; + u32 group_bits, first_bit, count; + int ret = 0; + + co_payload = acp7x_alloc_asp_payload(sdev, &phys); + if (!co_payload) + return -ENOMEM; + + co_payload->cookie = ASP_MBOX_COOKIE; + /* Sentinel: ASP overwrites on success; 0xFFFFFFFF after the call means no carveout */ + co_payload->carveout_size = 0xFFFFFFFF; + co_payload->mall_addr_valid = 0xFFFFFFFF; + co_payload->mall_valid = 0xFFFFFFFF; + + adata->asp_mbox_buf = co_payload; + adata->asp_mbox_buf_phys = phys; + + ret = acp7x_asp_send_cmd(sdev, ASP_MBOX_CMD_GET_CARVEOUT_ADDR); + + dev_dbg(sdev->dev, + "GET_CARVEOUT response: status=0x%x addr=0x%llx size=0x%x mall_valid=0x%x mall_addr_valid=0x%x\n", + co_payload->status, co_payload->carveout_addr, + co_payload->carveout_size, co_payload->mall_valid, + co_payload->mall_addr_valid); + + if (ret) + goto out_free; + + if (co_payload->status || !co_payload->carveout_addr || + !co_payload->carveout_size || co_payload->carveout_size == 0xFFFFFFFF) { + dev_warn(sdev->dev, + "ASP GET_CARVEOUT_ADDR: status=0x%x addr=0x%llx size=0x%x - no carveout\n", + co_payload->status, co_payload->carveout_addr, + co_payload->carveout_size); + ret = -ENODATA; + goto out_free; + } + + if (!IS_ALIGNED(co_payload->carveout_addr, ACP7X_CARVEOUT_PAGE_SIZE) || + co_payload->carveout_size != ACP7X_CARVEOUT_MAX_SIZE) { + dev_warn(sdev->dev, + "ASP carveout: unexpected base alignment or size (addr=0x%llx size=0x%x, expected 0x%x)\n", + co_payload->carveout_addr, co_payload->carveout_size, + ACP7X_CARVEOUT_MAX_SIZE); + ret = -ENODATA; + goto out_free; + } + /* + * Extract ATU group start and count from MallGroupEntries bits[15:0]. + * Scan to find first set bit (start), then count + * contiguous set bits. Example: 0x7800 -> start=11, count=4. + * Defer committing asp_carveout_base/size until all validation passes + * so adata is never left with a non-zero base but zero group_count. + */ + group_bits = co_payload->mall_addr_valid & 0xFFFF; + first_bit = 0; + count = 0; + + if (group_bits) { + first_bit = __ffs(group_bits); + while ((group_bits >> (first_bit + count)) & 1) + count++; + /* Validate contiguous run - reject non-contiguous masks */ + if (group_bits != (((1u << count) - 1) << first_bit)) { + dev_warn(sdev->dev, + "ASP carveout: non-contiguous mall_addr_valid 0x%x unsupported\n", + group_bits); + ret = -ENODATA; + goto out_free; + } + } + if (!count) { + dev_warn(sdev->dev, "ASP carveout: no valid ATU groups in mall_addr_valid\n"); + ret = -ENODATA; + goto out_free; + } + adata->asp_carveout_base = co_payload->carveout_addr; + adata->asp_carveout_size = co_payload->carveout_size; + adata->asp_carveout_group_start = first_bit; + adata->asp_carveout_group_count = count; + + dev_dbg(sdev->dev, "ASP carveout: base=0x%llx size=0x%x grp_start=%u grp_count=%u\n", + adata->asp_carveout_base, adata->asp_carveout_size, + adata->asp_carveout_group_start, adata->asp_carveout_group_count); + +out_free: + adata->asp_mbox_buf = NULL; + adata->asp_mbox_buf_phys = 0; + acp7x_free_asp_payload(co_payload); + return ret; +} +EXPORT_SYMBOL_NS(acp7x_query_asp_carveout, "SND_SOC_SOF_AMD_COMMON"); + +int acp7x_load_firmware_carveout(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + struct acp_dev_data *adata = plat_data->hw_pdata; + struct asp_validate_image_payload *vi_payload; + const char *fw_filename; + struct page *fw_page; + phys_addr_t vi_phys; + u64 dest; + u32 page_count, dma_size; + u32 size_fw_signed; + unsigned int order; + int ret; + + /* + * Carveout query was already done in amd_sof_acp7x_probe() via + * acp7x_query_asp_carveout(). If ASP did not return a valid carveout + * address, fail immediately - no fallback to legacy path. + */ + + /* Program PTEs for the entire carveout region before any DMA into it. + * acp_init() in resume clears the scratch SRAM so PTEs must be reprogrammed + * on every boot including D3->D0 resume. + */ + ret = acp7x_configure_carveout_pte(sdev); + if (ret) + return ret; + + /* + * On D3->D0 resume the raw binary is already in the carveout region + * (placed by ASP during the previous VALIDATE_IMAGE call). Skip the + * filesystem load and VALIDATE_IMAGE - go directly to the data binary + * path. acp_dsp_pre_fw_run() will SHA-DMA from the preserved carveout + * address into DSP IRAM. + * + * On first boot fw_code_carveout_addr is 0 so the condition below is + * false and the full firmware load path executes. + */ + if (adata->fw_code_carveout_addr) + goto load_data_binary; + + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, adata->fw_code_bin); + if (!fw_filename) + return -ENOMEM; + + /* Release any previously loaded firmware before requesting again */ + if (sdev->basefw.fw) { + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + } + + ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev); + kfree(fw_filename); + if (ret < 0) { + dev_err(sdev->dev, "request_firmware %s failed: %d\n", + adata->fw_code_bin, ret); + return ret; + } + + page_count = PAGE_ALIGN(sdev->basefw.fw->size) >> PAGE_SHIFT; + dma_size = page_count * ACP_PAGE_SIZE; + + /* + * Allocate the firmware staging buffer below 4 GB using alloc_pages so + * virt_to_phys() gives the real CPU physical address that ASP can read + * via the SoC fabric (bypassing the IOMMU). dma_alloc_coherent() gives + * an IOMMU-remapped DMA address which ASP cannot use. + * + * get_order() gives the smallest power-of-2 number of pages covering + * dma_size; alloc_pages() allocates that contiguous range. + */ + order = get_order(dma_size); + fw_page = alloc_pages(GFP_KERNEL | __GFP_DMA32 | __GFP_ZERO, order); + if (!fw_page) + fw_page = alloc_pages(GFP_KERNEL | __GFP_DMA | __GFP_ZERO, order); + if (!fw_page) { + dev_err(sdev->dev, "failed to allocate FW staging buffer\n"); + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + adata->bin_buf = page_address(fw_page); + adata->sha_dma_addr = page_to_phys(fw_page); + + if (adata->sha_dma_addr > 0xFFFFFFFFULL) { + dev_err(sdev->dev, + "FW staging buffer at phys=0x%llx exceeds 4 GB\n", + (u64)adata->sha_dma_addr); + __free_pages(fw_page, order); + adata->bin_buf = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + + memcpy(adata->bin_buf, sdev->basefw.fw->data, sdev->basefw.fw->size); + adata->fw_bin_size = sdev->basefw.fw->size; + + if (adata->fw_bin_size <= ACP_IMAGE_HEADER_SIZE) { + dev_err(sdev->dev, "signed FW too small: %u\n", adata->fw_bin_size); + ret = -EINVAL; + goto free_bin; + } + /* + * SizeFWSigned (header[0x14]) is the size of the signed payload + * including the ASP signature trailer. ASP strips the ACP image + * header (0x100 bytes) but preserves the signature, so the raw + * binary deposited in carveout has size = SizeFWSigned and the + * SHA DMA length must equal SizeFWSigned. + */ + size_fw_signed = get_unaligned_le32(adata->bin_buf + ACP_IMAGE_HDR_SIZE_FW_SIGNED_OFF); + + if (size_fw_signed <= ACP_ASP_SIGNATURE_LENGTH || + size_fw_signed > adata->fw_bin_size - ACP_IMAGE_HEADER_SIZE) { + dev_err(sdev->dev, + "invalid SizeFWSigned 0x%x in carveout firmware header\n", + size_fw_signed); + ret = -EINVAL; + goto free_bin; + } + adata->fw_code_raw_size = size_fw_signed; + vi_payload = acp7x_alloc_asp_payload(sdev, &vi_phys); + if (!vi_payload) { + ret = -ENOMEM; + goto free_bin; + } + + vi_payload->cookie = ASP_MBOX_COOKIE; + vi_payload->dest_offset = 0; + /* + * ASP bypasses the CPU IOMMU and accesses memory via the SoC + * fabric directly, so it must be given a real CPU physical + * address for the staging buffer (not an IOMMU/DMA-translated + * address). Use virt_to_phys(bin_buf) which returns the real + * CPU physical address, same as for mailbox payload buffers. + */ + vi_payload->src_phys_addr = (u64)virt_to_phys(adata->bin_buf); + vi_payload->fw_image_size = adata->fw_bin_size; + + adata->asp_mbox_buf = vi_payload; + adata->asp_mbox_buf_phys = vi_phys; + + dev_dbg(sdev->dev, "sending VALIDATE_IMAGE(0x01): src=0x%llx size=0x%x dest_offset=0x%llx phys=0x%llx\n", + vi_payload->src_phys_addr, vi_payload->fw_image_size, + vi_payload->dest_offset, (u64)adata->asp_mbox_buf_phys); + + ret = acp7x_asp_send_cmd(sdev, ASP_MBOX_CMD_VALIDATE_IMAGE); + + /* Read result before freeing payload */ + if (!ret && !vi_payload->status) { + dest = vi_payload->carveout_dest_addr; + + if (dest < adata->asp_carveout_base || + dest - adata->asp_carveout_base + adata->fw_code_raw_size > + adata->asp_carveout_size) { + dev_err(sdev->dev, + "VALIDATE_IMAGE: dest 0x%llx outside carveout window\n", + dest); + ret = -EIO; + } else { + adata->fw_code_carveout_addr = dest; + } + } else if (!ret && vi_payload->status) { + ret = -EIO; + } + + adata->asp_mbox_buf = NULL; + adata->asp_mbox_buf_phys = 0; + acp7x_free_asp_payload(vi_payload); + + if (ret) { + if (ret == -EIO) + dev_err(sdev->dev, "ASP VALIDATE_IMAGE failed\n"); + goto free_bin; + } + + dev_dbg(sdev->dev, "ASP VALIDATE_IMAGE: raw binary at carveout 0x%llx size 0x%x\n", + adata->fw_code_carveout_addr, adata->fw_code_raw_size); + + /* DDR staging buffer is no longer needed - raw binary is in carveout */ + free_pages((unsigned long)adata->bin_buf, get_order(dma_size)); + adata->bin_buf = NULL; + +load_data_binary: + fw_filename = kasprintf(GFP_KERNEL, "%s/%s", + plat_data->fw_filename_prefix, adata->fw_data_bin); + if (!fw_filename) { + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return -ENOMEM; + } + + ret = request_firmware(&adata->fw_dbin, fw_filename, sdev->dev); + kfree(fw_filename); + if (ret < 0) { + dev_err(sdev->dev, "request_firmware %s failed: %d\n", + adata->fw_data_bin, ret); + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; + } + + ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_SRAM, 0, + (void *)adata->fw_dbin->data, + adata->fw_dbin->size); + release_firmware(adata->fw_dbin); + adata->fw_dbin = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; + +free_bin: + free_pages((unsigned long)adata->bin_buf, get_order(dma_size)); + adata->bin_buf = NULL; + release_firmware(sdev->basefw.fw); + sdev->basefw.fw = NULL; + return ret; +} +EXPORT_SYMBOL_NS(acp7x_load_firmware_carveout, "SND_SOC_SOF_AMD_COMMON"); diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 532e15313795..5a21ab8ebb4b 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -26,8 +26,8 @@ #define ACP7X_REG_START 0x1240000 #define ACP7X_REG_END 0x125C000 -static const struct sof_amd_acp_desc acp7x_chip_info = { - .name = "acp7x", +static const struct sof_amd_acp_desc acp7b_chip_info = { + .name = "acp7b", .pgfsm_base = ACP7X_PGFSM_BASE, .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, @@ -40,16 +40,18 @@ static const struct sof_amd_acp_desc acp7x_chip_info = { .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, .reg_start_addr = ACP7X_REG_START, + .sdw_max_link_count = ACP7X_SDW_MAX_MANAGER_COUNT, + .sdw_acpi_dev_addr = SDW_ACPI_ADDR_ACP7X, .reg_end_addr = ACP7X_REG_END, }; -static const struct sof_dev_desc acp7x_desc = { - .machines = snd_soc_acpi_amd_acp7x_sof_machines, +static const struct sof_dev_desc acp7b_desc = { + .machines = snd_soc_acpi_amd_acp7b_sof_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, .irqindex_host_ipc = -1, - .chip_info = &acp7x_chip_info, + .chip_info = &acp7b_chip_info, .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), .ipc_default = SOF_IPC_TYPE_3, .default_fw_path = { @@ -59,30 +61,81 @@ static const struct sof_dev_desc acp7x_desc = { [SOF_IPC_TYPE_3] = "amd/sof-tplg", }, .default_fw_filename = { - [SOF_IPC_TYPE_3] = "sof-acp7x.ri", + [SOF_IPC_TYPE_3] = "sof-acp7b.ri", }, .nocodec_tplg_filename = "sof-acp.tplg", .ops = &sof_acp7x_ops, .ops_init = sof_acp7x_ops_init, }; +static const struct sof_amd_acp_desc acp7f_chip_info = { + .name = "acp7f", + .pgfsm_base = ACP7X_PGFSM_BASE, + .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB, + .ext_intr_cntl = ACP7X_EXTERNAL_INTR_CNTL, + .ext_intr_stat = ACP7X_EXT_INTR_STAT, + .ext_intr_stat1 = ACP7X_EXT_INTR_STAT1, + .dsp_intr_base = ACP7X_DSP_SW_INTR_BASE, + .acp_error_stat = ACP7X_ERROR_STATUS, + .sram_pte_offset = ACP7X_SRAM_PTE_OFFSET, + .hw_semaphore_offset = ACP7X_AXI2DAGB_SEM_0, + .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, + .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, + .reg_start_addr = ACP7X_REG_START, + .sdw_max_link_count = ACP7X_SDW_MAX_MANAGER_COUNT, + .sdw_acpi_dev_addr = SDW_ACPI_ADDR_ACP7X, + .reg_end_addr = ACP7X_REG_END, +}; + +static const struct sof_dev_desc acp7f_desc = { + .machines = snd_soc_acpi_amd_acp7f_sof_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &acp7f_chip_info, + .ipc_supported_mask = BIT(SOF_IPC_TYPE_3), + .ipc_default = SOF_IPC_TYPE_3, + .default_fw_path = { + [SOF_IPC_TYPE_3] = "amd/sof", + }, + .default_tplg_path = { + [SOF_IPC_TYPE_3] = "amd/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC_TYPE_3] = "sof-acp7f.ri", + }, + .nocodec_tplg_filename = "sof-acp.tplg", + .ops = &sof_acp7x_ops, + .ops_init = sof_acp7x_ops_init, +}; + +static const struct pci_device_id acp7b_pci_id = { + PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&acp7b_desc, +}; + +static const struct pci_device_id acp7f_pci_id = { + PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&acp7f_desc, +}; + static int acp7x_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { unsigned int flag; - switch (pci->revision) { - case ACP7B_PCI_ID: - case ACP7F_PCI_ID: - break; - default: - return -ENODEV; - } - flag = snd_amd_acp_find_config(pci); if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) return -ENODEV; - return sof_pci_probe(pci, pci_id); + switch (pci->revision) { + case ACP7B_PCI_ID: + return sof_pci_probe(pci, &acp7b_pci_id); + case ACP7F_PCI_ID: + return sof_pci_probe(pci, &acp7f_pci_id); + default: + return -ENODEV; + } } static void acp7x_pci_remove(struct pci_dev *pci) @@ -92,8 +145,7 @@ static void acp7x_pci_remove(struct pci_dev *pci) /* PCI IDs */ static const struct pci_device_id acp7x_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), - .driver_data = (unsigned long)&acp7x_desc}, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID) }, { 0, } }; MODULE_DEVICE_TABLE(pci, acp7x_pci_ids); diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 2d394389c945..41747ef0e432 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -463,11 +463,12 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); - /* set up platform component driver */ - snd_sof_new_platform_drv(sdev); - if (sdev->dspless_mode_selected) { sof_set_fw_state(sdev, SOF_DSPLESS_MODE); + + /* set up platform component driver */ + snd_sof_new_platform_drv(sdev); + goto skip_dsp_init; } @@ -492,6 +493,9 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto ipc_err; } + /* set up platform component driver after initializing the IPC ops */ + snd_sof_new_platform_drv(sdev); + /* load the firmware */ ret = snd_sof_load_firmware(sdev); if (ret < 0) { diff --git a/sound/soc/sof/imx/imx-common.c b/sound/soc/sof/imx/imx-common.c index 7a03c8cc5dd4..436fe49246ba 100644 --- a/sound/soc/sof/imx/imx-common.c +++ b/sound/soc/sof/imx/imx-common.c @@ -34,6 +34,7 @@ void imx8_get_registers(struct snd_sof_dev *sdev, /* first read registers */ sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + xoops->plat_hdr.numaregs = 0; /* then get panic info */ if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) { diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index 7e9eab2e3034..0820724dbb34 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -36,19 +36,23 @@ struct imx8m_chip_data { void __iomem *dap; - struct regmap *regmap; struct reset_control *run_stall; }; static int imx8_shutdown(struct snd_sof_dev *sdev) { + int ret; /* * Force the DSP to stall. After the firmware image is loaded, * the stall will be removed during run() by a matching * imx_sc_pm_cpu_start() call. */ - imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, false, - RESET_VECTOR_VADDR); + ret = imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, false, + RESET_VECTOR_VADDR); + if (ret < 0) { + dev_err(sdev->dev, "Error stalling DSP core %d\n", ret); + return ret; + } return 0; } @@ -89,8 +93,12 @@ static int imx8x_run(struct snd_sof_dev *sdev) return ret; } - imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true, - RESET_VECTOR_VADDR); + ret = imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true, + RESET_VECTOR_VADDR); + if (ret < 0) { + dev_err(sdev->dev, "Error starting DSP %d\n", ret); + return ret; + } return 0; } @@ -106,8 +114,12 @@ static int imx8_run(struct snd_sof_dev *sdev) return ret; } - imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true, - RESET_VECTOR_VADDR); + ret = imx_sc_pm_cpu_start(get_chip_pdata(sdev), IMX_SC_R_DSP, true, + RESET_VECTOR_VADDR); + if (ret < 0) { + dev_err(sdev->dev, "Error starting DSP %d\n", ret); + return ret; + } return 0; } diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index 915abbef398d..02b41e4ccab2 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -186,8 +186,6 @@ config SND_SOC_SOF_INTEL_ICL tristate select SND_SOC_SOF_HDA_GENERIC select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC3 - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_CNL config SND_SOC_SOF_ICELAKE @@ -214,9 +212,8 @@ config SND_SOC_SOF_INTEL_TGL tristate select SND_SOC_SOF_HDA_GENERIC select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC3 - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_CNL + select SND_SOC_SOF_COMPRESS config SND_SOC_SOF_TIGERLAKE tristate "SOF support for Tigerlake" @@ -253,6 +250,7 @@ config SND_SOC_SOF_INTEL_MTL select SND_SOC_SOF_HDA_GENERIC select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE select SND_SOC_SOF_IPC4 + select SND_SOC_SOF_COMPRESS config SND_SOC_SOF_METEORLAKE tristate "SOF support for Meteorlake" @@ -268,7 +266,6 @@ config SND_SOC_SOF_INTEL_LNL tristate select SND_SOC_SOF_HDA_GENERIC select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_MTL config SND_SOC_SOF_LUNARLAKE @@ -285,7 +282,6 @@ config SND_SOC_SOF_INTEL_PTL tristate select SND_SOC_SOF_HDA_COMMON select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_LNL config SND_SOC_SOF_PANTHERLAKE @@ -302,7 +298,6 @@ config SND_SOC_SOF_INTEL_NVL tristate select SND_SOC_SOF_HDA_COMMON select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE - select SND_SOC_SOF_IPC4 select SND_SOC_SOF_INTEL_PTL config SND_SOC_SOF_NOVALAKE diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index b0072601181e..753da09301fa 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -59,6 +59,7 @@ int sof_apl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5; diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 660c1475e5a4..ec33cd18e669 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -406,6 +406,7 @@ int sof_cnl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_8; diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c index fd371850b0d6..91f7ca22a4a7 100644 --- a/sound/soc/sof/intel/hda-codec.c +++ b/sound/soc/sof/intel/hda-codec.c @@ -89,7 +89,7 @@ void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev, bool enable) list_for_each_codec(codec, hbus) { /* only set WAKEEN when needed for HDaudio codecs */ mask |= BIT(codec->core.addr); - if (codec->jacktbl.used) + if (codec->jacktbl_used) val |= BIT(codec->core.addr); } } else { @@ -118,7 +118,7 @@ void hda_codec_jack_check(struct snd_sof_dev *sdev) * Wake up all jack-detecting codecs regardless whether an event * has been recorded in STATESTS */ - if (codec->jacktbl.used) + if (codec->jacktbl_used) pm_request_resume(&codec->core.dev); } EXPORT_SYMBOL_NS_GPL(hda_codec_jack_check, "SND_SOC_SOF_HDA_AUDIO_CODEC"); diff --git a/sound/soc/sof/intel/hda-common-ops.c b/sound/soc/sof/intel/hda-common-ops.c index 746b426b1329..aa91f2b6fd65 100644 --- a/sound/soc/sof/intel/hda-common-ops.c +++ b/sound/soc/sof/intel/hda-common-ops.c @@ -57,6 +57,14 @@ const struct snd_sof_dsp_ops sof_hda_common_ops = { .pcm_pointer = hda_dsp_pcm_pointer, .pcm_ack = hda_dsp_pcm_ack, + .compr_open = hda_dsp_compr_open, + .compr_hw_params = hda_dsp_compr_hw_params, + .compr_hw_free = hda_dsp_stream_compr_hw_free, + .compr_close = hda_dsp_compr_close, + .compr_trigger = hda_dsp_compr_trigger, + .compr_pointer = hda_dsp_compr_pointer, + .compr_get_dai_frame_counter = hda_dsp_compr_get_stream_llp, + .get_dai_frame_counter = hda_dsp_get_stream_llp, .get_host_byte_counter = hda_dsp_get_stream_ldp, diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index bb44d4f8a4da..c8729b6d82bd 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -760,8 +760,20 @@ void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4 && !hda_use_tplg_nhlt) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct snd_ipc4_nhlt *entry; - ipc4_data->nhlt = intel_nhlt_init(sdev->dev); + entry = devm_kzalloc(sdev->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return; + + entry->nhlt = intel_nhlt_init(sdev->dev); + if (!entry->nhlt) { + devm_kfree(sdev->dev, entry); + return; + } + + entry->from_acpi = true; + list_add(&entry->list, &ipc4_data->nhlt_list); } } EXPORT_SYMBOL_NS(hda_set_dai_drv_ops, "SND_SOC_SOF_INTEL_HDA_COMMON"); @@ -770,9 +782,17 @@ void hda_ops_free(struct snd_sof_dev *sdev) { if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct snd_ipc4_nhlt *entry; + struct snd_ipc4_nhlt *tmp; - if (!hda_use_tplg_nhlt) - intel_nhlt_free(ipc4_data->nhlt); + if (!hda_use_tplg_nhlt) { + list_for_each_entry_safe(entry, tmp, &ipc4_data->nhlt_list, list) { + if (entry->from_acpi && entry->nhlt) + intel_nhlt_free(entry->nhlt); + + list_del(&entry->list); + } + } kfree(sdev->private); sdev->private = NULL; diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c index 16a364072821..83efc9bdf418 100644 --- a/sound/soc/sof/intel/hda-pcm.c +++ b/sound/soc/sof/intel/hda-pcm.c @@ -151,6 +151,71 @@ int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, } EXPORT_SYMBOL_NS(hda_dsp_pcm_hw_params, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_compr_hw_params(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_sof_platform_stream_params *platform_params) +{ + struct hdac_stream *hstream = cstream->runtime->private_data; + struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct snd_dma_buffer *dmab; + u32 bits, rate; + int bps; + int ret; + + hstream->cstream = cstream; + dmab = cstream->runtime->dma_buffer_p; + + /* Use correct format based on the used codec */ + switch (params->codec.id) { + case SND_AUDIOCODEC_PCM: + bps = snd_pcm_format_physical_width((snd_pcm_format_t)params->codec.format); + break; + case SND_AUDIOCODEC_VORBIS: + bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S16_LE); + break; + case SND_AUDIOCODEC_FLAC: + { + struct snd_dec_flac *dec_flac = ¶ms->codec.options.flac_d; + + if (dec_flac->sample_size == 16) + bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S16_LE); + else + bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32_LE); + break; + } + default: + bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32_LE); + } + + if (bps < 0) + return bps; + bits = hda_dsp_get_bits(sdev, bps); + rate = hda_dsp_get_mult_div(sdev, params->codec.sample_rate); + + hstream->format_val = rate | bits | (params->codec.ch_out - 1); + hstream->bufsize = cstream->runtime->buffer_size; + hstream->period_bytes = cstream->runtime->fragment_size; + hstream->no_period_wakeup = false; + + /* params is not used so pass NULL */ + dmab = cstream->runtime->dma_buffer_p; + ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, NULL); + if (ret < 0) { + dev_err(sdev->dev, "%s: hdac prepare failed: %d\n", __func__, ret); + return ret; + } + + if (hda) + platform_params->no_ipc_position = hda->no_ipc_position; + + platform_params->stream_tag = hstream->stream_tag; + + return 0; +} +EXPORT_SYMBOL_NS(hda_dsp_compr_hw_params, "SND_SOC_SOF_INTEL_HDA_COMMON"); + /* update SPIB register with appl position */ int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { @@ -184,6 +249,16 @@ int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev, } EXPORT_SYMBOL_NS(hda_dsp_pcm_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_compr_trigger(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd) +{ + struct hdac_stream *hstream = cstream->runtime->private_data; + struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); + + return hda_dsp_stream_trigger(sdev, hext_stream, cmd); +} +EXPORT_SYMBOL_NS(hda_dsp_compr_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON"); + snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { @@ -216,6 +291,20 @@ snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, } EXPORT_SYMBOL_NS(hda_dsp_pcm_pointer, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_compr_pointer(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp) +{ + struct hdac_stream *hstream = cstream->runtime->private_data; + + /* hstream->curr_pos is updated when we receive the ioc */ + tstamp->copied_total = hstream->curr_pos; + + tstamp->byte_offset = hda_dsp_stream_get_position(hstream, cstream->direction, true); + + return 0; +} +EXPORT_SYMBOL_NS(hda_dsp_compr_pointer, "SND_SOC_SOF_INTEL_HDA_COMMON"); + int hda_dsp_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { @@ -342,6 +431,41 @@ int hda_dsp_pcm_open(struct snd_sof_dev *sdev, } EXPORT_SYMBOL_NS(hda_dsp_pcm_open, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_compr_open(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *scomp = sdev->component; + struct hdac_ext_stream *dsp_stream; + struct snd_sof_pcm *spcm; + int direction = cstream->direction; + + spcm = snd_sof_find_spcm_dai(scomp, rtd); + if (!spcm) { + dev_err(sdev->dev, "%s: can't find PCM with DAI ID %d\n", + __func__, rtd->dai_link->id); + return -EINVAL; + } + + dsp_stream = hda_dsp_stream_get(sdev, direction, 0); + if (!dsp_stream) { + dev_err(sdev->dev, "%s: no stream available\n", __func__); + return -ENODEV; + } + + /* binding compr stream to hda stream */ + cstream->runtime->private_data = &dsp_stream->hstream; + + /* + * Reset the llp cache values (they are used for LLP compensation in + * case the counter is not reset) + */ + dsp_stream->pplcllpl = 0; + dsp_stream->pplcllpu = 0; + + return 0; +} +EXPORT_SYMBOL_NS(hda_dsp_compr_open, "SND_SOC_SOF_INTEL_HDA_COMMON"); + int hda_dsp_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { @@ -361,3 +485,21 @@ int hda_dsp_pcm_close(struct snd_sof_dev *sdev, return 0; } EXPORT_SYMBOL_NS(hda_dsp_pcm_close, "SND_SOC_SOF_INTEL_HDA_COMMON"); + +int hda_dsp_compr_close(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + struct hdac_stream *hstream = cstream->runtime->private_data; + int direction = cstream->direction; + int ret; + + ret = hda_dsp_stream_put(sdev, direction, hstream->stream_tag); + if (ret) + return -ENODEV; + + /* unbinding compress stream to hda stream */ + hstream->cstream = NULL; + cstream->runtime->private_data = NULL; + + return 0; +} +EXPORT_SYMBOL_NS(hda_dsp_compr_close, "SND_SOC_SOF_INTEL_HDA_COMMON"); diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index c95230487a1b..bdd4831e050c 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -293,9 +293,8 @@ static int _hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stre { const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata); struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + struct hdac_ext_stream *hext_stream_match = NULL; struct hdac_bus *bus = sof_to_bus(sdev); - struct sof_intel_hda_stream *hda_stream; - struct hdac_ext_stream *hext_stream; struct hdac_ext_stream *link_stream; struct hdac_stream *s; bool dmi_l1_enable = true; @@ -308,6 +307,9 @@ static int _hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stre * that are DMI L1 incompatible. */ list_for_each_entry(s, &bus->stream_list, list) { + struct sof_intel_hda_stream *hda_stream; + struct hdac_ext_stream *hext_stream; + hext_stream = stream_to_hdac_ext_stream(s); hda_stream = container_of(hext_stream, struct sof_intel_hda_stream, hext_stream); @@ -315,8 +317,10 @@ static int _hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stre continue; if (s->direction == direction && s->stream_tag == stream_tag) { + hext_stream_match = hext_stream; s->opened = false; found = true; + s->curr_pos = 0; if (pair) link_stream = hext_stream; } else if (!(hda_stream->flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) { @@ -324,6 +328,20 @@ static int _hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stre } } + if (!sdev->dspless_mode_selected && !pair && hext_stream_match) { + /* + * Couple host and link DMA if link DMA is unused + * In case of channel pair the snd_hdac_ext_stream_release() will + * take care of coupling te HDA channel. + */ + if (!hext_stream_match->link_locked) { + u32 mask = BIT(hext_stream_match->hstream.index); + + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, + SOF_HDA_REG_PP_PPCTL, mask, 0); + } + } + spin_unlock_irq(&bus->reg_lock); /* Enable DMI L1 if permitted */ @@ -754,13 +772,12 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, return ret; } -int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream) +static int _hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, + struct hdac_stream *hstream) { - struct hdac_stream *hstream = substream->runtime->private_data; struct hdac_ext_stream *hext_stream = container_of(hstream, - struct hdac_ext_stream, - hstream); + struct hdac_ext_stream, + hstream); int ret; ret = hda_dsp_stream_reset(sdev, hstream); @@ -785,8 +802,21 @@ int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, return 0; } + +int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + return _hda_dsp_stream_hw_free(sdev, substream->runtime->private_data); +} EXPORT_SYMBOL_NS(hda_dsp_stream_hw_free, "SND_SOC_SOF_INTEL_HDA_COMMON"); +int hda_dsp_stream_compr_hw_free(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream) +{ + return _hda_dsp_stream_hw_free(sdev, cstream->runtime->private_data); +} +EXPORT_SYMBOL_NS(hda_dsp_stream_compr_hw_free, "SND_SOC_SOF_INTEL_HDA_COMMON"); + bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev) { struct hdac_bus *bus = sof_to_bus(sdev); @@ -1161,19 +1191,9 @@ EXPORT_SYMBOL_NS(hda_dsp_stream_get_position, "SND_SOC_SOF_INTEL_HDA_COMMON"); #define merge_u64(u32_u, u32_l) (((u64)(u32_u) << 32) | (u32_l)) -/** - * hda_dsp_get_stream_llp - Retrieve the LLP (Linear Link Position) of the stream - * @sdev: SOF device - * @component: ASoC component - * @substream: PCM substream - * - * Returns the raw Linear Link Position value - */ -u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, - struct snd_soc_component *component, - struct snd_pcm_substream *substream) +static u64 hda_dsp_get_llp(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime *rtd, int dir) { - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_pcm_runtime *be_rtd = NULL; struct hdac_ext_stream *hext_stream; struct snd_soc_dai *cpu_dai; @@ -1184,7 +1204,7 @@ u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, * The LLP needs to be read from the Link DMA used for this FE as it is * allowed to use any combination of Link and Host channels */ - for_each_dpcm_be(rtd, substream->stream, dpcm) { + for_each_dpcm_be(rtd, dir, dpcm) { if (dpcm->fe != rtd) continue; @@ -1198,7 +1218,7 @@ u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, if (!cpu_dai) return 0; - hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + hext_stream = snd_soc_dai_dma_data_get(cpu_dai, dir); if (!hext_stream) return 0; @@ -1222,8 +1242,37 @@ u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, return merge_u64(llp_u, llp_l); } + +/** + * hda_dsp_get_stream_llp - Retrieve the LLP (Linear Link Position) of the stream + * @sdev: SOF device + * @component: ASoC component + * @substream: PCM substream + * + * Returns the raw Linear Link Position value + */ +u64 hda_dsp_get_stream_llp(struct snd_sof_dev *sdev, + struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return hda_dsp_get_llp(sdev, snd_soc_substream_to_rtd(substream), + substream->stream); +} EXPORT_SYMBOL_NS(hda_dsp_get_stream_llp, "SND_SOC_SOF_INTEL_HDA_COMMON"); +/** + * hda_dsp_compr_get_stream_llp - Retrieve the LLP (Linear Link Position) of the stream + * @sdev: SOF device + * @cstream: Compress stream + * + * Returns the raw Linear Link Position value + */ +u64 hda_dsp_compr_get_stream_llp(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + return hda_dsp_get_llp(sdev, cstream->private_data, cstream->direction); +} +EXPORT_SYMBOL_NS(hda_dsp_compr_get_stream_llp, "SND_SOC_SOF_INTEL_HDA_COMMON"); + /** * hda_dsp_get_stream_ldp - Retrieve the LDP (Linear DMA Position) of the stream * @sdev: SOF device diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 1609589929a1..701578ec35cf 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -683,6 +683,21 @@ snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); +int hda_dsp_compr_open(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); +int hda_dsp_compr_close(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); +int hda_dsp_compr_hw_params(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_sof_platform_stream_params *platform_params); +int hda_dsp_stream_compr_hw_free(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream); +int hda_dsp_compr_trigger(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd); +int hda_dsp_compr_pointer(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp); +u64 hda_dsp_compr_get_stream_llp(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream); + /* * DSP Stream Operations. */ diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index 549bb4ca73e1..196caba14be0 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -131,6 +131,7 @@ int sof_icl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 3d67d6777f1b..2506506f813a 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -762,6 +762,7 @@ int sof_mtl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; diff --git a/sound/soc/sof/intel/skl.c b/sound/soc/sof/intel/skl.c index 90519ebd3168..f831143a7afc 100644 --- a/sound/soc/sof/intel/skl.c +++ b/sound/soc/sof/intel/skl.c @@ -67,6 +67,7 @@ int sof_skl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET_CAVS_1_5; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5; diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 7936361e2e39..09a8a4a2b017 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -95,6 +95,7 @@ int sof_tgl_ops_init(struct snd_sof_dev *sdev) return -ENOMEM; ipc4_data = sdev->private; + INIT_LIST_HEAD(&ipc4_data->nhlt_list); ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/ipc3-compress.c similarity index 66% rename from sound/soc/sof/compress.c rename to sound/soc/sof/ipc3-compress.c index 93f2376585db..115d454bcaf5 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/ipc3-compress.c @@ -12,88 +12,8 @@ #include "sof-utils.h" #include "ops.h" -static void sof_set_transferred_bytes(struct sof_compr_stream *sstream, - u64 host_pos, u64 buffer_size) -{ - u64 prev_pos; - unsigned int copied; - - div64_u64_rem(sstream->copied_total, buffer_size, &prev_pos); - - if (host_pos < prev_pos) - copied = (buffer_size - prev_pos) + host_pos; - else - copied = host_pos - prev_pos; - - sstream->copied_total += copied; -} - -static void snd_sof_compr_fragment_elapsed_work(struct work_struct *work) -{ - struct snd_sof_pcm_stream *sps = - container_of(work, struct snd_sof_pcm_stream, - period_elapsed_work); - - snd_compr_fragment_elapsed(sps->cstream); -} - -void snd_sof_compr_init_elapsed_work(struct work_struct *work) -{ - INIT_WORK(work, snd_sof_compr_fragment_elapsed_work); -} - -/* - * sof compr fragment elapse, this could be called in irq thread context - */ -void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) -{ - struct snd_soc_pcm_runtime *rtd; - struct snd_compr_runtime *crtd; - struct snd_soc_component *component; - struct sof_compr_stream *sstream; - struct snd_sof_pcm *spcm; - - if (!cstream) - return; - - rtd = cstream->private_data; - crtd = cstream->runtime; - sstream = crtd->private_data; - component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); - - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) { - dev_err(component->dev, - "fragment elapsed called for unknown stream!\n"); - return; - } - - sof_set_transferred_bytes(sstream, spcm->stream[cstream->direction].posn.host_posn, - crtd->buffer_size); - - /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */ - schedule_work(&spcm->stream[cstream->direction].period_elapsed_work); -} - -static int create_page_table(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - unsigned char *dma_area, size_t size) -{ - struct snd_dma_buffer *dmab = cstream->runtime->dma_buffer_p; - struct snd_soc_pcm_runtime *rtd = cstream->private_data; - int dir = cstream->direction; - struct snd_sof_pcm *spcm; - - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) - return -EINVAL; - - return snd_sof_create_page_table(component->dev, dmab, - spcm->stream[dir].page_table.area, size); -} - -static int sof_compr_open(struct snd_soc_component *component, - struct snd_compr_stream *cstream) +static int sof_ipc3_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_compr_runtime *crtd = cstream->runtime; @@ -128,8 +48,8 @@ static int sof_compr_open(struct snd_soc_component *component, return 0; } -static int sof_compr_free(struct snd_soc_component *component, - struct snd_compr_stream *cstream) +static int sof_ipc3_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct sof_compr_stream *sstream = cstream->runtime->private_data; @@ -159,8 +79,9 @@ static int sof_compr_free(struct snd_soc_component *component, return ret; } -static int sof_compr_set_params(struct snd_soc_component *component, - struct snd_compr_stream *cstream, struct snd_compr_params *params) +static int sof_ipc3_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_params *params) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = cstream->private_data; @@ -213,7 +134,7 @@ static int sof_compr_set_params(struct snd_soc_component *component, if (ret < 0) goto out; - ret = create_page_table(component, cstream, crtd->dma_area, crtd->dma_bytes); + ret = snd_sof_compr_create_page_table(component, cstream, crtd->dma_area, crtd->dma_bytes); if (ret < 0) goto out; @@ -265,8 +186,9 @@ static int sof_compr_set_params(struct snd_soc_component *component, return ret; } -static int sof_compr_get_params(struct snd_soc_component *component, - struct snd_compr_stream *cstream, struct snd_codec *params) +static int sof_ipc3_compr_get_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_codec *params) { struct sof_compr_stream *sstream = cstream->runtime->private_data; @@ -275,8 +197,8 @@ static int sof_compr_get_params(struct snd_soc_component *component, return 0; } -static int sof_compr_trigger(struct snd_soc_component *component, - struct snd_compr_stream *cstream, int cmd) +static int sof_ipc3_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *cstream, int cmd) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = cstream->private_data; @@ -312,8 +234,8 @@ static int sof_compr_trigger(struct snd_soc_component *component, return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream)); } -static int sof_compr_copy_playback(struct snd_compr_runtime *rtd, - char __user *buf, size_t count) +static int sof_ipc3_compr_copy_playback(struct snd_compr_runtime *rtd, + char __user *buf, size_t count) { void *ptr; unsigned int offset, n; @@ -333,8 +255,8 @@ static int sof_compr_copy_playback(struct snd_compr_runtime *rtd, return count - ret; } -static int sof_compr_copy_capture(struct snd_compr_runtime *rtd, - char __user *buf, size_t count) +static int sof_ipc3_compr_copy_capture(struct snd_compr_runtime *rtd, + char __user *buf, size_t count) { void *ptr; unsigned int offset, n; @@ -354,9 +276,9 @@ static int sof_compr_copy_capture(struct snd_compr_runtime *rtd, return count - ret; } -static int sof_compr_copy(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - char __user *buf, size_t count) +static int sof_ipc3_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) { struct snd_compr_runtime *rtd = cstream->runtime; @@ -364,14 +286,14 @@ static int sof_compr_copy(struct snd_soc_component *component, count = rtd->buffer_size; if (cstream->direction == SND_COMPRESS_PLAYBACK) - return sof_compr_copy_playback(rtd, buf, count); + return sof_ipc3_compr_copy_playback(rtd, buf, count); else - return sof_compr_copy_capture(rtd, buf, count); + return sof_ipc3_compr_copy_capture(rtd, buf, count); } -static int sof_compr_pointer(struct snd_soc_component *component, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp64 *tstamp) +static int sof_ipc3_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp) { struct snd_sof_pcm *spcm; struct snd_soc_pcm_runtime *rtd = cstream->private_data; @@ -392,13 +314,12 @@ static int sof_compr_pointer(struct snd_soc_component *component, return 0; } -struct snd_compress_ops sof_compressed_ops = { - .open = sof_compr_open, - .free = sof_compr_free, - .set_params = sof_compr_set_params, - .get_params = sof_compr_get_params, - .trigger = sof_compr_trigger, - .pointer = sof_compr_pointer, - .copy = sof_compr_copy, +const struct snd_compress_ops sof_ipc3_compressed_ops = { + .open = sof_ipc3_compr_open, + .free = sof_ipc3_compr_free, + .set_params = sof_ipc3_compr_set_params, + .get_params = sof_ipc3_compr_get_params, + .trigger = sof_ipc3_compr_trigger, + .pointer = sof_ipc3_compr_pointer, + .copy = sof_ipc3_compr_copy, }; -EXPORT_SYMBOL(sof_compressed_ops); diff --git a/sound/soc/sof/ipc3-dtrace.c b/sound/soc/sof/ipc3-dtrace.c index 22053357731a..e8c998c09dc3 100644 --- a/sound/soc/sof/ipc3-dtrace.c +++ b/sound/soc/sof/ipc3-dtrace.c @@ -534,7 +534,7 @@ static int ipc3_dtrace_init(struct snd_sof_dev *sdev) /* create compressed page table for audio firmware */ ret = snd_sof_create_page_table(sdev->dev, &priv->dmatb, - priv->dmatp.area, priv->dmatb.bytes); + &priv->dmatp, priv->dmatb.bytes); if (ret < 0) goto table_err; diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c index 143bf0fe8dd9..42f93e596598 100644 --- a/sound/soc/sof/ipc3-pcm.c +++ b/sound/soc/sof/ipc3-pcm.c @@ -14,23 +14,18 @@ #include "sof-audio.h" static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component, - struct snd_pcm_substream *substream) + struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int dir) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sof_ipc_stream stream; - struct snd_sof_pcm *spcm; - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) - return -EINVAL; - - if (!spcm->prepared[substream->stream]) + if (!spcm->prepared[dir]) return 0; stream.hdr.size = sizeof(stream); stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; - stream.comp_id = spcm->stream[substream->stream].comp_id; + stream.comp_id = spcm->stream[dir].comp_id; /* send IPC to the DSP */ return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream)); @@ -141,20 +136,15 @@ static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component, } static int sof_ipc3_pcm_trigger(struct snd_soc_component *component, - struct snd_pcm_substream *substream, int cmd) + struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int cmd, int dir) { - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct sof_ipc_stream stream; - struct snd_sof_pcm *spcm; - - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) - return -EINVAL; stream.hdr.size = sizeof(stream); stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG; - stream.comp_id = spcm->stream[substream->stream].comp_id; + stream.comp_id = spcm->stream[dir].comp_id; switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_PUSH: @@ -172,7 +162,7 @@ static int sof_ipc3_pcm_trigger(struct snd_soc_component *component, stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; break; default: - spcm_err(spcm, substream->stream, "Unhandled trigger cmd %d\n", cmd); + spcm_err(spcm, dir, "Unhandled trigger cmd %d\n", cmd); return -EINVAL; } @@ -442,4 +432,7 @@ const struct sof_ipc_pcm_ops ipc3_pcm_ops = { .dai_link_fixup = sof_ipc3_pcm_dai_link_fixup, .reset_hw_params_during_stop = true, .d0i3_supported_in_s0ix = true, +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) + .compress_ops = &sof_ipc3_compressed_ops, +#endif }; diff --git a/sound/soc/sof/ipc3-priv.h b/sound/soc/sof/ipc3-priv.h index 866c5f67b91a..f95957453ab8 100644 --- a/sound/soc/sof/ipc3-priv.h +++ b/sound/soc/sof/ipc3-priv.h @@ -17,6 +17,9 @@ extern const struct sof_ipc_tplg_ops ipc3_tplg_ops; extern const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops; extern const struct sof_ipc_fw_loader_ops ipc3_loader_ops; extern const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) +extern const struct snd_compress_ops sof_ipc3_compressed_ops; +#endif /* helpers for fw_ready and ext_manifest parsing */ int sof_ipc3_get_ext_windows(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/ipc4-compress.c b/sound/soc/sof/ipc4-compress.c new file mode 100644 index 000000000000..97a4ec751905 --- /dev/null +++ b/sound/soc/sof/ipc4-compress.c @@ -0,0 +1,823 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright 2026 Intel Corporation. All rights reserved. +// +#include +#include +#include +#include +#include "sof-audio.h" +#include "sof-priv.h" +#include "sof-utils.h" +#include "ops.h" +#include "ipc4-priv.h" +#include "ipc4-topology.h" +#include "ipc4-fw-reg.h" + +/* Maximum processing size of the decoder/encoder is 2048 bytes */ +#define SOF_IPC4_COMPR_MAX_PROCESSING_SIZE (SZ_2K) + +#define SOF_IPC4_COMPR_MIN_FRAGMENTS 3 +#define SOF_IPC4_COMPR_MAX_FRAGMENT_SIZE (SZ_128K) +#define SOF_IPC4_COMPR_MAX_FRAGMENTS 64 +#define SOF_IPC4_COMPR_MIN_BUFFER_SIZE(min_size) ((min_size) * \ + SOF_IPC4_COMPR_MIN_FRAGMENTS) + +struct sof_ipc4_compr_init_data { + struct snd_codec codec; + u32 dir; +} __packed __aligned(4); + +static struct sof_ipc4_process * +sof_ipc4_compr_get_module(struct snd_sof_pcm *spcm, int dir) +{ + int id = dir ? snd_soc_dapm_encoder : snd_soc_dapm_decoder; + struct snd_sof_pcm_stream *sps = &spcm->stream[dir]; + struct snd_soc_dapm_widget *widget; + int i; + + /* Find the (first) compr module in path */ + for_each_dapm_widgets(sps->list, i, widget) { + struct snd_sof_widget *swidget = widget->dobj.private; + + if (!swidget) + continue; + + if (swidget->widget->id == id) + return swidget->private; + } + + return NULL; +} + +static u32 sof_ipc4_compr_calc_min_fragment_size(struct snd_sof_pcm_stream *sps) +{ + u32 host_buffer_estimate; + + /* Estimated host DMA buffer size based on stereo S32_LE, 48KHz */ + host_buffer_estimate = snd_pcm_format_size(SNDRV_PCM_FORMAT_S32_LE, 2 * 48); + host_buffer_estimate *= sps->dsp_max_burst_size_in_ms; + /* + * The minimum fragment size must not be smaller than the processing size + * or in case of deep buffer on host side, the host DMA buffer size. + */ + return max(SOF_IPC4_COMPR_MAX_PROCESSING_SIZE, host_buffer_estimate); +} + +/* The buffer must be described by the page table allocated for the stream */ +static u64 sof_ipc4_compr_calc_max_buffer_size(struct snd_sof_pcm_stream *sps) +{ + return (u64)SOF_PAGE_TABLE_MAX_PFNS(sps->page_table.bytes) * PAGE_SIZE; +} + +static int sof_ipc4_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_sof_pcm *spcm; + int dir, ret; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + dir = cstream->direction; + + if (spcm->stream[dir].cstream) + return -EBUSY; + + spcm_dbg(spcm, dir, "Entry: open\n"); + + ret = snd_sof_compr_platform_open(sdev, cstream); + if (ret < 0) { + spcm_err(spcm, dir, "platform compress open failed %d\n", ret); + return ret; + } + + spcm->stream[dir].cstream = cstream; + spcm->stream[dir].posn.host_posn = 0; + spcm->stream[dir].posn.dai_posn = 0; + spcm->prepared[dir] = false; + spcm->pending_stop[dir] = false; + spcm->compr_started[dir] = false; + + return 0; +} + +static int sof_ipc4_compr_stream_free(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, + struct snd_compr_stream *cstream) +{ + const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); + int dir = cstream->direction; + int ret = 0; + int err = 0; + + if (spcm->prepared[dir]) { + if (spcm->pending_stop[dir]) + pcm_ops->trigger(sdev->component, NULL, spcm, + SNDRV_PCM_TRIGGER_STOP, dir); + + snd_sof_compr_platform_trigger(sdev, cstream, + SNDRV_PCM_TRIGGER_STOP); + + err = pcm_ops->hw_free(sdev->component, NULL, spcm, dir); + if (err < 0) + spcm_err(spcm, dir, "pcm_ops->hw_free failed %d\n", err); + } + + spcm->prepared[dir] = false; + spcm->pending_stop[dir] = false; + spcm->compr_started[dir] = false; + spcm->stream[dir].cstream = NULL; + + /* reset the DMA */ + ret = snd_sof_compr_platform_hw_free(sdev, cstream); + if (ret < 0) { + spcm_err(spcm, dir, "platform hw free failed %d\n", ret); + if (!err) + err = ret; + } + + /* free widget list */ + ret = sof_widget_list_free(sdev, spcm, dir); + if (ret < 0 && err == 0) { + spcm_err(spcm, dir, "sof_widget_list_free failed %d\n", ret); + err = ret; + } + + return err; +} + +static int sof_ipc4_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_sof_pcm *spcm; + int ret, err; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + spcm_dbg(spcm, cstream->direction, "Entry: free\n"); + + ret = sof_ipc4_compr_stream_free(sdev, spcm, cstream); + + /* unprepare and free the list of DAPM widgets */ + sof_widget_list_unprepare(sdev, spcm, cstream->direction); + + cancel_work_sync(&spcm->stream[cstream->direction].period_elapsed_work); + + snd_compr_free_pages(cstream); + + err = snd_sof_compr_platform_close(sdev, cstream); + if (err < 0) { + spcm_err(spcm, cstream->direction, + "platform compress close failed %d\n", err); + if (!ret) + ret = err; + } + + return ret; +} + +#define SOF_IPC4_CODEC_INFO_GET_ID(value) ((value) & 0xff) +#define SOF_IPC4_CODEC_INFO_GET_DIR(value) (((value) >> 8) & 0xf) + +struct sof_ipc4_codec_info_data { + u32 count; + u32 items[]; +} __packed __aligned(4); + +static int sof_ipc4_compr_get_caps(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_ipc4_codec_info_data *codec_info = ipc4_data->codec_info; + int dir = cstream->direction; + u64 max_buffer_size; + u64 max_fragments; + struct snd_sof_pcm *spcm; + int i; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + /* No compress support available in booted firmware */ + if (!codec_info || !codec_info->count) { + spcm_err(spcm, dir, + "Compress is not supported (no codecs available)\n"); + return -EINVAL; + } + + for (i = 0; i < codec_info->count; i++) { + int _dir = SOF_IPC4_CODEC_INFO_GET_DIR(codec_info->items[i]); + + if (_dir == dir) { + int id = SOF_IPC4_CODEC_INFO_GET_ID(codec_info->items[i]); + + if (caps->num_codecs < ARRAY_SIZE(caps->codecs)) { + spcm_dbg(spcm, dir, "codec#%d: %d\n", + caps->num_codecs, id); + caps->codecs[caps->num_codecs++] = id; + } else { + spcm_dbg(spcm, dir, "codec#%d: %d ignored\n", + caps->num_codecs, id); + } + } + } + + caps->direction = dir; + caps->min_fragment_size = + sof_ipc4_compr_calc_min_fragment_size(&spcm->stream[dir]); + caps->max_fragment_size = SOF_IPC4_COMPR_MAX_FRAGMENT_SIZE; + if (caps->max_fragment_size < caps->min_fragment_size) + caps->max_fragment_size = caps->min_fragment_size; + + caps->min_fragments = SOF_IPC4_COMPR_MIN_FRAGMENTS; + caps->max_fragments = SOF_IPC4_COMPR_MAX_FRAGMENTS; + /* + * A buffer of max_fragments number of max_fragment_size fragments must + * be describable by the page table allocated for the stream + */ + max_buffer_size = sof_ipc4_compr_calc_max_buffer_size(&spcm->stream[dir]); + max_fragments = div_u64(max_buffer_size, caps->max_fragment_size); + if (caps->max_fragments > max_fragments) + caps->max_fragments = max_fragments; + + spcm_dbg(spcm, dir, + "num_codecs: %u, fragment_size: %u-%u, fragments: %u-%u\n", + caps->num_codecs, + caps->min_fragment_size, caps->max_fragment_size, + caps->min_fragments, caps->max_fragments); + return 0; +} + +static int sof_ipc4_compr_alloc_pages(struct device *dev, + struct snd_sof_pcm_stream *sps, + struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + u32 min_fragment_size = sof_ipc4_compr_calc_min_fragment_size(sps); + u64 max_buffer_size = sof_ipc4_compr_calc_max_buffer_size(sps); + struct snd_compr_runtime *crtd = cstream->runtime; + u64 fragments = crtd->buffer_size; + int ret; + + if (crtd->buffer_size < SOF_IPC4_COMPR_MIN_BUFFER_SIZE(min_fragment_size)) { + dev_err(dev, "%s: Buffer size is too small: %llu (minimum is %u)\n", + __func__, crtd->buffer_size, + SOF_IPC4_COMPR_MIN_BUFFER_SIZE(min_fragment_size)); + return -EINVAL; + } + + if (crtd->buffer_size > max_buffer_size) { + dev_err(dev, "%s: Buffer size is too big: %llu (maximum is %llu)\n", + __func__, crtd->buffer_size, max_buffer_size); + return -EINVAL; + } + + if (crtd->fragment_size < min_fragment_size) { + dev_err(dev, "%s: Fragment size is too small: %u (minimum is %u)\n", + __func__, crtd->fragment_size, min_fragment_size); + return -EINVAL; + } + + do_div(fragments, crtd->fragment_size); + if (fragments < SOF_IPC4_COMPR_MIN_FRAGMENTS || + fragments > SOF_IPC4_COMPR_MAX_FRAGMENTS) { + dev_err(dev, + "%s: Incorrect number of fragments: %llu (valid: %d-%d)\n", + __func__, fragments, SOF_IPC4_COMPR_MIN_FRAGMENTS, + SOF_IPC4_COMPR_MAX_FRAGMENTS); + return -EINVAL; + } + + cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + cstream->dma_buffer.dev.dev = dev; + + ret = snd_compr_malloc_pages(cstream, crtd->buffer_size); + if (ret < 0) + return ret; + + ret = snd_sof_compr_create_page_table(component, cstream, crtd->dma_area, + crtd->dma_bytes); + if (ret < 0) + snd_compr_free_pages(cstream); + + return ret; +} + +static bool +sof_ipc4_compr_codec_supported(struct snd_sof_dev *sdev, int codec_id, int dir) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_ipc4_codec_info_data *codec_info = ipc4_data->codec_info; + int i; + + /* No compress support available in booted firmware */ + if (!codec_info || !codec_info->count) + return false; + + for (i = 0; i < codec_info->count; i++) { + int _dir = SOF_IPC4_CODEC_INFO_GET_DIR(codec_info->items[i]); + int _id = SOF_IPC4_CODEC_INFO_GET_ID(codec_info->items[i]); + + if (_dir == dir && codec_id == _id) + return true; + } + + return false; +} + +static int sof_ipc4_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); + struct sof_ipc4_compr_init_data *compr_data __free(kfree) = NULL; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_sof_platform_stream_params *platform_params; + struct sof_ipc4_timestamp_info *time_info; + struct snd_compr_params *compr_params; + struct snd_sof_widget *host_swidget; + struct sof_ipc4_process *process; + struct snd_pcm_hw_params p = {0}; + struct snd_interval *interval; + struct snd_sof_pcm *spcm; + struct snd_mask *fmt; + int dir = cstream->direction; + int ret; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + host_swidget = snd_sof_find_swidget_by_comp_id(sdev, spcm->stream[dir].comp_id); + if (!host_swidget) { + spcm_err(spcm, dir, "failed to find host widget with comp_id %d\n", + spcm->stream[dir].comp_id); + return -ENODEV; + } + + if (!sof_ipc4_compr_codec_supported(sdev, params->codec.id, dir)) { + spcm_err(spcm, dir, "Unsupported codec id: %u\n", params->codec.id); + return -EINVAL; + } + + spcm_dbg(spcm, dir, + "codec_id: %u, rate: %u, ch in/out: %u/%u, format: %u/%u\n", + params->codec.id, params->codec.sample_rate, params->codec.ch_in, + params->codec.ch_out, params->codec.format, params->codec.pcm_format); + + if (spcm->prepared[dir]) { + /* + * This can only happen if user space re-configures the device + * without closing it, for example after DRAIN completion + */ + ret = sof_ipc4_compr_stream_free(sdev, spcm, cstream); + if (ret) + return ret; + } + + /* save the compress params */ + compr_params = &spcm->cparams[dir]; + memcpy(compr_params, params, sizeof(*params)); + + /* + * Force format, rate and channels and use PCM hw_params structure to + * set up the pipelines. + */ + fmt = hw_param_mask(&p, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(fmt); + /* Use correct format based on the used codec */ + switch (params->codec.id) { + case SND_AUDIOCODEC_PCM: + /* The format is provided by user space, it must be validated */ + if (snd_pcm_format_width((snd_pcm_format_t)params->codec.format) <= 0) { + spcm_err(spcm, dir, "Invalid PCM format: %u\n", + params->codec.format); + return -EINVAL; + } + snd_mask_set_format(fmt, (snd_pcm_format_t)params->codec.format); + break; + case SND_AUDIOCODEC_VORBIS: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + break; + case SND_AUDIOCODEC_FLAC: + { + struct snd_dec_flac *dec_flac = ¶ms->codec.options.flac_d; + + if (dec_flac->sample_size == 16) + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + else + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + break; + } + default: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + } + + interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_CHANNELS); + interval->min = compr_params->codec.ch_out; + interval->max = compr_params->codec.ch_out; + + interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_RATE); + interval->min = compr_params->codec.sample_rate; + interval->max = compr_params->codec.sample_rate; + + ret = sof_ipc4_compr_alloc_pages(sdev->dev, &spcm->stream[dir], + component, cstream); + if (ret < 0) + return ret; + + spcm_dbg(spcm, dir, + "buffer_size: %llu, fragment_size: %u (fragments: %u)\n", + cstream->runtime->buffer_size, cstream->runtime->fragment_size, + (u32)cstream->runtime->buffer_size / cstream->runtime->fragment_size); + + interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_PERIOD_BYTES); + interval->min = cstream->runtime->fragment_size; + interval->max = cstream->runtime->fragment_size; + + interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_BUFFER_BYTES); + interval->min = cstream->runtime->buffer_size; + interval->max = cstream->runtime->buffer_size; + + platform_params = &spcm->platform_params[dir]; + ret = snd_sof_compr_platform_hw_params(sdev, cstream, compr_params, + platform_params); + if (ret < 0) { + spcm_err(spcm, dir, "platform compress hw params failed\n"); + goto free_pages; + } + + /* set up the list of DAPM widgets if not already done */ + if (!spcm->stream[dir].list) { + ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, &p, + platform_params, dir); + if (ret < 0) + goto free_pages; + } + + process = sof_ipc4_compr_get_module(spcm, dir); + if (!process) { + ret = -EINVAL; + goto free_list; + } + + compr_data = kzalloc(sizeof(*compr_data), GFP_KERNEL); + if (!compr_data) { + ret = -ENOMEM; + goto free_list; + } + + memcpy(&compr_data->codec, &compr_params->codec, sizeof(compr_data->codec)); + compr_data->dir = dir; + + process->init_ext_module_data = compr_data; + process->init_ext_module_size = sizeof(*compr_data); + + /* + * Make sure that the DSP is booted up, which might not be the + * case if the on-demand DSP boot is used + */ + ret = snd_sof_boot_dsp_firmware(sdev); + if (ret) + goto clear_init_ext; + + /* set the host DMA ID */ + if (tplg_ops && tplg_ops->host_config) + tplg_ops->host_config(sdev, host_swidget, platform_params); + + /* set up the widgets and pipelines in the DSP */ + ret = sof_widget_list_setup(sdev, spcm, &p, platform_params, dir); + if (ret < 0) { + spcm_err(spcm, dir, "widget list set up failed\n"); + goto clear_init_ext; + } + + memcpy(&spcm->params[dir], &p, sizeof(p)); + spcm->prepared[dir] = true; + + time_info = sof_ipc4_sps_to_time_info(&spcm->stream[dir]); + if (time_info) { + /* delay calculation supported */ + time_info->stream_start_offset = SOF_IPC4_INVALID_STREAM_POSITION; + time_info->llp_offset = 0; + + sof_ipc4_build_time_info(sdev, &spcm->stream[dir]); + } + + process->init_ext_module_data = NULL; + process->init_ext_module_size = 0; + + return 0; + +clear_init_ext: + process->init_ext_module_data = NULL; + process->init_ext_module_size = 0; + +free_list: + sof_widget_list_unprepare(sdev, spcm, dir); + +free_pages: + snd_compr_free_pages(cstream); + + return ret; +} + +static int sof_ipc4_compr_get_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_codec *params) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_sof_pcm *spcm; + /* TODO: we don't query the supported codecs for now, if the + * application asks for an unsupported codec the set_params() will fail. + */ + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + dev_err(sdev->dev, "%s: can't find spcm\n", __func__); + return -EINVAL; + } + + spcm_dbg(spcm, cstream->direction, "Entry: get_params\n"); + + memcpy(params, &spcm->cparams[cstream->direction].codec, + sizeof(*params)); + + return 0; +} + +static int sof_ipc4_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *cstream, int cmd) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); + struct snd_sof_pcm *spcm; + int dir = cstream->direction; + bool trigger_platform = false; + int ret = 0; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + dev_err(sdev->dev, "%s: can't find spcm\n", __func__); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* + * The host DMA is left running on stop and drain to match the + * firmware, which only pauses the pipeline, and it is only + * stopped during hw_free. A stream restarted after stop or + * drain - which the compress core allows for capture - would + * continue from a position the two sides no longer agree on. + */ + if (spcm->compr_started[dir]) { + spcm_err(spcm, dir, "Stream restart is not supported\n"); + return -EINVAL; + } + fallthrough; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spcm->pending_stop[dir] = false; + trigger_platform = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spcm->pending_stop[dir] = false; + break; + case SND_COMPR_TRIGGER_DRAIN: + spcm->pending_stop[dir] = true; + break; + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + case SND_COMPR_TRIGGER_NEXT_TRACK: + /* Gapless playback is not supported */ + spcm_dbg(spcm, dir, "Unsupported trigger cmd: %d\n", cmd); + return -EOPNOTSUPP; + default: + spcm_dbg(spcm, dir, "Unhandled trigger cmd: %d\n", cmd); + return 0; + } + + spcm_dbg(spcm, dir, "Entry: trigger (cmd: %d)\n", cmd); + + ret = pcm_ops->trigger(component, NULL, spcm, cmd, dir); + if (ret < 0) { + spcm_err(spcm, dir, "pcm_ops->trigger failed for cmd %d\n", cmd); + return ret; + } + + if (!ret && trigger_platform) { + ret = snd_sof_compr_platform_trigger(sdev, cstream, cmd); + if (ret < 0) + spcm_err(spcm, dir, + "platform compress trigger start failed %d\n", + ret); + } + + if (!ret && cmd == SNDRV_PCM_TRIGGER_START) + spcm->compr_started[dir] = true; + + return ret; +} + +static int sof_ipc4_compr_copy_playback(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *crtd = cstream->runtime; + u64 offset, n; + void *ptr; + int ret; + + div64_u64_rem(crtd->total_bytes_available, crtd->buffer_size, &offset); + ptr = crtd->dma_area + offset; + n = crtd->buffer_size - offset; + + if (count < n) { + ret = copy_from_user(ptr, buf, count); + } else { + ret = copy_from_user(ptr, buf, n); + ret += copy_from_user(crtd->dma_area, buf + n, count - n); + } + + return count - ret; +} + +static int sof_ipc4_compr_copy_capture(struct snd_compr_runtime *crtd, + char __user *buf, size_t count) +{ + u64 offset, n; + void *ptr; + int ret; + + div64_u64_rem(crtd->total_bytes_transferred, crtd->buffer_size, &offset); + ptr = crtd->dma_area + offset; + n = crtd->buffer_size - offset; + + if (count < n) { + ret = copy_to_user(buf, ptr, count); + } else { + ret = copy_to_user(buf, ptr, n); + ret += copy_to_user(buf + n, crtd->dma_area, count - n); + } + + return count - ret; +} + +static int sof_ipc4_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *crtd = cstream->runtime; + + if (count > crtd->buffer_size) + count = crtd->buffer_size; + + if (cstream->direction == SND_COMPRESS_PLAYBACK) + return sof_ipc4_compr_copy_playback(component, cstream, buf, count); + + return sof_ipc4_compr_copy_capture(crtd, buf, count); +} + +static int sof_ipc4_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct sof_ipc4_timestamp_info *time_info; + struct snd_pcm_hw_params *params; + struct snd_sof_pcm_stream *sps; + struct snd_sof_pcm *spcm; + u64 dai_cnt = 0; + int ret; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + params = &spcm->params[cstream->direction]; + + ret = snd_sof_compr_platform_pointer(sdev, cstream, tstamp); + if (ret < 0) { + spcm_err(spcm, cstream->direction, + "platform compress pointer failed %d\n", ret); + return ret; + } + + sps = &spcm->stream[cstream->direction]; + time_info = sof_ipc4_sps_to_time_info(sps); + if (!time_info) + goto host_only; + + /* + * stream_start_offset is updated to memory window by FW based on + * pipeline statistics and it may be invalid if host query happens before + * the statistics is complete. And it will not change after the first + * initialization. + */ + if (time_info->stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION) { + ret = sof_ipc4_get_stream_start_offset(sdev, NULL, sps, time_info); + if (ret < 0) + goto host_only; + } + + if (!time_info->llp_offset) { + dai_cnt = snd_sof_compr_get_dai_frame_counter(sdev, cstream); + } else { + struct sof_ipc4_llp_reading_slot llp; + + sof_mailbox_read(sdev, time_info->llp_offset, &llp, sizeof(llp)); + dai_cnt = ((u64)llp.reading.llp_u << 32) | llp.reading.llp_l; + } + + if (dai_cnt) { + dai_cnt = sof_ipc4_frames_dai_to_host(time_info, dai_cnt); + dai_cnt += time_info->stream_end_offset; + if (dai_cnt < time_info->stream_start_offset) + dai_cnt = 0; + else + dai_cnt -= time_info->stream_start_offset; + } + +host_only: + tstamp->sampling_rate = params_rate(params); + tstamp->pcm_io_frames = dai_cnt; + + return 0; +} + +void sof_ipc4_compr_drain_done(struct snd_sof_dev *sdev, void *ipc_message) +{ + struct sof_ipc4_msg *ipc4_msg = ipc_message; + struct sof_ipc4_notify_module_data *ndata = ipc4_msg->data_ptr; + struct snd_sof_widget *swidget, *host_swidget; + bool widget_found = false; + struct snd_sof_pcm *spcm; + int dir; + + /* Find the swidget based on ndata->module_id and ndata->instance_id */ + swidget = sof_ipc4_find_swidget_by_ids(sdev, ndata->module_id, + ndata->instance_id); + if (!swidget) { + dev_err(sdev->dev, "%s: Failed to find widget for module %u.%u\n", + __func__, ndata->module_id, ndata->instance_id); + return; + } + + if (!swidget->spipe || !swidget->spipe->pipe_widget) + return; + + /* Find the swidget of the host copier on the same pipeline */ + list_for_each_entry(host_swidget, &sdev->widget_list, list) { + if (WIDGET_IS_AIF(host_swidget->id) && + host_swidget->pipeline_id == swidget->pipeline_id) { + widget_found = true; + break; + } + } + + if (!widget_found) { + dev_err(sdev->dev, "%s: Host widget not found for pipeline: %s\n", + __func__, swidget->spipe->pipe_widget->widget->name); + return; + } + + /* Look up the spcm of the host copier */ + spcm = snd_sof_find_spcm_comp(sdev->component, host_swidget->comp_id, &dir); + if (!spcm) { + dev_err(sdev->dev, "%s: Stream cannot be found for %s\n", __func__, + host_swidget->widget->name); + return; + } + + spcm_dbg(spcm, dir, "Entry: EOS done\n"); + + if (spcm->stream[dir].cstream) + snd_compr_drain_notify(spcm->stream[dir].cstream); +} + +const struct snd_compress_ops sof_ipc4_compressed_ops = { + .open = sof_ipc4_compr_open, + .free = sof_ipc4_compr_free, + .get_caps = sof_ipc4_compr_get_caps, + .set_params = sof_ipc4_compr_set_params, + .get_params = sof_ipc4_compr_get_params, + .trigger = sof_ipc4_compr_trigger, + .pointer = sof_ipc4_compr_pointer, + .copy = sof_ipc4_compr_copy, +}; diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c index 07a78cb3c25c..2dff394f4128 100644 --- a/sound/soc/sof/ipc4-loader.c +++ b/sound/soc/sof/ipc4-loader.c @@ -408,6 +408,38 @@ static int sof_ipc4_validate_firmware(struct snd_sof_dev *sdev) return 0; } +static int sof_ipc4_query_sof_info(struct snd_sof_dev *sdev, + void *sof_info_data, u32 sof_info_size) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_ipc4_tuple *tuple; + size_t offset = 0; + + /* + * No size validation is needed as the sof_info data is coming from + * firmware as part of the firmware config, it is guarantied to be + * correct. + */ + while (offset < sof_info_size) { + tuple = (struct sof_ipc4_tuple *)((u8 *)sof_info_data + offset); + + switch (tuple->type) { + case SOF_IPC4_SOF_CODEC_INFO: + ipc4_data->codec_info = devm_kmemdup(sdev->dev, tuple->value, + tuple->size, GFP_KERNEL); + if (!ipc4_data->codec_info) + return -ENOMEM; + break; + default: + break; + } + + offset += sizeof(*tuple) + tuple->size; + } + + return 0; +} + int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; @@ -483,6 +515,11 @@ int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev) */ ipc4_data->libraries_restored = ipc4_data->fw_context_save; break; + case SOF_IPC4_FW_CFG_SOF_INFO: + ret = sof_ipc4_query_sof_info(sdev, tuple->value, tuple->size); + if (ret) + goto out; + break; default: break; } diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 5929ecf6642e..cbf948d84791 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -15,29 +15,6 @@ #include "ipc4-topology.h" #include "ipc4-fw-reg.h" -/** - * struct sof_ipc4_timestamp_info - IPC4 timestamp info - * @host_copier: the host copier of the pcm stream - * @dai_copier: the dai copier of the pcm stream - * @stream_start_offset: reported by fw in memory window (converted to - * frames at host_copier sampling rate) - * @stream_end_offset: reported by fw in memory window (converted to - * frames at host_copier sampling rate) - * @llp_offset: llp offset in memory window - * @delay: Calculated and stored in pointer callback. The stored value is - * returned in the delay callback. Expressed in frames at host copier - * sampling rate. - */ -struct sof_ipc4_timestamp_info { - struct sof_ipc4_copier *host_copier; - struct sof_ipc4_copier *dai_copier; - u64 stream_start_offset; - u64 stream_end_offset; - u32 llp_offset; - - snd_pcm_sframes_t delay; -}; - /** * struct sof_ipc4_pcm_stream_priv - IPC4 specific private data * @time_info: pointer to time info struct if it is supported, otherwise NULL @@ -61,7 +38,7 @@ struct sof_ipc4_pcm_stream_priv { #define DELAY_MAX (DELAY_BOUNDARY >> 1) -static inline struct sof_ipc4_timestamp_info * +struct sof_ipc4_timestamp_info * sof_ipc4_sps_to_time_info(struct snd_sof_pcm_stream *sps) { struct sof_ipc4_pcm_stream_priv *stream_priv = sps->private; @@ -176,7 +153,8 @@ sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state, struct snd_sof_widget *pipe_widget = spipe->pipe_widget; struct sof_ipc4_pipeline *pipeline = pipe_widget->private; - if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET) + if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET && + state != SOF_IPC4_PIPE_EOS) return; switch (state) { @@ -196,7 +174,12 @@ sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state, true); break; case SOF_IPC4_PIPE_PAUSED: - /* Pause the pipeline only when its started_count is 1 more than paused_count */ + case SOF_IPC4_PIPE_EOS: + /* + * Pause the pipeline only when its started_count is 1 more than + * paused_count. + * Same rule applies to EOS state. + */ if (spipe->paused_count == (spipe->started_count - 1)) sof_ipc4_add_pipeline_by_priority(trigger_list, pipe_widget, pipe_priority, true); @@ -412,28 +395,24 @@ static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev, } static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, - struct snd_pcm_substream *substream, int state, int cmd) + struct snd_pcm_substream *substream, int state, int cmd, + struct snd_sof_pcm *spcm, int dir) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_sof_pcm_stream_pipeline_list *pipeline_list; struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct ipc4_pipeline_set_state_data *trigger_list; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; struct snd_sof_pipeline *spipe; - struct snd_sof_pcm *spcm; u8 *pipe_priority; int ret; int i; - spcm = snd_sof_find_spcm_dai(component, rtd); - if (!spcm) - return -EINVAL; + spcm_dbg(spcm, dir, "cmd: %d, state: %d\n", cmd, state); - spcm_dbg(spcm, substream->stream, "cmd: %d, state: %d\n", cmd, state); - - pipeline_list = &spcm->stream[substream->stream].pipeline_list; + pipeline_list = &spcm->stream[dir].pipeline_list; + guard(mutex)(&ipc4_data->pipeline_state_mutex); /* nothing to trigger if the list is empty */ if (!pipeline_list->pipelines || !pipeline_list->count) @@ -450,9 +429,9 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, if (pipeline->use_chain_dma) { struct sof_ipc4_timestamp_info *time_info; - time_info = sof_ipc4_sps_to_time_info(&spcm->stream[substream->stream]); + time_info = sof_ipc4_sps_to_time_info(&spcm->stream[dir]); - ret = sof_ipc4_chain_dma_trigger(sdev, spcm, substream->stream, + ret = sof_ipc4_chain_dma_trigger(sdev, spcm, dir, pipeline_list, state, cmd); if (ret || !time_info) return ret; @@ -461,12 +440,16 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, /* * Record the DAI position for delay reporting * To handle multiple pause/resume/xrun we need to add - * the positions to simulate how the firmware behaves + * the positions to simulate how the firmware behaves. + * Chained DMA does not support compress streams. We should + * never get here with compress. */ - u64 pos = snd_sof_pcm_get_dai_frame_counter(sdev, component, - substream); + if (substream) { + u64 pos = snd_sof_pcm_get_dai_frame_counter(sdev, component, + substream); - time_info->stream_end_offset += pos; + time_info->stream_end_offset += pos; + } } else if (state == SOF_IPC4_PIPE_RESET) { /* Reset the end offset as the stream is stopped */ time_info->stream_end_offset = 0; @@ -487,8 +470,6 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, return -ENOMEM; } - guard(mutex)(&ipc4_data->pipeline_state_mutex); - /* * IPC4 requires pipelines to be triggered in order starting at the sink and * walking all the way to the source. So traverse the pipeline_list in the order @@ -517,8 +498,9 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, goto free; } - /* no need to pause before reset or before pause release */ - if (state == SOF_IPC4_PIPE_RESET || cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) + /* no need to pause before reset, EOS or before pause release */ + if (state == SOF_IPC4_PIPE_RESET || state == SOF_IPC4_PIPE_EOS || + cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) goto skip_pause_transition; /* @@ -527,7 +509,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, */ ret = sof_ipc4_set_multi_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, trigger_list); if (ret < 0) { - spcm_err(spcm, substream->stream, "failed to pause all pipelines\n"); + spcm_err(spcm, dir, "failed to pause all pipelines\n"); /* * workaround: if the firmware is crashed or the IPC timed out * while setting the pipeline state we must ignore the error @@ -558,7 +540,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, * Invalidate the stream_start_offset to make sure that it is * going to be updated if the stream resumes */ - time_info = sof_ipc4_sps_to_time_info(&spcm->stream[substream->stream]); + time_info = sof_ipc4_sps_to_time_info(&spcm->stream[dir]); if (time_info) time_info->stream_start_offset = SOF_IPC4_INVALID_STREAM_POSITION; @@ -568,7 +550,7 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, /* else set the RUNNING/RESET state in the DSP */ ret = sof_ipc4_set_multi_pipeline_state(sdev, state, trigger_list); if (ret < 0) { - spcm_err(spcm, substream->stream, + spcm_err(spcm, dir, "failed to set final state %d for all pipelines\n", state); /* @@ -599,7 +581,8 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, } static int sof_ipc4_pcm_trigger(struct snd_soc_component *component, - struct snd_pcm_substream *substream, int cmd) + struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int cmd, int dir) { int state; @@ -615,20 +598,25 @@ static int sof_ipc4_pcm_trigger(struct snd_soc_component *component, case SNDRV_PCM_TRIGGER_STOP: state = SOF_IPC4_PIPE_PAUSED; break; + case SND_COMPR_TRIGGER_DRAIN: + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + state = SOF_IPC4_PIPE_EOS; + break; default: dev_err(component->dev, "%s: unhandled trigger cmd %d\n", __func__, cmd); return -EINVAL; } /* set the pipeline state */ - return sof_ipc4_trigger_pipelines(component, substream, state, cmd); + return sof_ipc4_trigger_pipelines(component, substream, state, cmd, spcm, dir); } static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component, - struct snd_pcm_substream *substream) + struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int dir) { /* command is not relevant with RESET, so just pass 0 */ - return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET, 0); + return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET, 0, spcm, dir); } static int ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, @@ -903,13 +891,17 @@ static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, static void sof_ipc4_pcm_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm) { struct snd_sof_pcm_stream_pipeline_list *pipeline_list; + struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct sof_ipc4_pcm_stream_priv *stream_priv; int stream; + guard(mutex)(&ipc4_data->pipeline_state_mutex); + for_each_pcm_streams(stream) { pipeline_list = &spcm->stream[stream].pipeline_list; kfree(pipeline_list->pipelines); pipeline_list->pipelines = NULL; + pipeline_list->count = 0; stream_priv = spcm->stream[stream].private; kfree(stream_priv->time_info); @@ -975,7 +967,7 @@ static int sof_ipc4_pcm_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm return 0; } -static void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *sps) +void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *sps) { struct sof_ipc4_copier *host_copier = NULL; struct sof_ipc4_copier *dai_copier = NULL; @@ -1073,7 +1065,7 @@ static int sof_ipc4_pcm_hw_params(struct snd_soc_component *component, return 0; } -static u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info, u64 value) +u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info, u64 value) { u64 dai_rate, host_rate; @@ -1102,10 +1094,10 @@ static u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info return value; } -static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - struct snd_sof_pcm_stream *sps, - struct sof_ipc4_timestamp_info *time_info) +int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_sof_pcm_stream *sps, + struct sof_ipc4_timestamp_info *time_info) { struct sof_ipc4_copier *host_copier = time_info->host_copier; struct sof_ipc4_copier *dai_copier = time_info->dai_copier; @@ -1119,7 +1111,8 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_INVALID_NODE_ID) { return -EINVAL; - } else if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_CHAIN_DMA_NODE_ID) { + } else if (substream && + host_copier->data.gtw_cfg.node_id == SOF_IPC4_CHAIN_DMA_NODE_ID) { /* * While the firmware does not support time_info reporting for * streams using ChainDMA, it is granted that ChainDMA can only @@ -1332,4 +1325,7 @@ const struct sof_ipc_pcm_ops ipc4_pcm_ops = { .delay = sof_ipc4_pcm_delay, .ipc_first_on_start = true, .platform_stop_during_hw_free = true, +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) + .compress_ops = &sof_ipc4_compressed_ops, +#endif }; diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index a8cdf9bc750b..31f7d68029ce 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -58,13 +58,18 @@ struct sof_ipc4_fw_library { struct sof_ipc4_fw_module *modules; }; +struct snd_ipc4_nhlt { + struct list_head list; + void *nhlt; + bool from_acpi; +}; + /** * struct sof_ipc4_fw_data - IPC4-specific data * @manifest_fw_hdr_offset: FW header offset in the manifest * @fw_lib_xa: XArray for firmware libraries, including basefw (ID = 0) * Used to store the FW libraries and to manage the unique IDs of the * libraries. - * @nhlt: NHLT table either from the BIOS or the topology manifest * @mtrace_type: mtrace type supported on the booted platform * @mtrace_log_bytes: log bytes as reported by the firmware via fw_config reply * @num_playback_streams: max number of playback DMAs, needed for CHAIN_DMA offset @@ -74,14 +79,16 @@ struct sof_ipc4_fw_library { * base firmware * @fw_context_save: Firmware supports full context save and restore * @libraries_restored: The libraries have been retained during firmware boot + * @nhlt_list: The NHLT tables from the BIOS and the topology manifest * + * @codec_info: Information about the available codecs in booted firmware. The + * data is to be used by the code for compressed support. * @load_library: Callback function for platform dependent library loading * @pipeline_state_mutex: Mutex to protect pipeline triggers, ref counts, states and deletion */ struct sof_ipc4_fw_data { u32 manifest_fw_hdr_offset; struct xarray fw_lib_xa; - void *nhlt; enum sof_ipc4_mtrace_type mtrace_type; u32 mtrace_log_bytes; int num_playback_streams; @@ -90,6 +97,9 @@ struct sof_ipc4_fw_data { u32 max_libs_count; bool fw_context_save; bool libraries_restored; + struct list_head nhlt_list; + + void *codec_info; int (*load_library)(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib, bool reload); @@ -98,11 +108,37 @@ struct sof_ipc4_fw_data { struct mutex pipeline_state_mutex; /* protect pipeline triggers, ref counts and states */ }; +/** + * struct sof_ipc4_timestamp_info - IPC4 timestamp info + * @host_copier: the host copier of the pcm stream + * @dai_copier: the dai copier of the pcm stream + * @stream_start_offset: reported by fw in memory window (converted to + * frames at host_copier sampling rate) + * @stream_end_offset: reported by fw in memory window (converted to + * frames at host_copier sampling rate) + * @llp_offset: llp offset in memory window + * @delay: Calculated and stored in pointer callback. The stored value is + * returned in the delay callback. Expressed in frames at host copier + * sampling rate. + */ +struct sof_ipc4_timestamp_info { + struct sof_ipc4_copier *host_copier; + struct sof_ipc4_copier *dai_copier; + u64 stream_start_offset; + u64 stream_end_offset; + u32 llp_offset; + + snd_pcm_sframes_t delay; +}; + extern const struct sof_ipc_fw_loader_ops ipc4_loader_ops; extern const struct sof_ipc_tplg_ops ipc4_tplg_ops; extern const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops; extern const struct sof_ipc_pcm_ops ipc4_pcm_ops; extern const struct sof_ipc_fw_tracing_ops ipc4_mtrace_ops; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) +extern const struct snd_compress_ops sof_ipc4_compressed_ops; +#endif int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 instance_id, u32 state); int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core); @@ -129,4 +165,19 @@ void sof_ipc4_mic_privacy_state_change(struct snd_sof_dev *sdev, bool state); enum sof_ipc4_pipeline_state; const char *sof_ipc4_pipeline_state_str(enum sof_ipc4_pipeline_state state); +struct sof_ipc4_timestamp_info *sof_ipc4_sps_to_time_info(struct snd_sof_pcm_stream *sps); +void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *sps); +int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_sof_pcm_stream *sps, + struct sof_ipc4_timestamp_info *time_info); +u64 sof_ipc4_frames_dai_to_host(struct sof_ipc4_timestamp_info *time_info, u64 value); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) +void sof_ipc4_compr_drain_done(struct snd_sof_dev *sdev, void *ipc_message); +#else +static inline void sof_ipc4_compr_drain_done(struct snd_sof_dev *sdev, + void *ipc_message) { } +#endif + #endif diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 22c15fbb93f2..8b13c97b3480 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -744,6 +744,9 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) sps->dsp_max_burst_size_in_ms = 1; } + if (spcm->pcm.compress) + ipc4_copier->data.copier_feature_mask |= BIT(SOF_IPC4_COPIER_FAST_MODE); + skip_gtw_cfg: ipc4_copier->gtw_attr = kzalloc_obj(*ipc4_copier->gtw_attr); if (!ipc4_copier->gtw_attr) { @@ -1844,12 +1847,13 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai u32 linktype, u8 dir, u32 **dst, u32 *len) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; - struct nhlt_specific_cfg *cfg; + struct nhlt_specific_cfg *cfg = NULL; + struct snd_ipc4_nhlt *entry = NULL; int sample_rate, channel_count; bool format_change = false; int bit_depth, ret; u32 nhlt_type; - int dev_type = 0; + int dev_type = -EINVAL; /* convert to NHLT type */ switch (linktype) { @@ -1880,10 +1884,17 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai * Query the type for the port and then pass that information back * to the blob lookup function. */ - dev_type = intel_nhlt_ssp_device_type(sdev->dev, ipc4_data->nhlt, - dai_index); - if (dev_type < 0) + list_for_each_entry(entry, &ipc4_data->nhlt_list, list) { + dev_type = intel_nhlt_ssp_device_type(sdev->dev, entry->nhlt, + dai_index); + if (dev_type >= 0) + break; + } + if (dev_type < 0) { + dev_err(sdev->dev, "%s: No match for SSP%d in NHLT table\n", + __func__, dai_index); return dev_type; + } break; default: return 0; @@ -1893,9 +1904,14 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai dai_index, nhlt_type, dir, dev_type); /* find NHLT blob with matching params */ - cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, dai_index, nhlt_type, - bit_depth, bit_depth, channel_count, sample_rate, - dir, dev_type); + list_for_each_entry(entry, &ipc4_data->nhlt_list, list) { + cfg = intel_nhlt_get_endpoint_blob(sdev->dev, entry->nhlt, dai_index, + nhlt_type, bit_depth, bit_depth, + channel_count, sample_rate, dir, + dev_type); + if (cfg) + break; + } if (!cfg) { bool get_new_blob = false; @@ -1929,13 +1945,15 @@ snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai } if (get_new_blob) { - cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, - dai_index, nhlt_type, - bit_depth, bit_depth, - channel_count, sample_rate, - dir, dev_type); - if (cfg) - goto out; + list_for_each_entry(entry, &ipc4_data->nhlt_list, list) { + cfg = intel_nhlt_get_endpoint_blob(sdev->dev, entry->nhlt, + dai_index, nhlt_type, + bit_depth, bit_depth, + channel_count, sample_rate, + dir, dev_type); + if (cfg) + goto out; + } } dev_err(sdev->dev, @@ -3266,22 +3284,56 @@ static void sof_ipc4_add_init_ext_dp_memory_data(struct snd_sof_dev *sdev, u32 *payload, u32 *ext_pos, struct sof_ipc4_module_init_ext_object **hdr) { - /* Add memory_data if comp_domain indicates DP */ - if (swidget->comp_domain == SOF_COMP_DOMAIN_DP) { - struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; + struct sof_ipc4_mod_init_ext_dp_memory_data *dp_mem_data; - *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; - (*hdr)->header = - SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | - SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), - sizeof(u32))); - *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); - dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[*ext_pos]; - dp_mem_data->domain_id = swidget->domain_id; - dp_mem_data->stack_bytes = swidget->stack_bytes; - dp_mem_data->heap_bytes = swidget->heap_bytes; - *ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); + *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; + (*hdr)->header = + SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_DP_DATA) | + SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(sizeof(*dp_mem_data), + sizeof(u32))); + *ext_pos += DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)); + dp_mem_data = (struct sof_ipc4_mod_init_ext_dp_memory_data *)&payload[*ext_pos]; + dp_mem_data->domain_id = swidget->domain_id; + dp_mem_data->stack_bytes = swidget->stack_bytes; + dp_mem_data->heap_bytes = swidget->heap_bytes; + *ext_pos += DIV_ROUND_UP(sizeof(*dp_mem_data), sizeof(u32)); +} + +static int +sof_ipc4_add_init_ext_module_data(struct snd_sof_dev *sdev, + struct sof_ipc4_process *process, + u32 *payload, u32 *ext_pos, + struct sof_ipc4_module_init_ext_object **hdr) +{ + u32 data_size = process->init_ext_module_size; + void *data = process->init_ext_module_data; + size_t needed; + + /* + * Unlike the other objects, the module data is of variable size, + * provided by the module which is being set up. + * Make sure that the object fits into the payload buffer before any of + * it is written. + */ + needed = ((size_t)*ext_pos + DIV_ROUND_UP(sizeof(**hdr), sizeof(u32)) + + DIV_ROUND_UP(data_size, sizeof(u32))) * sizeof(u32); + if (needed > sdev->ipc->max_payload_size) { + dev_err(sdev->dev, + "Max ipc payload size %zu exceeded by module data: %zu\n", + sdev->ipc->max_payload_size, needed); + return -EINVAL; } + + *hdr = (struct sof_ipc4_module_init_ext_object *)&payload[*ext_pos]; + (*hdr)->header = SOF_IPC4_MOD_INIT_EXT_OBJ_ID(SOF_IPC4_MOD_INIT_DATA_ID_MODULE_DATA) | + SOF_IPC4_MOD_INIT_EXT_OBJ_WORDS(DIV_ROUND_UP(data_size, sizeof(u32))); + *ext_pos += DIV_ROUND_UP(sizeof(*(*hdr)), sizeof(u32)); + + memcpy(&payload[*ext_pos], data, data_size); + + *ext_pos += DIV_ROUND_UP(data_size, sizeof(u32)); + + return 0; } static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, @@ -3290,17 +3342,17 @@ static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, void *ipc_data, u32 ipc_size, void **new_data) { - struct sof_ipc4_module_init_ext_init *ext_init; + struct sof_ipc4_process *process = swidget->private; struct sof_ipc4_module_init_ext_object *hdr = NULL; + struct sof_ipc4_module_init_ext_init *ext_init; + bool in_dp_domain = swidget->comp_domain == SOF_COMP_DOMAIN_DP; + bool has_ext_data = WIDGET_IS_PROCESS(swidget->id) && process->init_ext_module_size; int new_size; u32 *payload; u32 ext_pos; + int ret; - /* - * Only DP widgets currently add init-ext objects here. Avoid allocating - * a max-sized payload buffer for widgets that will immediately return 0. - */ - if (swidget->comp_domain != SOF_COMP_DOMAIN_DP) + if (!in_dp_domain && !has_ext_data) return 0; payload = kzalloc(sdev->ipc->max_payload_size, GFP_KERNEL); @@ -3309,25 +3361,24 @@ static int sof_ipc4_widget_mod_init_msg_payload(struct snd_sof_dev *sdev, /* Add ext_init first and set objects array flag to 1 */ ext_init = (struct sof_ipc4_module_init_ext_init *)payload; + ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; ext_pos = DIV_ROUND_UP(sizeof(*ext_init), sizeof(u32)); /* Add object array objects after ext_init */ + if (in_dp_domain) + sof_ipc4_add_init_ext_dp_memory_data(sdev, swidget, payload, + &ext_pos, &hdr); - sof_ipc4_add_init_ext_dp_memory_data(sdev, swidget, payload, &ext_pos, &hdr); - - /* Add following object array items here */ - - if (!hdr) { - /* - * NOTE: Remove this early bail out, when struct - * sof_ipc4_module_init_ext_init alone has some - * function. - */ - kfree(payload); - return 0; + if (has_ext_data) { + ret = sof_ipc4_add_init_ext_module_data(sdev, process, payload, + &ext_pos, &hdr); + if (ret) { + kfree(payload); + return ret; + } } - ext_init->word0 |= SOF_IPC4_MOD_INIT_EXT_OBJ_ARRAY_MASK; + /* Set last bit for the last object in the array */ hdr->header |= SOF_IPC4_MOD_INIT_EXT_OBJ_LAST_MASK; /* Calculate final size and check that it fits to max payload size */ @@ -3556,6 +3607,8 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget msg = &asrc->msg; break; } + case snd_soc_dapm_decoder: + case snd_soc_dapm_encoder: case snd_soc_dapm_effect: { struct sof_ipc4_process *process = swidget->private; @@ -4072,6 +4125,7 @@ static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct sof_manifest_tlv *manifest_tlv; + struct snd_ipc4_nhlt *tplg_nhlt; struct sof_manifest *manifest; u32 size = le32_to_cpu(man->priv.size); u8 *man_ptr = man->priv.data; @@ -4107,13 +4161,20 @@ static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index, switch (le32_to_cpu(manifest_tlv->type)) { case SOF_MANIFEST_DATA_TYPE_NHLT: - /* no NHLT in BIOS, so use the one from topology manifest */ - if (ipc4_data->nhlt) - break; - ipc4_data->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data, - le32_to_cpu(manifest_tlv->size), GFP_KERNEL); - if (!ipc4_data->nhlt) + /* Get the nhlt from topology manifest */ + tplg_nhlt = devm_kzalloc(sdev->dev, sizeof(*tplg_nhlt), GFP_KERNEL); + if (!tplg_nhlt) return -ENOMEM; + + tplg_nhlt->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data, + le32_to_cpu(manifest_tlv->size), GFP_KERNEL); + if (!tplg_nhlt->nhlt) + return -ENOMEM; + + tplg_nhlt->from_acpi = false; + + list_add(&tplg_nhlt->list, &ipc4_data->nhlt_list); + break; default: dev_warn(scomp->dev, "Skipping unknown manifest data type %d\n", @@ -4328,6 +4389,15 @@ static const struct sof_ipc_tplg_widget_ops tplg_ipc4_widget_ops[SND_SOC_DAPM_TY process_token_list, ARRAY_SIZE(process_token_list), NULL, sof_ipc4_prepare_process_module, NULL}, + /* for all practical purposes a decoder is like an effect type widget */ + [snd_soc_dapm_decoder] = {sof_ipc4_widget_setup_comp_process, + sof_ipc4_widget_free_comp_process, + process_token_list, ARRAY_SIZE(process_token_list), + NULL, sof_ipc4_prepare_process_module, NULL}, + [snd_soc_dapm_encoder] = {sof_ipc4_widget_setup_comp_process, + sof_ipc4_widget_free_comp_process, + process_token_list, ARRAY_SIZE(process_token_list), + NULL, sof_ipc4_prepare_process_module, NULL}, }; const struct sof_ipc_tplg_ops ipc4_tplg_ops = { diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index c9fe9f2e4083..c2949ff979a5 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -222,6 +222,11 @@ struct sof_copier_gateway_cfg { uint32_t config_data[]; }; +/* bit definition in copier_feature_mask */ +enum sof_ipc4_copier_feature { + SOF_IPC4_COPIER_FAST_MODE = 0, /* free running mode of host copier */ +}; + /** * struct sof_ipc4_copier_data - IPC data for copier * @base_config: Base configuration including input audio format @@ -524,6 +529,8 @@ struct sof_ipc4_base_module_cfg_ext { * @msg: IPC4 message struct containing header and data info * @base_config_ext_size: Size of the base config extension data in bytes * @init_config: Module init config type (SOF_IPC4_MODULE_INIT_CONFIG_TYPE_*) + * @init_ext_module_data: module_data for init_ext object + * @init_ext_module_size: size of init_ext_module_data */ struct sof_ipc4_process { struct sof_ipc4_base_module_cfg base_config; @@ -535,6 +542,8 @@ struct sof_ipc4_process { struct sof_ipc4_msg msg; u32 base_config_ext_size; u32 init_config; + void *init_ext_module_data; + size_t init_ext_module_size; }; bool sof_ipc4_copier_is_single_bitdepth(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index e77f73390bd8..b9dd798e59bc 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -776,12 +776,21 @@ static void sof_ipc4_module_notification_handler(struct snd_sof_dev *sdev, } /* Handle ALSA kcontrol notification */ - if ((data->event_id & SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_MASK) == - SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL) { + switch (data->event_id & SOF_IPC4_NOTIFY_MODULE_EVENTID_SOF_MAGIC_MASK) { + case SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL: + { const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; if (tplg_ops->control->update) tplg_ops->control->update(sdev, ipc4_msg); + + break; + } + case SOF_IPC4_NOTIFY_MODULE_EVENTID_COMPR_MAGIC_VAL: + sof_ipc4_compr_drain_done(sdev, ipc4_msg); + break; + default: + break; } } diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.c b/sound/soc/sof/mediatek/mt8186/mt8186.c index c1bea967737d..a5ccee87e684 100644 --- a/sound/soc/sof/mediatek/mt8186/mt8186.c +++ b/sound/soc/sof/mediatek/mt8186/mt8186.c @@ -49,7 +49,7 @@ static int platform_parse_resource(struct platform_device *pdev, void *data) struct mtk_adsp_chip_info *adsp = data; int ret; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_err(dev, "of_reserved_mem_device_init failed\n"); return ret; diff --git a/sound/soc/sof/mediatek/mt8195/mt8195.c b/sound/soc/sof/mediatek/mt8195/mt8195.c index 4d6e9300a9c0..e4b6b6c18e02 100644 --- a/sound/soc/sof/mediatek/mt8195/mt8195.c +++ b/sound/soc/sof/mediatek/mt8195/mt8195.c @@ -49,7 +49,7 @@ static int platform_parse_resource(struct platform_device *pdev, void *data) struct mtk_adsp_chip_info *adsp = data; int ret; - ret = of_reserved_mem_device_init(dev); + ret = devm_of_reserved_mem_device_init(dev); if (ret) { dev_err(dev, "of_reserved_mem_device_init failed\n"); return ret; diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 72af1f4ff620..4afc92490fbe 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "sof-priv.h" @@ -448,6 +449,79 @@ snd_sof_pcm_platform_hw_params(struct snd_sof_dev *sdev, return 0; } +static inline int +snd_sof_compr_platform_open(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_open) + return sof_ops(sdev)->compr_open(sdev, cstream); + + return 0; +} + +/* disconnect pcm substream to a host stream */ +static inline int +snd_sof_compr_platform_close(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_close) + return sof_ops(sdev)->compr_close(sdev, cstream); + + return 0; +} + +/* host stream hw params */ +static inline int +snd_sof_compr_platform_hw_params(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_sof_platform_stream_params *platform_params) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_hw_params) + return sof_ops(sdev)->compr_hw_params(sdev, cstream, params, platform_params); + + return 0; +} + +static inline int +snd_sof_compr_platform_hw_free(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_hw_free) + return sof_ops(sdev)->compr_hw_free(sdev, cstream); + + return 0; +} + +static inline int +snd_sof_compr_platform_trigger(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, int cmd) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_trigger) + return sof_ops(sdev)->compr_trigger(sdev, cstream, cmd); + + return 0; +} + +static inline int +snd_sof_compr_platform_pointer(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_pointer) + return sof_ops(sdev)->compr_pointer(sdev, cstream, tstamp); + + return 0; +} + +static inline u64 +snd_sof_compr_get_dai_frame_counter(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream) +{ + if (sof_ops(sdev) && sof_ops(sdev)->compr_get_dai_frame_counter) + return sof_ops(sdev)->compr_get_dai_frame_counter(sdev, cstream); + + return 0; +} + /* host stream hw free */ static inline int snd_sof_pcm_platform_hw_free(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index f748d072109a..b4305dc43d61 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -66,7 +66,7 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); -static int +int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params, struct snd_sof_platform_stream_params *platform_params, int dir) @@ -100,8 +100,8 @@ sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_run return 0; } -static struct snd_sof_widget *snd_sof_find_swidget_by_comp_id(struct snd_sof_dev *sdev, - int comp_id) +struct snd_sof_widget *snd_sof_find_swidget_by_comp_id(struct snd_sof_dev *sdev, + int comp_id) { struct snd_sof_widget *swidget; @@ -152,7 +152,7 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, * between. At least ALSA OSS emulation depends on this. */ if (spcm->prepared[substream->stream] && pcm_ops && pcm_ops->hw_free) { - ret = pcm_ops->hw_free(component, substream); + ret = pcm_ops->hw_free(component, substream, spcm, substream->stream); if (ret < 0) return ret; @@ -194,7 +194,7 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream); ret = snd_sof_create_page_table(component->dev, dmab, - spcm->stream[substream->stream].page_table.area, + &spcm->stream[substream->stream].page_table, runtime->dma_bytes); if (ret < 0) return ret; @@ -223,7 +223,8 @@ static int sof_pcm_stream_free(struct snd_sof_dev *sdev, /* free PCM in the DSP */ if (pcm_ops && pcm_ops->hw_free) { - ret = pcm_ops->hw_free(sdev->component, substream); + ret = pcm_ops->hw_free(sdev->component, substream, spcm, + substream->stream); if (ret < 0) { spcm_err(spcm, substream->stream, "pcm_ops->hw_free failed %d\n", ret); @@ -458,7 +459,7 @@ static int sof_pcm_trigger(struct snd_soc_component *component, snd_sof_pcm_platform_trigger(sdev, substream, cmd); if (pcm_ops && pcm_ops->trigger) - ret = pcm_ops->trigger(component, substream, cmd); + ret = pcm_ops->trigger(component, substream, spcm, cmd, substream->stream); switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: @@ -847,7 +848,10 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->delay = sof_pcm_delay; #if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) - pd->compress_ops = &sof_compressed_ops; + const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); + + if (pcm_ops) + pd->compress_ops = pcm_ops->compress_ops; #endif pd->pcm_new = sof_pcm_new; diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 72ff03b0b80d..8cbeca471542 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -11,6 +11,7 @@ #include #include #include "sof-audio.h" +#include "sof-utils.h" #include "ops.h" /* @@ -102,7 +103,7 @@ static int sof_widget_free_unlocked(struct snd_sof_dev *sdev, * decrement ref count for cores associated with all modules in the pipeline and clear * the complete flag */ - if (swidget->id == snd_soc_dapm_scheduler) { + if (swidget->id == snd_soc_dapm_scheduler && spipe) { int i; for_each_set_bit(i, &spipe->core_mask, sdev->num_cores) { @@ -114,16 +115,16 @@ static int sof_widget_free_unlocked(struct snd_sof_dev *sdev, err = ret; } } - swidget->spipe->complete = 0; + spipe->complete = 0; } /* * free the scheduler widget (same as pipe_widget) associated with the current swidget. * skip for static pipelines */ - if (swidget->spipe && swidget->dynamic_pipeline_widget && + if (spipe && spipe->pipe_widget && swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { - ret = sof_widget_free_unlocked(sdev, swidget->spipe->pipe_widget); + ret = sof_widget_free_unlocked(sdev, spipe->pipe_widget); if (ret < 0 && !err) err = ret; } @@ -628,6 +629,10 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d if (!pipeline_list->pipelines) goto sink_setup; + /* a widget which is not part of a pipeline has nothing to trigger */ + if (!swidget->spipe) + goto sink_setup; + /* * Add the widget's pipe_widget to the list of pipelines to be triggered if not * already in the list. This will result in the pipelines getting added in the @@ -640,8 +645,8 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d } if (i == pipeline_list->count) { - pipeline_list->count++; pipeline_list->pipelines[i] = swidget->spipe; + pipeline_list->count++; } } @@ -1050,3 +1055,83 @@ int sof_dai_get_tdm_slots(struct snd_soc_pcm_runtime *rtd) return sof_dai_get_param(rtd, SOF_DAI_PARAM_INTEL_SSP_TDM_SLOTS); } EXPORT_SYMBOL(sof_dai_get_tdm_slots); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) +static void sof_set_transferred_bytes(struct sof_compr_stream *sstream, + u64 host_pos, u64 buffer_size) +{ + u64 prev_pos; + unsigned int copied; + + div64_u64_rem(sstream->copied_total, buffer_size, &prev_pos); + + if (host_pos < prev_pos) + copied = (buffer_size - prev_pos) + host_pos; + else + copied = host_pos - prev_pos; + + sstream->copied_total += copied; +} + +static void snd_sof_compr_fragment_elapsed_work(struct work_struct *work) +{ + struct snd_sof_pcm_stream *sps = container_of(work, struct snd_sof_pcm_stream, + period_elapsed_work); + + snd_compr_fragment_elapsed(sps->cstream); +} + +void snd_sof_compr_init_elapsed_work(struct work_struct *work) +{ + INIT_WORK(work, snd_sof_compr_fragment_elapsed_work); +} + +/* + * sof compr fragment elapse, this could be called in irq thread context + */ +void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_compr_runtime *crtd; + struct snd_soc_component *component; + struct sof_compr_stream *sstream; + struct snd_sof_pcm *spcm; + + if (!cstream) + return; + + rtd = cstream->private_data; + crtd = cstream->runtime; + sstream = crtd->private_data; + component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + dev_err(component->dev, "fragment elapsed called for unknown stream!\n"); + return; + } + + sof_set_transferred_bytes(sstream, spcm->stream[cstream->direction].posn.host_posn, + crtd->buffer_size); + + /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */ + schedule_work(&spcm->stream[cstream->direction].period_elapsed_work); +} + +int snd_sof_compr_create_page_table(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + unsigned char *dma_area, size_t size) +{ + struct snd_dma_buffer *dmab = cstream->runtime->dma_buffer_p; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + int dir = cstream->direction; + struct snd_sof_pcm *spcm; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + return snd_sof_create_page_table(component->dev, dmab, + &spcm->stream[dir].page_table, size); +} +#endif diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index ae95efc9be1c..672879371d14 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -43,6 +43,9 @@ #define WIDGET_IS_AIF(id) ((id) == snd_soc_dapm_aif_in || (id) == snd_soc_dapm_aif_out) #define WIDGET_IS_AIF_OR_DAI(id) (WIDGET_IS_DAI(id) || WIDGET_IS_AIF(id)) #define WIDGET_IS_COPIER(id) (WIDGET_IS_AIF_OR_DAI(id) || (id) == snd_soc_dapm_buffer) +#define WIDGET_IS_PROCESS(id) ((id) == snd_soc_dapm_effect || \ + (id) == snd_soc_dapm_decoder || \ + (id) == snd_soc_dapm_encoder) #define SOF_DAI_PARAM_INTEL_SSP_MCLK 0 #define SOF_DAI_PARAM_INTEL_SSP_BCLK 1 @@ -119,14 +122,16 @@ struct snd_sof_dai_config_data { * therefore the host must do the same and should stop the DMA during * hw_free. * @d0i3_supported_in_s0ix: Allow DSP D0I3 during S0iX + * @compress_ops: Pointer to ops for compressed streams */ struct sof_ipc_pcm_ops { int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_sof_platform_stream_params *platform_params); - int (*hw_free)(struct snd_soc_component *component, struct snd_pcm_substream *substream); - int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream, - int cmd); + int (*hw_free)(struct snd_soc_component *component, struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int dir); + int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int cmd, int dir); int (*dai_link_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); int (*pcm_setup)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); void (*pcm_free)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); @@ -139,6 +144,7 @@ struct sof_ipc_pcm_ops { bool ipc_first_on_start; bool platform_stop_during_hw_free; bool d0i3_supported_in_s0ix; + const struct snd_compress_ops *compress_ops; }; /** @@ -354,10 +360,12 @@ struct snd_sof_pcm { struct snd_sof_pcm_stream stream[2]; struct list_head list; /* list in sdev pcm list */ struct snd_pcm_hw_params params[2]; + struct snd_compr_params cparams[2]; /* applicable for compress devices */ struct snd_sof_platform_stream_params platform_params[2]; bool prepared[2]; /* PCM_PARAMS set successfully */ bool setup_done[2]; /* the setup of the SOF PCM device is done */ bool pending_stop[2]; /* only used if (!pcm_ops->platform_stop_during_hw_free) */ + bool compr_started[2]; /* compress stream has been started */ /* Must be last - ends in a flex-array member. */ struct snd_soc_tplg_pcm pcm; @@ -634,7 +642,12 @@ struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, int *direction); void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); void snd_sof_pcm_init_elapsed_work(struct work_struct *work); - +int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, + struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params, + int dir); +struct snd_sof_widget *snd_sof_find_swidget_by_comp_id(struct snd_sof_dev *sdev, + int comp_id); /* * snd_sof_pcm specific wrappers for dev_dbg() and dev_err() to provide * consistent and useful prints. @@ -660,6 +673,9 @@ void snd_sof_pcm_init_elapsed_work(struct work_struct *work); #if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream); void snd_sof_compr_init_elapsed_work(struct work_struct *work); +int snd_sof_compr_create_page_table(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + unsigned char *dma_area, size_t size); #else static inline void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) { } static inline void snd_sof_compr_init_elapsed_work(struct work_struct *work) { } diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 38753b088fc1..560be58611c0 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -256,6 +256,18 @@ struct snd_sof_dsp_ops { /* pcm ack */ int (*pcm_ack)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); /* optional */ + int (*compr_open)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); + int (*compr_close)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); + int (*compr_hw_params)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_sof_platform_stream_params *platform_params); + int (*compr_hw_free)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream); + int (*compr_trigger)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + int cmd); + int (*compr_pointer)(struct snd_sof_dev *sdev, struct snd_compr_stream *cstream, + struct snd_compr_tstamp64 *tstamp); + u64 (*compr_get_dai_frame_counter)(struct snd_sof_dev *sdev, + struct snd_compr_stream *cstream); /* * optional callback to retrieve the number of frames left/arrived from/to * the DSP on the DAI side (link/codec/DMIC/etc). diff --git a/sound/soc/sof/sof-utils.c b/sound/soc/sof/sof-utils.c index f70089317b8c..59dc10196958 100644 --- a/sound/soc/sof/sof-utils.c +++ b/sound/soc/sof/sof-utils.c @@ -24,12 +24,20 @@ int snd_sof_create_page_table(struct device *dev, struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size) + struct snd_dma_buffer *page_table, size_t size) { int i, pages; pages = snd_sgbuf_aligned_pages(size); + if (pages < 1 || page_table->bytes < sizeof(u32) || + pages > SOF_PAGE_TABLE_MAX_PFNS(page_table->bytes)) { + dev_err(dev, + "Can not store %d pages in a %zu bytes page table\n", + pages, page_table->bytes); + return -EINVAL; + } + dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n", dmab->area, size, pages); @@ -45,7 +53,7 @@ int snd_sof_create_page_table(struct device *dev, u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; u8 *pg_table; - pg_table = (u8 *)(page_table + idx); + pg_table = (u8 *)(page_table->area + idx); /* * pagetable compression: diff --git a/sound/soc/sof/sof-utils.h b/sound/soc/sof/sof-utils.h index 9ac6de9a6d6a..58e5822a63f0 100644 --- a/sound/soc/sof/sof-utils.h +++ b/sound/soc/sof/sof-utils.h @@ -12,8 +12,17 @@ struct snd_dma_buffer; struct device; +/* + * Number of PFNs which can be stored in a page table of @bytes size. + * The PFNs are compressed to 2.5 bytes each but they are written with 32 bit + * accesses, therefore the last PFN can reach up to 3 bytes past the space it + * needs for itself. + */ +#define SOF_PAGE_TABLE_MAX_PFNS(bytes) \ + (((((bytes) - sizeof(u32)) << 1) + 1) / 5 + 1) + int snd_sof_create_page_table(struct device *dev, struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size); + struct snd_dma_buffer *page_table, size_t size); #endif diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 8338133899ab..750916b57def 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1530,6 +1530,8 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, list_add(&dai->list, &sdev->dai_list); swidget->private = dai; break; + case snd_soc_dapm_decoder: + case snd_soc_dapm_encoder: case snd_soc_dapm_effect: /* check we have some tokens - we need at least process type */ if (le32_to_cpu(tw->priv.size) == 0) { @@ -2517,7 +2519,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) int i; const char **tplg_files __free(kfree) = - kcalloc(scomp->card->num_links, sizeof(char *), GFP_KERNEL); + kcalloc(scomp->card->num_links, sizeof(*tplg_files), + GFP_KERNEL); if (!tplg_files) return -ENOMEM; diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c index e5249924b54d..f790b959cd4e 100644 --- a/sound/soc/sprd/sprd-pcm-compress.c +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -145,6 +145,7 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, enum dma_slave_buswidth bus_width; int period, period_cnt, sg_num = 2; dma_addr_t src_addr, dst_addr; + struct dma_chan *chan; unsigned long flags; int ret, j; @@ -153,12 +154,12 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, return -EINVAL; } - dma->chan = dma_request_slave_channel(dev, - dma_params->chan_name[channel]); - if (!dma->chan) { + chan = dma_request_chan(dev, dma_params->chan_name[channel]); + if (IS_ERR(chan)) { dev_err(dev, "failed to request dma channel\n"); return -ENODEV; } + dma->chan = chan; sgt = sg = kzalloc_objs(*sg, sg_num); if (!sg) { diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index f509a4601de2..bbd7516f8195 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -170,15 +170,16 @@ static int sprd_pcm_request_dma_channel(struct snd_soc_component *component, for (i = 0; i < channels; i++) { struct sprd_pcm_dma_data *data = &dma_private->data[i]; + struct dma_chan *chan; - data->chan = dma_request_slave_channel(dev, - dma_params->chan_name[i]); - if (!data->chan) { + chan = dma_request_chan(dev, dma_params->chan_name[i]); + if (IS_ERR(chan)) { dev_err(dev, "failed to request dma channel:%s\n", dma_params->chan_name[i]); sprd_pcm_release_dma_channel(substream); return -ENODEV; } + data->chan = chan; } return 0; @@ -459,10 +460,9 @@ static const struct snd_soc_component_driver sprd_soc_component = { static int sprd_soc_platform_probe(struct platform_device *pdev) { - struct device_node *np = pdev->dev.of_node; int ret; - ret = of_reserved_mem_device_init_by_idx(&pdev->dev, np, 0); + ret = devm_of_reserved_mem_device_init(&pdev->dev); if (ret) dev_warn(&pdev->dev, "no reserved DMA memory for audio platform device\n"); diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index e4b9799ad9b2..844ddcfc421a 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1028,16 +1028,27 @@ static int uni_player_parse_dt_audio_glue(struct platform_device *pdev, return 0; } +static const u64 uni_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops uni_player_dai_ops = { - .startup = uni_player_startup, - .shutdown = uni_player_shutdown, - .prepare = uni_player_prepare, - .probe = sti_uniperiph_dai_probe, - .trigger = uni_player_trigger, - .hw_params = sti_uniperiph_dai_hw_params, - .set_fmt = sti_uniperiph_dai_set_fmt, - .set_sysclk = uni_player_set_sysclk, - .set_tdm_slot = sti_uniperiph_set_tdm_slot + .startup = uni_player_startup, + .shutdown = uni_player_shutdown, + .prepare = uni_player_prepare, + .probe = sti_uniperiph_dai_probe, + .trigger = uni_player_trigger, + .hw_params = sti_uniperiph_dai_hw_params, + .set_fmt = sti_uniperiph_dai_set_fmt, + .set_sysclk = uni_player_set_sysclk, + .set_tdm_slot = sti_uniperiph_set_tdm_slot, + .auto_selectable_formats = &uni_selectable_formats, + .num_auto_selectable_formats = 1, }; int uni_player_init(struct platform_device *pdev, diff --git a/sound/soc/sti/uniperif_reader.c b/sound/soc/sti/uniperif_reader.c index 5347f9620f25..acdd5206ff9c 100644 --- a/sound/soc/sti/uniperif_reader.c +++ b/sound/soc/sti/uniperif_reader.c @@ -391,15 +391,26 @@ static void uni_reader_shutdown(struct snd_pcm_substream *substream, reader->substream = NULL; } +static const u64 uni_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops uni_reader_dai_ops = { - .startup = uni_reader_startup, - .shutdown = uni_reader_shutdown, - .prepare = uni_reader_prepare, - .probe = sti_uniperiph_dai_probe, - .trigger = uni_reader_trigger, - .hw_params = sti_uniperiph_dai_hw_params, - .set_fmt = sti_uniperiph_dai_set_fmt, - .set_tdm_slot = sti_uniperiph_set_tdm_slot + .startup = uni_reader_startup, + .shutdown = uni_reader_shutdown, + .prepare = uni_reader_prepare, + .probe = sti_uniperiph_dai_probe, + .trigger = uni_reader_trigger, + .hw_params = sti_uniperiph_dai_hw_params, + .set_fmt = sti_uniperiph_dai_set_fmt, + .set_tdm_slot = sti_uniperiph_set_tdm_slot, + .auto_selectable_formats = &uni_selectable_formats, + .num_auto_selectable_formats = 1, }; int uni_reader_init(struct platform_device *pdev, diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c index 83b51893b37c..22196f789ae4 100644 --- a/sound/soc/stm/stm32_i2s.c +++ b/sound/soc/stm/stm32_i2s.c @@ -1089,6 +1089,16 @@ static const struct regmap_config stm32_h7_i2s_regmap_conf = { .cache_type = REGCACHE_FLAT, }; +static const u64 stm32_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = { .probe = stm32_i2s_dai_probe, .set_sysclk = stm32_i2s_set_sysclk, @@ -1097,6 +1107,8 @@ static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = { .hw_params = stm32_i2s_hw_params, .trigger = stm32_i2s_trigger, .shutdown = stm32_i2s_shutdown, + .auto_selectable_formats = &stm32_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_pcm_hardware stm32_i2s_pcm_hw = { @@ -1185,7 +1197,7 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, struct device_node *np = pdev->dev.of_node; struct reset_control *rst; struct resource *res; - int irq, ret; + int ret; if (!np) return -ENODEV; @@ -1231,16 +1243,6 @@ static int stm32_i2s_parse_dt(struct platform_device *pdev, return ret; } - /* Get irqs */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, - dev_name(&pdev->dev), i2s); - if (ret) - return ret; - /* Reset */ rst = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL); if (IS_ERR(rst)) @@ -1265,7 +1267,7 @@ static int stm32_i2s_probe(struct platform_device *pdev) { struct stm32_i2s_data *i2s; u32 val; - int ret; + int irq, ret; i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); if (!i2s) @@ -1291,6 +1293,15 @@ static int stm32_i2s_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, PTR_ERR(i2s->regmap), "Regmap init error\n"); + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, 0, + dev_name(&pdev->dev), i2s); + if (ret) + return ret; + ret = snd_dmaengine_pcm_register(&pdev->dev, &stm32_i2s_pcm_config, 0); if (ret) return ret; diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index 9a201bdb306f..d00377efe3f9 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -1358,6 +1358,17 @@ static int stm32_sai_dai_probe(struct snd_soc_dai *cpu_dai) return stm32_sai_sub_reg_up(sai, STM_SAI_CR1_REGX, cr1_mask, cr1); } +static const u64 stm32_sai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = { .probe = stm32_sai_dai_probe, .set_sysclk = stm32_sai_set_sysclk, @@ -1368,6 +1379,8 @@ static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops = { .trigger = stm32_sai_trigger, .shutdown = stm32_sai_shutdown, .pcm_new = stm32_sai_pcm_new, + .auto_selectable_formats = &stm32_sai_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops2 = { @@ -1379,6 +1392,8 @@ static const struct snd_soc_dai_ops stm32_sai_pcm_dai_ops2 = { .hw_params = stm32_sai_hw_params, .trigger = stm32_sai_trigger, .shutdown = stm32_sai_shutdown, + .auto_selectable_formats = &stm32_sai_selectable_formats, + .num_auto_selectable_formats = 1, }; static int stm32_sai_pcm_process_spdif(struct snd_pcm_substream *substream, diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index 13a07ef93240..88863dbd05d9 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -400,7 +400,7 @@ static int stm32_spdifrx_dma_ctrl_register(struct device *dev, spdifrx->ctrl_chan = dma_request_chan(dev, "rx-ctrl"); if (IS_ERR(spdifrx->ctrl_chan)) return dev_err_probe(dev, PTR_ERR(spdifrx->ctrl_chan), - "dma_request_slave_channel error\n"); + "dma_request_chan error\n"); spdifrx->dmab = devm_kzalloc(dev, sizeof(struct snd_dma_buffer), GFP_KERNEL); diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index 716fa4d872ff..43e6ea2563fe 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -1106,6 +1106,17 @@ static int sun4i_i2s_dai_startup(struct snd_pcm_substream *sub, struct snd_soc_d i2s->variant->pcm_formats); } +static const u64 sun4i_i2s_selectable_format = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops sun4i_i2s_dai_ops = { .probe = sun4i_i2s_dai_probe, .startup = sun4i_i2s_dai_startup, @@ -1114,6 +1125,8 @@ static const struct snd_soc_dai_ops sun4i_i2s_dai_ops = { .set_sysclk = sun4i_i2s_set_sysclk, .set_tdm_slot = sun4i_i2s_set_tdm_slot, .trigger = sun4i_i2s_trigger, + .auto_selectable_formats = &sun4i_i2s_selectable_format, + .num_auto_selectable_formats = 1, }; #define SUN4I_FORMATS_ALL (SNDRV_PCM_FMTBIT_S16_LE | \ diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c index 6bd42da57ac0..2e4a205e4e56 100644 --- a/sound/soc/sunxi/sun8i-codec.c +++ b/sound/soc/sunxi/sun8i-codec.c @@ -688,12 +688,25 @@ static int sun8i_codec_hw_free(struct snd_pcm_substream *substream, return 0; } +static const u64 sun8i_codec_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops sun8i_codec_dai_ops = { .set_fmt = sun8i_codec_set_fmt, .set_tdm_slot = sun8i_codec_set_tdm_slot, .startup = sun8i_codec_startup, .hw_params = sun8i_codec_hw_params, .hw_free = sun8i_codec_hw_free, + .auto_selectable_formats = &sun8i_codec_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver sun8i_codec_dais[] = { diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index fdc954028d62..dd3b2d3d6c4c 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -239,6 +239,16 @@ config SND_SOC_TEGRA_WM8962 boards using the WM8962 codec. Currently, the supported boards are Microsoft Surface RT. +config SND_SOC_TEGRA_WM8994 + tristate "SoC Audio support for Tegra boards using a WM8994 codec" + depends on I2C && GPIOLIB && MFD_WM8994 + select SND_SOC_TEGRA_MACHINE_DRV + select SND_SOC_WM8994 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the WM8994 codec. Currently, the supported boards are + LG Optimus X2 P990. + config SND_SOC_TEGRA_WM9712 tristate "SoC Audio support for Tegra boards using a WM9712 codec" depends on GPIOLIB diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 1c18ef6971c0..b8f90149bfa6 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -43,10 +43,12 @@ obj-$(CONFIG_SND_SOC_TEGRA210_OPE) += snd-soc-tegra210-ope.o # Tegra machine Support snd-soc-tegra-wm8903-y := tegra_wm8903.o snd-soc-tegra-wm8962-y := tegra_wm8962.o +snd-soc-tegra-wm8994-y := tegra_wm8994.o snd-soc-tegra-machine-y := tegra_asoc_machine.o snd-soc-tegra-audio-graph-card-y := tegra_audio_graph_card.o obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o obj-$(CONFIG_SND_SOC_TEGRA_WM8962) += snd-soc-tegra-wm8962.o +obj-$(CONFIG_SND_SOC_TEGRA_WM8994) += snd-soc-tegra-wm8994.o obj-$(CONFIG_SND_SOC_TEGRA_MACHINE_DRV) += snd-soc-tegra-machine.o obj-$(CONFIG_SND_SOC_TEGRA_AUDIO_GRAPH_CARD) += snd-soc-tegra-audio-graph-card.o diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index d9ab210ad69a..b31f9477cb2e 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -309,12 +309,22 @@ static int tegra20_i2s_startup(struct snd_pcm_substream *substream, SNDRV_PCM_HW_PARAM_RATE, -1); } +static const u64 tegra20_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = { .probe = tegra20_i2s_probe, .set_fmt = tegra20_i2s_set_fmt, .hw_params = tegra20_i2s_hw_params, .trigger = tegra20_i2s_trigger, .startup = tegra20_i2s_startup, + .auto_selectable_formats = &tegra20_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver tegra20_i2s_dai_template = { diff --git a/sound/soc/tegra/tegra210_adx.c b/sound/soc/tegra/tegra210_adx.c index e75bf50f8fb0..f2adf1c6e37e 100644 --- a/sound/soc/tegra/tegra210_adx.c +++ b/sound/soc/tegra/tegra210_adx.c @@ -676,11 +676,13 @@ static int tegra210_adx_platform_probe(struct platform_device *pdev) void __iomem *regs; int err, i; - adx = devm_kzalloc(dev, sizeof(*adx), GFP_KERNEL); + soc_data = of_device_get_match_data(dev); + adx = devm_kzalloc(dev, + struct_size(adx, map, soc_data->ram_depth * TEGRA_ADX_SLOTS_PER_WORD), + GFP_KERNEL); if (!adx) return -ENOMEM; - soc_data = of_device_get_match_data(dev); adx->soc_data = soc_data; dev_set_drvdata(dev, adx); @@ -697,12 +699,6 @@ static int tegra210_adx_platform_probe(struct platform_device *pdev) regcache_cache_only(adx->regmap, true); - adx->map = devm_kcalloc(dev, - soc_data->ram_depth * TEGRA_ADX_SLOTS_PER_WORD, - sizeof(*adx->map), GFP_KERNEL); - if (!adx->map) - return -ENOMEM; - adx->byte_mask = devm_kcalloc(dev, soc_data->byte_mask_size, sizeof(*adx->byte_mask), GFP_KERNEL); if (!adx->byte_mask) diff --git a/sound/soc/tegra/tegra210_adx.h b/sound/soc/tegra/tegra210_adx.h index a6298c3dcca5..e5f1b44b2e76 100644 --- a/sound/soc/tegra/tegra210_adx.h +++ b/sound/soc/tegra/tegra210_adx.h @@ -92,8 +92,8 @@ struct tegra210_adx_soc_data { struct tegra210_adx { struct regmap *regmap; unsigned int *byte_mask; - u16 *map; const struct tegra210_adx_soc_data *soc_data; + u16 map[]; }; #endif diff --git a/sound/soc/tegra/tegra210_i2s.c b/sound/soc/tegra/tegra210_i2s.c index 79a2f898ea28..f2cfe3b5ccce 100644 --- a/sound/soc/tegra/tegra210_i2s.c +++ b/sound/soc/tegra/tegra210_i2s.c @@ -739,11 +739,24 @@ static int tegra210_i2s_hw_params(struct snd_pcm_substream *substream, cif_conf.client_ch); } +static const u64 tegra210_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops tegra210_i2s_dai_ops = { .set_fmt = tegra210_i2s_set_fmt, .hw_params = tegra210_i2s_hw_params, .set_bclk_ratio = tegra210_i2s_set_dai_bclk_ratio, .set_tdm_slot = tegra210_i2s_set_tdm_slot, + .auto_selectable_formats = &tegra210_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver tegra210_i2s_dais[] = { diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c index 1d7b3aed51fd..69acff73f68a 100644 --- a/sound/soc/tegra/tegra30_i2s.c +++ b/sound/soc/tegra/tegra30_i2s.c @@ -302,12 +302,22 @@ static int tegra30_i2s_probe(struct snd_soc_dai *dai) return 0; } +static const u64 tegra30_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops tegra30_i2s_dai_ops = { .probe = tegra30_i2s_probe, .set_fmt = tegra30_i2s_set_fmt, .hw_params = tegra30_i2s_hw_params, .trigger = tegra30_i2s_trigger, .set_tdm_slot = tegra30_i2s_set_tdm, + .auto_selectable_formats = &tegra30_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static const struct snd_soc_dai_driver tegra30_i2s_dai_template = { diff --git a/sound/soc/tegra/tegra_asoc_machine.c b/sound/soc/tegra/tegra_asoc_machine.c index 50b23bbb637b..e2d0e5947339 100644 --- a/sound/soc/tegra/tegra_asoc_machine.c +++ b/sound/soc/tegra/tegra_asoc_machine.c @@ -122,6 +122,8 @@ static const struct snd_soc_dapm_widget tegra_machine_dapm_widgets[] = { SND_SOC_DAPM_LINE("Line In Jack", NULL), SND_SOC_DAPM_LINE("Line In", NULL), SND_SOC_DAPM_LINE("LineIn", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_INPUT("FM Radio"), }; static const struct snd_kcontrol_new tegra_machine_controls[] = { @@ -550,6 +552,10 @@ int tegra_asoc_machine_probe(struct platform_device *pdev) if (asoc->add_common_snd_ops) card->dai_link->ops = &tegra_machine_snd_ops; + err = snd_soc_of_parse_aux_devs(card, "nvidia,aux-devs"); + if (err) + return err; + if (!card->owner) card->owner = THIS_MODULE; if (!card->driver_name) diff --git a/sound/soc/tegra/tegra_wm8994.c b/sound/soc/tegra/tegra_wm8994.c new file mode 100644 index 000000000000..1550faeba725 --- /dev/null +++ b/sound/soc/tegra/tegra_wm8994.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// tegra_wm8994.c - Tegra machine ASoC driver for boards using WM8994 codec. +// +// Copyright (C) 2026 Svyatoslav Ryhel +// +// Based on tegra_wm8994 code copyright/by: +// +// Author: Sumit Bhattacharya +// Copyright (C) 2010-2011 - NVIDIA, Inc. +// +// Based on code copyright/by: +// +// (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. +// +// Copyright 2007 Wolfson Microelectronics PLC. +// Author: Graeme Gregory +// graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/wm8994.h" + +#include "tegra_asoc_machine.h" + +static struct snd_soc_jack_pin tegra_wm8994_mic_jack_pins[] = { + { .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE }, +}; + +static unsigned int tegra_wm8994_mclk_rate(unsigned int srate) +{ + unsigned int mclk; + + switch (srate) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + mclk = 12000000; + break; + } + + return mclk; +} + +static const struct snd_kcontrol_new tegra_wm8994_controls[] = { + SOC_DAPM_PIN_SWITCH("Speakers"), + SOC_DAPM_PIN_SWITCH("Int Spk"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Internal Mic 1"), + SOC_DAPM_PIN_SWITCH("Internal Mic 2"), + SOC_DAPM_PIN_SWITCH("Headphones"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Line Out"), + SOC_DAPM_PIN_SWITCH("FM Radio"), +}; + +static int tegra_wm8994_init(struct snd_soc_pcm_runtime *rtd) +{ + struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card); + int err; + + err = tegra_asoc_machine_init(rtd); + if (err) + return err; + + if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) { + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + err = snd_soc_card_jack_new_pins(rtd->card, "Mic Jack", + SND_JACK_MICROPHONE, + machine->mic_jack, + tegra_wm8994_mic_jack_pins, + ARRAY_SIZE(tegra_wm8994_mic_jack_pins)); + if (err) { + dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err); + return err; + } + + wm8994_mic_detect(component, machine->mic_jack, 1); + } + + return 0; +} + +static int tegra_wm8994_remove(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link = &card->dai_link[0]; + struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, link); + struct tegra_machine *machine = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + + if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) + wm8994_mic_detect(component, NULL, 1); + + return 0; +} + +SND_SOC_DAILINK_DEFS(wm8994_hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tegra_wm8994_dai = { + .name = "WM8994", + .stream_name = "WM8994 PCM", + .init = tegra_wm8994_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(wm8994_hifi), +}; + +static struct snd_soc_card snd_soc_tegra_wm8994 = { + .components = "codec:wm8994", + .owner = THIS_MODULE, + .dai_link = &tegra_wm8994_dai, + .num_links = 1, + .controls = tegra_wm8994_controls, + .num_controls = ARRAY_SIZE(tegra_wm8994_controls), + .remove = tegra_wm8994_remove, + .fully_routed = true, +}; + +static const struct tegra_asoc_data tegra_wm8994_data = { + .mclk_rate = tegra_wm8994_mclk_rate, + .mclk_id = WM8994_SYSCLK_MCLK1, + .card = &snd_soc_tegra_wm8994, + .add_common_dapm_widgets = true, + .add_common_snd_ops = true, + .add_mic_jack = true, + .add_hp_jack = true, +}; + +static const struct of_device_id tegra_wm8994_of_match[] = { + { .compatible = "nvidia,tegra-audio-wm8994", .data = &tegra_wm8994_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_wm8994_of_match); + +static struct platform_driver tegra_wm8994_driver = { + .driver = { + .name = "tegra-wm8994", + .of_match_table = tegra_wm8994_of_match, + .pm = &snd_soc_pm_ops, + }, + .probe = tegra_asoc_machine_probe, +}; +module_platform_driver(tegra_wm8994_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("Tegra+WM8994 machine ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/ti/davinci-i2s.c b/sound/soc/ti/davinci-i2s.c index 059967f0e632..3614f614117d 100644 --- a/sound/soc/ti/davinci-i2s.c +++ b/sound/soc/ti/davinci-i2s.c @@ -49,7 +49,7 @@ * incompatible with ASP and with either McBSP. * * In short: this uses ASP to implement I2S, not McBSP. - * And it won't be the only DaVinci implemention of I2S. + * And it won't be the only DaVinci implementation of I2S. */ #define DAVINCI_MCBSP_DRR_REG 0x00 #define DAVINCI_MCBSP_DXR_REG 0x04 @@ -743,6 +743,17 @@ static int davinci_i2s_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 davinci_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_CONT | + SND_SOC_POSSIBLE_DAIFMT_GATED | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops davinci_i2s_dai_ops = { .probe = davinci_i2s_dai_probe, .shutdown = davinci_i2s_shutdown, @@ -752,7 +763,8 @@ static const struct snd_soc_dai_ops davinci_i2s_dai_ops = { .set_fmt = davinci_i2s_set_dai_fmt, .set_clkdiv = davinci_i2s_dai_set_clkdiv, .set_tdm_slot = davinci_i2s_set_tdm_slot, - + .auto_selectable_formats = &davinci_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver davinci_i2s_dai = { diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index 79239131b659..481d1042ddb6 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -799,6 +799,7 @@ static int __davinci_mcasp_set_clkdiv(struct davinci_mcasp *mcasp, int div_id, break; default: + pm_runtime_put(mcasp->dev); return -EINVAL; } @@ -1818,7 +1819,7 @@ static int davinci_mcasp_startup(struct snd_pcm_substream *substream, !mcasp->async_mode) max_channels = mcasp->channels; /* - * But we can always allow channels upto the amount of + * But we can always allow channels up to the amount of * the available tdm_slots. */ if (max_channels < tdm_slots) @@ -1993,6 +1994,18 @@ static int davinci_mcasp_dai_probe(struct snd_soc_dai *dai) return 0; } +static const u64 davinci_mcasp_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_AC97 | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF; + static const struct snd_soc_dai_ops davinci_mcasp_dai_ops = { .probe = davinci_mcasp_dai_probe, .startup = davinci_mcasp_startup, @@ -2004,6 +2017,8 @@ static const struct snd_soc_dai_ops davinci_mcasp_dai_ops = { .set_clkdiv = davinci_mcasp_set_clkdiv, .set_sysclk = davinci_mcasp_set_sysclk, .set_tdm_slot = davinci_mcasp_set_tdm_slot, + .auto_selectable_formats = &davinci_mcasp_selectable_formats, + .num_auto_selectable_formats = 1, }; #define DAVINCI_MCASP_RATES SNDRV_PCM_RATE_8000_192000 diff --git a/sound/soc/ti/omap-mcbsp.c b/sound/soc/ti/omap-mcbsp.c index 26af616c33f5..889e357b64d0 100644 --- a/sound/soc/ti/omap-mcbsp.c +++ b/sound/soc/ti/omap-mcbsp.c @@ -1237,7 +1237,7 @@ static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, break; case OMAP_MCBSP_SYSCLK_CLKR_EXT: regs->pcr0 |= SCLKME; - /* Disable ouput on CLKR pin in master mode */ + /* Disable output on CLKR pin in master mode */ regs->pcr0 &= ~CLKRM; break; default: @@ -1269,6 +1269,16 @@ static int omap_mcbsp_remove(struct snd_soc_dai *dai) return 0; } +static const u64 mcbsp_mcbsp_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B | + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_IB_NF | + SND_SOC_POSSIBLE_DAIFMT_IB_IF; + static const struct snd_soc_dai_ops mcbsp_dai_ops = { .probe = omap_mcbsp_probe, .remove = omap_mcbsp_remove, @@ -1281,6 +1291,8 @@ static const struct snd_soc_dai_ops mcbsp_dai_ops = { .set_fmt = omap_mcbsp_dai_set_dai_fmt, .set_clkdiv = omap_mcbsp_dai_set_clkdiv, .set_sysclk = omap_mcbsp_dai_set_dai_sysclk, + .auto_selectable_formats = &mcbsp_mcbsp_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver omap_mcbsp_dai = { diff --git a/sound/soc/uniphier/aio-cpu.c b/sound/soc/uniphier/aio-cpu.c index 78c82bf42a8f..b263526f1990 100644 --- a/sound/soc/uniphier/aio-cpu.c +++ b/sound/soc/uniphier/aio-cpu.c @@ -440,6 +440,11 @@ static int uniphier_aio_pxs2_probe(struct snd_soc_dai *dai) return 0; } +static const u64 uniphier_aio_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | + SND_SOC_POSSIBLE_DAIFMT_LEFT_J; + const struct snd_soc_dai_ops uniphier_aio_i2s_ld11_ops = { .probe = uniphier_aio_ld11_probe, .remove = uniphier_aio_dai_remove, @@ -451,6 +456,8 @@ const struct snd_soc_dai_ops uniphier_aio_i2s_ld11_ops = { .hw_params = uniphier_aio_hw_params, .hw_free = uniphier_aio_hw_free, .prepare = uniphier_aio_prepare, + .auto_selectable_formats = &uniphier_aio_selectable_formats, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(uniphier_aio_i2s_ld11_ops); @@ -492,6 +499,8 @@ const struct snd_soc_dai_ops uniphier_aio_i2s_pxs2_ops = { .hw_params = uniphier_aio_hw_params, .hw_free = uniphier_aio_hw_free, .prepare = uniphier_aio_prepare, + .auto_selectable_formats = &uniphier_aio_selectable_formats, + .num_auto_selectable_formats = 1, }; EXPORT_SYMBOL_GPL(uniphier_aio_i2s_pxs2_ops); diff --git a/sound/soc/uniphier/aio-dma.c b/sound/soc/uniphier/aio-dma.c index c01eae55d4fc..389a45319dfc 100644 --- a/sound/soc/uniphier/aio-dma.c +++ b/sound/soc/uniphier/aio-dma.c @@ -66,8 +66,7 @@ static void aiodma_compr_irq(struct uniphier_aio_sub *sub) static irqreturn_t aiodma_irq(int irq, void *p) { - struct platform_device *pdev = p; - struct uniphier_aio_chip *chip = platform_get_drvdata(pdev); + struct uniphier_aio_chip *chip = p; irqreturn_t ret = IRQ_NONE; int i, j; @@ -263,7 +262,7 @@ int uniphier_aiodma_soc_register_platform(struct platform_device *pdev) return irq; ret = devm_request_irq(dev, irq, aiodma_irq, - IRQF_SHARED, dev_name(dev), pdev); + IRQF_SHARED, dev_name(dev), chip); if (ret) return ret; diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h index 1900ea01ce3d..87b27256cf80 100644 --- a/sound/soc/uniphier/aio.h +++ b/sound/soc/uniphier/aio.h @@ -183,7 +183,7 @@ struct uniphier_aio_selector { * * These are examples for sound data paths: * - * For caputure device: + * For capture device: * (outer of AIO) -> iport -> iif -> ch -> rb -> (CPU) * For playback device: * (CPU) -> rb -> ch -> oif -> oport -> (outer of AIO) diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig index 113058060350..f3c7c216267c 100644 --- a/sound/soc/ux500/Kconfig +++ b/sound/soc/ux500/Kconfig @@ -6,28 +6,19 @@ menuconfig SND_SOC_UX500 tristate "Ux500" depends on SND_SOC depends on MFD_DB8500_PRCMU + depends on AB8500_CORE && AB8500_GPADC + select SND_SOC_AB8500_CODEC + select SND_SOC_UX500_PLAT_MSP_I2S + select SND_SOC_UX500_PLAT_DMA help - Say Y if you want to enable ASoC-support for - any of the Ux500 platforms (e.g. U8500). + Say Y if you want to enable ASoC support for Ux500 platforms + using the AB8500 or AB8505 codec. config SND_SOC_UX500_PLAT_MSP_I2S tristate depends on SND_SOC_UX500 config SND_SOC_UX500_PLAT_DMA - tristate "Platform - DB8500 (DMA)" + tristate depends on SND_SOC_UX500 select SND_SOC_GENERIC_DMAENGINE_PCM - help - Say Y if you want to enable the Ux500 platform-driver. - -config SND_SOC_UX500_MACH_MOP500 - tristate "Machine - MOP500 (Ux500 + AB8500)" - depends on AB8500_CORE && AB8500_GPADC && SND_SOC_UX500 - select SND_SOC_AB8500_CODEC - select SND_SOC_UX500_PLAT_MSP_I2S - select SND_SOC_UX500_PLAT_DMA - help - Select this to enable the MOP500 machine-driver. - This will enable platform-drivers for: Ux500 - This will enable codec-drivers for: AB8500 diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile index a63787d9d664..83e7aeff70f8 100644 --- a/sound/soc/ux500/Makefile +++ b/sound/soc/ux500/Makefile @@ -6,6 +6,3 @@ obj-$(CONFIG_SND_SOC_UX500_PLAT_MSP_I2S) += snd-soc-ux500-plat-msp-i2s.o snd-soc-ux500-plat-dma-y := ux500_pcm.o obj-$(CONFIG_SND_SOC_UX500_PLAT_DMA) += snd-soc-ux500-plat-dma.o - -snd-soc-ux500-mach-mop500-y := mop500.o mop500_ab8500.o -obj-$(CONFIG_SND_SOC_UX500_MACH_MOP500) += snd-soc-ux500-mach-mop500.o diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c deleted file mode 100644 index 6d196b4b8802..000000000000 --- a/sound/soc/ux500/mop500.c +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Author: Ola Lilja (ola.o.lilja@stericsson.com) - * for ST-Ericsson. - */ - -#include - -#include -#include -#include -#include - -#include -#include - -#include "ux500_pcm.h" -#include "ux500_msp_dai.h" - -#include "mop500_ab8500.h" - -/* Define the whole MOP500 soundcard, linking platform to the codec-drivers */ -SND_SOC_DAILINK_DEFS(link1, - DAILINK_COMP_ARRAY(COMP_CPU("ux500-msp-i2s.1")), - DAILINK_COMP_ARRAY(COMP_CODEC("ab8500-codec.0", "ab8500-codec-dai.0")), - DAILINK_COMP_ARRAY(COMP_PLATFORM("ux500-msp-i2s.1"))); - -SND_SOC_DAILINK_DEFS(link2, - DAILINK_COMP_ARRAY(COMP_CPU("ux500-msp-i2s.3")), - DAILINK_COMP_ARRAY(COMP_CODEC("ab8500-codec.0", "ab8500-codec-dai.1")), - DAILINK_COMP_ARRAY(COMP_PLATFORM("ux500-msp-i2s.3"))); - -static struct snd_soc_dai_link mop500_dai_links[] = { - { - .name = "ab8500_0", - .stream_name = "ab8500_0", - .init = mop500_ab8500_machine_init, - .ops = mop500_ab8500_ops, - SND_SOC_DAILINK_REG(link1), - }, - { - .name = "ab8500_1", - .stream_name = "ab8500_1", - .init = NULL, - .ops = mop500_ab8500_ops, - SND_SOC_DAILINK_REG(link2), - }, -}; - -static struct snd_soc_card mop500_card = { - .name = "MOP500-card", - .owner = THIS_MODULE, - .probe = NULL, - .dai_link = mop500_dai_links, - .num_links = ARRAY_SIZE(mop500_dai_links), -}; - -static void mop500_of_node_put(void) -{ - int i; - - for (i = 0; i < 2; i++) - of_node_put(mop500_dai_links[i].cpus->of_node); - - /* Both links use the same codec, which is refcounted only once */ - of_node_put(mop500_dai_links[0].codecs->of_node); -} - -static int mop500_of_probe(struct snd_soc_card *card) -{ - struct device *dev = card->dev; - struct device_node *codec_np, *msp_np[2]; - struct device_node *np = dev->of_node; - int i; - - msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0); - msp_np[1] = of_parse_phandle(np, "stericsson,cpu-dai", 1); - codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0); - - if (!(msp_np[0] && msp_np[1] && codec_np)) { - dev_err(dev, "Phandle missing or invalid\n"); - for (i = 0; i < 2; i++) - of_node_put(msp_np[i]); - of_node_put(codec_np); - return -EINVAL; - } - - for (i = 0; i < 2; i++) { - mop500_dai_links[i].cpus->of_node = msp_np[i]; - mop500_dai_links[i].cpus->dai_name = NULL; - mop500_dai_links[i].platforms->of_node = msp_np[i]; - mop500_dai_links[i].platforms->name = NULL; - mop500_dai_links[i].codecs->of_node = codec_np; - mop500_dai_links[i].codecs->name = NULL; - } - - snd_soc_of_parse_card_name(card, "stericsson,card-name"); - - return 0; -} - -static int mop500_probe(struct platform_device *pdev) -{ - int ret; - - dev_dbg(&pdev->dev, "%s: Enter.\n", __func__); - - mop500_card.dev = &pdev->dev; - - ret = mop500_of_probe(&mop500_card); - if (ret) - return ret; - - dev_dbg(&pdev->dev, "%s: Card %s: Set platform drvdata.\n", - __func__, mop500_card.name); - - snd_soc_card_set_drvdata(&mop500_card, NULL); - - dev_dbg(&pdev->dev, "%s: Card %s: num_links = %d\n", - __func__, mop500_card.name, mop500_card.num_links); - dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: name = %s\n", - __func__, mop500_card.name, mop500_card.dai_link[0].name); - dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: stream_name = %s\n", - __func__, mop500_card.name, - mop500_card.dai_link[0].stream_name); - - ret = snd_soc_register_card(&mop500_card); - if (ret) - dev_err(&pdev->dev, - "Error: snd_soc_register_card failed (%d)!\n", ret); - - return ret; -} - -static void mop500_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - - pr_debug("%s: Enter.\n", __func__); - - snd_soc_unregister_card(card); - mop500_ab8500_remove(card); - mop500_of_node_put(); -} - -static const struct of_device_id snd_soc_mop500_match[] = { - { .compatible = "stericsson,snd-soc-mop500", }, - {}, -}; -MODULE_DEVICE_TABLE(of, snd_soc_mop500_match); - -static struct platform_driver snd_soc_mop500_driver = { - .driver = { - .name = "snd-soc-mop500", - .of_match_table = snd_soc_mop500_match, - }, - .probe = mop500_probe, - .remove = mop500_remove, -}; - -module_platform_driver(snd_soc_mop500_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("ASoC MOP500 board driver"); -MODULE_AUTHOR("Ola Lilja"); diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c deleted file mode 100644 index feb683c55d11..000000000000 --- a/sound/soc/ux500/mop500_ab8500.c +++ /dev/null @@ -1,437 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Author: Ola Lilja , - * Kristoffer Karlsson - * for ST-Ericsson. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "ux500_pcm.h" -#include "ux500_msp_dai.h" -#include "mop500_ab8500.h" -#include "../codecs/ab8500-codec.h" - -#define TX_SLOT_MONO 0x0008 -#define TX_SLOT_STEREO 0x000a -#define RX_SLOT_MONO 0x0001 -#define RX_SLOT_STEREO 0x0003 -#define TX_SLOT_8CH 0x00FF -#define RX_SLOT_8CH 0x00FF - -#define DEF_TX_SLOTS TX_SLOT_STEREO -#define DEF_RX_SLOTS RX_SLOT_MONO - -#define DRIVERMODE_NORMAL 0 -#define DRIVERMODE_CODEC_ONLY 1 - -/* Slot configuration */ -static unsigned int tx_slots = DEF_TX_SLOTS; -static unsigned int rx_slots = DEF_RX_SLOTS; - -/* Configuration consistency parameters */ -static DEFINE_MUTEX(mop500_ab8500_params_lock); -static unsigned long mop500_ab8500_usage; -static int mop500_ab8500_rate; -static int mop500_ab8500_channels; - -/* Clocks */ -static const char * const enum_mclk[] = { - "SYSCLK", - "ULPCLK" -}; -enum mclk { - MCLK_SYSCLK, - MCLK_ULPCLK, -}; - -static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk); - -/* Private data for machine-part MOP500<->AB8500 */ -struct mop500_ab8500_drvdata { - /* Clocks */ - enum mclk mclk_sel; - struct clk *clk_ptr_intclk; - struct clk *clk_ptr_sysclk; - struct clk *clk_ptr_ulpclk; -}; - -static inline const char *get_mclk_str(enum mclk mclk_sel) -{ - switch (mclk_sel) { - case MCLK_SYSCLK: - return "SYSCLK"; - case MCLK_ULPCLK: - return "ULPCLK"; - default: - return "Unknown"; - } -} - -static int mop500_ab8500_set_mclk(struct device *dev, - struct mop500_ab8500_drvdata *drvdata) -{ - int status; - struct clk *clk_ptr; - - if (IS_ERR(drvdata->clk_ptr_intclk)) { - dev_err(dev, - "%s: ERROR: intclk not initialized!\n", __func__); - return -EIO; - } - - switch (drvdata->mclk_sel) { - case MCLK_SYSCLK: - clk_ptr = drvdata->clk_ptr_sysclk; - break; - case MCLK_ULPCLK: - clk_ptr = drvdata->clk_ptr_ulpclk; - break; - default: - return -EINVAL; - } - - if (IS_ERR(clk_ptr)) { - dev_err(dev, "%s: ERROR: %s not initialized!\n", __func__, - get_mclk_str(drvdata->mclk_sel)); - return -EIO; - } - - status = clk_set_parent(drvdata->clk_ptr_intclk, clk_ptr); - if (status) - dev_err(dev, - "%s: ERROR: Setting intclk parent to %s failed (ret = %d)!", - __func__, get_mclk_str(drvdata->mclk_sel), status); - else - dev_dbg(dev, - "%s: intclk parent changed to %s.\n", - __func__, get_mclk_str(drvdata->mclk_sel)); - - return status; -} - -/* - * Control-events - */ - -static int mclk_input_control_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); - struct mop500_ab8500_drvdata *drvdata = - snd_soc_card_get_drvdata(card); - - ucontrol->value.enumerated.item[0] = drvdata->mclk_sel; - - return 0; -} - -static int mclk_input_control_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); - struct mop500_ab8500_drvdata *drvdata = - snd_soc_card_get_drvdata(card); - unsigned int val = ucontrol->value.enumerated.item[0]; - - if (val > (unsigned int)MCLK_ULPCLK) - return -EINVAL; - if (drvdata->mclk_sel == val) - return 0; - - drvdata->mclk_sel = val; - - return 1; -} - -/* - * Controls - */ - -static struct snd_kcontrol_new mop500_ab8500_ctrls[] = { - SOC_ENUM_EXT("Master Clock Select", - soc_enum_mclk, - mclk_input_control_get, mclk_input_control_put), - SOC_DAPM_PIN_SWITCH("Headset Left"), - SOC_DAPM_PIN_SWITCH("Headset Right"), - SOC_DAPM_PIN_SWITCH("Earpiece"), - SOC_DAPM_PIN_SWITCH("Speaker Left"), - SOC_DAPM_PIN_SWITCH("Speaker Right"), - SOC_DAPM_PIN_SWITCH("LineOut Left"), - SOC_DAPM_PIN_SWITCH("LineOut Right"), - SOC_DAPM_PIN_SWITCH("Vibra 1"), - SOC_DAPM_PIN_SWITCH("Vibra 2"), - SOC_DAPM_PIN_SWITCH("Mic 1"), - SOC_DAPM_PIN_SWITCH("Mic 2"), - SOC_DAPM_PIN_SWITCH("LineIn Left"), - SOC_DAPM_PIN_SWITCH("LineIn Right"), - SOC_DAPM_PIN_SWITCH("DMic 1"), - SOC_DAPM_PIN_SWITCH("DMic 2"), - SOC_DAPM_PIN_SWITCH("DMic 3"), - SOC_DAPM_PIN_SWITCH("DMic 4"), - SOC_DAPM_PIN_SWITCH("DMic 5"), - SOC_DAPM_PIN_SWITCH("DMic 6"), -}; - -/* ASoC */ - -static int mop500_ab8500_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - - /* Set audio-clock source */ - return mop500_ab8500_set_mclk(rtd->card->dev, - snd_soc_card_get_drvdata(rtd->card)); -} - -static void mop500_ab8500_shutdown(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct device *dev = rtd->card->dev; - - dev_dbg(dev, "%s: Enter\n", __func__); - - /* Reset slots configuration to default(s) */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - tx_slots = DEF_TX_SLOTS; - else - rx_slots = DEF_RX_SLOTS; -} - -static int mop500_ab8500_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); - struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - struct device *dev = rtd->card->dev; - unsigned int fmt; - int channels, ret = 0, driver_mode, slots; - unsigned int sw_codec, sw_cpu; - bool is_playback; - - dev_dbg(dev, "%s: Enter\n", __func__); - - dev_dbg(dev, "%s: substream->pcm->name = %s\n" - "substream->pcm->id = %s.\n" - "substream->name = %s.\n" - "substream->number = %d.\n", - __func__, - substream->pcm->name, - substream->pcm->id, - substream->name, - substream->number); - - /* Ensure configuration consistency between DAIs */ - scoped_guard(mutex, &mop500_ab8500_params_lock) { - if (mop500_ab8500_usage) { - if (mop500_ab8500_rate != params_rate(params) || - mop500_ab8500_channels != params_channels(params)) { - return -EBUSY; - } - } else { - mop500_ab8500_rate = params_rate(params); - mop500_ab8500_channels = params_channels(params); - } - __set_bit(cpu_dai->id, &mop500_ab8500_usage); - } - - channels = params_channels(params); - - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S32_LE: - sw_cpu = 32; - break; - - case SNDRV_PCM_FORMAT_S16_LE: - sw_cpu = 16; - break; - - default: - return -EINVAL; - } - - /* Setup codec depending on driver-mode */ - if (channels == 8) - driver_mode = DRIVERMODE_CODEC_ONLY; - else - driver_mode = DRIVERMODE_NORMAL; - dev_dbg(dev, "%s: Driver-mode: %s.\n", __func__, - (driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY"); - - /* Setup format */ - - if (driver_mode == DRIVERMODE_NORMAL) { - fmt = SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_CBP_CFP | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CONT; - } else { - fmt = SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_CBP_CFP | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_GATED; - } - - ret = snd_soc_runtime_set_dai_fmt(rtd, fmt); - if (ret) - return ret; - - /* Setup TDM-slots */ - - is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); - switch (channels) { - case 1: - slots = 16; - tx_slots = (is_playback) ? TX_SLOT_MONO : 0; - rx_slots = (is_playback) ? 0 : RX_SLOT_MONO; - break; - case 2: - slots = 16; - tx_slots = (is_playback) ? TX_SLOT_STEREO : 0; - rx_slots = (is_playback) ? 0 : RX_SLOT_STEREO; - break; - case 8: - slots = 16; - tx_slots = (is_playback) ? TX_SLOT_8CH : 0; - rx_slots = (is_playback) ? 0 : RX_SLOT_8CH; - break; - default: - return -EINVAL; - } - - if (driver_mode == DRIVERMODE_NORMAL) - sw_codec = sw_cpu; - else - sw_codec = 20; - - dev_dbg(dev, "%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, - tx_slots, rx_slots); - ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots, - sw_cpu); - if (ret) - return ret; - - dev_dbg(dev, "%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, - tx_slots, rx_slots); - ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots, - sw_codec); - if (ret) - return ret; - - return 0; -} - -static int mop500_ab8500_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - - guard(mutex)(&mop500_ab8500_params_lock); - __clear_bit(cpu_dai->id, &mop500_ab8500_usage); - - return 0; -} - -const struct snd_soc_ops mop500_ab8500_ops[] = { - { - .hw_params = mop500_ab8500_hw_params, - .hw_free = mop500_ab8500_hw_free, - .startup = mop500_ab8500_startup, - .shutdown = mop500_ab8500_shutdown, - } -}; - -int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(rtd->card); - struct device *dev = rtd->card->dev; - struct mop500_ab8500_drvdata *drvdata; - int ret; - - dev_dbg(dev, "%s Enter.\n", __func__); - - /* Create driver private-data struct */ - drvdata = devm_kzalloc(dev, sizeof(struct mop500_ab8500_drvdata), - GFP_KERNEL); - - if (!drvdata) - return -ENOMEM; - - snd_soc_card_set_drvdata(rtd->card, drvdata); - - /* Setup clocks */ - - drvdata->clk_ptr_sysclk = clk_get(dev, "sysclk"); - if (IS_ERR(drvdata->clk_ptr_sysclk)) - dev_warn(dev, "%s: WARNING: clk_get failed for 'sysclk'!\n", - __func__); - drvdata->clk_ptr_ulpclk = clk_get(dev, "ulpclk"); - if (IS_ERR(drvdata->clk_ptr_ulpclk)) - dev_warn(dev, "%s: WARNING: clk_get failed for 'ulpclk'!\n", - __func__); - drvdata->clk_ptr_intclk = clk_get(dev, "intclk"); - if (IS_ERR(drvdata->clk_ptr_intclk)) - dev_warn(dev, "%s: WARNING: clk_get failed for 'intclk'!\n", - __func__); - - /* Set intclk default parent to ulpclk */ - drvdata->mclk_sel = MCLK_ULPCLK; - ret = mop500_ab8500_set_mclk(dev, drvdata); - if (ret < 0) - dev_warn(dev, "%s: WARNING: mop500_ab8500_set_mclk!\n", - __func__); - - drvdata->mclk_sel = MCLK_ULPCLK; - - /* Add controls */ - ret = snd_soc_add_card_controls(rtd->card, mop500_ab8500_ctrls, - ARRAY_SIZE(mop500_ab8500_ctrls)); - if (ret < 0) { - pr_err("%s: Failed to add machine-controls (%d)!\n", - __func__, ret); - return ret; - } - - ret = snd_soc_dapm_disable_pin(dapm, "Earpiece"); - ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Left"); - ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Right"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Left"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Right"); - ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 1"); - ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 2"); - ret |= snd_soc_dapm_disable_pin(dapm, "Mic 1"); - ret |= snd_soc_dapm_disable_pin(dapm, "Mic 2"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Left"); - ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Right"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 1"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 2"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 3"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 4"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 5"); - ret |= snd_soc_dapm_disable_pin(dapm, "DMic 6"); - - return ret; -} - -void mop500_ab8500_remove(struct snd_soc_card *card) -{ - struct mop500_ab8500_drvdata *drvdata = snd_soc_card_get_drvdata(card); - - clk_put(drvdata->clk_ptr_sysclk); - clk_put(drvdata->clk_ptr_ulpclk); - clk_put(drvdata->clk_ptr_intclk); - - snd_soc_card_set_drvdata(card, NULL); -} diff --git a/sound/soc/ux500/mop500_ab8500.h b/sound/soc/ux500/mop500_ab8500.h deleted file mode 100644 index 98de80a9cc4f..000000000000 --- a/sound/soc/ux500/mop500_ab8500.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Author: Ola Lilja - * for ST-Ericsson. - */ - -#ifndef MOP500_AB8500_H -#define MOP500_AB8500_H - -extern const struct snd_soc_ops mop500_ab8500_ops[]; - -int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd); -void mop500_ab8500_remove(struct snd_soc_card *card); - -#endif diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c index 37c48cc70394..c135a77766ca 100644 --- a/sound/soc/ux500/ux500_msp_dai.c +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -644,18 +644,25 @@ static int ux500_msp_dai_of_probe(struct snd_soc_dai *dai) return 0; } -static const struct snd_soc_dai_ops ux500_msp_dai_ops[] = { - { - .probe = ux500_msp_dai_of_probe, - .set_sysclk = ux500_msp_dai_set_dai_sysclk, - .set_fmt = ux500_msp_dai_set_dai_fmt, - .set_tdm_slot = ux500_msp_dai_set_tdm_slot, - .startup = ux500_msp_dai_startup, - .shutdown = ux500_msp_dai_shutdown, - .prepare = ux500_msp_dai_prepare, - .trigger = ux500_msp_dai_trigger, - .hw_params = ux500_msp_dai_hw_params, - } +static const u64 ux500_msp_dai_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_NB_NF | + SND_SOC_POSSIBLE_DAIFMT_NB_IF | + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_DSP_A | + SND_SOC_POSSIBLE_DAIFMT_DSP_B; + +static const struct snd_soc_dai_ops ux500_msp_dai_ops = { + .probe = ux500_msp_dai_of_probe, + .set_sysclk = ux500_msp_dai_set_dai_sysclk, + .set_fmt = ux500_msp_dai_set_dai_fmt, + .set_tdm_slot = ux500_msp_dai_set_tdm_slot, + .startup = ux500_msp_dai_startup, + .shutdown = ux500_msp_dai_shutdown, + .prepare = ux500_msp_dai_prepare, + .trigger = ux500_msp_dai_trigger, + .hw_params = ux500_msp_dai_hw_params, + .auto_selectable_formats = &ux500_msp_dai_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver ux500_msp_dai_drv = { @@ -667,7 +674,7 @@ static struct snd_soc_dai_driver ux500_msp_dai_drv = { .capture.channels_max = UX500_MSP_MAX_CHANNELS, .capture.rates = UX500_I2S_RATES, .capture.formats = UX500_I2S_FORMATS, - .ops = ux500_msp_dai_ops, + .ops = &ux500_msp_dai_ops, }; static const struct snd_soc_component_driver ux500_msp_component = { diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c index 683b485fb570..bd2cfc618290 100644 --- a/sound/soc/ux500/ux500_msp_i2s.c +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -19,7 +19,7 @@ #include "ux500_msp_i2s.h" - /* Protocol desciptors */ + /* Protocol descriptors */ static const struct msp_protdesc prot_descs[] = { { /* I2S */ MSP_SINGLE_PHASE, diff --git a/sound/soc/xilinx/xlnx_formatter_pcm.c b/sound/soc/xilinx/xlnx_formatter_pcm.c index 95312b4ddc5b..304220d9aaf5 100644 --- a/sound/soc/xilinx/xlnx_formatter_pcm.c +++ b/sound/soc/xilinx/xlnx_formatter_pcm.c @@ -81,7 +81,6 @@ struct xlnx_pcm_drv_data { int mm2s_irq; struct snd_pcm_substream *play_stream; struct snd_pcm_substream *capture_stream; - struct clk *axi_clk; unsigned int sysclk; }; @@ -595,100 +594,68 @@ static int xlnx_formatter_pcm_probe(struct platform_device *pdev) u32 val; struct xlnx_pcm_drv_data *aud_drv_data; struct device *dev = &pdev->dev; + struct clk *axi_clk; + + axi_clk = devm_clk_get_enabled(dev, "s_axi_lite_aclk"); + if (IS_ERR(axi_clk)) + return dev_err_probe(dev, PTR_ERR(axi_clk), "failed to get s_axi_lite_aclk"); aud_drv_data = devm_kzalloc(dev, sizeof(*aud_drv_data), GFP_KERNEL); if (!aud_drv_data) return -ENOMEM; - aud_drv_data->axi_clk = devm_clk_get(dev, "s_axi_lite_aclk"); - if (IS_ERR(aud_drv_data->axi_clk)) { - ret = PTR_ERR(aud_drv_data->axi_clk); - dev_err(dev, "failed to get s_axi_lite_aclk(%d)\n", ret); - return ret; - } - ret = clk_prepare_enable(aud_drv_data->axi_clk); - if (ret) { - dev_err(dev, - "failed to enable s_axi_lite_aclk(%d)\n", ret); - return ret; - } - aud_drv_data->mmio = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(aud_drv_data->mmio)) { - dev_err(dev, "audio formatter ioremap failed\n"); - ret = PTR_ERR(aud_drv_data->mmio); - goto clk_err; - } + if (IS_ERR(aud_drv_data->mmio)) + return PTR_ERR(aud_drv_data->mmio); val = readl(aud_drv_data->mmio + XLNX_AUD_CORE_CONFIG); if (val & AUD_CFG_MM2S_MASK) { aud_drv_data->mm2s_presence = true; ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio + XLNX_MM2S_OFFSET); - if (ret) { - dev_err(dev, "audio formatter reset failed\n"); - goto clk_err; - } + if (ret) + return dev_err_probe(dev, ret, "audio formatter reset failed\n"); xlnx_formatter_disable_irqs(aud_drv_data->mmio + XLNX_MM2S_OFFSET, SNDRV_PCM_STREAM_PLAYBACK); aud_drv_data->mm2s_irq = platform_get_irq_byname(pdev, "irq_mm2s"); - if (aud_drv_data->mm2s_irq < 0) { - ret = aud_drv_data->mm2s_irq; - goto clk_err; - } + if (aud_drv_data->mm2s_irq < 0) + return aud_drv_data->mm2s_irq; + ret = devm_request_irq(dev, aud_drv_data->mm2s_irq, xlnx_mm2s_irq_handler, 0, "xlnx_formatter_pcm_mm2s_irq", aud_drv_data); - if (ret) { - dev_err(dev, "xlnx audio mm2s irq request failed\n"); - goto clk_err; - } + if (ret) + return ret; } if (val & AUD_CFG_S2MM_MASK) { aud_drv_data->s2mm_presence = true; ret = xlnx_formatter_pcm_reset(aud_drv_data->mmio + XLNX_S2MM_OFFSET); - if (ret) { - dev_err(dev, "audio formatter reset failed\n"); - goto clk_err; - } + if (ret) + return dev_err_probe(dev, ret, "audio formatter reset failed\n"); xlnx_formatter_disable_irqs(aud_drv_data->mmio + XLNX_S2MM_OFFSET, SNDRV_PCM_STREAM_CAPTURE); aud_drv_data->s2mm_irq = platform_get_irq_byname(pdev, "irq_s2mm"); - if (aud_drv_data->s2mm_irq < 0) { - ret = aud_drv_data->s2mm_irq; - goto clk_err; - } + if (aud_drv_data->s2mm_irq < 0) + return aud_drv_data->s2mm_irq; + ret = devm_request_irq(dev, aud_drv_data->s2mm_irq, xlnx_s2mm_irq_handler, 0, "xlnx_formatter_pcm_s2mm_irq", aud_drv_data); - if (ret) { - dev_err(dev, "xlnx audio s2mm irq request failed\n"); - goto clk_err; - } + if (ret) + return ret; } dev_set_drvdata(dev, aud_drv_data); - ret = devm_snd_soc_register_component(dev, &xlnx_asoc_component, - NULL, 0); - if (ret) { - dev_err(dev, "pcm platform device register failed\n"); - goto clk_err; - } - - return 0; - -clk_err: - clk_disable_unprepare(aud_drv_data->axi_clk); - return ret; + return devm_snd_soc_register_component(dev, &xlnx_asoc_component, NULL, 0); } static void xlnx_formatter_pcm_remove(struct platform_device *pdev) @@ -705,8 +672,6 @@ static void xlnx_formatter_pcm_remove(struct platform_device *pdev) if (ret) dev_err(&pdev->dev, "audio formatter reset failed\n"); - - clk_disable_unprepare(adata->axi_clk); } static const struct of_device_id xlnx_formatter_pcm_of_match[] = { diff --git a/sound/soc/xtensa/xtfpga-i2s.c b/sound/soc/xtensa/xtfpga-i2s.c index fd0990201b40..e5e8b72fd817 100644 --- a/sound/soc/xtensa/xtfpga-i2s.c +++ b/sound/soc/xtensa/xtfpga-i2s.c @@ -485,10 +485,16 @@ static const struct snd_soc_component_driver xtfpga_i2s_component = { .legacy_dai_naming = 1, }; +static const u64 xtfpga_i2s_selectable_formats = + SND_SOC_POSSIBLE_DAIFMT_I2S | + SND_SOC_POSSIBLE_DAIFMT_NB_NF; + static const struct snd_soc_dai_ops xtfpga_i2s_dai_ops = { - .startup = xtfpga_i2s_startup, - .hw_params = xtfpga_i2s_hw_params, - .set_fmt = xtfpga_i2s_set_fmt, + .startup = xtfpga_i2s_startup, + .hw_params = xtfpga_i2s_hw_params, + .set_fmt = xtfpga_i2s_set_fmt, + .auto_selectable_formats = &xtfpga_i2s_selectable_formats, + .num_auto_selectable_formats = 1, }; static struct snd_soc_dai_driver xtfpga_i2s_dai[] = { diff --git a/sound/sparc/cs4231.c b/sound/sparc/cs4231.c index d9e5cca94c73..0489eac165c9 100644 --- a/sound/sparc/cs4231.c +++ b/sound/sparc/cs4231.c @@ -1497,8 +1497,8 @@ static int cs4231_attach_begin(struct platform_device *op, return -ENOENT; } - err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, - sizeof(struct snd_cs4231), &card); + err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_cs4231), &card); if (err < 0) return err; @@ -1519,28 +1519,24 @@ static int cs4231_attach_finish(struct snd_card *card) err = snd_cs4231_pcm(card); if (err < 0) - goto out_err; + return err; err = snd_cs4231_mixer(card); if (err < 0) - goto out_err; + return err; err = snd_cs4231_timer(card); if (err < 0) - goto out_err; + return err; err = snd_card_register(card); if (err < 0) - goto out_err; + return err; dev_set_drvdata(&chip->op->dev, chip); dev++; return 0; - -out_err: - snd_card_free(card); - return err; } #ifdef SBUS_SUPPORT @@ -1771,7 +1767,7 @@ static int snd_cs4231_sbus_create(struct snd_card *card, static int cs4231_sbus_probe(struct platform_device *op) { struct resource *rp = &op->resource[0]; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; err = cs4231_attach_begin(op, &card); @@ -1785,12 +1781,14 @@ static int cs4231_sbus_probe(struct platform_device *op) op->archdata.irqs[0]); err = snd_cs4231_sbus_create(card, op, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } - return cs4231_attach_finish(card); + err = cs4231_attach_finish(card); + if (err < 0) + return err; + card = NULL; /* probe succeeded, don't release as error */ + return 0; } #endif @@ -1968,7 +1966,7 @@ static int snd_cs4231_ebus_create(struct snd_card *card, static int cs4231_ebus_probe(struct platform_device *op) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; err = cs4231_attach_begin(op, &card); @@ -1981,12 +1979,15 @@ static int cs4231_ebus_probe(struct platform_device *op) op->archdata.irqs[0]); err = snd_cs4231_ebus_create(card, op, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } - return cs4231_attach_finish(card); + err = cs4231_attach_finish(card); + if (err < 0) + return err; + + card = NULL; /* probe succeeded, don't release as error */ + return 0; } #endif @@ -2004,13 +2005,6 @@ static int cs4231_probe(struct platform_device *op) return -ENODEV; } -static void cs4231_remove(struct platform_device *op) -{ - struct snd_cs4231 *chip = dev_get_drvdata(&op->dev); - - snd_card_free(chip->card); -} - static const struct of_device_id cs4231_match[] = { { .name = "SUNW,CS4231", @@ -2030,7 +2024,6 @@ static struct platform_driver cs4231_driver = { .of_match_table = cs4231_match, }, .probe = cs4231_probe, - .remove = cs4231_remove, }; module_platform_driver(cs4231_driver); diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c index ccaa36525f8d..9fad4102dc70 100644 --- a/sound/sparc/dbri.c +++ b/sound/sparc/dbri.c @@ -2576,11 +2576,16 @@ static void snd_dbri_free(struct snd_dbri *dbri) (void *)dbri->dma, dbri->dma_dvma); } +static void dbri_card_free(struct snd_card *card) +{ + snd_dbri_free(card->private_data); +} + static int dbri_probe(struct platform_device *op) { struct snd_dbri *dbri; struct resource *rp; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; static int dev; int irq; int err; @@ -2598,8 +2603,8 @@ static int dbri_probe(struct platform_device *op) return -ENODEV; } - err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, - sizeof(struct snd_dbri), &card); + err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_dbri), &card); if (err < 0) return err; @@ -2609,21 +2614,20 @@ static int dbri_probe(struct platform_device *op) sprintf(card->longname, "%s at 0x%02lx:0x%016llx, irq %d", card->shortname, rp->flags & 0xffL, (unsigned long long)rp->start, irq); + card->private_free = dbri_card_free; err = snd_dbri_create(card, op, irq, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } dbri = card->private_data; err = snd_dbri_pcm(card); if (err < 0) - goto _err; + return err; err = snd_dbri_mixer(card); if (err < 0) - goto _err; + return err; /* /proc file handling */ snd_dbri_proc(card); @@ -2631,27 +2635,15 @@ static int dbri_probe(struct platform_device *op) err = snd_card_register(card); if (err < 0) - goto _err; + return err; printk(KERN_INFO "audio%d at %p (irq %d) is DBRI(%c)+CS4215(%d)\n", dev, dbri->regs, dbri->irq, op->dev.of_node->name[9], dbri->mm.version); dev++; + card = NULL; /* probe succeeded, don't release as error */ return 0; - -_err: - snd_dbri_free(dbri); - snd_card_free(card); - return err; -} - -static void dbri_remove(struct platform_device *op) -{ - struct snd_card *card = dev_get_drvdata(&op->dev); - - snd_dbri_free(card->private_data); - snd_card_free(card); } static const struct of_device_id dbri_match[] = { @@ -2672,7 +2664,6 @@ static struct platform_driver dbri_sbus_driver = { .of_match_table = dbri_match, }, .probe = dbri_probe, - .remove = dbri_remove, }; module_platform_driver(dbri_sbus_driver); diff --git a/sound/synth/emux/emux.c b/sound/synth/emux/emux.c index 450147b1abed..2a42d041e1da 100644 --- a/sound/synth/emux/emux.c +++ b/sound/synth/emux/emux.c @@ -71,7 +71,12 @@ static int sf_sample_free(void *private_data, struct snd_sf_sample *sp, static void sf_sample_reset(void *private_data) { struct snd_emux *emu = private_data; - emu->ops.sample_reset(emu); + + /* stop all voices that might refer to zones and samples to be freed */ + if (emu->ops.sample_reset) + emu->ops.sample_reset(emu); + else + snd_emux_terminate_all(emu); } int snd_emux_register(struct snd_emux *emu, struct snd_card *card, int index, char *name) @@ -95,8 +100,7 @@ int snd_emux_register(struct snd_emux *emu, struct snd_card *card, int index, ch sf_cb.private_data = emu; sf_cb.sample_new = sf_sample_new; sf_cb.sample_free = sf_sample_free; - if (emu->ops.sample_reset) - sf_cb.sample_reset = sf_sample_reset; + sf_cb.sample_reset = sf_sample_reset; emu->sflist = snd_sf_new(&sf_cb, emu->memhdr); if (emu->sflist == NULL) return -ENOMEM; diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c index 1145c7956afd..30e353e7323d 100644 --- a/sound/synth/emux/emux_oss.c +++ b/sound/synth/emux/emux_oss.c @@ -124,7 +124,7 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) arg->private_data = p; arg->addr.client = p->chset.client; arg->addr.port = p->chset.port; - p->oss_arg = arg; + WRITE_ONCE(p->oss_arg, arg); reset_port_mode(p, arg->seq_mode); @@ -141,16 +141,24 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) static void reset_port_mode(struct snd_emux_port *port, int midi_mode) { + struct snd_seq_oss_arg *arg; + + /* OSS events can arrive before the open path has set oss_arg. */ + arg = READ_ONCE(port->oss_arg); + if (midi_mode) { port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_MIDI; port->drum_flags = DEFAULT_DRUM_FLAGS; port->volume_atten = 0; - port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_KEYPRESS; + if (arg) + arg->event_passing = + SNDRV_SEQ_OSS_PROCESS_KEYPRESS; } else { port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_SYNTH; port->drum_flags = 0; port->volume_atten = 32; - port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; + if (arg) + arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; } } diff --git a/sound/synth/emux/emux_synth.c b/sound/synth/emux/emux_synth.c index 6982e1317da5..2cbf12364a1e 100644 --- a/sound/synth/emux/emux_synth.c +++ b/sound/synth/emux/emux_synth.c @@ -60,6 +60,9 @@ snd_emux_note_on(void *p, int note, int vel, struct snd_midi_channel *chan) return; key = note; /* remember the original note */ + + /* hold sflist->lock for protecting against concurrent free */ + guard(spinlock_irqsave)(&emu->sflist->lock); nvoices = get_zone(emu, port, ¬e, vel, chan, table); if (! nvoices) return; diff --git a/sound/synth/emux/soundfont.c b/sound/synth/emux/soundfont.c index c172b53b17a4..42d1f33fb2b5 100644 --- a/sound/synth/emux/soundfont.c +++ b/sound/synth/emux/soundfont.c @@ -443,6 +443,15 @@ remove_info(struct snd_sf_list *sflist, struct snd_soundfont *sf, struct snd_sf_zone *prev, *next, *p; int removed = 0; + /* stop voices before freeing the zones they might refer to */ + for (p = sf->zones; p; p = p->next) { + if (!p->mapped && p->bank == bank && p->instr == instr) { + if (sflist->callback.sample_reset) + sflist->callback.sample_reset(sflist->callback.private_data); + break; + } + } + prev = NULL; for (p = sf->zones; p; p = next) { next = p->next; @@ -1225,6 +1234,9 @@ delete_preset(struct snd_sf_list *sflist, struct snd_sf_zone *zp) * The found zones are stored on 'table' array. max_layers defines * the maximum number of elements in this array. * This function returns the number of found zones. 0 if not found. + * + * The caller must hold sflist->lock, and keep holding it as long as + * the returned zones are accessed. */ int snd_soundfont_search_zone(struct snd_sf_list *sflist, int *notep, int vel, @@ -1234,11 +1246,11 @@ snd_soundfont_search_zone(struct snd_sf_list *sflist, int *notep, int vel, { int nvoices; - /* this function is supposed to be called atomically, - * so we check the lock. if it's busy, just returns 0 to + lockdep_assert_held(&sflist->lock); + + /* if the presets are being modified, just returns 0 to * tell the caller the busy state */ - guard(spinlock_irqsave)(&sflist->lock); if (sflist->presets_locked) return 0; nvoices = search_zones(sflist, notep, vel, preset, bank, diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c index 10104cb42165..da5a2d7ac107 100644 --- a/sound/usb/6fire/control.c +++ b/sound/usb/6fire/control.c @@ -20,6 +20,7 @@ #include "control.h" #include "comm.h" #include "chip.h" +#include "pcm.h" static const char * const opt_coax_texts[2] = { "Optical", "Coax" }; static const char * const line_phono_texts[2] = { "Line", "Phono" }; @@ -148,9 +149,14 @@ static int usb6fire_control_set_channels( if (ret < 0) return ret; - /* disable digital inputs and outputs */ - /* TODO: use spdif_x to enable/disable digital channels */ - ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00); + /* + * digital (S/PDIF) input and output: bit 0 adds four slots (4-7) to + * the capture frame, which carry the subframes of the receiver; up to + * two receiver frames arrive per card frame; bit 1 enables the + * transmitter, which encodes the playback pair in slots 6/7 (pcm.c) + */ + ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, + (spdif_in ? 0x01 : 0x00) | (spdif_out ? 0x02 : 0x00)); if (ret < 0) return ret; @@ -160,14 +166,26 @@ static int usb6fire_control_set_channels( static int usb6fire_control_streaming_update(struct control_runtime *rt) { struct comm_runtime *comm_rt = rt->chip->comm; + int err = 0; + int ret; if (comm_rt) { + /* + * the digital pairs are enabled only while a stream runs; the + * streaming register is written even if this write fails. + * usb6fire_control_digital_thru_put() holds stream_mutex, so + * this write cannot run after set_channels() and before + * streaming starts in usb6fire_pcm_set_rate() + */ + if (!rt->usb_streaming) + err = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00); if (!rt->usb_streaming && rt->digital_thru_switch) usb6fire_control_set_rate(rt, DIGITAL_THRU_ONLY_SAMPLERATE); - return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, + ret = comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, (rt->usb_streaming ? 0x01 : 0x00) | (rt->digital_thru_switch ? 0x08 : 0x00)); + return err < 0 ? err : ret; } return -EINVAL; } @@ -383,6 +401,12 @@ static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol, struct control_runtime *rt = snd_kcontrol_chip(kcontrol); int changed = 0; + /* + * with Digital Thru on, streaming_update() selects the altsetting and + * writes the clock register of an idle card; prepare does the same + * under stream_mutex + */ + guard(mutex)(&rt->chip->pcm->stream_mutex); if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) { rt->digital_thru_switch = ucontrol->value.integer.value[0]; usb6fire_control_streaming_update(rt); diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index 0285d79ace0f..f5dfb8c3096a 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -9,13 +9,34 @@ * Copyright: (C) Torsten Schenk */ +#include +#include +#include + #include "pcm.h" #include "chip.h" #include "comm.h" #include "control.h" +/* + * Every USB frame is a 4 byte header followed by 32-bit slots, one per + * channel: 3 bytes of sample and a marker byte. Analog slots are marked + * 0x40. Up to 96 kHz (altsettings 1 and 2) the playback frame carries two + * more slots, 6/7, which carry the data for the S/PDIF transmitter in + * the FPGA. Their marker byte tells the transmitter how to frame the + * sample: 0x10 marks the left subframe and 0x00 the right one; 0x20 marks + * the start of the channel status block (left subframe, every 192 + * frames); 0x04 is the channel status bit of the subframe; 0x01 is the + * validity bit, set for non-audio data; 0x08 is the parity bit, which + * makes the number of ones in the sample, the validity and channel status + * bits and itself even. + * At altsetting 3 (176.4/192 kHz) the packets hold the analog slots only. + */ enum { - OUT_N_CHANNELS = 6, IN_N_CHANNELS = 4 + OUT_N_ANALOG = 6, N_SPDIF = 2, + OUT_N_CHANNELS = OUT_N_ANALOG + N_SPDIF, IN_N_CHANNELS = 4, + SPDIF_OUT_SLOT = OUT_N_ANALOG, + SPDIF_BLOCK_FRAMES = 192 }; /* keep next two synced with @@ -24,6 +45,15 @@ enum { static const int rates_in_packet_size[] = { 228, 228, 420, 420, 404, 404 }; static const int rates_out_packet_size[] = { 228, 228, 420, 420, 604, 604 }; static const int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000 }; +static const u8 rates_iec958_fs[] = { + IEC958_AES3_CON_FS_44100, IEC958_AES3_CON_FS_48000, + IEC958_AES3_CON_FS_88200, IEC958_AES3_CON_FS_96000, + IEC958_AES3_CON_FS_176400, IEC958_AES3_CON_FS_192000 }; +/* channel status bits that "IEC958 Playback Default" sets, bytes 0 and 1 */ +static const u8 spdif_ctl_mask[] = { + IEC958_AES0_NONAUDIO | IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS, + IEC958_AES1_CON_CATEGORY | IEC958_AES1_CON_ORIGINAL }; static const int rates_alsaid[] = { SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000, SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000, @@ -71,6 +101,7 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) { int ret; struct control_runtime *ctrl_rt = rt->chip->control; + bool spdif; ctrl_rt->usb_streaming = false; ret = ctrl_rt->update_streaming(ctrl_rt); @@ -89,8 +120,9 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) return ret; } - ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_CHANNELS, IN_N_CHANNELS, - false, false); + spdif = rt->rate <= CONTROL_RATE_96KHZ; + ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_ANALOG, IN_N_CHANNELS, + spdif, false); if (ret < 0) { dev_err(&rt->chip->dev->dev, "error initializing channels while setting samplerate %d.\n", @@ -104,16 +136,108 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) dev_err(&rt->chip->dev->dev, "error starting streaming while setting samplerate %d.\n", rates[rt->rate]); + /* stream_stop() will not run; clear 0x02/0x03 here */ + ctrl_rt->usb_streaming = false; + ctrl_rt->update_streaming(ctrl_rt); return ret; } rt->in_n_analog = IN_N_CHANNELS; - rt->out_n_analog = OUT_N_CHANNELS; + rt->out_n_slots = spdif ? OUT_N_CHANNELS : OUT_N_ANALOG; rt->in_packet_size = rates_in_packet_size[rt->rate]; rt->out_packet_size = rates_out_packet_size[rt->rate]; + + /* + * consumer channel status, 24 bit samples; bytes 0 and 1 are taken + * from rt->spdif_bits at the start of every block + */ + memset(rt->spdif_status, 0, sizeof(rt->spdif_status)); + rt->spdif_status[3] = rates_iec958_fs[rt->rate]; + rt->spdif_status[4] = IEC958_AES4_CON_MAX_WORDLEN_24 | + IEC958_AES4_CON_WORDLEN_24_20; + rt->spdif_frame = 0; return 0; } +/* + * Return the marker byte of one playback slot whose 3 sample bytes are + * already in place. The marker is 0x40 for an analog slot and the S/PDIF + * framing for a digital slot. With "IEC958 Playback Switch" off, the + * function also clears the sample bytes of a digital slot. After the right + * subframe, the function advances the channel status position. + */ +static u8 usb6fire_pcm_marker(struct pcm_runtime *rt, int slot, + u8 *sample) +{ + bool right = slot == SPDIF_OUT_SLOT + 1; + unsigned int n = rt->spdif_frame; + u8 marker = right ? 0x00 : 0x10; + u8 status; + + if (slot < SPDIF_OUT_SLOT) + return 0x40; + if (!right && n == 0) { + /* a new block takes the bits of "IEC958 Playback Default" */ + u32 bits = READ_ONCE(rt->spdif_bits); + + rt->spdif_status[0] = bits & 0xff; + rt->spdif_status[1] = bits >> 8; + /* + * "IEC958 Playback Switch" off omits the block start flag and + * clears the sample bytes of the digital slots + */ + rt->spdif_on = READ_ONCE(rt->spdif_switch); + if (rt->spdif_on) + marker |= 0x20; + } + if (!rt->spdif_on) + memset(sample, 0, 3); + status = rt->spdif_status[n >> 3]; + /* byte 2 holds the channel number: 1 = A (left), 2 = B (right) */ + if ((n >> 3) == 2) + status |= right ? (2 << 4) : (1 << 4); + if (status & (1 << (n & 7))) + marker |= 0x04; + /* V = 1 for non-audio data (AC3, DTS), as the vendor driver does */ + if (rt->spdif_status[0] & IEC958_AES0_NONAUDIO) + marker |= 0x01; + /* P makes the number of ones in bits 0-27 (sample, V, U, C, P) even */ + if (parity8(sample[0] ^ sample[1] ^ sample[2] ^ (marker & 0x07))) + marker |= 0x08; + if (right) + rt->spdif_frame = (n + 1) % SPDIF_BLOCK_FRAMES; + return marker; +} + +/* more than OUT_N_ANALOG playback channels need a rate up to 96 kHz */ +static int usb6fire_pcm_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { .min = 1, .max = OUT_N_ANALOG, .integer = 1 }; + + if (r->min <= rates[CONTROL_RATE_96KHZ]) + return 0; + return snd_interval_refine(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS), &t); +} + +static int usb6fire_pcm_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = 0, .max = rates[CONTROL_RATE_96KHZ], .integer = 1 + }; + + if (c->min <= OUT_N_ANALOG) + return 0; + return snd_interval_refine(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), &t); +} + static struct pcm_substream *usb6fire_pcm_get_substream( struct snd_pcm_substream *alsa_sub) { @@ -264,19 +388,21 @@ static void usb6fire_pcm_playback(struct pcm_substream *sub, } for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { - /* at least 4 header bytes for valid packet. - * after that: 32 bits per sample for analog channels */ + /* + * at least 4 header bytes for valid packet. + * after that: 32 bits per slot + */ isoc = &urb->instance->iso_frame_desc[i]; if (isoc->length > 4) frame_count = (isoc->length - 4) - / (rt->out_n_analog << 2); + / (rt->out_n_slots << 2); else frame_count = 0; dest++; /* skip leading 4 bytes of every frame */ for (frame = 0; frame < frame_count; frame++) { memcpy(dest, src, bytes_per_frame); src += alsa_rt->channels; - dest += rt->out_n_analog; + dest += rt->out_n_slots; sub->dma_off++; sub->period_off++; if (src == src_end) { @@ -301,6 +427,7 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) int channel; int i; u8 *dest; + unsigned int spdif_frame; if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING) return; @@ -344,10 +471,10 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) / (rt->in_n_analog << 2); frames = min_t(unsigned int, frames, (rt->out_packet_size - 4) - / (rt->out_n_analog << 2)); + / (rt->out_n_slots << 2)); isoc_out->offset = total_length; - isoc_out->length = frames * (rt->out_n_analog << 2) + 4; + isoc_out->length = frames * (rt->out_n_slots << 2) + 4; isoc_out->status = 0; total_length += isoc_out->length; } @@ -368,28 +495,38 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) if (period_elapsed) snd_pcm_period_elapsed(sub->instance); - /* setup the 4th byte of each sample (0x40 for analog channels) */ + /* + * set the 4th byte of each sample (0x40 for analog channels, + * S/PDIF framing for the digital pair) + */ + spdif_frame = rt->spdif_frame; dest = out_urb->buffer; for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { isoc_out = &out_urb->instance->iso_frame_desc[i]; if (isoc_out->length >= 4) { frame_count = (isoc_out->length - 4) - / (rt->out_n_analog << 2); + / (rt->out_n_slots << 2); *(dest++) = 0xaa; *(dest++) = 0xaa; *(dest++) = frame_count; *(dest++) = 0x00; for (frame = 0; frame < frame_count; frame++) for (channel = 0; - channel < rt->out_n_analog; + channel < rt->out_n_slots; channel++) { - dest += 3; /* skip sample data */ - *(dest++) = 0x40; + dest[3] = usb6fire_pcm_marker(rt, + channel, dest); + dest += 4; } } } - usb_submit_urb(out_urb->instance, GFP_ATOMIC); + /* + * the frames of an out urb that was not submitted never reach the + * transmitter; the next urb continues the block at the same position + */ + if (usb_submit_urb(out_urb->instance, GFP_ATOMIC) < 0) + rt->spdif_frame = spdif_frame; usb_submit_urb(in_urb->instance, GFP_ATOMIC); } @@ -409,6 +546,7 @@ static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); struct pcm_substream *sub = NULL; struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime; + int ret; if (rt->panic) return -EPIPE; @@ -420,6 +558,17 @@ static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) if (rt->rate < ARRAY_SIZE(rates)) alsa_rt->hw.rates = rates_alsaid[rt->rate]; alsa_rt->hw.channels_max = OUT_N_CHANNELS; + ret = snd_pcm_hw_rule_add(alsa_rt, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + usb6fire_pcm_rule_channels, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (ret < 0) + return ret; + ret = snd_pcm_hw_rule_add(alsa_rt, 0, SNDRV_PCM_HW_PARAM_RATE, + usb6fire_pcm_rule_rate, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret < 0) + return ret; sub = &rt->playback; } else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) { if (rt->rate < ARRAY_SIZE(rates)) @@ -489,7 +638,17 @@ static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) alsa_rt->rate); return -EINVAL; } - + } + /* the digital pair is not available at 176.4 and 192 kHz */ + if (rt->rate > CONTROL_RATE_96KHZ && + alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK && + alsa_rt->channels > OUT_N_ANALOG) { + dev_err(&rt->chip->dev->dev, + "%d channels not available at %d Hz.\n", + alsa_rt->channels, rates[rt->rate]); + return -EINVAL; + } + if (rt->stream_state == STREAM_DISABLED) { ret = usb6fire_pcm_set_rate(rt); if (ret) return ret; @@ -612,6 +771,128 @@ static void usb6fire_pcm_free(struct pcm_runtime *rt) kfree(rt); } +static int usb6fire_pcm_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int usb6fire_pcm_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0, + sizeof(ucontrol->value.iec958.status)); + ucontrol->value.iec958.status[0] = spdif_ctl_mask[0]; + ucontrol->value.iec958.status[1] = spdif_ctl_mask[1]; + return 0; +} + +static int usb6fire_pcm_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + u32 bits = READ_ONCE(rt->spdif_bits); + + memset(ucontrol->value.iec958.status, 0, + sizeof(ucontrol->value.iec958.status)); + ucontrol->value.iec958.status[0] = bits & 0xff; + ucontrol->value.iec958.status[1] = bits >> 8; + return 0; +} + +static int usb6fire_pcm_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + u32 bits = (ucontrol->value.iec958.status[0] & spdif_ctl_mask[0]) | + (ucontrol->value.iec958.status[1] & spdif_ctl_mask[1]) << 8; + + /* the control core serializes puts; the urb handler uses READ_ONCE */ + if (rt->spdif_bits == bits) + return 0; + WRITE_ONCE(rt->spdif_bits, bits); + return 1; +} + +static int usb6fire_pcm_spdif_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = READ_ONCE(rt->spdif_switch); + return 0; +} + +static int usb6fire_pcm_spdif_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + bool on = ucontrol->value.integer.value[0]; + + /* the control core serializes puts; the urb handler uses READ_ONCE */ + if (rt->spdif_switch == on) + return 0; + WRITE_ONCE(rt->spdif_switch, on); + return 1; +} + +/* + * playback channel map: channel n goes to analog output n, which the + * manual of the card assigns to FL FR FC LFE RL RR for n = 1..6; a + * 1-channel stream is MONO, as in snd_pcm_std_chmaps, and plays on + * analog output 1 only; channels 7 and 8 are the S/PDIF pair and have + * no position + */ +static const struct snd_pcm_chmap_elem usb6fire_pcm_chmaps[] = { + { .channels = 1, .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 3, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC } }, + { .channels = 4, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, + { .channels = 5, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL } }, + { .channels = 6, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 7, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_UNKNOWN } }, + { .channels = 8, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_UNKNOWN, SNDRV_CHMAP_UNKNOWN } }, + { } +}; + +static const struct snd_kcontrol_new usb6fire_pcm_spdif_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = usb6fire_pcm_spdif_info, + .get = usb6fire_pcm_spdif_default_get, + .put = usb6fire_pcm_spdif_default_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = usb6fire_pcm_spdif_info, + .get = usb6fire_pcm_spdif_mask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), + .info = snd_ctl_boolean_mono_info, + .get = usb6fire_pcm_spdif_switch_get, + .put = usb6fire_pcm_spdif_switch_put, + }, +}; + int usb6fire_pcm_init(struct sfire_chip *chip) { int i; @@ -630,6 +911,9 @@ int usb6fire_pcm_init(struct sfire_chip *chip) rt->chip = chip; rt->stream_state = STREAM_DISABLED; rt->rate = ARRAY_SIZE(rates); + rt->spdif_switch = true; + rt->spdif_bits = IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES1_CON_GENERAL << 8; init_waitqueue_head(&rt->stream_wait_queue); mutex_init(&rt->stream_mutex); @@ -665,6 +949,22 @@ int usb6fire_pcm_init(struct sfire_chip *chip) rt->instance = pcm; chip->pcm = rt; + + /* on error, rt and the controls added so far are freed with the card */ + for (i = 0; i < ARRAY_SIZE(usb6fire_pcm_spdif_controls); i++) { + ret = snd_ctl_add(chip->card, + snd_ctl_new1(&usb6fire_pcm_spdif_controls[i], rt)); + if (ret < 0) { + dev_err(&chip->dev->dev, "cannot add S/PDIF control.\n"); + return ret; + } + } + ret = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + usb6fire_pcm_chmaps, OUT_N_CHANNELS, 0, NULL); + if (ret < 0) { + dev_err(&chip->dev->dev, "cannot add channel map.\n"); + return ret; + } return 0; error: diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h index b586fe220fd1..f1b867b9c468 100644 --- a/sound/usb/6fire/pcm.h +++ b/sound/usb/6fire/pcm.h @@ -53,7 +53,12 @@ struct pcm_runtime { int in_packet_size; int out_packet_size; int in_n_analog; /* number of analog channels soundcard sends */ - int out_n_analog; /* number of analog channels soundcard receives */ + int out_n_slots; /* 32-bit slots per frame the soundcard receives */ + bool spdif_switch; /* "IEC958 Playback Switch" */ + bool spdif_on; /* copy of spdif_switch, read at the start of each block */ + unsigned int spdif_frame; /* position in the channel status block */ + u8 spdif_status[24]; /* IEC 60958 channel status of the S/PDIF output */ + u32 spdif_bits; /* status bytes 0 and 1, "IEC958 Playback Default" */ struct mutex stream_mutex; u8 stream_state; /* one of STREAM_XXX (pcm.c) */ diff --git a/sound/usb/Makefile b/sound/usb/Makefile index e62794a87e73..2e842dcc73de 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -11,6 +11,7 @@ snd-usb-audio-y := card.o \ helper.o \ implicit.o \ mixer.o \ + mixer_evo.o \ mixer_quirks.o \ mixer_scarlett.o \ mixer_scarlett2.o \ diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile index 9a99c17a2c1b..ffa5a7edf95c 100644 --- a/sound/usb/caiaq/Makefile +++ b/sound/usb/caiaq/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -snd-usb-caiaq-y := device.o audio.o midi.o control.o +snd-usb-caiaq-y := device.o audio.o midi.o control.o lcd.o snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index f604846ec6e6..634dc36f2ead 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -22,6 +22,7 @@ #include "midi.h" #include "control.h" #include "input.h" +#include "lcd.h" MODULE_AUTHOR("Daniel Mack "); MODULE_DESCRIPTION("caiaq USB audio"); @@ -142,7 +143,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) return; } if (urb->actual_length < 1) - return; + goto resubmit; payload_len = urb->actual_length - 1; @@ -192,6 +193,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) break; } + resubmit: cdev->ep1_in_urb->actual_length = 0; ret = usb_submit_urb(cdev->ep1_in_urb, GFP_ATOMIC); if (ret < 0) @@ -212,6 +214,8 @@ int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev, if (len > EP1_BUFSIZE - 1) len = EP1_BUFSIZE - 1; + guard(mutex)(&cdev->ep1_out_mutex); + if (buffer && len > 0) memcpy(cdev->ep1_out_buf+1, buffer, len); @@ -235,6 +239,8 @@ int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev, if (len > EP1_BUFSIZE - 2) len = EP1_BUFSIZE - 2; + guard(mutex)(&cdev->ep1_out_mutex); + if (buffer && len > 0) memcpy(cdev->ep1_out_buf+2, buffer, len); @@ -383,10 +389,9 @@ static int setup_card(struct snd_usb_caiaqdev *cdev) } #endif - /* finally, register the card and all its sub-instances */ - ret = snd_card_register(cdev->chip.card); + ret = snd_usb_caiaq_lcd_init(cdev); if (ret < 0) { - dev_err(dev, "snd_card_register() returned %d\n", ret); + dev_err(dev, "Unable to set up LCD (ret=%d)\n", ret); return ret; } @@ -396,6 +401,13 @@ static int setup_card(struct snd_usb_caiaqdev *cdev) return ret; } + /* finally, register the card and all its sub-instances */ + ret = snd_card_register(cdev->chip.card); + if (ret < 0) { + dev_err(dev, "snd_card_register() returned %d\n", ret); + return ret; + } + return 0; } @@ -443,6 +455,8 @@ static int create_card(struct usb_device *usb_dev, cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor), le16_to_cpu(usb_dev->descriptor.idProduct)); spin_lock_init(&cdev->spinlock); + spin_lock_init(&cdev->midi_lock); + mutex_init(&cdev->ep1_out_mutex); *cardp = card; return 0; diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 1c6f34693fa8..16f65e7867d1 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -2,6 +2,8 @@ #ifndef CAIAQ_DEVICE_H #define CAIAQ_DEVICE_H +#include + #include "../usbaudio.h" #define USB_VID_NATIVEINSTRUMENTS 0x17cc @@ -28,6 +30,8 @@ #define MODNAME "snd-usb-caiaq" +#define CAIAQ_LCD_FRAME_SIZE 1024 + #define EP1_CMD_GET_DEVICE_INFO 0x1 #define EP1_CMD_READ_ERP 0x2 #define EP1_CMD_READ_ANALOG 0x3 @@ -35,6 +39,7 @@ #define EP1_CMD_WRITE_IO 0x5 #define EP1_CMD_MIDI_READ 0x6 #define EP1_CMD_MIDI_WRITE 0x7 +#define EP1_CMD_LCD 0x8 #define EP1_CMD_AUDIO_PARAMS 0x9 #define EP1_CMD_AUTO_MSG 0xb #define EP1_CMD_DIMM_LEDS 0xc @@ -68,10 +73,12 @@ struct snd_usb_caiaqdev { unsigned char ep1_in_buf[EP1_BUFSIZE]; unsigned char ep1_out_buf[EP1_BUFSIZE]; + struct mutex ep1_out_mutex; /* protects ep1_out_buf */ unsigned char midi_out_buf[EP1_BUFSIZE]; struct caiaq_device_spec spec; - spinlock_t spinlock; + spinlock_t spinlock; /* for PCM audio */ + spinlock_t midi_lock; /* midi_x_stream, midi_out_active */ wait_queue_head_t ep1_wait_queue; wait_queue_head_t prepare_wait_queue; int spec_received, audio_parm_answer; @@ -98,6 +105,13 @@ struct snd_usb_caiaqdev { unsigned char control_state[256]; unsigned char ep8_out_buf[2]; + /* Kore LCD */ + struct mutex lcd_mutex; /* protects the lcd_* fields */ + unsigned char lcd_frame[CAIAQ_LCD_FRAME_SIZE]; /* shown frame */ + unsigned char lcd_contrast; + bool lcd_ready; /* controller set up */ + bool lcd_frame_valid; /* lcd_frame matches the display */ + /* Linux input */ #ifdef CONFIG_SND_USB_CAIAQ_INPUT struct input_dev *input_dev; diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 8d924330c54c..2830a1da067d 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -41,11 +41,11 @@ static const unsigned short keycode_kore[] = { BTN_3, BTN_2, BTN_1, - BTN_8, - BTN_7, - BTN_6, BTN_5, - KEY_BRL_DOT4, /* touch sensitive knobs */ + BTN_6, + BTN_7, + BTN_8, + KEY_BRL_DOT4, /* touch sensitive knobs (Kore 2 order) */ KEY_BRL_DOT3, KEY_BRL_DOT2, KEY_BRL_DOT1, @@ -55,6 +55,21 @@ static const unsigned short keycode_kore[] = { KEY_BRL_DOT5 }; +/* index of the first touch sensor in keycode_kore */ +#define KORE_TOUCH_KEYS 24 + +/* the first Kore controller reports its touch sensors in this order */ +static const unsigned short keycode_kore1_touch[] = { + KEY_BRL_DOT4, + KEY_BRL_DOT8, + KEY_BRL_DOT3, + KEY_BRL_DOT7, + KEY_BRL_DOT2, + KEY_BRL_DOT6, + KEY_BRL_DOT1, + KEY_BRL_DOT5 +}; + #define MASCHINE_BUTTONS (42) #define MASCHINE_BUTTON(X) ((X) + BTN_MISC) #define MASCHINE_PADS (16) @@ -605,13 +620,15 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) struct usb_device *usb_dev = cdev->chip.dev; struct input_dev *input; int i, ret = 0; + int len; input = input_allocate_device(); if (!input) return -ENOMEM; - usb_make_path(usb_dev, cdev->phys, sizeof(cdev->phys)); - strlcat(cdev->phys, "/input0", sizeof(cdev->phys)); + len = usb_make_path(usb_dev, cdev->phys, sizeof(cdev->phys)); + if (len >= 0) + strscpy(cdev->phys + len, "/input0", sizeof(cdev->phys) - len); input->name = cdev->product_name; input->phys = cdev->phys; @@ -666,6 +683,12 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore)); memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore)); + BUILD_BUG_ON(KORE_TOUCH_KEYS + ARRAY_SIZE(keycode_kore1_touch) > + ARRAY_SIZE(keycode_kore)); + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER)) + memcpy(cdev->keycode + KORE_TOUCH_KEYS, keycode_kore1_touch, + sizeof(keycode_kore1_touch)); input->keycodemax = ARRAY_SIZE(keycode_kore); input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10); input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10); diff --git a/sound/usb/caiaq/lcd.c b/sound/usb/caiaq/lcd.c new file mode 100644 index 000000000000..dd2a3b949a43 --- /dev/null +++ b/sound/usb/caiaq/lcd.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LCD support for the Native Instruments Kore controllers + * + * Copyright (c) 2026 Niko Huuskonen + * + * Both Kore controllers have a 128x64 pixel monochrome LCD behind an + * ST7565-style controller. The firmware passes EP1 packets with the + * command byte EP1_CMD_LCD on to that controller: + * + * EP1_CMD_LCD 0x00 + * EP1_CMD_LCD 0x01 + * + * The display is exposed as an exclusive hwdep device. Each write carries + * a whole frame of CAIAQ_LCD_FRAME_SIZE bytes: 8 pages of 128 columns, + * one byte per column and page, with bit 0 as the top pixel of the page. + * Only the pages that changed since the previous frame are sent to the + * device. The contrast is an ALSA control; the backlight is the existing + * "LED lcd" control. + * + * The controller setup and the packet layout follow the USB traffic of + * the vendor software, as documented by the OpenKoreBridge project. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "device.h" +#include "lcd.h" + +#define LCD_WIDTH 128 +#define LCD_PAGES (CAIAQ_LCD_FRAME_SIZE / LCD_WIDTH) +#define LCD_COLUMN_OFFSET 4 /* first visible controller column */ +#define LCD_CHUNK 32 /* display RAM bytes per packet */ +#define LCD_CONTRAST_MAX 0x3f +#define LCD_CONTRAST_DEFAULT 0x1c + +#define LCD_KIND_COMMAND 0x00 +#define LCD_KIND_DATA 0x01 + +static int lcd_send(struct snd_usb_caiaqdev *cdev, u8 kind, + const u8 *bytes, unsigned int len) +{ + u8 buf[2 + LCD_CHUNK]; + + if (WARN_ON(len > LCD_CHUNK)) + return -EINVAL; + + buf[0] = kind; + buf[1] = len; + memcpy(buf + 2, bytes, len); + return snd_usb_caiaq_send_command(cdev, EP1_CMD_LCD, buf, len + 2); +} + +static int lcd_command(struct snd_usb_caiaqdev *cdev, u8 cmd) +{ + return lcd_send(cdev, LCD_KIND_COMMAND, &cmd, 1); +} + +static int lcd_set_contrast(struct snd_usb_caiaqdev *cdev) +{ + const u8 cmd[] = { 0x81, cdev->lcd_contrast }; + + return lcd_send(cdev, LCD_KIND_COMMAND, cmd, sizeof(cmd)); +} + +/* controller setup, in the order used by the vendor software */ +static int lcd_setup(struct snd_usb_caiaqdev *cdev) +{ + static const u8 head[] = { + 0xe2, /* reset */ + 0xa1, /* reverse column direction */ + 0xc8, /* reverse row direction */ + 0xa2, /* 1/9 bias */ + 0x2c, 0x2e, 0x2f, /* power up in three steps */ + 0x27, /* regulator resistor ratio */ + }; + static const u8 tail[] = { + 0xa6, /* normal, non-inverted display */ + 0x88, 0xef, /* sent by the vendor software */ + 0xaf, /* display on */ + }; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(head); i++) { + ret = lcd_command(cdev, head[i]); + if (ret) + return ret; + } + + ret = lcd_set_contrast(cdev); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(tail); i++) { + ret = lcd_command(cdev, tail[i]); + if (ret) + return ret; + } + + return 0; +} + +static int lcd_write_page(struct snd_usb_caiaqdev *cdev, unsigned int page, + const u8 *data) +{ + unsigned int col; + int ret; + + for (col = 0; col < LCD_WIDTH; col += LCD_CHUNK) { + unsigned int addr = LCD_COLUMN_OFFSET + col; + const u8 column[] = { 0x10 | (addr >> 4), addr & 0x0f }; + + ret = lcd_command(cdev, 0xb0 | page); + if (!ret) + ret = lcd_send(cdev, LCD_KIND_COMMAND, + column, sizeof(column)); + if (!ret) + ret = lcd_send(cdev, LCD_KIND_DATA, + data + col, LCD_CHUNK); + if (ret) + return ret; + } + + return 0; +} + +static long lcd_hwdep_write(struct snd_hwdep *hw, const char __user *buf, + long count, loff_t *offset) +{ + struct snd_usb_caiaqdev *cdev = hw->private_data; + unsigned int page; + int ret; + + if (count != CAIAQ_LCD_FRAME_SIZE) + return -EINVAL; + + u8 *frame __free(kfree) = memdup_user(buf, count); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + guard(mutex)(&cdev->lcd_mutex); + + if (!cdev->lcd_ready) { + ret = lcd_setup(cdev); + if (ret) + return ret; + cdev->lcd_ready = true; + cdev->lcd_frame_valid = false; + } + + for (page = 0; page < LCD_PAGES; page++) { + u8 *shown = cdev->lcd_frame + page * LCD_WIDTH; + const u8 *next = frame + page * LCD_WIDTH; + + if (cdev->lcd_frame_valid && !memcmp(shown, next, LCD_WIDTH)) + continue; + + ret = lcd_write_page(cdev, page, next); + if (ret) { + cdev->lcd_frame_valid = false; + return ret; + } + memcpy(shown, next, LCD_WIDTH); + } + cdev->lcd_frame_valid = true; + + return count; +} + +static int lcd_contrast_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = LCD_CONTRAST_MAX; + return 0; +} + +static int lcd_contrast_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&cdev->lcd_mutex); + ucontrol->value.integer.value[0] = cdev->lcd_contrast; + return 0; +} + +static int lcd_contrast_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol); + long val = ucontrol->value.integer.value[0]; + int ret; + + if (val < 0 || val > LCD_CONTRAST_MAX) + return -EINVAL; + + guard(mutex)(&cdev->lcd_mutex); + if (val == cdev->lcd_contrast) + return 0; + + cdev->lcd_contrast = val; + if (cdev->lcd_ready) { + ret = lcd_set_contrast(cdev); + if (ret) + return ret; + } + + return 1; +} + +static const struct snd_kcontrol_new lcd_contrast_control = { + .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, + .name = "LCD Contrast", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = lcd_contrast_info, + .get = lcd_contrast_get, + .put = lcd_contrast_put, +}; + +int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev) +{ + struct snd_card *card = cdev->chip.card; + struct snd_hwdep *hw; + int ret; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + break; + default: + return 0; + } + + mutex_init(&cdev->lcd_mutex); + cdev->lcd_contrast = LCD_CONTRAST_DEFAULT; + + ret = snd_hwdep_new(card, "Kore LCD", 0, &hw); + if (ret < 0) + return ret; + + strscpy(hw->name, "Kore LCD", sizeof(hw->name)); + hw->iface = SNDRV_HWDEP_IFACE_CAIAQ; + hw->private_data = cdev; + hw->exclusive = 1; + hw->ops.write = lcd_hwdep_write; + + return snd_ctl_add(card, snd_ctl_new1(&lcd_contrast_control, cdev)); +} diff --git a/sound/usb/caiaq/lcd.h b/sound/usb/caiaq/lcd.h new file mode 100644 index 000000000000..423464750278 --- /dev/null +++ b/sound/usb/caiaq/lcd.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_LCD_H +#define CAIAQ_LCD_H + +int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev); + +#endif /* CAIAQ_LCD_H */ diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c index 18529484c8dc..b7d7d24937a7 100644 --- a/sound/usb/caiaq/midi.c +++ b/sound/usb/caiaq/midi.c @@ -79,6 +79,7 @@ static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *subs { struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; + guard(spinlock_irqsave)(&cdev->midi_lock); if (up) { cdev->midi_out_substream = substream; if (!cdev->midi_out_active) @@ -151,6 +152,7 @@ void snd_usb_caiaq_midi_output_done(struct urb* urb) { struct snd_usb_caiaqdev *cdev = urb->context; + guard(spinlock_irqsave)(&cdev->midi_lock); cdev->midi_out_active = 0; if (urb->status != 0) return; diff --git a/sound/usb/card.c b/sound/usb/card.c index 9307da95efbe..59979b793fe9 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -618,93 +618,70 @@ static void usb_audio_make_shortname(struct usb_device *dev, { struct snd_card *card = chip->card; const struct usb_audio_device_name *preset; - const char *s = NULL; + *card->shortname = 0; preset = lookup_device_name(chip->usb_id); if (preset && preset->product_name) - s = preset->product_name; + strscpy(card->shortname, preset->product_name); else if (quirk && quirk->product_name) - s = quirk->product_name; - if (s && *s) { - strscpy(card->shortname, s, sizeof(card->shortname)); + strscpy(card->shortname, quirk->product_name); + if (*card->shortname) + return; + + if (dev->product && *dev->product) { + strscpy(card->shortname, dev->product); + strim(card->shortname); return; } - /* retrieve the device string as shortname */ - if (dev->product && *dev->product) { - strscpy(card->shortname, dev->product); - } else { - /* no name available from anywhere, so use ID */ - scnprintf(card->shortname, sizeof(card->shortname), - "USB Device %#04x:%#04x", - USB_ID_VENDOR(chip->usb_id), - USB_ID_PRODUCT(chip->usb_id)); - } - - strim(card->shortname); + scnprintf(card->shortname, sizeof(card->shortname), "USB Device %#04x:%#04x", + USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id)); } static void usb_audio_make_longname(struct usb_device *dev, struct snd_usb_audio *chip, const struct snd_usb_audio_quirk *quirk) { + static const char * const speed_suffixes[] = { + [USB_SPEED_LOW] = ", low speed", + [USB_SPEED_FULL] = ", full speed", + [USB_SPEED_HIGH] = ", high speed", + [USB_SPEED_SUPER] = ", super speed", + [USB_SPEED_SUPER_PLUS] = ", super speed plus", + }; struct snd_card *card = chip->card; const struct usb_audio_device_name *preset; - const char *s = NULL; - int len; + enum usb_device_speed speed = snd_usb_get_speed(dev); + int len, ret; preset = lookup_device_name(chip->usb_id); /* shortcut - if any pre-defined string is given, use it */ - if (preset && preset->profile_name) - s = preset->profile_name; - if (s && *s) { - strscpy(card->longname, s, sizeof(card->longname)); + if (preset && preset->profile_name && *preset->profile_name) { + strscpy(card->longname, preset->profile_name); return; } - if (preset && preset->vendor_name) - s = preset->vendor_name; - else if (quirk && quirk->vendor_name) - s = quirk->vendor_name; *card->longname = 0; - if (s && *s) - strscpy(card->longname, s); - else if (dev->manufacturer && *dev->manufacturer) - strscpy(card->longname, dev->manufacturer); + if (preset && preset->vendor_name) + strscpy(card->longname, preset->vendor_name); + else if (quirk && quirk->vendor_name) + strscpy(card->longname, quirk->vendor_name); + if (!*card->longname) + strscpy(card->longname, dev->manufacturer ?: ""); - if (*card->longname) { - strim(card->longname); - if (*card->longname) - strlcat(card->longname, " ", sizeof(card->longname)); - } + strim(card->longname); + len = strlen(card->longname); + len += scnprintf(card->longname + len, sizeof(card->longname) - len, + "%s%s at ", len ? " " : "", card->shortname); - strlcat(card->longname, card->shortname, sizeof(card->longname)); + ret = usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); + if (ret < 0) + return; + len += ret; - len = strlcat(card->longname, " at ", sizeof(card->longname)); - - if (len < sizeof(card->longname)) - usb_make_path(dev, card->longname + len, sizeof(card->longname) - len); - - switch (snd_usb_get_speed(dev)) { - case USB_SPEED_LOW: - strlcat(card->longname, ", low speed", sizeof(card->longname)); - break; - case USB_SPEED_FULL: - strlcat(card->longname, ", full speed", sizeof(card->longname)); - break; - case USB_SPEED_HIGH: - strlcat(card->longname, ", high speed", sizeof(card->longname)); - break; - case USB_SPEED_SUPER: - strlcat(card->longname, ", super speed", sizeof(card->longname)); - break; - case USB_SPEED_SUPER_PLUS: - strlcat(card->longname, ", super speed plus", sizeof(card->longname)); - break; - default: - break; - } + if (speed < ARRAY_SIZE(speed_suffixes) && speed_suffixes[speed]) + strscpy(card->longname + len, speed_suffixes[speed], sizeof(card->longname) - len); } static void snd_usb_init_quirk_flags(int idx, struct snd_usb_audio *chip) diff --git a/sound/usb/card.h b/sound/usb/card.h index 8299ac241c60..f956bcf00f77 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -123,6 +123,7 @@ struct snd_usb_endpoint { unsigned int syncmaxsize; /* sync endpoint packet size */ unsigned int fill_max:1; /* fill max packet size always */ unsigned int tenor_fb_quirk:1; /* corrupted feedback data */ + unsigned int snap_fb_quirk:1; /* ignore implausible feedback */ unsigned int datainterval; /* log_2 of data packet interval */ unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ unsigned char silence_value; diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 34832183a1f0..cc77eace7b59 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -549,6 +549,30 @@ static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, return le32_to_cpu(data); } +/* + * Return the partner rate in the other base-rate family. + * See QUIRK_FLAG_SWAP_RATES. + */ +static unsigned int swap_base_rate(unsigned int rate) +{ + switch (rate) { + case 44100: + return 48000; + case 48000: + return 44100; + case 88200: + return 96000; + case 96000: + return 88200; + case 176400: + return 192000; + case 192000: + return 176400; + default: + return rate; + } +} + /* * Try to set the given sample rate: * @@ -585,6 +609,9 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, if (!writeable) return 0; + if (chip->quirk_flags & QUIRK_FLAG_SWAP_RATES) + rate = swap_base_rate(rate); + data = cpu_to_le32(rate); err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 879d0451536e..9c844c55ad3f 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -1908,6 +1908,22 @@ static void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, else f >>= -ep->freqshift; + if (unlikely(sender->snap_fb_quirk) && + (f < ep->freqn - ep->freqn / 128 || + f > ep->freqn + ep->freqn / 128)) { + /* + * Feedback deviates from nominal far beyond what real + * clock differences can explain. This firmware can get + * stuck at nominal+1 sample per frame, causing audible + * pops. Trust the nominal rate instead. + */ + dev_warn_ratelimited(&ep->chip->dev->dev, + "EP 0x%x: ignoring implausible feedback 0x%x.%04x (nominal 0x%x.%04x)\n", + ep->ep_num, f >> 16, f & 0xffff, + ep->freqn >> 16, ep->freqn & 0xffff); + f = ep->freqn; + } + if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) { /* * If the frequency looks valid, set it. diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c index bce28f683666..41a43bc59397 100644 --- a/sound/usb/hiface/chip.c +++ b/sound/usb/hiface/chip.c @@ -63,18 +63,15 @@ static int hiface_chip_create(struct usb_interface *intf, return ret; } - strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + strscpy(card->driver, DRIVER_NAME); if (quirk && quirk->device_name) - strscpy(card->shortname, quirk->device_name, sizeof(card->shortname)); + strscpy(card->shortname, quirk->device_name); else - strscpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname)); + strscpy(card->shortname, "M2Tech generic audio"); - strlcat(card->longname, card->shortname, sizeof(card->longname)); - len = strlcat(card->longname, " at ", sizeof(card->longname)); - if (len < sizeof(card->longname)) - usb_make_path(device, card->longname + len, - sizeof(card->longname) - len); + len = scnprintf(card->longname, sizeof(card->longname), "%s at ", card->shortname); + usb_make_path(device, card->longname + len, sizeof(card->longname) - len); chip = card->private_data; chip->dev = device; diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 6dbaf30232f9..17f49f567efb 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -117,7 +117,8 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) } else { /* copy single chunk */ memcpy(runtime->dma_area + - line6pcm->in.pos_done * bytes_per_frame, fbuf, fsize); + line6pcm->in.pos_done * bytes_per_frame, fbuf, + frames * bytes_per_frame); } line6pcm->in.pos_done += frames; @@ -125,24 +126,21 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) line6pcm->in.pos_done -= runtime->buffer_size; } -void line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length) +/* update the capture position and check whether we need period_elapsed call */ +bool line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length) { - struct snd_pcm_substream *substream = - get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); - line6pcm->in.bytes += length; if (line6pcm->in.bytes >= line6pcm->in.period) { line6pcm->in.bytes %= line6pcm->in.period; - spin_unlock(&line6pcm->in.lock); - snd_pcm_period_elapsed(substream); - spin_lock(&line6pcm->in.lock); + return true; } + return false; } /* * Callback for completed capture URB. */ -static void audio_in_callback(struct urb *urb) +static bool handle_audio_in(struct urb *urb) { int i, index, length = 0, shutdown = 0; struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; @@ -207,8 +205,18 @@ static void audio_in_callback(struct urb *urb) if (!test_bit(LINE6_STREAM_IMPULSE, &line6pcm->in.running) && test_bit(LINE6_STREAM_PCM, &line6pcm->in.running)) - line6_capture_check_period(line6pcm, length); + return line6_capture_check_period(line6pcm, length); } + + return false; +} + +static void audio_in_callback(struct urb *urb) +{ + struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; + + if (handle_audio_in(urb)) + snd_pcm_period_elapsed(get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE)); } /* open capture callback */ diff --git a/sound/usb/line6/capture.h b/sound/usb/line6/capture.h index 90572dae134e..628315389efc 100644 --- a/sound/usb/line6/capture.h +++ b/sound/usb/line6/capture.h @@ -17,7 +17,7 @@ extern const struct snd_pcm_ops snd_line6_capture_ops; extern void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize); -extern void line6_capture_check_period(struct snd_line6_pcm *line6pcm, +extern bool line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length); extern int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm); extern int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm); diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 2932eaf157f4..d17baf7761c6 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -155,7 +155,7 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { pstr->buffer = - kmalloc(array3_size(line6pcm->line6->iso_buffers, + kzalloc(array3_size(line6pcm->line6->iso_buffers, LINE6_ISO_PACKETS, pkt_size), GFP_KERNEL); if (!pstr->buffer) @@ -173,6 +173,14 @@ static void line6_buffer_release(struct snd_line6_pcm *line6pcm, clear_bit(type, &pstr->opened); if (!pstr->opened) { line6_wait_clear_audio_urbs(line6pcm, pstr); + if (pstr == &line6pcm->in) { + /* prev_fbuf points into the capture buffer we are about + * to free; drop it while no capture URB is in flight. + */ + guard(spinlock_irqsave)(&line6pcm->in.lock); + line6pcm->prev_fbuf = NULL; + line6pcm->prev_fsize = 0; + } kfree(pstr->buffer); pstr->buffer = NULL; } @@ -554,7 +562,11 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->max_packet_size_out = usb_maxpacket(line6->usbdev, usb_sndisocpipe(line6->usbdev, ep_write)); - if (!line6pcm->max_packet_size_in || !line6pcm->max_packet_size_out) { + /* reject max packet sizes smaller than bytes-per-frame; + * (the magic number 6 is taken from the playback case) + */ + if (line6pcm->max_packet_size_in < 6 || + line6pcm->max_packet_size_out < 6) { dev_err(line6pcm->line6->ifcdev, "cannot get proper max packet size\n"); return -EINVAL; diff --git a/sound/usb/line6/playback.c b/sound/usb/line6/playback.c index aa6ddf8746a0..e93a16f6958e 100644 --- a/sound/usb/line6/playback.c +++ b/sound/usb/line6/playback.c @@ -136,6 +136,8 @@ static void add_monitor_signal(struct urb *urb_out, unsigned char *signal, /* Find a free URB, prepare audio data, and submit URB. must be called in line6pcm->out.lock context + Returns 1 if snd_pcm_period_elapsed() needs to be called for the + capture stream after releasing the lock, 0 if not, or a negative error */ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) { @@ -151,6 +153,7 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) line6pcm->properties->rates.rats[0].den * (line6pcm->line6->intervals_per_second / LINE6_ISO_INTERVAL); struct urb *urb_out; + bool capture_period_elapsed = false; index = find_first_zero_bit(&line6pcm->out.active_urbs, line6pcm->line6->iso_buffers); @@ -260,8 +263,9 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) urb_out->transfer_buffer, urb_out-> transfer_buffer_length); - line6_capture_check_period(line6pcm, - urb_out->transfer_buffer_length); + capture_period_elapsed = + line6_capture_check_period(line6pcm, + urb_out->transfer_buffer_length); } } else { if (!(line6pcm->line6->properties->capabilities & LINE6_CAP_HWMON) @@ -283,24 +287,26 @@ static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) dev_err(line6pcm->line6->ifcdev, "URB out #%d submission failed (%d)\n", index, ret); - return 0; + return capture_period_elapsed; } /* Submit all currently available playback URBs. must be called in line6pcm->out.lock context + The capture period notification is skipped here since we can't call + snd_pcm_period_elapsed() in the lock; it'll be caught up at the next one. */ int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm) { - int ret = 0, i; + int ret, i; for (i = 0; i < line6pcm->line6->iso_buffers; ++i) { ret = submit_audio_out_urb(line6pcm); if (ret < 0) - break; + return ret; } - return ret; + return 0; } /* @@ -316,6 +322,8 @@ static void audio_out_callback(struct urb *urb) const int bytes_per_frame = line6pcm->properties->bytes_per_channel * line6pcm->properties->playback_hw.channels_max; + bool capture_period_elapsed = false; + bool playback_period_elapsed = false; #if USE_CLEAR_BUFFER_WORKAROUND memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); @@ -358,19 +366,22 @@ static void audio_out_callback(struct urb *urb) shutdown = 1; if (!shutdown) { - submit_audio_out_urb(line6pcm); + capture_period_elapsed = submit_audio_out_urb(line6pcm) > 0; if (test_bit(LINE6_STREAM_PCM, &line6pcm->out.running)) { line6pcm->out.bytes += length; if (line6pcm->out.bytes >= line6pcm->out.period) { line6pcm->out.bytes %= line6pcm->out.period; - spin_unlock(&line6pcm->out.lock); - snd_pcm_period_elapsed(substream); - spin_lock(&line6pcm->out.lock); + playback_period_elapsed = true; } } } spin_unlock_irqrestore(&line6pcm->out.lock, flags); + + if (capture_period_elapsed) + snd_pcm_period_elapsed(get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE)); + if (playback_period_elapsed) + snd_pcm_period_elapsed(substream); } /* open playback callback */ diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 7e5b1b13360f..e3d157554061 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -113,6 +113,7 @@ struct snd_usb_midi { unsigned int opened[2]; unsigned char disconnected; unsigned char input_running; + unsigned char keep_input_running; struct snd_kcontrol *roland_load_ctl; }; @@ -1149,41 +1150,42 @@ static int substream_open(struct snd_rawmidi_substream *substream, int dir, int open) { struct snd_usb_midi *umidi = substream->rmidi->private_data; - struct snd_kcontrol *ctl; + struct snd_ctl_elem_id ctl_id; + bool activate_ctl = false; + bool active; guard(rwsem_read)(&umidi->disc_rwsem); if (umidi->disconnected) return open ? -ENODEV : 0; - guard(mutex)(&umidi->mutex); - if (open) { - if (!umidi->opened[0] && !umidi->opened[1]) { - if (umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); - update_roland_altsetting(umidi); + scoped_guard(mutex, &umidi->mutex) { + if (open) { + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl_id = umidi->roland_load_ctl->id; + activate_ctl = true; + active = false; + update_roland_altsetting(umidi); + } } - } - umidi->opened[dir]++; - if (umidi->opened[1]) - snd_usbmidi_input_start(&umidi->list); - } else { - umidi->opened[dir]--; - if (!umidi->opened[1]) - snd_usbmidi_input_stop(&umidi->list); - if (!umidi->opened[0] && !umidi->opened[1]) { - if (umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + umidi->opened[dir]++; + if (umidi->opened[1]) + snd_usbmidi_input_start(&umidi->list); + } else { + umidi->opened[dir]--; + if (!umidi->opened[1] && !umidi->keep_input_running) + snd_usbmidi_input_stop(&umidi->list); + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl_id = umidi->roland_load_ctl->id; + activate_ctl = true; + active = true; + } } } } + if (activate_ctl) + snd_ctl_activate_id(umidi->card, &ctl_id, active); return 0; } @@ -1281,10 +1283,7 @@ static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream, { struct snd_usb_midi *umidi = substream->rmidi->private_data; - if (up) - set_bit(substream->number, &umidi->input_triggered); - else - clear_bit(substream->number, &umidi->input_triggered); + assign_bit(substream->number, &umidi->input_triggered, up); } static const struct snd_rawmidi_ops snd_usbmidi_output_ops = { @@ -2262,6 +2261,10 @@ static int snd_usbmidi_detect_roland(struct snd_usb_midi *umidi, struct usb_host_interface *hostif; u8 *cs_desc; + /* TD-11 becomes unusable if input isn't serviced continuously */ + if (umidi->usb_id == USB_ID(0x0582, 0x0151)) + umidi->keep_input_running = 1; + intf = umidi->iface; if (!intf) return -ENOENT; @@ -2469,7 +2472,7 @@ void snd_usbmidi_input_start(struct list_head *p) int i; umidi = list_entry(p, struct snd_usb_midi, list); - if (umidi->input_running || !umidi->opened[1]) + if (umidi->input_running || (!umidi->opened[1] && !umidi->keep_input_running)) return; for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) snd_usbmidi_input_start_ep(umidi, umidi->endpoints[i].in); @@ -2644,6 +2647,10 @@ int __snd_usbmidi_create(struct snd_card *card, if (err < 0) goto exit; + /* start input for devices that need continuous servicing */ + if (umidi->keep_input_running) + snd_usbmidi_input_start(&umidi->list); + usb_autopm_get_interface_no_resume(umidi->iface); list_add_tail(&umidi->list, midi_list); diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index ecaa8bc08d7c..95209ec006b3 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -304,8 +304,9 @@ static inline int mixer_ctrl_intf(struct usb_mixer_interface *mixer) return get_iface_desc(mixer->hostif)->bInterfaceNumber; } -static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* send a request for UAC1 feature & mixer unit */ +static int request_get_ctl_v1(struct usb_mixer_elem_info *cval, + u8 request, int validx, int *value_ret) { struct snd_usb_audio *chip = cval->head.mixer->chip; unsigned char buf[2]; @@ -317,6 +318,8 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, if (pm.err < 0) return -EIO; + validx += cval->idx_off; + while (timeout-- > 0) { idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request, @@ -335,94 +338,84 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, return -EINVAL; } -static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* convert the given UAC1 wValue (ICN|OCN) to UAC2 MCN */ +static unsigned char to_mcn(const struct usb_mixer_elem_info *cval, + unsigned int validx) +{ + unsigned char m = (validx >> 8) & 0xff; /* 1-based input channel */ + unsigned char v = validx & 0xff; /* 1-based output channel */ + + /* num_inputs * num_outputs is guaranteed to be < 256 */ + return (m - 1) * cval->num_outputs + (v - 1); +} + +/* send a request for UAC2 feature & mixer unit */ +static int request_get_ctl_v2(struct usb_mixer_elem_info *cval, + u8 request, int validx, unsigned char *buf, + int size) { struct snd_usb_audio *chip = cval->head.mixer->chip; - /* enough space for one range */ - unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)]; - unsigned char *val; - int idx = 0, ret, val_size, size; - __u8 bRequest; + int idx, ret; - val_size = uac2_ctl_value_size(cval->val_type); + CLASS(snd_usb_lock, pm)(chip); + if (pm.err) + return -EIO; - if (request == UAC_GET_CUR) { - bRequest = UAC2_CS_CUR; - size = val_size; - } else { - bRequest = UAC2_CS_RANGE; - size = sizeof(__u16) + 3 * val_size; - } + validx += cval->idx_off; - memset(buf, 0, sizeof(buf)); + /* correct wValue for UAC2 mixer control with MCN */ + if (cval->v2_mixer) + validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); - { - CLASS(snd_usb_lock, pm)(chip); - if (pm.err) - return -EIO; - - idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); - ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - validx, idx, buf, size); - } - - if (ret < 0) { + memset(buf, 0, size); + idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + request, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, buf, size); + if (ret < 0) usb_audio_dbg(chip, "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", request, validx, idx, cval->val_type); + + return ret; +} + +/* read the current value for UAC2 */ +static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, + int validx, int *value_ret) +{ + /* enough space for one value */ + unsigned char buf[sizeof(__u32)]; + int ret, val_size; + + val_size = uac2_ctl_value_size(cval->val_type); + + ret = request_get_ctl_v2(cval, UAC2_CS_CUR, validx, buf, val_size); + if (ret < 0) return ret; - } - - /* FIXME: how should we handle multiple triplets here? */ - - switch (request) { - case UAC_GET_CUR: - val = buf; - break; - case UAC_GET_MIN: - val = buf + sizeof(__u16); - break; - case UAC_GET_MAX: - val = buf + sizeof(__u16) + val_size; - break; - case UAC_GET_RES: - val = buf + sizeof(__u16) + val_size * 2; - break; - default: - return -EINVAL; - } *value_ret = convert_signed_value(cval, - snd_usb_combine_bytes(val, val_size)); - + snd_usb_combine_bytes(buf, val_size)); return 0; } -static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) -{ - validx += cval->idx_off; - - return (cval->head.mixer->protocol == UAC_VERSION_1) ? - get_ctl_value_v1(cval, request, validx, value_ret) : - get_ctl_value_v2(cval, request, validx, value_ret); -} - +/* read the current value */ static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, - int validx, int *value) + int validx, int *value_ret) { - return get_ctl_value(cval, UAC_GET_CUR, validx, value); + return (cval->head.mixer->protocol == UAC_VERSION_1) ? + request_get_ctl_v1(cval, UAC_GET_CUR, validx, value_ret) : + get_ctl_value_v2(cval, validx, value_ret); } /* channel = 0: master, 1 = first channel */ static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, int channel, int *value) { - return get_ctl_value(cval, UAC_GET_CUR, - (cval->control << 8) | channel, - value); + return get_cur_ctl_value(cval, + (cval->control << 8) | channel, + value); } int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, @@ -430,7 +423,7 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, { int err; - if (cval->cached & BIT(channel)) { + if (test_bit(channel, cval->cached)) { *value = cval->cache_val[index]; return 0; } @@ -447,11 +440,86 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, cval->control, channel, err); return err; } - cval->cached |= BIT(channel); + set_bit(channel, cval->cached); cval->cache_val[index] = *value; return 0; } +/* extract the mixer min/max/res info from UAC1 feature / mixer unit */ +static int get_ctl_range_v1(struct usb_mixer_elem_info *cval, int validx) +{ + int last_valid_res; + + if (request_get_ctl_v1(cval, UAC_GET_MAX, validx, &cval->max) < 0 || + request_get_ctl_v1(cval, UAC_GET_MIN, validx, &cval->min) < 0) { + usb_audio_err(cval->head.mixer->chip, + "%d:%d: cannot get min/max values for control %d (id %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->control, cval->head.id); + return -EAGAIN; /* handled by the caller later again */ + } + + if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0) { + cval->res = 1; + return 0; + } + + last_valid_res = cval->res; + while (cval->res > 1) { + if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, + validx, cval->res / 2) < 0) + break; + cval->res /= 2; + } + if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0) + cval->res = last_valid_res; + + return 0; +} + +/* extract the mixer min/max/res info from UAC2 feature / mixer unit */ +static int get_ctl_range_v2(struct usb_mixer_elem_info *cval, int validx) +{ + /* enough space for one range */ + unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)]; + unsigned char *val; + int val_size, size; + + val_size = uac2_ctl_value_size(cval->val_type); + size = sizeof(__u16) + 3 * val_size; + + if (request_get_ctl_v2(cval, UAC2_CS_RANGE, validx, buf, size) < 0) { + usb_audio_err(cval->head.mixer->chip, + "%d:%d: cannot get RANGE values for control %d (id %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->control, cval->head.id); + return -EAGAIN; /* handled by the caller later again */ + } + + /* FIXME: how should we handle multiple triplets here? */ + val = buf + 2; + cval->min = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + val += val_size; + cval->max = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + val += val_size; + cval->res = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + return 0; +} + +/* extract the mixer min/max/res info */ +static int get_ctl_range(struct usb_mixer_elem_info *cval, int validx) +{ + switch (cval->head.mixer->protocol) { + case UAC_VERSION_1: + return get_ctl_range_v1(cval, validx); + case UAC_VERSION_2: + case UAC_VERSION_3: + return get_ctl_range_v2(cval, validx); + default: + return -EINVAL; + } +} + /* * set a mixer value */ @@ -478,6 +546,10 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, } request = UAC2_CS_CUR; + + /* correct wValue for UAC2 mixer control with MCN */ + if (cval->v2_mixer) + validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); } value_set = convert_bytes_value(cval, value_set); @@ -532,7 +604,7 @@ int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, value); if (err < 0) return err; - cval->cached |= BIT(channel); + set_bit(channel, cval->cached); cval->cache_val[index] = value; return 0; } @@ -577,20 +649,25 @@ static int check_matrix_bitmap(unsigned char *bmap, /* * add an alsa control element - * search and increment the index until an empty slot is found. + * when @look_for_empty is set, search and increment the index until an empty + * slot is found. * * if failed, give up and free the control instance. */ int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, struct snd_kcontrol *kctl, - bool is_std_info) + bool is_std_info, + bool look_for_empty) { struct usb_mixer_interface *mixer = list->mixer; int err; - while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) - kctl->id.index++; + if (look_for_empty) { + while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) + kctl->id.index++; + } + err = snd_ctl_add(mixer->chip->card, kctl); if (err < 0) { usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n", @@ -786,7 +863,7 @@ static int parse_term_uac2_iterm_unit(struct mixer_build *state, return err; /* save input term properties after recursion, - * to ensure they are not overriden by the recursion calls + * to ensure they are not overridden by the recursion calls */ term->id = id; term->type = le16_to_cpu(d->wTerminalType); @@ -809,7 +886,7 @@ static int parse_term_uac3_iterm_unit(struct mixer_build *state, return err; /* save input term properties after recursion, - * to ensure they are not overriden by the recursion calls + * to ensure they are not overridden by the recursion calls */ term->id = id; term->type = le16_to_cpu(d->wTerminalType); @@ -1344,32 +1421,11 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, break; } } - if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 || - get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { - usb_audio_err(cval->head.mixer->chip, - "%d:%d: cannot get min/max values for control %d (id %d)\n", - cval->head.id, mixer_ctrl_intf(cval->head.mixer), - cval->control, cval->head.id); - return -EAGAIN; - } - if (get_ctl_value(cval, UAC_GET_RES, - (cval->control << 8) | minchn, - &cval->res) < 0) { - cval->res = 1; - } else if (cval->head.mixer->protocol == UAC_VERSION_1) { - int last_valid_res = cval->res; - while (cval->res > 1) { - if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, - (cval->control << 8) | minchn, - cval->res / 2) < 0) - break; - cval->res /= 2; - } - if (get_ctl_value(cval, UAC_GET_RES, - (cval->control << 8) | minchn, &cval->res) < 0) - cval->res = last_valid_res; - } + ret = get_ctl_range(cval, (cval->control << 8) | minchn); + if (ret < 0) + return ret; + if (cval->res == 0) cval->res = 1; @@ -1398,7 +1454,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, * properly. */ if (ret) - cval->cached = 0; + bitmap_zero(cval->cached, MAX_CHANNELS + 1); cval->initialized = 1; } @@ -1480,6 +1536,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int ret; + guard(mutex)(&cval->head.mixer->lock); if (cval->val_type == USB_MIXER_BOOLEAN || cval->val_type == USB_MIXER_INV_BOOLEAN) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1514,6 +1571,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int c, cnt, val, err; + guard(mutex)(&cval->head.mixer->lock); ucontrol->value.integer.value[0] = cval->min; if (cval->cmask) { cnt = 0; @@ -1544,10 +1602,12 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); - int max_val = get_max_exposed(cval); + int max_val; int c, cnt, val, oval, err; int changed = 0; + guard(mutex)(&cval->head.mixer->lock); + max_val = get_max_exposed(cval); if (cval->cmask) { cnt = 0; for (c = 0; c < MAX_CHANNELS; c++) { @@ -1595,6 +1655,7 @@ static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; + guard(mutex)(&cval->head.mixer->lock); err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return filter_error(cval, err); @@ -1720,9 +1781,11 @@ const struct snd_kcontrol_new *snd_usb_feature_unit_ctl = &usb_feature_unit_ctl; /* * build a feature control */ -static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) +static void append_ctl_name(struct snd_kcontrol *kctl, const char *str) { - return strlcat(kctl->id.name, str, sizeof(kctl->id.name)); + size_t len = strlen(kctl->id.name); + + strscpy(kctl->id.name + len, str, sizeof(kctl->id.name) - len); } /* @@ -2090,7 +2153,7 @@ static void build_connector_control(struct usb_mixer_interface *mixer, } if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) - strlcat(kctl->id.name, " Jack", sizeof(kctl->id.name)); + append_ctl_name(kctl, " Jack"); else get_connector_control_name(mixer, term, is_input, kctl->id.name, sizeof(kctl->id.name)); @@ -2326,7 +2389,7 @@ static bool mixer_bitmap_overflow(struct uac_mixer_unit_descriptor *desc, */ static void build_mixer_unit_ctl(struct mixer_build *state, struct uac_mixer_unit_descriptor *desc, - int in_pin, int in_ch, int num_outs, + int in_ch, int num_outs, int unitid, struct usb_audio_term *iterm) { struct usb_mixer_elem_info *cval; @@ -2345,6 +2408,10 @@ static void build_mixer_unit_ctl(struct mixer_build *state, snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid); cval->control = in_ch + 1; /* based on 1 */ + if (state->mixer->protocol == UAC_VERSION_2 || + state->mixer->protocol == UAC_VERSION_3) + cval->v2_mixer = true; + cval->num_outputs = num_outs; cval->val_type = USB_MIXER_S16; for (i = 0; i < num_outs; i++) { __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); @@ -2417,24 +2484,13 @@ static int parse_audio_input_terminal(struct mixer_build *state, int unitid, /* * parse a mixer unit */ -static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, - void *raw_desc) +/* UAC1 mixer unit */ +static int parse_audio_mixer_unit_v1(struct mixer_build *state, int unitid, + int input_pins, int num_outs, + struct uac_mixer_unit_descriptor *desc) { - struct uac_mixer_unit_descriptor *desc = raw_desc; struct usb_audio_term iterm; - int input_pins, num_ins, num_outs; - int pin, ich, err; - - err = uac_mixer_unit_get_channels(state, desc); - if (err < 0) { - usb_audio_err(state->chip, - "invalid MIXER UNIT descriptor %d\n", - unitid); - return err; - } - - num_outs = err; - input_pins = desc->bNrInPins; + int num_ins, pin, ich, och, err; num_ins = 0; ich = 0; @@ -2453,25 +2509,90 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, num_ins, num_outs)) break; for (; ich < num_ins; ich++) { - int och, ich_has_controls = 0; - for (och = 0; och < num_outs; och++) { __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); - if (check_matrix_bitmap(c, ich, och, num_outs)) { - ich_has_controls = 1; + if (check_matrix_bitmap(c, ich, och, num_outs)) break; - } } - if (ich_has_controls) - build_mixer_unit_ctl(state, desc, pin, ich, num_outs, + if (och < num_outs) + build_mixer_unit_ctl(state, desc, ich, num_outs, unitid, &iterm); } } return 0; } +/* UAC2/UAC3 mixer unit */ +static int parse_audio_mixer_unit_v2(struct mixer_build *state, int unitid, + int input_pins, int num_outs, + struct uac_mixer_unit_descriptor *desc) +{ + struct usb_audio_term iterm; + int pin, och, err; + + if (input_pins * num_outs > 256 || + mixer_bitmap_overflow(desc, state->mixer->protocol, + input_pins, num_outs)) { + usb_audio_err(state->chip, + "invalid channels for MIXER UNIT %d: input=%d, output=%d\n", + unitid, input_pins, num_outs); + return -EINVAL; + } + + for (pin = 0; pin < input_pins; pin++) { + err = parse_audio_unit(state, desc->baSourceID[pin]); + if (err < 0) + continue; + if (!num_outs) + continue; + err = check_input_term(state, desc->baSourceID[pin], &iterm); + if (err < 0) + return err; + + for (och = 0; och < num_outs; och++) { + __u8 *c = uac_mixer_unit_bmControls(desc, + state->mixer->protocol); + + if (check_matrix_bitmap(c, pin, och, num_outs)) + break; + } + if (och < num_outs) + build_mixer_unit_ctl(state, desc, pin, num_outs, + unitid, &iterm); + } + return 0; +} + +static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, + void *raw_desc) +{ + struct uac_mixer_unit_descriptor *desc = raw_desc; + int num_outs; + + num_outs = uac_mixer_unit_get_channels(state, desc); + if (num_outs < 0) { + usb_audio_err(state->chip, + "invalid MIXER UNIT descriptor %d\n", + unitid); + return num_outs; + } + + switch (state->mixer->protocol) { + case UAC_VERSION_1: + default: + return parse_audio_mixer_unit_v1(state, unitid, + desc->bNrInPins, num_outs, + desc); + case UAC_VERSION_2: + case UAC_VERSION_3: + return parse_audio_mixer_unit_v2(state, unitid, + desc->bNrInPins, num_outs, + desc); + } +} + /* * Processing Unit / Extension Unit */ @@ -2483,6 +2604,7 @@ static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int err, val; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { ucontrol->value.integer.value[0] = cval->min; @@ -2500,6 +2622,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); @@ -2851,6 +2974,7 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { ucontrol->value.enumerated.item[0] = 0; @@ -2868,6 +2992,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); @@ -3140,6 +3265,7 @@ static void snd_usb_mixer_free(struct usb_mixer_interface *mixer) } usb_free_urb(mixer->rc_urb); kfree(mixer->rc_setup_packet); + mutex_destroy(&mixer->lock); kfree(mixer); } @@ -3550,7 +3676,7 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) info = mixer_elem_list_to_info(list); /* invalidate cache, so the value is read from the device */ if (!info->get_cur_broken) - info->cached = 0; + bitmap_zero(info->cached, MAX_CHANNELS + 1); snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &list->kctl->id); } @@ -3617,7 +3743,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, __u8 channel = value & 0xff; unsigned int count = 0; - if (channel >= MAX_CHANNELS) { + if (channel > MAX_CHANNELS) { usb_audio_dbg(mixer->chip, "%s(): bogus channel number %d\n", __func__, channel); @@ -3649,9 +3775,9 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, /* invalidate cache, so the value is read from the device */ if (!info->get_cur_broken) { if (channel) - info->cached &= ~BIT(channel); + clear_bit(channel, info->cached); else /* master channel */ - info->cached = 0; + bitmap_zero(info->cached, MAX_CHANNELS + 1); } snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, @@ -3784,6 +3910,8 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif) return -ENOMEM; } + mutex_init(&mixer->lock); + mixer->hostif = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0]; switch (get_iface_desc(mixer->hostif)->bInterfaceProtocol) { case UAC_VERSION_1: @@ -3887,7 +4015,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list) for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & BIT(c))) continue; - if (cval->cached & BIT(c + 1)) { + if (test_bit(c + 1, cval->cached)) { err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) @@ -3897,7 +4025,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list) } } else { /* master */ - if (cval->cached) + if (test_bit(0, cval->cached)) snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 037b446d8b6f..c40f832852be 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -18,6 +18,7 @@ struct usb_mixer_interface { struct usb_host_interface *hostif; struct list_head list; unsigned int ignore_ctl_error; + struct mutex lock; /* lock for feature unit callbacks */ /* UAC2 status interrupt endpoint; owned by mixer.c */ struct urb *urb; /* array[MAX_ID_ELEMS], indexed by unit id */ @@ -92,11 +93,13 @@ struct usb_mixer_elem_info { int min, max, res; int max_exposed; /* control API exposes the value in 0..max_exposed */ int dBmin, dBmax; - int cached; + DECLARE_BITMAP(cached, MAX_CHANNELS + 1); int cache_val[MAX_CHANNELS]; u8 initialized; u8 min_mute; u8 get_cur_broken; + u8 num_outputs; + bool v2_mixer; void *private_data; }; @@ -110,10 +113,11 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, struct snd_kcontrol *kctl, - bool is_std_info); + bool is_std_info, + bool look_for_empty); #define snd_usb_mixer_add_control(list, kctl) \ - snd_usb_mixer_add_list(list, kctl, true) + snd_usb_mixer_add_list(list, kctl, true, true) void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, struct usb_mixer_interface *mixer, diff --git a/sound/usb/mixer_evo.c b/sound/usb/mixer_evo.c new file mode 100644 index 000000000000..c327b82c8d2d --- /dev/null +++ b/sound/usb/mixer_evo.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Audient EVO driver for ALSA + * Copyright (C) 2026 Christian Ruppert + * + * Based on the work of Ivan Hazucha for audient-evo-py. + * https://github.com/vanzaho/audient-evo-py/ + */ + + +#include +#include +#include +#include +#include +#include + +#include "usbaudio.h" +#include "mixer.h" +#include "mixer_quirks.h" +#include "helper.h" +#include "mixer_evo.h" + + +enum snd_evo_type { + SND_EVO_TYPE_MONITOR = 0, + SND_EVO_TYPE_PHANTOM, + SND_EVO_TYPE_MUTE, + SND_EVO_TYPE_MUTE_OUT, + SND_EVO_TYPE_MIXER, + SND_EVO_TYPE_NCTYPES +}; + +static const struct snd_evo_ctrl_type { + snd_ctl_elem_type_t type; + bool invert; + union { + int integer; + } min; + union { + int integer; + } max; + u16 wValue; + u16 wIndex; +} snd_evo_ctypes[SND_EVO_TYPE_NCTYPES] = { + [SND_EVO_TYPE_MONITOR] = { + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .min.integer = 0, + .max.integer = 127, + .wValue = 0x0000, + .wIndex = 0x3800, + }, + [SND_EVO_TYPE_PHANTOM] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = false, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0000, + .wIndex = 0x3A00, + }, + [SND_EVO_TYPE_MUTE] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = true, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0200, + .wIndex = 0x3A00, + }, + [SND_EVO_TYPE_MUTE_OUT] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = true, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0100, + .wIndex = 0x3B00, + }, + [SND_EVO_TYPE_MIXER] = { + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .min.integer = -128 * 256, + .max.integer = 6 * 256, + .wValue = 0x0100, + .wIndex = 0x3C00, + }, +}; + +#define SND_EVO_FLAGS_DIRECTION (1L<<0) +#define SND_EVO_FLAGS_CAPTURE (1L<<0) +#define SND_EVO_FLAGS_PLAYBACK (0L<<0) + +struct snd_evo_ctrl { + char *basename; + enum snd_evo_type type; + unsigned int channels; + unsigned int hwchannels; + unsigned int val_offs; + unsigned long flags; +}; + +static const struct snd_evo_ctrl evo4_controls[] = { + { + .basename = "Master", + .type = SND_EVO_TYPE_MUTE_OUT, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Mic", + .type = SND_EVO_TYPE_MUTE, + .channels = 4, + .hwchannels = 2, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Monitor", + .type = SND_EVO_TYPE_MONITOR, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Mic 1", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 0, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 2", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 2, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Master Left", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 4, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Master Right", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 6, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Loopback Left", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 8, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Loopback Right", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 10, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 1 Phantom", + .type = SND_EVO_TYPE_PHANTOM, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 2 Phantom", + .type = SND_EVO_TYPE_PHANTOM, + .channels = 1, + .hwchannels = 1, + .val_offs = 1, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { 0 }, /* sentinel */ +}; + +static const struct evo_devinfo { + u32 usb_id; + const struct snd_evo_ctrl *controls; +} evo_devinfo[] = { + { + .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), + .controls = evo4_controls, + }, + { + .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), + .controls = NULL, + }, + { 0 } /* sentinel */ +}; + + +static inline unsigned long snd_evo_kctl_priv(enum snd_evo_type type, + u8 valoffs, u8 hwchannels) +{ + unsigned long type_l = type & 0xFF; + unsigned long valoffs_l = valoffs & 0xFF; + unsigned long hwchannels_l = hwchannels & 0xFF; + + return (hwchannels_l << 16) | (valoffs_l << 8) | type_l; +} + +static __always_inline +const struct snd_evo_ctrl_type *snd_evo_kctl_type(struct snd_kcontrol *kctl) +{ + return &snd_evo_ctypes[kctl->private_value & 0xFF]; +} + +static __always_inline u16 snd_evo_kctl_valoffs(struct snd_kcontrol *kctl) +{ + return (kctl->private_value >> 8) & 0xFF; +} + +static __always_inline u16 snd_evo_kctl_hwchannels(struct snd_kcontrol *kctl) +{ + return (kctl->private_value >> 16) & 0xFF; +} + +static inline u16 snd_evo_kctl_channels(struct snd_kcontrol *kctl) +{ + struct usb_mixer_elem_info *info = kctl->private_data; + + return info->channels; +} + + +#define EVO_REQ_CUR (0x01) +#define EVO_REQTYPE_SET (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) +#define EVO_REQTYPE_GET (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +static inline int snd_evo_get_cur(struct usb_mixer_interface *mixer, + u16 value, u16 index, + void *buf, size_t len) +{ + struct usb_device *const dev = mixer->chip->dev; + + return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + EVO_REQ_CUR, EVO_REQTYPE_GET, + value, index, buf, len); +} + +static inline int snd_evo_set_cur(struct usb_mixer_interface *mixer, + u16 value, u16 index, + void *buf, size_t len) +{ + struct usb_device *const dev = mixer->chip->dev; + + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + EVO_REQ_CUR, EVO_REQTYPE_SET, + value, index, buf, len); +} + + +static int snd_ctl_evo_boolean_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *const mixer = list->mixer; + + const unsigned int nchan = snd_evo_kctl_channels(kctl); + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int i; + + for (i = 0; i < hwchan; i++) { + u32 buf; + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + + ctl_val->value.integer.value[i] = le32_to_cpu(buf) ? 1 : 0; + + if (ctype->invert) + ctl_val->value.integer.value[i] ^= 0x1; + } + + for (; i < nchan; i++) + ctl_val->value.integer.value[i] = ctype->invert + ? ctype->max.integer + : ctype->min.integer; + + return 0; +} + +static int snd_ctl_evo_boolean_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int changed = 0; + int i; + + for (i = 0; i < hwchan; i++) { + u32 rbuf; + u32 buf = cpu_to_le32(ctl_val->value.integer.value[i] ? 1 : 0); + int ret; + + if (ctype->invert) + buf ^= 0x1; + + ret = snd_evo_get_cur(mixer, value + i, index, + &rbuf, sizeof(rbuf)); + if (ret < 0) + return ret; + + if (rbuf != buf) { + changed = 1; + + ret = snd_evo_set_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + } + } + + return changed; +} + +static int snd_ctl_evo_integer_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *const mixer = list->mixer; + + const unsigned int nchan = snd_evo_kctl_channels(kctl); + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int i; + + for (i = 0; i < hwchan; i++) { + u16 buf; + s16 val; + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + + val = le16_to_cpu(buf); + ctl_val->value.integer.value[i] = val; + } + + for (; i < nchan; i++) + ctl_val->value.integer.value[i] = ctype->min.integer; + + return 0; +} + +static int snd_ctl_evo_integer_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int changed = 0; + int i; + + for (i = 0; i < hwchan; i++) { + s16 rbuf; + s16 buf = cpu_to_le16(ctl_val->value.integer.value[i]); + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &rbuf, sizeof(rbuf)); + if (ret < 0) + return ret; + + if (rbuf != buf) { + changed = 1; + + ret = snd_evo_set_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + } + } + + return changed; +} + +static int snd_ctl_evo_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + + uinfo->type = ctype->type; + uinfo->access = SNDRV_CTL_ELEM_ACCESS_READWRITE + | SNDRV_CTL_ELEM_ACCESS_VOLATILE; + uinfo->count = snd_evo_kctl_channels(kctl); + uinfo->value.integer.min = ctype->min.integer; + uinfo->value.integer.max = ctype->max.integer; + return 0; +} + + +static int snd_evo_add_ctrl(struct usb_mixer_interface *mixer, + const struct snd_evo_ctrl *ctrl) +{ + const struct snd_evo_ctrl_type *ctype = &snd_evo_ctypes[ctrl->type]; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct snd_kcontrol_new knew = { 0 }; + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.name = name; + knew.info = snd_ctl_evo_info; + knew.private_value = snd_evo_kctl_priv(ctrl->type, ctrl->val_offs, + ctrl->hwchannels); + + switch (ctype->type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER: + knew.get = snd_ctl_evo_integer_get; + knew.put = snd_ctl_evo_integer_set; + break; + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + knew.get = snd_ctl_evo_boolean_get; + knew.put = snd_ctl_evo_boolean_set; + break; + default: + return -EINVAL; + } + + snprintf(name, sizeof(name), "%s %s %s", ctrl->basename, + ((ctrl->flags & SND_EVO_FLAGS_DIRECTION) + == SND_EVO_FLAGS_CAPTURE) ? "Capture" : "Playback", + (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN) + ? "Switch" : "Volume"); + + elem = kzalloc_obj(*elem); + if (!elem) + return -ENOMEM; + + elem->head.mixer = mixer; + elem->channels = ctrl->channels; + + kctl = snd_ctl_new1(&knew, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + + kctl->private_free = snd_usb_mixer_elem_free; + + return snd_usb_mixer_add_control(&elem->head, kctl); +} + + +/* called from mixer_quirks.c */ +int snd_evo_controls_create(struct usb_mixer_interface *mixer) +{ + const struct evo_devinfo *info; + const struct snd_evo_ctrl *ctrl; + + for (info = evo_devinfo; info->controls; info++) + if (info->usb_id == mixer->chip->usb_id) + break; + + if (!info->controls) + return -ENODEV; + + for (ctrl = info->controls; ctrl->basename; ctrl++) { + const int ret = snd_evo_add_ctrl(mixer, ctrl); + + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/sound/usb/mixer_evo.h b/sound/usb/mixer_evo.h new file mode 100644 index 000000000000..7fe7e197185c --- /dev/null +++ b/sound/usb/mixer_evo.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_MIXER_EVO_H +#define __USB_MIXER_EVO_H + +#include "mixer.h" + +#define USB_AUDIENT_VID (0x2708) +#define USB_EVO4_PID (0x0006) + +int snd_evo_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_EVO_H */ diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index f8e1f78039e1..98b4146216ae 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -505,6 +505,14 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { {} }; +/* Audient EVO4: Rename FU 10 from 'EVO4 ' to 'Master' according to the + * ALSA naming convention + */ +static const struct usbmix_name_map audient_evo4_map[] = { + { 10, "Master Playback" }, + {} +}; + /* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is * traced through to the Speaker output terminal, so it is named "Speaker * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI @@ -632,6 +640,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x2708, 0x0002), .map = audient_id14_map, }, + { + /* Audient EVO4 MkI */ + .id = USB_ID(0x2708, 0x0006), + .map = audient_evo4_map, + }, { /* Audient iD14 MkII */ .id = USB_ID(0x2708, 0x0008), diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index fc622eb95dc5..01210cc3fae7 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -38,6 +38,7 @@ #include "mixer_scarlett2.h" #include "mixer_us16x08.h" #include "mixer_s1810c.h" +#include "mixer_evo.h" #include "helper.h" #include "fcp.h" @@ -166,7 +167,7 @@ static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer, } kctl->private_free = snd_usb_mixer_elem_free; /* don't use snd_usb_mixer_add_control() here, this is a special list element */ - return snd_usb_mixer_add_list(list, kctl, false); + return snd_usb_mixer_add_list(list, kctl, false, true); } /* @@ -4539,6 +4540,10 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_fcp_init(mixer); break; + case USB_ID(USB_AUDIENT_VID, USB_EVO4_PID): /* Audient EVO 4 */ + err = snd_evo_controls_create(mixer); + break; + case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ err = snd_soundblaster_e1_switch_create(mixer); break; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index 369968565c19..6808730ef047 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -292,7 +292,7 @@ static int forte_get_ctl_value(struct usb_mixer_elem_info *elem, int *value) /* Device may not support reading input controls. * Return cached value or default to avoid blocking module load. */ - if (elem->cached) + if (test_bit(0, elem->cached)) *value = elem->cache_val[0]; else *value = 0; /* Default: first option */ @@ -353,7 +353,7 @@ static int forte_input_gain_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -364,7 +364,7 @@ static int forte_input_gain_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -405,7 +405,7 @@ static int forte_ctl_enum_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -416,7 +416,7 @@ static int forte_ctl_enum_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -454,7 +454,7 @@ static int forte_ctl_switch_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -465,7 +465,7 @@ static int forte_ctl_switch_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -532,7 +532,7 @@ static int scarlett_ctl_resume(struct usb_mixer_elem_list *list) int i; for (i = 0; i < elem->channels; i++) - if (elem->cached & (1 << i)) + if (test_bit(i, elem->cached)) snd_usb_set_cur_mix_value(elem, i, i, elem->cache_val[i]); return 0; @@ -692,7 +692,7 @@ static int scarlett_ctl_enum_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) snd_usb_set_cur_mix_value(elem, 0, 0, *elem->cache_val); return 0; } diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index 14fb1ad764a7..18e8c2bd2678 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -189,6 +189,7 @@ static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* route has no bias */ ucontrol->value.enumerated.item[0] = elem->cache_val[index]; @@ -204,6 +205,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, char buf[sizeof(route_msg)]; int val, val_org, err; + guard(mutex)(&elem->head.mixer->lock); /* get the new value (no bias for routes) */ val = ucontrol->value.enumerated.item[0]; @@ -236,7 +238,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -258,6 +260,7 @@ static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = elem->cache_val[index]; return 0; @@ -272,6 +275,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, int val, err; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* new control value incl. bias*/ val = ucontrol->value.integer.value[0]; @@ -295,7 +299,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -310,6 +314,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, val = ucontrol->value.integer.value[0]; + guard(mutex)(&elem->head.mixer->lock); /* prepare the message buffer from template */ switch (elem->head.id) { case SND_US16X08_ID_BYPASS: @@ -336,7 +341,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -346,6 +351,7 @@ static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol, { struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); + guard(mutex)(&elem->head.mixer->lock); switch (elem->head.id) { case SND_US16X08_ID_BUSS_OUT: ucontrol->value.integer.value[0] = elem->cache_val[0]; @@ -368,6 +374,7 @@ static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = elem->cache_val[index]; return 0; @@ -382,6 +389,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, int val, err; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -404,7 +412,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -428,6 +436,7 @@ static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol, int index = ucontrol->id.index; int val_idx = COMP_STORE_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = store->val[val_idx][index]; return 0; @@ -445,6 +454,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, int threshold, ratio, attack, release, gain, switch_on; int err; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -508,7 +518,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, } store->val[val_idx][index] = val; - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -521,6 +531,7 @@ static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol, struct snd_us16x08_eq_store *store = elem->private_data; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* get low switch from cache is enough, cause all bands are together */ val = store->val[EQ_STORE_BAND_IDX(elem->head.id)] [EQ_STORE_PARAM_IDX(elem->head.id)][index]; @@ -540,6 +551,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, int val, err = 0; int b_idx; + guard(mutex)(&elem->head.mixer->lock); /* new control value incl. bias*/ val = ucontrol->value.integer.value[0] + SND_US16X08_KCBIAS(kcontrol); @@ -567,7 +579,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -582,6 +594,7 @@ static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol, int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); val = store->val[b_idx][p_idx][index]; ucontrol->value.integer.value[0] = val; @@ -601,6 +614,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -631,7 +645,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, store->val[b_idx][p_idx][index] = val; /* store new value in EQ band cache */ - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -729,6 +743,7 @@ static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol, struct snd_us16x08_meter_store *store = elem->private_data; u8 meter_urb[64] = {0}; + guard(mutex)(&elem->head.mixer->lock); switch (kcontrol->private_value) { case 0: { char tmp[sizeof(mix_init_msg1)]; @@ -788,6 +803,7 @@ static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol, struct snd_us16x08_meter_store *store = elem->private_data; int val; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -1118,7 +1134,8 @@ static int add_new_ctl(struct usb_mixer_interface *mixer, strscpy(kctl->id.name, name, sizeof(kctl->id.name)); - err = snd_usb_mixer_add_control(&elem->head, kctl); + /* look_for_empty=false, as we want the fixed id.index as channel */ + err = snd_usb_mixer_add_list(&elem->head, kctl, true, false); if (err < 0) return err; @@ -1352,7 +1369,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) } for (i = 0; i < 8; i++) elem->cache_val[i] = i < 2 ? i : i + 2; - elem->cached = 0xff; + bitmap_set(elem->cached, 0, 8); /* create compressor mixer elements */ comp_store = snd_us16x08_create_comp_store(); @@ -1374,7 +1391,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) if (err < 0) return err; elem->cache_val[0] = master_controls[i].default_val; - elem->cached = 1; + set_bit(0, elem->cached); } /* add channel controls */ @@ -1394,7 +1411,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) elem->cache_val[j] = channel_controls[i].default_val; } - elem->cached = 0xffff; + bitmap_set(elem->cached, 0, SND_US16X08_MAX_CHANNELS); } /* create eq store */ diff --git a/sound/usb/mixer_us16x08.h b/sound/usb/mixer_us16x08.h index 7b8583dd3b07..e98fe55265d1 100644 --- a/sound/usb/mixer_us16x08.h +++ b/sound/usb/mixer_us16x08.h @@ -60,7 +60,7 @@ #define SND_US16X08_ID_EQHIGHMIDLEVEL 0x03 #define SND_US16X08_ID_EQHIGHLEVEL 0x04 -/* EQ frequence IDs */ +/* EQ frequency IDs */ #define SND_US16X08_ID_EQLOWFREQ 0x11 #define SND_US16X08_ID_EQLOWMIDFREQ 0x12 #define SND_US16X08_ID_EQHIGHMIDFREQ 0x13 diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index dd02c6294edf..6343a50fb273 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -1227,15 +1227,15 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - /* only 44.1 kHz works at the moment */ + /* S24_3LE; altsettings 1-4 match the device clock. */ USB_DEVICE(0x0582, 0x0120), QUIRK_DRIVER_INFO { /* .vendor_name = "Roland", */ - /* .product_name = "OCTO-CAPTURE", */ + /* .product_name = "OCTA-CAPTURE", */ QUIRK_DATA_COMPOSITE { { QUIRK_DATA_AUDIOFORMAT(0) { - .formats = SNDRV_PCM_FMTBIT_S32_LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 10, .iface = 0, .altsetting = 1, @@ -1249,9 +1249,57 @@ YAMAHA_DEVICE(0x7010, "UB99"), .rate_table = (unsigned int[]) { 44100 } } }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 0, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, { QUIRK_DATA_AUDIOFORMAT(1) { - .formats = SNDRV_PCM_FMTBIT_S32_LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 12, .iface = 1, .altsetting = 1, @@ -1265,10 +1313,58 @@ YAMAHA_DEVICE(0x7010, "UB99"), .rate_table = (unsigned int[]) { 44100 } } }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 1, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 1, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 1, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, { QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { - .out_cables = 0x0001, - .in_cables = 0x0001 + .out_cables = 0x0005, + .in_cables = 0x0003 } }, { QUIRK_DATA_IGNORE(3) }, @@ -1278,7 +1374,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - /* only 44.1 kHz works at the moment */ + /* S32_LE; altsettings 1-4 match the device clock. */ USB_DEVICE(0x0582, 0x012f), QUIRK_DRIVER_INFO { /* .vendor_name = "Roland", */ @@ -1300,6 +1396,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), .rate_table = (unsigned int[]) { 44100 } } }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 4, + .iface = 0, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 4, + .iface = 0, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .iface = 0, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, { QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S32_LE, @@ -1316,6 +1460,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), .rate_table = (unsigned int[]) { 44100 } } }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 6, + .iface = 1, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 6, + .iface = 1, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .iface = 1, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, { QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, @@ -2719,7 +2911,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .iface = 2, .altsetting = 1, .altset_idx = 1, - .attributes = 0, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, .endpoint = 0x01, .ep_attr = USB_ENDPOINT_XFER_ISOC, .rates = SNDRV_PCM_RATE_44100 | @@ -2740,7 +2932,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), .iface = 3, .altsetting = 1, .altset_idx = 1, - .attributes = 0, + .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE, .endpoint = 0x82, .ep_attr = USB_ENDPOINT_XFER_ISOC, .rates = SNDRV_PCM_RATE_44100 | @@ -3539,6 +3731,53 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + /* + * Pioneer DJ DDJ-SZ + * 10 channels playback & 10 channels capture @ 44.1kHz S24LE + */ + USB_DEVICE_VENDOR_SPEC(0x08e4, 0x0191), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, { /* * Pioneer DJ DJM-750MK2 diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index a54fabce7920..43eea724779a 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -3,6 +3,7 @@ */ #include +#include #include #include #include @@ -870,6 +871,49 @@ static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) return 0; } +/* + * The Xiaomi audio connector ignores UAC2 volume changes after reconnecting + * until the device and configuration descriptors have been read again. + */ +#define XIAOMI_AUDIO_MAX_CONFIG_SIZE 1024 + +static void snd_usb_xiaomi_boot_quirk(struct usb_device *dev) +{ + u8 *buf __free(kfree) = kmalloc(XIAOMI_AUDIO_MAX_CONFIG_SIZE, GFP_KERNEL); + struct usb_config_descriptor *config; + unsigned int length; + int ret; + + if (!buf) + return; + + ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, buf, + USB_DT_DEVICE_SIZE); + if (ret != USB_DT_DEVICE_SIZE) { + dev_warn(&dev->dev, "device descriptor re-read failed: %d\n", ret); + return; + } + + ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf, + USB_DT_CONFIG_SIZE); + if (ret != USB_DT_CONFIG_SIZE) { + dev_warn(&dev->dev, "configuration header re-read failed: %d\n", ret); + return; + } + + config = (struct usb_config_descriptor *)buf; + length = le16_to_cpu(config->wTotalLength); + if (length < USB_DT_CONFIG_SIZE || + length > XIAOMI_AUDIO_MAX_CONFIG_SIZE) { + dev_warn(&dev->dev, "unexpected configuration length: %u\n", length); + return; + } + + ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf, length); + if (ret != (int)length) + dev_warn(&dev->dev, "configuration descriptor re-read failed: %d\n", ret); +} + /* * Some sound cards from Native Instruments are in fact compliant to the USB * audio standard of version 2 and other approved USB standards, even though @@ -1606,22 +1650,60 @@ static int s1810c_skip_setting_quirk(struct snd_usb_audio *chip, return 0; } +static int hp_elite_x3_lap_dock_skip_setting_quirk(struct snd_usb_audio *chip, + int iface, int altno) +{ + /* + * 1:1 - Capture, S16_LE, 8000/32000/44100/48000Hz + * 2:1 - Playback, S16_LE, 48000Hz + * 2:2 - Playback, S24_3LE, 48000Hz + * + * 2:2 is broken because: + * - it doesn't accept SET_CUR(SAMPLE_RATE). QUIRK_FLAG_FIXED_RATE works + * around it, however... + * - it constantly produces severe harmonic distortion once the capture + * stream is also opened. The interface 2 must be closed and reopened + * to make it recover. IOW, simply closing the capture stream makes no + * difference. + * + * Considering that S24_3LE offers no additional benefit on small + * speakers compared to S16_LE, and 2:1 is always usable as an + * alternative, skip 2:2 to get rid of the trouble. + * + * Setting chip->setup to any non-default value disables the fixup and + * reenables 2:2 (in this case QUIRK_FLAG_FIXED_RATE is required). + */ + if (!chip->setup && iface == 2 && altno == 2) { + usb_audio_info(chip, + "%d:%d: skipping broken altsetting on HP Elite x3 Lap Dock\n", + iface, altno); + return 1; + } + + return 0; +} + int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, int iface, int altno) { - /* audiophile usb: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x0763, 0x2003)) - return audiophile_skip_setting_quirk(chip, iface, altno); + switch (chip->usb_id) { + /* HP Elite x3 Lap Dock: skip broken altsets */ + case USB_ID(0x03f0, 0x0c56): + return hp_elite_x3_lap_dock_skip_setting_quirk(chip, iface, altno); /* quattro usb: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x0763, 0x2001)) + case USB_ID(0x0763, 0x2001): return quattro_skip_setting_quirk(chip, iface, altno); + /* audiophile usb: skip altsets incompatible with device_setup */ + case USB_ID(0x0763, 0x2003): + return audiophile_skip_setting_quirk(chip, iface, altno); /* fasttrackpro usb: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x0763, 0x2012)) + case USB_ID(0x0763, 0x2012): return fasttrackpro_skip_setting_quirk(chip, iface, altno); /* presonus studio 1810c: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x194f, 0x010c)) + case USB_ID(0x194f, 0x010c): return s1810c_skip_setting_quirk(chip, iface, altno); + } return 0; } @@ -1702,11 +1784,21 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, switch (id) { case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ return snd_usb_motu_m_series_boot_quirk(dev); + case USB_ID(0x2717, 0xd005): /* Xiaomi audio connector */ + snd_usb_xiaomi_boot_quirk(dev); + return 0; } /* Behringer devices may need explicit device descriptor read at boot */ - if (USB_ID_VENDOR(id) == 0x1397) + if (USB_ID_VENDOR(id) == 0x1397) { + /* the quirk seems breaking on some Behringer devices */ + switch (id) { + case USB_ID(0x1397, 0x0508): /* UMC204HD */ + case USB_ID(0x1397, 0x0509): /* UMC204HD */ + return 0; + } return snd_usb_cm1a_boot_quirk(dev); + } return 0; } @@ -1791,6 +1883,62 @@ static void set_format_emu_quirk(struct snd_usb_substream *subs, subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0; } +/* + * The DDJ-SZ needs a vendor "arm" sequence before its capture path + * produces real audio; without it capture runs with no USB or ALSA error + * but delivers a hard zero on every channel. The sequence is replicated + * byte-for-byte from a USB capture of the Windows driver: each write is + * followed by a status read whose content is a fixed value regardless of + * what was written, but the read is replicated too, since it is unclear + * whether the device requires it to process the preceding write. + * + * This runs from snd_usb_set_format_quirk(), i.e. on every format setup + * rather than once per device. Re-arming is harmless in practice and + * keeps the device armed if it is reset behind our back. + */ +static void ddj_sz_arm_quirk(struct usb_device *dev) +{ + static const struct { + u16 value; + u16 index; + u8 read_len; + } cmds[] = { + { 0x0100, 0x8002, 6 }, + { 0x0200, 0x8002, 6 }, + { 0x0303, 0x8002, 6 }, + { 0x0403, 0x8002, 6 }, + { 0x050a, 0x8002, 6 }, + { 0x0000, 0x8003, 2 }, + }; + u8 buf[6]; + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(cmds); i++) { + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 3, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + cmds[i].value, cmds[i].index, NULL, 0); + if (err < 0) + goto err_out; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + 0x0000, cmds[i].index, buf, + cmds[i].read_len); + if (err < 0) + goto err_out; + } + + return; + +err_out: + dev_warn(&dev->dev, + "DDJ-SZ: arm sequence step %u failed (%d), capture may be silent\n", + i, err); +} + static int pioneer_djm_set_format_quirk(struct snd_usb_substream *subs, u16 windex) { @@ -1880,6 +2028,75 @@ static int rme_digiface_set_format_quirk(struct snd_usb_substream *subs) return 0; } +#define ROLAND_CAPTURE_RATE_REQUEST 3 +#define ROLAND_CAPTURE_RATE_READ_VALUE 0x0001 +#define ROLAND_CAPTURE_RATE_WRITE_VALUE 0x0008 +#define ROLAND_CAPTURE_RATE_WRITE_PREFIX 0x40 +#define ROLAND_CAPTURE_RATE_RETRIES 40 +#define ROLAND_CAPTURE_RATE_POLL_MS 25 + +/* + * OCTA-CAPTURE and QUAD-CAPTURE use the same vendor request for their + * hardware clock. A read returns the 24-bit little-endian rate followed by + * a transition-status byte. A write carries 0x40 followed by the rate. + */ +static int roland_capture_read_rate(struct usb_device *dev, u32 *rate) +{ + u8 data[4]; + int err; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + ROLAND_CAPTURE_RATE_REQUEST, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + ROLAND_CAPTURE_RATE_READ_VALUE, 0, + data, sizeof(data)); + if (err != sizeof(data)) + return err < 0 ? err : -EIO; + + *rate = combine_triple(data); + return 0; +} + +static void roland_capture_set_rate(struct snd_usb_substream *subs) +{ + struct snd_usb_audio *chip = subs->stream->chip; + struct usb_device *dev = chip->dev; + u32 rate = subs->data_endpoint->cur_rate; + u32 current_rate; + u8 data[4]; + int err; + int i; + + /* Serialize playback and capture endpoint starts during a clock change. */ + guard(mutex)(&chip->mutex); + err = roland_capture_read_rate(dev, ¤t_rate); + if (!err && current_rate == rate) + return; + + data[0] = ROLAND_CAPTURE_RATE_WRITE_PREFIX; + data[1] = rate; + data[2] = rate >> 8; + data[3] = rate >> 16; + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + ROLAND_CAPTURE_RATE_REQUEST, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + ROLAND_CAPTURE_RATE_WRITE_VALUE, 0, + data, sizeof(data)); + if (err != sizeof(data)) { + usb_audio_warn(chip, "cannot set Roland sample rate to %u Hz: %d\n", + rate, err < 0 ? err : -EIO); + return; + } + + for (i = 0; i < ROLAND_CAPTURE_RATE_RETRIES; i++) { + err = roland_capture_read_rate(dev, ¤t_rate); + if (!err && current_rate == rate) + return; + msleep(ROLAND_CAPTURE_RATE_POLL_MS); + } + usb_audio_warn(chip, "Roland sample rate did not reach %u Hz\n", rate); +} + void snd_usb_set_format_quirk(struct snd_usb_substream *subs, const struct audioformat *fmt) { @@ -1903,6 +2120,10 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs, case USB_ID(0x08e4, 0x0163): /* Pioneer DJM-850 */ pioneer_djm_set_format_quirk(subs, 0x0086); break; + case USB_ID(0x08e4, 0x0191): /* Pioneer DDJ-SZ */ + ddj_sz_arm_quirk(subs->dev); + pioneer_djm_set_format_quirk(subs, 0x0082); + break; case USB_ID(0x0dba, 0x5000): mbox3_set_format_quirk(subs, fmt); /* Digidesign Mbox 3 */ break; @@ -1910,6 +2131,10 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs, case USB_ID(0x2a39, 0x3fa0): /* RME Digiface USB (alternate) */ rme_digiface_set_format_quirk(subs); break; + case USB_ID(0x0582, 0x0120): /* Roland OCTA-CAPTURE */ + case USB_ID(0x0582, 0x012f): /* Roland QUAD-CAPTURE */ + roland_capture_set_rate(subs); + break; } } @@ -1980,6 +2205,16 @@ void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep) ep->chip->usb_id == USB_ID(0x1852, 0x5034)) && /* T+A Dac8 */ ep->syncmaxsize == 4) ep->tenor_fb_quirk = 1; + + /* + * Mayflower ARC AMP DAC (MAG Technology USB front-end): the + * feedback can get stuck at nominal+1 sample per frame, causing + * pops every 1-2 sec until stream restart. Snap implausible + * feedback to nominal. + */ + if (ep->chip->usb_id == USB_ID(0x0493, 0x4170) && + ep->type == SND_USB_ENDPOINT_TYPE_SYNC) + ep->snap_fb_quirk = 1; } /* quirk applied after snd_usb_ctl_msg(); not applied during boot quirks */ @@ -2114,6 +2349,21 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, int stream) { switch (chip->usb_id) { + case USB_ID(0x0499, 0x5500): /* Yamaha 01V96i */ + /* + * The 01V96i declares its audio streaming interfaces as + * vendor-specific (class 0xff) and provides no class-specific + * endpoint descriptor. parse_uac_endpoint_attributes() therefore + * returns no attributes, and the driver never issues the SET_CUR + * sampling-frequency request the hardware needs to engage its USB + * stream: playback and capture PCMs report RUNNING, but the desk + * passes no audio. + * + * Force the attribute on so the stream is initialised the same way + * the Windows driver does it. + */ + fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE; + break; case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */ /* Optoplay sets the sample rate attribute although * it seems not supporting it in fact. @@ -2148,6 +2398,14 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, */ fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX; break; + case USB_ID(0x0a12, 0x1244): /* Shanling UP4 */ + /* + * MaxPacketsOnly is set, but the device expects nominal-size + * packets. Filling 48 frames per packet at 44.1 kHz results + * in silence. No effect at 48 kHz, where both sizes match. + */ + fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX; + break; case USB_ID(0x1224, 0x2a25): /* Jieli Technology USB PHY 2.0 */ /* mic works only when ep packet size is set to wMaxPacketSize */ fp->attributes |= UAC_EP_CS_ATTR_FILL_MAX; @@ -2238,6 +2496,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x0124, 0x0c21, /* Generic USB Headphone */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x03f0, 0x0c56, /* HP Elite x3 Lap Dock */ + QUIRK_FLAG_FIXED_RATE), DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ @@ -2251,6 +2511,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DISABLE_AUTOSUSPEND), DEVICE_FLG(0x045e, 0x070f, /* MS LifeChat LX-3000 Headset */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x046d, 0x0802, /* Logitech Webcam C200 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0807, /* Logitech Webcam C500 */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0808, /* Logitech Webcam C600 */ @@ -2271,6 +2533,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x046d, 0x08ca, /* Logitech Quickcam Fusion */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x08e5, /* Logitech HD Pro Webcam c920 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x046d, 0x094c, /* Logitech Brio 100 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0991, /* Logitech QuickCam Pro */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_IGNORE_CTL_ERROR | QUIRK_FLAG_MIC_RES_384), @@ -2323,6 +2589,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x0644, 0x8044, /* Esoteric D-05X */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY | QUIRK_FLAG_FORCE_IFACE_RESET), + DEVICE_FLG(0x0644, 0x8047, /* TASCAM US-16x08 */ + QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY | + QUIRK_FLAG_FORCE_IFACE_RESET), DEVICE_FLG(0x0644, 0x804a, /* TEAC UD-301 */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY), @@ -2404,6 +2673,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1237, 0x17b8, /* rongyuan GS20 */ + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE | QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), @@ -2460,6 +2731,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY), DEVICE_FLG(0x1901, 0x0191, /* GE B850V3 CP2114 audio interface */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x194f, 0x0301, /* PreSonus AudioBox USB */ + QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x19f7, 0x0003, /* RODE NT-USB */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x19f7, 0x0035, /* RODE NT-USB+ */ @@ -2556,8 +2829,12 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x3443, 0x930d, /* NexiGo N930W 60fps Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x35d8, 0x011d, /* MOONDROP DAWN PRO2 */ + QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x36f9, 0xc009, /* XIBERIA K03S */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x3703, 0x2000, /* NUX NAI-24 */ + QUIRK_FLAG_SWAP_RATES), DEVICE_FLG(0x3c20, 0x3d21, /* AB13X USB Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x413c, 0xa506, /* Dell AE515 sound bar */ @@ -2685,6 +2962,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + QUIRK_STRING_ENTRY(SWAP_RATES), NULL }; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 24436473931d..c6d59218d35f 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -258,6 +258,10 @@ extern bool snd_usb_skip_validation; * Issue SET_CUR for the sample rate even when the clock already reports the * requested rate. A device advertising a single rate is otherwise never sent * the request at all, and some require it before streaming will start. + * QUIRK_FLAG_SWAP_RATES + * The device applies a requested sample rate to the other base-rate family + * (44100 <-> 48000, 88200 <-> 96000, 176400 <-> 192000). Send the partner + * rate in SET_CUR so that the device runs at the requested rate. */ enum { @@ -294,6 +298,7 @@ enum { QUIRK_TYPE_MIXER_GET_CUR_OK = 30, QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, QUIRK_TYPE_ALWAYS_SET_RATE = 32, + QUIRK_TYPE_SWAP_RATES = 33, /* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ }; @@ -332,5 +337,6 @@ enum { #define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) +#define QUIRK_FLAG_SWAP_RATES QUIRK_FLAG(SWAP_RATES) #endif /* __USBAUDIO_H */ diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index 368dafa5df3f..8837dfc12828 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -118,6 +118,8 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss, void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss); +void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss); + int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, unsigned long offset, unsigned long bytes); diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c index a5c4e7027717..72103f606716 100644 --- a/sound/virtio/virtio_pcm_msg.c +++ b/sound/virtio/virtio_pcm_msg.c @@ -182,6 +182,25 @@ void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss) vss->nmsgs = 0; } +/** + * virtsnd_pcm_msg_reset() - Reset the fill level of all I/O messages. + * @vss: VirtIO PCM substream. + * + * Messages that were only partially filled by the application when the + * substream was stopped are never sent to the device, so their length must be + * cleared before the substream is started again. + * + * Context: Any context. Takes and releases the VirtIO substream spinlock. + */ +void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss) +{ + unsigned int i; + + guard(spinlock_irqsave)(&vss->lock); + for (i = 0; vss->msgs && i < vss->nmsgs; ++i) + vss->msgs[i]->length = 0; +} + /** * virtsnd_pcm_msg_send() - Send asynchronous I/O messages. * @vss: VirtIO PCM substream. diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c index 1105e7ff3523..1091100a4c98 100644 --- a/sound/virtio/virtio_pcm_ops.c +++ b/sound/virtio/virtio_pcm_ops.c @@ -299,6 +299,7 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) vss->xfer_xrun = false; vss->suspended = false; vss->msg_count = 0; + virtsnd_pcm_msg_reset(vss); memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect)); vss->pcm_indirect.sw_buffer_size = diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c index 91dfcf47e979..542fc35ec9b7 100644 --- a/sound/x86/intel_hdmi_audio.c +++ b/sound/x86/intel_hdmi_audio.c @@ -475,7 +475,8 @@ static void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata) kfree(intelhaddata->chmap->chmap); intelhaddata->chmap->chmap = NULL; - chmap = kzalloc_obj(*chmap); + /* info->chmap must be zero-terminated; allocate a spare terminator entry */ + chmap = kzalloc_objs(*chmap, 2); if (!chmap) return; @@ -575,6 +576,12 @@ static int had_register_chmap_ctls(struct snd_intelhad *intelhaddata, intelhaddata->chmap->kctl->info = had_chmap_ctl_info; intelhaddata->chmap->kctl->get = had_chmap_ctl_get; intelhaddata->chmap->chmap = NULL; + + /* drop the inherited TLV read: it walks chmap without our mutex */ + intelhaddata->chmap->kctl->tlv.c = NULL; + intelhaddata->chmap->kctl->vd[0].access &= + ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + return 0; } @@ -1634,9 +1641,9 @@ static void hdmi_lpe_audio_free(struct snd_card *card) * This function is called when the i915 driver creates the * hdmi-lpe-audio platform device. */ -static int __hdmi_lpe_audio_probe(struct platform_device *pdev) +static int hdmi_lpe_audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_intelhad_card *card_ctx; struct snd_intelhad *ctx; struct snd_pcm *pcm; @@ -1797,14 +1804,10 @@ static int __hdmi_lpe_audio_probe(struct platform_device *pdev) schedule_work(&ctx->hdmi_audio_wq); } + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int hdmi_lpe_audio_probe(struct platform_device *pdev) -{ - return snd_card_free_on_error(&pdev->dev, __hdmi_lpe_audio_probe(pdev)); -} - static const struct dev_pm_ops hdmi_lpe_audio_pm = { SYSTEM_SLEEP_PM_OPS(hdmi_lpe_audio_suspend, hdmi_lpe_audio_resume) }; diff --git a/tools/testing/selftests/alsa/.gitignore b/tools/testing/selftests/alsa/.gitignore index 3dd8e1176b89..7b0e1e9ebf1b 100644 --- a/tools/testing/selftests/alsa/.gitignore +++ b/tools/testing/selftests/alsa/.gitignore @@ -1,3 +1,4 @@ +aloop-test global-timer mixer-test pcm-test diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index 8dab90ad22bb..afd64a679dc1 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -16,7 +16,7 @@ LDLIBS+=-lpthread OVERRIDE_TARGETS = 1 -TEST_GEN_PROGS := mixer-test pcm-test test-pcmtest-driver utimer-test +TEST_GEN_PROGS := aloop-test mixer-test pcm-test test-pcmtest-driver utimer-test TEST_GEN_PROGS_EXTENDED := libatest.so global-timer diff --git a/tools/testing/selftests/alsa/aloop-test.c b/tools/testing/selftests/alsa/aloop-test.c new file mode 100644 index 000000000000..74d6ccc12724 --- /dev/null +++ b/tools/testing/selftests/alsa/aloop-test.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Tests for the hw constraints between the two ends of an snd-aloop cable, + * with and without the "PCM Notify" control, and for the "PCM Slave" + * controls that report the playback side's parameters. + * + * Needs snd-aloop loaded with the default card id "Loopback". The tests use + * the cable between hw:Loopback,0,0 (playback) and hw:Loopback,1,0 (capture). + */ +#include +#include +#include +#include +#include +#include "kselftest_harness.h" + +#define FRAMES 1024 +#define MAX_CHANNELS 4 + +struct stream_params { + snd_pcm_access_t access; + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; +}; + +/* The playback params differ from the capture params in every field. */ +static const struct stream_params capture_params = { + SND_PCM_ACCESS_RW_INTERLEAVED, SND_PCM_FORMAT_S16_LE, 2, 44100 +}; + +static const struct stream_params playback_params = { + SND_PCM_ACCESS_RW_NONINTERLEAVED, SND_PCM_FORMAT_S32_LE, 4, 96000 +}; + +struct probe_result { + unsigned int rate_min, rate_max; + unsigned int channels_min, channels_max; + bool format_ok; +}; + +/* Value events seen on the cable's controls */ +enum { + EV_ACTIVE = 1 << 0, + EV_FORMAT = 1 << 1, + EV_RATE = 1 << 2, + EV_CHANNELS = 1 << 3, + EV_ACCESS = 1 << 4, +}; + +FIXTURE(aloop) { + char play_name[32]; + char capt_name[32]; + snd_ctl_t *ctl; + bool saved_notify; + bool notify_saved; +}; + +/* The cable's controls are on the capture side device. */ +static void cable_ctl_id(snd_ctl_elem_value_t *value, const char *name) +{ + snd_ctl_elem_value_set_interface(value, SND_CTL_ELEM_IFACE_PCM); + snd_ctl_elem_value_set_name(value, name); + snd_ctl_elem_value_set_device(value, 1); + snd_ctl_elem_value_set_subdevice(value, 0); +} + +static long cable_ctl_get(snd_ctl_t *ctl, const char *name) +{ + snd_ctl_elem_value_t *value; + + snd_ctl_elem_value_alloca(&value); + cable_ctl_id(value, name); + if (snd_ctl_elem_read(ctl, value) < 0) + return -1; + if (!strcmp(name, "PCM Slave Access Mode")) + return snd_ctl_elem_value_get_enumerated(value, 0); + return snd_ctl_elem_value_get_integer(value, 0); +} + +static int set_notify(snd_ctl_t *ctl, bool on) +{ + snd_ctl_elem_value_t *value; + + snd_ctl_elem_value_alloca(&value); + cable_ctl_id(value, "PCM Notify"); + snd_ctl_elem_value_set_boolean(value, 0, on); + return snd_ctl_elem_write(ctl, value); +} + +/* Read all pending control events and return the EV_* bits for the cable's controls. */ +static unsigned int read_events(snd_ctl_t *ctl) +{ + static const struct { + const char *name; + unsigned int bit; + } names[] = { + { "PCM Slave Active", EV_ACTIVE }, + { "PCM Slave Format", EV_FORMAT }, + { "PCM Slave Rate", EV_RATE }, + { "PCM Slave Channels", EV_CHANNELS }, + { "PCM Slave Access Mode", EV_ACCESS }, + }; + snd_ctl_event_t *event; + unsigned int seen = 0; + int i; + + snd_ctl_event_alloca(&event); + while (snd_ctl_read(ctl, event) > 0) { + if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM || + !(snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE) || + snd_ctl_event_elem_get_device(event) != 1 || + snd_ctl_event_elem_get_subdevice(event) != 0) + continue; + for (i = 0; i < ARRAY_SIZE(names); i++) + if (!strcmp(snd_ctl_event_elem_get_name(event), names[i].name)) + seen |= names[i].bit; + } + return seen; +} + +/* Open and configure a stream. snd_pcm_hw_params() also prepares it. */ +static int open_pcm(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, + const struct stream_params *p) +{ + unsigned int buffer_time = 100000; + snd_pcm_hw_params_t *hw; + int err; + + snd_pcm_hw_params_alloca(&hw); + err = snd_pcm_open(pcm, name, stream, 0); + if (err < 0) + return err; + err = snd_pcm_hw_params_any(*pcm, hw); + if (err >= 0) + err = snd_pcm_hw_params_set_access(*pcm, hw, p->access); + if (err >= 0) + err = snd_pcm_hw_params_set_format(*pcm, hw, p->format); + if (err >= 0) + err = snd_pcm_hw_params_set_channels(*pcm, hw, p->channels); + if (err >= 0) + err = snd_pcm_hw_params_set_rate(*pcm, hw, p->rate, 0); + if (err >= 0) + err = snd_pcm_hw_params_set_buffer_time_near(*pcm, hw, &buffer_time, NULL); + if (err >= 0) + err = snd_pcm_hw_params(*pcm, hw); + if (err < 0) { + snd_pcm_close(*pcm); + *pcm = NULL; + } + return err; +} + +/* What a client probing the device sees, and whether it may use the given format */ +static int probe_pcm(const char *name, snd_pcm_stream_t stream, snd_pcm_format_t format, + struct probe_result *res) +{ + snd_pcm_hw_params_t *hw; + snd_pcm_t *pcm; + int err; + + snd_pcm_hw_params_alloca(&hw); + err = snd_pcm_open(&pcm, name, stream, 0); + if (err < 0) + return err; + err = snd_pcm_hw_params_any(pcm, hw); + if (err >= 0) + err = snd_pcm_hw_params_get_rate_min(hw, &res->rate_min, NULL); + if (err >= 0) + err = snd_pcm_hw_params_get_rate_max(hw, &res->rate_max, NULL); + if (err >= 0) + err = snd_pcm_hw_params_get_channels_min(hw, &res->channels_min); + if (err >= 0) + err = snd_pcm_hw_params_get_channels_max(hw, &res->channels_max); + if (err >= 0) + res->format_ok = !snd_pcm_hw_params_test_format(pcm, hw, format); + snd_pcm_close(pcm); + return err; +} + +static int start_playback(snd_pcm_t *pcm, const struct stream_params *p) +{ + static char silence[FRAMES * MAX_CHANNELS * 4]; + void *bufs[MAX_CHANNELS]; + snd_pcm_sframes_t written; + unsigned int i; + + if (p->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + for (i = 0; i < p->channels; i++) + bufs[i] = silence + i * FRAMES * 4; + written = snd_pcm_writen(pcm, bufs, FRAMES); + } else { + written = snd_pcm_writei(pcm, silence, FRAMES); + } + if (written < 0) + return written; + if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED) + return snd_pcm_start(pcm); + return 0; +} + +FIXTURE_SETUP(aloop) { + char ctl_name[32]; + snd_pcm_t *pcm; + int card, err; + + card = snd_card_get_index("Loopback"); + if (card < 0) + SKIP(return, "No Loopback card, snd-aloop is probably not loaded"); + + sprintf(ctl_name, "hw:%d", card); + sprintf(self->play_name, "hw:%d,0,0", card); + sprintf(self->capt_name, "hw:%d,1,0", card); + + err = snd_pcm_open(&pcm, self->capt_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + if (err == -EBUSY) + SKIP(return, "%s is in use", self->capt_name); + ASSERT_EQ(err, 0); + snd_pcm_close(pcm); + err = snd_pcm_open(&pcm, self->play_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (err == -EBUSY) + SKIP(return, "%s is in use", self->play_name); + ASSERT_EQ(err, 0); + snd_pcm_close(pcm); + + ASSERT_EQ(snd_ctl_open(&self->ctl, ctl_name, SND_CTL_NONBLOCK), 0); + ASSERT_EQ(snd_ctl_subscribe_events(self->ctl, 1), 0); + self->saved_notify = cable_ctl_get(self->ctl, "PCM Notify"); + self->notify_saved = true; +} + +FIXTURE_TEARDOWN(aloop) { + if (self->notify_saved) + set_notify(self->ctl, self->saved_notify); + if (self->ctl) + snd_ctl_close(self->ctl); +} + +/* Without notify, a playback opened while a capture is set up is pinned to its parameters. */ +TEST_F(aloop, playback_constrained_without_notify) { + struct probe_result res; + snd_pcm_t *capt; + + ASSERT_EQ(set_notify(self->ctl, false), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + + ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format, + &res), 0); + EXPECT_EQ(res.rate_min, capture_params.rate); + EXPECT_EQ(res.rate_max, capture_params.rate); + EXPECT_EQ(res.channels_min, capture_params.channels); + EXPECT_EQ(res.channels_max, capture_params.channels); + EXPECT_FALSE(res.format_ok); + + snd_pcm_close(capt); +} + +/* With notify, the playback side is free to pick other parameters. */ +TEST_F(aloop, playback_unconstrained_with_notify) { + struct probe_result res; + snd_pcm_t *capt; + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + + ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format, + &res), 0); + EXPECT_LE(res.rate_min, capture_params.rate); + EXPECT_GE(res.rate_max, playback_params.rate); + EXPECT_LE(res.channels_min, capture_params.channels); + EXPECT_GE(res.channels_max, playback_params.channels); + EXPECT_TRUE(res.format_ok); + + snd_pcm_close(capt); +} + +/* + * With notify, starting a playback with different parameters stops the + * running capture, and the "PCM Slave" controls report the new parameters + * with a value event for each one that changed. + */ +TEST_F(aloop, params_change_stops_capture_with_notify) { + snd_pcm_t *capt, *play; + unsigned int events; + + /* + * The controls keep the last playback's parameters, and only notify on + * a change. Start a playback with the capture's parameters while no + * capture is open, so that every control changes below. + */ + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &capture_params), 0); + ASSERT_EQ(start_playback(play, &capture_params), 0); + snd_pcm_close(play); + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + ASSERT_EQ(snd_pcm_start(capt), 0); + ASSERT_EQ(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 0); + read_events(self->ctl); + + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0) + TH_LOG("Playback refused other parameters while the capture is running"); + ASSERT_EQ(start_playback(play, &playback_params), 0); + + /* loopback_check_format() stops the capture from the playback's start trigger. */ + EXPECT_NE(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 1); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Format"), playback_params.format); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Rate"), playback_params.rate); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Channels"), playback_params.channels); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Access Mode"), 1); + + events = read_events(self->ctl); + EXPECT_TRUE(events & EV_ACTIVE); + EXPECT_TRUE(events & EV_FORMAT); + EXPECT_TRUE(events & EV_RATE); + EXPECT_TRUE(events & EV_CHANNELS); + EXPECT_TRUE(events & EV_ACCESS); + + snd_pcm_close(play); + snd_pcm_close(capt); +} + +/* + * A playback that joins a running capture with the same parameters doesn't + * stop it, and the "PCM Slave" controls still report the playback's + * parameters rather than those of an earlier stream. + */ +TEST_F(aloop, matching_playback_updates_controls) { + snd_pcm_t *capt, *play; + unsigned int events; + + /* Leave the controls at other parameters than the ones used below. */ + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0); + ASSERT_EQ(start_playback(play, &playback_params), 0); + snd_pcm_close(play); + + ASSERT_EQ(set_notify(self->ctl, false), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + ASSERT_EQ(snd_pcm_start(capt), 0); + read_events(self->ctl); + + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &capture_params), 0); + ASSERT_EQ(start_playback(play, &capture_params), 0); + + EXPECT_EQ(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 1); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Format"), capture_params.format); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Rate"), capture_params.rate); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Channels"), capture_params.channels); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Access Mode"), 0); + + events = read_events(self->ctl); + EXPECT_TRUE(events & EV_FORMAT); + EXPECT_TRUE(events & EV_RATE); + EXPECT_TRUE(events & EV_CHANNELS); + EXPECT_TRUE(events & EV_ACCESS); + + snd_pcm_close(play); + snd_pcm_close(capt); +} + +/* With notify, a capture opened second is still pinned to the playback's parameters. */ +TEST_F(aloop, capture_constrained_with_notify) { + struct probe_result res; + snd_pcm_t *play; + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0); + + ASSERT_EQ(probe_pcm(self->capt_name, SND_PCM_STREAM_CAPTURE, capture_params.format, + &res), 0); + EXPECT_EQ(res.rate_min, playback_params.rate); + EXPECT_EQ(res.rate_max, playback_params.rate); + EXPECT_EQ(res.channels_min, playback_params.channels); + EXPECT_EQ(res.channels_max, playback_params.channels); + EXPECT_FALSE(res.format_ok); + + snd_pcm_close(play); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index 0857d64c322a..966ef83354b9 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -28,7 +28,13 @@ #include "kselftest.h" #include "alsa-local.h" -#define TESTS_PER_CONTROL 7 +#define TESTS_PER_CONTROL 8 + +/* Suffixes of the SNDRV_CTL_NAME_IEC958() names, not exported to userspace */ +#define IEC958_DEFAULT "Default" +#define IEC958_CON_MASK "Con Mask" +#define IEC958_PRO_MASK "Pro Mask" +#define IEC958_MASK "Mask" struct card_data { snd_ctl_t *handle; @@ -46,9 +52,14 @@ struct ctl_data { snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *def_val; + snd_ctl_elem_value_t *snapshot; + bool snapshot_valid; + bool moved; int elem; int event_missing; int event_spurious; + int side_effects; + unsigned int ev_mask; struct card_data *card; struct ctl_data *next; }; @@ -153,6 +164,10 @@ static void find_controls(void) if (err < 0) ksft_exit_fail_msg("Out of memory\n"); + err = snd_ctl_elem_value_malloc(&ctl_data->snapshot); + if (err < 0) + ksft_exit_fail_msg("Out of memory\n"); + snd_ctl_elem_list_get_id(card_data->ctls, ctl, ctl_data->id); snd_ctl_elem_info_set_id(ctl_data->info, ctl_data->id); @@ -201,6 +216,20 @@ static void find_controls(void) snd_config_delete(config); } +/* The control on the same card that an event's numid refers to */ +static struct ctl_data *find_ctl_by_numid(struct card_data *card, + unsigned int numid) +{ + struct ctl_data *ctl; + + for (ctl = ctl_list; ctl != NULL; ctl = ctl->next) + if (ctl->card == card && + snd_ctl_elem_info_get_numid(ctl->info) == numid) + return ctl; + + return NULL; +} + /* * Block for up to timeout ms for an event, returns a negative value * on error, 0 for no event and 1 for an event. @@ -259,8 +288,21 @@ static int wait_for_event(struct ctl_data *ctl, int timeout) mask = snd_ctl_event_elem_get_mask(event); ev_id = snd_ctl_event_elem_get_numid(event); if (ev_id != snd_ctl_elem_info_get_numid(ctl->info)) { + struct ctl_data *other = find_ctl_by_numid(ctl->card, + ev_id); + + /* + * Remember that the driver announced this one. + * test_ctl_write_side_effects() uses that to tell a + * deliberate link from a silent register collision. + */ + if (other) + other->ev_mask |= mask; + ksft_print_msg("Event for unexpected ctl %s\n", snd_ctl_event_elem_get_name(event)); + /* The loop condition must not see the other control's mask */ + mask = 0; continue; } @@ -842,6 +884,251 @@ static bool test_ctl_write_valid_enumerated(struct ctl_data *ctl) return !fail; } +/* + * Find the read only mask control for an IEC958 value control. The two + * share device, subdevice and index but not always the interface. + */ +static struct ctl_data *find_iec958_mask_ctl(struct ctl_data *ctl, + const char *suffix) +{ + char name[64]; + int stem; + struct ctl_data *mask; + + stem = strlen(ctl->name) - strlen(IEC958_DEFAULT); + if (snprintf(name, sizeof(name), "%.*s%s", stem, ctl->name, suffix) >= + (int)sizeof(name)) + return NULL; + + for (mask = ctl_list; mask != NULL; mask = mask->next) { + if (mask->card != ctl->card) + continue; + if (snd_ctl_elem_info_get_type(mask->info) != + SND_CTL_ELEM_TYPE_IEC958) + continue; + if (snd_ctl_elem_info_is_inactive(mask->info)) + continue; + if (!snd_ctl_elem_info_is_readable(mask->info)) + continue; + if (snd_ctl_elem_id_get_device(mask->id) != + snd_ctl_elem_id_get_device(ctl->id)) + continue; + if (snd_ctl_elem_id_get_subdevice(mask->id) != + snd_ctl_elem_id_get_subdevice(ctl->id)) + continue; + if (snd_ctl_elem_id_get_index(mask->id) != + snd_ctl_elem_id_get_index(ctl->id)) + continue; + if (strcmp(mask->name, name) == 0) + return mask; + } + + return NULL; +} + +/* + * Read the bits the device says it implements. Bit 0 of the first status + * byte picks which mask applies, some devices publish only a plain Mask. + */ +static bool read_iec958_mask(struct ctl_data *ctl, + const snd_aes_iec958_t *cur, + snd_aes_iec958_t *mask) +{ + int err; + struct ctl_data *mask_ctl; + snd_ctl_elem_value_t *val; + + if (!strend(ctl->name, IEC958_DEFAULT)) + return false; + + if (cur->status[0] & IEC958_AES0_PROFESSIONAL) + mask_ctl = find_iec958_mask_ctl(ctl, IEC958_PRO_MASK); + else + mask_ctl = find_iec958_mask_ctl(ctl, IEC958_CON_MASK); + if (!mask_ctl) + mask_ctl = find_iec958_mask_ctl(ctl, IEC958_MASK); + if (!mask_ctl) + return false; + + snd_ctl_elem_value_alloca(&val); + snd_ctl_elem_value_set_id(val, mask_ctl->id); + + err = snd_ctl_elem_read(mask_ctl->card->handle, val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + mask_ctl->name, snd_strerror(err)); + return false; + } + + snd_ctl_elem_value_get_iec958(val, mask); + + return true; +} + +/* + * Throw away the events from a write we are not checking, one left behind + * would make a missing notification look like a notification we got. + */ +static int drop_events(struct ctl_data *ctl) +{ + int err; + + do { + err = wait_for_event(ctl, 0); + } while (err > 0); + + return err; +} + +/* + * Toggle every bit the device advertises, one at a time. Each one starts + * from the value we read since a driver can look at the rest of the block + * when it stores a bit, and bit 0 of the first status byte selects the + * layout the others are read in so leave that one alone. + */ +static bool test_ctl_write_valid_iec958_mask(struct ctl_data *ctl, + snd_ctl_elem_value_t *orig_val, + const snd_aes_iec958_t *mask) +{ + int err, j; + unsigned int byte; + unsigned char bit; + bool fail = false, found = false; + snd_aes_iec958_t iec958; + snd_ctl_elem_value_t *val; + snd_ctl_elem_value_alloca(&val); + + snd_ctl_elem_value_copy(val, orig_val); + + for (byte = 0; byte < sizeof(mask->status); byte++) { + for (j = 0; j < 8; j++) { + bit = 1 << j; + + if (byte == 0 && bit == IEC958_AES0_PROFESSIONAL) + continue; + if (!(mask->status[byte] & bit)) + continue; + found = true; + + snd_ctl_elem_value_get_iec958(orig_val, &iec958); + iec958.status[byte] ^= bit; + snd_ctl_elem_value_set_iec958(val, &iec958); + + err = write_and_verify(ctl, val, NULL); + if (err != 0) { + ksft_print_msg("%s failed to set advertised status[%u] 0x%02x\n", + ctl->name, byte, bit); + fail = true; + } + } + } + + if (!found) + ksft_print_msg("%s implements no settable status bits\n", + ctl->name); + + return !fail; +} + +/* + * With nothing advertised all we can do is try non audio, and the device + * need not implement even that. Write it once and ignore the result, then + * compare against what came back so the value cannot fail, which leaves the + * notification counted by event_missing. + */ +static bool test_ctl_write_valid_iec958_guess(struct ctl_data *ctl, + snd_ctl_elem_value_t *orig_val) +{ + int err; + snd_aes_iec958_t iec958; + snd_ctl_elem_value_t *val, *read_val, *w_val; + snd_ctl_elem_value_alloca(&val); + snd_ctl_elem_value_alloca(&read_val); + snd_ctl_elem_value_alloca(&w_val); + + snd_ctl_elem_value_get_iec958(orig_val, &iec958); + iec958.status[0] ^= IEC958_AES0_NONAUDIO; + snd_ctl_elem_value_copy(val, orig_val); + snd_ctl_elem_value_set_iec958(val, &iec958); + + /* Writing can modify the value so keep a copy to write from */ + snd_ctl_elem_value_copy(w_val, val); + err = snd_ctl_elem_write(ctl->card->handle, w_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed: %s\n", + snd_strerror(err)); + return false; + } + + snd_ctl_elem_value_set_id(read_val, ctl->id); + err = snd_ctl_elem_read(ctl->card->handle, read_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed: %s\n", + snd_strerror(err)); + return false; + } + + /* Put it back where we found it, then drop the events from both */ + snd_ctl_elem_value_copy(w_val, orig_val); + err = snd_ctl_elem_write(ctl->card->handle, w_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed: %s\n", + snd_strerror(err)); + return false; + } + + err = drop_events(ctl); + if (err < 0) { + ksft_print_msg("drop_events() failed for %s: %d\n", + ctl->name, err); + return false; + } + + if (snd_ctl_elem_value_compare(val, read_val)) { + ksft_print_msg("%s does not implement status[0] 0x%02x\n", + ctl->name, IEC958_AES0_NONAUDIO); + + /* Expect what came back, the notification is still checked */ + return write_and_verify(ctl, val, read_val) == 0; + } + + return write_and_verify(ctl, val, NULL) == 0; +} + +/* + * Write the channel status bits and check that userspace is told about it. + * Where a mask was found every bit in it has to stick, otherwise we fall + * back to one guessed bit the device is free to ignore. + */ +static bool test_ctl_write_valid_iec958(struct ctl_data *ctl) +{ + int err; + snd_aes_iec958_t iec958, mask; + snd_ctl_elem_value_t *orig_val; + snd_ctl_elem_value_alloca(&orig_val); + + /* + * The bytes past the ones a driver implements are compared too, + * so start from a read rather than building a value here. + */ + snd_ctl_elem_value_set_id(orig_val, ctl->id); + err = snd_ctl_elem_read(ctl->card->handle, orig_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed: %s\n", + snd_strerror(err)); + return false; + } + + snd_ctl_elem_value_get_iec958(orig_val, &iec958); + + if (!read_iec958_mask(ctl, &iec958, &mask)) { + ksft_print_msg("%s falling back to non audio\n", ctl->name); + return test_ctl_write_valid_iec958_guess(ctl, orig_val); + } + + return test_ctl_write_valid_iec958_mask(ctl, orig_val, &mask); +} + static void test_ctl_write_valid(struct ctl_data *ctl) { bool pass; @@ -878,6 +1165,10 @@ static void test_ctl_write_valid(struct ctl_data *ctl) pass = test_ctl_write_valid_enumerated(ctl); break; + case SND_CTL_ELEM_TYPE_IEC958: + pass = test_ctl_write_valid_iec958(ctl); + break; + default: /* No tests for this yet */ ksft_test_result_skip("write_valid.%s.%d\n", @@ -892,6 +1183,202 @@ static void test_ctl_write_valid(struct ctl_data *ctl) ctl->card->card_name, ctl->elem); } +/* + * Build the smallest or the largest value the control offers. The smallest + * clears the control's register field and the largest sets its top bit, which + * is the bit a mask one bit too wide puts in its neighbour. + */ +static bool set_limit_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val, + bool max) +{ + int i, count = snd_ctl_elem_info_get_count(ctl->info); + + snd_ctl_elem_value_set_id(val, ctl->id); + + switch (snd_ctl_elem_info_get_type(ctl->info)) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_boolean(val, i, max); + return true; + + case SND_CTL_ELEM_TYPE_INTEGER: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer(val, i, max ? + snd_ctl_elem_info_get_max(ctl->info) : + snd_ctl_elem_info_get_min(ctl->info)); + return true; + + case SND_CTL_ELEM_TYPE_INTEGER64: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer64(val, i, max ? + snd_ctl_elem_info_get_max64(ctl->info) : + snd_ctl_elem_info_get_min64(ctl->info)); + return true; + + case SND_CTL_ELEM_TYPE_ENUMERATED: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_enumerated(val, i, max ? + snd_ctl_elem_info_get_items(ctl->info) - 1 : 0); + return true; + + default: + /* Nothing sensible to write for the rest */ + return false; + } +} + +/* Note every control on the card that no longer reads as it did */ +static void find_moved_ctls(struct ctl_data *ctl, snd_ctl_elem_value_t *val) +{ + struct ctl_data *other; + int err; + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->snapshot_valid) + continue; + + /* + * The buffer is shared and compare() looks at all of it, so + * clear what the last control left in the slots this one + * does not use. + */ + snd_ctl_elem_value_clear(val); + snd_ctl_elem_value_set_id(val, other->id); + err = snd_ctl_elem_read(ctl->card->handle, val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + other->name, snd_strerror(err)); + continue; + } + + if (snd_ctl_elem_value_compare(other->snapshot, val)) + other->moved = true; + } +} + +/* + * Write one control and look for others on the same card that moved with it. + * A driver that links two controls on purpose tells userspace about both, so + * only an unannounced change is counted. That is what a control whose + * register mask covers bits belonging to its neighbour looks like from here. + */ +static void test_ctl_write_side_effects(struct ctl_data *ctl) +{ + struct ctl_data *other; + snd_ctl_elem_value_t *min_val, *max_val, *read_val; + int err; + + snd_ctl_elem_value_alloca(&min_val); + snd_ctl_elem_value_alloca(&max_val); + snd_ctl_elem_value_alloca(&read_val); + + /* Without a readable default there is nothing to put back */ + if (snd_ctl_elem_info_is_inactive(ctl->info) || + !snd_ctl_elem_info_is_writable(ctl->info) || + !snd_ctl_elem_info_is_readable(ctl->info) || + !set_limit_value(ctl, min_val, false) || + !set_limit_value(ctl, max_val, true)) { + ksft_test_result_skip("write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); + return; + } + + /* Drain first, a stale event would look like the driver announced it */ + drop_events(ctl); + + /* + * Record what the rest of the card reads as. A volatile control can + * move on its own so there is nothing to compare it against. + */ + for (other = ctl_list; other != NULL; other = other->next) { + other->snapshot_valid = false; + other->moved = false; + other->ev_mask = 0; + + if (other == ctl || other->card != ctl->card) + continue; + if (!snd_ctl_elem_info_is_readable(other->info) || + snd_ctl_elem_info_is_volatile(other->info)) + continue; + + snd_ctl_elem_value_clear(other->snapshot); + snd_ctl_elem_value_set_id(other->snapshot, other->id); + err = snd_ctl_elem_read(ctl->card->handle, other->snapshot); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + other->name, snd_strerror(err)); + continue; + } + + other->snapshot_valid = true; + } + + /* + * Compare against the snapshot after each write, before anything is + * put back. Restoring the control we wrote goes through the same + * mask, so doing it first would hide the change we are looking for. + */ + err = snd_ctl_elem_write(ctl->card->handle, min_val); + if (err >= 0) { + drop_events(ctl); + find_moved_ctls(ctl, read_val); + err = snd_ctl_elem_write(ctl->card->handle, max_val); + } + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed for %s: %s\n", + ctl->name, snd_strerror(err)); + } else { + drop_events(ctl); + find_moved_ctls(ctl, read_val); + } + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->moved) + continue; + + if (other->ev_mask & SND_CTL_EVENT_MASK_VALUE) { + ksft_print_msg("Writing %s changed %s, the driver said so\n", + ctl->name, other->name); + } else { + ksft_print_msg("Writing %s silently changed %s\n", + ctl->name, other->name); + ctl->side_effects++; + } + } + + /* + * The control we wrote goes back first so its mask stops moving the + * rest. A plain write keeps this out of the event counters, they + * belong to the tests that check them. + */ + snd_ctl_elem_write(ctl->card->handle, ctl->def_val); + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->snapshot_valid || + !snd_ctl_elem_info_is_writable(other->info)) + continue; + + snd_ctl_elem_value_clear(read_val); + snd_ctl_elem_value_set_id(read_val, other->id); + if (snd_ctl_elem_read(ctl->card->handle, read_val) < 0) + continue; + + if (snd_ctl_elem_value_compare(other->snapshot, read_val)) + snd_ctl_elem_write(ctl->card->handle, other->snapshot); + } + + /* Our own restores queue events, the next test must not see them */ + drop_events(ctl); + + if (err < 0) + ksft_test_result_skip("write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); + else + ksft_test_result(!ctl->side_effects, + "write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); +} + static bool test_ctl_write_invalid_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val) { @@ -1135,6 +1622,7 @@ int main(void) test_ctl_name(ctl); test_ctl_write_default(ctl); test_ctl_write_valid(ctl); + test_ctl_write_side_effects(ctl); test_ctl_write_invalid(ctl); test_ctl_event_missing(ctl); test_ctl_event_spurious(ctl); diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c index 95065ef3b441..18ee72d5d4c9 100644 --- a/tools/testing/selftests/alsa/test-pcmtest-driver.c +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -10,9 +10,11 @@ #include "kselftest_harness.h" #define CH_NUM 4 +/* The driver accepts patterns of up to 4096 bytes */ +#define MAX_PATTERN_LEN 4096 struct pattern_buf { - char buf[1024]; + char buf[MAX_PATTERN_LEN]; int len; }; @@ -166,7 +168,7 @@ FIXTURE_SETUP(pcmtest) { TEST_F(pcmtest, playback) { snd_pcm_t *handle; unsigned char *it; - size_t write_res; + snd_pcm_sframes_t write_res; int test_results; int i, cur_ch, pos_in_ch; void *samples; @@ -190,7 +192,7 @@ TEST_F(pcmtest, playback) { it[i] = patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]; } write_res = snd_pcm_writei(handle, samples, params->rate * params->time); - ASSERT_GE(write_res, 0); + ASSERT_EQ(write_res, params->rate * params->time); snd_pcm_close(handle); free(samples); @@ -206,7 +208,7 @@ TEST_F(pcmtest, playback) { TEST_F(pcmtest, capture) { snd_pcm_t *handle; unsigned char *it; - size_t read_res; + snd_pcm_sframes_t read_res; int i, cur_ch, pos_in_ch; void *samples; struct pcmtest_test_params *params = &self->params; @@ -222,7 +224,7 @@ TEST_F(pcmtest, capture) { snd_pcm_format_set_silence(params->format, samples, params->rate * params->channels * params->time); read_res = snd_pcm_readi(handle, samples, params->rate * params->time); - ASSERT_GE(read_res, 0); + 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++) { @@ -239,7 +241,8 @@ TEST_F(pcmtest, ni_capture) { snd_pcm_t *handle; struct pcmtest_test_params params = self->params; char **chan_samples; - size_t i, j, read_res; + snd_pcm_sframes_t read_res; + size_t i, j; chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); ASSERT_NE(chan_samples, NULL); @@ -257,12 +260,12 @@ TEST_F(pcmtest, ni_capture) { for (i = 0; i < 1; i++) { read_res = snd_pcm_readn(handle, (void **)chan_samples, params.rate * params.time); - ASSERT_GE(read_res, 0); + ASSERT_EQ(read_res, params.rate * params.time); } snd_pcm_close(handle); for (i = 0; i < CH_NUM; i++) { - for (j = 0; j < params.rate * params.time; j++) + for (j = 0; j < params.rate * params.time * params.sample_size; j++) ASSERT_EQ(chan_samples[i][j], patterns[i].buf[j % patterns[i].len]); free(chan_samples[i]); } @@ -273,7 +276,8 @@ TEST_F(pcmtest, ni_playback) { snd_pcm_t *handle; struct pcmtest_test_params params = self->params; char **chan_samples; - size_t i, j, read_res; + snd_pcm_sframes_t write_res; + size_t i, j; int test_res; chan_samples = calloc(CH_NUM, sizeof(*chan_samples)); @@ -294,8 +298,9 @@ TEST_F(pcmtest, ni_playback) { } for (i = 0; i < 1; i++) { - read_res = snd_pcm_writen(handle, (void **)chan_samples, params.rate * params.time); - ASSERT_GE(read_res, 0); + write_res = snd_pcm_writen(handle, (void **)chan_samples, + params.rate * params.time); + ASSERT_EQ(write_res, params.rate * params.time); } snd_pcm_close(handle); diff --git a/tools/testing/selftests/alsa/utimer-test.c b/tools/testing/selftests/alsa/utimer-test.c index 1a9ff010cb11..07aeaa777649 100644 --- a/tools/testing/selftests/alsa/utimer-test.c +++ b/tools/testing/selftests/alsa/utimer-test.c @@ -51,6 +51,9 @@ FIXTURE_SETUP(timer_f) { self->utimer_info->resolution = (NANO / FRAME_RATE * PERIOD_SIZE); timer_dev_fd = open("/dev/snd/timer", O_RDONLY); + if (timer_dev_fd < 0 && + (errno == ENOENT || errno == ENODEV || errno == ENXIO)) + SKIP(return, "ALSA timer device unavailable"); ASSERT_GE(timer_dev_fd, 0); if (ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info) < 0) { @@ -156,6 +159,9 @@ TEST(wrong_timers_test) { }; timer_dev_fd = open("/dev/snd/timer", O_RDONLY); + if (timer_dev_fd < 0 && + (errno == ENOENT || errno == ENODEV || errno == ENXIO)) + SKIP(return, "ALSA timer device unavailable"); ASSERT_GE(timer_dev_fd, 0); utimer_fd = ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, &wrong_timer);