diff options
| author | Mark Brown <broonie@kernel.org> | 2026-09-29 19:14:26 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-29 19:14:26 +0100 |
| commit | 0fbfaeecfd6f5925bfcd7cfcbd62860a9e868619 (patch) | |
| tree | 1a7726ceef53338e457951374e9d1d559080856a | |
| parent | f9aac7cea1ffe9f4a942a773a9be0d8c065f99f4 (diff) | |
| parent | 995b96380ba5d98131acc162608da3538e1395d8 (diff) | |
| download | linux-next-0fbfaeecfd6f5925bfcd7cfcbd62860a9e868619.tar.gz linux-next-0fbfaeecfd6f5925bfcd7cfcbd62860a9e868619.zip | |
Merge asoc/for-7.4 into asoc-next
1119 files changed, 31343 insertions, 6885 deletions
@@ -355,6 +355,8 @@ Hans Verkuil <hverkuil@kernel.org> <hverkuil-cisco@xs4all.nl> Hans Verkuil <hverkuil@kernel.org> <hansverk@cisco.com> Hans Verkuil <hverkuil@kernel.org> <hans.verkuil@cisco.com> Hao Ge <hao.ge@linux.dev> <gehao@kylinos.cn> +Haowen Bai <baihaowen88@gmail.com> <baihaowen@meizu.com> +Haowen Bai <baihaowen88@gmail.com> <calvin.bai@ugreen.com> Harry Yoo <harry.yoo@oracle.com> <42.hyeyoo@gmail.com> Harry Yoo <harry@kernel.org> <harry.yoo@oracle.com> Heiko Carstens <hca@linux.ibm.com> <h.carstens@de.ibm.com> @@ -975,6 +977,7 @@ Vladimir Davydov <vdavydov.dev@gmail.com> <vdavydov@virtuozzo.com> Vlastimil Babka <vbabka@kernel.org> <vbabka@suse.cz> WangYuli <wangyuli@aosc.io> <wangyl5933@chinaunicom.cn> WangYuli <wangyuli@aosc.io> <wangyuli@deepin.org> +Wei Wang <weiwan@google.com> <weibunny@fb.com> Weiwen Hu <huweiwen@linux.alibaba.com> <sehuww@mail.scut.edu.cn> WeiXiong Liao <gmpy.liaowx@gmail.com> <liaoweixiong@allwinnertech.com> Wen Gong <quic_wgong@quicinc.com> <wgong@codeaurora.org> @@ -982,6 +985,8 @@ Wesley Cheng <quic_wcheng@quicinc.com> <wcheng@codeaurora.org> Will Deacon <will@kernel.org> <will.deacon@arm.com> Wolfram Sang <wsa@kernel.org> <w.sang@pengutronix.de> Wolfram Sang <wsa@kernel.org> <wsa@the-dreams.de> +Xu Xin <xu.xin@linux.dev> <xu.xin16@zte.com.cn> +Xu Xin <xu.xin@linux.dev> xu xin <cgel.zte@gmail.com> Yakir Yang <kuankuan.y@gmail.com> <ykk@rock-chips.com> Yanteng Si <si.yanteng@linux.dev> <siyanteng@loongson.cn> Ying Huang <huang.ying.caritas@gmail.com> <ying.huang@intel.com> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 33cd30996e47..e75344f4e0cd 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -589,6 +589,9 @@ Kernel parameters arm64.nosve [ARM64] Unconditionally disable Scalable Vector Extension support + arm64.nowfxt [ARM64] Unconditionally disable Wait For Event with + Timeout and Wait For Interrupt with Timeout support + ataflop= [HW,M68k] atarimouse= [HW,MOUSE] Atari Mouse @@ -3491,7 +3494,7 @@ Kernel parameters * SATA link speed limit: 1.5Gbps or 3.0Gbps. - * Transfer mode: pio[0-7], mwdma[0-4] and udma[0-7]. + * Transfer mode: pio[0-6], mwdma[0-4] and udma[0-7]. udma[/][16,25,33,44,66,100,133] notation is also allowed. @@ -3509,7 +3512,7 @@ Kernel parameters * [no]ncqtrim: Enable or disable queued DSM TRIM. - * [no]ncqati: Enable or disable NCQ trim on ATI chipset. + * [no]ncqati: Enable or disable NCQ on ATI chipsets. * [no]trim: Enable or disable (unqueued) TRIM. @@ -3540,11 +3543,9 @@ Kernel parameters * max_sec_128: Set transfer size limit to 128 sectors. - * max_sec_1024: Set or clear transfer size limit to - 1024 sectors. + * max_sec_1024: Set transfer size limit to 1024 sectors. - * max_sec_lba48: Set or clear transfer size limit to - 65535 sectors. + * max_sec_lba48: Set transfer size limit to 65535 sectors. * external: Mark port as external (hotplug-capable). diff --git a/Documentation/arch/arm64/booting.rst b/Documentation/arch/arm64/booting.rst index 13ef311dace8..3fea4b14ef7c 100644 --- a/Documentation/arch/arm64/booting.rst +++ b/Documentation/arch/arm64/booting.rst @@ -465,6 +465,7 @@ Before jumping into the kernel, the following conditions must be met: - HDFGWTR2_EL2.nPMICNTR_EL0 (bit 2) must be initialised to 0b1. - HDFGWTR2_EL2.nPMICFILTR_EL0 (bit 3) must be initialised to 0b1. - HDFGWTR2_EL2.nPMUACR_EL1 (bit 4) must be initialised to 0b1. + - HDFGWTR2_EL2.nPMZR_EL0 (bit 21) must be initialised to 0b1. For CPUs with SPE data source filtering (FEAT_SPE_FDS): diff --git a/Documentation/arch/s390/pci.rst b/Documentation/arch/s390/pci.rst index 80f4ba193159..565434626fb5 100644 --- a/Documentation/arch/s390/pci.rst +++ b/Documentation/arch/s390/pci.rst @@ -67,7 +67,7 @@ Entries specific to zPCI functions and entries that hold zPCI information. A physical function that currently supports a virtual function cannot be powered off until all virtual functions are removed with: - echo 0 > /sys/bus/pci/devices/DDDD:BB:dd.f/sriov_numvf + echo 0 > /sys/bus/pci/devices/DDDD:BB:dd.f/sriov_numvfs * /sys/bus/pci/devices/DDDD:BB:dd.f/: diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst index 85f73e0bbd0f..6691fe8a32c3 100644 --- a/Documentation/bpf/kfuncs.rst +++ b/Documentation/bpf/kfuncs.rst @@ -486,6 +486,16 @@ Example usage in BPF program: /* note that the last argument is omitted */ bpf_task_work_schedule_signal(task, &work->tw, &arrmap, task_work_callback); +2.5.10 KF_PERFMON flag +---------------------- + +The KF_PERFMON flag is used for kfuncs that can expose kernel memory or kernel +addresses to the BPF program, for example by reading through a pointer that the +verifier does not check. Calling such a kfunc requires CAP_PERFMON, or +CAP_SYS_ADMIN, in the same way that the equivalent BPF helpers are gated in +bpf_base_func_proto(). A program loaded with CAP_BPF alone is rejected at load +time. + 2.6 Registering the kfuncs -------------------------- diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml b/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml index 4bb511719f31..56758a85ead8 100644 --- a/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml +++ b/Documentation/devicetree/bindings/pinctrl/qcom,nord-tlmm.yaml @@ -67,7 +67,7 @@ $defs: Specify the alternative function to be configured for the specified pins. - enum: [ aoss_cti, atest_char, atest_usb20, atest_usb21, + enum: [ gpio, aoss_cti, atest_char, atest_usb20, atest_usb21, aud_intfc0_clk, aud_intfc0_data, aud_intfc0_ws, aud_intfc10_clk, aud_intfc10_data, aud_intfc10_ws, aud_intfc1_clk, aud_intfc1_data, aud_intfc1_ws, @@ -98,9 +98,10 @@ $defs: pcie3_clk_req_n, phase_flag, pll_bist_sync, pll_clk_aux, prng_rosc0, prng_rosc1, pwrbrk_i_n, qdss, qdss_cti, qspi, qup0_se0, qup0_se1, qup0_se2, qup0_se3, qup0_se4, qup0_se5, - qup1_se0, qup1_se1, qup1_se3, qup1_se2, qup1_se4, qup1_se5, + qup1_se0, qup1_se1, qup1_se2_01, qup1_se2_23, qup1_se3_01, + qup1_se3_23, qup1_se4, qup1_se5, qup1_se6, qup2_se0, qup2_se1, qup2_se2, qup2_se3, qup2_se4, - qup2_se5, qup2_se6, + qup2_se5, qup2_se6, qup3_se0_mira, qup3_se0_mirb, sailss_ospi, sdc4_clk, sdc4_cmd, sdc4_data, smb_alert, smb_alert_n, smb_clk, smb_dat, tb_trig_sdc4, tmess_prng0, tmess_prng1, tsc_timer, tsense_pwm, usb0_hs, 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 <ansuelsmth@gmail.com> + +properties: + compatible: + const: airoha,an7581-afe + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/interrupt-controller/irq.h> + + afe@1fbe2200 { + compatible = "airoha,an7581-afe"; + reg = <0x1fbe2200 0x9000>; + + interrupts = <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>; + }; + +... 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 <ansuelsmth@gmail.com> + +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 <dt-bindings/gpio/gpio.h> + 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..1ba809888a88 100644 --- a/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml +++ b/Documentation/devicetree/bindings/sound/cirrus,cs35l45.yaml @@ -53,6 +53,24 @@ 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 + patternProperties: "^cirrus,gpio-ctrl[1-3]$": description: @@ -135,25 +153,27 @@ 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,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 <dt-bindings/sound/cs42l42.h> i2c { - #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>; - - reset-gpios = <&axi_gpio_0 1 0>; - interrupt-parent = <&gpio0>; - interrupts = <55 8>; - - cirrus,ts-inv = <CS42L42_TS_INV_DIS>; - cirrus,ts-dbnc-rise = <CS42L42_TS_DBNCE_1000>; - cirrus,ts-dbnc-fall = <CS42L42_TS_DBNCE_0>; - cirrus,btn-det-init-dbnce = <100>; - cirrus,btn-det-event-dbnce = <10>; - cirrus,bias-lvls = <0x0F 0x08 0x04 0x01>; - cirrus,hs-bias-ramp-rate = <CS42L42_HSBIAS_RAMP_SLOW>; - }; + #address-cells = <1>; + #size-cells = <0>; + + 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>; + + cirrus,ts-inv = <CS42L42_TS_INV_DIS>; + cirrus,ts-dbnc-rise = <CS42L42_TS_DBNCE_1000>; + cirrus,ts-dbnc-fall = <CS42L42_TS_DBNCE_0>; + cirrus,btn-det-init-dbnce = <100>; + cirrus,btn-det-event-dbnce = <10>; + cirrus,bias-lvls = <0x0F 0x08 0x04 0x01>; + cirrus,hs-bias-ramp-rate = <CS42L42_HSBIAS_RAMP_SLOW>; + }; }; 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 <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> i2c { - #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>; - }; + #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>; + }; }; 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>; - - 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>; - }; + #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>; + }; }; spi { - #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>; - }; + #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>; + }; }; 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 <dt-bindings/interrupt-controller/irq.h> i2c { - #address-cells = <1>; - #size-cells = <0>; - - codec: da7219@1a { - compatible = "dlg,da7219"; - reg = <0x1a>; - - interrupt-parent = <&gpio6>; - interrupts = <11 IRQ_TYPE_LEVEL_LOW>; - - VDD-supply = <&vdd_reg>; - VDDMIC-supply = <&vddmic_reg>; - VDDIO-supply = <&vddio_reg>; - - #clock-cells = <1>; - clock-output-names = "da7219-dai-wclk", "da7219-dai-bclk"; - - clocks = <&clks 201>; - clock-names = "mclk"; - - 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>; - - 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>; - }; - }; + #address-cells = <1>; + #size-cells = <0>; + + codec: audio-codec@1a { + compatible = "dlg,da7219"; + reg = <0x1a>; + + interrupt-parent = <&gpio6>; + interrupts = <11 IRQ_TYPE_LEVEL_LOW>; + + VDD-supply = <&vdd_reg>; + VDDMIC-supply = <&vddmic_reg>; + VDDIO-supply = <&vddio_reg>; + + #clock-cells = <1>; + clock-output-names = "da7219-dai-wclk", "da7219-dai-bclk"; + + clocks = <&clks 201>; + clock-names = "mclk"; + + 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>; + + 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>; + }; + }; }; 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 <karl@asseily.com> + +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>; + + port@0 { + reg = <0>; + playback-only; + endpoint { + remote-endpoint = <&fe00_ep>; + }; + }; + + port@1 { + reg = <1>; + capture-only; + endpoint { + remote-endpoint = <&fe01_ep>; + }; + }; - asrc_endpoint: endpoint { - remote-endpoint = <&fe00_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 <YLCHANG2@nuvoton.com> + +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/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 = <PRIMARY_MI2S_RX>; - 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 = <PRIMARY_MI2S_RX>; + 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,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index 1536fcd96d68..fc7bccdace0f 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -54,6 +54,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 <srinivas.kandagatla@oss.qualcomm.com> + +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 <dt-bindings/gpio/gpio.h> + + 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 = <GIC_SPI 326 IRQ_TYPE_LEVEL_HIGH>, - <GIC_SPI 327 IRQ_TYPE_EDGE_RISING>, - <GIC_SPI 328 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "int_req", "dma_rx", "dma_tx"; - clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>, - <&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>, - <&audio_clk1>, - <&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 = <GIC_SPI 326 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 327 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 328 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "int_req", "dma_rx", "dma_tx"; + clocks = <&cpg CPG_MOD R9A07G044_SSI0_PCLK2>, + <&cpg CPG_MOD R9A07G044_SSI0_PCLK_SFR>, + <&audio_clk1>, + <&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 <linusw@kernel.org> + +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 <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/reset/stericsson,db8500-prcc-reset.h> + + msp@80123000 { + compatible = "stericsson,ux500-msp-i2s"; + reg = <0x80123000 0x1000>; + interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>; + 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 <egbostina@gmail.com> + +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,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. - - <name>-supply : Phandle to the regulator <name> 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 ba2002969373..ff4a85f4cbd4 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -559,6 +559,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/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index 3e3f03bd5c29..0562ba1f567a 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -851,6 +851,8 @@ operations: name: bind-tx doc: Bind dmabuf to netdev for TX attribute-set: dmabuf + # Intentionally unprivileged (no admin-perm / uns-admin-perm); see + # comment above netdev_nl_bind_tx_doit(). do: request: attributes: diff --git a/Documentation/trace/events-landlock.rst b/Documentation/trace/events-landlock.rst index af9267cca47d..304a8d06273e 100644 --- a/Documentation/trace/events-landlock.rst +++ b/Documentation/trace/events-landlock.rst @@ -6,7 +6,7 @@ Landlock Trace Events ===================== :Author: Mickaël Salaün -:Date: August 2026 +:Date: September 2026 Landlock emits trace events for sandbox lifecycle operations and access denials. These events can be consumed by ftrace (for human-readable @@ -31,8 +31,8 @@ Landlock trace events are organized in four categories: **Syscall events** are emitted during Landlock system calls: - ``landlock_create_ruleset``: a new ruleset is created -- ``landlock_add_rule_fs``: a filesystem rule is added to a ruleset -- ``landlock_add_rule_net``: a network port rule is added to a ruleset +- ``landlock_add_rule_path_beneath``: a filesystem rule is added to a ruleset +- ``landlock_add_rule_net_port``: a network port rule is added to a ruleset - ``landlock_create_domain``: a new domain is created from a ruleset - ``landlock_enforce_domain``: a domain is enforced on a thread @@ -47,8 +47,8 @@ Landlock trace events are organized in four categories: **Rule evaluation events** are emitted during rule matching: -- ``landlock_check_rule_fs``: a filesystem rule is evaluated -- ``landlock_check_rule_net``: a network port rule is evaluated +- ``landlock_check_rule_inode``: an inode-keyed rule is evaluated +- ``landlock_check_rule_net_port``: a network-port-keyed rule is evaluated **Lifecycle events**: @@ -141,10 +141,10 @@ in some field formats: treated as untrusted input and escaped in the trace text output so it cannot inject field separators or control characters. -- **Other party's domain**: A scope or ptrace denial compares the - subject's denying domain (``domain=``, always the enforcing domain and - never the current task) with the other party's domain, so these - tracepoints also report the other party's domain as a scalar ID: +- **Other party's domain**: A scope or ptrace denial compares the subject's + denying domain (``domain=``), which is the enforcing domain and not + necessarily the current task's domain, with the other party's domain. + These tracepoints also report the other party's domain as a scalar ID: ``tracee_domain=`` (ptrace), ``target_domain=`` (signal), and ``peer_domain=`` (abstract unix socket). It is ``0`` when the other party is unsandboxed, and otherwise a domain ID that a consumer resolves @@ -189,7 +189,7 @@ rather than the caller's, so correlate those to the syscall by domain ID. Interpreting check_rule events ============================== -The ``check_rule_fs`` and ``check_rule_net`` events expose the per-layer +The ``check_rule_inode`` and ``check_rule_net_port`` events expose the per-layer rule evaluation, which is useful for understanding *why* a specific access is allowed or denied. @@ -231,9 +231,10 @@ contribution, not the final decision: through another matching rule, or the right is denied and appears in the ``blockers=`` field of the corresponding ``deny_access`` event. -To reconstruct the decision for an object, aggregate the ``grants=`` -groups of all ``check_rule`` events emitted for that object during the -check. +For an access check that a consumer can delimit, aggregate the ``grants=`` +groups of all matching ``check_rule`` events. These events do not carry a +request ID; use their execution context and generic tracepoints to separate +concurrent or successive checks of the same object. .. note:: @@ -244,29 +245,31 @@ check. For example, a program sandboxed with read and execute access to the whole filesystem reads ``/etc/passwd``; both the ``execve()`` and the -read match the rule covering ``/`` (inode 2), so ``check_rule_fs`` fires +read match the rule covering ``/`` (inode 2), so ``check_rule_inode`` fires with the requested rights intersected against what that rule grants. The ``access_request=`` mask includes ``truncate`` because the file-open hook evaluates that optional right alongside the required access, but the rule does not grant it, so ``truncate`` never appears in ``grants=``:: - cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file} - cat-127 [...] landlock_check_rule_fs: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file} + cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file} + cat-127 [...] landlock_check_rule_inode: domain=1e40cb56f access_request=read_file|truncate dev=0:17 ino=2 grants={read_file} The ``[...]`` replaces the ftrace CPU, flags, and timestamp columns. A single ``grants=`` group means the enforcing domain has one layer. With two nested sandboxes that each grant the same rights, the rule spans both layers, so ``grants=`` has one group per layer:: - cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file} - cat-128 [...] landlock_check_rule_fs: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file} + cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=execute|read_file|truncate dev=0:17 ino=2 grants={execute|read_file,execute|read_file} + cat-128 [...] landlock_check_rule_inode: domain=184788b52 access_request=read_file|truncate dev=0:17 ino=2 grants={read_file,read_file} eBPF access =========== -eBPF programs attached via ``BPF_RAW_TRACEPOINT`` can access the -tracepoint arguments directly through BTF. The arguments include both -standard kernel objects and Landlock-internal objects: +BTF-enabled raw tracepoint programs attached through libbpf +``SEC("tp_btf/...")`` sections receive typed callback arguments. The +event prototypes in `Event reference`_ document their argument layouts. +The arguments include both standard kernel objects and Landlock-internal +objects: - Standard kernel objects (``struct task_struct``, ``struct sock``, ``struct path``, ``struct dentry``) can be used with existing BPF @@ -277,7 +280,7 @@ standard kernel objects and Landlock-internal objects: Internal struct layouts may change between kernel versions; use CO-RE for field relocation. -A stateful eBPF program observes the full event stream and maintains +A stateful eBPF program attached before sandbox construction can maintain per-domain state in BPF maps: 1. On ``landlock_create_domain``: record the domain ID and parent (the @@ -293,7 +296,10 @@ per-domain state in BPF maps: final statistics. This approach requires no kernel modification and no Landlock-specific -BPF helpers. The Landlock IDs serve as correlation keys across events. +BPF helpers. Landlock IDs serve as correlation keys within one boot. +Records exported through tracing or BPF buffers can be lost, and records +from different CPUs are not globally ordered, so consumers must detect and +reconcile incomplete state. Audit filtering equivalence =========================== diff --git a/MAINTAINERS b/MAINTAINERS index cc3cae2e378b..2527409232e9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -767,6 +767,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 <ansuelsmth@gmail.com> +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 <lorenzo@kernel.org> M: Ray Liu <ray.liu@airoha.com> @@ -5454,6 +5462,7 @@ F: include/linux/brcmphy.h BROADCOM GENET ETHERNET DRIVER M: Doug Berger <opendmb@gmail.com> M: Florian Fainelli <florian.fainelli@broadcom.com> +M: Nicolai Buchwitz <nb@tipi-net.de> R: Broadcom internal kernel review list <bcm-kernel-feedback-list@broadcom.com> L: netdev@vger.kernel.org S: Maintained @@ -9700,6 +9709,13 @@ S: Maintained F: include/linux/errseq.h F: lib/errseq.c +ES9039Q2M AUDIO CODEC DRIVER +M: Karl Asseily <karl@asseily.com> +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 <stefan.maetje@esd.eu> R: socketcan@esd.eu @@ -14905,7 +14921,7 @@ M: Damien Le Moal <dlemoal@kernel.org> M: Niklas Cassel <cassel@kernel.org> L: linux-ide@vger.kernel.org S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/libata/linux.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/libata/linux.git for-next F: Documentation/ABI/testing/sysfs-ata F: Documentation/devicetree/bindings/ata/ F: drivers/ata/ @@ -17194,7 +17210,7 @@ F: tools/testing/selftests/mm/gup_test.c MEMORY MANAGEMENT - KSM (Kernel Samepage Merging) M: Andrew Morton <akpm@linux-foundation.org> M: David Hildenbrand <david@kernel.org> -R: Xu Xin <xu.xin16@zte.com.cn> +R: Xu Xin <xu.xin@linux.dev> R: Chengming Zhou <chengming.zhou@linux.dev> L: linux-mm@kvack.org S: Maintained @@ -17242,6 +17258,8 @@ R: Barry Song <baohua@kernel.org> R: Axel Rasmussen <axelrasmussen@google.com> R: Yuanchu Xie <yuanchu@google.com> R: Wei Xu <weixugc@google.com> +R: Baoquan He <baoquan.he@linux.dev> +R: Baolin Wang <baolin.wang@linux.alibaba.com> L: linux-mm@kvack.org S: Maintained W: http://www.linux-mm.org @@ -30183,9 +30201,12 @@ F: tools/testing/selftests/cgroup/test_zswap.c SENARYTECH AUDIO CODEC DRIVER M: bo liu <bo.liu@senarytech.com> +M: qianghua.wang <qianghua.wang@senarytech.com> 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 <han.junyang@zte.com.cn> @@ -2,7 +2,7 @@ VERSION = 7 PATCHLEVEL = 3 SUBLEVEL = 0 -EXTRAVERSION = -rc4 +EXTRAVERSION = -rc5 NAME = Baby Opossum Posse # *DOCUMENTATION* diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h index aa8ec9df8024..87560d8b254e 100644 --- a/arch/arm64/include/asm/el2_setup.h +++ b/arch/arm64/include/asm/el2_setup.h @@ -418,6 +418,7 @@ b.lt .Lskip_fgt2_\@ mov x0, xzr + mov x2, xzr mrs x1, id_aa64dfr0_el1 ubfx x1, x1, #ID_AA64DFR0_EL1_PMUVer_SHIFT, #4 cmp x1, #ID_AA64DFR0_EL1_PMUVer_V3P9 @@ -426,6 +427,11 @@ orr x0, x0, #HDFGRTR2_EL2_nPMICNTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMICFILTR_EL0 orr x0, x0, #HDFGRTR2_EL2_nPMUACR_EL1 + orr x2, x2, #HDFGWTR2_EL2_nPMICNTR_EL0 + orr x2, x2, #HDFGWTR2_EL2_nPMICFILTR_EL0 + orr x2, x2, #HDFGWTR2_EL2_nPMUACR_EL1 + /* PMZR_EL0 is write-only, so it has no read trap to disable */ + orr x2, x2, #HDFGWTR2_EL2_nPMZR_EL0 .Lskip_pmuv3p9_\@: /* If SPE is implemented, */ __spe_vers_imp .Lskip_spefds_\@, ID_AA64DFR0_EL1_PMSVer_IMP, x1 @@ -436,10 +442,11 @@ cbz x1, .Lskip_spefds_\@ /* disable traps of PMSDSFR to EL2. */ orr x0, x0, #HDFGRTR2_EL2_nPMSDSFR_EL1 + orr x2, x2, #HDFGWTR2_EL2_nPMSDSFR_EL1 .Lskip_spefds_\@: msr_s SYS_HDFGRTR2_EL2, x0 - msr_s SYS_HDFGWTR2_EL2, x0 + msr_s SYS_HDFGWTR2_EL2, x2 msr_s SYS_HFGRTR2_EL2, xzr msr_s SYS_HFGWTR2_EL2, xzr msr_s SYS_HFGITR2_EL2, xzr diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h index 49a7002661a9..9aa0bb08ab60 100644 --- a/arch/arm64/include/asm/io.h +++ b/arch/arm64/include/asm/io.h @@ -280,7 +280,8 @@ static inline void __iomem *ioremap_prot(phys_addr_t phys, size_t size, pgprot_t prot; ptval_t user_prot_val = pgprot_val(user_prot); - if (WARN_ON_ONCE(!(user_prot_val & PTE_USER))) + /* Reject PROT_NONE and exec-only */ + if (!(user_prot_val & PTE_USER)) return NULL; prot = __pgprot_modify(PAGE_KERNEL, PTE_ATTRINDX_MASK, diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index 27fe0cd5b2d7..cd9b9d2462f9 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -322,7 +322,7 @@ struct kvm_arch { * Stage 2 paging state for VMs with nested S2 using a virtual * VMID. */ - struct kvm_s2_mmu *nested_mmus; + struct kvm_s2_mmu **nested_mmus; size_t nested_mmus_size; int nested_mmus_next; diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h index 1ed708335809..586026e85903 100644 --- a/arch/arm64/include/asm/kvm_nested.h +++ b/arch/arm64/include/asm/kvm_nested.h @@ -66,7 +66,8 @@ static inline u64 translate_ttbr0_el2_to_ttbr0_el1(u64 ttbr0) extern bool forward_smc_trap(struct kvm_vcpu *vcpu); extern bool forward_debug_exception(struct kvm_vcpu *vcpu); -extern void kvm_init_nested(struct kvm *kvm); +extern int kvm_init_nested(struct kvm *kvm); +extern void kvm_destroy_nested(struct kvm *kvm); extern int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu); extern void kvm_init_nested_s2_mmu(struct kvm_s2_mmu *mmu); extern struct kvm_s2_mmu *lookup_s2_mmu(struct kvm_vcpu *vcpu); diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h index beea00e693a0..cad60569f061 100644 --- a/arch/arm64/include/asm/kvm_pkvm.h +++ b/arch/arm64/include/asm/kvm_pkvm.h @@ -65,8 +65,7 @@ static inline bool kvm_pkvm_ioctl_allowed(struct kvm *kvm, unsigned int ioctl) int r; r = kvm_get_cap_for_kvm_ioctl(ioctl, &ext); - - if (WARN_ON_ONCE(r < 0)) + if (r < 0) return false; return kvm_pkvm_ext_allowed(kvm, ext); diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index b33dccfafaf8..8ec47d89b45b 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -28,18 +28,21 @@ bool cpu_errata_set_target_impl(u64 num, void *impl_cpus) return true; } +static inline bool __is_midr_in_range(u32 midr, struct midr_range const *range) +{ + return midr_is_cpu_model_range(midr, range->model, + range->rv_min, range->rv_max); +} + static inline bool is_midr_in_range(struct midr_range const *range) { int i; if (!target_impl_cpu_num) - return midr_is_cpu_model_range(read_cpuid_id(), range->model, - range->rv_min, range->rv_max); + return __is_midr_in_range(read_cpuid_id(), range); for (i = 0; i < target_impl_cpu_num; i++) { - if (midr_is_cpu_model_range(target_impl_cpus[i].midr, - range->model, - range->rv_min, range->rv_max)) + if (__is_midr_in_range(target_impl_cpus[i].midr, range)) return true; } return false; @@ -59,7 +62,7 @@ __is_affected_midr_range(const struct arm64_cpu_capabilities *entry, u32 midr, u32 revidr) { const struct arm64_midr_revidr *fix; - if (!is_midr_in_range(&entry->midr_range)) + if (!__is_midr_in_range(midr, &entry->midr_range)) return false; midr &= MIDR_REVISION_MASK | MIDR_VARIANT_MASK; diff --git a/arch/arm64/kernel/pi/idreg-override.c b/arch/arm64/kernel/pi/idreg-override.c index 274bc72ba98d..acaf56234973 100644 --- a/arch/arm64/kernel/pi/idreg-override.c +++ b/arch/arm64/kernel/pi/idreg-override.c @@ -186,6 +186,7 @@ static const struct ftr_set_desc isar2 __prel64_initconst = { .name = "id_aa64isar2", .override = &id_aa64isar2_override, .fields = { + FIELD("wfxt", ID_AA64ISAR2_EL1_WFxT_SHIFT, NULL), FIELD("gpa3", ID_AA64ISAR2_EL1_GPA3_SHIFT, NULL), FIELD("apa3", ID_AA64ISAR2_EL1_APA3_SHIFT, NULL), FIELD("mops", ID_AA64ISAR2_EL1_MOPS_SHIFT, NULL), @@ -254,6 +255,7 @@ static const struct { "id_aa64isar1.api=0 id_aa64isar1.apa=0 " "id_aa64isar2.gpa3=0 id_aa64isar2.apa3=0" }, { "arm64.nomops", "id_aa64isar2.mops=0" }, + { "arm64.nowfxt", "id_aa64isar2.wfxt=0" }, { "arm64.nomte", "id_aa64pfr1.mte=0" }, { "nokaslr", "arm64_sw.nokaslr=1" }, { "rodata=off", "arm64_sw.rodataoff=1" }, diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 8b080804bc90..0576c2022ef5 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -236,8 +236,6 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) mutex_unlock(&kvm->lock); #endif - kvm_init_nested(kvm); - ret = kvm_share_hyp(kvm, kvm + 1); if (ret) return ret; @@ -252,6 +250,10 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) if (ret) goto err_free_cpumask; + ret = kvm_init_nested(kvm); + if (ret) + goto err_uninit_mmu; + if (is_protected_kvm_enabled()) { /* * If any failures occur after this is successful, make sure to @@ -280,6 +282,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) err_uninit_mmu: kvm_uninit_stage2_mmu(kvm); + kvm_destroy_nested(kvm); err_free_cpumask: free_cpumask_var(kvm->arch.supported_cpus); err_unshare_kvm: @@ -337,6 +340,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm) kvm_unshare_hyp(kvm, kvm + 1); + kvm_destroy_nested(kvm); kvm_arm_teardown_hypercalls(kvm); } @@ -1129,7 +1133,7 @@ static int kvm_vcpu_suspend(struct kvm_vcpu *vcpu) static int check_vcpu_requests(struct kvm_vcpu *vcpu) { if (kvm_request_pending(vcpu)) { - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) return -EIO; if (kvm_check_request(KVM_REQ_SLEEP, vcpu)) diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index 625604019fb3..3806ff0920fe 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -1445,7 +1445,7 @@ static const struct encoding_to_trap_config encoding_to_fgt[] __initconst = { SR_FGT(OP_AT_S1E1A, HFGITR, ATS1E1A, 1), SR_FGT(OP_COSP_RCTX, HFGITR, COSPRCTX, 1), SR_FGT(OP_GCSPUSHX, HFGITR, nGCSEPP, 0), - SR_FGT(OP_GCSPOPX, HFGITR, nGCSEPP, 0), + SR_FGT(OP_GCSPOPCX, HFGITR, nGCSEPP, 0), SR_FGT(OP_GCSPUSHM, HFGITR, nGCSPUSHM_EL1, 0), SR_FGT(OP_BRB_IALL, HFGITR, nBRBIALL, 0), SR_FGT(OP_BRB_INJ, HFGITR, nBRBINJ, 0), diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h index 29935c7da1de..ec85a9547120 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h @@ -52,8 +52,10 @@ int __pkvm_host_test_clear_young_guest(u64 gfn, u64 nr_pages, bool mkold, struct int __pkvm_host_mkyoung_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu); bool addr_is_memory(phys_addr_t phys); +bool addr_is_hyp_text(phys_addr_t phys); int host_stage2_idmap_locked(phys_addr_t addr, u64 size, enum kvm_pgtable_prot prot); int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id); +bool host_stage2_pte_is_hyp_owned(kvm_pte_t pte); int kvm_host_prepare_stage2(void *pgt_pool_base); int kvm_guest_prepare_stage2(struct pkvm_hyp_vm *vm, void *pgd); void kvm_guest_destroy_stage2(struct pkvm_hyp_vm *vm); diff --git a/arch/arm64/kvm/hyp/include/nvhe/mm.h b/arch/arm64/kvm/hyp/include/nvhe/mm.h index 6e83ce35c2f2..31cae95ddb71 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/mm.h +++ b/arch/arm64/kvm/hyp/include/nvhe/mm.h @@ -29,6 +29,7 @@ int __pkvm_create_private_mapping(phys_addr_t phys, size_t size, enum kvm_pgtable_prot prot, unsigned long *haddr); int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr); +int pkvm_check_host_ownership(void); int pkvm_alloc_private_va_range(size_t size, unsigned long *haddr); #endif /* __KVM_HYP_MM_H */ diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c index 39aa8911f62c..a6a47c1e058b 100644 --- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c +++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c @@ -450,6 +450,14 @@ bool addr_is_memory(phys_addr_t phys) return !!find_mem_range(phys, &range); } +bool addr_is_hyp_text(phys_addr_t phys) +{ + phys_addr_t start = ALIGN_DOWN(__hyp_pa(__hyp_text_start), PAGE_SIZE); + phys_addr_t end = PAGE_ALIGN(__hyp_pa(__hyp_text_end)); + + return phys >= start && phys < end; +} + static bool is_in_mem_range(u64 addr, struct kvm_mem_range *range) { return range->start <= addr && addr < range->end; @@ -633,6 +641,18 @@ int host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_id) return ret; } +bool host_stage2_pte_is_hyp_owned(kvm_pte_t pte) +{ + if (kvm_pte_valid(pte)) + return false; + + if (FIELD_GET(KVM_INVALID_PTE_TYPE_MASK, pte) != + KVM_HOST_INVALID_PTE_TYPE_DONATION) + return false; + + return FIELD_GET(KVM_HOST_DONATION_PTE_OWNER_MASK, pte) == PKVM_ID_HYP; +} + #define KVM_HOST_PTE_OWNER_GUEST_HANDLE_MASK GENMASK(15, 0) /* We need 40 bits for the GFN to cover a 52-bit IPA with 4k pages and LPA2 */ #define KVM_HOST_PTE_OWNER_GUEST_GFN_MASK GENMASK(55, 16) diff --git a/arch/arm64/kvm/hyp/nvhe/mm.c b/arch/arm64/kvm/hyp/nvhe/mm.c index 3b0bee496bff..29ab5ee9d57f 100644 --- a/arch/arm64/kvm/hyp/nvhe/mm.c +++ b/arch/arm64/kvm/hyp/nvhe/mm.c @@ -25,6 +25,7 @@ struct memblock_region hyp_memory[HYP_MEMBLOCK_REGIONS]; unsigned int hyp_memblock_nr; static u64 __io_map_base; +static u64 __io_map_next; struct hyp_fixmap_slot { u64 addr; @@ -50,7 +51,7 @@ static int __pkvm_alloc_private_va_range(unsigned long start, size_t size) hyp_assert_lock_held(&pkvm_pgd_lock); - if (!start || start < __io_map_base) + if (!start || start < __io_map_next) return -EINVAL; /* The allocated size is always a multiple of PAGE_SIZE */ @@ -60,7 +61,7 @@ static int __pkvm_alloc_private_va_range(unsigned long start, size_t size) if (cur > __hyp_vmemmap) return -ENOMEM; - __io_map_base = cur; + __io_map_next = cur; return 0; } @@ -70,7 +71,7 @@ static int __pkvm_alloc_private_va_range(unsigned long start, size_t size) * @size: The size of the VA range to reserve. * @haddr: The hypervisor virtual start address of the allocation. * - * The private virtual address (VA) range is allocated above __io_map_base + * The private virtual address (VA) range is allocated above __io_map_next * and aligned based on the order of @size. * * Return: 0 on success or negative error code on failure. @@ -81,7 +82,7 @@ int pkvm_alloc_private_va_range(size_t size, unsigned long *haddr) int ret; hyp_spin_lock(&pkvm_pgd_lock); - addr = __io_map_base; + addr = __io_map_next; ret = __pkvm_alloc_private_va_range(addr, size); hyp_spin_unlock(&pkvm_pgd_lock); @@ -341,7 +342,7 @@ static int create_fixblock(void) return -EINVAL; hyp_spin_lock(&pkvm_pgd_lock); - addr = ALIGN(__io_map_base, PMD_SIZE); + addr = ALIGN(__io_map_next, PMD_SIZE); ret = __pkvm_alloc_private_va_range(addr, PMD_SIZE); if (ret) goto unlock; @@ -426,6 +427,7 @@ int hyp_create_idmap(u32 hyp_va_bits) */ __io_map_base = start & BIT(hyp_va_bits - 2); __io_map_base ^= BIT(hyp_va_bits - 2); + __io_map_next = __io_map_base; __hyp_vmemmap = __io_map_base | BIT(hyp_va_bits - 3); return __pkvm_create_mappings(start, end - start, start, PAGE_HYP_EXEC); @@ -433,19 +435,19 @@ int hyp_create_idmap(u32 hyp_va_bits) int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr) { - unsigned long addr, prev_base; + unsigned long addr, prev_next; size_t size; int ret; hyp_spin_lock(&pkvm_pgd_lock); - prev_base = __io_map_base; + prev_next = __io_map_next; /* * Efficient stack verification using the NVHE_STACK_SHIFT bit implies * an alignment of our allocation on the order of the size. */ size = NVHE_STACK_SIZE * 2; - addr = ALIGN(__io_map_base, size); + addr = ALIGN(__io_map_next, size); ret = __pkvm_alloc_private_va_range(addr, size); if (!ret) { @@ -461,7 +463,7 @@ int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr) ret = kvm_pgtable_hyp_map(&pkvm_pgtable, addr + NVHE_STACK_SIZE, NVHE_STACK_SIZE, phys, PAGE_HYP); if (ret) - __io_map_base = prev_base; + __io_map_next = prev_next; } hyp_spin_unlock(&pkvm_pgd_lock); @@ -470,6 +472,65 @@ int pkvm_create_stack(phys_addr_t phys, unsigned long *haddr) return ret; } +static int check_page_ownership(phys_addr_t phys) +{ + kvm_pte_t pte; + bool host_ok; + int ret; + + if (addr_is_memory(phys)) { + struct hyp_page *page = hyp_phys_to_page(phys); + + if (get_hyp_state(page) != PKVM_PAGE_OWNED || + get_host_state(page) != PKVM_NOPAGE) + return -EPERM; + } + + ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, NULL); + if (ret) + return ret; + + /* Hyp text may stay host-readable, see fix_host_ownership_walker(). */ + if (kvm_pte_valid(pte) && addr_is_hyp_text(phys)) + host_ok = !(kvm_pgtable_stage2_pte_prot(pte) & KVM_PGTABLE_PROT_W); + else + host_ok = host_stage2_pte_is_hyp_owned(pte); + + return host_ok ? 0 : -EPERM; +} + +static int check_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx, + enum kvm_pgtable_walk_flags visit) +{ + phys_addr_t phys, end; + int ret; + + if (!kvm_pte_valid(ctx->old)) + return 0; + + phys = kvm_pte_to_phys(ctx->old); + end = phys + kvm_granule_size(ctx->level); + for (; phys < end; phys += PAGE_SIZE) { + ret = check_page_ownership(phys); + if (ret) + return ret; + } + + return 0; +} + +int pkvm_check_host_ownership(void) +{ + struct kvm_pgtable_walker walker = { + .cb = check_host_ownership_walker, + .flags = KVM_PGTABLE_WALK_LEAF, + }; + + /* The private range and the vmemmap share one quarter of the VA space. */ + return kvm_pgtable_walk(&pkvm_pgtable, __io_map_base, + BIT(pkvm_pgtable.ia_bits - 2), &walker); +} + static void *admit_host_page(void *arg) { struct kvm_hyp_memcache *host_mc = arg; diff --git a/arch/arm64/kvm/hyp/nvhe/pkvm.c b/arch/arm64/kvm/hyp/nvhe/pkvm.c index 459bd9eb7e4b..bb3e0dc0676e 100644 --- a/arch/arm64/kvm/hyp/nvhe/pkvm.c +++ b/arch/arm64/kvm/hyp/nvhe/pkvm.c @@ -360,7 +360,7 @@ static void pkvm_init_features_from_host(struct pkvm_hyp_vm *hyp_vm, const struc if (test_bit(KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS, &host_arch_flags)) hyp_vm->kvm.arch.midr_el1 = host_kvm->arch.midr_el1; - return; + goto out; } if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_MTE)) @@ -379,13 +379,14 @@ static void pkvm_init_features_from_host(struct pkvm_hyp_vm *hyp_vm, const struc if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_PTRAUTH_GENERIC)) set_bit(KVM_ARM_VCPU_PTRAUTH_GENERIC, allowed_features); - if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_SVE)) { + if (kvm_pkvm_ext_allowed(kvm, KVM_CAP_ARM_SVE)) set_bit(KVM_ARM_VCPU_SVE, allowed_features); - kvm->arch.flags |= host_arch_flags & BIT(KVM_ARCH_FLAG_GUEST_HAS_SVE); - } bitmap_and(kvm->arch.vcpu_features, host_kvm->arch.vcpu_features, allowed_features, KVM_VCPU_MAX_FEATURES); +out: + __assign_bit(KVM_ARCH_FLAG_GUEST_HAS_SVE, &kvm->arch.flags, + kvm_vcpu_has_feature(kvm, KVM_ARM_VCPU_SVE)); } static void unpin_host_vcpu(struct kvm_vcpu *host_vcpu) @@ -398,10 +399,10 @@ static void unpin_host_sve_state(struct pkvm_hyp_vcpu *hyp_vcpu) { void *sve_state; - if (!vcpu_has_feature(&hyp_vcpu->vcpu, KVM_ARM_VCPU_SVE)) + sve_state = hyp_vcpu->vcpu.arch.sve_state; + if (!sve_state) return; - sve_state = hyp_vcpu->vcpu.arch.sve_state; hyp_unpin_shared_mem(sve_state, sve_state + vcpu_sve_state_size(&hyp_vcpu->vcpu)); } @@ -450,7 +451,7 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h unsigned int sve_max_vl; size_t sve_state_size; void *sve_state; - int ret = 0; + int ret; if (!vcpu_has_feature(vcpu, KVM_ARM_VCPU_SVE)) { vcpu_clear_flag(vcpu, VCPU_SVE_FINALIZED); @@ -459,25 +460,21 @@ static int pkvm_vcpu_init_sve(struct pkvm_hyp_vcpu *hyp_vcpu, struct kvm_vcpu *h /* Limit guest vector length to the maximum supported by the host. */ sve_max_vl = min(READ_ONCE(host_vcpu->arch.sve_max_vl), kvm_host_sve_max_vl); - sve_state_size = sve_state_size_from_vl(sve_max_vl); sve_state = kern_hyp_va(READ_ONCE(host_vcpu->arch.sve_state)); - if (!sve_state || !sve_state_size) { - ret = -EINVAL; - goto err; - } + if (!sve_vl_valid(sve_max_vl) || !sve_state) + return -EINVAL; + + sve_state_size = sve_state_size_from_vl(sve_max_vl); ret = hyp_pin_shared_mem(sve_state, sve_state + sve_state_size); if (ret) - goto err; + return ret; vcpu->arch.sve_state = sve_state; vcpu->arch.sve_max_vl = sve_max_vl; return 0; -err: - clear_bit(KVM_ARM_VCPU_SVE, vcpu->kvm->arch.vcpu_features); - return ret; } static int vm_copy_id_regs(struct pkvm_hyp_vcpu *hyp_vcpu) diff --git a/arch/arm64/kvm/hyp/nvhe/setup.c b/arch/arm64/kvm/hyp/nvhe/setup.c index 75b00c323310..45ac5f2ba4f7 100644 --- a/arch/arm64/kvm/hyp/nvhe/setup.c +++ b/arch/arm64/kvm/hyp/nvhe/setup.c @@ -217,7 +217,7 @@ static int fix_host_ownership_walker(const struct kvm_pgtable_visit_ctx *ctx, case PKVM_PAGE_OWNED: set_hyp_state(page, PKVM_PAGE_OWNED); /* hyp text is RO in the host stage-2 to be inspected on panic. */ - if (prot == PAGE_HYP_EXEC) { + if (addr_is_hyp_text(phys)) { set_host_state(page, PKVM_NOPAGE); return host_stage2_idmap_locked(phys, PAGE_SIZE, KVM_PGTABLE_PROT_R); } else { @@ -269,6 +269,16 @@ static int fix_host_ownership(void) return ret; } + /* The stacks sit in the private VA range, not the linear map. */ + for (i = 0; i < hyp_nr_cpus; i++) { + struct kvm_nvhe_init_params *params = per_cpu_ptr(&kvm_init_params, i); + u64 start = params->stack_hyp_va - NVHE_STACK_SIZE; + + ret = kvm_pgtable_walk(&pkvm_pgtable, start, NVHE_STACK_SIZE, &walker); + if (ret) + return ret; + } + return 0; } @@ -324,6 +334,10 @@ void __noreturn __pkvm_init_finalise(void) if (ret) goto out; + ret = pkvm_check_host_ownership(); + if (ret) + goto out; + ret = hyp_ffa_init(ffa_proxy_pages); if (ret) goto out; diff --git a/arch/arm64/kvm/hypercalls.c b/arch/arm64/kvm/hypercalls.c index b11b8821c9fb..dfa25bb6f25d 100644 --- a/arch/arm64/kvm/hypercalls.c +++ b/arch/arm64/kvm/hypercalls.c @@ -185,7 +185,8 @@ static int kvm_smccc_set_filter(struct kvm *kvm, struct kvm_smccc_filter __user start = filter.base; end = start + filter.nr_functions - 1; - if (end < start || filter.action >= NR_SMCCC_FILTER_ACTIONS) + if (!filter.nr_functions || end < start || + filter.action >= NR_SMCCC_FILTER_ACTIONS) return -EINVAL; mutex_lock(&kvm->arch.config_lock); diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 9ba86450fe4a..2d44cd6a5aed 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -59,27 +59,36 @@ static phys_addr_t stage2_range_addr_end(phys_addr_t addr, phys_addr_t end) * long will also starve other vCPUs. We have to also make sure that the page * tables are not freed while we released the lock. */ -static int stage2_apply_range(struct kvm_s2_mmu *mmu, phys_addr_t addr, +static int stage2_apply_range(struct kvm_s2_mmu *mmu, phys_addr_t start, phys_addr_t end, int (*fn)(struct kvm_pgtable *, u64, u64), bool resched) { struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu); + bool lock_dropped = false; + phys_addr_t addr = start; int ret; u64 next; do { struct kvm_pgtable *pgt = mmu->pgt; + /* + * We may be raced on PGT teardown when we release the + * kvm->mmu_lock. That's fine as the PGT is legitimately no + * longer present. + */ if (!pgt) - return -EINVAL; + return lock_dropped ? 0 : -EINVAL; next = stage2_range_addr_end(addr, end); ret = fn(pgt, addr, next - addr); if (ret) break; - if (resched && next != end) + if (resched && next != end) { cond_resched_rwlock_write(&kvm->mmu_lock); + lock_dropped = true; + } } while (addr = next, addr != end); return ret; diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c index 3c4fc566eafc..b191365d97cc 100644 --- a/arch/arm64/kvm/nested.c +++ b/arch/arm64/kvm/nested.c @@ -45,11 +45,24 @@ struct vncr_tlb { */ #define S2_MMU_PER_VCPU 2 -void kvm_init_nested(struct kvm *kvm) +int kvm_init_nested(struct kvm *kvm) { - kvm->arch.nested_mmus = NULL; + kvm->arch.nested_mmus = kvmalloc_objs(struct kvm_s2_mmu *, + KVM_MAX_VCPUS * S2_MMU_PER_VCPU, + GFP_KERNEL_ACCOUNT); kvm->arch.nested_mmus_size = 0; atomic_set(&kvm->arch.vncr_tlb_count, 0); + + return kvm->arch.nested_mmus ? 0 : -ENOMEM; +} + +void kvm_destroy_nested(struct kvm *kvm) +{ + for (int i = 0; i < kvm->arch.nested_mmus_size; i+= S2_MMU_PER_VCPU) + kvfree(kvm->arch.nested_mmus[i]); + + kvm->arch.nested_mmus_size = 0; + kvfree(kvm->arch.nested_mmus); } static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu) @@ -70,8 +83,9 @@ static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu) int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu) { struct kvm *kvm = vcpu->kvm; - struct kvm_s2_mmu *tmp; - int num_mmus, ret = 0; + int num_mmus; + + lockdep_assert_held(&kvm->arch.config_lock); if (test_bit(KVM_ARM_VCPU_HAS_EL2_E2H0, kvm->arch.vcpu_features) && !cpus_have_final_cap(ARM64_HAS_HCR_NV1)) @@ -84,51 +98,40 @@ int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu) if (!vcpu->arch.ctxt.vncr_array) return -ENOMEM; - /* - * Let's treat memory allocation failures as benign: If we fail to - * allocate anything, return an error and keep the allocated array - * alive. Userspace may try to recover by initializing the vcpu - * again, and there is no reason to affect the whole VM for this. - */ num_mmus = atomic_read(&kvm->online_vcpus) * S2_MMU_PER_VCPU; if (num_mmus > kvm->arch.nested_mmus_size) { - tmp = kvzalloc_objs(*tmp, num_mmus, GFP_KERNEL_ACCOUNT); - if (!tmp) - return -ENOMEM; + struct kvm_s2_mmu *tmp; + int i, ret = 0; - write_lock(&kvm->mmu_lock); - - if (kvm->arch.nested_mmus_size) { - memcpy(tmp, kvm->arch.nested_mmus, - size_mul(sizeof(*tmp), kvm->arch.nested_mmus_size)); + tmp = kvzalloc_objs(*tmp, S2_MMU_PER_VCPU, GFP_KERNEL_ACCOUNT); + if (!tmp) + ret = -ENOMEM; - for (int i = 0; i < kvm->arch.nested_mmus_size; i++) - tmp[i].pgt->mmu = &tmp[i]; + for (i = 0; !ret && i < S2_MMU_PER_VCPU; i++) { + ret = init_nested_s2_mmu(kvm, &tmp[i]); + if (ret) + break; } - swap(kvm->arch.nested_mmus, tmp); - - write_unlock(&kvm->mmu_lock); - - kvfree(tmp); - } + if (ret) { + while (--i >= 0) + kvm_free_stage2_pgd(&tmp[i]); - for (int i = kvm->arch.nested_mmus_size; !ret && i < num_mmus; i++) - ret = init_nested_s2_mmu(kvm, &kvm->arch.nested_mmus[i]); + kvfree(tmp); + free_page((unsigned long)vcpu->arch.ctxt.vncr_array); + vcpu->arch.ctxt.vncr_array = NULL; + return ret; + } - if (ret) { - for (int i = kvm->arch.nested_mmus_size; i < num_mmus; i++) - kvm_free_stage2_pgd(&kvm->arch.nested_mmus[i]); + guard(write_lock)(&kvm->mmu_lock); - free_page((unsigned long)vcpu->arch.ctxt.vncr_array); - vcpu->arch.ctxt.vncr_array = NULL; + for (i = 0; i < S2_MMU_PER_VCPU; i++) + kvm->arch.nested_mmus[i + kvm->arch.nested_mmus_size] = &tmp[i]; - return ret; + kvm->arch.nested_mmus_size += S2_MMU_PER_VCPU; } - kvm->arch.nested_mmus_size = num_mmus; - return 0; } @@ -742,7 +745,7 @@ void kvm_s2_mmu_iterate_by_vmid(struct kvm *kvm, u16 vmid, write_lock(&kvm->mmu_lock); for (int i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!kvm_s2_mmu_valid(mmu)) continue; @@ -784,7 +787,7 @@ struct kvm_s2_mmu *lookup_s2_mmu(struct kvm_vcpu *vcpu) * if S2 translation is disabled. */ for (int i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!kvm_s2_mmu_valid(mmu)) continue; @@ -823,7 +826,7 @@ static struct kvm_s2_mmu *get_s2_mmu_nested(struct kvm_vcpu *vcpu) for (i = kvm->arch.nested_mmus_next; i < (kvm->arch.nested_mmus_size + kvm->arch.nested_mmus_next); i++) { - s2_mmu = &kvm->arch.nested_mmus[i % kvm->arch.nested_mmus_size]; + s2_mmu = kvm->arch.nested_mmus[i % kvm->arch.nested_mmus_size]; if (atomic_read(&s2_mmu->refcnt) == 0) break; @@ -1260,6 +1263,17 @@ void kvm_handle_s1e2_tlbi(struct kvm_vcpu *vcpu, u32 inst, u64 val) invalidate_vncr_va(vcpu->kvm, &scope); } +static void kvm_invalidate_vncr_ipa_all(struct kvm *kvm) +{ + struct kvm_pgtable *pgt = kvm->arch.mmu.pgt; + + lockdep_assert_held_write(&kvm->mmu_lock); + + /* if the mmu lock was dropped, pgt teardown may have raced. */ + if (pgt) + kvm_invalidate_vncr_ipa(kvm, 0, BIT(pgt->ia_bits)); +} + void kvm_nested_s2_wp(struct kvm *kvm) { int i; @@ -1270,13 +1284,13 @@ void kvm_nested_s2_wp(struct kvm *kvm) return; for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (kvm_s2_mmu_valid(mmu)) kvm_stage2_wp_range(mmu, 0, kvm_phys_size(mmu)); } - kvm_invalidate_vncr_ipa(kvm, 0, BIT(kvm->arch.mmu.pgt->ia_bits)); + kvm_invalidate_vncr_ipa_all(kvm); } void kvm_nested_s2_unmap(struct kvm *kvm, bool may_block) @@ -1289,13 +1303,13 @@ void kvm_nested_s2_unmap(struct kvm *kvm, bool may_block) return; for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (kvm_s2_mmu_valid(mmu)) kvm_stage2_unmap_range(mmu, 0, kvm_phys_size(mmu), may_block); } - kvm_invalidate_vncr_ipa(kvm, 0, BIT(kvm->arch.mmu.pgt->ia_bits)); + kvm_invalidate_vncr_ipa_all(kvm); } void kvm_nested_s2_flush(struct kvm *kvm) @@ -1308,7 +1322,7 @@ void kvm_nested_s2_flush(struct kvm *kvm) return; for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (kvm_s2_mmu_valid(mmu)) kvm_stage2_flush_range(mmu, 0, kvm_phys_size(mmu)); @@ -1317,17 +1331,12 @@ void kvm_nested_s2_flush(struct kvm *kvm) void kvm_arch_flush_shadow_all(struct kvm *kvm) { - int i; - - for (i = 0; i < kvm->arch.nested_mmus_size; i++) { - struct kvm_s2_mmu *mmu = &kvm->arch.nested_mmus[i]; + for (int i = 0; i < kvm->arch.nested_mmus_size; i++) { + struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i]; if (!WARN_ON(atomic_read(&mmu->refcnt))) kvm_free_stage2_pgd(mmu); } - kvfree(kvm->arch.nested_mmus); - kvm->arch.nested_mmus = NULL; - kvm->arch.nested_mmus_size = 0; kvm_uninit_stage2_mmu(kvm); } diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c index 44aae52c473d..a2f4e769a428 100644 --- a/arch/arm64/kvm/sys_regs.c +++ b/arch/arm64/kvm/sys_regs.c @@ -926,6 +926,7 @@ static u64 *demux_wb_reg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *rd) struct kvm_guest_debug_arch *dbg = &vcpu->arch.vcpu_debug_state; switch (rd->Op2) { + case 0b001: case 0b100: return &dbg->dbg_bvr[rd->CRm]; case 0b101: diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 9e782a4fea7e..0904ae850c35 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -1658,7 +1658,7 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, unsigned long val) { const struct vgic_its_abi *abi = vgic_its_get_abi(its); - u64 entry_size, table_type; + u64 old, entry_size, table_type; u64 reg, *regptr, clearbits = 0; /* When GITS_CTLR.Enable is 1, we ignore write accesses. */ @@ -1681,7 +1681,9 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, return; } - reg = update_64bit_reg(*regptr, addr & 7, len, val); + old = *regptr; + + reg = update_64bit_reg(old, addr & 7, len, val); reg &= ~GITS_BASER_RO_MASK; reg &= ~clearbits; @@ -1691,7 +1693,8 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, *regptr = reg; - if (!(reg & GITS_BASER_VALID)) { + /* The ITS driver rewrites an unchanged GITS_BASER<n> on resume. */ + if (reg != old) { /* Take the its_lock to prevent a race with a save/restore */ mutex_lock(&its->its_lock); switch (table_type) { @@ -1702,6 +1705,8 @@ static void vgic_mmio_write_its_baser(struct kvm *kvm, vgic_its_free_collection_list(kvm, its); break; } + /* A concurrent injection may have cached a translation. */ + vgic_its_invalidate_cache(its); mutex_unlock(&its->its_lock); } } @@ -2019,18 +2024,22 @@ out: return ret; } -static u32 compute_next_devid_offset(struct list_head *h, +static u32 compute_next_devid_offset(struct vgic_its *its, u64 baser, struct its_device *dev) { - struct its_device *next; - u32 next_offset; + struct its_device *next = dev; - if (list_is_last(&dev->dev_list, h)) - return 0; - next = list_next_entry(dev, dev_list); - next_offset = next->device_id - dev->device_id; + /* + * Point at the next device vgic_its_save_device_tables() saves. It + * sorts device_list first, so the subtraction cannot underflow. + */ + list_for_each_entry_continue(next, &its->device_list, dev_list) { + if (vgic_its_check_id(its, baser, next->device_id, NULL)) + return min_t(u32, next->device_id - dev->device_id, + VITS_DTE_MAX_DEVID_OFFSET); + } - return min_t(u32, next_offset, VITS_DTE_MAX_DEVID_OFFSET); + return 0; } static u32 compute_next_eventid_offset(struct list_head *h, struct its_ite *ite) @@ -2271,17 +2280,18 @@ static int vgic_its_restore_itt(struct vgic_its *its, struct its_device *dev) * vgic_its_save_dte - Save a device table entry at a given GPA * * @its: ITS handle + * @baser: GITS_BASER<dev> the caller is saving against * @dev: ITS device * @ptr: GPA */ -static int vgic_its_save_dte(struct vgic_its *its, struct its_device *dev, - gpa_t ptr) +static int vgic_its_save_dte(struct vgic_its *its, u64 baser, + struct its_device *dev, gpa_t ptr) { u64 val, itt_addr_field; u32 next_offset; itt_addr_field = dev->itt_addr >> 8; - next_offset = compute_next_devid_offset(&its->device_list, dev); + next_offset = compute_next_devid_offset(its, baser, dev); val = (1ULL << KVM_ITS_DTE_VALID_SHIFT | ((u64)next_offset << KVM_ITS_DTE_NEXT_SHIFT) | (itt_addr_field << KVM_ITS_DTE_ITTADDR_SHIFT) | @@ -2380,15 +2390,16 @@ static int vgic_its_save_device_tables(struct vgic_its *its) int ret; gpa_t eaddr; + /* Don't fail a save that userspace must be able to issue. */ if (!vgic_its_check_id(its, baser, dev->device_id, &eaddr)) - return -EINVAL; + continue; ret = vgic_its_save_itt(its, dev); if (ret) return ret; - ret = vgic_its_save_dte(its, dev, eaddr); + ret = vgic_its_save_dte(its, baser, dev, eaddr); if (ret) return ret; } @@ -2541,9 +2552,6 @@ static int vgic_its_save_collection_table(struct vgic_its *its) max_size = GITS_BASER_NR_PAGES(baser) * SZ_64K; list_for_each_entry(collection, &its->collection_list, coll_list) { - if (!vgic_its_check_id(its, baser, collection->collection_id, NULL)) - return -EINVAL; - ret = vgic_its_save_cte(its, collection, gpa); if (ret) return ret; diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index c18e005a41db..c5f55d6161fe 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -600,6 +600,8 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf) * 12 registers are on the stack */ emit(A64_SUB_I(1, A64_SP, A64_FP, 96), ctx); + /* The callback may use its own BPF stack, set up fp for it. */ + ctx->fp_used = true; } /* Stack must be multiples of 16B */ diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c index 40a878b672f5..15a2a153dc87 100644 --- a/arch/mips/net/bpf_jit_comp32.c +++ b/arch/mips/net/bpf_jit_comp32.c @@ -1111,7 +1111,7 @@ static void emit_jmp_i64(struct jit_context *ctx, emit(ctx, xor, tmp, lo(dst), tmp); } if (imm < 0) { /* Compare sign extension */ - emit(ctx, addu, MIPS_R_T9, hi(dst), 1); + emit(ctx, addiu, MIPS_R_T9, hi(dst), 1); emit(ctx, or, tmp, tmp, MIPS_R_T9); } else { /* Compare zero extension */ emit(ctx, or, tmp, tmp, hi(dst)); diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c index fa7e9aa37f49..6681ccac9dd9 100644 --- a/arch/mips/net/bpf_jit_comp64.c +++ b/arch/mips/net/bpf_jit_comp64.c @@ -305,8 +305,7 @@ static void emit_bswap_r64(struct jit_context *ctx, u8 dst, u32 width) case 16: emit_sext(ctx, dst, dst); emit_bswap_r(ctx, dst, width); - if (cpu_has_mips64r2 || cpu_has_mips64r6) - emit_zext(ctx, dst); + emit_zext(ctx, dst); break; } clobber_reg(ctx, dst); diff --git a/arch/parisc/include/asm/compat_ucontext.h b/arch/parisc/include/asm/compat_ucontext.h deleted file mode 100644 index c606f1bc891d..000000000000 --- a/arch/parisc/include/asm/compat_ucontext.h +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _ASM_PARISC_COMPAT_UCONTEXT_H -#define _ASM_PARISC_COMPAT_UCONTEXT_H - -#include <linux/compat.h> - -/* 32-bit ucontext as seen from an 64-bit kernel */ -struct compat_ucontext { - compat_uint_t uc_flags; - compat_uptr_t uc_link; - compat_stack_t uc_stack; /* struct compat_sigaltstack (12 bytes)*/ - /* FIXME: Pad out to get uc_mcontext to start at an 8-byte aligned boundary */ - compat_uint_t pad[1]; - struct compat_sigcontext uc_mcontext; - compat_sigset_t uc_sigmask; /* mask last for extensibility */ -}; - -#endif /* !_ASM_PARISC_COMPAT_UCONTEXT_H */ diff --git a/arch/parisc/include/asm/thread_info.h b/arch/parisc/include/asm/thread_info.h index b283738bb6da..249370706242 100644 --- a/arch/parisc/include/asm/thread_info.h +++ b/arch/parisc/include/asm/thread_info.h @@ -24,7 +24,7 @@ struct thread_info { /* thread information allocation */ -#ifdef CONFIG_IRQSTACKS +#if defined(CONFIG_IRQSTACKS) && !defined(CONFIG_64BIT) #define THREAD_SIZE_ORDER 2 /* PA-RISC requires at least 16k stack */ #else #define THREAD_SIZE_ORDER 3 /* PA-RISC requires at least 32k stack */ diff --git a/arch/parisc/kernel/setup.c b/arch/parisc/kernel/setup.c index d3e17a7a8901..4d3015a411d6 100644 --- a/arch/parisc/kernel/setup.c +++ b/arch/parisc/kernel/setup.c @@ -132,6 +132,18 @@ void __init setup_arch(char **cmdline_p) parisc_cache_init(); paging_init(); + /* + * Parse early parameters before mm_core_init_early() runs. + * Several early_param() handlers only record data that is consumed + * from there - for example hugepages=, hugepagesz=, + * default_hugepagesz=, hugetlb_cma= and hugetlb_free_vmemmap= - so + * the generic parse_early_param() call in start_kernel() is too late + * for them. jump_label_init() must come first, since early param + * handlers may enable or disable static keys. + */ + jump_label_init(); + parse_early_param(); + #ifdef CONFIG_PA11 dma_ops_init(); #endif diff --git a/arch/parisc/kernel/unwind.c b/arch/parisc/kernel/unwind.c index 32103a270a8e..fab9ae22191a 100644 --- a/arch/parisc/kernel/unwind.c +++ b/arch/parisc/kernel/unwind.c @@ -14,6 +14,7 @@ #include <linux/sched.h> #include <linux/slab.h> #include <linux/sort.h> +#include <linux/bsearch.h> #include <linux/sched/task_stack.h> #include <linux/uaccess.h> @@ -49,27 +50,23 @@ static DEFINE_SPINLOCK(unwind_lock); static struct unwind_table kernel_unwind_table __ro_after_init; static LIST_HEAD(unwind_tables); -static inline const struct unwind_table_entry * -find_unwind_entry_in_table(const struct unwind_table *table, unsigned long addr) +static int cmp_unwind_entry(const void *key, const void *elt) { - const struct unwind_table_entry *e = NULL; - unsigned long lo, hi, mid; + unsigned long addr = (unsigned long)key; + const struct unwind_table_entry *e = elt; - lo = 0; - hi = table->length - 1; - - while (lo <= hi) { - mid = (hi - lo) / 2 + lo; - e = &table->table[mid]; - if (addr < e->region_start) - hi = mid - 1; - else if (addr > e->region_end) - lo = mid + 1; - else - return e; - } + if (addr < e->region_start) + return -1; + if (addr > e->region_end) + return 1; + return 0; +} - return NULL; +static inline const struct unwind_table_entry * +find_unwind_entry_in_table(const struct unwind_table *table, unsigned long addr) +{ + return bsearch((void *)addr, table->table, table->length, + sizeof(*table->table), cmp_unwind_entry); } static const struct unwind_table_entry * diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h index 6c823361be86..621f84d1898f 100644 --- a/arch/riscv/include/asm/csr.h +++ b/arch/riscv/include/asm/csr.h @@ -188,12 +188,24 @@ #define HGATP_MODE_SHIFT HGATP32_MODE_SHIFT #endif -/* VSIP & HVIP relation */ +/* + * VSIP & HVIP relation + * + * The bit positions are same between VSIP and HVIP for interrupt + * numbers 13-63, where there's a shift for the SSI, STI and SEI. + */ #define VSIP_TO_HVIP_SHIFT (IRQ_VS_SOFT - IRQ_S_SOFT) -#define VSIP_VALID_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ +#define VSIP_BIAS_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ (_AC(1, UL) << IRQ_S_TIMER) | \ - (_AC(1, UL) << IRQ_S_EXT) | \ - (_AC(1, UL) << IRQ_PMU_OVF)) + (_AC(1, UL) << IRQ_S_EXT)) +#define VSIP_NO_BIAS_MASK (_AC(1, UL) << IRQ_PMU_OVF) +#define VSIP_VALID_MASK (VSIP_BIAS_MASK | VSIP_NO_BIAS_MASK) +#define vsip_to_hvip(_vsip) ((((_vsip) & VSIP_BIAS_MASK) << \ + VSIP_TO_HVIP_SHIFT) | \ + ((_vsip) & VSIP_NO_BIAS_MASK)) +#define hvip_to_vsip(_hvip) ((((_hvip) >> VSIP_TO_HVIP_SHIFT) & \ + VSIP_BIAS_MASK) | \ + ((_hvip) & VSIP_NO_BIAS_MASK)) /* AIA CSR bits */ #define TOPI_IID_SHIFT 16 diff --git a/arch/riscv/kvm/aia_imsic.c b/arch/riscv/kvm/aia_imsic.c index c1af23e79ae0..f17dc559a709 100644 --- a/arch/riscv/kvm/aia_imsic.c +++ b/arch/riscv/kvm/aia_imsic.c @@ -965,9 +965,14 @@ int kvm_riscv_aia_imsic_rw_attr(struct kvm *kvm, unsigned long type, if (!vcpu) return -ENODEV; + if (mutex_lock_killable(&vcpu->mutex)) + return -EINTR; + imsic = vcpu->arch.aia_context.imsic_state; - if (!imsic) - return -ENODEV; + if (!imsic) { + rc = -ENODEV; + goto out_unlock; + } isel = KVM_DEV_RISCV_AIA_IMSIC_GET_ISEL(type); read_lock_irqsave(&imsic->vsfile_lock, flags); @@ -991,6 +996,8 @@ int kvm_riscv_aia_imsic_rw_attr(struct kvm *kvm, unsigned long type, rc = imsic_vsfile_rw(vsfile_hgei, vsfile_cpu, imsic->nr_eix, isel, write, val); +out_unlock: + mutex_unlock(&vcpu->mutex); return rc; } diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c index 6035b5ec9503..3e955d808743 100644 --- a/arch/riscv/kvm/mmu.c +++ b/arch/riscv/kvm/mmu.c @@ -627,6 +627,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, int ret; kvm_pfn_t hfn; bool is_hugetlb; + bool unused = false; bool writable; unsigned int vma_pageshift; gfn_t gfn = gpa >> PAGE_SHIFT; @@ -707,6 +708,8 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, vma_pageshift, current); return 0; } + if (is_sigpending_pfn(hfn)) + return -EINTR; if (is_error_noslot_pfn(hfn)) return -EFAULT; @@ -719,8 +722,10 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, write_lock(&kvm->mmu_lock); - if (mmu_invalidate_retry(kvm, mmu_seq)) + if (mmu_invalidate_retry(kvm, mmu_seq)) { + unused = true; goto out_unlock; + } /* * Check if we are backed by a THP and thus use block mapping if @@ -743,7 +748,8 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, kvm_err("Failed to map in G-stage\n"); out_unlock: - kvm_release_faultin_page(kvm, page, ret && ret != -EEXIST, writable); + kvm_release_faultin_page(kvm, page, + unused || (ret && ret != -EEXIST), writable); write_unlock(&kvm->mmu_lock); return ret; } diff --git a/arch/riscv/kvm/nacl.c b/arch/riscv/kvm/nacl.c index 9aff03c4f667..a5cda9a65156 100644 --- a/arch/riscv/kvm/nacl.c +++ b/arch/riscv/kvm/nacl.c @@ -42,12 +42,24 @@ again: } } - entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_CONFIG(i); - *entp = cpu_to_lelong(control); + /* + * Per SBI v3.0 section 15.1.2, the Page_Number and Page_Count + * words must be updated before the Config word with its Pending + * bit set. WRITE_ONCE() stops the compiler from reordering the + * stores and smp_wmb() makes the parameter words globally + * visible to the SBI implementation (or NACL hardware) before + * the Pending bit is set. + */ entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_PNUM(i); - *entp = cpu_to_lelong(page_num); + WRITE_ONCE(*entp, cpu_to_lelong(page_num)); entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_PCOUNT(i); - *entp = cpu_to_lelong(page_count); + WRITE_ONCE(*entp, cpu_to_lelong(page_count)); + + /* Ensure the parameter words are visible before the Pending bit */ + smp_wmb(); + + entp = shmem + SBI_NACL_SHMEM_HFENCE_ENTRY_CONFIG(i); + WRITE_ONCE(*entp, cpu_to_lelong(control)); } int kvm_riscv_nacl_enable(void) diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c index e062ca19f9d8..7f1636c44d41 100644 --- a/arch/riscv/kvm/vcpu.c +++ b/arch/riscv/kvm/vcpu.c @@ -491,8 +491,7 @@ bool kvm_riscv_vcpu_has_interrupts(struct kvm_vcpu *vcpu, u64 mask) bool ret; raw_spin_lock_irqsave(&vcpu->arch.irqs_pending_lock, flags); - ie = ((vcpu->arch.guest_csr.vsie & VSIP_VALID_MASK) - << VSIP_TO_HVIP_SHIFT) & (unsigned long)mask; + ie = vsip_to_hvip(vcpu->arch.guest_csr.vsie) & (unsigned long)mask; ie |= vcpu->arch.guest_csr.vsie & ~IRQ_LOCAL_MASK & (unsigned long)mask; ret = vcpu->arch.irqs_pending[0] & ie; diff --git a/arch/riscv/kvm/vcpu_exit.c b/arch/riscv/kvm/vcpu_exit.c index 88e0c369b354..8d36eb8abce1 100644 --- a/arch/riscv/kvm/vcpu_exit.c +++ b/arch/riscv/kvm/vcpu_exit.c @@ -283,7 +283,7 @@ int kvm_riscv_vcpu_exit(struct kvm_vcpu *vcpu, struct kvm_run *run, } /* Print details in-case of error */ - if (ret < 0) { + if (ret < 0 && ret != -EINTR) { kvm_err("VCPU exit error %d\n", ret); kvm_err("SEPC=0x%lx SSTATUS=0x%lx HSTATUS=0x%lx\n", vcpu->arch.guest_context.sepc, diff --git a/arch/riscv/kvm/vcpu_onereg.c b/arch/riscv/kvm/vcpu_onereg.c index 841f2cf87168..f16e25098af1 100644 --- a/arch/riscv/kvm/vcpu_onereg.c +++ b/arch/riscv/kvm/vcpu_onereg.c @@ -272,7 +272,7 @@ static int kvm_riscv_vcpu_general_get_csr(struct kvm_vcpu *vcpu, if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) { kvm_riscv_vcpu_flush_interrupts(vcpu); - *out_val = (csr->hvip >> VSIP_TO_HVIP_SHIFT) & VSIP_VALID_MASK; + *out_val = hvip_to_vsip(csr->hvip); *out_val |= csr->hvip & ~IRQ_LOCAL_MASK; } else *out_val = ((unsigned long *)csr)[reg_num]; @@ -293,10 +293,8 @@ static int kvm_riscv_vcpu_general_set_csr(struct kvm_vcpu *vcpu, reg_num = array_index_nospec(reg_num, regs_max); - if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) { - reg_val &= VSIP_VALID_MASK; - reg_val <<= VSIP_TO_HVIP_SHIFT; - } + if (reg_num == KVM_REG_RISCV_CSR_REG(sip)) + reg_val = vsip_to_hvip(reg_val); ((unsigned long *)csr)[reg_num] = reg_val; diff --git a/arch/riscv/kvm/vcpu_pmu.c b/arch/riscv/kvm/vcpu_pmu.c index 6ff741ee7803..e095d1ff439f 100644 --- a/arch/riscv/kvm/vcpu_pmu.c +++ b/arch/riscv/kvm/vcpu_pmu.c @@ -270,12 +270,13 @@ static int pmu_ctr_read(struct kvm_vcpu *vcpu, unsigned long cidx, return -EINVAL; pmc->counter_val = kvpmu->fw_event[fevent_code].value; + *out_val = pmc->counter_val; } else if (pmc->perf_event) { - pmc->counter_val += perf_event_read_value(pmc->perf_event, &enabled, &running); + *out_val = pmc->counter_val + + perf_event_read_value(pmc->perf_event, &enabled, &running); } else { return -EINVAL; } - *out_val = pmc->counter_val; return 0; } @@ -454,6 +455,14 @@ int kvm_riscv_vcpu_pmu_snapshot_set_shmem(struct kvm_vcpu *vcpu, unsigned long s } } + /* + * Clear any previously installed snapshot area to avoid leaking + * the old sdata and to keep sdata/snapshot_addr consistent if + * the re-install fails below. + */ + if (kvpmu->snapshot_addr != INVALID_GPA) + kvm_pmu_clear_snapshot_area(vcpu); + kvpmu->sdata = kzalloc(snapshot_area_size, GFP_ATOMIC | __GFP_ACCOUNT); if (!kvpmu->sdata) { sbiret = SBI_ERR_FAILURE; @@ -645,7 +654,6 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, { struct kvm_pmu *kvpmu = vcpu_to_pmu(vcpu); int i, pmc_index, sbiret = 0; - u64 enabled, running; struct kvm_pmc *pmc; int fevent_code; bool snap_flag_set = flags & SBI_PMU_STOP_FLAG_TAKE_SNAPSHOT; @@ -675,14 +683,19 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, goto out; } - if (!kvpmu->fw_event[fevent_code].started) + if (!kvpmu->fw_event[fevent_code].started) { sbiret = SBI_ERR_ALREADY_STOPPED; - - kvpmu->fw_event[fevent_code].started = false; + } else { + kvpmu->fw_event[fevent_code].started = false; + pmc->counter_val = kvpmu->fw_event[fevent_code].value; + } } else if (pmc->perf_event) { if (pmc->started) { - /* Stop counting the counter */ - perf_event_disable(pmc->perf_event); + /* + * Stop the counter and fold the live count into counter_val. + * Reset the event value to avoid redundant accumulation. + */ + pmc->counter_val += perf_event_pause(pmc->perf_event, true); pmc->started = false; } else { sbiret = SBI_ERR_ALREADY_STOPPED; @@ -696,11 +709,6 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } if (snap_flag_set && !sbiret) { - if (pmc->cinfo.type == SBI_PMU_CTR_TYPE_FW) - pmc->counter_val = kvpmu->fw_event[fevent_code].value; - else if (pmc->perf_event) - pmc->counter_val += perf_event_read_value(pmc->perf_event, - &enabled, &running); /* * The counter and overflow indices in the snapshot region are w.r.to * cbase. Modify the set bit in the counter mask instead of the pmc_index @@ -727,9 +735,10 @@ int kvm_riscv_vcpu_pmu_ctr_stop(struct kvm_vcpu *vcpu, unsigned long ctr_base, } } - if (shmem_needs_update) - kvm_vcpu_write_guest(vcpu, kvpmu->snapshot_addr, kvpmu->sdata, - sizeof(struct riscv_pmu_snapshot_data)); + if (shmem_needs_update && + kvm_vcpu_write_guest(vcpu, kvpmu->snapshot_addr, kvpmu->sdata, + sizeof(struct riscv_pmu_snapshot_data))) + sbiret = SBI_ERR_FAILURE; out: retdata->err_val = sbiret; diff --git a/arch/riscv/kvm/vcpu_sbi_hsm.c b/arch/riscv/kvm/vcpu_sbi_hsm.c index f26207f84bab..06a15629c26b 100644 --- a/arch/riscv/kvm/vcpu_sbi_hsm.c +++ b/arch/riscv/kvm/vcpu_sbi_hsm.c @@ -95,9 +95,9 @@ static int kvm_sbi_ext_hsm_handler(struct kvm_vcpu *vcpu, struct kvm_run *run, ret = kvm_sbi_hsm_vcpu_get_status(vcpu); if (ret >= 0) { retdata->out_val = ret; - retdata->err_val = 0; + ret = 0; } - return 0; + break; case SBI_EXT_HSM_HART_SUSPEND: switch (lower_32_bits(cp->a0)) { case SBI_HSM_SUSPEND_RET_DEFAULT: diff --git a/arch/riscv/kvm/vcpu_timer.c b/arch/riscv/kvm/vcpu_timer.c index ae53133c7ab0..a2cd277a4059 100644 --- a/arch/riscv/kvm/vcpu_timer.c +++ b/arch/riscv/kvm/vcpu_timer.c @@ -61,10 +61,13 @@ static enum hrtimer_restart kvm_riscv_vcpu_hrtimer_expired(struct hrtimer *h) static int kvm_riscv_vcpu_timer_cancel(struct kvm_vcpu_timer *t) { - if (!t->init_done || !t->next_set) + if (!t->init_done) return -EINVAL; hrtimer_cancel(&t->hrt); + + if (!t->next_set) + return -EINVAL; t->next_set = false; return 0; diff --git a/arch/s390/crypto/hmac_s390.c b/arch/s390/crypto/hmac_s390.c index f8cd09f341d4..445fa7bbd958 100644 --- a/arch/s390/crypto/hmac_s390.c +++ b/arch/s390/crypto/hmac_s390.c @@ -150,7 +150,10 @@ static int hash_data(const u8 *in, unsigned int inlen, #undef PARAM_INIT - cpacf_klmd(func, ¶m, in, inlen); + if (final) + cpacf_klmd(func, ¶m, in, inlen); + else + cpacf_kimd(func, ¶m, in, inlen); memcpy(digest, ¶m, digestsize); diff --git a/arch/s390/include/asm/debug.h b/arch/s390/include/asm/debug.h index 39d484c59774..ad438d6352c8 100644 --- a/arch/s390/include/asm/debug.h +++ b/arch/s390/include/asm/debug.h @@ -460,7 +460,11 @@ static int VNAME(var, active_entries)[EARLY_AREAS] __initdata #define __REGISTER_STATIC_DEBUG_INFO(var, name, pages, areas, view) \ static int __init VNAME(var, reg)(void) \ { \ - debug_register_static(&var, (pages), (areas)); \ + int rc; \ + \ + rc = debug_register_static(&var, (pages), (areas)); \ + if (rc) \ + return rc; \ debug_register_view(&var, (view)); \ return 0; \ } \ @@ -493,7 +497,7 @@ static debug_info_t __refdata var = \ static debug_info_t __used __section(".s390dbf_info") *VNAME(var, info) = &var; \ __REGISTER_STATIC_DEBUG_INFO(var, name, pages, nr_areas, view) -void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas); +int debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas); #endif /* MODULE */ diff --git a/arch/s390/kernel/debug.c b/arch/s390/kernel/debug.c index b5bf8284dbfc..3b68f5c58333 100644 --- a/arch/s390/kernel/debug.c +++ b/arch/s390/kernel/debug.c @@ -434,7 +434,8 @@ static debug_info_t *debug_info_copy(debug_info_t *in, int mode) debug_info_free(rc); } while (1); - if (mode == NO_AREAS) + /* debug_register_static() failure leaves areas NULL, bounds intact */ + if (mode == NO_AREAS || !in->areas) goto out; for (i = 0; i < in->nr_areas; i++) { @@ -823,6 +824,9 @@ ssize_t debug_dump(debug_info_t *id, struct debug_view *view, file_private_info_t *p_info; size_t size, offset = 0; + if (!id) + return -EINVAL; + /* Need space for '\0' byte */ if (buf_size < 1) return 0; @@ -950,8 +954,12 @@ EXPORT_SYMBOL(debug_register); * * Note: This function is called automatically via an initcall generated by * DEFINE_STATIC_DEBUG_INFO. + * + * Return: + * - 0 on success + * - negative error code on failure */ -void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) +int debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) { unsigned long flags; debug_info_t *copy; @@ -959,7 +967,7 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) if (!initialized) { pr_err("Tried to register debug feature %s too early\n", id->name); - return; + return -EINVAL; } debug_get_param(id->name, &id->level, &pages_per_area, false); @@ -975,7 +983,7 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) id->active_entries = NULL; raw_spin_unlock_irqrestore(&id->lock, flags); - return; + return -ENOMEM; } /* Replace static trace area with dynamic copy. */ @@ -993,6 +1001,8 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas) mutex_lock(&debug_mutex); _debug_register(id); mutex_unlock(&debug_mutex); + + return 0; } /* Remove debugfs entries. */ diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c index dc14ebc0105b..8ea9dd7704ff 100644 --- a/arch/s390/kernel/uv.c +++ b/arch/s390/kernel/uv.c @@ -825,7 +825,7 @@ static int find_secret_in_page(const u8 secret_id[UV_SECRET_ID_LEN], { u16 i; - for (i = 0; i < list->total_num_secrets; i++) { + for (i = 0; i < list->num_secr_stored; i++) { if (memcmp(secret_id, list->secrets[i].id, UV_SECRET_ID_LEN) == 0) { *secret = list->secrets[i].hdr; return 0; @@ -846,11 +846,14 @@ int uv_find_secret(const u8 secret_id[UV_SECRET_ID_LEN], struct uv_secret_list *list, struct uv_secret_list_item_hdr *secret) { - u16 start_idx = 0; + u16 start_idx; u16 list_rc; int ret; + list->next_secret_idx = 0; + do { + start_idx = list->next_secret_idx; uv_list_secrets(list, start_idx, &list_rc, NULL); if (list_rc != UVC_RC_EXECUTED && list_rc != UVC_RC_MORE_DATA) { if (list_rc == UVC_RC_INV_CMD) @@ -861,7 +864,6 @@ int uv_find_secret(const u8 secret_id[UV_SECRET_ID_LEN], ret = find_secret_in_page(secret_id, list, secret); if (ret == 0) return ret; - start_idx = list->next_secret_idx; } while (list_rc == UVC_RC_MORE_DATA && start_idx < list->next_secret_idx); return -ENOENT; diff --git a/arch/s390/kvm/gmap/dat.c b/arch/s390/kvm/gmap/dat.c index 24547e39fab2..ff80d02c9f56 100644 --- a/arch/s390/kvm/gmap/dat.c +++ b/arch/s390/kvm/gmap/dat.c @@ -621,17 +621,20 @@ int dat_get_storage_key(union asce asce, gfn_t gfn, union skey *skey) union pte *ptep; int rc; +again: skey->skey = 0; rc = dat_entry_walk(NULL, gfn, asce, DAT_WALK_ANY, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; if (!ptep) { - union crste crste; + union crste crste = READ_ONCE(*crstep); - crste = READ_ONCE(*crstep); - if (!crste.h.fc || !crste.s.fc1.pr) + if (!crste_leaf(crste) && !crste.h.i) + goto again; + if (!crste.s.fc1.pr) return 0; + skey->skey = page_get_storage_key(large_crste_to_phys(crste, gfn)); return 0; } @@ -662,13 +665,20 @@ int dat_set_storage_key(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t gf union pte *ptep; int rc; +again: rc = dat_entry_walk(mc, gfn, asce, DAT_WALK_LEAF_ALLOC, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; if (!ptep) { - page_set_storage_key(large_crste_to_phys(*crstep, gfn), skey.skey, !nq); + union crste crste = READ_ONCE(*crstep); + + /* A large page has been split concurrently, try again */ + if (!crste_leaf(crste)) + goto again; + + page_set_storage_key(large_crste_to_phys(crste, gfn), skey.skey, !nq); return 0; } @@ -718,15 +728,22 @@ int dat_cond_set_storage_key(struct kvm_s390_mmu_cache *mmc, union asce asce, gf union pte *ptep; int rc; +again: rc = dat_entry_walk(mmc, gfn, asce, DAT_WALK_LEAF_ALLOC, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; if (!ptep) { + union crste crste = READ_ONCE(*crstep); + + /* A large page has been split concurrently, try again */ + if (!crste_leaf(crste)) + goto again; if (!oldkey) oldkey = &prev; - return page_cond_set_storage_key(large_crste_to_phys(*crstep, gfn), skey, oldkey, + + return page_cond_set_storage_key(large_crste_to_phys(crste, gfn), skey, oldkey, nq, mr, mc); } @@ -768,7 +785,7 @@ int dat_reset_reference_bit(union asce asce, gfn_t gfn, union skey *skey) int rc; skey->skey = 0; - +again: rc = dat_entry_walk(NULL, gfn, asce, DAT_WALK_ANY, TABLE_TYPE_PAGE_TABLE, &crstep, &ptep); if (rc) return rc; @@ -776,9 +793,12 @@ int dat_reset_reference_bit(union asce asce, gfn_t gfn, union skey *skey) if (!ptep) { union crste crste = READ_ONCE(*crstep); - if (!crste.h.fc || !crste.s.fc1.pr) + /* A large page has been split concurrently, try again */ + if (!crste_leaf(crste) && !crste.h.i) + goto again; + if (!crste.s.fc1.pr) return 0; - skey->skey = page_reset_referenced(large_crste_to_phys(*crstep, gfn)) << 1; + skey->skey = page_reset_referenced(large_crste_to_phys(crste, gfn)) << 1; return 0; } old = pgste_get_lock(ptep); @@ -846,19 +866,12 @@ long dat_reset_skeys(union asce asce, gfn_t start) } #endif /* KVM_S390_MANAGES_S390_GUEST */ -struct slot_priv { - unsigned long token; - struct kvm_s390_mmu_cache *mc; -}; - static long _dat_slot_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_walk *walk) { - struct slot_priv *p = walk->priv; - union crste dummy = { .val = p->token }; union pte new_pte, pte = READ_ONCE(*ptep); union pgste pgste; - new_pte = _PTE_TOK(dummy.tok.type, dummy.tok.par); + new_pte = walk->priv ? _PTE_EMPTY : _PTE_TOK(_DAT_TOKEN_PIC, PGM_ADDRESSING); /* Table entry already in the desired state. */ if (pte.val == new_pte.val) @@ -875,10 +888,9 @@ static long _dat_slot_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_wal static long _dat_slot_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct dat_walk *walk) { union crste new_crste, crste = READ_ONCE(*crstep); - struct slot_priv *p = walk->priv; + struct kvm_s390_mmu_cache *mc = walk->priv; - new_crste.val = p->token; - new_crste.h.tt = crste.h.tt; + new_crste = mc ? _CRSTE_EMPTY(crste.h.tt) : _CRSTE_HOLE(crste.h.tt); /* Table entry already in the desired state. */ if (crste.val == new_crste.val) @@ -902,7 +914,10 @@ static long _dat_slot_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct d if (!crste.h.fc && !crste.h.i) return 0; /* Split (install a lower level table), and handle things there. */ - return dat_split_crste(p->mc, crstep, gfn, walk->asce, false); + if (mc) + return dat_split_crste(mc, crstep, gfn, walk->asce, false); + /* A large page should never cross memslots boundaries */ + return -EINVAL; } static const struct dat_walk_ops dat_slot_ops = { @@ -910,16 +925,10 @@ static const struct dat_walk_ops dat_slot_ops = { .crste_ops = { _dat_slot_crste, _dat_slot_crste, _dat_slot_crste, _dat_slot_crste, }, }; -int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end, - u16 type, u16 param) +int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end) { - struct slot_priv priv = { - .token = _CRSTE_TOK(0, type, param).val, - .mc = mc, - }; - return _dat_walk_gfn_range(start, end, asce, &dat_slot_ops, - DAT_WALK_IGN_HOLES | DAT_WALK_ANY, &priv); + DAT_WALK_IGN_HOLES | DAT_WALK_ANY, mc); } static void pgste_set_unlock_multiple(union pte *first, int n, union pgste *pgstes) diff --git a/arch/s390/kvm/gmap/dat.h b/arch/s390/kvm/gmap/dat.h index e452c141b841..90389d47ba4e 100644 --- a/arch/s390/kvm/gmap/dat.h +++ b/arch/s390/kvm/gmap/dat.h @@ -547,8 +547,7 @@ long dat_reset_skeys(union asce asce, gfn_t start); unsigned long dat_get_ptval(struct page_table *table, struct ptval_param param); void dat_set_ptval(struct page_table *table, struct ptval_param param, unsigned long val); -int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end, - u16 type, u16 param); +int dat_set_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, gfn_t end); #if KVM_S390_MANAGES_S390_GUEST int dat_set_prefix_notif_bit(union asce asce, gfn_t gfn); @@ -973,16 +972,15 @@ static inline int get_level(union crste *crstep, union pte *ptep) return ptep ? TABLE_TYPE_PAGE_TABLE : crstep->h.tt; } -static inline int dat_delete_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, - unsigned long npages) +static inline int dat_delete_slot(union asce asce, gfn_t start, unsigned long npages) { - return dat_set_slot(mc, asce, start, start + npages, _DAT_TOKEN_PIC, PGM_ADDRESSING); + return dat_set_slot(NULL, asce, start, start + npages); } static inline int dat_create_slot(struct kvm_s390_mmu_cache *mc, union asce asce, gfn_t start, unsigned long npages) { - return dat_set_slot(mc, asce, start, start + npages, _DAT_TOKEN_NONE, 0); + return dat_set_slot(mc, asce, start, start + npages); } static inline bool crste_is_ucas(union crste crste) diff --git a/arch/s390/kvm/gmap/gmap.c b/arch/s390/kvm/gmap/gmap.c index 4968330e9553..3f3fa864cc36 100644 --- a/arch/s390/kvm/gmap/gmap.c +++ b/arch/s390/kvm/gmap/gmap.c @@ -994,11 +994,13 @@ static long _destroy_pages_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct da static long _destroy_pages_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct dat_walk *walk) { phys_addr_t origin, cur, end; + union crste crste; - if (!crstep->h.fc || !crstep->s.fc1.pr) + crste = READ_ONCE(*crstep); + if (!crste.h.fc || !crste.s.fc1.pr) return 0; - origin = crste_origin_large(*crstep); + origin = crste_origin_large(crste); cur = ((max(gfn, walk->start) - gfn) << PAGE_SHIFT) + origin; end = ((min(next, walk->end) - gfn) << PAGE_SHIFT) + origin; for ( ; cur < end; cur += PAGE_SIZE) diff --git a/arch/s390/kvm/gmap/kvm_mmu.c b/arch/s390/kvm/gmap/kvm_mmu.c index b08b8229bb6f..c2ffb5e59ec6 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.c +++ b/arch/s390/kvm/gmap/kvm_mmu.c @@ -47,6 +47,9 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *new, enum kvm_mr_change change) { + struct kvm_s390_mmu_cache *mc __free(kvm_s390_mmu_cache) = NULL; + int rc = 0; + if (kvm_is_ucontrol(kvm) && new && new->id < KVM_USER_MEM_SLOTS) return -EINVAL; @@ -61,6 +64,10 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, * and munmap() stuff in this slot after doing this call at any * time. */ + if (change != KVM_MR_MOVE && change != KVM_MR_CREATE) { + WARN(1, "Unknown KVM MR CHANGE: %d\n", change); + return -EINVAL; + } if (new->userspace_addr & ~PAGE_MASK) return -EINVAL; if ((new->base_gfn + new->npages) * PAGE_SIZE > kvm->arch.mem_limit) @@ -69,65 +76,41 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, return -EINVAL; } - if (!kvm_s390_is_migration_mode(kvm)) - return 0; - - /* - * Turn off migration mode when: - * - userspace creates a new memslot with dirty logging off, - * - userspace modifies an existing memslot (MOVE or FLAGS_ONLY) and - * dirty logging is turned off. - * Migration mode expects dirty page logging being enabled to store - * its dirty bitmap. - */ - if (change != KVM_MR_DELETE && - !(new->flags & KVM_MEM_LOG_DIRTY_PAGES)) - WARN(kvm_s390_vm_stop_migration(kvm), - "Failed to stop migration mode"); - - return 0; -} - -void s390_kvm_mmu_commit_memory_region(struct kvm *kvm, - struct kvm_memory_slot *old, - const struct kvm_memory_slot *new, - enum kvm_mr_change change) -{ - struct kvm_s390_mmu_cache *mc __free(kvm_s390_mmu_cache) = NULL; - int rc = 0; - - guard(mutex)(&kvm->slots_arch_lock); + if (kvm->arch.migration_mode) { + /* + * Turn off migration mode when: + * - userspace creates a new memslot with dirty logging off, + * - userspace modifies an existing memslot (MOVE or FLAGS_ONLY) + * and dirty logging is turned off. + * Migration mode expects dirty page logging being enabled to + * store its dirty bitmap. + */ + if (change != KVM_MR_DELETE && + !(new->flags & KVM_MEM_LOG_DIRTY_PAGES)) + WARN(kvm_s390_vm_stop_migration(kvm), + "Failed to stop migration mode"); + } if (change == KVM_MR_FLAGS_ONLY) - return; - - mc = kvm_s390_new_mmu_cache(); - if (!mc) { - rc = -ENOMEM; - goto out; + return 0; + if (change != KVM_MR_DELETE) { + /* Enough capacity to add a new memslot */ + mc = kvm_s390_new_mmu_cache(); + if (!mc) + return -ENOMEM; } - scoped_guard(write_lock, &kvm->mmu_lock) { kvm_s390_update_cmma_dirty(kvm, old); - switch (change) { - case KVM_MR_DELETE: - rc = dat_delete_slot(mc, kvm->arch.gmap->asce, old->base_gfn, old->npages); - break; - case KVM_MR_MOVE: - rc = dat_delete_slot(mc, kvm->arch.gmap->asce, old->base_gfn, old->npages); - if (rc) - break; - fallthrough; - case KVM_MR_CREATE: + if (change == KVM_MR_DELETE || change == KVM_MR_MOVE) + rc = dat_delete_slot(kvm->arch.gmap->asce, old->base_gfn, old->npages); + if (!rc && (change == KVM_MR_MOVE || change == KVM_MR_CREATE)) rc = dat_create_slot(mc, kvm->arch.gmap->asce, new->base_gfn, new->npages); - break; - case KVM_MR_FLAGS_ONLY: - break; - default: - WARN(1, "Unknown KVM MR CHANGE: %d\n", change); - } } -out: - if (rc) - pr_warn("failed to commit memory region\n"); + /* + * Can only be triggered if dat_{create,delete}_slot() found an + * internal inconsistency or if the mmu cache ran out of memory; + * both should be impossible. + */ + KVM_BUG_ON(rc, kvm); + return rc; } diff --git a/arch/s390/kvm/gmap/kvm_mmu.h b/arch/s390/kvm/gmap/kvm_mmu.h index cdbd390bd33c..43cde61bae03 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.h +++ b/arch/s390/kvm/gmap/kvm_mmu.h @@ -10,9 +10,5 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, const struct kvm_memory_slot *old, struct kvm_memory_slot *new, enum kvm_mr_change change); -void s390_kvm_mmu_commit_memory_region(struct kvm *kvm, - struct kvm_memory_slot *old, - const struct kvm_memory_slot *new, - enum kvm_mr_change change); #endif /* ARCH_KVM_GMAP_KVM_MMU_H */ diff --git a/arch/s390/kvm/s390/gaccess.c b/arch/s390/kvm/s390/gaccess.c index e5c064f263df..405345ccc4f1 100644 --- a/arch/s390/kvm/s390/gaccess.c +++ b/arch/s390/kvm/s390/gaccess.c @@ -1589,12 +1589,25 @@ static inline int ___gaccess_shadow_fault(struct kvm_vcpu *vcpu, struct gmap *sg parent = READ_ONCE(sg->parent); if (!parent) return -EAGAIN; +retry: scoped_guard(spinlock, &parent->children_lock) { if (READ_ONCE(sg->parent) != parent) return -EAGAIN; sg->invalidated = false; rc = _gaccess_do_shadow(vcpu->arch.mc, sg, saddr, walk); } + if (rc == -ENOENT) { + struct kvm_memory_slot *slot; + struct guest_fault *entries; + + entries = get_entries(walk); + slot = kvm_vcpu_gfn_to_memslot(vcpu, entries[LEVEL_MEM].gfn); + if (!slot) + return PGM_ADDRESSING; + rc = gmap_link(vcpu->arch.mc, parent, entries + LEVEL_MEM, slot); + if (!rc) + goto retry; + } if (!rc) kvm_s390_release_faultin_array(vcpu->kvm, walk->raw_entries, false); return rc; diff --git a/arch/s390/kvm/s390/interrupt.c b/arch/s390/kvm/s390/interrupt.c index 0381ae981703..f892f4307883 100644 --- a/arch/s390/kvm/s390/interrupt.c +++ b/arch/s390/kvm/s390/interrupt.c @@ -2984,61 +2984,58 @@ static int adapter_indicators_set(struct kvm *kvm, struct s390_io_adapter *adapter, struct kvm_s390_adapter_int *adapter_int) { - unsigned long bit; - int summary_set, idx; struct s390_map_info *ind_info, *summary_info; - void *map; struct page *ind_page, *summary_page; - unsigned long flags; + unsigned long bit; + int summary_set; + void *map; ind_page = NULL; - spin_lock_irqsave(&adapter->maps_lock, flags); - ind_info = get_map_info(adapter, adapter_int->ind_addr); + scoped_guard(spinlock_irqsave, &adapter->maps_lock) { + ind_info = get_map_info(adapter, adapter_int->ind_addr); + if (ind_info) { + map = page_address(ind_info->page); + bit = get_ind_bit(ind_info->addr, adapter_int->ind_offset, adapter->swap); + set_bit(bit, map); + } + } if (!ind_info) { - spin_unlock_irqrestore(&adapter->maps_lock, flags); ind_page = pin_map_page(kvm, adapter_int->ind_addr, 0); if (!ind_page) return -1; - idx = srcu_read_lock(&kvm->srcu); map = page_address(ind_page); bit = get_ind_bit(adapter_int->ind_addr, adapter_int->ind_offset, adapter->swap); set_bit(bit, map); - mark_page_dirty(kvm, adapter_int->ind_gaddr >> PAGE_SHIFT); set_page_dirty_lock(ind_page); - srcu_read_unlock(&kvm->srcu, idx); unpin_user_page(ind_page); - } else { - map = page_address(ind_info->page); - bit = get_ind_bit(ind_info->addr, adapter_int->ind_offset, adapter->swap); - set_bit(bit, map); - spin_unlock_irqrestore(&adapter->maps_lock, flags); } + scoped_guard(srcu, &kvm->srcu) + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->ind_gaddr)); - spin_lock_irqsave(&adapter->maps_lock, flags); - summary_info = get_map_info(adapter, adapter_int->summary_addr); + scoped_guard(spinlock_irqsave, &adapter->maps_lock) { + summary_info = get_map_info(adapter, adapter_int->summary_addr); + if (summary_info) { + map = page_address(summary_info->page); + bit = get_ind_bit(summary_info->addr, adapter_int->summary_offset, + adapter->swap); + summary_set = test_and_set_bit(bit, map); + } + } if (!summary_info) { - spin_unlock_irqrestore(&adapter->maps_lock, flags); summary_page = pin_map_page(kvm, adapter_int->summary_addr, 0); if (!summary_page) return -1; - idx = srcu_read_lock(&kvm->srcu); map = page_address(summary_page); bit = get_ind_bit(adapter_int->summary_addr, adapter_int->summary_offset, adapter->swap); summary_set = test_and_set_bit(bit, map); - mark_page_dirty(kvm, adapter_int->summary_gaddr >> PAGE_SHIFT); set_page_dirty_lock(summary_page); - srcu_read_unlock(&kvm->srcu, idx); unpin_user_page(summary_page); - } else { - map = page_address(summary_info->page); - bit = get_ind_bit(summary_info->addr, adapter_int->summary_offset, - adapter->swap); - summary_set = test_and_set_bit(bit, map); - spin_unlock_irqrestore(&adapter->maps_lock, flags); } + scoped_guard(srcu, &kvm->srcu) + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->summary_gaddr)); return summary_set ? 0 : 1; } @@ -3048,26 +3045,29 @@ static int adapter_indicators_set_fast(struct kvm *kvm, struct kvm_s390_adapter_int *adapter_int, int setbit) { + struct s390_map_info *ind_info, *summary_info; unsigned long bit; int summary_set; - struct s390_map_info *ind_info, *summary_info; void *map; - spin_lock(&adapter->maps_lock); + guard(srcu)(&kvm->srcu); + guard(spinlock)(&adapter->maps_lock); + ind_info = get_map_info(adapter, adapter_int->ind_addr); - if (!ind_info) { - spin_unlock(&adapter->maps_lock); + if (!ind_info) return -EWOULDBLOCK; - } + map = page_address(ind_info->page); bit = get_ind_bit(ind_info->addr, adapter_int->ind_offset, adapter->swap); - if (setbit) + if (setbit) { set_bit(bit, map); + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->ind_gaddr)); + } + summary_info = get_map_info(adapter, adapter_int->summary_addr); - if (!summary_info) { - spin_unlock(&adapter->maps_lock); + if (!summary_info) return -EWOULDBLOCK; - } + map = page_address(summary_info->page); bit = get_ind_bit(summary_info->addr, adapter_int->summary_offset, adapter->swap); @@ -3077,7 +3077,8 @@ static int adapter_indicators_set_fast(struct kvm *kvm, summary_set = test_and_set_bit(bit, map); else summary_set = test_and_clear_bit(bit, map); - spin_unlock(&adapter->maps_lock); + mark_page_dirty(kvm, gpa_to_gfn(adapter_int->summary_gaddr)); + return summary_set ? 0 : 1; } @@ -3228,9 +3229,9 @@ int kvm_s390_set_irq_state(struct kvm_vcpu *vcpu, void __user *irqstate, int len break; } } - if (storestatus) { - n = kvm_s390_store_status_unloaded(vcpu, KVM_S390_STORE_STATUS_NOADDR); + scoped_guard(srcu, &vcpu->kvm->srcu) + n = kvm_s390_store_status_unloaded(vcpu, KVM_S390_STORE_STATUS_NOADDR); return r ? r : n; } diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index 5c73f43782a7..efd77b9cd36c 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -5766,7 +5766,7 @@ static long cmma_d_count_pte(union pte *ptep, gfn_t gfn, gfn_t next, struct dat_ return 0; } -void kvm_s390_update_cmma_dirty(struct kvm *kvm, struct kvm_memory_slot *old) +void kvm_s390_update_cmma_dirty(struct kvm *kvm, const struct kvm_memory_slot *old) { const struct dat_walk_ops ops = { .pte_entry = cmma_d_count_pte, }; @@ -5781,7 +5781,6 @@ void kvm_arch_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *old, const struct kvm_memory_slot *new, enum kvm_mr_change change) { - s390_kvm_mmu_commit_memory_region(kvm, old, new, change); } /** diff --git a/arch/s390/kvm/s390/s390.h b/arch/s390/kvm/s390/s390.h index d284a263ba70..aa0d1d062f8d 100644 --- a/arch/s390/kvm/s390/s390.h +++ b/arch/s390/kvm/s390/s390.h @@ -472,7 +472,7 @@ int __kvm_s390_mprotect_many(struct gmap *gmap, gpa_t gpa, u8 npages, unsigned i unsigned long bits); bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu); -void kvm_s390_update_cmma_dirty(struct kvm *kvm, struct kvm_memory_slot *old); +void kvm_s390_update_cmma_dirty(struct kvm *kvm, const struct kvm_memory_slot *old); int kvm_s390_vm_stop_migration(struct kvm *kvm); diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index f317a1465dad..d6af4015223e 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -226,6 +226,7 @@ static pci_ers_result_t zpci_event_attempt_error_recovery(struct pci_dev *pdev, device_lock(&pdev->dev); if (pdev->error_state == pci_channel_io_perm_failure) { ers_res = PCI_ERS_RESULT_DISCONNECT; + status_str = "skipped (permanent failure)"; goto out_unlock; } pdev->error_state = pci_channel_io_frozen; @@ -297,8 +298,8 @@ static pci_ers_result_t zpci_event_attempt_error_recovery(struct pci_dev *pdev, driver->err_handler->resume(pdev); pci_uevent_ers(pdev, PCI_ERS_RESULT_RECOVERED); out_unlock: + zpci_report_status(zdev, pdev, "recovery", status_str); device_unlock(&pdev->dev); - zpci_report_status(zdev, "recovery", status_str); return ers_res; } @@ -361,8 +362,10 @@ static void __zpci_event_error(struct zpci_ccdf_err *ccdf) __zpci_event_print_error(pdev, ccdf); - if (!pdev) + if (!pdev) { + zpci_report_status(zdev, NULL, "error event", "no pdev bound"); goto no_pdev; + } switch (ccdf->pec) { case 0x002a: /* Error event concerns FMB */ diff --git a/arch/s390/pci/pci_report.c b/arch/s390/pci/pci_report.c index 7030f7052926..72ecabf7003c 100644 --- a/arch/s390/pci/pci_report.c +++ b/arch/s390/pci/pci_report.c @@ -87,9 +87,23 @@ static struct debug_view debug_log_view = { NULL }; +static ssize_t zpci_report_pdev(struct pci_dev *pdev, char *buf, size_t size) +{ + struct pci_driver *driver; + const char *start = buf; + char *end = buf + size; + + device_lock_assert(&pdev->dev); + buf += scnprintf(buf, end - buf, "state: %s\n", zpci_state_str(pdev->error_state)); + driver = to_pci_driver(pdev->dev.driver); + buf += scnprintf(buf, end - buf, "driver: %s\n", (driver) ? driver->name : "n/a"); + return buf - start; +} + /** * zpci_report_status - Report the status of operations on a PCI device - * @zdev: The PCI device for which to report status + * @zdev: The zPCI device for which to report status + * @pdev: The PCI device associated with the zdev if any, NULL otherwise * @operation: A string representing the operation reported * @status: A string representing the status of the operation * @@ -103,15 +117,14 @@ static struct debug_view debug_log_view = { * * Return: 0 on success an error code < 0 otherwise. */ -int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status) +int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, + const char *operation, const char *status) { struct zpci_report_error *report; - struct pci_driver *driver = NULL; - struct pci_dev *pdev = NULL; char *buf, *end; int ret; - if (!zdev || !zdev->zbus) + if (!zdev) return -ENODEV; /* Protected virtualization hosts get nothing from us */ @@ -121,18 +134,13 @@ int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char report = (void *)get_zeroed_page(GFP_KERNEL); if (!report) return -ENOMEM; - if (zdev->zbus->bus) - pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn); - if (pdev) - driver = to_pci_driver(pdev->dev.driver); buf = report->data.log_data; end = report->data.log_data + ZPCI_REPORT_DATA_SIZE; buf += scnprintf(buf, end - buf, "report: %s\n", operation); buf += scnprintf(buf, end - buf, "status: %s\n", status); - buf += scnprintf(buf, end - buf, "state: %s\n", - (pdev) ? zpci_state_str(pdev->error_state) : "n/a"); - buf += scnprintf(buf, end - buf, "driver: %s\n", (driver) ? driver->name : "n/a"); + if (pdev) + buf += zpci_report_pdev(pdev, buf, end - buf); ret = debug_dump(pci_debug_msg_id, &debug_log_view, buf, end - buf, true); if (ret < 0) pr_err("Reading PCI debug messages failed with code %d\n", ret); diff --git a/arch/s390/pci/pci_report.h b/arch/s390/pci/pci_report.h index e08003d51a97..dd7b0b05001c 100644 --- a/arch/s390/pci/pci_report.h +++ b/arch/s390/pci/pci_report.h @@ -8,9 +8,11 @@ */ #ifndef __S390_PCI_REPORT_H #define __S390_PCI_REPORT_H +#include <linux/pci.h> struct zpci_dev; -int zpci_report_status(struct zpci_dev *zdev, const char *operation, const char *status); +int zpci_report_status(struct zpci_dev *zdev, struct pci_dev *pdev, + const char *operation, const char *status); #endif /* __S390_PCI_REPORT_H */ diff --git a/arch/x86/coco/sev/svsm.c b/arch/x86/coco/sev/svsm.c index 916d62cd17dc..2d493d55ff2e 100644 --- a/arch/x86/coco/sev/svsm.c +++ b/arch/x86/coco/sev/svsm.c @@ -74,6 +74,14 @@ int svsm_perform_call_protocol(struct svsm_call *call) flags = native_local_irq_save(); + /* + * 'caa' is a per-CPU variable. To avoid using a stale or incorrect + * 'caa' if the task is preempted or migrated to another CPU after it + * is fetched, always fetch 'caa' and then issue the SVSM call with + * interrupts disabled. This ensures the correct 'caa' is used. + */ + call->caa = svsm_get_caa(); + ghcb = __sev_get_ghcb(&state); do { @@ -321,7 +329,6 @@ int snp_svsm_vtpm_send_command(u8 *buffer) { struct svsm_call call = {}; - call.caa = svsm_get_caa(); call.rax = SVSM_VTPM_CALL(SVSM_VTPM_CMD); call.rcx = __pa(buffer); @@ -345,7 +352,6 @@ bool snp_svsm_vtpm_probe(void) if (!snp_vmpl) return false; - call.caa = svsm_get_caa(); call.rax = SVSM_VTPM_CALL(SVSM_VTPM_QUERY); if (svsm_perform_call_protocol(&call)) diff --git a/arch/x86/events/amd/brs.c b/arch/x86/events/amd/brs.c index dc564688f3d7..54b13faba116 100644 --- a/arch/x86/events/amd/brs.c +++ b/arch/x86/events/amd/brs.c @@ -343,11 +343,10 @@ void amd_brs_drain(void) if (!amd_brs_match_plm(event, from, to)) continue; - perf_clear_branch_entry_bitfields(br+nr); - - br[nr].from = from; - br[nr].to = to; - + br[nr] = (struct perf_branch_entry){ + .from = from, + .to = to, + }; nr++; } empty: diff --git a/arch/x86/events/amd/lbr.c b/arch/x86/events/amd/lbr.c index 9d9c961989d5..a55646fcb846 100644 --- a/arch/x86/events/amd/lbr.c +++ b/arch/x86/events/amd/lbr.c @@ -184,13 +184,6 @@ void amd_pmu_lbr_read(void) entry.to.split.reserved) continue; - perf_clear_branch_entry_bitfields(br + out); - - br[out].from = sign_ext_branch_ip(entry.from.split.ip); - br[out].to = sign_ext_branch_ip(entry.to.split.ip); - br[out].mispred = entry.from.split.mispredict; - br[out].predicted = !br[out].mispred; - /* * Set branch speculation information using the status of * the valid and spec bits. @@ -208,7 +201,14 @@ void amd_pmu_lbr_read(void) * speculative and took the correct path */ idx = (entry.to.split.valid << 1) | entry.to.split.spec; - br[out].spec = lbr_spec_map[idx]; + + br[out] = (struct perf_branch_entry){ + .from = sign_ext_branch_ip(entry.from.split.ip), + .to = sign_ext_branch_ip(entry.to.split.ip), + .mispred = entry.from.split.mispredict, + .predicted = !entry.from.split.mispredict, + .spec = lbr_spec_map[idx], + }; out++; } diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 1ac2ca35db53..3ef80882843e 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -506,6 +506,8 @@ static struct event_constraint intel_pnc_event_constraints[] = { INTEL_EVENT_CONSTRAINT(0xce, 0x1), INTEL_UEVENT_CONSTRAINT(0x01b1, 0x8), + INTEL_UEVENT_CONSTRAINT(0x01b2, 0xf), + INTEL_UEVENT_CONSTRAINT(0x02b2, 0xf), INTEL_UEVENT_CONSTRAINT(0x0847, 0xf), INTEL_UEVENT_CONSTRAINT(0x0446, 0xf), INTEL_UEVENT_CONSTRAINT(0x0846, 0xf), @@ -520,6 +522,14 @@ static struct extra_reg intel_pnc_extra_regs[] __read_mostly = { INTEL_UEVENT_EXTRA_REG(0x022a, MSR_OMR_1, 0x40ffffff0000ffffull, OMR_1), INTEL_UEVENT_EXTRA_REG(0x042a, MSR_OMR_2, 0x40ffffff0000ffffull, OMR_2), INTEL_UEVENT_EXTRA_REG(0x082a, MSR_OMR_3, 0x40ffffff0000ffffull, OMR_3), + INTEL_UEVENT_EXTRA_REG(0x014f, MSR_OMR_0, 0x40ffffff0000ffffull, OMR_0), + INTEL_UEVENT_EXTRA_REG(0x024f, MSR_OMR_1, 0x40ffffff0000ffffull, OMR_1), + INTEL_UEVENT_EXTRA_REG(0x044f, MSR_OMR_2, 0x40ffffff0000ffffull, OMR_2), + INTEL_UEVENT_EXTRA_REG(0x084f, MSR_OMR_3, 0x40ffffff0000ffffull, OMR_3), + INTEL_UEVENT_EXTRA_REG(0x01d6, MSR_OMR_0, 0x40ffffff0000ffffull, OMR_0), + INTEL_UEVENT_EXTRA_REG(0x02d6, MSR_OMR_1, 0x40ffffff0000ffffull, OMR_1), + INTEL_UEVENT_EXTRA_REG(0x04d6, MSR_OMR_2, 0x40ffffff0000ffffull, OMR_2), + INTEL_UEVENT_EXTRA_REG(0x08d6, MSR_OMR_3, 0x40ffffff0000ffffull, OMR_3), INTEL_UEVENT_PEBS_LDLAT_EXTRA_REG(0x01cd), INTEL_UEVENT_EXTRA_REG(0x02c6, MSR_PEBS_FRONTEND, 0x9, FE), INTEL_UEVENT_EXTRA_REG(0x03c6, MSR_PEBS_FRONTEND, 0x7fff1f, FE), @@ -5292,12 +5302,15 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) struct kvm_pmu *kvm_pmu = (struct kvm_pmu *)data; u64 intel_ctrl = hybrid(cpuc->pmu, intel_ctrl); u64 pebs_mask = cpuc->pebs_enabled & x86_pmu.pebs_capable; - int global_ctrl, pebs_enable; + u64 guest_pebs_mask; + int global_ctrl; /* * In addition to obeying exclude_guest/exclude_host, remove bits being * used for PEBS when running a guest, because PEBS writes to virtual - * addresses (not physical addresses). + * addresses (not physical addresses). If the guest wants to utilize + * PEBS, and PEBS can be safely enabled in the guest, bits for the guest's + * PEBS-enabled counters will be OR'd back in as appropriate. */ *nr = 0; global_ctrl = (*nr)++; @@ -5327,41 +5340,68 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) return arr; } - if (!kvm_pmu || !x86_pmu.pebs_ept) + /* + * If the CPU doesn't support PEBS in the guest, then there's nothing + * more to do as disabling PMCs via PERF_GLOBAL_CTRL is sufficient on + * CPUs with guest/host isolation. + */ + if (!x86_pmu.pebs_ept) return arr; + /* + * Restrict guest PEBS events to counters that (a) perf supports, (b) + * the guest wants to use for PEBS, (c) are not excluded from counting + * in the guest, and (d) _are_ excluded from counting in the host. + */ + guest_pebs_mask = pebs_mask & intel_ctrl & kvm_pmu->pebs_enable & + ~cpuc->intel_ctrl_host_mask & + cpuc->intel_ctrl_guest_mask; + + /* + * Disable counters where the guest PMC is different than the host PMC + * being used on behalf of the guest, as the PEBS record includes + * PERF_GLOBAL_STATUS, i.e. the guest will see overflow status for the + * wrong counter(s). + */ + guest_pebs_mask &= ~kvm_pmu->host_cross_mapped_mask; + + /* + * FIXME: Allow guest and host usage of PEBS events to co-exist instead + * of disabling guest PEBS entirely if the host is using PEBS. + * What exactly goes wrong if guest and host are using PEBS is + * unknown. + */ + if (pebs_mask & ~cpuc->intel_ctrl_guest_mask) + guest_pebs_mask = 0; + + /* + * Context switch DS_AREA and PEBS_DATA_CFG if and only if PEBS will be + * active in the guest; if no records will be generated while the guest + * is running, then simply keep the host values resident in hardware. + */ arr[(*nr)++] = (struct perf_guest_switch_msr){ .msr = MSR_IA32_DS_AREA, .host = (unsigned long)cpuc->ds, - .guest = kvm_pmu->ds_area, + .guest = guest_pebs_mask ? kvm_pmu->ds_area : (unsigned long)cpuc->ds, }; if (x86_pmu.intel_cap.pebs_baseline) { arr[(*nr)++] = (struct perf_guest_switch_msr){ .msr = MSR_PEBS_DATA_CFG, .host = cpuc->active_pebs_data_cfg, - .guest = kvm_pmu->pebs_data_cfg, + .guest = guest_pebs_mask ? kvm_pmu->pebs_data_cfg : + cpuc->active_pebs_data_cfg, }; } - pebs_enable = (*nr)++; - arr[pebs_enable] = (struct perf_guest_switch_msr){ - .msr = MSR_IA32_PEBS_ENABLE, - .host = cpuc->pebs_enabled & ~cpuc->intel_ctrl_guest_mask, - .guest = pebs_mask & ~cpuc->intel_ctrl_host_mask & kvm_pmu->pebs_enable, - }; - - if (arr[pebs_enable].host) { - /* Disable guest PEBS if host PEBS is enabled. */ - arr[pebs_enable].guest = 0; - } else { - /* Disable guest PEBS thoroughly for cross-mapped PEBS counters. */ - arr[pebs_enable].guest &= ~kvm_pmu->host_cross_mapped_mask; - arr[global_ctrl].guest &= ~kvm_pmu->host_cross_mapped_mask; - /* Set hw GLOBAL_CTRL bits for PEBS counter when it runs for guest */ - arr[global_ctrl].guest |= arr[pebs_enable].guest; - } - + /* + * Do NOT mess with PEBS_ENABLED. As above, disabling counters via + * PERF_GLOBAL_CTRL is sufficient, and loading a stale PEBS_ENABLED, + * e.g. on VM-Exit, can put the system in a bad state. Simply enable + * counters in PERF_GLOBAL_CTRL, as perf load PEBS_ENABLED with the + * full value, i.e. perf *also* relies on PERF_GLOBAL_CTRL. + */ + arr[global_ctrl].guest |= guest_pebs_mask; return arr; } @@ -6561,6 +6601,8 @@ static void intel_pmu_filter(struct pmu *pmu, int cpu, bool *ret) PMU_FORMAT_ATTR(offcore_rsp, "config1:0-63"); +PMU_FORMAT_ATTR(offmodule_rsp, "config1:0-63"); + PMU_FORMAT_ATTR(ldlat, "config1:0-15"); PMU_FORMAT_ATTR(frontend, "config1:0-23"); @@ -6609,6 +6651,20 @@ static struct attribute *skl_format_attr[] = { NULL, }; +static struct attribute *pnc_format_attr_rtm[] = { + &format_attr_in_tx.attr, + &format_attr_in_tx_cp.attr, + &format_attr_offmodule_rsp.attr, + &format_attr_ldlat.attr, + NULL +}; + +static struct attribute *pnc_format_attr[] = { + &format_attr_offmodule_rsp.attr, + &format_attr_ldlat.attr, + NULL +}; + static __initconst const struct x86_pmu core_pmu = { .name = "core", .handle_irq = x86_pmu_handle_irq, @@ -7474,6 +7530,7 @@ static struct attribute *adl_hybrid_tsx_attrs[] = { FORMAT_ATTR_HYBRID(in_tx, hybrid_big); FORMAT_ATTR_HYBRID(in_tx_cp, hybrid_big); FORMAT_ATTR_HYBRID(offcore_rsp, hybrid_big_small_tiny); +FORMAT_ATTR_HYBRID(offmodule_rsp, hybrid_big_small_tiny); FORMAT_ATTR_HYBRID(ldlat, hybrid_big_small_tiny); FORMAT_ATTR_HYBRID(frontend, hybrid_big); @@ -7512,6 +7569,23 @@ static struct attribute *mtl_hybrid_extra_attr[] = { NULL }; +static struct attribute *nvl_hybrid_extra_attr_rtm[] = { + ADL_HYBRID_RTM_FORMAT_ATTR, + FORMAT_HYBRID_PTR(offmodule_rsp), + FORMAT_HYBRID_PTR(ldlat), + FORMAT_HYBRID_PTR(frontend), + FORMAT_HYBRID_PTR(snoop_rsp), + NULL +}; + +static struct attribute *nvl_hybrid_extra_attr[] = { + FORMAT_HYBRID_PTR(offmodule_rsp), + FORMAT_HYBRID_PTR(ldlat), + FORMAT_HYBRID_PTR(frontend), + FORMAT_HYBRID_PTR(snoop_rsp), + NULL +}; + static bool is_attr_for_this_pmu(struct kobject *kobj, struct attribute *attr) { struct device *dev = kobj_to_dev(kobj); @@ -8549,6 +8623,8 @@ __init int intel_pmu_init(void) case INTEL_DIAMONDRAPIDS_X: intel_pmu_init_pnc(NULL); x86_pmu.pebs_latency_data = pnc_latency_data; + extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? + pnc_format_attr_rtm : pnc_format_attr; pr_cont("Panthercove events, "); name = "panthercove"; @@ -8557,13 +8633,12 @@ __init int intel_pmu_init(void) glc_common: intel_pmu_init_glc(NULL); intel_pmu_pebs_data_source_skl(true); - + extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? + hsw_format_attr : nhm_format_attr; glc_base: x86_pmu.pebs_ept = 1; x86_pmu.hw_config = hsw_hw_config; x86_pmu.get_event_constraints = glc_get_event_constraints; - extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? - hsw_format_attr : nhm_format_attr; extra_skl_attr = skl_format_attr; mem_attr = glc_events_attrs; td_attr = glc_td_events_attrs; @@ -8785,7 +8860,7 @@ __init int intel_pmu_init(void) mem_attr = mtl_hybrid_mem_attrs; tsx_attr = adl_hybrid_tsx_attrs; extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? - mtl_hybrid_extra_attr_rtm : mtl_hybrid_extra_attr; + nvl_hybrid_extra_attr_rtm : nvl_hybrid_extra_attr; /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; @@ -8794,8 +8869,6 @@ __init int intel_pmu_init(void) /* Initialize Atom core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; intel_pmu_init_arw(&pmu->pmu); - - intel_pmu_pebs_data_source_lnl(); break; default: diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index b98029b44052..d0329a7eb8a5 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -277,8 +277,8 @@ static u64 pnc_pebs_l2_hit_data_source[PNC_PEBS_DATA_SOURCE_MAX] = { 0, /* 0x06: Reserved */ OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, HIT), /* 0x07: L2 Hit Snoop HIT */ OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, HITM), /* 0x08: L2 Hit Snoop Hit Modified */ - OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, MISS), /* 0x09: Prefetch Promotion */ - OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, MISS), /* 0x0a: Cross Core Prefetch Promotion */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, NONE), /* 0x09: Prefetch Promotion */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, NONE), /* 0x0a: Cross Core Prefetch Promotion */ 0, /* 0x0b: Reserved */ 0, /* 0x0c: Reserved */ 0, /* 0x0d: Reserved */ @@ -455,6 +455,7 @@ static inline void pebs_set_tlb_lock(u64 *val, bool tlb, bool lock) static u64 __grt_latency_data(struct perf_event *event, u64 status, u8 dse, bool tlb, bool lock, bool blk) { + union perf_mem_data_src src; u64 val; WARN_ON_ONCE(is_hybrid() && @@ -470,7 +471,16 @@ static u64 __grt_latency_data(struct perf_event *event, u64 status, else val |= P(BLK, NA); - return val; + src.val = val; + + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) + src.mem_op = P(OP, STORE); + + return src.val; } u64 grt_latency_data(struct perf_event *event, u64 status) @@ -528,7 +538,11 @@ static u64 arw_latency_data(struct perf_event *event, u64 status) val |= P(BLK, NA); src.val = val; - if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) src.mem_op = P(OP, STORE); return src.val; @@ -563,7 +577,11 @@ static u64 lnc_latency_data(struct perf_event *event, u64 status) val |= P(BLK, NA); src.val = val; - if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) src.mem_op = P(OP, STORE); return src.val; @@ -621,7 +639,11 @@ u64 pnc_latency_data(struct perf_event *event, u64 status) val |= P(BLK, NA); src.val = val; - if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_LDLAT | PERF_X86_EVENT_PEBS_LD_HSW)) + src.mem_op = P(OP, LOAD); + if (event->hw.flags & + (PERF_X86_EVENT_PEBS_STLAT | PERF_X86_EVENT_PEBS_ST_HSW)) src.mem_op = P(OP, STORE); return src.val; @@ -1291,22 +1313,22 @@ struct event_constraint intel_glm_pebs_event_constraints[] = { struct event_constraint intel_grt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0x3), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0x3f), + INTEL_HYBRID_LDLAT_CONSTRAINT(0x5d0, 0x3), + INTEL_HYBRID_STLAT_CONSTRAINT(0x6d0, 0x3f), EVENT_CONSTRAINT_END }; struct event_constraint intel_cmt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0x3), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xff), + INTEL_HYBRID_LDLAT_CONSTRAINT(0x5d0, 0x3), + INTEL_HYBRID_STLAT_CONSTRAINT(0x6d0, 0xff), EVENT_CONSTRAINT_END }; struct event_constraint intel_dkt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0xff), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xff), + INTEL_HYBRID_LDLAT_CONSTRAINT(0x5d0, 0xff), + INTEL_HYBRID_STLAT_CONSTRAINT(0x6d0, 0xff), EVENT_CONSTRAINT_END }; @@ -1496,24 +1518,8 @@ struct event_constraint intel_lnc_pebs_event_constraints[] = { INTEL_FLAGS_UEVENT_CONSTRAINT(0x012a, 0x1), /* OCR.* events */ INTEL_FLAGS_UEVENT_CONSTRAINT(0x012b, 0x1), /* OCR.* events */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x04a4, 0x1), /* TOPDOWN.BAD_SPEC_SLOTS */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x08a4, 0x1), /* TOPDOWN.BR_MISPREDICT_SLOTS */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x10a4, 0x8), /* TOPDOWN.MEMORY_BOUND_SLOTS */ - INTEL_HYBRID_LDLAT_CONSTRAINT(0x1cd, 0x3fc), INTEL_HYBRID_STLAT_CONSTRAINT(0x2cd, 0x3), - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x11d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x12d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x21d0, 0xf), /* MEM_INST_RETIRED.LOCK_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x41d0, 0xf), /* MEM_INST_RETIRED.SPLIT_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x42d0, 0xf), /* MEM_INST_RETIRED.SPLIT_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x81d0, 0xf), /* MEM_INST_RETIRED.ALL_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x82d0, 0xf), /* MEM_INST_RETIRED.ALL_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT(0x87d0, 0x3ff), /* MEM_INST_RETIRED.ANY */ - - INTEL_FLAGS_EVENT_CONSTRAINT_DATALA_LD_RANGE(0xd1, 0xd4, 0xf), - - INTEL_FLAGS_EVENT_CONSTRAINT(0xd0, 0xf), /* * Everything else is handled by PMU_FL_PEBS_ALL, because we @@ -1526,18 +1532,6 @@ struct event_constraint intel_lnc_pebs_event_constraints[] = { struct event_constraint intel_pnc_pebs_event_constraints[] = { INTEL_HYBRID_LDLAT_CONSTRAINT(0x1cd, 0xfc), INTEL_HYBRID_STLAT_CONSTRAINT(0x2cd, 0x3), - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x11d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x12d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x21d0, 0xf), /* MEM_INST_RETIRED.LOCK_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x41d0, 0xf), /* MEM_INST_RETIRED.SPLIT_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x42d0, 0xf), /* MEM_INST_RETIRED.SPLIT_STORES */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x81d0, 0xf), /* MEM_INST_RETIRED.ALL_LOADS */ - INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x82d0, 0xf), /* MEM_INST_RETIRED.ALL_STORES */ - - INTEL_FLAGS_EVENT_CONSTRAINT_DATALA_LD_RANGE(0xd1, 0xd4, 0xf), - - INTEL_FLAGS_EVENT_CONSTRAINT(0xd0, 0xf), - INTEL_FLAGS_EVENT_CONSTRAINT(0xd6, 0xf), /* * Everything else is handled by PMU_FL_PEBS_ALL, because we diff --git a/arch/x86/events/intel/lbr.c b/arch/x86/events/intel/lbr.c index cbe5c762008d..22e2a06d5786 100644 --- a/arch/x86/events/intel/lbr.c +++ b/arch/x86/events/intel/lbr.c @@ -756,10 +756,10 @@ void intel_pmu_lbr_read_32(struct cpu_hw_events *cpuc) rdmsrq(x86_pmu.lbr_from + lbr_idx, msr_lastbranch.lbr); - perf_clear_branch_entry_bitfields(br); - - br->from = msr_lastbranch.from; - br->to = msr_lastbranch.to; + *br = (struct perf_branch_entry){ + .from = msr_lastbranch.from, + .to = msr_lastbranch.to, + }; br++; } cpuc->lbr_stack.nr = i; @@ -847,14 +847,15 @@ void intel_pmu_lbr_read_64(struct cpu_hw_events *cpuc) if (abort && x86_pmu.lbr_double_abort && out > 0) out--; - perf_clear_branch_entry_bitfields(br+out); - br[out].from = from; - br[out].to = to; - br[out].mispred = mis; - br[out].predicted = pred; - br[out].in_tx = in_tx; - br[out].abort = abort; - br[out].cycles = cycles; + br[out] = (struct perf_branch_entry){ + .from = from, + .to = to, + .mispred = mis, + .predicted = pred, + .in_tx = in_tx, + .abort = abort, + .cycles = cycles, + }; out++; } cpuc->lbr_stack.nr = out; @@ -905,6 +906,7 @@ static void intel_pmu_store_lbr(struct cpu_hw_events *cpuc, struct perf_branch_entry *e; struct lbr_entry *lbr; u64 from, to, info; + bool mispred; int i; for (i = 0; i < x86_pmu.lbr_nr; i++) { @@ -921,24 +923,27 @@ static void intel_pmu_store_lbr(struct cpu_hw_events *cpuc, to = rdlbr_to(i, lbr); info = rdlbr_info(i, lbr); - perf_clear_branch_entry_bitfields(e); - - e->from = from; - e->to = to; - e->mispred = get_lbr_mispred(info); - e->predicted = !e->mispred; - e->in_tx = !!(info & LBR_INFO_IN_TX); - e->abort = !!(info & LBR_INFO_ABORT); - e->cycles = get_lbr_cycles(info); - e->type = get_lbr_br_type(info); - - /* - * Leverage the reserved field of cpuc->lbr_entries[i] to - * temporarily store the branch counters information. - * The later code will decide what content can be disclosed - * to the perf tool. Pleae see intel_pmu_lbr_counters_reorder(). - */ - e->reserved = (info >> LBR_INFO_BR_CNTR_OFFSET) & LBR_INFO_BR_CNTR_FULL_MASK; + mispred = get_lbr_mispred(info); + + *e = (struct perf_branch_entry){ + .from = from, + .to = to, + .mispred = mispred, + .predicted = !mispred, + .in_tx = !!(info & LBR_INFO_IN_TX), + .abort = !!(info & LBR_INFO_ABORT), + .cycles = get_lbr_cycles(info), + .type = get_lbr_br_type(info), + /* + * Leverage the reserved field of + * cpuc->lbr_entries[i] to temporarily store the + * branch counters information. The later code will + * decide what content can be disclosed to the perf + * tool. Pleae see intel_pmu_lbr_counters_reorder(). + */ + .reserved = (info >> LBR_INFO_BR_CNTR_OFFSET) & + LBR_INFO_BR_CNTR_FULL_MASK, + }; } cpuc->lbr_stack.nr = i; diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 4680cba91340..fab9da78a5c7 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -517,10 +517,6 @@ struct cpu_hw_events { __EVENT_CONSTRAINT(c, n, INTEL_ARCH_EVENT_MASK|X86_ALL_EVENT_FLAGS, \ HWEIGHT(n), 0, PERF_X86_EVENT_PEBS_ST) -#define INTEL_HYBRID_LAT_CONSTRAINT(c, n) \ - __EVENT_CONSTRAINT(c, n, INTEL_ARCH_EVENT_MASK|X86_ALL_EVENT_FLAGS, \ - HWEIGHT(n), 0, PERF_X86_EVENT_PEBS_LAT_HYBRID) - #define INTEL_HYBRID_LDLAT_CONSTRAINT(c, n) \ __EVENT_CONSTRAINT(c, n, INTEL_ARCH_EVENT_MASK|X86_ALL_EVENT_FLAGS, \ HWEIGHT(n), 0, PERF_X86_EVENT_PEBS_LAT_HYBRID|PERF_X86_EVENT_PEBS_LD_HSW) diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index ab469605fc89..39f238952e14 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -2108,6 +2108,9 @@ bool filter_mce(struct mce *m) static __always_inline void exc_machine_check_kernel(struct pt_regs *regs) { irqentry_state_t irq_state; + unsigned long dr7; + + dr7 = local_db_save(); WARN_ON_ONCE(user_mode(regs)); @@ -2116,20 +2119,26 @@ static __always_inline void exc_machine_check_kernel(struct pt_regs *regs) * mce_check_crashing_cpu() for details. */ if (mca_cfg.initialized && mce_check_crashing_cpu()) - return; + goto out; irq_state = irqentry_nmi_enter(regs); do_machine_check(regs); irqentry_nmi_exit(regs, irq_state); +out: + local_db_restore(dr7); } static __always_inline void exc_machine_check_user(struct pt_regs *regs) { + unsigned long dr7; + irqentry_enter_from_user_mode(regs); + dr7 = local_db_save(); do_machine_check(regs); + local_db_restore(dr7); irqentry_exit_to_user_mode(regs); } @@ -2138,21 +2147,13 @@ static __always_inline void exc_machine_check_user(struct pt_regs *regs) /* MCE hit kernel mode */ DEFINE_IDTENTRY_MCE(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); exc_machine_check_kernel(regs); - local_db_restore(dr7); } /* The user mode variant. */ DEFINE_IDTENTRY_MCE_USER(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); exc_machine_check_user(regs); - local_db_restore(dr7); } #ifdef CONFIG_X86_FRED @@ -2169,28 +2170,20 @@ DEFINE_IDTENTRY_MCE_USER(exc_machine_check) */ DEFINE_FREDENTRY_MCE(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); if (user_mode(regs)) exc_machine_check_user(regs); else exc_machine_check_kernel(regs); - local_db_restore(dr7); } #endif #else /* 32bit unified entry point */ DEFINE_IDTENTRY_RAW(exc_machine_check) { - unsigned long dr7; - - dr7 = local_db_save(); if (user_mode(regs)) exc_machine_check_user(regs); else exc_machine_check_kernel(regs); - local_db_restore(dr7); } #endif diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 064ecc33b926..8e62476e477b 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -5058,7 +5058,7 @@ static int kvm_tdp_page_prefault(struct kvm_vcpu *vcpu, gpa_t gpa, if (signal_pending(current)) return -EINTR; - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) return -EIO; cond_resched(); diff --git a/arch/x86/kvm/pmu.c b/arch/x86/kvm/pmu.c index a7d60c8785cd..d2fd47ee5ec8 100644 --- a/arch/x86/kvm/pmu.c +++ b/arch/x86/kvm/pmu.c @@ -823,14 +823,6 @@ void kvm_pmu_deliver_pmi(struct kvm_vcpu *vcpu) bool kvm_pmu_is_valid_msr(struct kvm_vcpu *vcpu, u32 msr) { - switch (msr) { - case MSR_CORE_PERF_GLOBAL_STATUS: - case MSR_CORE_PERF_GLOBAL_CTRL: - case MSR_CORE_PERF_GLOBAL_OVF_CTRL: - return kvm_pmu_has_perf_global_ctrl(vcpu_to_pmu(vcpu)); - default: - break; - } return kvm_pmu_call(msr_idx_to_pmc)(vcpu, msr) || kvm_pmu_call(is_valid_msr)(vcpu, msr); } diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c index 73f37b050d0a..f9090b601efa 100644 --- a/arch/x86/kvm/svm/nested.c +++ b/arch/x86/kvm/svm/nested.c @@ -2125,13 +2125,8 @@ static bool svm_get_nested_state_pages(struct kvm_vcpu *vcpu) return false; } - if (!nested_svm_merge_msrpm(vcpu)) { - vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; - vcpu->run->internal.suberror = - KVM_INTERNAL_ERROR_EMULATION; - vcpu->run->internal.ndata = 0; + if (!nested_svm_merge_msrpm(vcpu)) return false; - } if (kvm_hv_verify_vp_assist(vcpu)) return false; diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 5705723f1f41..63eb2155a774 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2048,6 +2048,12 @@ static void sev_migrate_from(struct kvm *dst_kvm, struct kvm *src_kvm) src->pages_locked = 0; src->es_active = false; + /* + * Do cache maintenance on the source VM as it is no longer an SEV VM, + * i.e. memory reclaim flows won't trigger cache maintenance on the VM. + */ + sev_writeback_caches(src_kvm); + list_cut_before(&dst->regions_list, &src->regions_list, &src->regions_list); mutex_lock(&sev_mirror_lock); @@ -2187,6 +2193,10 @@ int sev_vm_move_enc_context_from(struct kvm *kvm, unsigned int source_fd) * the set of CPUs from the source. If a CPU was used to run a vCPU in * the source VM but is never used for the destination VM, then the CPU * can only have cached memory that was accessible to the source VM. + * Furthermore, KVM *must* perform cache maintenance on the source VM, + * as the source VM may have access to memory that the destination VM + * does not, i.e. KVM could skip flushes if memory is reclaimed from + * the old VM but not the new VM. */ if (!zalloc_cpumask_var(&dst_sev->have_run_cpus, GFP_KERNEL_ACCOUNT)) { ret = -ENOMEM; @@ -2980,13 +2990,17 @@ void sev_vm_destroy(struct kvm *kvm) struct list_head *head = &sev->regions_list; struct list_head *pos, *q; + /* + * Free the mask even if the VM is not *currently* an SEV VM, as it may + * have been an SEV VM prior to intra-host migration. + */ + free_cpumask_var(sev->have_run_cpus); + if (!sev_guest(kvm)) return; WARN_ON(!list_empty(&sev->mirror_vms)); - free_cpumask_var(sev->have_run_cpus); - /* * If this is a mirror VM, remove it from the owner's list of a mirrors * and skip ASID cleanup (the ASID is tied to the lifetime of the owner). diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index 151873407abd..40c1a5f6fa8a 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -3465,10 +3465,6 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu *vcpu) } else { pr_debug_ratelimited("%s: no backing for APIC-access address in vmcs12\n", __func__); - vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; - vcpu->run->internal.suberror = - KVM_INTERNAL_ERROR_EMULATION; - vcpu->run->internal.ndata = 0; return false; } } @@ -3539,11 +3535,6 @@ static bool vmx_get_nested_state_pages(struct kvm_vcpu *vcpu) if (!nested_get_evmcs_page(vcpu)) { pr_debug_ratelimited("%s: enlightened vmptrld failed\n", __func__); - vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; - vcpu->run->internal.suberror = - KVM_INTERNAL_ERROR_EMULATION; - vcpu->run->internal.ndata = 0; - return false; } #endif @@ -3915,8 +3906,12 @@ static int nested_vmx_run(struct kvm_vcpu *vcpu, bool launch) vmentry_failed: vcpu->arch.nested_run_pending = 0; - if (status == NVMX_VMENTRY_KVM_INTERNAL_ERROR) + if (status == NVMX_VMENTRY_KVM_INTERNAL_ERROR) { + vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; + vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION; + vcpu->run->internal.ndata = 0; return 0; + } if (status == NVMX_VMENTRY_VMEXIT) return 1; WARN_ON_ONCE(status != NVMX_VMENTRY_VMFAIL); diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c index bfa8612fb450..70a8c4816135 100644 --- a/arch/x86/kvm/vmx/pmu_intel.c +++ b/arch/x86/kvm/vmx/pmu_intel.c @@ -187,6 +187,9 @@ static bool intel_is_valid_msr(struct kvm_vcpu *vcpu, u32 msr) int ret; switch (msr) { + case MSR_CORE_PERF_GLOBAL_STATUS: + case MSR_CORE_PERF_GLOBAL_CTRL: + case MSR_CORE_PERF_GLOBAL_OVF_CTRL: case MSR_CORE_PERF_FIXED_CTR_CTRL: return kvm_pmu_has_perf_global_ctrl(pmu); case MSR_IA32_PEBS_ENABLE: diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index b272c20586a7..6c842e9191a5 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -1995,7 +1995,7 @@ static int tdx_handle_ept_violation(struct kvm_vcpu *vcpu) if (kvm_vcpu_has_events(vcpu) || signal_pending(current)) break; - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) { + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) { ret = -EIO; break; } diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 79468ddfe473..aad065d035fb 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -8060,7 +8060,7 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) bool req_immediate_exit = false; if (kvm_request_pending(vcpu)) { - if (kvm_check_request(KVM_REQ_VM_DEAD, vcpu)) { + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) { r = -EIO; goto out; } @@ -8072,6 +8072,10 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu) if (kvm_check_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu)) { if (unlikely(!kvm_nested_call(get_nested_state_pages)(vcpu))) { + vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR; + vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION; + vcpu->run->internal.ndata = 0; + kvm_make_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu); r = 0; goto out; } diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c index b301c6c8df75..795d81c7a4de 100644 --- a/arch/x86/pci/fixup.c +++ b/arch/x86/pci/fixup.c @@ -886,6 +886,105 @@ static void quirk_clear_strap_no_soft_reset_dev2_f0(struct pci_dev *dev) } } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15b8, quirk_clear_strap_no_soft_reset_dev2_f0); + +/* + * Enhanced atomic operations can cause corruption with 64-bit DMA + * on these devices. + */ +#define RX_ENH_ATOMIC_EN BIT(8) + +static const u32 nbio_7_7_pcie_smn_addrs[] = { + 0x111401d0, + 0x111411d0, + 0x111421d0, + 0x111431d0, + 0x111441d0, + 0x112401d0, + 0x112411d0, + 0x112421d0, + 0x112431d0, + 0x112441d0, + 0x112451d0, + 0x113401d0, + 0x114401d0, +}; + +static const u32 nbio_7_11_pcie_smn_addrs[] = { + 0x112401d0, + 0x112411d0, + 0x112421d0, + 0x112431d0, + 0x112441d0, + 0x112451d0, + 0x113401d0, + 0x113411d0, + 0x113421d0, + 0x113431d0, + 0x113441d0, + 0x113451d0, +}; + +static void quirk_amd_nbio_enhanced_atomic(struct pci_dev *host_bridge, + const u32 *smn_addrs, + size_t nr_smn_addrs) +{ + bool changed = false; + size_t i; + u32 data; + int ret; + + for (i = 0; i < nr_smn_addrs; i++) { + ret = amd_smn_read(0, smn_addrs[i], &data); + if (ret) + continue; + if (!(data & RX_ENH_ATOMIC_EN)) + continue; + data = data & ~RX_ENH_ATOMIC_EN; + ret = amd_smn_write(0, smn_addrs[i], data); + if (ret) + continue; + if (changed) + continue; + ret = amd_smn_read(0, smn_addrs[i], &data); + if (ret) + continue; + if (data & RX_ENH_ATOMIC_EN) + continue; + changed = true; + } + + if (changed) + pci_info(host_bridge, "enhanced atomics disabled\n"); +} + +static void quirk_amd_nbio_7_7_disable_enhanced_atomic(struct pci_dev *dev) +{ + quirk_amd_nbio_enhanced_atomic(dev, nbio_7_7_pcie_smn_addrs, + ARRAY_SIZE(nbio_7_7_pcie_smn_addrs)); +} + +static void quirk_amd_nbio_7_11_disable_enhanced_atomic(struct pci_dev *dev) +{ + quirk_amd_nbio_enhanced_atomic(dev, nbio_7_11_pcie_smn_addrs, + ARRAY_SIZE(nbio_7_11_pcie_smn_addrs)); +} + +/* Phoenix, Hawk Point (NBIO 7.7) */ +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x14E8, + quirk_amd_nbio_7_7_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x14E8, + quirk_amd_nbio_7_7_disable_enhanced_atomic); + +/* Strix, Krackan, Strix Halo (NBIO 7.11) */ +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1507, + quirk_amd_nbio_7_11_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1507, + quirk_amd_nbio_7_11_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1122, + quirk_amd_nbio_7_11_disable_enhanced_atomic); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_AMD, 0x1122, + quirk_amd_nbio_7_11_disable_enhanced_atomic); + #endif /* diff --git a/block/blk-zoned.c b/block/blk-zoned.c index a5afb842bf35..475aa16bc41a 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -2018,12 +2018,17 @@ static int disk_revalidate_zone_resources(struct gendisk *disk, struct blk_revalidate_zone_args *args) { struct queue_limits *lim = &disk->queue->limits; + unsigned long long nr_zones; unsigned int pool_size; int ret = 0; args->disk = disk; - args->nr_zones = - DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors); + nr_zones = DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors); + if (nr_zones > UINT_MAX) { + pr_warn("%s: Too many zones (%llu)\n", disk->disk_name, nr_zones); + return -EINVAL; + } + args->nr_zones = nr_zones; /* Cached zone conditions: 1 byte per zone */ args->zones_cond = kzalloc(args->nr_zones, GFP_NOIO); @@ -2131,6 +2136,12 @@ static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx, { enum blk_zone_cond cond = zone->cond; + if (idx >= args->nr_zones) { + pr_warn("%s: Zone report index %u exceeds zone count %u\n", + args->disk->disk_name, idx, args->nr_zones); + return -EINVAL; + } + /* Check that the zone condition is consistent with the zone type. */ switch (cond) { case BLK_ZONE_COND_NOT_WP: diff --git a/drivers/accel/ivpu/ivpu_drv.c b/drivers/accel/ivpu/ivpu_drv.c index 95120957f42a..8c1c87e69f91 100644 --- a/drivers/accel/ivpu/ivpu_drv.c +++ b/drivers/accel/ivpu/ivpu_drv.c @@ -515,6 +515,7 @@ void ivpu_prepare_for_reset(struct ivpu_device *vdev) { ivpu_hw_irq_disable(vdev); disable_irq(vdev->irq); + atomic_set(&vdev->job_timeout_detected, 0); flush_work(&vdev->irq_dct_work); flush_work(&vdev->context_abort_work); flush_work(&vdev->job_destroy_work); @@ -710,7 +711,7 @@ static int ivpu_dev_init(struct ivpu_device *vdev) vdev->context_xa_limit.max = IVPU_USER_CONTEXT_MAX_SSID; atomic64_set(&vdev->unique_id_counter, 0); atomic_set(&vdev->job_timeout_counter, 0); - atomic_set(&vdev->faults_detected, 0); + atomic_set(&vdev->job_timeout_detected, 0); xa_init_flags(&vdev->context_xa, XA_FLAGS_ALLOC | XA_FLAGS_LOCK_IRQ); xa_init_flags(&vdev->submitted_jobs_xa, XA_FLAGS_ALLOC1); xa_init_flags(&vdev->db_xa, XA_FLAGS_ALLOC1); diff --git a/drivers/accel/ivpu/ivpu_drv.h b/drivers/accel/ivpu/ivpu_drv.h index 86d7c9966cac..6f4012926478 100644 --- a/drivers/accel/ivpu/ivpu_drv.h +++ b/drivers/accel/ivpu/ivpu_drv.h @@ -171,7 +171,7 @@ struct ivpu_device { struct xarray submitted_jobs_xa; struct ivpu_ipc_consumer job_done_consumer; atomic_t job_timeout_counter; - atomic_t faults_detected; + atomic_t job_timeout_detected; atomic64_t unique_id_counter; diff --git a/drivers/accel/ivpu/ivpu_job.c b/drivers/accel/ivpu/ivpu_job.c index ebb2c865b09a..4689b8ab519d 100644 --- a/drivers/accel/ivpu/ivpu_job.c +++ b/drivers/accel/ivpu/ivpu_job.c @@ -621,7 +621,6 @@ bool ivpu_job_handle_engine_error(struct ivpu_device *vdev, u32 job_id, u32 job_ * status and ensure both are handled in the same way */ job->file_priv->has_mmu_faults = true; - atomic_set(&vdev->faults_detected, 1); queue_work(system_percpu_wq, &vdev->context_abort_work); return true; } @@ -1175,10 +1174,10 @@ static int reset_engine_and_mark_faulty_contexts(struct ivpu_device *vdev) return ret; /* - * If faults are detected, ignore guilty contexts from engine reset as NPU may not be stuck - * and could return currently running good context and faulty contexts are already marked + * If job timeout is detected, read guilty context from engine reset, for other reasons + * faulty context is already known */ - if (atomic_cmpxchg(&vdev->faults_detected, 1, 0) == 1) + if (atomic_cmpxchg(&vdev->job_timeout_detected, 1, 0) == 0) return 0; num_impacted_contexts = resp.payload.engine_reset_done.num_impacted_contexts; diff --git a/drivers/accel/ivpu/ivpu_mmu.c b/drivers/accel/ivpu/ivpu_mmu.c index 41efd8985fa6..b2025274f91d 100644 --- a/drivers/accel/ivpu/ivpu_mmu.c +++ b/drivers/accel/ivpu/ivpu_mmu.c @@ -964,7 +964,6 @@ void ivpu_mmu_irq_evtq_handler(struct ivpu_device *vdev) file_priv = xa_load(&vdev->context_xa, ssid); if (file_priv) { if (!READ_ONCE(file_priv->has_mmu_faults)) { - atomic_set(&vdev->faults_detected, 1); ivpu_mmu_dump_event(vdev, event); WRITE_ONCE(file_priv->has_mmu_faults, true); } diff --git a/drivers/accel/ivpu/ivpu_pm.c b/drivers/accel/ivpu/ivpu_pm.c index c1ce8329790e..de0becbfdffb 100644 --- a/drivers/accel/ivpu/ivpu_pm.c +++ b/drivers/accel/ivpu/ivpu_pm.c @@ -229,6 +229,7 @@ abort: ivpu_jsm_state_dump(vdev); ivpu_dev_coredump(vdev); + atomic_set(&vdev->job_timeout_detected, 1); queue_work(system_percpu_wq, &vdev->context_abort_work); } diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index f482c0a6d7e9..92233fb25051 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -2972,9 +2972,10 @@ static void ata_dev_config_lpm(struct ata_device *dev) (dev->id[ATA_ID_SATA_CAPABILITY] & 0xe) == 0x2) dev->quirks |= ATA_QUIRK_NOLPM; - /* ATI specific quirk */ - if ((dev->quirks & ATA_QUIRK_NO_LPM_ON_ATI) && - ata_dev_check_adapter(dev, PCI_VENDOR_ID_ATI)) + /* ATI and AMD specific quirk */ + if ((dev->quirks & ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD) && + (ata_dev_check_adapter(dev, PCI_VENDOR_ID_ATI) || + ata_dev_check_adapter(dev, PCI_VENDOR_ID_AMD))) dev->quirks |= ATA_QUIRK_NOLPM; } @@ -4136,7 +4137,7 @@ static const char * const ata_quirk_names[] = { [__ATA_QUIRK_MAX_SEC] = "maxsec", [__ATA_QUIRK_MAX_TRIM_128M] = "maxtrim128m", [__ATA_QUIRK_NO_NCQ_ON_ATI] = "noncqonati", - [__ATA_QUIRK_NO_LPM_ON_ATI] = "nolpmonati", + [__ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD] = "nolpmonatiandamd", [__ATA_QUIRK_NO_ID_DEV_LOG] = "noiddevlog", [__ATA_QUIRK_NO_LOG_DIR] = "nologdir", [__ATA_QUIRK_NO_FUA] = "nofua", @@ -4420,15 +4421,15 @@ static const struct ata_dev_quirks_entry __ata_dev_quirks[] = { { "Samsung SSD 860*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM | ATA_QUIRK_NO_NCQ_ON_ATI | - ATA_QUIRK_NO_LPM_ON_ATI }, + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD }, { "Samsung SSD 870*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM | ATA_QUIRK_NO_NCQ_ON_ATI | - ATA_QUIRK_NO_LPM_ON_ATI }, + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD }, { "SAMSUNG*MZ7LH*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM | ATA_QUIRK_NO_NCQ_ON_ATI | - ATA_QUIRK_NO_LPM_ON_ATI }, + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD }, { "FCCT*M500*", NULL, ATA_QUIRK_NO_NCQ_TRIM | ATA_QUIRK_ZERO_AFTER_TRIM }, diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 7e22bbc38238..8f9aa97a519d 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -261,12 +261,18 @@ static void ata_scsi_set_passthru_sense_fields(struct ata_queued_cmd *qc) /* descriptor format */ len = sb[7]; - desc = (char *)scsi_sense_desc_find(sb, len + 8, 9); + desc = (char *)scsi_sense_desc_find(sb, SCSI_SENSE_BUFFERSIZE, 9); if (!desc) { - if (SCSI_SENSE_BUFFERSIZE < len + 14) + /* + * The descriptor is written at sb[8 + len] and is 14 + * bytes long, so it needs len + 22 bytes of buffer. + */ + if (len + 22 > SCSI_SENSE_BUFFERSIZE) return; sb[7] = len + 14; desc = sb + 8 + len; + } else if (desc - sb > SCSI_SENSE_BUFFERSIZE - 14) { + return; } desc[0] = 9; desc[1] = 12; diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c index 9f9c72727a05..7a47a392568a 100644 --- a/drivers/base/cacheinfo.c +++ b/drivers/base/cacheinfo.c @@ -1040,9 +1040,10 @@ static int cacheinfo_cpu_online(unsigned int cpu) rc = cache_add_dev(cpu); if (rc) goto err; - if (cpu_map_shared_cache(true, cpu, &cpu_map)) + if (cpu_map_shared_cache(true, cpu, &cpu_map)) { update_per_cpu_data_slice_size(true, cpu, cpu_map); - sched_update_llc_bytes(cpu); + sched_update_llc_bytes(cpu_map); + } return 0; err: free_cache_attributes(cpu); @@ -1059,10 +1060,10 @@ static int cacheinfo_cpu_pre_down(unsigned int cpu) cpu_cache_sysfs_exit(cpu); free_cache_attributes(cpu); - if (nr_shared > 1) + if (nr_shared > 1) { update_per_cpu_data_slice_size(false, cpu, cpu_map); - - sched_update_llc_bytes(cpu); + sched_update_llc_bytes(cpu_map); + } return 0; } diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index a6e4689000af..c73036744468 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -322,6 +322,7 @@ static inline unsigned int regcache_get_index_by_order(const struct regmap *map, } struct regmap_ram_data { + unsigned int base_reg; unsigned int *vals; /* Allocatd by caller */ bool *read; bool *written; diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 99b55b1053ee..715eb9e7aa2a 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -963,7 +963,7 @@ int regmap_add_irq_chip_fwnode(struct fwnode_handle *fwnode, return 0; err_domain: - /* Should really dispose of the domain but... */ + irq_domain_remove(d->domain); err_mutex: mutex_destroy(&d->lock); lockdep_unregister_key(&d->lock_key); diff --git a/drivers/base/regmap/regmap-kunit.c b/drivers/base/regmap/regmap-kunit.c index 2999d9c185d5..c78772b660d6 100644 --- a/drivers/base/regmap/regmap-kunit.c +++ b/drivers/base/regmap/regmap-kunit.c @@ -11,6 +11,8 @@ #define BLOCK_TEST_SIZE 12 +#define HIGH_BIT_REG ((UINT_MAX / 2) - 3) + KUNIT_DEFINE_ACTION_WRAPPER(regmap_exit_action, regmap_exit, struct regmap *); struct regmap_test_priv { @@ -23,6 +25,7 @@ struct regmap_test_param { enum regcache_type cache; enum regmap_endian val_endian; + unsigned int base_reg; unsigned int from_reg; bool fast_io; }; @@ -204,7 +207,8 @@ static struct regmap *gen_regmap(struct kunit *test, config->max_register += (BLOCK_TEST_SIZE * config->reg_stride); } - size = array_size(config->max_register + 1, sizeof(*buf)); + size = array_size(config->max_register - param->base_reg + 1, + sizeof(*buf)); buf = kmalloc(size, GFP_KERNEL); if (!buf) return ERR_PTR(-ENOMEM); @@ -215,6 +219,7 @@ static struct regmap *gen_regmap(struct kunit *test, if (!(*data)) goto out_free; (*data)->vals = buf; + (*data)->base_reg = param->base_reg; if (config->num_reg_defaults) { defaults = kunit_kcalloc(test, @@ -227,8 +232,8 @@ static struct regmap *gen_regmap(struct kunit *test, config->reg_defaults = defaults; for (i = 0; i < config->num_reg_defaults; i++) { - defaults[i].reg = param->from_reg + (i * config->reg_stride); - defaults[i].def = buf[param->from_reg + (i * config->reg_stride)]; + defaults[i].reg = param->from_reg - param->base_reg + (i * config->reg_stride); + defaults[i].def = buf[param->from_reg - param->base_reg + (i * config->reg_stride)]; } } @@ -1648,6 +1653,92 @@ static void cache_write_zero(struct kunit *test) KUNIT_ASSERT_FALSE(test, regcache_reg_cached(map, 1)); } +static const struct regmap_test_param high_bit_cache_types_list[] = { + { .cache = REGCACHE_RBTREE, .from_reg = HIGH_BIT_REG, + .base_reg = HIGH_BIT_REG }, + { .cache = REGCACHE_MAPLE, .from_reg = HIGH_BIT_REG, + .base_reg = HIGH_BIT_REG }, +}; + +KUNIT_ARRAY_PARAM(high_bit_cache_types, high_bit_cache_types_list, param_to_desc); + +/* + * Dynamically allocated cache types should support registers with the + * high bit set, the backend will return an error for any operation on + * a register below the expected window. + */ +static void cache_high_bit_reg(struct kunit *test) +{ + const struct regmap_test_param *param = test->param_value; + struct regmap_ram_data *data; + struct regmap_config config; + struct regmap *map; + unsigned int val[BLOCK_TEST_SIZE], rval; + int i; + + config = test_regmap_config; + + map = gen_regmap(test, &config, &data); + KUNIT_ASSERT_FALSE(test, IS_ERR(map)); + if (IS_ERR(map)) + return; + + get_random_bytes(&val, sizeof(val)); + + /* No defaults so no registers cached */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) + KUNIT_EXPECT_FALSE(test, regcache_reg_cached(map, param->from_reg + i)); + + /* Writes should reach the device */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) + KUNIT_EXPECT_EQ(test, 0, regmap_write(map, param->from_reg + i, + val[i])); + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_TRUE(test, data->written[i]); + KUNIT_EXPECT_EQ(test, val[i], data->vals[i]); + } + + /* Reads should be satisfied from the cache */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_TRUE(test, regcache_reg_cached(map, param->from_reg + i)); + KUNIT_EXPECT_EQ(test, 0, regmap_read(map, param->from_reg + i, + &rval)); + KUNIT_EXPECT_EQ(test, val[i], rval); + KUNIT_EXPECT_FALSE(test, data->read[i]); + } + + /* Trash the data on the device then resync */ + regcache_mark_dirty(map); + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + data->vals[i] = 0; + data->written[i] = false; + } + KUNIT_EXPECT_EQ(test, 0, regcache_sync(map)); + + /* Did we just write the correct data out? */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_TRUE(test, data->written[i]); + KUNIT_EXPECT_EQ(test, val[i], data->vals[i]); + } + + /* Drop some registers */ + KUNIT_EXPECT_EQ(test, 0, regcache_drop_region(map, param->from_reg + 3, + param->from_reg + 5)); + for (i = 0; i < BLOCK_TEST_SIZE; i++) + KUNIT_EXPECT_EQ(test, i < 3 || i > 5, + regcache_reg_cached(map, param->from_reg + i)); + + /* Reread and check only the dropped registers hit the device */ + for (i = 0; i < BLOCK_TEST_SIZE; i++) + data->read[i] = false; + for (i = 0; i < BLOCK_TEST_SIZE; i++) { + KUNIT_EXPECT_EQ(test, 0, regmap_read(map, param->from_reg + i, + &rval)); + KUNIT_EXPECT_EQ(test, val[i], rval); + KUNIT_EXPECT_EQ(test, i >= 3 && i <= 5, data->read[i]); + } +} + /* Check that caching the window register works with sync */ static void cache_range_window_reg(struct kunit *test) { @@ -2168,6 +2259,7 @@ static struct kunit_case regmap_test_cases[] = { KUNIT_CASE_PARAM(cache_drop_all_and_sync_has_defaults, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_present, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_write_zero, sparse_cache_types_gen_params), + KUNIT_CASE_PARAM(cache_high_bit_reg, high_bit_cache_types_gen_params), KUNIT_CASE_PARAM(cache_range_window_reg, real_cache_types_only_gen_params), KUNIT_CASE_PARAM(raw_read_defaults_single, raw_test_types_gen_params), diff --git a/drivers/base/regmap/regmap-ram.c b/drivers/base/regmap/regmap-ram.c index c7356b0d8c83..a666bfb4d667 100644 --- a/drivers/base/regmap/regmap-ram.c +++ b/drivers/base/regmap/regmap-ram.c @@ -20,8 +20,11 @@ static int regmap_ram_write(void *context, unsigned int reg, unsigned int val) { struct regmap_ram_data *data = context; - data->vals[reg] = val; - data->written[reg] = true; + if (reg < data->base_reg) + return -EINVAL; + + data->vals[reg - data->base_reg] = val; + data->written[reg - data->base_reg] = true; return 0; } @@ -30,8 +33,11 @@ static int regmap_ram_read(void *context, unsigned int reg, unsigned int *val) { struct regmap_ram_data *data = context; - *val = data->vals[reg]; - data->read[reg] = true; + if (reg < data->base_reg) + return -EINVAL; + + *val = data->vals[reg - data->base_reg]; + data->read[reg - data->base_reg] = true; return 0; } @@ -60,17 +66,25 @@ struct regmap *__regmap_init_ram(struct device *dev, const char *lock_name) { struct regmap *map; + unsigned int num_regs; if (!config->max_register) { pr_crit("No max_register specified for RAM regmap\n"); return ERR_PTR(-EINVAL); } - data->read = kzalloc_objs(bool, config->max_register + 1); + if (data->base_reg > config->max_register) { + pr_crit("base_reg above max_register for RAM regmap\n"); + return ERR_PTR(-EINVAL); + } + + num_regs = config->max_register - data->base_reg + 1; + + data->read = kzalloc_objs(bool, num_regs); if (!data->read) return ERR_PTR(-ENOMEM); - data->written = kzalloc_objs(bool, config->max_register + 1); + data->written = kzalloc_objs(bool, num_regs); if (!data->written) { kfree(data->read); return ERR_PTR(-ENOMEM); diff --git a/drivers/base/regmap/regmap-raw-ram.c b/drivers/base/regmap/regmap-raw-ram.c index 60d6e95cdd1b..c63c464549f9 100644 --- a/drivers/base/regmap/regmap-raw-ram.c +++ b/drivers/base/regmap/regmap-raw-ram.c @@ -16,14 +16,17 @@ #include "internal.h" -static unsigned int decode_reg(enum regmap_endian endian, const void *reg) +static unsigned int decode_reg(struct regmap_ram_data *data, const void *reg) { const u16 *r = reg; + unsigned int decode; - if (endian == REGMAP_ENDIAN_BIG) - return be16_to_cpu(*r); + if (data->reg_endian == REGMAP_ENDIAN_BIG) + decode = be16_to_cpu(*r); else - return le16_to_cpu(*r); + decode = le16_to_cpu(*r); + + return decode - data->base_reg; } static int regmap_raw_ram_gather_write(void *context, @@ -40,7 +43,7 @@ static int regmap_raw_ram_gather_write(void *context, if (val_len % 2) return -EINVAL; - r = decode_reg(data->reg_endian, reg); + r = decode_reg(data, reg); if (data->noinc_reg && data->noinc_reg(data, r)) { memcpy(&our_buf[r], val + val_len - 2, 2); data->written[r] = true; @@ -74,7 +77,7 @@ static int regmap_raw_ram_read(void *context, if (val_len % 2) return -EINVAL; - r = decode_reg(data->reg_endian, reg); + r = decode_reg(data, reg); if (data->noinc_reg && data->noinc_reg(data, r)) { for (i = 0; i < val_len; i += 2) memcpy(val + i, &our_buf[r], 2); @@ -114,6 +117,7 @@ struct regmap *__regmap_init_raw_ram(struct device *dev, const char *lock_name) { struct regmap *map; + unsigned int regs; if (config->reg_bits != 16) return ERR_PTR(-EINVAL); @@ -123,11 +127,12 @@ struct regmap *__regmap_init_raw_ram(struct device *dev, return ERR_PTR(-EINVAL); } - data->read = kzalloc_objs(bool, config->max_register + 1); + regs = config->max_register - data->base_reg + 1; + data->read = kzalloc_objs(bool, regs); if (!data->read) return ERR_PTR(-ENOMEM); - data->written = kzalloc_objs(bool, config->max_register + 1); + data->written = kzalloc_objs(bool, regs); if (!data->written) return ERR_PTR(-ENOMEM); diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e6e022b02637..98184a00097a 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -2722,7 +2722,7 @@ EXPORT_SYMBOL_GPL(regmap_multi_reg_write_bypassed); * If supported by the underlying bus the write will be scheduled * asynchronously, helping maximise I/O speed on higher speed buses * like SPI. regmap_async_complete() can be called to ensure that all - * asynchrnous writes have been completed. + * asynchronous writes have been completed. * * A value of zero will be returned on success, a negative errno will * be returned in error cases. diff --git a/drivers/bluetooth/btintel_pcie.c b/drivers/bluetooth/btintel_pcie.c index 6e6e2b19815c..2819e001797b 100644 --- a/drivers/bluetooth/btintel_pcie.c +++ b/drivers/bluetooth/btintel_pcie.c @@ -1099,6 +1099,11 @@ static void btintel_pcie_msix_tx_handle(struct btintel_pcie_data *data) txq = &data->txq; + if (cr_hia >= txq->count) { + bt_dev_err(data->hdev, "TXQ: invalid cr_hia %u", cr_hia); + return; + } + while (cr_tia != cr_hia) { data->tx_wait_done = true; wake_up(&data->tx_wait_q); @@ -1650,6 +1655,11 @@ static void btintel_pcie_msix_rx_handle(struct btintel_pcie_data *data) rxq = &data->rxq; + if (cr_hia >= rxq->count) { + bt_dev_err(hdev, "RXQ: invalid cr_hia %u", cr_hia); + return; + } + /* The firmware sends multiple CD in a single MSI-X and it needs to * process all received CDs in this interrupt. */ @@ -1657,6 +1667,12 @@ static void btintel_pcie_msix_rx_handle(struct btintel_pcie_data *data) urbd1 = &rxq->urbd1s[cr_tia]; ipc_print_urbd1(data->hdev, urbd1, cr_tia); + if (urbd1->frbd_tag >= rxq->count) { + bt_dev_err(hdev, "RXQ: invalid frbd_tag %u", + urbd1->frbd_tag); + return; + } + buf = &rxq->bufs[urbd1->frbd_tag]; if (!buf) { bt_dev_err(hdev, "RXQ: failed to get the DMA buffer for %d", diff --git a/drivers/bluetooth/btnxpuart.c b/drivers/bluetooth/btnxpuart.c index 25e7b41b349f..4e23d71d00e8 100644 --- a/drivers/bluetooth/btnxpuart.c +++ b/drivers/bluetooth/btnxpuart.c @@ -1388,9 +1388,11 @@ static int nxp_process_fw_dump(struct hci_dev *hdev, struct sk_buff *skb) msecs_to_jiffies(20000)); } - err = hci_devcd_append(hdev, skb_clone(skb, GFP_ATOMIC)); - if (err < 0) - goto free_skb; + if (IS_ENABLED(CONFIG_DEV_COREDUMP)) { + err = hci_devcd_append(hdev, skb_clone(skb, GFP_ATOMIC)); + if (err < 0) + goto free_skb; + } if (buf_len == 0) { bt_dev_warn(hdev, "==== FW dump complete ==="); diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 45365214fbef..fb24fd53f2e1 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -1210,8 +1210,7 @@ dpll_pin_ref_sync_state_set(struct dpll_pin *pin, struct dpll_device *dpll; int ret; - ref_sync_pin = xa_find(&pin->ref_sync_pins, &ref_sync_pin_idx, - ULONG_MAX, XA_PRESENT); + ref_sync_pin = xa_load(&pin->ref_sync_pins, ref_sync_pin_idx); if (!ref_sync_pin) { NL_SET_ERR_MSG(extack, "reference sync pin not found"); return -EINVAL; diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index e49d8a58be09..664952a67a11 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -1397,8 +1397,10 @@ static void iso_resource_auto_work(struct work_struct *work) } else { // Transit from allocation to reallocation, except if the client requested // deallocation in the meantime. - scoped_guard(spinlock_irq, &client->lock) - r->todo = ISO_RES_AUTO_REALLOC; + scoped_guard(spinlock_irq, &client->lock) { + if (r->todo == ISO_RES_AUTO_ALLOC) + r->todo = ISO_RES_AUTO_REALLOC; + } if (channel >= 0) r->params.channels_mask = BIT_ULL(channel); diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index bb93b24c659d..04015280ac5d 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -3505,7 +3505,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/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/gpio/gpio-arizona.c b/drivers/gpio/gpio-arizona.c index a7e98d395d8e..88cf013f0d90 100644 --- a/drivers/gpio/gpio-arizona.c +++ b/drivers/gpio/gpio-arizona.c @@ -115,8 +115,12 @@ static int arizona_gpio_direction_out(struct gpio_chip *chip, if (value) value = ARIZONA_GPN_LVL; - return regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, - ARIZONA_GPN_DIR | ARIZONA_GPN_LVL, value); + ret = regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, + ARIZONA_GPN_DIR | ARIZONA_GPN_LVL, value); + if (ret < 0 && (val & ARIZONA_GPN_DIR) && persistent) + pm_runtime_put_autosuspend(chip->parent); + + return ret; } static int arizona_gpio_set(struct gpio_chip *chip, unsigned int offset, diff --git a/drivers/gpio/gpio-mvebu.c b/drivers/gpio/gpio-mvebu.c index 93b8a08b04b9..a2796240fe14 100644 --- a/drivers/gpio/gpio-mvebu.c +++ b/drivers/gpio/gpio-mvebu.c @@ -1034,6 +1034,8 @@ static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state) static int mvebu_gpio_resume(struct platform_device *pdev) { struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev); + u32 edge_cache = ~0U, level_cache = ~0U; + unsigned long flags; int i; regmap_write(mvchip->regs, GPIO_OUT_OFF + mvchip->offset, @@ -1045,32 +1047,51 @@ static int mvebu_gpio_resume(struct platform_device *pdev) regmap_write(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset, mvchip->in_pol_reg); + /* + * genirq skips mask_irq() for a line it already considers masked, so + * unmasking one behind its back leaves an asserted level line that + * nobody masks. Restore only bits the irqchip cache still has set. + * + * Snapshot the caches under the raw spinlock, but release it before + * the regmap writes below: regmap_write() takes a sleepable lock on + * PREEMPT_RT. + */ + if (mvchip->domain) { + struct irq_chip_generic *gc; + + gc = irq_get_domain_generic_chip(mvchip->domain, 0); + raw_spin_lock_irqsave(&gc->lock, flags); + level_cache = gc->chip_types[0].mask_cache_priv; + edge_cache = gc->chip_types[1].mask_cache_priv; + raw_spin_unlock_irqrestore(&gc->lock, flags); + } + switch (mvchip->soc_variant) { case MVEBU_GPIO_SOC_VARIANT_ORION: case MVEBU_GPIO_SOC_VARIANT_A8K: regmap_write(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset, - mvchip->edge_mask_regs[0]); + mvchip->edge_mask_regs[0] & edge_cache); regmap_write(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset, - mvchip->level_mask_regs[0]); + mvchip->level_mask_regs[0] & level_cache); break; case MVEBU_GPIO_SOC_VARIANT_MV78200: for (i = 0; i < 2; i++) { regmap_write(mvchip->regs, GPIO_EDGE_MASK_MV78200_OFF(i), - mvchip->edge_mask_regs[i]); + mvchip->edge_mask_regs[i] & edge_cache); regmap_write(mvchip->regs, GPIO_LEVEL_MASK_MV78200_OFF(i), - mvchip->level_mask_regs[i]); + mvchip->level_mask_regs[i] & level_cache); } break; case MVEBU_GPIO_SOC_VARIANT_ARMADAXP: for (i = 0; i < 4; i++) { regmap_write(mvchip->regs, GPIO_EDGE_MASK_ARMADAXP_OFF(i), - mvchip->edge_mask_regs[i]); + mvchip->edge_mask_regs[i] & edge_cache); regmap_write(mvchip->regs, GPIO_LEVEL_MASK_ARMADAXP_OFF(i), - mvchip->level_mask_regs[i]); + mvchip->level_mask_regs[i] & level_cache); } break; default: diff --git a/drivers/gpio/gpio-tps65219.c b/drivers/gpio/gpio-tps65219.c index 457fd8a589e8..6958466455d0 100644 --- a/drivers/gpio/gpio-tps65219.c +++ b/drivers/gpio/gpio-tps65219.c @@ -79,7 +79,7 @@ static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset) if (ret) return ret; - ret = !!(val & BIT(TPS65219_MFP_GPIO_STATUS_MASK)); + ret = !!(val & TPS65219_MFP_GPIO_STATUS_MASK); dev_warn(dev, "GPIO%d = %d, MULTI_DEVICE_ENABLE, not a standard GPIO\n", offset, ret); /* @@ -87,7 +87,7 @@ static int tps65219_gpio_get(struct gpio_chip *gc, unsigned int offset) * status bit. */ - if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) + if (gc->get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) return -ENOTSUPP; return ret; @@ -158,8 +158,10 @@ static int tps65214_gpio_change_direction(struct gpio_chip *gc, unsigned int off if (ret) dev_err(dev, "GPIO%d configured as VSEL, not GPIO\n", offset); + val = direction == GPIO_LINE_DIRECTION_OUT ? + TPS65214_GPIO0_DIR_MASK : 0; ret = regmap_update_bits(gpio->tps->regmap, TPS65219_REG_GENERAL_CONFIG, - TPS65214_GPIO0_DIR_MASK, direction); + TPS65214_GPIO0_DIR_MASK, val); if (ret) dev_err(dev, "Fail to change direction to %u for GPIO%d.\n", direction, offset); @@ -176,7 +178,7 @@ static int tps65219_gpio_direction_input(struct gpio_chip *gc, unsigned int offs return -ENOTSUPP; } - if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_IN) + if (gc->get_direction(gc, offset) == GPIO_LINE_DIRECTION_IN) return 0; return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_IN); @@ -190,7 +192,7 @@ static int tps65219_gpio_direction_output(struct gpio_chip *gc, unsigned int off if (offset != TPS6521X_GPIO0_IDX) return 0; - if (tps65219_gpio_get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) + if (gc->get_direction(gc, offset) == GPIO_LINE_DIRECTION_OUT) return 0; return gpio->change_dir(gc, offset, GPIO_LINE_DIRECTION_OUT); diff --git a/drivers/gpio/gpio-zynq.c b/drivers/gpio/gpio-zynq.c index 15a79d9a2e9e..13e4c5f0e297 100644 --- a/drivers/gpio/gpio-zynq.c +++ b/drivers/gpio/gpio-zynq.c @@ -798,15 +798,7 @@ static int zynq_gpio_runtime_resume(struct device *dev) static int zynq_gpio_request(struct gpio_chip *chip, unsigned int offset) { - int ret; - - ret = pm_runtime_get_sync(chip->parent); - - /* - * If the device is already active pm_runtime_get() will return 1 on - * success, but gpio_request still needs to return 0. - */ - return ret < 0 ? ret : 0; + return pm_runtime_resume_and_get(chip->parent); } static void zynq_gpio_free(struct gpio_chip *chip, unsigned int offset) diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index d1105b7ae437..5d53bfcdf726 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -2172,18 +2172,19 @@ static void gpio_v2_line_info_changed_to_v1( #endif /* CONFIG_GPIO_CDEV_V1 */ -static void gpio_desc_to_lineinfo(struct gpio_desc *desc, - struct gpio_v2_line_info *info, bool atomic) +static int gpio_desc_to_lineinfo(struct gpio_desc *desc, + struct gpio_v2_line_info *info, bool atomic) { u32 debounce_period_us; unsigned long dflags; const char *label; + memset(info, 0, sizeof(*info)); + CLASS(gpio_chip_guard, guard)(desc); if (!guard.gc) - return; + return -ENODEV; - memset(info, 0, sizeof(*info)); info->offset = gpiod_hwgpio(desc); if (desc->name) @@ -2258,6 +2259,8 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc, debounce_period_us; info->num_attrs++; } + + return 0; } struct gpio_chardev_data { @@ -2309,6 +2312,7 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip, struct gpio_desc *desc; struct gpioline_info lineinfo; struct gpio_v2_line_info lineinfo_v2; + int ret; if (copy_from_user(&lineinfo, ip, sizeof(lineinfo))) return -EFAULT; @@ -2326,7 +2330,10 @@ static int lineinfo_get_v1(struct gpio_chardev_data *cdev, void __user *ip, return -EBUSY; } - gpio_desc_to_lineinfo(desc, &lineinfo_v2, false); + ret = gpio_desc_to_lineinfo(desc, &lineinfo_v2, false); + if (ret) + return ret; + gpio_v2_line_info_to_v1(&lineinfo_v2, &lineinfo); if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) { @@ -2344,6 +2351,7 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, { struct gpio_desc *desc; struct gpio_v2_line_info lineinfo; + int ret; if (copy_from_user(&lineinfo, ip, sizeof(lineinfo))) return -EFAULT; @@ -2363,7 +2371,10 @@ static int lineinfo_get(struct gpio_chardev_data *cdev, void __user *ip, if (test_and_set_bit(lineinfo.offset, cdev->watched_lines)) return -EBUSY; } - gpio_desc_to_lineinfo(desc, &lineinfo, false); + + ret = gpio_desc_to_lineinfo(desc, &lineinfo, false); + if (ret) + return ret; if (copy_to_user(ip, &lineinfo, sizeof(lineinfo))) { if (watch) @@ -2489,6 +2500,7 @@ static int lineinfo_changed_notify(struct notifier_block *nb, struct lineinfo_changed_ctx *ctx; struct gpio_desc *desc = data; struct file *fp; + int ret; if (!test_bit(gpiod_hwgpio(desc), cdev->watched_lines)) return NOTIFY_DONE; @@ -2519,7 +2531,13 @@ static int lineinfo_changed_notify(struct notifier_block *nb, ctx->chg.event_type = action; ctx->chg.timestamp_ns = ktime_get_ns(); - gpio_desc_to_lineinfo(desc, &ctx->chg.info, true); + + ret = gpio_desc_to_lineinfo(desc, &ctx->chg.info, true); + if (ret) { + fput(fp); + return NOTIFY_DONE; + } + /* Keep the GPIO device alive until we emit the event. */ ctx->gdev = gpio_device_get(desc->gdev); ctx->cdev = cdev; diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 66d2325bfae8..772c6f04f489 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -1029,6 +1029,13 @@ int gpiochip_add_hog(struct gpio_chip *gc, struct fwnode_handle *fwnode) ret = of_gpiochip_get_lflags(gc, &gpiospec, &lflags); if (ret) return ret; + + /* + * If no line-name property is present, fall back to the OF + * node name as in the previous implementation. + */ + if (!name) + name = to_of_node(fwnode)->name; } else { /* * GPIO_ACTIVE_LOW is currently the only lookup flag diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c index 7f5abb03be1b..8b8acf98fdfe 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acpi.c @@ -1167,8 +1167,10 @@ int amdgpu_acpi_enumerate_xcc(void) } xcc_info = kzalloc_obj(struct amdgpu_acpi_xcc_info); - if (!xcc_info) + if (!xcc_info) { + acpi_dev_put(acpi_dev); return -ENOMEM; + } INIT_LIST_HEAD(&xcc_info->list); xcc_info->handle = acpi_device_handle(acpi_dev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c index 132d054900b5..aca1a8045afa 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c @@ -1780,8 +1780,10 @@ static int amdgpu_debugfs_test_ib_show(struct seq_file *m, void *unused) /* Avoid accidently unparking the sched thread during GPU reset */ r = down_write_killable(&adev->reset_domain->sem); - if (r) + if (r) { + pm_runtime_put_autosuspend(dev->dev); return r; + } /* hold on the scheduler */ for (i = 0; i < AMDGPU_MAX_RINGS; i++) { diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c index f6b7522c3c82..f8652fd0525d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_eviction_fence.c @@ -68,6 +68,9 @@ amdgpu_eviction_fence_suspend_worker(struct work_struct *work) mutex_lock(&uq_mgr->userq_mutex); + /* Fence waits are not allowed in a fence signalling critical section. */ + amdgpu_userq_wait_for_signal(uq_mgr); + /* * This is intentionally after taking the userq_mutex since we do * allocate memory while holding this lock, but only after ensuring that diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index 686c92e96025..5922406d0a03 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -254,7 +254,6 @@ int amdgpu_ring_init(struct amdgpu_device *adev, struct amdgpu_ring *ring, ring->adev = adev; ring->num_hw_submission = sched_hw_submission; ring->sched_score = sched_score; - ring->vmid_wait = dma_fence_get_stub(); ring->idx = adev->num_rings++; adev->rings[ring->idx] = ring; @@ -374,6 +373,7 @@ int amdgpu_ring_init(struct amdgpu_device *adev, struct amdgpu_ring *ring, ring->max_dw = max_dw; ring->hw_prio = hw_prio; + ring->vmid_wait = dma_fence_get_stub(); if (!ring->no_scheduler && ring->funcs->type < AMDGPU_HW_IP_NUM) { hw_ip = ring->funcs->type; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c index e43bda0cab3f..cc8e7af18834 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.c @@ -184,27 +184,27 @@ static void amdgpu_userq_hang_detect_work(struct work_struct *work) void amdgpu_userq_start_hang_detect_work(struct amdgpu_usermode_queue *queue) { struct amdgpu_device *adev; - unsigned long timeout_ms; + unsigned long timeout_jiffies; adev = queue->userq_mgr->adev; /* Determine timeout based on queue type */ switch (queue->queue_type) { case AMDGPU_RING_TYPE_GFX: - timeout_ms = adev->gfx_timeout; + timeout_jiffies = adev->gfx_timeout; break; case AMDGPU_RING_TYPE_COMPUTE: - timeout_ms = adev->compute_timeout; + timeout_jiffies = adev->compute_timeout; break; case AMDGPU_RING_TYPE_SDMA: - timeout_ms = adev->sdma_timeout; + timeout_jiffies = adev->sdma_timeout; break; default: - timeout_ms = adev->gfx_timeout; + timeout_jiffies = adev->gfx_timeout; break; } queue_delayed_work(adev->reset_domain->wq, &queue->hang_detect_work, - msecs_to_jiffies(timeout_ms)); + timeout_jiffies); } void amdgpu_userq_process_fence_irq(struct amdgpu_device *adev, u32 doorbell) @@ -1272,7 +1272,7 @@ amdgpu_userq_evict_all(struct amdgpu_userq_mgr *uq_mgr) return ret; } -static void +void amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr) { struct amdgpu_usermode_queue *queue; @@ -1291,8 +1291,6 @@ amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr) void amdgpu_userq_evict(struct amdgpu_userq_mgr *uq_mgr) { - /* Wait for any pending userqueue fence work to finish */ - amdgpu_userq_wait_for_signal(uq_mgr); amdgpu_userq_evict_all(uq_mgr); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h index 6412a7f7b6ef..488dc21d7c81 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_userq.h @@ -162,6 +162,7 @@ void amdgpu_userq_mgr_cancel_reset_work(struct amdgpu_device *adev); void amdgpu_userq_mgr_cancel_resume(struct amdgpu_userq_mgr *userq_mgr); void amdgpu_userq_mgr_fini(struct amdgpu_userq_mgr *userq_mgr); +void amdgpu_userq_wait_for_signal(struct amdgpu_userq_mgr *uq_mgr); void amdgpu_userq_evict(struct amdgpu_userq_mgr *uq_mgr); void amdgpu_userq_ensure_ev_fence(struct amdgpu_userq_mgr *userq_mgr, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c index bb04101b0fb5..4a63b472f68e 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c @@ -2678,6 +2678,7 @@ error_free_root: amdgpu_bo_unref(&root_bo); error_free_delayed: + dma_fence_put(vm->last_update); dma_fence_put(vm->last_tlb_flush); dma_fence_put(vm->last_unlocked); ttm_lru_bulk_move_fini(&adev->mman.bdev, &vm->lru_bulk_move); diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c index 179b892fb410..62e2e04314dc 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v4_0_3.c @@ -1689,7 +1689,8 @@ static int vcn_v4_0_3_reset_jpeg_pre_helper(struct amdgpu_device *adev, int inst /* if Jobs are still pending after timeout, * We'll handle them in the bottom helper */ - amdgpu_fence_wait_polling(ring, wait_seq, adev->video_timeout); + amdgpu_fence_wait_polling(ring, wait_seq, + jiffies_to_usecs(adev->video_timeout)); } return 0; diff --git a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c index 1a07c3bf4425..011afc0fdc87 100644 --- a/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c +++ b/drivers/gpu/drm/amd/amdgpu/vcn_v5_0_1.c @@ -1335,7 +1335,8 @@ static int vcn_v5_0_1_reset_jpeg_pre_helper(struct amdgpu_device *adev, int inst /* if Jobs are still pending after timeout, * We'll handle them in the bottom helper */ - amdgpu_fence_wait_polling(ring, wait_seq, adev->video_timeout); + amdgpu_fence_wait_polling(ring, wait_seq, + jiffies_to_usecs(adev->video_timeout)); } return 0; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 504a286368eb..344da6c0e96a 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -35,6 +35,7 @@ #include <linux/time.h> #include <linux/mm.h> #include <linux/mman.h> +#include <linux/pseudo_fs.h> #include <linux/ptrace.h> #include <linux/dma-buf.h> #include <linux/processor.h> @@ -70,18 +71,54 @@ static const struct class kfd_class = { }; /* - * Cache the address space of the chardev on first open so that the reset - * path can drop all userspace mappings of doorbell and MMIO ranges via - * unmap_mapping_range(). + * Private pseudo-filesystem for KFD, Provides a stable, module-owned + * inode whose address_space is the unmap target for all /dev/kfd + * openers during GPU reset. */ -static struct address_space *kfd_dev_mapping; +static struct vfsmount *kfd_fs_mnt; +static int kfd_fs_cnt; -void kfd_dev_unmap_mapping_range(loff_t const holebegin, loff_t const holelen) +static int kfd_fs_init_fs_context(struct fs_context *fc) +{ + return init_pseudo(fc, 0x4b464400 /* "KFD" */) ? 0 : -ENOMEM; +} + +static struct file_system_type kfd_fs_type = { + .name = "kfd", + .init_fs_context = kfd_fs_init_fs_context, + .kill_sb = kill_anon_super, +}; + +static struct inode *kfd_fs_inode_new(void) { - struct address_space *mapping = READ_ONCE(kfd_dev_mapping); + struct inode *inode; + int r; + + r = simple_pin_fs(&kfd_fs_type, &kfd_fs_mnt, &kfd_fs_cnt); + if (r < 0) + return ERR_PTR(r); + + inode = alloc_anon_inode(kfd_fs_mnt->mnt_sb); + if (IS_ERR(inode)) + simple_release_fs(&kfd_fs_mnt, &kfd_fs_cnt); - if (mapping) - unmap_mapping_range(mapping, holebegin, holelen, 1); + return inode; +} + +static void kfd_fs_inode_free(struct inode *inode) +{ + if (inode) { + iput(inode); + simple_release_fs(&kfd_fs_mnt, &kfd_fs_cnt); + } +} + +static struct inode *kfd_anon_inode; + +void kfd_dev_unmap_mapping_range(loff_t const holebegin, loff_t const holelen) +{ + if (kfd_anon_inode) + unmap_mapping_range(kfd_anon_inode->i_mapping, holebegin, holelen, 1); } static inline struct kfd_process_device *kfd_lock_pdd_by_id(struct kfd_process *p, __u32 gpu_id) @@ -107,6 +144,13 @@ int kfd_chardev_init(void) { int err = 0; + kfd_anon_inode = kfd_fs_inode_new(); + if (IS_ERR(kfd_anon_inode)) { + err = PTR_ERR(kfd_anon_inode); + kfd_anon_inode = NULL; + return err; + } + kfd_char_dev_major = register_chrdev(0, kfd_dev_name, &kfd_fops); err = kfd_char_dev_major; if (err < 0) @@ -130,6 +174,8 @@ err_device_create: err_class_create: unregister_chrdev(kfd_char_dev_major, kfd_dev_name); err_register_chrdev: + kfd_fs_inode_free(kfd_anon_inode); + kfd_anon_inode = NULL; return err; } @@ -138,6 +184,8 @@ void kfd_chardev_exit(void) device_destroy(&kfd_class, MKDEV(kfd_char_dev_major, 0)); class_unregister(&kfd_class); unregister_chrdev(kfd_char_dev_major, kfd_dev_name); + kfd_fs_inode_free(kfd_anon_inode); + kfd_anon_inode = NULL; kfd_device = NULL; } @@ -150,12 +198,7 @@ static int kfd_open(struct inode *inode, struct file *filep) if (iminor(inode) != 0) return -ENODEV; - /* - * /dev/kfd is a single chardev so all opens share one inode. Cache - * its address_space on the first open for use by the reset path. - */ - if (!READ_ONCE(kfd_dev_mapping)) - cmpxchg(&kfd_dev_mapping, NULL, inode->i_mapping); + filep->f_mapping = kfd_anon_inode->i_mapping; is_32bit_user_mode = in_compat_syscall(); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 36d2f86f000a..91fdf3de7202 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -5583,8 +5583,10 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, skip_modeset: /* Release extra reference */ - if (new_stream) + if (new_stream) { dc_stream_release(new_stream); + new_stream = NULL; + } new_stream = NULL; /* diff --git a/drivers/gpu/drm/amd/display/dc/dml/Makefile b/drivers/gpu/drm/amd/display/dc/dml/Makefile index 10d4ace04d4f..91465ac05c97 100644 --- a/drivers/gpu/drm/amd/display/dc/dml/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml/Makefile @@ -29,14 +29,14 @@ dml_ccflags := $(CC_FLAGS_FPU) dml_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)),y) + ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else frame_warn_limit := 3072 endif else - frame_warn_limit := 2048 + frame_warn_limit := 3072 endif ifeq ($(call test-lt, $(CONFIG_FRAME_WARN), $(frame_warn_limit)),y) diff --git a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile index 5388bf094fbc..39ee2d1999dd 100644 --- a/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile +++ b/drivers/gpu/drm/amd/display/dc/dml2_0/Makefile @@ -28,14 +28,14 @@ dml2_ccflags := $(CC_FLAGS_FPU) dml2_rcflags := $(CC_FLAGS_NO_FPU) ifneq ($(CONFIG_FRAME_WARN),0) - ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)),y) + ifeq ($(filter y,$(CONFIG_KASAN)$(CONFIG_KCSAN)$(CONFIG_UBSAN)),y) ifeq ($(CONFIG_CC_IS_CLANG)$(CONFIG_COMPILE_TEST),yy) frame_warn_limit := 4096 else frame_warn_limit := 3072 endif else - frame_warn_limit := 2056 + frame_warn_limit := 3072 endif ifeq ($(call test-lt, $(CONFIG_FRAME_WARN), $(frame_warn_limit)),y) diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index e2fc69fc51b6..4694241f4d22 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -1862,7 +1862,7 @@ static int samsung_dsim_register_te_irq(struct samsung_dsim *dsi, struct device int te_gpio_irq; int ret; - dsi->te_gpio = devm_gpiod_get_optional(dev, "te", GPIOD_IN); + dsi->te_gpio = gpiod_get_optional(dev, "te", GPIOD_IN); if (!dsi->te_gpio) return 0; else if (IS_ERR(dsi->te_gpio)) diff --git a/drivers/gpu/drm/clients/drm_fbdev_client.c b/drivers/gpu/drm/clients/drm_fbdev_client.c index 91d196a397cf..1c16bc1084c4 100644 --- a/drivers/gpu/drm/clients/drm_fbdev_client.c +++ b/drivers/gpu/drm/clients/drm_fbdev_client.c @@ -42,6 +42,14 @@ static int drm_fbdev_client_restore(struct drm_client_dev *client, bool force) { struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); + /* + * The client is registered before the initial fbdev probe. + * If probing failed, the client remains registered but there + * is no valid fbdev framebuffer to restore. + */ + if (!fb_helper->info || !fb_helper->fb) + return 0; + drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper, force); return 0; diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c index 0673f16f6fd0..824ffeef0103 100644 --- a/drivers/gpu/drm/i915/display/intel_cursor.c +++ b/drivers/gpu/drm/i915/display/intel_cursor.c @@ -536,7 +536,8 @@ static void i9xx_cursor_disable_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + if (!crtc_state->enable_psr2_sel_fetch && + !crtc_state->clear_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_CUR_CTL(pipe), 0); @@ -569,8 +570,10 @@ static void i9xx_cursor_update_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + if (!crtc_state->enable_psr2_sel_fetch) { + i9xx_cursor_disable_sel_fetch_arm(dsb, plane, crtc_state); return; + } if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) { if (crtc_state->enable_psr2_su_region_et) { diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index 43d53a98dae7..f6a9b0de1ade 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -1187,6 +1187,8 @@ struct intel_crtc_state { bool has_sel_update; bool enable_psr2_sel_fetch; bool enable_psr2_su_region_et; + /* Drop the stale selective fetch enable bits as selective fetch is turned off */ + bool clear_psr2_sel_fetch; bool req_psr2_sdp_prior_scanline; bool has_panel_replay; bool link_off_after_as_sdp_when_pr_active; diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c index 98657aa4d3d5..abec3e2519ca 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_caps.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_caps.c @@ -3,6 +3,8 @@ * Copyright © 2026 Intel Corporation */ +#include <kunit/visibility.h> + #include <linux/bitops.h> #include <linux/debugfs.h> #include <linux/log2.h> @@ -1302,14 +1304,14 @@ void intel_dp_link_caps_cleanup(struct intel_dp_link_caps *link_caps) const struct intel_dp_link_caps_test_ops i915_display_dp_link_caps_test_ops = { INTEL_DP_LINK_CAPS_TEST_OPS_INIT }; -EXPORT_SYMBOL(i915_display_dp_link_caps_test_ops); +EXPORT_SYMBOL_IF_KUNIT(i915_display_dp_link_caps_test_ops); #else const struct intel_dp_link_caps_test_ops intel_display_dp_link_caps_test_ops = { INTEL_DP_LINK_CAPS_TEST_OPS_INIT }; -EXPORT_SYMBOL(intel_display_dp_link_caps_test_ops); +EXPORT_SYMBOL_IF_KUNIT(intel_display_dp_link_caps_test_ops); #endif /* I915 */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c index cb92cff90614..9a692f4fdfee 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -2825,14 +2825,14 @@ void intel_dp_link_training_cleanup(struct intel_dp_link_training *link_training const struct intel_dp_link_training_test_ops i915_display_dp_link_training_test_ops = { INTEL_DP_LINK_TRAINING_TEST_OPS_INIT }; -EXPORT_SYMBOL(i915_display_dp_link_training_test_ops); +EXPORT_SYMBOL_IF_KUNIT(i915_display_dp_link_training_test_ops); #else const struct intel_dp_link_training_test_ops intel_display_dp_link_training_test_ops = { INTEL_DP_LINK_TRAINING_TEST_OPS_INIT }; -EXPORT_SYMBOL(intel_display_dp_link_training_test_ops); +EXPORT_SYMBOL_IF_KUNIT(intel_display_dp_link_training_test_ops); #endif /* I915 */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index 57daed0b0b36..fb3942f56b1f 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -852,7 +852,8 @@ static u8 get_pipes_downstream_of_mst_port(struct intel_atomic_state *state, if (&connector->mst.dp->mst.mgr != mst_mgr) continue; - if (connector->mst.port != parent_port && + if (parent_port && + connector->mst.port != parent_port && !drm_dp_mst_port_downstream_of_parent(mst_mgr, connector->mst.port, parent_port)) @@ -2167,6 +2168,27 @@ bool intel_dp_mst_crtc_needs_modeset(struct intel_atomic_state *state, return false; } +bool intel_dp_mst_stream_disconnected(struct intel_atomic_state *state, + const struct intel_crtc *crtc) +{ + struct intel_connector *connector; + + connector = get_connector_in_state_for_crtc(state, crtc); + if (!connector) + return false; + + if (!connector->mst.dp) + return false; + + if (!connector->mst.dp->mst.mgr.mst_state) + return true; + + if (drm_connector_is_unregistered(&connector->base)) + return true; + + return false; +} + /** * intel_dp_mst_prepare_probe - Prepare an MST link for topology probing * @intel_dp: DP port object diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.h b/drivers/gpu/drm/i915/display/intel_dp_mst.h index ab09b487c6bb..8ce89242c05c 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.h +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.h @@ -28,6 +28,8 @@ int intel_dp_mst_atomic_check_link(struct intel_atomic_state *state, struct intel_link_bw_limits *limits); bool intel_dp_mst_crtc_needs_modeset(struct intel_atomic_state *state, struct intel_crtc *crtc); +bool intel_dp_mst_stream_disconnected(struct intel_atomic_state *state, + const struct intel_crtc *crtc); void intel_dp_mst_prepare_probe(struct intel_dp *intel_dp); bool intel_dp_mst_verify_dpcd_state(struct intel_dp *intel_dp); diff --git a/drivers/gpu/drm/i915/display/intel_link_bw.c b/drivers/gpu/drm/i915/display/intel_link_bw.c index b47474a3e9fe..e71e76d6fd3e 100644 --- a/drivers/gpu/drm/i915/display/intel_link_bw.c +++ b/drivers/gpu/drm/i915/display/intel_link_bw.c @@ -64,7 +64,8 @@ void intel_link_bw_init_limits(struct intel_atomic_state *state, intel_atomic_get_new_crtc_state(state, crtc); int forced_bpp_x16 = get_forced_link_bpp_x16(state, crtc); - if (state->base.duplicated && crtc_state) { + if ((state->base.duplicated && crtc_state) || + intel_dp_mst_stream_disconnected(state, crtc)) { limits->max_bpp_x16[pipe] = crtc_state->max_link_bpp_x16; if (intel_dsc_enabled_on_link(crtc_state)) limits->link_dsc_pipes |= BIT(pipe); diff --git a/drivers/gpu/drm/i915/display/intel_psr.c b/drivers/gpu/drm/i915/display/intel_psr.c index 40e3d7095996..c28bcb1a0184 100644 --- a/drivers/gpu/drm/i915/display/intel_psr.c +++ b/drivers/gpu/drm/i915/display/intel_psr.c @@ -2883,6 +2883,8 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, struct intel_crtc *crtc) { struct intel_display *display = to_intel_display(state); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); struct intel_crtc_state *crtc_state = intel_atomic_get_new_crtc_state(state, crtc); struct intel_plane_state *new_plane_state, *old_plane_state; struct intel_plane *plane; @@ -2895,6 +2897,19 @@ int intel_psr2_sel_fetch_update(struct intel_atomic_state *state, bool full_update = false, su_area_changed; int i, ret; + /* + * Selective fetch is not always usable, for instance it is dropped + * while pipe CRC is active. The planes keep their selective fetch + * enable bit set in hardware over that, and a plane disabled while + * selective fetch is off never gets the bit cleared. Once selective + * fetch comes back the hardware would resume fetching for a plane that + * is no longer enabled and keep its DDB range reserved, so have the + * plane update drop the bit for every plane of the pipe as selective + * fetch is turned off. + */ + crtc_state->clear_psr2_sel_fetch = old_crtc_state->enable_psr2_sel_fetch && + !crtc_state->enable_psr2_sel_fetch; + if (!crtc_state->enable_psr2_sel_fetch) return 0; diff --git a/drivers/gpu/drm/i915/display/intel_quirks.c b/drivers/gpu/drm/i915/display/intel_quirks.c index 33245f44c0d5..7d7db774d8c7 100644 --- a/drivers/gpu/drm/i915/display/intel_quirks.c +++ b/drivers/gpu/drm/i915/display/intel_quirks.c @@ -257,6 +257,9 @@ static struct intel_quirk intel_quirks[] = { /* Dell XPS 13 7390 2-in-1 */ { 0x8a52, 0x1028, 0x08b0, quirk_edp_limit_rate_hbr2 }, + /* HP Pavilion Plus Laptop 14-ew1xxx */ + { 0x7d55, 0x103c, 0x8c31, quirk_edp_limit_rate_hbr2 }, + /* Xiaomi Book Pro 14 2026 */ { 0xb081, 0x1d72, 0x2424, quirk_disable_psr2 }, }; diff --git a/drivers/gpu/drm/i915/display/skl_universal_plane.c b/drivers/gpu/drm/i915/display/skl_universal_plane.c index 07a683293352..eb5ed981b40f 100644 --- a/drivers/gpu/drm/i915/display/skl_universal_plane.c +++ b/drivers/gpu/drm/i915/display/skl_universal_plane.c @@ -885,7 +885,8 @@ static void icl_plane_disable_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) + if (!crtc_state->enable_psr2_sel_fetch && + !crtc_state->clear_psr2_sel_fetch) return; intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), 0); @@ -1634,10 +1635,8 @@ static void icl_plane_update_sel_fetch_arm(struct intel_dsb *dsb, struct intel_display *display = to_intel_display(plane); enum pipe pipe = plane->pipe; - if (!crtc_state->enable_psr2_sel_fetch) - return; - - if (drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) + if (crtc_state->enable_psr2_sel_fetch && + drm_rect_height(&plane_state->psr2_sel_fetch_area) > 0) intel_de_write_dsb(display, dsb, SEL_FETCH_PLANE_CTL(pipe, plane->id), SEL_FETCH_PLANE_CTL_ENABLE); else diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object.c b/drivers/gpu/drm/i915/gem/i915_gem_object.c index 5172d3982654..9e01f8b2079a 100644 --- a/drivers/gpu/drm/i915/gem/i915_gem_object.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_object.c @@ -89,6 +89,7 @@ struct drm_i915_gem_object *i915_gem_object_alloc(void) void i915_gem_object_free(struct drm_i915_gem_object *obj) { + dma_resv_fini(&obj->base._resv); return kmem_cache_free(slab_objects, obj); } @@ -144,7 +145,6 @@ void __i915_gem_object_fini(struct drm_i915_gem_object *obj) { mutex_destroy(&obj->mm.get_page.lock); mutex_destroy(&obj->mm.get_dma_page.lock); - dma_resv_fini(&obj->base._resv); } /** diff --git a/drivers/gpu/drm/imagination/pvr_free_list.c b/drivers/gpu/drm/imagination/pvr_free_list.c index e85cac83834c..faf5e586d8dc 100644 --- a/drivers/gpu/drm/imagination/pvr_free_list.c +++ b/drivers/gpu/drm/imagination/pvr_free_list.c @@ -8,6 +8,7 @@ #include "pvr_vm.h" #include <drm/drm_gem.h> +#include <drm/drm_print.h> #include <linux/slab.h> #include <linux/xarray.h> #include <uapi/drm/pvr_drm.h> @@ -612,13 +613,21 @@ pvr_free_list_process_reconstruct_req(struct pvr_device *pvr_dev, }; struct rogue_fwif_freelists_reconstruction_data *resp = &resp_cmd.cmd_data.free_lists_reconstruction_data; + u32 count = min_t(u32, req->freelist_count, + ARRAY_SIZE(req->freelist_ids)); - for (u32 i = 0; i < req->freelist_count; i++) + if (count != req->freelist_count) { + drm_warn_once(from_pvr_device(pvr_dev), + "Requested reconstruction of %u freelists, limiting to %u\n", + req->freelist_count, count); + } + + for (u32 i = 0; i < count; i++) pvr_free_list_reconstruct(pvr_dev, req->freelist_ids[i]); - resp->freelist_count = req->freelist_count; + resp->freelist_count = count; memcpy(resp->freelist_ids, req->freelist_ids, - req->freelist_count * sizeof(resp->freelist_ids[0])); + count * sizeof(resp->freelist_ids[0])); WARN_ON(pvr_kccb_send_cmd(pvr_dev, &resp_cmd, NULL)); } diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 3cac482e1034..62eae7fcd5a2 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -12,6 +12,7 @@ #include "pvr_rogue_mmu_defs.h" #include <drm/drm_drv.h> +#include <drm/drm_print.h> #include <linux/atomic.h> #include <linux/bitops.h> #include <linux/dma-mapping.h> @@ -2335,6 +2336,7 @@ void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx) * pvr_mmu_op_context_create() - Create an MMU op context. * @ctx: MMU context associated with owning VM context. * @sgt: Scatter gather table containing pages pinned for use by this context. + * @device_addr: Virtual device address at the start of the requested mapping. * @sgt_offset: Start offset of the requested device-virtual memory mapping. * @size: Size in bytes of the requested device-virtual memory mapping. For an * unmapping, this should be zero so that no page tables are allocated. @@ -2346,8 +2348,9 @@ void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx) */ struct pvr_mmu_op_context * pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, - u64 sgt_offset, u64 size) + u64 device_addr, u64 sgt_offset, u64 size) { + u64 start_addr = device_addr + sgt_offset; int err; struct pvr_mmu_op_context *op_ctx = kzalloc_obj(*op_ctx); @@ -2363,16 +2366,16 @@ pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, if (size) { /* * The number of page table objects we need to prealloc is - * indicated by the mapping size, start offset and the sizes + * indicated by the mapping size, start address and the sizes * of the areas mapped per PT or PD. The range calculation is * identical to that for the index into a table for a device * address, so we reuse those functions here. */ - const u32 l1_start_idx = pvr_page_table_l2_idx(sgt_offset); - const u32 l1_end_idx = pvr_page_table_l2_idx(sgt_offset + size); + const u32 l1_start_idx = pvr_page_table_l2_idx(start_addr); + const u32 l1_end_idx = pvr_page_table_l2_idx(start_addr + size); const u32 l1_count = l1_end_idx - l1_start_idx + 1; - const u32 l0_start_idx = pvr_page_table_l1_idx(sgt_offset); - const u32 l0_end_idx = pvr_page_table_l1_idx(sgt_offset + size); + const u32 l0_start_idx = pvr_page_table_l1_idx(start_addr); + const u32 l0_end_idx = pvr_page_table_l1_idx(start_addr + size); const u32 l0_count = l0_end_idx - l0_start_idx + 1; /* @@ -2553,7 +2556,9 @@ pvr_mmu_map_sgl(struct pvr_mmu_op_context *op_ctx, struct scatterlist *sgl, err_destroy_pages: memcpy(&op_ctx->curr_page, &ptr_copy, sizeof(op_ctx->curr_page)); - err = pvr_mmu_op_context_unmap_curr_page(op_ctx, page); + if (pvr_mmu_op_context_unmap_curr_page(op_ctx, page)) + drm_err(from_pvr_device(op_ctx->mmu_ctx->pvr_dev), + "%s : Failure in unmapping pages\n", __func__); return err; } diff --git a/drivers/gpu/drm/imagination/pvr_mmu.h b/drivers/gpu/drm/imagination/pvr_mmu.h index a8ecd460168d..2c02d61ba0a2 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.h +++ b/drivers/gpu/drm/imagination/pvr_mmu.h @@ -99,7 +99,7 @@ dma_addr_t pvr_mmu_get_root_table_dma_addr(struct pvr_mmu_context *ctx); void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx); struct pvr_mmu_op_context * pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, - struct sg_table *sgt, u64 sgt_offset, u64 size); + struct sg_table *sgt, u64 device_addr, u64 sgt_offset, u64 size); int pvr_mmu_map(struct pvr_mmu_op_context *op_ctx, u64 size, u64 flags, u64 device_addr); diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c index ceb78694cd98..55cc999f3708 100644 --- a/drivers/gpu/drm/imagination/pvr_vm.c +++ b/drivers/gpu/drm/imagination/pvr_vm.c @@ -276,7 +276,7 @@ pvr_vm_bind_op_map_init(struct pvr_vm_bind_op *bind_op, goto err_bind_op_fini; bind_op->mmu_op_ctx = - pvr_mmu_op_context_create(vm_ctx->mmu_ctx, sgt, offset, size); + pvr_mmu_op_context_create(vm_ctx->mmu_ctx, sgt, device_addr, offset, size); err = PTR_ERR_OR_ZERO(bind_op->mmu_op_ctx); if (err) { bind_op->mmu_op_ctx = NULL; @@ -318,7 +318,7 @@ pvr_vm_bind_op_unmap_init(struct pvr_vm_bind_op *bind_op, } bind_op->mmu_op_ctx = - pvr_mmu_op_context_create(vm_ctx->mmu_ctx, NULL, 0, 0); + pvr_mmu_op_context_create(vm_ctx->mmu_ctx, NULL, device_addr, 0, 0); err = PTR_ERR_OR_ZERO(bind_op->mmu_op_ctx); if (err) { bind_op->mmu_op_ctx = NULL; diff --git a/drivers/gpu/drm/nouveau/include/nvif/cl0080.h b/drivers/gpu/drm/nouveau/include/nvif/cl0080.h index ea8267e0d8da..9e639df1da46 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/cl0080.h +++ b/drivers/gpu/drm/nouveau/include/nvif/cl0080.h @@ -4,6 +4,7 @@ #define NV_DEVICE_V0_INFO 0x00 #define NV_DEVICE_V0_TIME 0x01 +#define NV_DEVICE_V0_GCX_READY 0x02 struct nv_device_info_v0 { __u8 version; @@ -55,6 +56,15 @@ struct nv_device_time_v0 { __u64 time; }; +#define NV_DEVICE_GC6_READY BIT(0) +#define NV_DEVICE_GCOFF_READY BIT(1) + +struct nv_device_gcx_ready_v0 { + __u8 version; + __u8 pad01[6]; + __u8 ready; +}; + #define NV_DEVICE_INFO_UNIT (0xffffffffULL << 32) #define NV_DEVICE_INFO(n) ((n) | (0x00000000ULL << 32)) #define NV_DEVICE_HOST(n) ((n) | (0x00000001ULL << 32)) diff --git a/drivers/gpu/drm/nouveau/include/nvif/device.h b/drivers/gpu/drm/nouveau/include/nvif/device.h index 7877a2a79da9..ce2fadcb05d9 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/device.h +++ b/drivers/gpu/drm/nouveau/include/nvif/device.h @@ -22,4 +22,5 @@ int nvif_device_ctor(struct nvif_client *, const char *name, struct nvif_device void nvif_device_dtor(struct nvif_device *); int nvif_device_map(struct nvif_device *); u64 nvif_device_time(struct nvif_device *); +int nvif_device_gcx_ready(struct nvif_device *); #endif diff --git a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h index 64fed208e4cf..ed5c6e0e68d3 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h @@ -157,6 +157,10 @@ struct nvkm_gsp { } sr; struct { + bool use_raw_mode_comptagline_alloc; + } memsys; + + struct { struct nvkm_gsp_mem mem; struct { @@ -495,6 +499,8 @@ nvkm_gsp_event_dtor(struct nvkm_gsp_event *event) int nvkm_gsp_intr_stall(struct nvkm_gsp *, enum nvkm_subdev_type, int); int nvkm_gsp_intr_nonstall(struct nvkm_gsp *, enum nvkm_subdev_type, int); +int nvkm_gsp_gcx_ready(struct nvkm_gsp *gsp); + int gv100_gsp_new(struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); int tu102_gsp_new(struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); int tu116_gsp_new(struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c index 0e8de6d4b36f..6dcb92575eb4 100644 --- a/drivers/gpu/drm/nouveau/nouveau_bo.c +++ b/drivers/gpu/drm/nouveau/nouveau_bo.c @@ -578,8 +578,9 @@ int nouveau_bo_pin_locked(struct nouveau_bo *nvbo, uint32_t domain, bool contig) "0x%08x vs 0x%08x\n", bo, bo->resource->mem_type, domain); ret = -EBUSY; + } else { + ttm_bo_pin(&nvbo->bo); } - ttm_bo_pin(&nvbo->bo); goto out; } diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index b0b0ad9a0c24..4cfc9c7c2ae0 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -600,8 +600,11 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) new_edid = drm_get_edid(connector, nv_encoder->i2c); } else { ret = nvif_outp_edid_get(&nv_encoder->outp, (u8 **)&new_edid); - if (ret < 0) + if (ret < 0) { + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); return connector_status_disconnected; + } } nouveau_connector_set_edid(nv_connector, new_edid); diff --git a/drivers/gpu/drm/nouveau/nouveau_dmem.c b/drivers/gpu/drm/nouveau/nouveau_dmem.c index ad4570c50be7..e74d7bb975a8 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dmem.c +++ b/drivers/gpu/drm/nouveau/nouveau_dmem.c @@ -339,8 +339,8 @@ nouveau_dmem_chunk_alloc(struct nouveau_drm *drm, struct page **ppage, chunk->pagemap.ops = &nouveau_dmem_pagemap_ops; chunk->pagemap.owner = drm->dev; - ret = nouveau_bo_new_pin(&drm->client, NOUVEAU_GEM_DOMAIN_VRAM, DMEM_CHUNK_SIZE, - &chunk->bo); + ret = nouveau_bo_new_pin(&drm->client, NOUVEAU_GEM_DOMAIN_VRAM, + DMEM_CHUNK_SIZE * NR_CHUNKS, &chunk->bo); if (ret) goto out_release; diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 4d1ad718e09b..2c7077a49888 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -585,6 +585,7 @@ nouveau_drm_device_fini(struct nouveau_drm *drm) if (nouveau_pmops_runtime()) { pm_runtime_get_sync(dev->dev); pm_runtime_forbid(dev->dev); + pm_runtime_dont_use_autosuspend(dev->dev); } nouveau_led_fini(dev); @@ -1148,6 +1149,7 @@ nouveau_pmops_runtime_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct nouveau_drm *drm = pci_get_drvdata(pdev); + struct nvif_device *nvif = &drm->client.device; int ret; if (!nouveau_pmops_runtime()) { @@ -1155,6 +1157,18 @@ nouveau_pmops_runtime_suspend(struct device *dev) return -EBUSY; } + // Check if the GPU itself is ready for runtime suspend, otherwise mark as busy and check + // again in a bit. + ret = nvif_device_gcx_ready(nvif); + if (ret < 0) { + NV_ERROR(drm, "Failed to query GCX readiness (returned %d)\n", ret); + return -EBUSY; + } else if (!(ret & NV_DEVICE_GCOFF_READY)) { + NV_DEBUG(drm, "GPU isn't ready for suspend yet, delaying...\n"); + pm_runtime_mark_last_busy(dev); + return -EBUSY; + } + nouveau_switcheroo_optimus_dsm(); ret = nouveau_do_suspend(drm, true); pci_save_state(pdev); @@ -1250,10 +1264,8 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv) mutex_unlock(&drm->clients_lock); done: - if (ret && cli) { - nouveau_cli_fini(cli); + if (ret && cli) kfree(cli); - } pm_runtime_mark_last_busy(dev->dev); pm_runtime_put_autosuspend(dev->dev); diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c index 0b7123b163e4..51188be57221 100644 --- a/drivers/gpu/drm/nouveau/nouveau_gem.c +++ b/drivers/gpu/drm/nouveau/nouveau_gem.c @@ -522,6 +522,7 @@ retry: if (unlikely(ret)) { if (ret != -ERESTARTSYS) NV_PRINTK(err, cli, "fail reserve\n"); + drm_gem_object_put(gem); break; } } @@ -531,6 +532,7 @@ retry: struct nouveau_vma *vma = nouveau_vma_find(nvbo, vmm); if (!vma) { NV_PRINTK(err, cli, "vma not found!\n"); + drm_gem_object_put(gem); ret = -EINVAL; break; } diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.c b/drivers/gpu/drm/nouveau/nouveau_sched.c index 8b9f935afe09..b3f02c490ecb 100644 --- a/drivers/gpu/drm/nouveau/nouveau_sched.c +++ b/drivers/gpu/drm/nouveau/nouveau_sched.c @@ -517,7 +517,7 @@ nouveau_sched_destroy(struct nouveau_sched **psched) struct nouveau_sched *sched = *psched; nouveau_sched_fini(sched); - kfree(sched); + kfree_rcu(sched, rcu); *psched = NULL; } diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h b/drivers/gpu/drm/nouveau/nouveau_sched.h index 20cd1da8db73..51ce8dcf6285 100644 --- a/drivers/gpu/drm/nouveau/nouveau_sched.h +++ b/drivers/gpu/drm/nouveau/nouveau_sched.h @@ -98,6 +98,7 @@ void nouveau_job_free(struct nouveau_job *job); struct nouveau_sched { struct drm_gpu_scheduler base; + struct rcu_head rcu; struct drm_sched_entity entity; struct workqueue_struct *wq; struct mutex mutex; diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index fc125fd44a9b..2026fe6b48c6 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -846,6 +846,9 @@ op_map(struct nouveau_uvma *uvma) { struct nouveau_bo *nvbo = nouveau_gem_object(uvma->va.gem.obj); + if (drm_gpuva_invalidated(&uvma->va)) + return; + nouveau_uvma_map(uvma, nouveau_mem(nvbo->bo.resource)); } @@ -1232,6 +1235,7 @@ bind_lock_validate(struct nouveau_job *job, struct drm_exec *exec, drm_gpuva_for_each_op(va_op, op->ops) { struct drm_gem_object *obj = op_gem_obj(va_op); + struct nouveau_bo *nvbo; if (unlikely(!obj)) continue; @@ -1246,8 +1250,13 @@ bind_lock_validate(struct nouveau_job *job, struct drm_exec *exec, if (va_op->op == DRM_GPUVA_OP_UNMAP) continue; - ret = nouveau_bo_validate(nouveau_gem_object(obj), - true, false); + nvbo = nouveau_gem_object(obj); + if (!(nvbo->valid_domains & + (NOUVEAU_GEM_DOMAIN_VRAM | NOUVEAU_GEM_DOMAIN_GART))) + return -EINVAL; + + nouveau_bo_placement_set(nvbo, nvbo->valid_domains, 0); + ret = nouveau_bo_validate(nvbo, true, false); if (ret) return ret; } diff --git a/drivers/gpu/drm/nouveau/nvif/device.c b/drivers/gpu/drm/nouveau/nvif/device.c index 24880931039f..1be9fbe6cb70 100644 --- a/drivers/gpu/drm/nouveau/nvif/device.c +++ b/drivers/gpu/drm/nouveau/nvif/device.c @@ -39,6 +39,19 @@ nvif_device_time(struct nvif_device *device) } int +nvif_device_gcx_ready(struct nvif_device *device) +{ + struct nv_device_gcx_ready_v0 args = {}; + int ret; + + ret = nvif_object_mthd(&device->object, NV_DEVICE_V0_GCX_READY, &args, sizeof(args)); + if (ret) + return ret; + + return args.ready; +} + +int nvif_device_map(struct nvif_device *device) { return nvif_object_map(&device->object, NULL, 0); diff --git a/drivers/gpu/drm/nouveau/nvif/vmm.c b/drivers/gpu/drm/nouveau/nvif/vmm.c index 65c3e883b119..579af70766f2 100644 --- a/drivers/gpu/drm/nouveau/nvif/vmm.c +++ b/drivers/gpu/drm/nouveau/nvif/vmm.c @@ -192,6 +192,7 @@ void nvif_vmm_dtor(struct nvif_vmm *vmm) { kfree(vmm->page); + vmm->page = NULL; nvif_object_dtor(&vmm->object); } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c index f2e9a06263ce..28702741a88b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/ctrl.c @@ -74,6 +74,7 @@ nvkm_control_mthd_pstate_attr(struct nvkm_control *ctrl, void *data, u32 size) const struct nvkm_domain *domain; struct nvkm_pstate *pstate; struct nvkm_cstate *cstate; + bool found = false; int i = 0, j = -1; u32 lo, hi; int ret = -ENOSYS; @@ -104,10 +105,15 @@ nvkm_control_mthd_pstate_attr(struct nvkm_control *ctrl, void *data, u32 size) if (args->v0.state != NVIF_CONTROL_PSTATE_ATTR_V0_STATE_CURRENT) { list_for_each_entry(pstate, &clk->states, head) { - if (i++ == args->v0.state) + if (i++ == args->v0.state) { + found = true; break; + } } + if (!found) + return -EINVAL; + lo = pstate->base.domain[domain->name]; hi = lo; list_for_each_entry(cstate, &pstate->list, head) { diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c index 23d11d8221cb..f78e6b9b4292 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/user.c @@ -27,6 +27,7 @@ #include <core/client.h> #include <subdev/fb.h> +#include <subdev/gsp.h> #include <subdev/instmem.h> #include <subdev/timer.h> @@ -190,6 +191,38 @@ nvkm_udevice_time(struct nvkm_udevice *udev, void *data, u32 size) } static int +nvkm_udevice_gcx_ready(struct nvkm_udevice *udev, void *data, u32 size) +{ + struct nvkm_object *object = &udev->object; + struct nvkm_device *device = udev->device; + struct nvkm_gsp *gsp = device->gsp; + union { + struct nv_device_gcx_ready_v0 v0; + } *args = data; + int ret = -ENOSYS; + + if (!gsp) { + args->v0.ready = NV_DEVICE_GC6_READY | NV_DEVICE_GCOFF_READY; + return 0; + } + + nvif_ioctl(object, "device gcx ready size %d\n", size); + ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false); + if (!ret) { + nvif_ioctl(object, "device gcx ready vers %d\n", args->v0.version); + + ret = nvkm_gsp_gcx_ready(gsp); + if (ret < 0) + return ret; + + args->v0.ready = ret; + ret = 0; + } + + return ret; +} + +static int nvkm_udevice_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) { struct nvkm_udevice *udev = nvkm_udevice(object); @@ -199,6 +232,8 @@ nvkm_udevice_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) return nvkm_udevice_info(udev, data, size); case NV_DEVICE_V0_TIME: return nvkm_udevice_time(udev, data, size); + case NV_DEVICE_V0_GCX_READY: + return nvkm_udevice_gcx_ready(udev, data, size); default: break; } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c index 377d0e0cef84..9887b3898505 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c @@ -253,8 +253,7 @@ nvkm_uoutp_mthd_hdmi(struct nvkm_outp *outp, void *argv, u32 argc) if (!ior->func->hdmi || args->v0.max_ac_packet > 0x1f || - args->v0.rekey > 0x7f || - (args->v0.scdc && !ior->func->hdmi->scdc)) + args->v0.rekey > 0x7f) return -EINVAL; if (!args->v0.enable) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c index 572e63846315..1cb83edc78dc 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/clk/base.c @@ -199,16 +199,18 @@ nvkm_cstate_prog(struct nvkm_clk *clk, struct nvkm_pstate *pstate, int cstatei) } if (volt) { - ret = nvkm_volt_set_id(volt, cstate->voltage, - pstate->base.voltage, clk->temp, -1); - if (ret && ret != -ENODEV) - nvkm_error(subdev, "failed to lower voltage: %d\n", ret); + int err = nvkm_volt_set_id(volt, cstate->voltage, + pstate->base.voltage, clk->temp, -1); + + if (err && err != -ENODEV) + nvkm_error(subdev, "failed to lower voltage: %d\n", err); } if (therm) { - ret = nvkm_therm_cstate(therm, pstate->fanspeed, -1); - if (ret && ret != -ENODEV) - nvkm_error(subdev, "failed to lower fan speed: %d\n", ret); + int err = nvkm_therm_cstate(therm, pstate->fanspeed, -1); + + if (err && err != -ENODEV) + nvkm_error(subdev, "failed to lower fan speed: %d\n", err); } return ret; @@ -270,13 +272,19 @@ nvkm_pstate_prog(struct nvkm_clk *clk, int pstatei) struct nvkm_fb *fb = subdev->device->fb; struct nvkm_pci *pci = subdev->device->pci; struct nvkm_pstate *pstate; + bool found = false; int ret, idx = 0; list_for_each_entry(pstate, &clk->states, head) { - if (idx++ == pstatei) + if (idx++ == pstatei) { + found = true; break; + } } + if (!found) + return -EINVAL; + nvkm_debug(subdev, "setting performance state %d\n", pstatei); clk->pstate = pstatei; @@ -473,6 +481,7 @@ static int nvkm_clk_ustate_update(struct nvkm_clk *clk, int req) { struct nvkm_pstate *pstate; + bool found = false; int i = 0; if (!clk->allow_reclock) @@ -480,12 +489,14 @@ nvkm_clk_ustate_update(struct nvkm_clk *clk, int req) if (req != -1 && req != -2) { list_for_each_entry(pstate, &clk->states, head) { - if (pstate->pstate == req) + if (pstate->pstate == req) { + found = true; break; + } i++; } - if (pstate->pstate != req) + if (!found) return -EINVAL; req = i; } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c index 18241c6ba5fa..4d52a158f320 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/fb/ramnv1a.c @@ -51,6 +51,8 @@ nv1a_ram_new(struct nvkm_fb *fb, struct nvkm_ram **pram) mib = ((mem >> 4) & 127) + 1; } + pci_dev_put(bridge); + return nvkm_ram_new_(&nv04_ram_func, fb, NVKM_RAM_TYPE_STOLEN, mib * 1024 * 1024, pram); } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c index 9ba1316831e7..e475d0e8fa7b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/base.c @@ -20,6 +20,7 @@ * OTHER DEALINGS IN THE SOFTWARE. */ #include "priv.h" +#include <nvif/cl0080.h> int nvkm_gsp_intr_nonstall(struct nvkm_gsp *gsp, enum nvkm_subdev_type type, int inst) @@ -47,6 +48,15 @@ nvkm_gsp_intr_stall(struct nvkm_gsp *gsp, enum nvkm_subdev_type type, int inst) return -ENOENT; } +int +nvkm_gsp_gcx_ready(struct nvkm_gsp *gsp) +{ + if (!gsp->rm->api->gsp->gcx_ready) + return NV_DEVICE_GC6_READY | NV_DEVICE_GCOFF_READY; + + return gsp->rm->api->gsp->gcx_ready(gsp); +} + static int nvkm_gsp_fini(struct nvkm_subdev *subdev, enum nvkm_suspend_state suspend) { diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h index 71b7203bef50..b07797813b04 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/priv.h @@ -93,6 +93,7 @@ void r535_gsp_dtor(struct nvkm_gsp *); int r535_gsp_oneinit(struct nvkm_gsp *); int r535_gsp_init(struct nvkm_gsp *); int r535_gsp_fini(struct nvkm_gsp *, enum nvkm_suspend_state suspend); +int r535_gsp_gcx_ready(struct nvkm_gsp *gsp); int nvkm_gsp_new_(const struct nvkm_gsp_fwif *, struct nvkm_device *, enum nvkm_subdev_type, int, struct nvkm_gsp **); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c index 94925f1590ea..63aa30f94747 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gsp.c @@ -1783,6 +1783,23 @@ r535_gsp_fini(struct nvkm_gsp *gsp, enum nvkm_suspend_state suspend) } int +r535_gsp_get_static_memsys_info(struct nvkm_gsp *gsp) +{ + NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_MEMSYS_GET_STATIC_CONFIG, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + gsp->memsys.use_raw_mode_comptagline_alloc = ctrl->bUseRawModeComptaglineAllocation; + + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.subdevice, ctrl); + return 0; +} + +int r535_gsp_init(struct nvkm_gsp *gsp) { int ret; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h index b6683a5bf870..7b10b7548c57 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/nvrm/gsp.h @@ -782,6 +782,51 @@ typedef struct NV2080_CTRL_INTERNAL_INTR_GET_KERNEL_TABLE_PARAMS { #define GSP_FW_HEAP_PARAM_CLIENT_ALLOC_SIZE ((48 << 10) * 2048) // Support 2048 channels +typedef struct NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS { + /*! Determines if RM should use 1 to 1 Comptagline allocation policy */ + NvBool bOneToOneComptagLineAllocation; + + /*! Determines if RM should use 1 to 4 Comptagline allocation policy */ + NvBool bUseOneToFourComptagLineAllocation; + + /*! Determines if RM should use raw Comptagline allocation policy */ + NvBool bUseRawModeComptaglineAllocation; + + /*! Has COMPBIT_BACKING_SIZE been overridden to zero (i.e. disabled)? */ + NvBool bDisableCompbitBacking; + + /*! Determine if we need to disable post L2 compression */ + NvBool bDisablePostL2Compression; + + /*! Is ECC DRAM feature supported? */ + NvBool bEnabledEccFBPA; + + NvBool bL2PreFill; + + /*! L2 cache size */ + NV_DECLARE_ALIGNED(NvU64 l2CacheSize, 8); + + /*! Indicate whether fpba is present or not */ + NvBool bFbpaPresent; + + /*! Size covered by one comptag */ + NvU32 comprPageSize; + + /*! log32(comprPageSize) */ + NvU32 comprPageShift; + + /*! RAM type */ + NvU32 ramType; + + /*! LTC count */ + NvU32 ltcCount; + + /*! LTS per LTC count */ + NvU32 ltsPerLtcCount; +} NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS; + +#define NV2080_CTRL_CMD_INTERNAL_MEMSYS_GET_STATIC_CONFIG (0x20800a1c) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_MEMSYS_GET_STATIC_CONFIG_PARAMS_MESSAGE_ID" */ + typedef union rpc_message_rpc_union_field_v03_00 { NvU32 spare; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c index af5aa5065c3d..469e7eed1d6f 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/fbsr.c @@ -26,6 +26,52 @@ r570_fbsr_suspend_channels(struct nvkm_gsp *gsp, bool suspend) return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); } +static int +r570_fb_get_compbit_store_size(struct nvkm_gsp *gsp, u64 *size) +{ + NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS *ctrl; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.object, + NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + *size = ctrl->Size; + + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.object, ctrl); + return 0; +} + +static int +r570_memsys_enable_raw_comp_mode(struct nvkm_gsp *gsp, bool enable) +{ + NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS *ctrl; + int ret; + + ctrl = nvkm_gsp_rm_ctrl_get(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + ctrl->bRawMode = enable; + + ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); + if (!ret) + nvkm_debug(&gsp->subdev, "memsys: Raw compression mode %s\n", + str_enabled_disabled(enable)); + + return ret; +} + +static bool +r570_need_raw_comp_war(struct nvkm_gsp *gsp, struct nvkm_device *device) +{ + return (device->card_type == GA100 || device->card_type == AD100) && + gsp->memsys.use_raw_mode_comptagline_alloc; +} + static void r570_fbsr_resume(struct nvkm_gsp *gsp) { @@ -33,6 +79,7 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) struct nvkm_instmem *imem = device->imem; struct nvkm_instobj *iobj; struct nvkm_vmm *vmm; + int ret; /* Restore BAR2 page tables via BAR0 window, and re-enable BAR2. */ list_for_each_entry(iobj, &imem->boot, head) { @@ -54,6 +101,13 @@ r570_fbsr_resume(struct nvkm_gsp *gsp) vmm = nvkm_bar_bar1_vmm(device); vmm->func->flush(vmm, 0); + /* Re-enable raw mode if it was previously disabled */ + if (r570_need_raw_comp_war(gsp, device)) { + ret = r570_memsys_enable_raw_comp_mode(gsp, true); + if (ret) + nvkm_error(&gsp->subdev, "Failed to re-enable raw comp mode\n"); + } + /* Resume channel scheduling. */ r570_fbsr_suspend_channels(device->gsp, false); @@ -81,7 +135,7 @@ r570_fbsr_init(struct nvkm_gsp *gsp, struct sg_table *sgt, u64 size) ctrl->hClient = gsp->internal.client.object.handle; ctrl->hSysMem = memlist.handle; ctrl->sysmemAddrOfSuspendResumeData = gsp->sr.meta.addr; - ctrl->bEnteringGcoffState = 0; + ctrl->bEnteringGcoffState = 1; ret = nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl); if (ret) @@ -98,12 +152,29 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) struct nvkm_device *device = subdev->device; struct nvkm_instmem *imem = device->imem; struct nvkm_instobj *iobj; - u64 size; + u64 size, compbit_store_size; int ret; /* Stop channel scheduling. */ r570_fbsr_suspend_channels(gsp, true); + /* Temporarily disable raw mode to prevent FBSR restore operations from corrupting + * compressed surfaces. Required for ampere and ada. + * + * Nvidia bug #3172217 + */ + if (r570_need_raw_comp_war(gsp, device)) { + ret = r570_memsys_enable_raw_comp_mode(gsp, false); + if (ret) + return ret; + } + + ret = r570_fb_get_compbit_store_size(gsp, &compbit_store_size); + if (ret < 0) + return ret; + nvkm_debug(&gsp->subdev, "fbsr: Compbit backing store size: 0x%llx bytes\n", + compbit_store_size); + /* Save BAR2 allocations to system memory. */ list_for_each_entry(iobj, &imem->list, head) { if (iobj->preserve) { @@ -126,6 +197,8 @@ r570_fbsr_suspend(struct nvkm_gsp *gsp) size = gsp->fb.heap.size; size += gsp->fb.rsvd_size; size += gsp->fb.bios.vga_workspace.size; + size += compbit_store_size; + nvkm_debug(subdev, "fbsr: size: 0x%llx bytes\n", size); ret = nvkm_gsp_sg(device, size, &gsp->sr.fbsr); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c index b45781cd0dfd..ea38a94211f4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c @@ -10,6 +10,7 @@ #include "nvrm/gsp.h" #include "nvrm/rpcfn.h" #include "nvrm/msgfn.h" +#include "nvif/cl0080.h" #include <core/pci.h> #include <subdev/pci/priv.h> @@ -137,6 +138,14 @@ r570_gsp_get_static_info(struct nvkm_gsp *gsp) } } + ret = r535_gsp_get_static_memsys_info(gsp); + if (ret) { + nvkm_error(&gsp->subdev, "Retrieving static memsys info failed\n"); + return ret; + } + nvkm_debug(&gsp->subdev, "memsys: Use raw mode for comptag allocations? %s\n", + str_yes_no(gsp->memsys.use_raw_mode_comptagline_alloc)); + return 0; } @@ -215,6 +224,32 @@ r570_gsp_set_rmargs(struct nvkm_gsp *gsp, bool resume) args->bDmemStack = 1; } +int +r570_gsp_gcx_ready(struct nvkm_gsp *gsp) +{ + NV2080_CTRL_INTERNAL_GCX_ENTRY_PREREQUISITE_PARAMS *ctrl; + int ret = 0; + + ctrl = nvkm_gsp_rm_ctrl_rd(&gsp->internal.device.subdevice, + NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE, + sizeof(*ctrl)); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl); + + if (ctrl->bIsGC6Satisfied) + ret |= NV_DEVICE_GC6_READY; + if (ctrl->bIsGCOFFSatisfied) + ret |= NV_DEVICE_GCOFF_READY; + + nvkm_debug(&gsp->subdev, + "GCX ready status: GC6=%s GCOFF=%s\n", + str_yes_no(ctrl->bIsGC6Satisfied), str_yes_no(ctrl->bIsGCOFFSatisfied)); + + nvkm_gsp_rm_ctrl_done(&gsp->internal.device.subdevice, ctrl); + return ret; +} + + const struct nvkm_rm_api_gsp r570_gsp = { .set_rmargs = r570_gsp_set_rmargs, @@ -223,4 +258,5 @@ r570_gsp = { .xlat_mc_engine_idx = r570_gsp_xlat_mc_engine_idx, .drop_post_nocat_record = r570_gsp_drop_post_nocat_record, .sr_data_size = r570_gsp_sr_data_size, + .gcx_ready = r570_gsp_gcx_ready, }; diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h index 8af432375f7a..cb3e448415b4 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/fbsr.h @@ -16,4 +16,33 @@ typedef struct NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS { NV_DECLARE_ALIGNED(NvU64 sysmemAddrOfSuspendResumeData, 8); } NV2080_CTRL_INTERNAL_FBSR_INIT_PARAMS; +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO (0x801306) /* finn: Evaluated from "(FINN_NV01_DEVICE_0_FB_INTERFACE_ID << 8) | NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS_MESSAGE_ID" */ + +typedef struct NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS { + NV_DECLARE_ALIGNED(NvU64 Size, 8); + NV_DECLARE_ALIGNED(NvU64 Address, 8); + NvU32 AddressSpace; + NvU32 MaxCompbitLine; + NvU32 comptagsPerCacheLine; + NvU32 cacheLineSize; + NvU32 cacheLineSizePerSlice; + NvU32 cacheLineFetchAlignment; + NV_DECLARE_ALIGNED(NvU64 backingStoreBase, 8); + NvU32 gobsPerComptagPerSlice; + NvU32 backingStoreCbcBase; + NvU32 comptaglineAllocationPolicy; + NV_DECLARE_ALIGNED(NvU64 privRegionStartOffset, 8); + NvU32 cbcCoveragePerSlice; +} NV0080_CTRL_FB_GET_COMPBIT_STORE_INFO_PARAMS; + +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO_ADDRESS_SPACE_UNKNOWN 0 // ADDR_UNKNOWN +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO_ADDRESS_SPACE_SYSMEM 1 // ADDR_SYSMEM +#define NV0080_CTRL_CMD_FB_GET_COMPBIT_STORE_INFO_ADDRESS_SPACE_FBMEM 2 // ADDR_FBMEM + +#define NV2080_CTRL_CMD_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE (0x20800a6f) /* finn: Evaluated from "(FINN_NV20_SUBDEVICE_0_INTERNAL_INTERFACE_ID << 8) | NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS_MESSAGE_ID" */ + +typedef struct NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS { + NvBool bRawMode; +} NV2080_CTRL_INTERNAL_MEMSYS_PROGRAM_RAW_COMPRESSION_MODE_PARAMS; + #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h index c458569af9d7..2814629fddd2 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/gsp.h @@ -639,4 +639,11 @@ typedef struct GSP_FMC_BOOT_PARAMS } GSP_FMC_BOOT_PARAMS; #define GSP_FW_HEAP_PARAM_BASE_RM_SIZE_GH100 (14 << 20) // Hopper+ + +#define NV2080_CTRL_CMD_INTERNAL_GCX_ENTRY_PREREQUISITE (0x2080a7d7) + +typedef struct NV2080_CTRL_INTERNAL_GCX_ENTRY_PREREQUISITE_PARAMS { + NvBool bIsGC6Satisfied; + NvBool bIsGCOFFSatisfied; +} NV2080_CTRL_INTERNAL_GCX_ENTRY_PREREQUISITE_PARAMS; #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h index e9ac47d86b69..17480d4e527a 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h @@ -40,6 +40,7 @@ struct nvkm_rm_api { void (*drop_send_user_shared_data)(struct nvkm_gsp *); void (*drop_post_nocat_record)(struct nvkm_gsp *); u32 (*sr_data_size)(struct nvkm_gsp *); + int (*gcx_ready)(struct nvkm_gsp *gsp); } *gsp; const struct nvkm_rm_api_rpc { @@ -174,6 +175,8 @@ int r535_gr_chan_new(struct nvkm_gr *, struct nvkm_chan *, const struct nvkm_ocl int r535_gr_promote_ctx(struct r535_gr *, bool golden, struct nvkm_vmm *, struct nvkm_memory **pctxbuf_mem, struct nvkm_vma **pctxbuf_vma, struct nvkm_gsp_object *chan); +int r570_gsp_gcx_ready(struct nvkm_gsp *gsp); +int r535_gsp_get_static_memsys_info(struct nvkm_gsp *gsp); extern const struct nvkm_rm_api_engine r535_nvdec; extern const struct nvkm_rm_api_engine r535_nvenc; extern const struct nvkm_rm_api_engine r535_nvjpg; diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h index 9df4c7117341..f3bbbe4468bf 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.h +++ b/drivers/gpu/drm/virtio/virtgpu_drv.h @@ -114,6 +114,8 @@ struct virtio_gpu_object { bool dumb; bool created; bool attached; + /* a guest-bound transfer is queued and its mapping not yet synced */ + bool from_host_pending; bool host3d_blob, guest_blob; uint32_t blob_mem, blob_flags; @@ -196,6 +198,9 @@ struct virtio_gpu_vbuffer { struct list_head list; uint32_t seqno; + + /* guest-bound transfer whose shmem backing needs a CPU sync */ + bool sync_for_cpu; }; struct virtio_gpu_output { diff --git a/drivers/gpu/drm/virtio/virtgpu_gem.c b/drivers/gpu/drm/virtio/virtgpu_gem.c index 66c3f6f74e9c..d2f0b8a3f172 100644 --- a/drivers/gpu/drm/virtio/virtgpu_gem.c +++ b/drivers/gpu/drm/virtio/virtgpu_gem.c @@ -45,7 +45,7 @@ static int virtio_gpu_gem_create(struct drm_file *file, ret = drm_gem_handle_create(file, &obj->base.base, &handle); if (ret) { - drm_gem_object_release(&obj->base.base); + drm_gem_object_put(&obj->base.base); return ret; } diff --git a/drivers/gpu/drm/virtio/virtgpu_ioctl.c b/drivers/gpu/drm/virtio/virtgpu_ioctl.c index 3d8e4ccdb7c1..81e70a12b356 100644 --- a/drivers/gpu/drm/virtio/virtgpu_ioctl.c +++ b/drivers/gpu/drm/virtio/virtgpu_ioctl.c @@ -185,7 +185,7 @@ static int virtio_gpu_resource_create_ioctl(struct drm_device *dev, void *data, ret = drm_gem_handle_create(file, obj, &handle); if (ret) { - drm_gem_object_release(obj); + drm_gem_object_put(obj); return ret; } @@ -261,6 +261,27 @@ static int virtio_gpu_transfer_from_host_ioctl(struct drm_device *dev, if (ret != 0) goto err_put_free; + if (virtio_gpu_is_shmem(bo) && virtio_gpu_use_dma_api(vgdev->vdev)) { + /* + * The sync on completion restores the whole mapping, so an + * earlier transfer has to be done before this one snapshots it. + * Otherwise the snapshot predates anything the CPU wrote once + * that transfer's fence signalled, and the later sync would + * discard it. Nothing can add a fence behind our back here, + * since doing so takes the reservation we already hold. + * This writes the pages, so it waits as a writer does. READ + * usage covers existing readers. + */ + long wait = dma_resv_wait_timeout(objs->objs[0]->resv, + DMA_RESV_USAGE_READ, true, + MAX_SCHEDULE_TIMEOUT); + + if (wait < 0) { + ret = wait; + goto err_unlock; + } + } + fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context, 0); if (!fence) { ret = -ENOMEM; @@ -320,6 +341,28 @@ static int virtio_gpu_transfer_to_host_ioctl(struct drm_device *dev, void *data, if (ret != 0) goto err_put_free; + /* + * A transfer the other way may have queued without yet syncing + * its mapping. Pushing the guest pages into it now would + * discard what the device wrote there, so wait for that sync: + * it runs before the fence it belongs to is signalled. The + * flag is only set under this reservation, so it cannot appear + * behind our back, and the acquire pairs with the release in + * that sync, so finding it clear means the pages it wrote are + * visible here too. + */ + if (smp_load_acquire(&bo->from_host_pending)) { + long wait = dma_resv_wait_timeout(objs->objs[0]->resv, + DMA_RESV_USAGE_WRITE, + true, + MAX_SCHEDULE_TIMEOUT); + + if (wait < 0) { + ret = wait; + goto err_unlock; + } + } + ret = -ENOMEM; fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context, 0); @@ -557,14 +600,14 @@ static int virtio_gpu_resource_create_blob_ioctl(struct drm_device *dev, if (params.blob_flags & VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE) { ret = virtio_gpu_resource_assign_uuid(vgdev, bo); if (ret) { - drm_gem_object_release(obj); + drm_gem_object_put(obj); return ret; } } ret = drm_gem_handle_create(file, obj, &handle); if (ret) { - drm_gem_object_release(obj); + drm_gem_object_put(obj); return ret; } diff --git a/drivers/gpu/drm/virtio/virtgpu_plane.c b/drivers/gpu/drm/virtio/virtgpu_plane.c index 640815af4098..b422eba42a5f 100644 --- a/drivers/gpu/drm/virtio/virtgpu_plane.c +++ b/drivers/gpu/drm/virtio/virtgpu_plane.c @@ -589,6 +589,7 @@ struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev, struct drm_plane *plane; const uint32_t *formats; int nformats; + int ret; if (type == DRM_PLANE_TYPE_CURSOR) { formats = virtio_gpu_cursor_formats; @@ -614,5 +615,17 @@ struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev, drm_plane_create_blend_mode_property(plane, BIT(DRM_MODE_BLEND_PREMULTI)); + if (type == DRM_PLANE_TYPE_CURSOR) { + /* + * The cursor plane exposes a format with an alpha channel, + * which requires a blend mode property. The host blends + * premultiplied alpha, matching the property's default. + */ + ret = drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PREMULTI)); + if (ret) + return ERR_PTR(ret); + } + return plane; } diff --git a/drivers/gpu/drm/virtio/virtgpu_prime.c b/drivers/gpu/drm/virtio/virtgpu_prime.c index 149e6bcb5878..ebf471044d06 100644 --- a/drivers/gpu/drm/virtio/virtgpu_prime.c +++ b/drivers/gpu/drm/virtio/virtgpu_prime.c @@ -349,7 +349,7 @@ struct drm_gem_object *virtgpu_gem_prime_import(struct drm_device *dev, } } - if (!vgdev->has_resource_blob) + if (!vgdev->has_resource_blob || vgdev->has_virgl_3d) return drm_gem_prime_import(dev, buf); bo = kzalloc_obj(*bo); diff --git a/drivers/gpu/drm/virtio/virtgpu_submit.c b/drivers/gpu/drm/virtio/virtgpu_submit.c index 32cb1e4aa425..3d35326dd904 100644 --- a/drivers/gpu/drm/virtio/virtgpu_submit.c +++ b/drivers/gpu/drm/virtio/virtgpu_submit.c @@ -389,10 +389,13 @@ static int virtio_gpu_init_submit(struct virtio_gpu_submit *submit, if ((exbuf->flags & VIRTGPU_EXECBUF_FENCE_FD_OUT) || exbuf->num_out_syncobjs || exbuf->num_bo_handles || - drm_fence_event) + drm_fence_event) { out_fence = virtio_gpu_fence_alloc(vgdev, fence_ctx, ring_idx); - else + if (!out_fence) + return -ENOMEM; + } else { out_fence = NULL; + } if (drm_fence_event) { err = virtio_gpu_fence_event_create(dev, file, out_fence, ring_idx); @@ -538,6 +541,10 @@ int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data, virtio_gpu_process_post_deps(&submit); virtio_gpu_complete_submit(&submit); cleanup: + if (ret && submit.out_fence && submit.out_fence->e) { + drm_event_cancel_free(dev, &submit.out_fence->e->base); + submit.out_fence->e = NULL; + } virtio_gpu_cleanup_submit(&submit); return ret; diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c index c02c03c10d92..d99fb9e326e8 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vq.c +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c @@ -256,6 +256,33 @@ void virtio_gpu_dequeue_ctrl_func(struct work_struct *work) } while (!virtqueue_enable_cb(vgdev->ctrlq.vq)); spin_unlock(&vgdev->ctrlq.qlock); + /* + * Sync guest-bound transfers before signalling anything, so that a + * waiter cannot read the backing pages while what the device wrote is + * still in a bounce buffer. This cannot be folded into the loop below: + * virtio_gpu_fence_event_process() also signals every earlier fence in + * the same context, so any entry there may signal this entry's fence. + */ + list_for_each_entry(entry, &reclaim_list, list) { + if (entry->sync_for_cpu) { + struct virtio_gpu_object *bo = + gem_to_virtio_gpu_obj(entry->objs->objs[0]); + + dma_sync_sgtable_for_cpu(vgdev->vdev->dev.parent, + bo->base.sgt, DMA_FROM_DEVICE); + /* + * Release, so a transfer the other way that skips its + * wait on the strength of this cannot go on to read + * the backing pages before the sync above is visible. + * Nothing orders the two otherwise: where the mapping + * bounces on a coherent device the sync is a plain + * copy, and dma_direct_sync_sg_for_cpu() emits its + * barrier only for the non-coherent case. + */ + smp_store_release(&bo->from_host_pending, false); + } + } + list_for_each_entry(entry, &reclaim_list, list) { resp = (struct virtio_gpu_ctrl_hdr *)entry->resp_buf; @@ -1278,12 +1305,31 @@ void virtio_gpu_cmd_transfer_from_host_3d(struct virtio_gpu_device *vgdev, struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(objs->objs[0]); struct virtio_gpu_transfer_host_3d *cmd_p; struct virtio_gpu_vbuffer *vbuf; + bool use_dma_api = virtio_gpu_use_dma_api(vgdev->vdev); cmd_p = virtio_gpu_alloc_cmd(vgdev, &vbuf, sizeof(*cmd_p)); memset(cmd_p, 0, sizeof(*cmd_p)); vbuf->objs = objs; + if (virtio_gpu_is_shmem(bo) && use_dma_api) { + /* + * The device writes only the requested box, so prime the + * mapping with the current contents: otherwise the sync on + * completion would hand back whatever a bounce buffer held for + * the regions the device does not touch. + */ + dma_sync_sgtable_for_device(vgdev->vdev->dev.parent, + bo->base.sgt, DMA_TO_DEVICE); + vbuf->sync_for_cpu = true; + /* + * Set under the reservation the caller holds, so a transfer + * the other way cannot miss it and push the guest pages into + * the mapping while the device still owns it. + */ + WRITE_ONCE(bo->from_host_pending, true); + } + cmd_p->hdr.type = cpu_to_le32(VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D); cmd_p->hdr.ctx_id = cpu_to_le32(ctx_id); cmd_p->resource_id = cpu_to_le32(bo->hw_res_handle); diff --git a/drivers/gpu/drm/virtio/virtgpu_vram.c b/drivers/gpu/drm/virtio/virtgpu_vram.c index 5b4a3ab81cd5..01241ce4d07c 100644 --- a/drivers/gpu/drm/virtio/virtgpu_vram.c +++ b/drivers/gpu/drm/virtio/virtgpu_vram.c @@ -215,16 +215,12 @@ int virtio_gpu_vram_create(struct virtio_gpu_device *vgdev, /* Create fake offset */ ret = drm_gem_create_mmap_offset(obj); - if (ret) { - kfree(vram); - return ret; - } + if (ret) + goto err_release_obj; ret = virtio_gpu_resource_id_get(vgdev, &vram->base.hw_res_handle); - if (ret) { - kfree(vram); - return ret; - } + if (ret) + goto err_release_obj; virtio_gpu_cmd_resource_create_blob(vgdev, &vram->base, params, NULL, 0); @@ -240,6 +236,11 @@ int virtio_gpu_vram_create(struct virtio_gpu_device *vgdev, *bo_ptr = &vram->base; return 0; + +err_release_obj: + drm_gem_object_release(obj); + kfree(vram); + return ret; } void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram) diff --git a/drivers/gpu/drm/xe/regs/xe_gt_regs.h b/drivers/gpu/drm/xe/regs/xe_gt_regs.h index 08251c7a1a4b..247a736a54aa 100644 --- a/drivers/gpu/drm/xe/regs/xe_gt_regs.h +++ b/drivers/gpu/drm/xe/regs/xe_gt_regs.h @@ -651,6 +651,7 @@ #define MEM_THERMAL_MASK REG_BIT(2) #define VR_THERMAL_MASK REG_BIT(3) #define ICCMAX_MASK REG_BIT(4) +#define PWRBRK_MASK REG_BIT(5) #define SOC_AVG_THERMAL_MASK REG_BIT(6) #define FASTVMODE_MASK REG_BIT(7) #define PSYS_PL1_MASK REG_BIT(12) diff --git a/drivers/gpu/drm/xe/xe_bo.c b/drivers/gpu/drm/xe/xe_bo.c index dde309821237..b4921a627ff3 100644 --- a/drivers/gpu/drm/xe/xe_bo.c +++ b/drivers/gpu/drm/xe/xe_bo.c @@ -1037,6 +1037,13 @@ static int xe_bo_move(struct ttm_buffer_object *ttm_bo, bool evict, } else { drm_dbg(&xe->drm, "Evict system allocator BO failed=%pe\n", ERR_PTR(ret)); + /* + * The semantic we want upon SVM eviction failure + * because of racing access is keep walking for + * eviction, which is -ENOSPC. + */ + if (ret == -EBUSY) + ret = -ENOSPC; } goto out; diff --git a/drivers/gpu/drm/xe/xe_bo.h b/drivers/gpu/drm/xe/xe_bo.h index e8081af5bfc1..152bfcffe399 100644 --- a/drivers/gpu/drm/xe/xe_bo.h +++ b/drivers/gpu/drm/xe/xe_bo.h @@ -9,6 +9,8 @@ #include <drm/drm_prime.h> #include <drm/ttm/ttm_tt.h> +#include <xen/xen.h> + #include "xe_bo_types.h" #include "xe_ggtt.h" #include "xe_macros.h" @@ -575,6 +577,23 @@ static inline unsigned int xe_sg_segment_size(struct device *dev) struct scatterlist __maybe_unused sg; size_t max = BIT_ULL(sizeof(sg.length) * 8) - 1; + /* + * For Xen PV guests pages aren't contiguous in DMA (machine) address + * space. The DMA API takes care of that both in dma_alloc_* (by + * calling into the hypervisor to make the pages contiguous) and in + * dma_map_* (by bounce buffering). But xe (like i915, see commit + * 78a07fe777c4) ignores the coherency aspects of the DMA API and thus + * can't cope with bounce buffering actually happening, so add a hack + * here to force small allocations and mappings when running in PV + * mode on Xen. + * + * Note this will still break if bounce buffering is required for other + * reasons, like confidential computing hypervisors or PCIe root ports + * with addressing limitations. + */ + if (xen_pv_domain()) + return PAGE_SIZE; + max = min_t(size_t, max, dma_max_mapping_size(dev)); /* diff --git a/drivers/gpu/drm/xe/xe_gt_throttle.c b/drivers/gpu/drm/xe/xe_gt_throttle.c index 1e7e3a31aa69..c0af5484611d 100644 --- a/drivers/gpu/drm/xe/xe_gt_throttle.c +++ b/drivers/gpu/drm/xe/xe_gt_throttle.c @@ -39,7 +39,7 @@ * - ``reason_mem_thermal``: Memory thermal * - ``reason_vr_thermal``: VR thermal * - ``reason_iccmax``: ICCMAX - * - ``reason_ratl``: RATL thermal algorithm + * - ``reason_pwrbrk``: Power brake * - ``reason_soc_avg_thermal``: SoC average temp * - ``reason_fastvmode``: VR is hitting FastVMode * - ``reason_psys_pl1``: PSYS PL1 @@ -200,6 +200,7 @@ static THROTTLE_ATTR_RO(reason_psys_pl1, PSYS_PL1_MASK); static THROTTLE_ATTR_RO(reason_psys_pl2, PSYS_PL2_MASK); static THROTTLE_ATTR_RO(reason_p0_freq, P0_FREQ_MASK); static THROTTLE_ATTR_RO(reason_psys_crit, PSYS_CRIT_MASK); +static THROTTLE_ATTR_RO(reason_pwrbrk, PWRBRK_MASK); static struct attribute *cri_throttle_attrs[] = { /* Common */ @@ -209,12 +210,12 @@ static struct attribute *cri_throttle_attrs[] = { &attr_reason_pl2.attr.attr, &attr_reason_pl4.attr.attr, &attr_reason_prochot.attr.attr, - &attr_reason_ratl.attr.attr, /* CRI */ &attr_reason_vr_thermal.attr.attr, &attr_reason_soc_thermal.attr.attr, &attr_reason_mem_thermal.attr.attr, &attr_reason_iccmax.attr.attr, + &attr_reason_pwrbrk.attr.attr, &attr_reason_soc_avg_thermal.attr.attr, &attr_reason_fastvmode.attr.attr, &attr_reason_psys_pl1.attr.attr, diff --git a/drivers/gpu/drm/xe/xe_guc_ads.c b/drivers/gpu/drm/xe/xe_guc_ads.c index ff8eee3831aa..58a3f6293ce4 100644 --- a/drivers/gpu/drm/xe/xe_guc_ads.c +++ b/drivers/gpu/drm/xe/xe_guc_ads.c @@ -864,7 +864,7 @@ static unsigned int guc_mmio_regset_write(struct xe_guc_ads *ads, } } - if (XE_GT_WA(hwe->gt, 16023105232)) + if (XE_GT_WA(hwe->gt, 16023105232) || XE_GT_WA(hwe->gt, 14025941587)) guc_mmio_regset_write_one(ads, regset_map, RING_IDLEDLY(hwe->mmio_base), count++); diff --git a/drivers/gpu/drm/xe/xe_hw_engine.c b/drivers/gpu/drm/xe/xe_hw_engine.c index 010499766fce..9680d2a5adec 100644 --- a/drivers/gpu/drm/xe/xe_hw_engine.c +++ b/drivers/gpu/drm/xe/xe_hw_engine.c @@ -585,28 +585,102 @@ static void hw_engine_init_early(struct xe_gt *gt, struct xe_hw_engine *hwe, xe_reg_whitelist_process_engine(hwe); } +static u32 idledly_floor_ticks(u32 idledly_ns, u32 idledly_units_ps) +{ + return DIV_ROUND_DOWN_ULL((u64)idledly_ns * 1000, idledly_units_ps); +} + static void adjust_idledly(struct xe_hw_engine *hwe) { struct xe_gt *gt = hwe->gt; - u32 idledly, maxcnt; + u32 idledly, idledly_hw, idledly_reg_val, maxcnt; u32 idledly_units_ps = 8 * gt->info.timestamp_base; u32 maxcnt_units_ns = 640; - bool inhibit_switch = 0; + bool inhibit_switch = false; + bool wa_applied = false; + bool clamped_below_maxcnt = false; + + if ((!IS_SRIOV_VF(gt_to_xe(gt)) && XE_GT_WA(gt, 16023105232)) || + XE_GT_WA(gt, 14025941587)) { + u32 mincnt_idledly_ns = 5000; + + /* xe_gt_clock_init() warns and zeroes timestamp_base on unknown crystal clock. */ + if (!idledly_units_ps) + return; - if (!IS_SRIOV_VF(gt_to_xe(hwe->gt)) && XE_GT_WA(gt, 16023105232)) { - idledly = xe_mmio_read32(>->mmio, RING_IDLEDLY(hwe->mmio_base)); + idledly_reg_val = xe_mmio_read32(>->mmio, RING_IDLEDLY(hwe->mmio_base)); maxcnt = xe_mmio_read32(>->mmio, RING_PWRCTX_MAXCNT(hwe->mmio_base)); - inhibit_switch = idledly & INHIBIT_SWITCH_UNTIL_PREEMPTED; - idledly = REG_FIELD_GET(IDLE_DELAY, idledly); - idledly = DIV_ROUND_CLOSEST(idledly * idledly_units_ps, 1000); + inhibit_switch = idledly_reg_val & INHIBIT_SWITCH_UNTIL_PREEMPTED; + idledly = REG_FIELD_GET(IDLE_DELAY, idledly_reg_val); + idledly = DIV_ROUND_CLOSEST_ULL((u64)idledly * idledly_units_ps, 1000); + idledly_hw = idledly; maxcnt = REG_FIELD_GET(IDLE_WAIT_TIME, maxcnt); maxcnt *= maxcnt_units_ns; - if (xe_gt_WARN_ON(gt, idledly >= maxcnt || inhibit_switch)) { - idledly = DIV_ROUND_CLOSEST(((maxcnt - 1) * 1000), - idledly_units_ps); - xe_mmio_write32(>->mmio, RING_IDLEDLY(hwe->mmio_base), idledly); + /* + * Wa_14025941587 is applied before Wa_16023105232, which takes + * priority if the two ever conflict (not expected in practice). + */ + if (XE_GT_WA(gt, 14025941587) && + idledly < mincnt_idledly_ns) { + idledly = mincnt_idledly_ns; + wa_applied = true; + } + + if (XE_GT_WA(gt, 16023105232)) { + /* Clear the inhibit switch without disturbing a valid delay. */ + if (inhibit_switch) { + idledly_reg_val &= ~INHIBIT_SWITCH_UNTIL_PREEMPTED; + wa_applied = true; + } + + /* Warn only on the value read from hardware. */ + xe_gt_WARN_ON(gt, idledly_hw >= maxcnt); + + if (idledly >= maxcnt) { + /* maxcnt may be 0 if IDLE_WAIT_TIME is unprogrammed. */ + idledly = maxcnt ? maxcnt - 1 : 0; + clamped_below_maxcnt = true; + wa_applied = true; + } + } + + if (wa_applied) { + u32 idledly_ticks; + + /* + * Wa_16023105232 requires idledly < maxcnt, so floor + * that clamp; otherwise round up to guarantee the + * Wa_14025941587 minimum survives tick quantization. + */ + if (clamped_below_maxcnt) + idledly_ticks = idledly_floor_ticks(idledly, idledly_units_ps); + else + idledly_ticks = DIV_ROUND_UP_ULL((u64)idledly * 1000, + idledly_units_ps); + + /* + * Tick quantization can still push the rounded-up value + * to/above maxcnt; re-floor here so Wa_16023105232 keeps + * priority even in that case. + */ + if (!clamped_below_maxcnt && XE_GT_WA(gt, 16023105232) && + (u64)idledly_ticks * idledly_units_ps >= (u64)maxcnt * 1000) { + xe_gt_dbg(gt, "idledly %s: %u ticks would exceed maxcnt=%u, so flooring\n", + hwe->name, idledly_ticks, maxcnt); + idledly = maxcnt ? maxcnt - 1 : 0; + idledly_ticks = idledly_floor_ticks(idledly, idledly_units_ps); + } + + idledly_reg_val &= ~IDLE_DELAY; + idledly_reg_val |= REG_FIELD_PREP(IDLE_DELAY, idledly_ticks); + xe_gt_dbg(gt, "idledly %s: set %u max=%u inh=%u ts=%u\n", + hwe->name, idledly, maxcnt, + !!inhibit_switch, gt->info.timestamp_base); + xe_mmio_write32(>->mmio, + RING_IDLEDLY(hwe->mmio_base), + idledly_reg_val); } } } diff --git a/drivers/gpu/drm/xe/xe_tlb_inval.c b/drivers/gpu/drm/xe/xe_tlb_inval.c index bbd21d393062..67b205bf1291 100644 --- a/drivers/gpu/drm/xe/xe_tlb_inval.c +++ b/drivers/gpu/drm/xe/xe_tlb_inval.c @@ -280,7 +280,8 @@ static void xe_tlb_inval_fence_prep(struct xe_tlb_inval_fence *fence) xe_tlb_inval_fence_signal_unlocked((__fence)); \ mutex_unlock(&(__tlb_inval)->seqno_lock); \ \ - __ret == -ECANCELED ? 0 : __ret; \ + /* Undelivered: fence already signalled, report done */ \ + (__ret == -ECANCELED || __ret == -ENOTRECOVERABLE) ? 0 : __ret; \ }) /** diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c index 23952ad8951e..ef20e205a734 100644 --- a/drivers/gpu/drm/xe/xe_vm.c +++ b/drivers/gpu/drm/xe/xe_vm.c @@ -1947,21 +1947,13 @@ void xe_vm_close_and_put(struct xe_vm *vm) vma->gpuva.flags |= XE_VMA_DESTROYED; } - /* - * All vm operations will add shared fences to resv. - * The only exception is eviction for a shared object, - * but even so, the unbind when evicted would still - * install a fence to resv. Hence it's safe to - * destroy the pagetables immediately. - */ - xe_vm_free_scratch(vm); - xe_vm_pt_destroy(vm); xe_vm_unlock(vm); /* - * VM is now dead, cannot re-add nodes to vm->vmas if it's NULL - * Since we hold a refcount to the bo, we can remove and free - * the members safely without locking. + * Unlink and destroy all contested external-BO VMAs before destroying + * the page tables. Otherwise, concurrent eviction holding only bo->resv + * can walk the BO's VMAs and attempt to invalidate/zap page tables that + * have already been freed. */ list_for_each_entry_safe(vma, next_vma, &contested, combined_links.destroy) { @@ -1969,6 +1961,11 @@ void xe_vm_close_and_put(struct xe_vm *vm) xe_vma_destroy_unlocked(vma); } + xe_vm_lock(vm, false); + xe_vm_free_scratch(vm); + xe_vm_pt_destroy(vm); + xe_vm_unlock(vm); + xe_svm_fini(vm); up_write(&vm->lock); diff --git a/drivers/gpu/drm/xe/xe_wa_oob.rules b/drivers/gpu/drm/xe/xe_wa_oob.rules index dd69ad07f7a9..3001155f8d09 100644 --- a/drivers/gpu/drm/xe/xe_wa_oob.rules +++ b/drivers/gpu/drm/xe/xe_wa_oob.rules @@ -72,3 +72,5 @@ 16029897822 MEDIA_VERSION(3500) GRAPHICS_VERSION(3510) 14027054324 GRAPHICS_VERSION(3511) +14025941587 GRAPHICS_VERSION_RANGE(2001, 3511), FUNC(xe_rtp_match_not_sriov_vf) + MEDIA_VERSION_RANGE(1301, 3503), FUNC(xe_rtp_match_not_sriov_vf) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index a81bf51cbcf1..c43e824428f0 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1210,7 +1210,8 @@ config HID_STEELSERIES depends on USB_HID help Support for Steelseries SRW-S1 steering wheel, and the Steelseries - Arctis 1 Wireless for XBox headset. + Arctis 1 Wireless for XBox, Arctis 7 (2018), Arctis 9, and Arctis + Nova headsets. config HID_SUNPLUS tristate "Sunplus wireless desktop" diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_common.h b/drivers/hid/amd-sfh-hid/amd_sfh_common.h index f8c6b7fc34fb..3d29119a3765 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_common.h +++ b/drivers/hid/amd-sfh-hid/amd_sfh_common.h @@ -13,11 +13,15 @@ #include <linux/auxiliary_bus.h> #include <linux/mutex.h> #include <linux/pci.h> +#include <linux/sizes.h> #include "amd_sfh_hid.h" #define PCI_DEVICE_ID_AMD_MP2 0x15E4 #define PCI_DEVICE_ID_AMD_MP2_1_1 0x164A +/* The BAR 2 size must cover the highest register offset (0x10958) */ +#define AMD_SFH_MIN_BAR_SIZE SZ_128K + #define AMD_C2P_MSG(regno) (0x10500 + ((regno) * 4)) #define AMD_P2C_MSG(regno) (0x10680 + ((regno) * 4)) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index eda26a094d3f..061a63519d44 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -497,6 +497,16 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i if (rc) return rc; + if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) { + dev_err(&pdev->dev, "BAR 2 is not IORESOURCE_MEM\n"); + return -ENODEV; + } + + if (pci_resource_len(pdev, 2) < AMD_SFH_MIN_BAR_SIZE) { + dev_err(&pdev->dev, "BAR 2 is too small\n"); + return -EINVAL; + } + rc = pcim_iomap_regions(pdev, BIT(2), DRIVER_NAME); if (rc) return rc; diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index 44671dbdeca8..d46779b63660 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -359,7 +359,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx) static int __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, - enum hid_report_type rtype) + enum hid_report_type rtype, bool hw_request) { struct hid_report_enum *report_enum; struct hid_report *report; @@ -388,6 +388,10 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz, report_len = hid_report_len(report); + /* unnumbered reports need to have a report ID reserved in the first byte */ + if (hw_request && report_enum->numbered == 0) + report_len += 1; + if (*buf__sz > report_len) *buf__sz = report_len; @@ -420,7 +424,7 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz, return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype, true); if (ret) return ret; @@ -480,7 +484,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz) return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT, true); if (ret) return ret; @@ -506,7 +510,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b return -EDEADLOCK; /* check arguments */ - ret = __hid_bpf_hw_check_params(ctx, buf, &size, type); + ret = __hid_bpf_hw_check_params(ctx, buf, &size, type, false); if (ret) return ret; diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 67179e3fe39b..0556cb5645eb 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -407,6 +407,8 @@ static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size) return 1; case U1_SP_ABSOLUTE_REPORT_ID: + if (!hdata->input2) + return 0; sp_x = get_unaligned_le16(data+2); sp_y = get_unaligned_le16(data+4); @@ -738,7 +740,6 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) goto exit; } - data->input2 = input2; input2->phys = input->phys; input2->name = "DualPoint Stick"; input2->id.bustype = BUS_I2C; @@ -762,11 +763,12 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi) __set_bit(INPUT_PROP_POINTER, input2->propbit); __set_bit(INPUT_PROP_POINTING_STICK, input2->propbit); - if (input_register_device(data->input2)) { + if (input_register_device(input2)) { input_free_device(input2); ret = -ENOENT; goto exit; } + data->input2 = input2; } exit: @@ -823,6 +825,24 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id) return 0; } +static void alps_remove(struct hid_device *hdev) +{ + struct alps_dev *data = hid_get_drvdata(hdev); + + /* + * input2 ("DualPoint Stick") is allocated separately and is not + * tracked in hdev->inputs, so the default remove path + * (hid_hw_stop -> hidinput_disconnect) does not unregister it. + * + * Stop the device first so that no URB callback can touch input2 + * while it is being unregistered, then drop it explicitly. + */ + hid_hw_stop(hdev); + + if (data->input2) + input_unregister_device(data->input2); +} + static const struct hid_device_id alps_id[] = { { HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) }, @@ -845,6 +865,7 @@ static struct hid_driver alps_driver = { .input_configured = alps_input_configured, .resume = pm_ptr(alps_post_resume), .reset_resume = pm_ptr(alps_post_reset), + .remove = alps_remove, }; module_hid_driver(alps_driver); diff --git a/drivers/hid/hid-corsair-void.c b/drivers/hid/hid-corsair-void.c index 071a663a6c26..7be8c974d11e 100644 --- a/drivers/hid/hid-corsair-void.c +++ b/drivers/hid/hid-corsair-void.c @@ -51,20 +51,23 @@ /* Receiver report information: (ID 102) */ /* -------------------------------------------------------------------------- */ /* - * When queried, the recevier responds with 4 bytes to describe the firmware - * The first 2 bytes are for the receiver, the second 2 are the headset + * When queried, the receiver responds with 5 bytes to describe the firmware + * The first byte is the ID, then 2 bytes for the receiver, then 2 for the headset * The headset firmware version will be 0 if no headset is connected * - * 0: Recevier firmware major version + * 0: Report ID + * 102 for the firmware packet + * + * 1: Receiver firmware major version * Major version of the receiver's firmware * - * 1: Recevier firmware minor version + * 2: Receiver firmware minor version * Minor version of the receiver's firmware * - * 2: Headset firmware major version + * 3: Headset firmware major version * Major version of the headset's firmware * - * 3: Headset firmware minor version + * 4: Headset firmware minor version * Minor version of the headset's firmware */ /* -------------------------------------------------------------------------- */ diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c index 37d88ce57f67..d21ead40bb07 100644 --- a/drivers/hid/hid-elecom.c +++ b/drivers/hid/hid-elecom.c @@ -79,7 +79,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB: case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F: case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC: - case USB_DEVICE_ID_ELECOM_M_XT4DRBK: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD: /* * Report descriptor format: * 12: button bit count @@ -104,6 +104,7 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc, mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8); break; case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C: + case USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E: /* * Report descriptor format: * 22: button bit count @@ -148,7 +149,8 @@ static const struct hid_device_id elecom_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index b3aca5aa9176..9a473f72c8cb 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -477,7 +477,8 @@ #define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc #define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c -#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD 0x00fd +#define USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E 0x018e #define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe #define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff #define USB_DEVICE_ID_ELECOM_M_DT2DRBK 0x018d @@ -765,6 +766,7 @@ #define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a #define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396 #define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987 +#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD 0x83db #define USB_DEVICE_ID_ITE8595 0x8595 #define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50 @@ -1296,6 +1298,9 @@ #define USB_DEVICE_ID_SAMSUNG_WIRELESS_UNIVERSAL_KBD 0xa006 #define USB_DEVICE_ID_SAMSUNG_WIRELESS_MULTI_HOGP_KBD 0xa064 +#define USB_VENDOR_ID_SDINNOVATION 0x36ae +#define USB_DEVICE_ID_SDINNOVATION_GAMING_KBD 0xfeab + #define USB_VENDOR_ID_SEMICO 0x1a2c #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD 0x0023 #define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2 0x0027 @@ -1395,6 +1400,7 @@ #define USB_VENDOR_ID_STEELSERIES 0x1038 #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018 0x12ad #define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202 diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 1504de32b1c8..493763a12518 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4924,6 +4924,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08D) }, { /* Logitech G502 X Plus Wireless Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC095) }, + { /* Logitech G502 X Lightspeed Wireless Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC098) }, { /* Logitech G703 Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC087) }, { /* Logitech G703 Hero Gaming Mouse over USB */ diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 451c7324e6a0..ab4ac261fe76 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -79,6 +79,7 @@ MODULE_LICENSE("GPL"); #define MT_QUIRK_APPLE_TOUCHBAR BIT(23) #define MT_QUIRK_YOGABOOK9I BIT(24) #define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25) +#define MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH BIT(26) #define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHPAD 0x03 @@ -235,6 +236,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); #define MT_CLS_APPLE_TOUCHBAR 0x0114 #define MT_CLS_YOGABOOK9I 0x0115 #define MT_CLS_EGALAX_P80H84 0x0116 +#define MT_CLS_ASUS_ROG_Z13_FOLIO 0x0117 #define MT_CLS_SIS 0x0457 #define MT_DEFAULT_MAXCONTACT 10 @@ -405,6 +407,16 @@ static const struct mt_class mt_classes[] = { .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | MT_QUIRK_ASUS_CUSTOM_UP }, + { .name = MT_CLS_ASUS_ROG_Z13_FOLIO, + .quirks = MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_STICKY_FINGERS | + MT_QUIRK_WIN8_PTP_BUTTONS | + MT_QUIRK_CONFIDENCE | + MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH, + .export_all_inputs = true }, { .name = MT_CLS_VTL, .quirks = MT_QUIRK_ALWAYS_VALID | MT_QUIRK_CONTACT_CNT_ACCURATE | @@ -507,6 +519,7 @@ static const struct attribute_group mt_attribute_group = { static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) { + struct mt_device *td = hid_get_drvdata(hdev); int ret; u32 size = hid_report_len(report); u8 *buf; @@ -528,8 +541,14 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report) dev_warn(&hdev->dev, "failed to fetch feature %d\n", report->id); } else { - /* The report ID in the request and the response should match */ - if (report->id != buf[0]) { + /* + * The report ID in the request and the response should match. + * Some firmware (e.g. the ASUS ROG Z13 Folio + * touchpad) returns a mismatched ID on this specific fetch; + * tolerate it only for devices explicitly flagged as such. + */ + if (report->id != buf[0] && + !(td->mtclass.quirks & MT_QUIRK_IGNORE_FEATURE_ID_MISMATCH)) { hid_err(hdev, "Returned feature report did not match the request\n"); goto free; } @@ -2715,6 +2734,12 @@ static const struct hid_device_id mt_devices[] = { HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288) }, + /* Asus ROG Flow Z13 (2025) GZ302EA keyboard-cover touchpad */ + { .driver_data = MT_CLS_ASUS_ROG_Z13_FOLIO, + HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, + USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO) }, + /* Generic MT device */ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) }, diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index d2ded6b08ce9..1e691ebc1199 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -1552,9 +1552,9 @@ static int oxp_hid_probe(struct hid_device *hdev, static void oxp_hid_remove(struct hid_device *hdev) { - cancel_delayed_work(&drvdata.oxp_rgb_queue); - cancel_delayed_work(&drvdata.oxp_btn_queue); - cancel_delayed_work(&drvdata.oxp_mcu_init); + cancel_delayed_work_sync(&drvdata.oxp_rgb_queue); + cancel_delayed_work_sync(&drvdata.oxp_btn_queue); + cancel_delayed_work_sync(&drvdata.oxp_mcu_init); hid_hw_close(hdev); hid_hw_stop(hdev); } diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 8a0b51d47040..96a36c5ba04f 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -186,6 +186,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_2), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X52_PRO), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_X65), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE }, + { HID_USB_DEVICE(USB_VENDOR_ID_SDINNOVATION, USB_DEVICE_ID_SDINNOVATION_GAMING_KBD), HID_QUIRK_ALWAYS_POLL }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS }, { HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET }, @@ -425,13 +426,14 @@ static const struct hid_device_id hid_have_special_driver[] = { #endif #if IS_ENABLED(CONFIG_HID_ELECOM) { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_00FD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK_018E) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT2DRBK) }, @@ -751,6 +753,7 @@ static const struct hid_device_id hid_have_special_driver[] = { #if IS_ENABLED(CONFIG_HID_STEELSERIES) { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7) }, diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index 4f15eb951039..5deb6da8d4f7 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -344,8 +344,6 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report return temp; } - mutex_unlock(&devices_lock); - init_waitqueue_head(&device->wait); INIT_LIST_HEAD(&device->readers); mutex_init(&device->readers_lock); @@ -356,6 +354,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report device->cbuf_end = 0; device->report_size = report_size; + mutex_unlock(&devices_lock); return minor; } EXPORT_SYMBOL_GPL(roccat_connect); @@ -369,15 +368,12 @@ void roccat_disconnect(int minor) mutex_lock(&devices_lock); device = devices[minor]; - mutex_unlock(&devices_lock); device->exist = 0; /* TODO exist maybe not needed */ device_destroy(device->dev->class, MKDEV(roccat_major, minor)); - mutex_lock(&devices_lock); devices[minor] = NULL; - mutex_unlock(&devices_lock); if (device->open) { hid_hw_close(device->hid); @@ -385,6 +381,8 @@ void roccat_disconnect(int minor) } else { roccat_free_device(device); } + + mutex_unlock(&devices_lock); } EXPORT_SYMBOL_GPL(roccat_disconnect); diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelseries-arctis.c index 7a855b66730a..df61fa33d4c6 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -101,6 +101,19 @@ static int steelseries_arctis_1_request_status(struct hid_device *hdev) return steelseries_send_output_report(hdev, data, sizeof(data)); } +static int steelseries_arctis_7_2018_request_status(struct hid_device *hdev) +{ + const u8 connection[] = { 0x06, 0x14 }; + const u8 battery[] = { 0x06, 0x18 }; + int ret; + + ret = steelseries_send_output_report(hdev, connection, sizeof(connection)); + if (ret) + return ret; + + return steelseries_send_output_report(hdev, battery, sizeof(battery)); +} + static int steelseries_arctis_9_request_status(struct hid_device *hdev) { const u8 data[] = { 0x00, 0x20 }; @@ -152,6 +165,27 @@ static void steelseries_arctis_1_parse_status(struct steelseries_device *sd, sd->battery_capacity = data[3]; } +static void steelseries_arctis_7_2018_parse_status(struct steelseries_device *sd, + u8 *data, int size) +{ + if (size < 3 || data[0] != 0x06) + return; + + switch (data[1]) { + case 0x14: + /* 0x03 means that the headset is connected to the transmitter. */ + sd->headset_connected = data[2] == 0x03; + break; + case 0x18: + if (!sd->headset_connected) + break; + + /* The Arctis 7 sometimes overreports battery. Cap to 100. */ + sd->battery_capacity = steelseries_map_capacity(data[2], 0, 100); + break; + } +} + static void steelseries_arctis_9_parse_status(struct steelseries_device *sd, u8 *data, int size) { @@ -232,6 +266,13 @@ static const struct steelseries_device_info arctis_1_info = { .parse_status = steelseries_arctis_1_parse_status, }; +static const struct steelseries_device_info arctis_7_2018_info = { + .sync_interface = 5, + .capabilities = SS_CAP_BATTERY, + .request_status = steelseries_arctis_7_2018_request_status, + .parse_status = steelseries_arctis_7_2018_parse_status, +}; + static const struct steelseries_device_info arctis_9_info = { .sync_interface = 0, .capabilities = SS_CAP_BATTERY, @@ -654,6 +695,9 @@ static const struct hid_device_id steelseries_arctis_devices[] = { USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X), .driver_data = (unsigned long)&arctis_1_info }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_7_2018), + .driver_data = (unsigned long)&arctis_7_2018_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9), .driver_data = (unsigned long)&arctis_9_info }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c index 9cd25a77999e..19b92c2c6579 100644 --- a/drivers/hid/hid-winwing.c +++ b/drivers/hid/hid-winwing.c @@ -315,7 +315,8 @@ static void winwing_haptic_rumble_cb(struct work_struct *work) static int winwing_play_effect(struct input_dev *dev, void *context, struct ff_effect *effect) { - struct winwing_drv_data *data = (struct winwing_drv_data *) context; + struct hid_device *hdev = input_get_drvdata(dev); + struct winwing_drv_data *data = hid_get_drvdata(hdev); if (effect->type != FF_RUMBLE) return 0; @@ -342,7 +343,12 @@ static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput) input_set_capability(hidinput->input, EV_FF, FF_RUMBLE); - return input_ff_create_memless(hidinput->input, data, + /* + * input_ff_create_memless() takes ownership of the context pointer + * and frees it on teardown; do not hand it the devm-managed drvdata. + * winwing_play_effect() fetches it from the input device instead. + */ + return input_ff_create_memless(hidinput->input, NULL, winwing_play_effect); } diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 0ff07fdab442..a61fd1de5203 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -123,9 +123,11 @@ static const struct i2c_hid_quirks { __u32 quirks; } i2c_hid_quirks[] = { { I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288, - I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET | I2C_HID_QUIRK_BAD_INPUT_SIZE }, { I2C_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_VOYO_WINPAD_A15, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, + { USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_G11_KEYBOARD, + I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_3118, I2C_HID_QUIRK_NO_IRQ_AFTER_RESET }, { USB_VENDOR_ID_ALPS_JP, HID_ANY_ID, diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 0eafa483b7f7..40770affdbde 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -113,8 +113,9 @@ static int wacom_wac_pen_serial_enforce(struct hid_device *hdev, /* Queue events which have invalid tool type or serial number */ for (i = 0; i < report->maxfield; i++) { - for (j = 0; j < report->field[i]->maxusage; j++) { - struct hid_field *field = report->field[i]; + struct hid_field *field = report->field[i]; + + for (j = 0; j < field->report_count; j++) { struct hid_usage *usage = &field->usage[j]; unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid); unsigned int offset; diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c index e9e41a174c20..6a39e21d5ed4 100644 --- a/drivers/i2c/busses/i2c-qcom-geni.c +++ b/drivers/i2c/busses/i2c-qcom-geni.c @@ -82,6 +82,9 @@ enum geni_i2c_err_code { #define XFER_TIMEOUT HZ #define RST_TIMEOUT HZ +#define GENI_SE_CLK_32MHZ (32 * HZ_PER_MHZ) +#define GENI_SE_CLK_19P2MHZ 19200000UL + struct geni_i2c_desc { bool no_dma_support; unsigned int tx_fifo_depth; @@ -127,6 +130,7 @@ struct geni_i2c_dev { spinlock_t lock; u32 clk_freq_out; const struct geni_i2c_clk_fld *clk_fld; + u32 clk_idx; void *dma_buf; size_t xfer_len; dma_addr_t dma_addr; @@ -197,19 +201,44 @@ static const struct geni_i2c_clk_fld geni_i2c_clk_map_32mhz[] = { static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c) { const struct geni_i2c_clk_fld *itr; + unsigned long res_freq; - if (clk_get_rate(gi2c->se.clk) == 32 * HZ_PER_MHZ) + /* + * Frequency counter tables are calibrated for a specific source + * clock frequency and are not valid for any multiple of it + * (e.g. 64 MHz, 128 MHz). + * Use exact=true and verify res_freq matches req_freq literally + * to reject harmonics: a 64 MHz clock that divides evenly to + * 32 MHz would pass exact matching but produce double the intended + * I2C frequency with these counter values. + */ + if (!geni_se_clk_freq_match(&gi2c->se, GENI_SE_CLK_32MHZ, + &gi2c->clk_idx, &res_freq, true) && + res_freq == GENI_SE_CLK_32MHZ) { itr = geni_i2c_clk_map_32mhz; - else + } else if (!geni_se_clk_freq_match(&gi2c->se, GENI_SE_CLK_19P2MHZ, + &gi2c->clk_idx, &res_freq, true) && + res_freq == GENI_SE_CLK_19P2MHZ) { itr = geni_i2c_clk_map_19p2mhz; + } else { + dev_err(gi2c->se.dev, + "Unsupported SE source clock: must be exactly 32 MHz or 19.2 MHz\n"); + return -EINVAL; + } while (itr->clk_freq_out != 0) { if (itr->clk_freq_out == gi2c->clk_freq_out) { gi2c->clk_fld = itr; + dev_dbg(gi2c->se.dev, + "I2C clk selected: freq: %u Hz, clk_idx: %u\n", + gi2c->clk_freq_out, gi2c->clk_idx); return 0; } itr++; } + + dev_err(gi2c->se.dev, "Unsupported I2C output frequency %u Hz\n", gi2c->clk_freq_out); + return -EINVAL; } @@ -219,7 +248,7 @@ static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq) const struct geni_i2c_clk_fld *itr = gi2c->clk_fld; u32 val; - writel_relaxed(0, gi2c->se.base + SE_GENI_CLK_SEL); + writel_relaxed(gi2c->clk_idx, gi2c->se.base + SE_GENI_CLK_SEL); val = (itr->clk_div << CLK_DIV_SHFT) | SER_CLK_EN; writel_relaxed(val, gi2c->se.base + GENI_SER_M_CLK_CFG); @@ -1111,8 +1140,7 @@ static int geni_i2c_resources_init(struct geni_se *se) ret = geni_i2c_clk_map_idx(gi2c); if (ret) - return dev_err_probe(gi2c->se.dev, ret, "Invalid clk frequency %d Hz\n", - gi2c->clk_freq_out); + return ret; return geni_icc_set_bw_ab(&gi2c->se, GENI_DEFAULT_BW, GENI_DEFAULT_BW, Bps_to_icc(gi2c->clk_freq_out)); diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index a9bff7663eec..de2489c3d9bf 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -490,7 +490,7 @@ static int bond_ipsec_add_sa(struct net_device *bond_dev, !real_dev->xfrmdev_ops->xdo_dev_state_add || netif_is_bond_master(real_dev)) { NL_SET_ERR_MSG_MOD(extack, "Slave does not support ipsec offload"); - err = -EINVAL; + err = -EOPNOTSUPP; goto out; } diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c index 784dd58a7158..de42f70afcfa 100644 --- a/drivers/net/dsa/mt7530-mdio.c +++ b/drivers/net/dsa/mt7530-mdio.c @@ -227,15 +227,17 @@ mt7530_remove(struct mdio_device *mdiodev) if (!priv) return; - ret = regulator_disable(priv->core_pwr); - if (ret < 0) - dev_err(priv->dev, - "Failed to disable core power: %d\n", ret); - - ret = regulator_disable(priv->io_pwr); - if (ret < 0) - dev_err(priv->dev, "Failed to disable io pwr: %d\n", - ret); + if (priv->id == ID_MT7530) { + ret = regulator_disable(priv->core_pwr); + if (ret < 0) + dev_err(priv->dev, + "Failed to disable core power: %d\n", ret); + + ret = regulator_disable(priv->io_pwr); + if (ret < 0) + dev_err(priv->dev, "Failed to disable io pwr: %d\n", + ret); + } mt7530_remove_common(priv); diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 3e61eb3c2b1e..96832852c65a 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -3593,9 +3593,6 @@ EXPORT_SYMBOL_GPL(mt7530_probe_common); void mt7530_remove_common(struct mt7530_priv *priv) { - if (priv->irq_domain) - mt7530_free_mdio_irq(priv); - dsa_unregister_switch(priv->ds); mutex_destroy(&priv->reg_mutex); diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 7f68a0c55802..a4a8c7e11bf4 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -5639,6 +5639,68 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = { .pcs_ops = &mv88e6390_pcs_ops, }; +static const struct mv88e6xxx_ops mv88e6191x_ops = { + /* MV88E6XXX_FAMILY_6393 without AVB and PTP: 6191X and 6193X */ + .irl_init_all = mv88e6390_g2_irl_init_all, + .get_eeprom = mv88e6xxx_g2_get_eeprom8, + .set_eeprom = mv88e6xxx_g2_set_eeprom8, + .set_switch_mac = mv88e6xxx_g2_set_switch_mac, + .phy_read = mv88e6xxx_g2_smi_phy_read_c22, + .phy_write = mv88e6xxx_g2_smi_phy_write_c22, + .phy_read_c45 = mv88e6xxx_g2_smi_phy_read_c45, + .phy_write_c45 = mv88e6xxx_g2_smi_phy_write_c45, + .port_set_link = mv88e6xxx_port_set_link, + .port_sync_link = mv88e6xxx_port_sync_link, + .port_set_rgmii_delay = mv88e6390_port_set_rgmii_delay, + .port_set_speed_duplex = mv88e6393x_port_set_speed_duplex, + .port_tag_remap = mv88e6390_port_tag_remap, + .port_set_policy = mv88e6393x_port_set_policy, + .port_set_frame_mode = mv88e6351_port_set_frame_mode, + .port_set_ucast_flood = mv88e6352_port_set_ucast_flood, + .port_set_mcast_flood = mv88e6352_port_set_mcast_flood, + .port_set_ether_type = mv88e6393x_port_set_ether_type, + .port_set_jumbo_size = mv88e6165_port_set_jumbo_size, + .port_egress_rate_limiting = mv88e6097_port_egress_rate_limiting, + .port_pause_limit = mv88e6390_port_pause_limit, + .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, + .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, + .port_get_cmode = mv88e6352_port_get_cmode, + .port_set_cmode = mv88e6393x_port_set_cmode, + .port_setup_message_port = mv88e6xxx_setup_message_port, + .port_set_upstream_port = mv88e6393x_port_set_upstream_port, + .port_enable_tcam = mv88e6xxx_port_enable_tcam, + .stats_snapshot = mv88e6390_g1_stats_snapshot, + .stats_set_histogram = mv88e6390_g1_stats_set_histogram, + .stats_get_sset_count = mv88e6320_stats_get_sset_count, + .stats_get_strings = mv88e6320_stats_get_strings, + .stats_get_stat = mv88e6390_stats_get_stat, + /* .set_cpu_port is missing because this family does not support a global + * CPU port, only per port CPU port which is set via + * .port_set_upstream_port method. + */ + .set_egress_port = mv88e6393x_set_egress_port, + .watchdog_ops = &mv88e6393x_watchdog_ops, + .mgmt_rsvd2cpu = mv88e6393x_port_mgmt_rsvd2cpu, + .pot_clear = mv88e6xxx_g2_pot_clear, + .hardware_reset_pre = mv88e6xxx_g2_eeprom_wait, + .hardware_reset_post = mv88e6xxx_g2_eeprom_wait, + .reset = mv88e6352_g1_reset, + .rmu_disable = mv88e6390_g1_rmu_disable, + .atu_get_hash = mv88e6165_g1_atu_get_hash, + .atu_set_hash = mv88e6165_g1_atu_set_hash, + .vtu_getnext = mv88e6390_g1_vtu_getnext, + .vtu_loadpurge = mv88e6390_g1_vtu_loadpurge, + .stu_getnext = mv88e6390_g1_stu_getnext, + .stu_loadpurge = mv88e6390_g1_stu_loadpurge, + .serdes_get_lane = mv88e6393x_serdes_get_lane, + .serdes_irq_mapping = mv88e6390_serdes_irq_mapping, + /* TODO: serdes stats */ + .gpio_ops = &mv88e6352_gpio_ops, + .phylink_get_caps = mv88e6393x_phylink_get_caps, + .pcs_ops = &mv88e6393x_pcs_ops, + .tcam_ops = &mv88e6393_tcam_ops, +}; + static const struct mv88e6xxx_ops mv88e6393x_ops = { /* MV88E6XXX_FAMILY_6393 */ .irl_init_all = mv88e6390_g2_irl_init_all, @@ -6163,8 +6225,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, - .ptp_support = true, - .ops = &mv88e6393x_ops, + .ops = &mv88e6191x_ops, }, [MV88E6193X] = { @@ -6190,8 +6251,7 @@ static const struct mv88e6xxx_info mv88e6xxx_table[] = { .atu_move_port_mask = 0x1f, .pvt = true, .multi_chip = true, - .ptp_support = true, - .ops = &mv88e6393x_ops, + .ops = &mv88e6191x_ops, }, [MV88E6220] = { diff --git a/drivers/net/ethernet/airoha/airoha_npu.c b/drivers/net/ethernet/airoha/airoha_npu.c index 5bb4817a898d..4d3195eb00f7 100644 --- a/drivers/net/ethernet/airoha/airoha_npu.c +++ b/drivers/net/ethernet/airoha/airoha_npu.c @@ -5,6 +5,7 @@ */ #include <linux/devcoredump.h> +#include <linux/devm-helpers.h> #include <linux/firmware.h> #include <linux/platform_device.h> #include <linux/of_net.h> @@ -751,12 +752,15 @@ static int airoha_npu_probe(struct platform_device *pdev) if (irq < 0) return irq; + err = devm_work_autocancel(dev, &core->wdt_work, + airoha_npu_wdt_work); + if (err) + return err; + err = devm_request_irq(dev, irq, airoha_npu_wdt_handler, IRQF_SHARED, "airoha-npu-wdt", core); if (err) return err; - - INIT_WORK(&core->wdt_work, airoha_npu_wdt_work); } /* wlan IRQ lines */ @@ -803,18 +807,8 @@ static int airoha_npu_probe(struct platform_device *pdev) return 0; } -static void airoha_npu_remove(struct platform_device *pdev) -{ - struct airoha_npu *npu = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < ARRAY_SIZE(npu->cores); i++) - cancel_work_sync(&npu->cores[i].wdt_work); -} - static struct platform_driver airoha_npu_driver = { .probe = airoha_npu_probe, - .remove = airoha_npu_remove, .driver = { .name = "airoha-npu", .of_match_table = of_airoha_npu_match, diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.c b/drivers/net/ethernet/amazon/ena/ena_netdev.c index ea89619039d8..7eb6456ed0d5 100644 --- a/drivers/net/ethernet/amazon/ena/ena_netdev.c +++ b/drivers/net/ethernet/amazon/ena/ena_netdev.c @@ -4122,6 +4122,8 @@ err_worker_destroy: err_device_destroy: ena_com_delete_host_info(ena_dev); ena_com_admin_destroy(ena_dev); + ena_phc_destroy(adapter); + ena_com_mmio_reg_read_request_destroy(ena_dev); ena_devlink_destroy: ena_devlink_free(devlink); err_metrics_destroy: diff --git a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c index 7efa3fc257b3..e58f1d2c26bd 100644 --- a/drivers/net/ethernet/atheros/atl1c/atl1c_main.c +++ b/drivers/net/ethernet/atheros/atl1c/atl1c_main.c @@ -1602,6 +1602,9 @@ static int atl1c_clean_tx(struct napi_struct *napi, int budget) AT_READ_REGW(&adapter->hw, atl1c_qregs[tpd_ring->num].tpd_cons, &hw_next_to_clean); + if (unlikely(hw_next_to_clean >= tpd_ring->count)) + hw_next_to_clean = next_to_clean; + while (next_to_clean != hw_next_to_clean) { buffer_info = &tpd_ring->buffer_info[next_to_clean]; if (buffer_info->skb) { diff --git a/drivers/net/ethernet/atheros/atl1e/atl1e_main.c b/drivers/net/ethernet/atheros/atl1e/atl1e_main.c index 40290028580b..437989edb780 100644 --- a/drivers/net/ethernet/atheros/atl1e/atl1e_main.c +++ b/drivers/net/ethernet/atheros/atl1e/atl1e_main.c @@ -1234,6 +1234,9 @@ static bool atl1e_clean_tx_irq(struct atl1e_adapter *adapter) u16 hw_next_to_clean = AT_READ_REGW(&adapter->hw, REG_TPD_CONS_IDX); u16 next_to_clean = atomic_read(&tx_ring->next_to_clean); + if (unlikely(hw_next_to_clean >= tx_ring->count)) + hw_next_to_clean = next_to_clean; + while (next_to_clean != hw_next_to_clean) { tx_buffer = &tx_ring->tx_buffer[next_to_clean]; if (tx_buffer->dma) { diff --git a/drivers/net/ethernet/atheros/atlx/atl1.c b/drivers/net/ethernet/atheros/atlx/atl1.c index 98a4d089270e..957d5598dda5 100644 --- a/drivers/net/ethernet/atheros/atlx/atl1.c +++ b/drivers/net/ethernet/atheros/atlx/atl1.c @@ -2066,6 +2066,9 @@ static int atl1_intr_tx(struct atl1_adapter *adapter) sw_tpd_next_to_clean = atomic_read(&tpd_ring->next_to_clean); cmb_tpd_next_to_clean = le16_to_cpu(adapter->cmb.cmb->tpd_cons_idx); + if (unlikely(cmb_tpd_next_to_clean >= tpd_ring->count)) + cmb_tpd_next_to_clean = sw_tpd_next_to_clean; + while (cmb_tpd_next_to_clean != sw_tpd_next_to_clean) { buffer_info = &tpd_ring->buffer_info[sw_tpd_next_to_clean]; if (buffer_info->dma) { diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c index 62bc9cae613c..622e89587e5d 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c @@ -5733,7 +5733,8 @@ const struct ethtool_ops bnxt_ethtool_ops = { .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | ETHTOOL_OP_NEEDS_RTNL_SCOALESCE | - ETHTOOL_OP_NEEDS_RTNL_RSS, + ETHTOOL_OP_NEEDS_RTNL_RSS | + ETHTOOL_OP_NEEDS_RTNL_TEST, .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USECS_IRQ | diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index b916080f4ff1..21668e41b696 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -852,7 +852,8 @@ static int bcmgenet_get_coalesce(struct net_device *dev, ec->rx_max_coalesced_frames = bcmgenet_rdma_ring_readl(priv, 0, DMA_MBUF_DONE_THRESH); ec->rx_coalesce_usecs = - bcmgenet_rdma_readl(priv, DMA_RING0_TIMEOUT) * 8192 / 1000; + (bcmgenet_rdma_readl(priv, DMA_RING0_TIMEOUT) & + DMA_TIMEOUT_MASK) * 8192 / 1000; for (i = 0; i <= priv->hw_params->rx_queues; i++) { ring = &priv->rx_rings[i]; @@ -1346,9 +1347,8 @@ static void bcmgenet_get_ethtool_stats(struct net_device *dev, p = (char *)&stats64; p += s->stat_offset; - if (sizeof(unsigned long) != sizeof(u32) && - s->stat_sizeof == sizeof(unsigned long)) - data[i] = *(unsigned long *)p; + if (s->stat_sizeof == sizeof(u64)) + data[i] = *(u64 *)p; else data[i] = *(u32 *)p; } @@ -1763,13 +1763,12 @@ static int bcmgenet_power_up(struct bcmgenet_priv *priv, int ret = 0; u32 reg; - if (!bcmgenet_has_ext(priv)) - return ret; - - reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); - switch (mode) { case GENET_POWER_PASSIVE: + if (!bcmgenet_has_ext(priv)) + break; + + reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); reg &= ~(EXT_PWR_DOWN_DLL | EXT_PWR_DOWN_BIAS | EXT_ENERGY_DET_MASK); if (GENET_IS_V5(priv) && !bcmgenet_has_ephy_16nm(priv)) { @@ -1793,8 +1792,12 @@ static int bcmgenet_power_up(struct bcmgenet_priv *priv, break; case GENET_POWER_CABLE_SENSE: + if (!bcmgenet_has_ext(priv)) + break; + /* enable APD */ if (!GENET_IS_V5(priv)) { + reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); reg |= EXT_PWR_DN_EN_LD; bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT); } @@ -3441,6 +3444,8 @@ static void bcmgenet_netif_stop(struct net_device *dev, bool stop_phy) { struct bcmgenet_priv *priv = netdev_priv(dev); + /* Stop completion polling before it can wake a stopped queue */ + bcmgenet_disable_tx_napi(priv); netif_tx_disable(dev); /* Disable MAC receive */ @@ -3455,7 +3460,6 @@ static void bcmgenet_netif_stop(struct net_device *dev, bool stop_phy) /* Disable MAC transmit. TX DMA disabled must be done before this */ umac_enable_set(priv, CMD_TX_EN, false); - bcmgenet_disable_tx_napi(priv); bcmgenet_disable_rx_napi(priv); bcmgenet_intr_disable(priv); @@ -3632,6 +3636,9 @@ static int bcmgenet_set_mac_addr(struct net_device *dev, void *p) if (netif_running(dev)) return -EBUSY; + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + eth_hw_addr_set(dev, addr->sa_data); return 0; @@ -4135,10 +4142,10 @@ static int bcmgenet_probe(struct platform_device *pdev) priv->rx_rings[i].rx_max_coalesced_frames = 1; /* Initialize u64 stats seq counter for 32bit machines */ - for (i = 0; i <= priv->hw_params->rx_queues; i++) + for (i = 0; i <= GENET_MAX_MQ_CNT; i++) { u64_stats_init(&priv->rx_rings[i].stats64.syncp); - for (i = 0; i <= priv->hw_params->tx_queues; i++) u64_stats_init(&priv->tx_rings[i].stats64.syncp); + } /* libphy will determine the link state */ netif_carrier_off(dev); @@ -4320,6 +4327,8 @@ static int bcmgenet_suspend(struct device *d) netif_device_detach(dev); if (device_may_wakeup(d) && priv->wolopts) { + /* Stop completion polling before it can wake a stopped queue */ + bcmgenet_disable_tx_napi(priv); netif_tx_disable(dev); /* Suspend non-wake Rx data flows */ @@ -4348,7 +4357,6 @@ static int bcmgenet_suspend(struct device *d) netdev_warn(priv->dev, "Timed out while disabling TX DMA\n"); - bcmgenet_disable_tx_napi(priv); bcmgenet_disable_rx_napi(priv); disable_irq(priv->irq1); bcmgenet_tx_reclaim_all(dev); diff --git a/drivers/net/ethernet/broadcom/tg3.c b/drivers/net/ethernet/broadcom/tg3.c index 73a4b569b03e..8b6806a79edf 100644 --- a/drivers/net/ethernet/broadcom/tg3.c +++ b/drivers/net/ethernet/broadcom/tg3.c @@ -17915,11 +17915,14 @@ static int tg3_init_one(struct pci_dev *pdev, err = tg3_get_device_address(tp, addr); if (err) { - dev_err(&pdev->dev, - "Could not obtain valid ethernet address, aborting\n"); - goto err_out_apeunmap; + dev_warn_probe(&pdev->dev, err, + "Could not obtain a valid ethernet address\n"); + if (err == -EPROBE_DEFER) + goto err_out_apeunmap; + eth_hw_addr_random(dev); + } else { + eth_hw_addr_set(dev, addr); } - eth_hw_addr_set(dev, addr); intmbx = MAILBOX_INTERRUPT_0 + TG3_64BIT_REG_LOW; rcvmbx = MAILBOX_RCVRET_CON_IDX_0 + TG3_64BIT_REG_LOW; @@ -18047,6 +18050,10 @@ static int tg3_init_one(struct pci_dev *pdev, return 0; err_out_apeunmap: + if (tg3_flag(tp, USE_PHYLIB)) + tg3_phy_fini(tp); + tg3_mdio_fini(tp); + if (tp->aperegs) { iounmap(tp->aperegs); tp->aperegs = NULL; diff --git a/drivers/net/ethernet/brocade/bna/bnad.c b/drivers/net/ethernet/brocade/bna/bnad.c index 8b75004ba7c9..55dfd4896785 100644 --- a/drivers/net/ethernet/brocade/bna/bnad.c +++ b/drivers/net/ethernet/brocade/bna/bnad.c @@ -2571,6 +2571,22 @@ bnad_ioceth_disable(struct bnad *bnad) return err; } +/* + * The IOC timers rearm one another, so deleting one cannot stop a + * sibling callback from arming it again. Shut them down so a later + * mod_timer() is ignored. + */ +static void +bnad_ioc_timers_shutdown(struct bnad *bnad) +{ + struct bfa_ioc *ioc = &bnad->bna.ioceth.ioc; + + timer_shutdown_sync(&ioc->ioc_timer); + timer_shutdown_sync(&ioc->sem_timer); + timer_shutdown_sync(&ioc->hb_timer); + timer_shutdown_sync(&ioc->iocpf_timer); +} + static int bnad_ioceth_enable(struct bnad *bnad) { @@ -3727,9 +3743,7 @@ probe_uninit: bnad_res_free(bnad, &bnad->mod_res_info[0], BNA_MOD_RES_T_MAX); disable_ioceth: bnad_ioceth_disable(bnad); - timer_delete_sync(&bnad->bna.ioceth.ioc.ioc_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.sem_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.hb_timer); + bnad_ioc_timers_shutdown(bnad); spin_lock_irqsave(&bnad->bna_lock, flags); bna_uninit(bna); spin_unlock_irqrestore(&bnad->bna_lock, flags); @@ -3770,9 +3784,7 @@ bnad_pci_remove(struct pci_dev *pdev) mutex_lock(&bnad->conf_mutex); bnad_ioceth_disable(bnad); - timer_delete_sync(&bnad->bna.ioceth.ioc.ioc_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.sem_timer); - timer_delete_sync(&bnad->bna.ioceth.ioc.hb_timer); + bnad_ioc_timers_shutdown(bnad); spin_lock_irqsave(&bnad->bna_lock, flags); bna_uninit(bna); spin_unlock_irqrestore(&bnad->bna_lock, flags); diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index b8234ac4b602..8e5c034dc3a4 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -2749,14 +2749,24 @@ static int macb_alloc(struct macb *bp) size = bp->num_queues * macb_tx_ring_size_per_queue(bp); tx = dma_alloc_coherent(dev, size, &tx_dma, GFP_KERNEL); - if (!tx || upper_32_bits(tx_dma) != upper_32_bits(tx_dma + size - 1)) + if (!tx) + goto out_err; + /* Record the buffer so that the error path frees it. */ + bp->queues[0].tx_ring = tx; + bp->queues[0].tx_ring_dma = tx_dma; + if (upper_32_bits(tx_dma) != upper_32_bits(tx_dma + size - 1)) goto out_err; netdev_dbg(bp->netdev, "Allocated %zu bytes for %u TX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)tx_dma, tx); size = bp->num_queues * macb_rx_ring_size_per_queue(bp); rx = dma_alloc_coherent(dev, size, &rx_dma, GFP_KERNEL); - if (!rx || upper_32_bits(rx_dma) != upper_32_bits(rx_dma + size - 1)) + if (!rx) + goto out_err; + /* Record the buffer so that the error path frees it. */ + bp->queues[0].rx_ring = rx; + bp->queues[0].rx_ring_dma = rx_dma; + if (upper_32_bits(rx_dma) != upper_32_bits(rx_dma + size - 1)) goto out_err; netdev_dbg(bp->netdev, "Allocated %zu bytes for %u RX rings at %08lx (mapped %p)\n", size, bp->num_queues, (unsigned long)rx_dma, rx); diff --git a/drivers/net/ethernet/freescale/fman/fman.c b/drivers/net/ethernet/freescale/fman/fman.c index 299bab043175..46cc28895e56 100644 --- a/drivers/net/ethernet/freescale/fman/fman.c +++ b/drivers/net/ethernet/freescale/fman/fman.c @@ -2734,6 +2734,7 @@ static struct fman *read_dts_node(struct platform_device *of_dev) } clk_rate = clk_get_rate(clk); + clk_put(clk); if (!clk_rate) { err = -EINVAL; dev_err(&of_dev->dev, "%s: Failed to determine FM%d clock rate\n", diff --git a/drivers/net/ethernet/google/gve/gve_desc_dqo.h b/drivers/net/ethernet/google/gve/gve_desc_dqo.h index f7786b03c744..d2c86c8eeae2 100644 --- a/drivers/net/ethernet/google/gve/gve_desc_dqo.h +++ b/drivers/net/ethernet/google/gve/gve_desc_dqo.h @@ -14,6 +14,11 @@ #define GVE_TX_MAX_HDR_SIZE_DQO 255 #define GVE_TX_MIN_TSO_MSS_DQO 88 +/* HW limit. This also has to fit in the 14 bits of the mss field of + * struct gve_tx_tso_context_desc_dqo. + */ +#define GVE_TX_MAX_TSO_MSS_DQO 9728 + #ifndef __LITTLE_ENDIAN_BITFIELD #error "Only little endian supported" #endif diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c index 80ab0a449ff5..616c1921aebe 100644 --- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c +++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c @@ -577,15 +577,18 @@ static int gve_prep_tso(struct sk_buff *skb) int header_len; int err; - /* Note: HW requires MSS (gso_size) to be <= 9728 and the total length - * of the TSO to be <= 262143. + /* Note: HW requires the total length of the TSO to be <= 262143, + * this is enforced by netif_set_tso_max_size(). * - * However, we don't validate these because: - * - Hypervisor enforces a limit of 9K MTU - * - Kernel will not produce a TSO larger than 64k + * MSS (gso_size) can not be trusted: packets forwarded from a tap or + * injected by a packet socket can carry an arbitrary value, while the + * mss field of the TSO context descriptor is only 14 bits wide. + * + * A too big MSS is dropped here instead of being rejected from + * gve_features_check_dqo(), because software segmentation would + * produce packets larger than the device can send. */ - - if (unlikely(shinfo->gso_size < GVE_TX_MIN_TSO_MSS_DQO)) + if (unlikely(shinfo->gso_size > GVE_TX_MAX_TSO_MSS_DQO)) return -1; /* Needed because we will modify header. */ @@ -918,13 +921,22 @@ static bool gve_can_send_tso(const struct sk_buff *skb) { const int max_bufs_per_seg = GVE_TX_MAX_DATA_DESCS - 1; const struct skb_shared_info *shinfo = skb_shinfo(skb); - const int header_len = skb_tcp_all_headers(skb); const int gso_size = shinfo->gso_size; int cur_seg_num_bufs; int prev_frag_size; int cur_seg_size; + int header_len; int i; + if (unlikely(gso_size < GVE_TX_MIN_TSO_MSS_DQO)) + return false; + + /* Must match the header length programmed by gve_prep_tso(). */ + if (skb_is_gso_tcp(skb)) + header_len = skb_tcp_all_headers(skb); + else + header_len = skb_transport_offset(skb) + sizeof(struct udphdr); + cur_seg_size = skb_headlen(skb) - header_len; prev_frag_size = skb_headlen(skb); cur_seg_num_bufs = cur_seg_size > 0; @@ -966,7 +978,17 @@ netdev_features_t gve_features_check_dqo(struct sk_buff *skb, struct net_device *dev, netdev_features_t features) { - if (skb_is_gso(skb) && !gve_can_send_tso(skb)) + if (!skb_is_gso(skb)) + return features; + + /* Keep the GSO bits for a too big MSS, so that gve_prep_tso() drops + * the packet: software segmentation would give packets larger than + * the device can send. + */ + if (skb_shinfo(skb)->gso_size > GVE_TX_MAX_TSO_MSS_DQO) + return features; + + if (!gve_can_send_tso(skb)) return features & ~NETIF_F_GSO_MASK; return features; diff --git a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c index bc6b269be299..f1cb6d56e4b9 100644 --- a/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c +++ b/drivers/net/ethernet/hisilicon/hns/hns_dsaf_mac.c @@ -793,6 +793,7 @@ static int hns_mac_register_phy(struct hns_mac_cb *mac_cb) dev_err(mac_cb->dev, "mac%d mdio is NULL, dsaf will probe again later\n", mac_cb->mac_id); + put_device(&pdev->dev); return -EPROBE_DEFER; } @@ -801,6 +802,8 @@ static int hns_mac_register_phy(struct hns_mac_cb *mac_cb) dev_dbg(mac_cb->dev, "mac%d register phy addr:%d\n", mac_cb->mac_id, addr); + put_device(&pdev->dev); + return rc; } diff --git a/drivers/net/ethernet/ibm/emac/core.c b/drivers/net/ethernet/ibm/emac/core.c index 1d46cf6c2c12..e7043523457c 100644 --- a/drivers/net/ethernet/ibm/emac/core.c +++ b/drivers/net/ethernet/ibm/emac/core.c @@ -3044,6 +3044,15 @@ static int emac_probe(struct platform_device *ofdev) if (err) goto err_gone; + if (emac_phy_supports_gige(dev->phy_mode)) { + ndev->netdev_ops = &emac_gige_netdev_ops; + dev->commac.ops = &emac_commac_sg_ops; + } else { + ndev->netdev_ops = &emac_netdev_ops; + dev->commac.ops = &emac_commac_ops; + } + ndev->ethtool_ops = &emac_ethtool_ops; + dev->emacp = devm_platform_ioremap_resource(ofdev, 0); if (IS_ERR(dev->emacp)) { err = PTR_ERR(dev->emacp); @@ -3076,7 +3085,6 @@ static int emac_probe(struct platform_device *ofdev) dev->mdio_instance = platform_get_drvdata(dev->mdio_dev); /* Register with MAL */ - dev->commac.ops = &emac_commac_ops; dev->commac.dev = dev; dev->commac.tx_chan_mask = MAL_CHAN_MASK(dev->mal_tx_chan); dev->commac.rx_chan_mask = MAL_CHAN_MASK(dev->mal_rx_chan); @@ -3144,12 +3152,6 @@ static int emac_probe(struct platform_device *ofdev) ndev->features |= ndev->hw_features | NETIF_F_RXCSUM; } ndev->watchdog_timeo = 5 * HZ; - if (emac_phy_supports_gige(dev->phy_mode)) { - ndev->netdev_ops = &emac_gige_netdev_ops; - dev->commac.ops = &emac_commac_sg_ops; - } else - ndev->netdev_ops = &emac_netdev_ops; - ndev->ethtool_ops = &emac_ethtool_ops; /* MTU range: 46 - 1500 or whatever is in OF */ ndev->min_mtu = EMAC_MIN_MTU; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/common.h b/drivers/net/ethernet/marvell/octeontx2/af/common.h index 779413a383b7..78e42549d990 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/common.h +++ b/drivers/net/ethernet/marvell/octeontx2/af/common.h @@ -7,6 +7,10 @@ #ifndef COMMON_H #define COMMON_H +#include <linux/dma-mapping.h> +#include <linux/gfp.h> +#include <linux/mm.h> + #include "rvu_struct.h" #define OTX2_ALIGN 128 /* Align to cacheline */ @@ -44,6 +48,33 @@ struct qmem { u32 qsize; }; +static inline void *otx2_dma_alloc_coherent(struct device *dev, size_t size, + dma_addr_t *dma_handle) +{ + dma_addr_t dma_addr; + void *vaddr; + + vaddr = kzalloc(size, GFP_KERNEL); + if (!vaddr) + return NULL; + + dma_addr = dma_map_single(dev, vaddr, size, DMA_BIDIRECTIONAL); + if (dma_mapping_error(dev, dma_addr)) { + kfree(vaddr); + return NULL; + } + + *dma_handle = dma_addr; + return vaddr; +} + +static inline void otx2_dma_free_coherent(struct device *dev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + dma_unmap_single(dev, dma_handle, size, DMA_BIDIRECTIONAL); + kfree(vaddr); +} + static inline int qmem_alloc(struct device *dev, struct qmem **q, int qsize, int entry_sz) { @@ -60,8 +91,11 @@ static inline int qmem_alloc(struct device *dev, struct qmem **q, qmem->entry_sz = entry_sz; qmem->alloc_sz = (qsize * entry_sz) + OTX2_ALIGN; - qmem->base = dma_alloc_attrs(dev, qmem->alloc_sz, &qmem->iova, - GFP_KERNEL, DMA_ATTR_FORCE_CONTIGUOUS); + + if (get_order(PAGE_ALIGN(qmem->alloc_sz)) > MAX_PAGE_ORDER) + return -ENOMEM; + + qmem->base = otx2_dma_alloc_coherent(dev, qmem->alloc_sz, &qmem->iova); if (!qmem->base) return -ENOMEM; @@ -80,10 +114,9 @@ static inline void qmem_free(struct device *dev, struct qmem *qmem) return; if (qmem->base) - dma_free_attrs(dev, qmem->alloc_sz, - qmem->base - qmem->align, - qmem->iova - qmem->align, - DMA_ATTR_FORCE_CONTIGUOUS); + otx2_dma_free_coherent(dev, qmem->alloc_sz, + qmem->base - qmem->align, + qmem->iova - qmem->align); devm_kfree(dev, qmem); } diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c index 904374baae6f..2927633465d9 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_debugfs.c @@ -714,110 +714,72 @@ static int get_max_column_width(struct rvu *rvu) } /* Dumps current provisioning status of all RVU block LFs */ -static ssize_t rvu_dbg_rsrc_attach_status(struct file *filp, - char __user *buffer, - size_t count, loff_t *ppos) +static int rvu_dbg_rsrc_attach_status(struct seq_file *filp, void *unused) { - int index, off = 0, flag = 0, len = 0, i = 0; - struct rvu *rvu = filp->private_data; - int bytes_not_copied = 0; + struct rvu *rvu = filp->private; + int index, pf, vf, pcifunc; struct rvu_block block; - int pf, vf, pcifunc; - int buf_size = 2048; int lf_str_size; char *lfs; - char *buf; - - /* don't allow partial reads */ - if (*ppos != 0) - return 0; - - buf = kzalloc(buf_size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - /* Get the maximum width of a column */ lf_str_size = get_max_column_width(rvu); + if (lf_str_size < 0) + return lf_str_size; lfs = kzalloc(lf_str_size, GFP_KERNEL); - if (!lfs) { - kfree(buf); + if (!lfs) return -ENOMEM; - } - off += scnprintf(&buf[off], buf_size - 1 - off, "%-*s", lf_str_size, - "pcifunc"); - for (index = 0; index < BLK_COUNT; index++) - if (strlen(rvu->hw->block[index].name)) { - off += scnprintf(&buf[off], buf_size - 1 - off, - "%-*s", lf_str_size, - rvu->hw->block[index].name); - } - off += scnprintf(&buf[off], buf_size - 1 - off, "\n"); - bytes_not_copied = copy_to_user(buffer + (i * off), buf, off); - if (bytes_not_copied) - goto out; + seq_printf(filp, "%-*s", lf_str_size, "pcifunc"); + for (index = 0; index < BLK_COUNT; index++) + if (strlen(rvu->hw->block[index].name)) + seq_printf(filp, "%-*s", lf_str_size, + rvu->hw->block[index].name); - i++; - *ppos += off; + seq_putc(filp, '\n'); for (pf = 0; pf < rvu->hw->total_pfs; pf++) { for (vf = 0; vf <= rvu->hw->total_vfs; vf++) { - off = 0; - flag = 0; pcifunc = rvu_make_pcifunc(rvu->pdev, pf, vf); if (!pcifunc) continue; - if (vf) { + for (index = 0; index < BLK_COUNT; index++) { + block = rvu->hw->block[index]; + if (!strlen(block.name)) + continue; + lfs[0] = '\0'; + get_lf_str_list(&block, pcifunc, lfs); + if (strlen(lfs)) + break; + } + if (index == BLK_COUNT) + continue; + + if (vf) sprintf(lfs, "PF%d:VF%d", pf, vf - 1); - off = scnprintf(&buf[off], - buf_size - 1 - off, - "%-*s", lf_str_size, lfs); - } else { + else sprintf(lfs, "PF%d", pf); - off = scnprintf(&buf[off], - buf_size - 1 - off, - "%-*s", lf_str_size, lfs); - } + seq_printf(filp, "%-*s", lf_str_size, lfs); for (index = 0; index < BLK_COUNT; index++) { block = rvu->hw->block[index]; if (!strlen(block.name)) continue; - len = 0; - lfs[len] = '\0'; - get_lf_str_list(&block, pcifunc, lfs); - if (strlen(lfs)) - flag = 1; - off += scnprintf(&buf[off], buf_size - 1 - off, - "%-*s", lf_str_size, lfs); - } - if (flag) { - off += scnprintf(&buf[off], - buf_size - 1 - off, "\n"); - bytes_not_copied = copy_to_user(buffer + - (i * off), - buf, off); - if (bytes_not_copied) - goto out; - - i++; - *ppos += off; + lfs[0] = '\0'; + get_lf_str_list(&block, pcifunc, lfs); + seq_printf(filp, "%-*s", lf_str_size, lfs); } + seq_putc(filp, '\n'); } } -out: kfree(lfs); - kfree(buf); - if (bytes_not_copied) - return -EFAULT; - return *ppos; + return 0; } -RVU_DEBUG_FOPS(rsrc_status, rsrc_attach_status, NULL); +RVU_DEBUG_SEQ_FOPS(rsrc_status, rsrc_attach_status, NULL); static int rvu_dbg_rvu_pf_cgx_map_display(struct seq_file *filp, void *unused) { diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c index fd7a49ae88d0..2ea5dfe85539 100644 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c @@ -4509,6 +4509,10 @@ static int mtk_unreg_dev(struct mtk_eth *eth) mac = netdev_priv(eth->netdev[i]); if (MTK_HAS_CAPS(eth->soc->caps, MTK_QDMA)) unregister_netdevice_notifier(&mac->device_notifier); + + if (eth->netdev[i]->reg_state != NETREG_REGISTERED) + continue; + unregister_netdev(eth->netdev[i]); } @@ -5344,7 +5348,7 @@ static int mtk_probe(struct platform_device *pdev) err = register_netdev(eth->netdev[i]); if (err) { dev_err(eth->dev, "error bringing up device\n"); - goto err_deinit_ppe; + goto err_unreg_netdev; } else netif_info(eth, probe, eth->netdev[i], "mediatek frame engine at 0x%08lx, irq %d\n", diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c index baac38bece14..4b7b0a0fc2b2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/rep/bridge.c @@ -85,9 +85,16 @@ mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(struct net_device *dev, struct m struct net_device *lower_dev; struct list_head *iter; - if (netif_is_lag_master(dev) || mlx5e_eswitch_rep(dev)) - return mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, esw, vport_num, - esw_owner_vhca_id); + if (netif_is_lag_master(dev) || mlx5e_eswitch_rep(dev)) { + struct net_device *rep; + + rep = mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, esw, vport_num, + esw_owner_vhca_id); + if (rep && !mlx5_esw_bridge_port_exists(*vport_num, *esw_owner_vhca_id, + esw->br_offloads)) + return NULL; + return rep; + } netdev_for_each_lower_dev(dev, lower_dev, iter) { struct net_device *rep; @@ -104,6 +111,28 @@ mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(struct net_device *dev, struct m return NULL; } +static bool mlx5_esw_bridge_rep_port_lookup(struct net_device *dev, + struct mlx5_esw_bridge_offloads *br_offloads, + u16 *vport_num, u16 *esw_owner_vhca_id) +{ + if (!mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, br_offloads->esw, vport_num, + esw_owner_vhca_id)) + return false; + + return mlx5_esw_bridge_port_exists(*vport_num, *esw_owner_vhca_id, br_offloads); +} + +static bool mlx5_esw_bridge_lower_rep_port_lookup(struct net_device *dev, + struct mlx5_esw_bridge_offloads *br_offloads, + u16 *vport_num, u16 *esw_owner_vhca_id) +{ + if (!mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(dev, br_offloads->esw, vport_num, + esw_owner_vhca_id)) + return false; + + return mlx5_esw_bridge_port_exists(*vport_num, *esw_owner_vhca_id, br_offloads); +} + static bool mlx5_esw_bridge_is_local(struct net_device *dev, struct net_device *rep, struct mlx5_eswitch *esw) { @@ -218,8 +247,7 @@ mlx5_esw_bridge_port_obj_add(struct net_device *dev, u16 vport_num, esw_owner_vhca_id; int err; - if (!mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, br_offloads->esw, &vport_num, - &esw_owner_vhca_id)) + if (!mlx5_esw_bridge_rep_port_lookup(dev, br_offloads, &vport_num, &esw_owner_vhca_id)) return 0; port_obj_info->handled = true; @@ -251,8 +279,7 @@ mlx5_esw_bridge_port_obj_del(struct net_device *dev, const struct switchdev_obj_port_mdb *mdb; u16 vport_num, esw_owner_vhca_id; - if (!mlx5_esw_bridge_rep_vport_num_vhca_id_get(dev, br_offloads->esw, &vport_num, - &esw_owner_vhca_id)) + if (!mlx5_esw_bridge_rep_port_lookup(dev, br_offloads, &vport_num, &esw_owner_vhca_id)) return 0; port_obj_info->handled = true; @@ -283,8 +310,8 @@ mlx5_esw_bridge_port_obj_attr_set(struct net_device *dev, u16 vport_num, esw_owner_vhca_id; int err = 0; - if (!mlx5_esw_bridge_lower_rep_vport_num_vhca_id_get(dev, br_offloads->esw, &vport_num, - &esw_owner_vhca_id)) + if (!mlx5_esw_bridge_lower_rep_port_lookup(dev, br_offloads, &vport_num, + &esw_owner_vhca_id)) return 0; port_attr_info->handled = true; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c index 6c87a1c7db09..c1841ce74d9a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/tc_ct.c @@ -1082,6 +1082,9 @@ mlx5_tc_ct_shared_counter_get(struct mlx5_tc_ct_priv *ct_priv, spin_unlock_bh(&ct_priv->ht_lock); + if (rev_entry) + mlx5_tc_ct_entry_put(rev_entry); + create_counter: shared_counter = mlx5_tc_ct_counter_create(ct_priv); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c index 329608c59313..8ffa8068e90a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/ipsec_fs.c @@ -1564,14 +1564,14 @@ static void setup_fte_addr6(struct mlx5_flow_spec *spec, memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6), saddr, 16); memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_criteria, - outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6), dmask, 16); + outer_headers.src_ipv4_src_ipv6.ipv6_layout.ipv6), smask, 16); } if (!addr6_all_zero(daddr)) { memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_value, outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6), daddr, 16); memcpy(MLX5_ADDR_OF(fte_match_param, spec->match_criteria, - outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6), smask, 16); + outer_headers.dst_ipv4_dst_ipv6.ipv6_layout.ipv6), dmask, 16); } } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c index daff53ba7d09..38a3415acf7a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c @@ -1724,6 +1724,8 @@ void mlx5e_macsec_build_netdev(struct mlx5e_priv *priv) mlx5_core_dbg(priv->mdev, "mlx5e: MACsec acceleration enabled\n"); netdev->macsec_ops = &macsec_offload_ops; netdev->features |= NETIF_F_HW_MACSEC; + netdev->hw_features |= NETIF_F_HW_MACSEC; + netdev->vlan_features |= NETIF_F_HW_MACSEC; netif_keep_dst(netdev); } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index fc110a7d16e8..4c6060d54fbe 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -5851,7 +5851,6 @@ static void mlx5e_build_nic_netdev(struct net_device *netdev) netdev->vlan_features |= NETIF_F_SG; netdev->vlan_features |= NETIF_F_HW_CSUM; - netdev->vlan_features |= NETIF_F_HW_MACSEC; netdev->vlan_features |= NETIF_F_GRO; netdev->vlan_features |= NETIF_F_TSO; netdev->vlan_features |= NETIF_F_TSO6; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c index 87b5fd349594..b4cf3c5ac0dd 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.c @@ -1649,10 +1649,8 @@ int mlx5_esw_bridge_vport_unlink(struct net_device *br_netdev, u16 vport_num, int err; port = mlx5_esw_bridge_port_lookup(vport_num, esw_owner_vhca_id, br_offloads); - if (!port) { - NL_SET_ERR_MSG_MOD(extack, "Port is not attached to any bridge"); - return -EINVAL; - } + if (!port) + return 0; if (port->bridge->ifindex != br_netdev->ifindex) { NL_SET_ERR_MSG_MOD(extack, "Port is attached to another bridge"); return -EINVAL; @@ -1682,10 +1680,19 @@ int mlx5_esw_bridge_vport_peer_unlink(struct net_device *br_netdev, u16 vport_nu struct mlx5_esw_bridge_offloads *br_offloads, struct netlink_ext_ack *extack) { + if (!MLX5_CAP_ESW(br_offloads->esw->dev, merged_eswitch)) + return 0; + return mlx5_esw_bridge_vport_unlink(br_netdev, vport_num, esw_owner_vhca_id, br_offloads, extack); } +bool mlx5_esw_bridge_port_exists(u16 vport_num, u16 esw_owner_vhca_id, + struct mlx5_esw_bridge_offloads *br_offloads) +{ + return mlx5_esw_bridge_port_lookup(vport_num, esw_owner_vhca_id, br_offloads); +} + int mlx5_esw_bridge_port_vlan_add(u16 vport_num, u16 esw_owner_vhca_id, u16 vid, u16 flags, struct mlx5_esw_bridge_offloads *br_offloads, struct netlink_ext_ack *extack) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h index d6f539161993..a4e59cc21089 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/bridge.h @@ -80,6 +80,8 @@ int mlx5_esw_bridge_vlan_proto_set(u16 vport_num, u16 esw_owner_vhca_id, u16 pro struct mlx5_esw_bridge_offloads *br_offloads); int mlx5_esw_bridge_mcast_set(u16 vport_num, u16 esw_owner_vhca_id, bool enable, struct mlx5_esw_bridge_offloads *br_offloads); +bool mlx5_esw_bridge_port_exists(u16 vport_num, u16 esw_owner_vhca_id, + struct mlx5_esw_bridge_offloads *br_offloads); int mlx5_esw_bridge_port_vlan_add(u16 vport_num, u16 esw_owner_vhca_id, u16 vid, u16 flags, struct mlx5_esw_bridge_offloads *br_offloads, struct netlink_ext_ack *extack); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c index c655f6e32e9b..dd14cdc378de 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c @@ -1266,25 +1266,45 @@ void mlx5_lag_remove_devices(struct mlx5_lag *ldev) mlx5_lag_remove_devices_filter(ldev, MLX5_LAG_FILTER_PORTS); } +static int mlx5_lag_reload_ib_reps_idx(struct mlx5_lag *ldev, int idx, + u32 flags) +{ + struct lag_func *pf = mlx5_lag_pf(ldev, idx); + struct mlx5_eswitch *esw; + int ret; + + if (pf->dev->priv.flags & flags) + return 0; + + esw = pf->dev->priv.eswitch; + mlx5_esw_reps_block(esw); + ret = mlx5_eswitch_reload_ib_reps(esw); + mlx5_esw_reps_unblock(esw); + + return ret; +} + static int mlx5_lag_reload_ib_reps_unlocked(struct mlx5_lag *ldev, u32 flags, u32 filter, bool cont_on_fail) { - struct lag_func *pf; + int master_idx = mlx5_lag_get_dev_index_by_seq_filter(ldev, MLX5_LAG_P1, + filter); int ret; int i; + if (master_idx < 0) + return -EINVAL; + + ret = mlx5_lag_reload_ib_reps_idx(ldev, master_idx, flags); + if (ret && !cont_on_fail) + return ret; + mlx5_lag_for_each(i, 0, ldev, filter) { - pf = mlx5_lag_pf(ldev, i); - if (!(pf->dev->priv.flags & flags)) { - struct mlx5_eswitch *esw; - - esw = pf->dev->priv.eswitch; - mlx5_esw_reps_block(esw); - ret = mlx5_eswitch_reload_ib_reps(esw); - mlx5_esw_reps_unblock(esw); - if (ret && !cont_on_fail) - return ret; - } + if (i == master_idx) + continue; + ret = mlx5_lag_reload_ib_reps_idx(ldev, i, flags); + if (ret && !cont_on_fail) + return ret; } return 0; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c index 6b4ad3c53f2f..424040918fa3 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/shared_fdb.c @@ -270,6 +270,7 @@ err_rescan_drivers: pf->sd_fdb_active = false; } mlx5_lag_destroy_single_fdb_filter(ldev, group_id); + mlx5_lag_unload_reps_from_locked(ldev, filter); } err_add_devices: mlx5_lag_add_devices_filter(ldev, filter); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c index 64f92427602d..75855481522b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/devcom.c @@ -37,6 +37,7 @@ struct mlx5_devcom_comp { struct mlx5_devcom_key key; mlx5_devcom_event_handler_t handler; struct kref ref; + int nr_devs; bool ready; struct rw_semaphore sem; struct lock_class_key lock_key; @@ -170,6 +171,7 @@ devcom_alloc_comp_dev(struct mlx5_devcom_dev *devc, down_write(&comp->sem); list_add_tail(&devcom->list, &comp->comp_dev_list_head); + WRITE_ONCE(comp->nr_devs, comp->nr_devs + 1); up_write(&comp->sem); return devcom; @@ -182,6 +184,7 @@ devcom_free_comp_dev(struct mlx5_devcom_comp_dev *devcom) down_write(&comp->sem); list_del(&devcom->list); + WRITE_ONCE(comp->nr_devs, comp->nr_devs - 1); up_write(&comp->sem); kref_put(&devcom->devc->ref, mlx5_devcom_dev_release); @@ -284,7 +287,7 @@ int mlx5_devcom_comp_get_size(struct mlx5_devcom_comp_dev *devcom) { struct mlx5_devcom_comp *comp = devcom->comp; - return kref_read(&comp->ref); + return READ_ONCE(comp->nr_devs); } int mlx5_devcom_locked_send_event(struct mlx5_devcom_comp_dev *devcom, diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h index 64b958df7774..baba3471bf5a 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h @@ -974,6 +974,7 @@ enum { /* PUL User Registers */ #define FBNIC_CSR_START_PUL_USER 0x31000 /* CSR section delimiter */ #define FBNIC_PUL_OB_TLP_HDR_AW_CFG 0x3103d /* 0xc40f4 */ +#define FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH_MODE CSR_BIT(20) #define FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH CSR_BIT(19) #define FBNIC_PUL_OB_TLP_HDR_AW_CFG_BME CSR_BIT(18) #define FBNIC_PUL_OB_TLP_HDR_AW_CFG_RDE_ATTR CSR_GENMASK(17, 15) @@ -1215,6 +1216,10 @@ enum { #define FBNIC_IPC_MBX_DESC_LEN_MASK DESC_GENMASK(63, 48) #define FBNIC_IPC_MBX_DESC_EOM DESC_BIT(46) #define FBNIC_IPC_MBX_DESC_ADDR_MASK DESC_GENMASK(45, 3) +/* Set with FW_CMPL when the FW completed a descriptor without successfully + * processing it (e.g. a mailbox DMA error); the completion has no valid data. + */ +#define FBNIC_IPC_MBX_DESC_FW_ERR DESC_BIT(2) #define FBNIC_IPC_MBX_DESC_FW_CMPL DESC_BIT(1) #define FBNIC_IPC_MBX_DESC_HOST_CMPL DESC_BIT(0) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c b/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c index 3c4563c8f403..6edfa0aa69f1 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c @@ -539,8 +539,8 @@ static void fbnic_dbg_fw_mbx_display(struct seq_file *s, /* Generate header */ seq_puts(s, mbx_idx == FBNIC_IPC_MBX_RX_IDX ? "Rx\n" : "Tx\n"); - seq_printf(s, "Rdy: %d Head: %d Tail: %d\n", - mbx->ready, mbx->head, mbx->tail); + seq_printf(s, "Rdy: %d Head: %d Tail: %d resp_error: %llu\n", + mbx->ready, mbx->head, mbx->tail, mbx->resp_error); snprintf(hdr, sizeof(hdr), "%3s %-4s %s %-12s %s %-3s %-16s\n", "Idx", "Len", "E", "Addr", "F", "H", "Raw"); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index 0e47088ec44b..76e9a545bb16 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -313,6 +313,11 @@ fbnic_get_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring, kernel_ring->hds_thresh = fbn->hds_thresh; } +static u32 fbnic_ring_size_pow2(u32 size) +{ + return size ? roundup_pow_of_two(size) : 0; +} + static void fbnic_set_rings(struct fbnic_net *fbn, struct ethtool_ringparam *ring, struct kernel_ethtool_ringparam *kernel_ring) @@ -334,10 +339,10 @@ fbnic_set_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring, struct fbnic_net *clone; int err; - ring->rx_pending = roundup_pow_of_two(ring->rx_pending); - ring->rx_mini_pending = roundup_pow_of_two(ring->rx_mini_pending); - ring->rx_jumbo_pending = roundup_pow_of_two(ring->rx_jumbo_pending); - ring->tx_pending = roundup_pow_of_two(ring->tx_pending); + ring->rx_pending = fbnic_ring_size_pow2(ring->rx_pending); + ring->rx_mini_pending = fbnic_ring_size_pow2(ring->rx_mini_pending); + ring->rx_jumbo_pending = fbnic_ring_size_pow2(ring->rx_jumbo_pending); + ring->tx_pending = fbnic_ring_size_pow2(ring->tx_pending); /* These are absolute minimums allowing the device and driver to operate * but not necessarily guarantee reasonable performance. Settings below @@ -2025,7 +2030,8 @@ static const struct ethtool_ops fbnic_ethtool_ops = { ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM | ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | - ETHTOOL_OP_NEEDS_RTNL_GLINK, + ETHTOOL_OP_NEEDS_RTNL_GLINK | + ETHTOOL_OP_NEEDS_RTNL_TEST, .get_drvinfo = fbnic_get_drvinfo, .get_regs_len = fbnic_get_regs_len, .get_regs = fbnic_get_regs, diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c index 283d25fae79e..6d7eb8479edf 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c @@ -60,8 +60,15 @@ static void fbnic_mbx_reset_desc_ring(struct fbnic_dev *fbd, int mbx_idx) */ switch (mbx_idx) { case FBNIC_IPC_MBX_RX_IDX: + /* Clearing BME blocks the device from writing to the host + * but leaves the requests parked in the write pipeline. The + * write path only clears outstanding requests when both FLUSH + * and FLUSH_MODE are set; FLUSH_MODE lets them drain without + * landing on the host. + */ wr32(fbd, FBNIC_PUL_OB_TLP_HDR_AW_CFG, - FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH); + FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH | + FBNIC_PUL_OB_TLP_HDR_AW_CFG_FLUSH_MODE); break; case FBNIC_IPC_MBX_TX_IDX: wr32(fbd, FBNIC_PUL_OB_TLP_HDR_AR_CFG, @@ -285,6 +292,12 @@ static void fbnic_mbx_process_tx_msgs(struct fbnic_dev *fbd) if (!(desc & FBNIC_IPC_MBX_DESC_FW_CMPL)) break; + if (desc & FBNIC_IPC_MBX_DESC_FW_ERR) { + tx_mbx->resp_error++; + dev_warn_ratelimited(fbd->dev, + "FW completed a Tx mailbox request with an error\n"); + } + fbnic_mbx_unmap_and_free_msg(fbd, FBNIC_IPC_MBX_TX_IDX, head); head++; @@ -1666,6 +1679,13 @@ static void fbnic_mbx_process_rx_msgs(struct fbnic_dev *fbd) if (!(desc & FBNIC_IPC_MBX_DESC_FW_CMPL)) break; + if (desc & FBNIC_IPC_MBX_DESC_FW_ERR) { + rx_mbx->resp_error++; + dev_warn_ratelimited(fbd->dev, + "FW reported an error on an Rx mailbox message; dropping\n"); + goto next_page; + } + dma_sync_single_for_cpu(fbd->dev, rx_mbx->buf_info[head].addr, FBNIC_RX_PAGE_SIZE, DMA_FROM_DEVICE); @@ -1733,7 +1753,9 @@ void fbnic_mbx_poll(struct fbnic_dev *fbd) int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd) { struct fbnic_fw_mbx *tx_mbx = &fbd->mbx[FBNIC_IPC_MBX_TX_IDX]; + struct fbnic_fw_mbx *rx_mbx = &fbd->mbx[FBNIC_IPC_MBX_RX_IDX]; unsigned long timeout = jiffies + 10 * HZ + 1; + u64 tx_resp_error, rx_resp_error; int err, i; do { @@ -1764,6 +1786,9 @@ int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd) * mgmt.version once we get the actual version from the firmware * in the capabilities request message. */ +send_cap_req: + tx_resp_error = tx_mbx->resp_error; + rx_resp_error = rx_mbx->resp_error; err = fbnic_fw_xmit_simple_msg(fbd, FBNIC_TLV_MSG_ID_HOST_CAP_REQ); if (err) goto clean_mbx; @@ -1781,9 +1806,27 @@ int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd) msleep(20); fbnic_mbx_poll(fbd); + /* A valid capabilities response ends the poll. Check it + * before the FW_ERR retry below so a response parsed in the + * same poll as an unrelated FW_ERR is not discarded. + */ + if (fbd->fw_cap.running.mgmt.version >= MIN_FW_VER_CODE) + break; + /* set err, but wait till mgmt.version check to report it */ - if (!time_is_after_jiffies(timeout)) + if (!time_is_after_jiffies(timeout)) { err = -ETIMEDOUT; + continue; + } + + /* The FW can flag our capabilities request (Tx) or its + * response (Rx) with FW_ERR, in which case it produced no + * usable response. The ring is not wedged, so re-issue the + * request instead of spinning until the timeout. + */ + if (tx_mbx->resp_error != tx_resp_error || + rx_mbx->resp_error != rx_resp_error) + goto send_cap_req; } return 0; diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h index d84723e4cfa3..5f9969247e30 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h @@ -13,6 +13,7 @@ struct fbnic_tlv_msg; struct fbnic_fw_mbx { u8 ready, head, tail; + u64 resp_error; struct { struct fbnic_tlv_msg *msg; dma_addr_t addr; diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c index 8b9bc9e8ea56..c6698e3002a1 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c @@ -434,6 +434,7 @@ static int fbnic_pm_suspend(struct device *dev) { struct fbnic_dev *fbd = dev_get_drvdata(dev); struct net_device *netdev = fbd->netdev; + struct fbnic_net *fbn; if (fbnic_init_failure(fbd)) goto null_uc_addr; @@ -441,11 +442,16 @@ static int fbnic_pm_suspend(struct device *dev) rtnl_lock(); netdev_lock(netdev); + fbn = netdev_priv(netdev); + netif_device_detach(netdev); if (netif_running(netdev)) netdev->netdev_ops->ndo_stop(netdev); + /* The IRQs are about to be freed, so drop the napi vector count */ + fbn->num_napi = 0; + netdev_unlock(netdev); rtnl_unlock(); @@ -508,16 +514,20 @@ static int __fbnic_pm_resume(struct device *dev) if (fbnic_init_failure(fbd)) return 0; + rtnl_lock(); + netdev_lock(netdev); + fbn = netdev_priv(netdev); /* Reset the queues if needed */ fbnic_reset_queues(fbn, fbn->num_tx_queues, fbn->num_rx_queues); - rtnl_lock(); - netdev_lock(netdev); - - if (netif_running(netdev)) + if (netif_running(netdev)) { err = __fbnic_open(fbn); + /* On failure the vectors are freed, so drop the count */ + if (err) + fbn->num_napi = 0; + } netdev_unlock(netdev); rtnl_unlock(); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c index e7918d3f6aba..10caacffee0f 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c @@ -7,6 +7,7 @@ #include <linux/iopoll.h> #include <linux/pci.h> #include <net/netdev_queues.h> +#include <net/netdev_rx_queue.h> #include <net/page_pool/helpers.h> #include <net/tcp.h> #include <net/xdp.h> @@ -1622,7 +1623,7 @@ fbnic_alloc_qt_page_pools(struct fbnic_net *fbn, struct fbnic_q_triad *qt, return 0; err_destroy_sub0: - page_pool_destroy(pp); + page_pool_destroy(qt->sub0.page_pool); return PTR_ERR(pp); } @@ -1791,7 +1792,7 @@ int fbnic_alloc_napi_vectors(struct fbnic_net *fbn) int err; /* Allocate 1 Tx queue per napi vector */ - if (num_napi < FBNIC_MAX_TXQS && num_napi == num_tx + num_rx) { + if (num_napi <= FBNIC_MAX_TXQS && num_napi == num_tx + num_rx) { while (num_tx) { err = fbnic_alloc_napi_vector(fbd, fbn, num_napi, v_idx, @@ -2853,6 +2854,17 @@ void fbnic_napi_depletion_check(struct net_device *netdev) fbnic_wrfl(fbd); } +/* Returns the napi vector servicing an Rx queue, or NULL if the datapath + * is torn down. The association is published by fbnic_set_netif_napi() + * and cleared by fbnic_reset_netif_napi(), both under the instance lock. + */ +static struct fbnic_napi_vector *fbnic_rxq_nv(struct net_device *dev, int idx) +{ + struct napi_struct *napi = __netif_get_rx_queue(dev, idx)->napi; + + return napi ? container_of(napi, struct fbnic_napi_vector, napi) : NULL; +} + static int fbnic_queue_mem_alloc(struct net_device *dev, struct netdev_queue_config *qcfg, void *qmem, int idx) @@ -2865,8 +2877,16 @@ static int fbnic_queue_mem_alloc(struct net_device *dev, if (!netif_running(dev)) return fbnic_alloc_qt_page_pools(fbn, qt, idx); + /* A failed PCIe recovery or resume can leave the datapath torn down + * while netif_running() is still true. This ndo runs before + * netdev_rx_queue_restart() checks netif_running(), so bail out + * rather than touching rings and vectors that are already freed. + */ + nv = fbnic_rxq_nv(dev, idx); + if (!nv) + return -ENETDOWN; + real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl); - nv = fbn->napi[idx % fbn->num_napi]; fbnic_ring_init(&qt->sub0, real->sub0.doorbell, real->sub0.q_idx, real->sub0.flags); @@ -2917,7 +2937,7 @@ static int fbnic_queue_start(struct net_device *dev, struct fbnic_q_triad *real; real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl); - nv = fbn->napi[idx % fbn->num_napi]; + nv = fbnic_rxq_nv(dev, idx); fbnic_aggregate_ring_bdq_counters(fbn, &real->sub0); fbnic_aggregate_ring_bdq_counters(fbn, &real->sub1); @@ -2939,7 +2959,7 @@ static int fbnic_queue_stop(struct net_device *dev, void *qmem, int idx) int err; real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl); - nv = fbn->napi[idx % fbn->num_napi]; + nv = fbnic_rxq_nv(dev, idx); fbnic_dbg_nv_exit(nv); napi_disable_locked(&nv->napi); diff --git a/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c b/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c index 9e7c285eaa6b..960d7513aa8d 100644 --- a/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c +++ b/drivers/net/ethernet/netronome/nfp/crypto/ipsec.c @@ -625,11 +625,12 @@ int nfp_net_ipsec_rx(struct nfp_meta_parsed *meta, struct sk_buff *skb) xa_lock(&nn->xa_ipsec); x = xa_load(&nn->xa_ipsec, saidx); + if (x) + xfrm_state_hold(x); xa_unlock(&nn->xa_ipsec); if (!x) return -EINVAL; - xfrm_state_hold(x); sp->xvec[sp->len++] = x; sp->olen++; xo = xfrm_offload(skb); diff --git a/drivers/net/ethernet/sfc/efx.c b/drivers/net/ethernet/sfc/efx.c index 3806cd3dd7f4..953f1d90b9b1 100644 --- a/drivers/net/ethernet/sfc/efx.c +++ b/drivers/net/ethernet/sfc/efx.c @@ -904,6 +904,10 @@ static const struct pci_device_id efx_pci_table[] = { .driver_data = (unsigned long)&efx_x4_nic_type}, {PCI_DEVICE(PCI_VENDOR_ID_SOLARFLARE, 0x2c03), /* X4 PF (FF only) */ .driver_data = (unsigned long)&efx_x4_nic_type}, + {PCI_DEVICE(PCI_VENDOR_ID_SOLARFLARE, 0x8c03), /* X4D PF (FF/LL) */ + .driver_data = (unsigned long)&efx_x4_nic_type}, + {PCI_DEVICE(PCI_VENDOR_ID_SOLARFLARE, 0xac03), /* X4D PF (FF only) */ + .driver_data = (unsigned long)&efx_x4_nic_type}, {0} /* end of list */ }; diff --git a/drivers/net/ethernet/spacemit/k1_emac.c b/drivers/net/ethernet/spacemit/k1_emac.c index f7f16397a2c2..d641ac26a1e8 100644 --- a/drivers/net/ethernet/spacemit/k1_emac.c +++ b/drivers/net/ethernet/spacemit/k1_emac.c @@ -803,6 +803,9 @@ err_free_skb: while (i != head) { emac_free_tx_buf(priv, i); + tx_desc_addr = &((struct emac_desc *)tx_ring->desc_addr)[i]; + memset(tx_desc_addr, 0, sizeof(*tx_desc_addr)); + if (++i == tx_ring->total_cnt) i = 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c index 8d7042e68926..72bdbcb5e863 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c @@ -1162,8 +1162,11 @@ static int gmac_clk_enable(struct rk_priv_data *bsp_priv, bool enable) return ret; ret = clk_prepare_enable(bsp_priv->clk_phy); - if (ret) + if (ret) { + clk_bulk_disable_unprepare(bsp_priv->num_clks, + bsp_priv->clks); return ret; + } rk_configure_io_clksel(bsp_priv); rk_ungate_rmii_clock(bsp_priv); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 2994df41ec2c..c6a8f8d73501 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -474,11 +474,11 @@ static void dwmac4_set_sarc(struct dma_desc *p, u32 sarc_type) sarc_type)); } -static int set_16kib_bfsize(int mtu) +static int set_16kib_bfsize(int len) { int ret = 0; - if (unlikely(mtu >= BUF_SIZE_8KiB)) + if (unlikely(len > BUF_SIZE_8KiB)) ret = BUF_SIZE_16KiB; return ret; } diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 9314bcb85c22..857f7562c6c6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -540,7 +540,7 @@ struct stmmac_mode_ops { bool (*is_jumbo_frm)(unsigned int len, bool enh_desc); int (*jumbo_frm)(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, int csum); - int (*set_16kib_bfsize)(int mtu); + int (*set_16kib_bfsize)(int len); void (*init_desc3)(struct dma_desc *p); void (*refill_desc3)(struct stmmac_rx_queue *rx_q, struct dma_desc *p); void (*clean_desc3)(struct stmmac_tx_queue *tx_q, struct dma_desc *p); diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index f7949419eb9f..d2f0c321661d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -124,10 +124,10 @@ static void clean_desc3(struct stmmac_tx_queue *tx_q, struct dma_desc *p) p->des3 = 0; } -static int set_16kib_bfsize(int mtu) +static int set_16kib_bfsize(int len) { int ret = 0; - if (unlikely(mtu > BUF_SIZE_8KiB)) + if (unlikely(len > BUF_SIZE_8KiB)) ret = BUF_SIZE_16KiB; return ret; } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 1fb5f804ea23..3f34d491c959 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1536,17 +1536,17 @@ static unsigned int stmmac_rx_offset(struct stmmac_priv *priv) return NET_SKB_PAD + NET_IP_ALIGN; } -static int stmmac_set_bfsize(int mtu) +static int stmmac_set_bfsize(int len) { int ret; - if (mtu >= BUF_SIZE_8KiB) + if (len > BUF_SIZE_8KiB) ret = BUF_SIZE_16KiB; - else if (mtu >= BUF_SIZE_4KiB) + else if (len > BUF_SIZE_4KiB) ret = BUF_SIZE_8KiB; - else if (mtu >= BUF_SIZE_2KiB) + else if (len > BUF_SIZE_2KiB) ret = BUF_SIZE_4KiB; - else if (mtu > DEFAULT_BUFSIZE) + else if (len > DEFAULT_BUFSIZE) ret = BUF_SIZE_2KiB; else ret = DEFAULT_BUFSIZE; @@ -4063,7 +4063,7 @@ static struct stmmac_dma_conf * stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) { struct stmmac_dma_conf *dma_conf; - int bfsize, ret; + int bfsize, len, ret; u8 chan; dma_conf = kzalloc_obj(*dma_conf); @@ -4073,13 +4073,15 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) return ERR_PTR(-ENOMEM); } - /* Returns 0 or BUF_SIZE_16KiB if mtu > 8KiB and dwmac4 or ring mode */ - bfsize = stmmac_set_16kib_bfsize(priv, mtu); + len = mtu + ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN; + + /* Returns 0 or BUF_SIZE_16KiB if len > 8KiB and dwmac4 or ring mode */ + bfsize = stmmac_set_16kib_bfsize(priv, len); if (bfsize < 0) bfsize = 0; if (bfsize < BUF_SIZE_16KiB) - bfsize = stmmac_set_bfsize(mtu); + bfsize = stmmac_set_bfsize(len); dma_conf->dma_buf_sz = bfsize; /* Chose the tx/rx size from the already defined one in the @@ -5887,6 +5889,7 @@ read_again: if (!skb) { page_pool_recycle_direct(rx_q->page_pool, buf->page); + buf->page = NULL; rx_dropped++; count++; goto drain_data; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 6372ec7c3f31..c25dc9f89270 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -12,6 +12,7 @@ #include <linux/ethtool.h> #include <linux/ip.h> #include <linux/udp.h> +#include <net/dsa.h> #include <net/pkt_cls.h> #include <net/pkt_sched.h> #include <net/tcp.h> @@ -29,6 +30,7 @@ struct stmmachdr { sizeof(struct stmmachdr)) #define STMMAC_TEST_PKT_MAGIC 0xdeadcafecafedeadULL #define STMMAC_LB_TIMEOUT msecs_to_jiffies(200) +#define STMMAC_SFT_MAX_LPI (5 * USEC_PER_SEC) struct stmmac_packet_attrs { int vlan; @@ -237,6 +239,10 @@ struct stmmac_test_priv { struct stmmac_packet_attrs *packet; struct packet_type pt; struct completion comp; + __be16 packet_type; + int (*func)(struct sk_buff *skb, struct net_device *ndev, + struct packet_type *pt, struct net_device *orig_ndev); + bool capture_all; int double_vlan; int vlan_id; int ok; @@ -316,6 +322,52 @@ out: return 0; } +static int stmmac_sft_filter(struct sk_buff *skb, struct net_device *ndev, + struct packet_type *pt, + struct net_device *orig_ndev) +{ + struct stmmac_test_priv *tpriv = pt->af_packet_priv; + struct ethhdr *hdr = eth_hdr(skb); + int ret = 0; + + if (hdr->h_proto == tpriv->packet_type) { + struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC); + + if (nskb) + ret = tpriv->func(nskb, ndev, pt, orig_ndev); + } + + kfree_skb(skb); + return ret; +} + +static void stmmac_sft_add_pack(struct packet_type *pt) +{ + struct stmmac_test_priv *tpriv = pt->af_packet_priv; + + if (netdev_uses_dsa(tpriv->pt.dev) || tpriv->capture_all) { + tpriv->packet_type = tpriv->pt.type; + tpriv->func = tpriv->pt.func; + + /* DSA conduit will report ETH_P_XDSA, so our packet handler + * won't match. Let's register a ETH_P_ALL match and filter + * manually in stmmac_sft_filter. This is also useful for + * VLAN tests, to capture packets otherwise marked as + * OTHERHOST. + */ + tpriv->pt.type = htons(ETH_P_ALL); + tpriv->pt.func = stmmac_sft_filter; + tpriv->pt.ignore_outgoing = true; + } + + dev_add_pack(pt); +} + +static void stmmac_sft_remove_pack(struct packet_type *pt) +{ + dev_remove_pack(pt); +} + static int __stmmac_test_loopback(struct stmmac_priv *priv, struct stmmac_packet_attrs *attr) { @@ -337,7 +389,7 @@ static int __stmmac_test_loopback(struct stmmac_priv *priv, tpriv->packet = attr; if (!attr->dont_wait) - dev_add_pack(&tpriv->pt); + stmmac_sft_add_pack(&tpriv->pt); skb = stmmac_test_get_udp_skb(priv, attr); if (!skb) { @@ -360,7 +412,7 @@ static int __stmmac_test_loopback(struct stmmac_priv *priv, cleanup: if (!attr->dont_wait) - dev_remove_pack(&tpriv->pt); + stmmac_sft_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -414,12 +466,16 @@ static int stmmac_test_mmc(struct stmmac_priv *priv) static int stmmac_test_eee(struct stmmac_priv *priv) { struct stmmac_extra_stats *initial, *final; - int retries = 10; + unsigned long timeout, max_duration; int ret; if (!priv->dma_cap.eee || !priv->eee_active) return -EOPNOTSUPP; + /* Bail out if the configured LPI timer is too long */ + if (priv->tx_lpi_timer > STMMAC_SFT_MAX_LPI) + return -EOPNOTSUPP; + initial = kzalloc_obj(*initial); if (!initial) return -ENOMEM; @@ -430,14 +486,21 @@ static int stmmac_test_eee(struct stmmac_priv *priv) goto out_free_initial; } + /* Snapshot stats, we want to count the in_lpi events. We may enter + * LPI just after the packet was sent. + */ memcpy(initial, &priv->xstats, sizeof(*initial)); + /* Send a frame, then wait to enter LPI */ ret = stmmac_test_mac_loopback(priv); if (ret) goto out_free_final; + max_duration = usecs_to_jiffies(2 * priv->tx_lpi_timer); + /* We have no traffic in the line so, sooner or later it will go LPI */ - while (--retries) { + timeout = jiffies + max_duration; + while (!time_after(jiffies, timeout)) { memcpy(final, &priv->xstats, sizeof(*final)); if (final->irq_tx_path_in_lpi_mode_n > @@ -446,20 +509,38 @@ static int stmmac_test_eee(struct stmmac_priv *priv) msleep(100); } - if (!retries) { + memcpy(final, &priv->xstats, sizeof(*final)); + if (final->irq_tx_path_in_lpi_mode_n <= + initial->irq_tx_path_in_lpi_mode_n) { ret = -ETIMEDOUT; goto out_free_final; } - if (final->irq_tx_path_in_lpi_mode_n <= - initial->irq_tx_path_in_lpi_mode_n) { - ret = -EINVAL; + /* Re-snapshot, as we want to measure exit_lpi events. We should be + * in LPI right now. + */ + memcpy(initial, &priv->xstats, sizeof(*initial)); + + /* TX something so we go out of LPI */ + ret = stmmac_test_mac_loopback(priv); + if (ret) goto out_free_final; + + /* Wait for the exit LPI interrupt */ + timeout = jiffies + max_duration; + while (!time_after(jiffies, timeout)) { + memcpy(final, &priv->xstats, sizeof(*final)); + + if (final->irq_tx_path_exit_lpi_mode_n > + initial->irq_tx_path_exit_lpi_mode_n) + break; + msleep(100); } + memcpy(final, &priv->xstats, sizeof(*final)); if (final->irq_tx_path_exit_lpi_mode_n <= initial->irq_tx_path_exit_lpi_mode_n) { - ret = -EINVAL; + ret = -ETIMEDOUT; goto out_free_final; } @@ -767,7 +848,7 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv) tpriv->pt.func = stmmac_test_flowctrl_validate; tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; - dev_add_pack(&tpriv->pt); + stmmac_sft_add_pack(&tpriv->pt); /* Compute minimum number of packets to make FIFO full */ pkt_count = rx_fifo_size; @@ -823,7 +904,7 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv) cleanup: dev_mc_del(priv->dev, paddr); dev_set_promiscuity(priv->dev, -1); - dev_remove_pack(&tpriv->pt); + stmmac_sft_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -865,6 +946,11 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, goto out; if (skb_headlen(skb) < (STMMAC_TEST_PKT_SIZE - ETH_HLEN)) goto out; + + ehdr = (struct ethhdr *)skb_mac_header(skb); + if (!ether_addr_equal_unaligned(ehdr->h_dest, tpriv->packet->dst)) + goto out; + if (tpriv->vlan_id) { if (skb->vlan_proto != htons(proto)) goto out; @@ -876,10 +962,6 @@ static int stmmac_test_vlan_validate(struct sk_buff *skb, } } - ehdr = (struct ethhdr *)skb_mac_header(skb); - if (!ether_addr_equal_unaligned(ehdr->h_dest, tpriv->packet->dst)) - goto out; - ihdr = ip_hdr(skb); if (tpriv->double_vlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); @@ -921,6 +1003,7 @@ static int __stmmac_test_vlanfilt(struct stmmac_priv *priv) tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; + tpriv->capture_all = true; /* * As we use HASH filtering, false positives may appear. This is a @@ -928,18 +1011,20 @@ static int __stmmac_test_vlanfilt(struct stmmac_priv *priv) * HASH values. */ tpriv->vlan_id = 0x123; - dev_add_pack(&tpriv->pt); ret = vlan_vid_add(priv->dev, htons(ETH_P_8021Q), tpriv->vlan_id); if (ret) goto cleanup; + attr.vlan = 1; + attr.dst = priv->dev->dev_addr; + attr.sport = 9; + attr.dport = 9; + + stmmac_sft_add_pack(&tpriv->pt); + for (i = 0; i < 4; i++) { - attr.vlan = 1; attr.vlan_id_out = tpriv->vlan_id + i; - attr.dst = priv->dev->dev_addr; - attr.sport = 9; - attr.dport = 9; skb = stmmac_test_get_udp_skb(priv, &attr); if (!skb) { @@ -966,9 +1051,9 @@ static int __stmmac_test_vlanfilt(struct stmmac_priv *priv) } vlan_del: + stmmac_sft_remove_pack(&tpriv->pt); vlan_vid_del(priv->dev, htons(ETH_P_8021Q), tpriv->vlan_id); cleanup: - dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -1015,6 +1100,7 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; + tpriv->capture_all = true; /* * As we use HASH filtering, false positives may appear. This is a @@ -1022,18 +1108,20 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) * HASH values. */ tpriv->vlan_id = 0x123; - dev_add_pack(&tpriv->pt); ret = vlan_vid_add(priv->dev, htons(ETH_P_8021AD), tpriv->vlan_id); if (ret) goto cleanup; + attr.vlan = 2; + attr.dst = priv->dev->dev_addr; + attr.sport = 9; + attr.dport = 9; + + stmmac_sft_add_pack(&tpriv->pt); + for (i = 0; i < 4; i++) { - attr.vlan = 2; attr.vlan_id_out = tpriv->vlan_id + i; - attr.dst = priv->dev->dev_addr; - attr.sport = 9; - attr.dport = 9; skb = stmmac_test_get_udp_skb(priv, &attr); if (!skb) { @@ -1060,9 +1148,9 @@ static int __stmmac_test_dvlanfilt(struct stmmac_priv *priv) } vlan_del: + stmmac_sft_remove_pack(&tpriv->pt); vlan_vid_del(priv->dev, htons(ETH_P_8021AD), tpriv->vlan_id); cleanup: - dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -1293,7 +1381,7 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) tpriv->pt.af_packet_priv = tpriv; tpriv->packet = &attr; tpriv->vlan_id = 0x123; - dev_add_pack(&tpriv->pt); + tpriv->capture_all = true; ret = vlan_vid_add(priv->dev, htons(proto), tpriv->vlan_id); if (ret) @@ -1301,6 +1389,8 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) attr.dst = priv->dev->dev_addr; + stmmac_sft_add_pack(&tpriv->pt); + skb = stmmac_test_get_udp_skb(priv, &attr); if (!skb) { ret = -ENOMEM; @@ -1318,9 +1408,9 @@ static int stmmac_test_vlanoff_common(struct stmmac_priv *priv, bool svlan) ret = tpriv->ok ? 0 : -ETIMEDOUT; vlan_del: + stmmac_sft_remove_pack(&tpriv->pt); vlan_vid_del(priv->dev, htons(proto), tpriv->vlan_id); cleanup: - dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } @@ -1332,7 +1422,7 @@ static int stmmac_test_vlanoff(struct stmmac_priv *priv) static int stmmac_test_svlanoff(struct stmmac_priv *priv) { - if (!priv->dma_cap.dvlan) + if (!(priv->dev->features & NETIF_F_HW_VLAN_STAG_TX)) return -EOPNOTSUPP; return stmmac_test_vlanoff_common(priv, true); } @@ -1699,6 +1789,9 @@ static int __stmmac_test_jumbo(struct stmmac_priv *priv, u16 queue) struct stmmac_packet_attrs attr = { }; int size = priv->dma_conf.dma_buf_sz; + if (!dwmac_is_xmac(priv->plat->core_type)) + size -= NET_IP_ALIGN; + attr.dst = priv->dev->dev_addr; attr.max_size = size - ETH_FCS_LEN; attr.queue_mapping = queue; diff --git a/drivers/net/ethernet/ti/netcp_core.c b/drivers/net/ethernet/ti/netcp_core.c index eb8fc2ed05f4..4f9e20468bbb 100644 --- a/drivers/net/ethernet/ti/netcp_core.c +++ b/drivers/net/ethernet/ti/netcp_core.c @@ -2225,7 +2225,7 @@ static int netcp_probe(struct platform_device *pdev) return -ENOMEM; pm_runtime_enable(&pdev->dev); - ret = pm_runtime_get_sync(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); if (ret < 0) { dev_err(dev, "Failed to enable NETCP power-domain\n"); pm_runtime_disable(&pdev->dev); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c index 122c4952d203..113552586be7 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c @@ -2518,6 +2518,7 @@ int wx_sw_init(struct wx *wx) } spin_lock_init(&wx->hw_stats_lock); + spin_lock_init(&wx->ptp_tx_lock); mutex_init(&wx->reset_lock); bitmap_zero(wx->state, WX_STATE_NBITS); bitmap_zero(wx->flags, WX_PF_FLAGS_NBITS); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_lib.c b/drivers/net/ethernet/wangxun/libwx/wx_lib.c index ed5aad7857bd..dcbf5811046e 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_lib.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_lib.c @@ -1200,9 +1200,11 @@ dma_error: i--; } - dev_kfree_skb_any(first->skb); - first->skb = NULL; - + /* first->skb is released by the caller, which keeps a reference on it + * until the PTP cleanup has compared it against wx->ptp_tx_skb. That + * prevents the address from being reused by a newer request while the + * comparison is pending. + */ tx_ring->next_to_use = i; return -ENOMEM; @@ -1649,9 +1651,11 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && wx->ptp_clock) { + unsigned long flags; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); if (wx->tstamp_config.tx_type == HWTSTAMP_TX_ON && - !test_and_set_bit_lock(WX_STATE_PTP_TX_IN_PROGRESS, - wx->state)) { + !test_and_set_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state)) { skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; tx_flags |= WX_TX_FLAGS_TSTAMP; wx->ptp_tx_skb = skb_get(skb); @@ -1659,6 +1663,7 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, } else { wx->tx_hwtstamp_skipped++; } + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); } /* record initial flags and protocol */ @@ -1677,19 +1682,35 @@ static netdev_tx_t wx_xmit_frame_ring(struct sk_buff *skb, wx->atr(tx_ring, first, ptype); if (wx_tx_map(tx_ring, first, hdr_len)) - goto cleanup_tx_tstamp; + goto out_drop; return NETDEV_TX_OK; out_drop: - dev_kfree_skb_any(first->skb); - first->skb = NULL; -cleanup_tx_tstamp: + /* The frame never reached the hardware, so no timestamp will ever be + * reported for it and the request has to be cancelled. The slot is + * shared, though: wx_ptp_clear_tx_timestamp() or wx_ptp_tx_hang() may + * have dropped our request already, and a transmit on another queue + * can have claimed the slot since. Only cancel it while it is still + * ours, otherwise we would free somebody else's skb and release their + * in-progress bit. + */ if (unlikely(tx_flags & WX_TX_FLAGS_TSTAMP)) { - dev_kfree_skb_any(wx->ptp_tx_skb); - wx->ptp_tx_skb = NULL; - wx->tx_hwtstamp_errors++; - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + struct sk_buff *ptp_tx_skb = NULL; + unsigned long flags; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); + if (wx->ptp_tx_skb == skb) { + ptp_tx_skb = wx->ptp_tx_skb; + wx->ptp_tx_skb = NULL; + clear_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + wx->tx_hwtstamp_errors++; + } + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + dev_kfree_skb_any(ptp_tx_skb); } + dev_kfree_skb_any(first->skb); + first->skb = NULL; return NETDEV_TX_OK; } diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c index 4708e7f3958f..65b8937f6e94 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c @@ -130,6 +130,34 @@ static int wx_ptp_settime64(struct ptp_clock_info *ptp, } /** + * __wx_ptp_detach_tx_skb - detach the skb tracking the Tx timestamp request + * @wx: the private board structure + * + * Detach the skb of the outstanding request and release the in-progress bit, + * so that a new request can be submitted. + * + * This performs no register access. Callers that need a timestamp the hardware + * may have left latched must unlatch it themselves, while the device is known + * to be alive. wx_ptp_quiesce() runs during PCIe error recovery, where MMIO is + * not reliable, and therefore deliberately skips the unlatch. + * + * Context: Expects wx->ptp_tx_lock to be held by the caller. + * Return: the detached skb, or NULL if no request was outstanding. The caller + * owns the returned reference and must release it once the lock is dropped. + */ +static struct sk_buff *__wx_ptp_detach_tx_skb(struct wx *wx) +{ + struct sk_buff *skb = wx->ptp_tx_skb; + + lockdep_assert_held(&wx->ptp_tx_lock); + + wx->ptp_tx_skb = NULL; + clear_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + + return skb; +} + +/** * wx_ptp_clear_tx_timestamp - utility function to clear Tx timestamp state * @wx: the private board structure * @@ -139,12 +167,16 @@ static int wx_ptp_settime64(struct ptp_clock_info *ptp, */ static void wx_ptp_clear_tx_timestamp(struct wx *wx) { + struct sk_buff *skb; + unsigned long flags; + + spin_lock_irqsave(&wx->ptp_tx_lock, flags); + /* Unlatch a timestamp the hardware may have left pending. */ rd32ptp(wx, WX_TSC_1588_STMPH); - if (wx->ptp_tx_skb) { - dev_kfree_skb_any(wx->ptp_tx_skb); - wx->ptp_tx_skb = NULL; - } - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + skb = __wx_ptp_detach_tx_skb(wx); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + dev_kfree_skb_any(skb); } /** @@ -175,49 +207,54 @@ static void wx_ptp_convert_to_hwtstamp(struct wx *wx, } /** - * wx_ptp_tx_hwtstamp - utility function which checks for TX time stamp + * wx_ptp_tx_hwtstamp_work - check for a pending Tx time stamp * @wx: the private board struct * - * if the timestamp is valid, we convert it into the timecounter ns - * value, then store that result into the shhwtstamps structure which - * is passed up the network stack + * If a Tx timestamp request is outstanding and the hardware has latched a + * valid value, we convert it into the timecounter ns value, then store that + * result into the shhwtstamps structure which is passed up the network stack. + * + * Return: 0 when there is nothing left to poll for, -1 when the timestamp is + * not available yet and the caller should poll again. */ -static void wx_ptp_tx_hwtstamp(struct wx *wx) +static int wx_ptp_tx_hwtstamp_work(struct wx *wx) { struct skb_shared_hwtstamps shhwtstamps; - struct sk_buff *skb = wx->ptp_tx_skb; + unsigned long flags; + struct sk_buff *skb; + u32 tsynctxctl; u64 regval = 0; - regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL); - regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32; - - wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval); - - wx->ptp_tx_skb = NULL; - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); - skb_tstamp_tx(skb, &shhwtstamps); - dev_kfree_skb_any(skb); - wx->tx_hwtstamp_pkts++; -} - -static int wx_ptp_tx_hwtstamp_work(struct wx *wx) -{ - u32 tsynctxctl; + spin_lock_irqsave(&wx->ptp_tx_lock, flags); /* we have to have a valid skb to poll for a timestamp */ if (!wx->ptp_tx_skb) { - wx_ptp_clear_tx_timestamp(wx); + rd32ptp(wx, WX_TSC_1588_STMPH); + __wx_ptp_detach_tx_skb(wx); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); return 0; } /* stop polling once we have a valid timestamp */ tsynctxctl = rd32ptp(wx, WX_TSC_1588_CTL); - if (tsynctxctl & WX_TSC_1588_CTL_VALID) { - wx_ptp_tx_hwtstamp(wx); - return 0; + if (!(tsynctxctl & WX_TSC_1588_CTL_VALID)) { + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + return -1; } - return -1; + regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPL); + regval |= (u64)rd32ptp(wx, WX_TSC_1588_STMPH) << 32; + skb = wx->ptp_tx_skb; + wx->ptp_tx_skb = NULL; + clear_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + wx_ptp_convert_to_hwtstamp(wx, &shhwtstamps, regval); + skb_tstamp_tx(skb, &shhwtstamps); + dev_kfree_skb_any(skb); + wx->tx_hwtstamp_pkts++; + + return 0; } /** @@ -296,24 +333,29 @@ static void wx_ptp_rx_hang(struct wx *wx) */ static void wx_ptp_tx_hang(struct wx *wx) { - bool timeout = time_is_before_jiffies(wx->ptp_tx_start + - WX_PTP_TX_TIMEOUT); - - if (!wx->ptp_tx_skb) - return; + struct sk_buff *skb = NULL; + unsigned long flags; - if (!test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state)) - return; + spin_lock_irqsave(&wx->ptp_tx_lock, flags); /* If we haven't received a timestamp within the timeout, it is * reasonable to assume that it will never occur, so we can unlock the * timestamp bit when this occurs. */ - if (timeout) { - wx_ptp_clear_tx_timestamp(wx); - wx->tx_hwtstamp_timeouts++; - dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n"); + if (wx->ptp_tx_skb && + test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state) && + time_is_before_jiffies(wx->ptp_tx_start + WX_PTP_TX_TIMEOUT)) { + rd32ptp(wx, WX_TSC_1588_STMPH); + skb = __wx_ptp_detach_tx_skb(wx); } + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + + if (!skb) + return; + + dev_kfree_skb_any(skb); + wx->tx_hwtstamp_timeouts++; + dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n"); } static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp) @@ -841,6 +883,9 @@ EXPORT_SYMBOL(wx_ptp_stop); void wx_ptp_quiesce(struct wx *wx) { + struct sk_buff *skb; + unsigned long flags; + if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state)) return; @@ -849,11 +894,14 @@ void wx_ptp_quiesce(struct wx *wx) if (wx->ptp_clock) ptp_cancel_worker_sync(wx->ptp_clock); - if (wx->ptp_tx_skb) { - dev_kfree_skb_any(wx->ptp_tx_skb); - wx->ptp_tx_skb = NULL; - } - clear_bit_unlock(WX_STATE_PTP_TX_IN_PROGRESS, wx->state); + /* Drop a pending Tx timestamp request. Do not touch the registers + * here: quiesce runs during PCIe error recovery, where the device may + * already be gone and MMIO is not reliable. + */ + spin_lock_irqsave(&wx->ptp_tx_lock, flags); + skb = __wx_ptp_detach_tx_skb(wx); + spin_unlock_irqrestore(&wx->ptp_tx_lock, flags); + dev_kfree_skb_any(skb); if (wx->ptp_clock) { ptp_clock_unregister(wx->ptp_clock); diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h index 9454e90258d8..afd980dbb793 100644 --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h @@ -1430,6 +1430,8 @@ struct wx { unsigned long last_overflow_check; unsigned long last_rx_ptp_check; unsigned long ptp_tx_start; + /* protects ptp_tx_skb, ptp_tx_start and the in-progress state bit */ + spinlock_t ptp_tx_lock; seqlock_t hw_tc_lock; /* seqlock for ptp */ struct cyclecounter hw_cc; struct timecounter hw_tc; diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c index a84010828551..59a47532618c 100644 --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c @@ -591,15 +591,24 @@ static void txgbe_fdir_filter_restore(struct wx *wx) queue = TXGBE_RDB_FDIR_DROP_QUEUE; } else { u32 ring = ethtool_get_flow_spec_ring(filter->action); + u8 vf = ethtool_get_flow_spec_ring_vf(filter->action); - if (ring >= wx->num_rx_queues) { + if (!vf && ring >= wx->num_rx_queues) { wx_err(wx, "FDIR restore failed, ring:%u\n", ring); continue; + } else if (vf && (vf > wx->num_vfs || + ring >= wx->num_rx_queues_per_pool)) { + wx_err(wx, "FDIR restore failed, vf:%u, ring:%u\n", + vf, ring); + continue; } /* Map the ring onto the absolute queue index */ - queue = wx->rx_ring[ring]->reg_idx; + if (!vf) + queue = wx->rx_ring[ring]->reg_idx; + else + queue = ((vf - 1) * wx->num_rx_queues_per_pool) + ring; } ret = txgbe_fdir_write_perfect_filter(wx, diff --git a/drivers/net/macsec.c b/drivers/net/macsec.c index 6f9f3aceffaa..78a19b134632 100644 --- a/drivers/net/macsec.c +++ b/drivers/net/macsec.c @@ -3539,6 +3539,22 @@ static int macsec_dev_init(struct net_device *dev) if (err) return err; + err = -ENOMEM; + macsec->stats = netdev_alloc_pcpu_stats(struct pcpu_secy_stats); + if (!macsec->stats) + goto destroy_gro_cells; + + macsec->secy.tx_sc.stats = + netdev_alloc_pcpu_stats(struct pcpu_tx_sc_stats); + if (!macsec->secy.tx_sc.stats) + goto free_secy_stats; + + macsec->secy.tx_sc.md_dst = metadata_dst_alloc(0, METADATA_MACSEC, + GFP_KERNEL); + if (!macsec->secy.tx_sc.md_dst) + goto free_tx_sc_stats; + macsec->secy.tx_sc.md_dst->u.macsec_info.sci = macsec->secy.sci; + macsec_inherit_tso_max(dev); dev->hw_features = real_dev->hw_features & MACSEC_OFFLOAD_FEATURES; @@ -3551,8 +3567,6 @@ static int macsec_dev_init(struct net_device *dev) macsec_set_head_tail_room(dev); - if (is_zero_ether_addr(dev->dev_addr)) - eth_hw_addr_inherit(dev, real_dev); if (is_zero_ether_addr(dev->broadcast)) memcpy(dev->broadcast, real_dev->broadcast, dev->addr_len); @@ -3560,6 +3574,14 @@ static int macsec_dev_init(struct net_device *dev) netdev_hold(real_dev, &macsec->dev_tracker, GFP_KERNEL); return 0; + +free_tx_sc_stats: + free_percpu(macsec->secy.tx_sc.stats); +free_secy_stats: + free_percpu(macsec->stats); +destroy_gro_cells: + gro_cells_destroy(&macsec->gro_cells); + return err; } static void macsec_dev_uninit(struct net_device *dev) @@ -4116,26 +4138,11 @@ static sci_t dev_to_sci(struct net_device *dev, __be16 port) return make_sci(dev->dev_addr, port); } -static int macsec_add_dev(struct net_device *dev, sci_t sci, u8 icv_len) +static void macsec_init_secy(struct net_device *dev, sci_t sci, u8 icv_len) { struct macsec_dev *macsec = macsec_priv(dev); struct macsec_secy *secy = &macsec->secy; - macsec->stats = netdev_alloc_pcpu_stats(struct pcpu_secy_stats); - if (!macsec->stats) - return -ENOMEM; - - secy->tx_sc.stats = netdev_alloc_pcpu_stats(struct pcpu_tx_sc_stats); - if (!secy->tx_sc.stats) - return -ENOMEM; - - secy->tx_sc.md_dst = metadata_dst_alloc(0, METADATA_MACSEC, GFP_KERNEL); - if (!secy->tx_sc.md_dst) - /* macsec and secy percpu stats will be freed when unregistering - * net_device in macsec_free_netdev() - */ - return -ENOMEM; - if (sci == MACSEC_UNDEF_SCI) sci = dev_to_sci(dev, MACSEC_PORT_ES); @@ -4149,15 +4156,12 @@ static int macsec_add_dev(struct net_device *dev, sci_t sci, u8 icv_len) secy->xpn = DEFAULT_XPN; secy->sci = sci; - secy->tx_sc.md_dst->u.macsec_info.sci = sci; secy->tx_sc.active = true; secy->tx_sc.encoding_sa = DEFAULT_ENCODING_SA; secy->tx_sc.encrypt = DEFAULT_ENCRYPT; secy->tx_sc.send_sci = DEFAULT_SEND_SCI; secy->tx_sc.end_station = false; secy->tx_sc.scb = false; - - return 0; } static struct lock_class_key macsec_netdev_addr_lock_key; @@ -4220,6 +4224,24 @@ static int macsec_newlink(struct net_device *dev, if (rx_handler && rx_handler != macsec_handle_frame) return -EBUSY; + if (is_zero_ether_addr(dev->dev_addr)) + eth_hw_addr_inherit(dev, real_dev); + + if (data && data[IFLA_MACSEC_SCI]) + sci = nla_get_sci(data[IFLA_MACSEC_SCI]); + else if (data && data[IFLA_MACSEC_PORT]) + sci = dev_to_sci(dev, nla_get_be16(data[IFLA_MACSEC_PORT])); + else + sci = dev_to_sci(dev, MACSEC_PORT_ES); + + /* Registration can notify listeners before returning. */ + macsec_init_secy(dev, sci, icv_len); + if (data) { + err = macsec_changelink_common(dev, data); + if (err) + return err; + } + err = register_netdevice(dev); if (err < 0) return err; @@ -4232,31 +4254,11 @@ static int macsec_newlink(struct net_device *dev, if (err < 0) goto unregister; - /* need to be already registered so that ->init has run and - * the MAC addr is set - */ - if (data && data[IFLA_MACSEC_SCI]) - sci = nla_get_sci(data[IFLA_MACSEC_SCI]); - else if (data && data[IFLA_MACSEC_PORT]) - sci = dev_to_sci(dev, nla_get_be16(data[IFLA_MACSEC_PORT])); - else - sci = dev_to_sci(dev, MACSEC_PORT_ES); - if (rx_handler && sci_exists(real_dev, sci)) { err = -EBUSY; goto unlink; } - err = macsec_add_dev(dev, sci, icv_len); - if (err) - goto unlink; - - if (data) { - err = macsec_changelink_common(dev, data); - if (err) - goto del_dev; - } - /* If h/w offloading is available, propagate to the device */ if (macsec_is_offloaded(macsec)) { const struct macsec_ops *ops; diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c index afd52a1cd7f8..9ce2b7807532 100644 --- a/drivers/net/mdio/mdio-realtek-rtl9300.c +++ b/drivers/net/mdio/mdio-realtek-rtl9300.c @@ -88,6 +88,8 @@ #define RTL9310_SMI_INDRT_ACCESS_BC_PHYID_CTRL 0x0c14 #define RTL9310_BC_PORT_ID GENMASK(10, 5) #define RTL9310_SMI_INDRT_ACCESS_CTRL_1 0x0c04 +#define RTL9310_SMI_INDRT_EXT_PAGE GENMASK(8, 0) +#define RTL9310_SMI_INDRT_EXT_PAGE_NO_CHANGE 0x1ff #define RTL9310_SMI_INDRT_ACCESS_CTRL_2_LOW 0x0c08 #define RTL9310_SMI_INDRT_ACCESS_CTRL_2_HIGH 0x0c0c #define RTL9310_SMI_INDRT_ACCESS_CTRL_3 0x0c10 /* I/O fields flipped */ @@ -325,6 +327,8 @@ static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u .broadcast = FIELD_PREP(RTL9310_BC_PORT_ID, port), .c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) | FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + .ext_page = FIELD_PREP(RTL9310_SMI_INDRT_EXT_PAGE, + RTL9310_SMI_INDRT_EXT_PAGE_NO_CHANGE), }; return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data, @@ -337,6 +341,8 @@ static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum, struct otto_emdio_cmd_regs cmd_data = { .c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) | FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)), + .ext_page = FIELD_PREP(RTL9310_SMI_INDRT_EXT_PAGE, + RTL9310_SMI_INDRT_EXT_PAGE_NO_CHANGE), .io_data = FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value), .port_mask_high = (u32)(BIT_ULL(port) >> 32), .port_mask_low = (u32)(BIT_ULL(port)), diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 4dad85294198..5432bc2eb8e8 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -100,6 +100,8 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, struct sockaddr_in6 *sin6; struct sockaddr_in *sin; struct in6_addr *in6; + struct nlattr *scope; + u32 scope_id = 0; __be16 port = 0; __be32 *in; @@ -114,6 +116,9 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, } else if (attrs[OVPN_A_PEER_REMOTE_IPV6]) { ss->ss_family = AF_INET6; in6 = nla_data(attrs[OVPN_A_PEER_REMOTE_IPV6]); + scope = attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]; + if (scope) + scope_id = nla_get_u32(scope); } else { return false; } @@ -126,6 +131,7 @@ static bool ovpn_nl_attr_sockaddr_remote(struct nlattr **attrs, if (!ipv6_addr_v4mapped(in6)) { sin6 = (struct sockaddr_in6 *)ss; sin6->sin6_port = port; + sin6->sin6_scope_id = scope_id; memcpy(&sin6->sin6_addr, in6, sizeof(*in6)); break; } @@ -179,6 +185,39 @@ static sa_family_t ovpn_nl_family_get(struct nlattr *addr4, return AF_UNSPEC; } +static int ovpn_nl_peer_check_vpn_addrs(const struct in_addr *addr4, + const struct in6_addr *addr6, + struct genl_info *info) +{ + int addr6_type; + + if (addr4->s_addr == htonl(INADDR_ANY) && ipv6_addr_any(addr6)) { + NL_SET_ERR_MSG_MOD(info->extack, + "at least one VPN IP must be configured in MP mode"); + return -EINVAL; + } + + if (ipv4_is_multicast(addr4->s_addr) || ipv4_is_lbcast(addr4->s_addr) || + ipv4_is_loopback(addr4->s_addr)) { + NL_SET_ERR_MSG_MOD(info->extack, + "VPN IPv4 address must be valid unicast or any"); + return -EADDRNOTAVAIL; + } + + if (!ipv6_addr_any(addr6)) { + addr6_type = ipv6_addr_type(addr6); + + if (!(addr6_type & IPV6_ADDR_UNICAST) || + (addr6_type & (IPV6_ADDR_LOOPBACK | IPV6_ADDR_COMPATv4))) { + NL_SET_ERR_MSG_MOD(info->extack, + "VPN IPv6 address must be valid unicast or any"); + return -EADDRNOTAVAIL; + } + } + + return 0; +} + static int ovpn_nl_peer_precheck(struct ovpn_priv *ovpn, struct genl_info *info, struct nlattr **attrs) @@ -346,8 +385,10 @@ err_unlock: int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct in_addr vpn_addr4 = { .s_addr = htonl(INADDR_ANY) }; + struct in6_addr vpn_addr6 = IN6ADDR_ANY_INIT; struct ovpn_priv *ovpn = info->user_ptr[0]; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; struct ovpn_socket *ovpn_sock; struct socket *sock = NULL; struct ovpn_peer *peer; @@ -371,11 +412,18 @@ int ovpn_nl_peer_new_doit(struct sk_buff *skb, struct genl_info *info) return -EINVAL; /* in MP mode VPN IPs are required for selecting the right peer */ - if (ovpn->mode == OVPN_MODE_MP && !attrs[OVPN_A_PEER_VPN_IPV4] && - !attrs[OVPN_A_PEER_VPN_IPV6]) { - NL_SET_ERR_MSG_FMT_MOD(info->extack, - "VPN IP must be provided in MP mode"); - return -EINVAL; + if (ovpn->mode == OVPN_MODE_MP) { + if (attrs[OVPN_A_PEER_VPN_IPV4]) + vpn_addr4.s_addr = + nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); + if (attrs[OVPN_A_PEER_VPN_IPV6]) + vpn_addr6 = + nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); + + ret = ovpn_nl_peer_check_vpn_addrs(&vpn_addr4, &vpn_addr6, + info); + if (ret < 0) + return ret; } peer_id = nla_get_u32(attrs[OVPN_A_PEER_ID]); @@ -474,8 +522,10 @@ peer_release: int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; struct ovpn_priv *ovpn = info->user_ptr[0]; + struct nlattr *attrs[OVPN_A_PEER_MAX + 1]; + struct in6_addr vpn_addr6; + struct in_addr vpn_addr4; struct ovpn_socket *sock; struct ovpn_peer *peer; u32 peer_id; @@ -522,28 +572,58 @@ int ovpn_nl_peer_set_doit(struct sk_buff *skb, struct genl_info *info) rcu_read_unlock(); spin_lock_bh(&ovpn->lock); - ret = ovpn_nl_peer_modify(peer, info, attrs); - if (ret < 0) { - spin_unlock_bh(&ovpn->lock); - ovpn_peer_put(peer); - return ret; + + vpn_addr4 = peer->vpn_addrs.ipv4; + vpn_addr6 = peer->vpn_addrs.ipv6; + + /* reject peer with conflicting VPN address */ + if (attrs[OVPN_A_PEER_VPN_IPV4]) { + vpn_addr4.s_addr = nla_get_in_addr(attrs[OVPN_A_PEER_VPN_IPV4]); + if (ovpn_peer_vpn_addr_conflict4(ovpn, peer, &vpn_addr4)) + goto addr_conflict; + } + if (attrs[OVPN_A_PEER_VPN_IPV6]) { + vpn_addr6 = nla_get_in6_addr(attrs[OVPN_A_PEER_VPN_IPV6]); + if (ovpn_peer_vpn_addr_conflict6(ovpn, peer, &vpn_addr6)) + goto addr_conflict; } + /* in MP mode VPN IPs are required for selecting the right peer */ + if (ovpn->mode == OVPN_MODE_MP) { + ret = ovpn_nl_peer_check_vpn_addrs(&vpn_addr4, &vpn_addr6, + info); + if (ret < 0) + goto unlock; + } + + ret = ovpn_nl_peer_modify(peer, info, attrs); + if (ret < 0) + goto unlock; + /* ret == 1 means that VPN IPv4/6 has been modified and rehashing * is required */ - if (ret > 0) + if (ret > 0) { ovpn_peer_hash_vpn_ip(peer); + ret = 0; + } /* if the remote endpoint was updated, the by_transp_addr hash bucket * also needs to be refreshed, otherwise incoming packets from the new * remote address would fail the lockless lookup */ if (attrs[OVPN_A_PEER_REMOTE_IPV4] || attrs[OVPN_A_PEER_REMOTE_IPV6]) ovpn_peer_hash_transp_addr(peer); + +unlock: spin_unlock_bh(&ovpn->lock); ovpn_peer_put(peer); - return 0; + return ret; +addr_conflict: + NL_SET_ERR_MSG_FMT_MOD(info->extack, + "VPN IP is already assigned to another peer"); + ret = -EADDRINUSE; + goto unlock; } static int ovpn_nl_send_peer(struct sk_buff *skb, const struct genl_info *info, diff --git a/drivers/net/ovpn/peer.c b/drivers/net/ovpn/peer.c index c95656ca7c35..2067825bb5b6 100644 --- a/drivers/net/ovpn/peer.c +++ b/drivers/net/ovpn/peer.c @@ -113,6 +113,7 @@ struct ovpn_peer *ovpn_peer_new(struct ovpn_priv *ovpn, u32 id) RCU_INIT_POINTER(peer->bind, NULL); ovpn_crypto_state_init(&peer->crypto); spin_lock_init(&peer->lock); + seqcount_spinlock_init(&peer->route_key_seq, &peer->lock); kref_init(&peer->refcount); ovpn_peer_stats_init(&peer->vpn_stats); ovpn_peer_stats_init(&peer->link_stats); @@ -199,13 +200,12 @@ static void __ovpn_peer_hash_transp_addr(struct ovpn_peer *peer, */ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) { + const void *local_ip = NULL; struct sockaddr_storage ss; struct sockaddr_in6 *sa6; - bool reset_cache = false; struct sockaddr_in *sa; struct ovpn_bind *bind; - const void *local_ip; - size_t salen = 0; + bool floated = false; spin_lock_bh(&peer->lock); bind = rcu_dereference_protected(peer->bind, @@ -232,8 +232,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) .sin_addr.s_addr = ip_hdr(skb)->saddr, .sin_port = udp_hdr(skb)->source, }; - salen = sizeof(*sa); - reset_cache = true; + floated = true; break; } @@ -245,10 +244,12 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) netdev_name(peer->ovpn->dev), peer->id, &bind->local.ipv4.s_addr, &ip_hdr(skb)->daddr); - bind->local.ipv4.s_addr = ip_hdr(skb)->daddr; - reset_cache = true; + local_ip = &ip_hdr(skb)->daddr; + memcpy(&ss, &bind->remote, sizeof(struct sockaddr_in)); + break; } - break; + /* nothing changed */ + goto unlock; case htons(ETH_P_IPV6): /* float check */ if (unlikely(!ovpn_bind_skb_src_match(bind, skb))) { @@ -270,8 +271,7 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) ipv6_iface_scope_id(&ipv6_hdr(skb)->saddr, skb->skb_iif), }; - salen = sizeof(*sa6); - reset_cache = true; + floated = true; break; } @@ -284,26 +284,30 @@ void ovpn_peer_endpoints_update(struct ovpn_peer *peer, struct sk_buff *skb) netdev_name(peer->ovpn->dev), peer->id, &bind->local.ipv6, &ipv6_hdr(skb)->daddr); - bind->local.ipv6 = ipv6_hdr(skb)->daddr; - reset_cache = true; + local_ip = &ipv6_hdr(skb)->daddr; + memcpy(&ss, &bind->remote, sizeof(struct sockaddr_in6)); + break; } - break; + /* nothing changed */ + goto unlock; default: goto unlock; } - if (unlikely(reset_cache)) - dst_cache_reset(&peer->dst_cache); - - /* if the peer did not float, we can bail out now */ - if (likely(!salen)) - goto unlock; - if (unlikely(ovpn_peer_reset_sockaddr(peer, (struct sockaddr_storage *)&ss, local_ip) < 0)) goto unlock; + /* reset the cache only after a successful bind update to avoid useless + * cache misses on concurrent TX + */ + dst_cache_reset(&peer->dst_cache); + + /* if only the local address changed, bail out now */ + if (!floated) + goto unlock; + net_dbg_ratelimited("%s: peer %d floated to %pIScp", netdev_name(peer->ovpn->dev), peer->id, &ss); @@ -484,7 +488,7 @@ begin: * Return: the peer if found or NULL otherwise */ static struct ovpn_peer *ovpn_peer_get_by_vpn_addr6(struct ovpn_priv *ovpn, - struct in6_addr *addr) + const struct in6_addr *addr) { struct hlist_nulls_head *nhead; struct hlist_nulls_node *ntmp; @@ -510,6 +514,64 @@ begin: } /** + * ovpn_peer_vpn_addr_conflict4 - check if the VPN v4 address is already in use + * @ovpn: the openvpn instance to search + * @peer: peer being added or updated, or NULL + * @addr: VPN IPv4 address to check + * + * Check whether @addr is already assigned to another peer. @peer is ignored + * when found, allowing peer updates that keep an existing address. + * Unspecified addresses are ignored. + * + * Note: the caller must hold @ovpn->lock. + * + * Return: true on conflict, false otherwise. + */ +bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in_addr *addr) +{ + struct ovpn_peer *tmp = NULL; + + lockdep_assert_held(&ovpn->lock); + + /* we don't hash INADDR_ANY, no conflict in that case */ + if (addr->s_addr != htonl(INADDR_ANY)) + tmp = ovpn_peer_get_by_vpn_addr4(ovpn, addr->s_addr); + + return tmp && tmp != peer; +} + +/** + * ovpn_peer_vpn_addr_conflict6 - check if the VPN v6 address is already in use + * @ovpn: the openvpn instance to search + * @peer: peer being added or updated, or NULL + * @addr: VPN IPv6 address to check + * + * Check whether @addr is already assigned to another peer. @peer is ignored + * when found, allowing peer updates that keep an existing address. + * Unspecified addresses are ignored. + * + * Note: the caller must hold @ovpn->lock. + * + * Return: true on conflict, false otherwise. + */ +bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in6_addr *addr) +{ + struct ovpn_peer *tmp = NULL; + + lockdep_assert_held(&ovpn->lock); + + /* we don't hash ::, no conflict in that case */ + if (!ipv6_addr_any(addr)) + tmp = ovpn_peer_get_by_vpn_addr6(ovpn, addr); + + return tmp && tmp != peer; +} + +/** * ovpn_peer_transp_match - check if sockaddr and peer binding match * @peer: the peer to get the binding from * @ss: the sockaddr to match @@ -990,10 +1052,11 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) if (hlist_unhashed(&peer->hash_entry_id)) return; - if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { - /* remove potential old hashing */ - hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); + /* remove potential old hashing */ + hlist_nulls_del_init_rcu(&peer->hash_entry_addr4); + hlist_nulls_del_init_rcu(&peer->hash_entry_addr6); + if (peer->vpn_addrs.ipv4.s_addr != htonl(INADDR_ANY)) { nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr4, &peer->vpn_addrs.ipv4, sizeof(peer->vpn_addrs.ipv4)); @@ -1001,9 +1064,6 @@ void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer) } if (!ipv6_addr_any(&peer->vpn_addrs.ipv6)) { - /* remove potential old hashing */ - hlist_nulls_del_init_rcu(&peer->hash_entry_addr6); - nhead = ovpn_get_hash_head(peer->ovpn->peers->by_vpn_addr6, &peer->vpn_addrs.ipv6, sizeof(peer->vpn_addrs.ipv6)); @@ -1038,6 +1098,13 @@ static int ovpn_peer_add_mp(struct ovpn_priv *ovpn, struct ovpn_peer *peer) goto out; } + /* reject peer with conflicting VPN address */ + if (ovpn_peer_vpn_addr_conflict4(ovpn, NULL, &peer->vpn_addrs.ipv4) || + ovpn_peer_vpn_addr_conflict6(ovpn, NULL, &peer->vpn_addrs.ipv6)) { + ret = -EADDRINUSE; + goto out; + } + bind = rcu_dereference_protected(peer->bind, true); /* peers connected via TCP have bind == NULL */ if (bind) { diff --git a/drivers/net/ovpn/peer.h b/drivers/net/ovpn/peer.h index dfa5c0037e02..1879bfb76992 100644 --- a/drivers/net/ovpn/peer.h +++ b/drivers/net/ovpn/peer.h @@ -10,6 +10,7 @@ #ifndef _NET_OVPN_OVPNPEER_H_ #define _NET_OVPN_OVPNPEER_H_ +#include <linux/seqlock.h> #include <net/dst_cache.h> #include <net/strparser.h> @@ -18,6 +19,16 @@ #include "stats.h" /** + * struct ovpn_route_key - route key used for the peer dst cache + * @mark: fwmark used for route lookup + * @sport: UDP source port used for route lookup + */ +struct ovpn_route_key { + u32 mark; + __be16 sport; +}; + +/** * struct ovpn_peer - the main remote peer object * @ovpn: main openvpn instance this peer belongs to * @dev_tracker: reference tracker for associated dev @@ -45,6 +56,8 @@ * @tcp.sk_cb.ops: pointer to the original prot_ops object (TCP only) * @crypto: the crypto configuration (ciphers, keys, etc..) * @dst_cache: cache for dst_entry used to send to peer + * @route_key: route key matching the current dst cache contents + * @route_key_seq: seqcount protecting lockless route_key reads * @bind: remote peer binding * @keepalive_interval: seconds after which a new keepalive should be sent * @keepalive_xmit_exp: future timestamp when next keepalive should be sent @@ -55,7 +68,7 @@ * @vpn_stats: per-peer in-VPN TX/RX stats * @link_stats: per-peer link/transport TX/RX stats * @delete_reason: why peer was deleted (i.e. timeout, transport error, ..) - * @lock: protects binding to peer (bind) and keepalive* fields + * @lock: protects binding to peer (bind), route_key and keepalive* fields * @refcount: reference counter * @rcu: used to free peer in an RCU safe way * @release_entry: entry for the socket release list @@ -99,6 +112,8 @@ struct ovpn_peer { } tcp; struct ovpn_crypto_state crypto; struct dst_cache dst_cache; + struct ovpn_route_key route_key; + seqcount_spinlock_t route_key_seq; struct ovpn_bind __rcu *bind; unsigned long keepalive_interval; unsigned long keepalive_xmit_exp; @@ -109,7 +124,7 @@ struct ovpn_peer { struct ovpn_peer_stats vpn_stats; struct ovpn_peer_stats link_stats; enum ovpn_del_peer_reason delete_reason; - spinlock_t lock; /* protects bind and keepalive* */ + spinlock_t lock; /* protects bind, route_key and keepalive* */ struct kref refcount; struct rcu_head rcu; struct llist_node release_entry; @@ -149,6 +164,12 @@ struct ovpn_peer *ovpn_peer_get_by_transp_addr(struct ovpn_priv *ovpn, struct ovpn_peer *ovpn_peer_get_by_id(struct ovpn_priv *ovpn, u32 peer_id); struct ovpn_peer *ovpn_peer_get_by_dst(struct ovpn_priv *ovpn, struct sk_buff *skb); +bool ovpn_peer_vpn_addr_conflict4(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in_addr *addr); +bool ovpn_peer_vpn_addr_conflict6(struct ovpn_priv *ovpn, + const struct ovpn_peer *peer, + const struct in6_addr *addr); void ovpn_peer_hash_vpn_ip(struct ovpn_peer *peer); void ovpn_peer_hash_transp_addr(struct ovpn_peer *peer); bool ovpn_peer_check_by_src(struct ovpn_priv *ovpn, struct sk_buff *skb, diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index 7f69e8890b5b..055cdb1bee13 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -131,6 +131,77 @@ drop_noovpn: return 0; } +static bool ovpn_route_key_equal(const struct ovpn_route_key *a, + const struct ovpn_route_key *b) +{ + return a->mark == b->mark && a->sport == b->sport; +} + +/** + * ovpn_dst_cache_check_key - reset peer dst cache after key changes + * @peer: the peer owning the dst cache + * @cache: the cache that might need to be reset + * @key: the route key for the packet being transmitted + * + * Reset the peer dst cache if it was populated for a different route key. + */ +static void ovpn_dst_cache_check_key(struct ovpn_peer *peer, + struct dst_cache *cache, + const struct ovpn_route_key *key) +{ + struct ovpn_route_key old_key; + unsigned int seq; + + /* snapshot the saved key before deciding whether the cache matches */ + do { + seq = read_seqcount_begin(&peer->route_key_seq); + old_key = peer->route_key; + } while (read_seqcount_retry(&peer->route_key_seq, seq)); + + /* nothing changed: the current cache can be reused */ + if (likely(ovpn_route_key_equal(&old_key, key))) + return; + + /* recheck under lock because another path may have updated the key */ + spin_lock_bh(&peer->lock); + if (!ovpn_route_key_equal(&peer->route_key, key)) { + write_seqcount_begin(&peer->route_key_seq); + peer->route_key = *key; + dst_cache_reset(cache); + write_seqcount_end(&peer->route_key_seq); + } + spin_unlock_bh(&peer->lock); +} + +/** + * ovpn_dst_cache_current - check whether a route lookup matches peer state + * @peer: the peer owning the bind and dst cache + * @bind: the RCU bind used for the route lookup + * @key: the route key used for the route lookup + * + * Check that @bind is still the current peer bind and that @key still matches + * the peer route key. The caller must hold @peer->lock. The TX path keeps + * @bind inside an RCU read-side critical section, so pointer identity is enough + * to detect whether the bind was replaced while the route lookup was running. + * + * Return: true if the lookup result still matches the current peer state and + * may update the dst cache or replace the bind. + */ +static bool ovpn_dst_cache_current(const struct ovpn_peer *peer, + const struct ovpn_bind *bind, + const struct ovpn_route_key *key) +{ + const struct ovpn_bind *curr_bind; + + lockdep_assert_held(&peer->lock); + + curr_bind = rcu_dereference_protected(peer->bind, + lockdep_is_held(&peer->lock)); + + return curr_bind == bind && + ovpn_route_key_equal(key, &peer->route_key); +} + /** * ovpn_udp4_output - send IPv4 packet over udp socket * @peer: the destination peer @@ -138,21 +209,26 @@ drop_noovpn: * @cache: dst cache * @sk: the socket to send the packet over * @skb: the packet to send + * @key: the route key snapshot used for cache validation and flow lookup * * Return: 0 on success or a negative error code otherwise */ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct dst_cache *cache, struct sock *sk, - struct sk_buff *skb) + struct sk_buff *skb, + const struct ovpn_route_key *key) { + struct sockaddr_storage remote; + struct in_addr local = {}; + bool reset_local = false; struct rtable *rt; struct flowi4 fl = { .saddr = bind->local.ipv4.s_addr, .daddr = bind->remote.in4.sin_addr.s_addr, - .fl4_sport = inet_sk(sk)->inet_sport, + .fl4_sport = key->sport, .fl4_dport = bind->remote.in4.sin_port, .flowi4_proto = sk->sk_protocol, - .flowi4_mark = sk->sk_mark, + .flowi4_mark = key->mark, }; int ret; @@ -161,26 +237,19 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (rt) goto transmit; - if (unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, fl.saddr, - RT_SCOPE_HOST))) { - /* we may end up here when the cached address is not usable - * anymore. In this case we reset address/cache and perform a - * new look up + if (fl.saddr && unlikely(!inet_confirm_addr(sock_net(sk), NULL, 0, + fl.saddr, RT_SCOPE_HOST))) { + /* The learned local address is not usable anymore. + * Retry with source address autoselection. */ fl.saddr = 0; - spin_lock_bh(&peer->lock); - bind->local.ipv4.s_addr = 0; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; } rt = ip_route_output_flow(sock_net(sk), &fl, sk); if (IS_ERR(rt) && PTR_ERR(rt) == -EINVAL) { fl.saddr = 0; - spin_lock_bh(&peer->lock); - bind->local.ipv4.s_addr = 0; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; rt = ip_route_output_flow(sock_net(sk), &fl, sk); } @@ -193,7 +262,30 @@ static int ovpn_udp4_output(struct ovpn_peer *peer, struct ovpn_bind *bind, ret); goto err; } - dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + + /* avoid storing a stale cache or local address */ + spin_lock_bh(&peer->lock); + if (likely(ovpn_dst_cache_current(peer, bind, key))) { + if (!reset_local) { + dst_cache_set_ip4(cache, &rt->dst, fl.saddr); + spin_unlock_bh(&peer->lock); + goto transmit; + } + + /* invalidate per-CPU dst entries that may still carry + * the stale source + */ + dst_cache_reset(cache); + + /* preserve the current remote */ + memcpy(&remote, &bind->remote, sizeof(struct sockaddr_in)); + /* The current packet already has a valid wildcard-source route. + * If replacing the bind fails, leave the stale local in place; + * a later cache miss will retry the repair. + */ + ovpn_peer_reset_sockaddr(peer, &remote, &local); + } + spin_unlock_bh(&peer->lock); transmit: udp_tunnel_xmit_skb(rt, sk, skb, fl.saddr, fl.daddr, 0, @@ -213,23 +305,28 @@ err: * @cache: dst cache * @sk: the socket to send the packet over * @skb: the packet to send + * @key: the route key snapshot used for cache validation and flow lookup * * Return: 0 on success or a negative error code otherwise */ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, struct dst_cache *cache, struct sock *sk, - struct sk_buff *skb) + struct sk_buff *skb, + const struct ovpn_route_key *key) { + struct in6_addr local = in6addr_any; + struct sockaddr_storage remote; + bool reset_local = false; struct dst_entry *dst; int ret; struct flowi6 fl = { .saddr = bind->local.ipv6, .daddr = bind->remote.in6.sin6_addr, - .fl6_sport = inet_sk(sk)->inet_sport, + .fl6_sport = key->sport, .fl6_dport = bind->remote.in6.sin6_port, .flowi6_proto = sk->sk_protocol, - .flowi6_mark = sk->sk_mark, + .flowi6_mark = key->mark, .flowi6_oif = bind->remote.in6.sin6_scope_id, }; @@ -238,16 +335,13 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, if (dst) goto transmit; - if (unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { - /* we may end up here when the cached address is not usable - * anymore. In this case we reset address/cache and perform a - * new look up + if (!ipv6_addr_any(&fl.saddr) && + unlikely(!ipv6_chk_addr(sock_net(sk), &fl.saddr, NULL, 0))) { + /* The learned local address is not usable anymore. + * Retry with source address autoselection. */ fl.saddr = in6addr_any; - spin_lock_bh(&peer->lock); - bind->local.ipv6 = in6addr_any; - spin_unlock_bh(&peer->lock); - dst_cache_reset(cache); + reset_local = true; } dst = ip6_dst_lookup_flow(sock_net(sk), sk, &fl, NULL); @@ -258,7 +352,30 @@ static int ovpn_udp6_output(struct ovpn_peer *peer, struct ovpn_bind *bind, &bind->remote.in6, ret); goto err; } - dst_cache_set_ip6(cache, dst, &fl.saddr); + + /* avoid storing a stale cache or local address */ + spin_lock_bh(&peer->lock); + if (likely(ovpn_dst_cache_current(peer, bind, key))) { + if (!reset_local) { + dst_cache_set_ip6(cache, dst, &fl.saddr); + spin_unlock_bh(&peer->lock); + goto transmit; + } + + /* invalidate per-CPU dst entries that may still carry + * the stale source + */ + dst_cache_reset(cache); + + /* preserve the current remote */ + memcpy(&remote, &bind->remote, sizeof(struct sockaddr_in6)); + /* The current packet already has a valid wildcard-source route. + * If replacing the bind fails, leave the stale local in place; + * a later cache miss will retry the repair. + */ + ovpn_peer_reset_sockaddr(peer, &remote, &local); + } + spin_unlock_bh(&peer->lock); transmit: /* user IPv6 packets may be larger than the transport interface @@ -287,6 +404,7 @@ err: * @cache: dst cache * @sk: the socket to send the packet over * @skb: the packet to send + * @key: route key snapshot used for cache validation and flow lookup * * rcu_read_lock should be held on entry. * On return, the skb is consumed. @@ -294,7 +412,8 @@ err: * Return: 0 on success or a negative error code otherwise */ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache, - struct sock *sk, struct sk_buff *skb) + struct sock *sk, struct sk_buff *skb, + struct ovpn_route_key *key) { struct ovpn_bind *bind; int ret; @@ -314,11 +433,11 @@ static int ovpn_udp_output(struct ovpn_peer *peer, struct dst_cache *cache, switch (bind->remote.in4.sin_family) { case AF_INET: - ret = ovpn_udp4_output(peer, bind, cache, sk, skb); + ret = ovpn_udp4_output(peer, bind, cache, sk, skb, key); break; #if IS_ENABLED(CONFIG_IPV6) case AF_INET6: - ret = ovpn_udp6_output(peer, bind, cache, sk, skb); + ret = ovpn_udp6_output(peer, bind, cache, sk, skb, key); break; #endif default: @@ -340,15 +459,21 @@ out: void ovpn_udp_send_skb(struct ovpn_peer *peer, struct sock *sk, struct sk_buff *skb) { + struct ovpn_route_key key = { + .mark = READ_ONCE(sk->sk_mark), + .sport = READ_ONCE(inet_sk(sk)->inet_sport), + }; int ret; skb->dev = peer->ovpn->dev; - skb->mark = READ_ONCE(sk->sk_mark); + skb->mark = key.mark; /* no checksum performed at this layer */ skb->ip_summed = CHECKSUM_NONE; + ovpn_dst_cache_check_key(peer, &peer->dst_cache, &key); + /* crypto layer -> transport (UDP) */ - ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb); + ret = ovpn_udp_output(peer, &peer->dst_cache, sk, skb, &key); if (unlikely(ret < 0)) kfree_skb(skb); } diff --git a/drivers/net/pcs/pcs-rzn1-miic.c b/drivers/net/pcs/pcs-rzn1-miic.c index 2b72fa98ddf1..cb74861e823c 100644 --- a/drivers/net/pcs/pcs-rzn1-miic.c +++ b/drivers/net/pcs/pcs-rzn1-miic.c @@ -683,7 +683,8 @@ static int miic_parse_dt(struct miic *miic, u32 *mode_cfg) if (!dt_val) return -ENOMEM; - memset(dt_val, MIIC_MODCTRL_CONF_NONE, sizeof(*dt_val)); + memset(dt_val, MIIC_MODCTRL_CONF_NONE, + sizeof(*dt_val) * miic->of_data->conf_conv_count); if (of_property_read_u32(np, "renesas,miic-switch-portin", &conf) == 0) dt_val[0] = conf; diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 0337e2bcc012..b415b93d77c1 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -1545,8 +1545,10 @@ static int xpcs_init_clks(struct dw_xpcs *xpcs) return dev_err_probe(dev, ret, "Failed to get clocks\n"); ret = clk_bulk_prepare_enable(DW_XPCS_NUM_CLKS, xpcs->clks); - if (ret) + if (ret) { + clk_bulk_put(DW_XPCS_NUM_CLKS, xpcs->clks); return dev_err_probe(dev, ret, "Failed to enable clocks\n"); + } return 0; } diff --git a/drivers/net/phy/intel-xway.c b/drivers/net/phy/intel-xway.c index afbcec711744..3cee31bb931f 100644 --- a/drivers/net/phy/intel-xway.c +++ b/drivers/net/phy/intel-xway.c @@ -16,6 +16,11 @@ #define XWAY_MDIO_ISTAT 0x1A /* interrupt status */ #define XWAY_MDIO_LED 0x1B /* led control */ +#define XWAY_MDIO_GCTRL_TM_MASK GENMASK(15, 13) +#define XWAY_MDIO_GCTRL_TM(mode) FIELD_PREP(XWAY_MDIO_GCTRL_TM_MASK, (mode)) +#define XWAY_MDIO_GCTRL_TM_NOP XWAY_MDIO_GCTRL_TM(0) /* Normal operation */ +#define XWAY_MDIO_GCTRL_TM_CDIAG XWAY_MDIO_GCTRL_TM(6) /* Cable diagnostics */ + #define XWAY_MDIO_ERRCNT_SEL GENMASK(11, 8) #define XWAY_MDIO_ERRCNT_COUNT GENMASK(7, 0) #define XWAY_MDIO_ERRCNT_SEL_RXERR 0 @@ -326,6 +331,28 @@ static int xway_gphy_probe(struct phy_device *phydev) return 0; } +static int xway_11g_int_config_init(struct phy_device *phydev) +{ + int err; + + /* An issue has been sporadically observed after device power-on on the + * first link-up attempt in 100BASE-TX mode resulting in either the + * link-up taking a long time, or failing to link-up altogether. + * + * Workaround: + * After power-on, enable Cable Diagnostic Mode for all ports and + * disable it. + */ + err = phy_modify(phydev, MII_CTRL1000, XWAY_MDIO_GCTRL_TM_MASK, XWAY_MDIO_GCTRL_TM_CDIAG); + if (err) + return err; + err = phy_modify(phydev, MII_CTRL1000, XWAY_MDIO_GCTRL_TM_MASK, XWAY_MDIO_GCTRL_TM_NOP); + if (err) + return err; + + return xway_gphy_config_init(phydev); +} + static int xway_gphy14_config_aneg(struct phy_device *phydev) { int reg, err; @@ -735,7 +762,7 @@ static struct phy_driver xway_gphy[] = { .phy_id_mask = 0xffffffff, .name = "Intel XWAY PHY11G (xRX v1.2 integrated)", /* PHY_GBIT_FEATURES */ - .config_init = xway_gphy_config_init, + .config_init = xway_11g_int_config_init, .probe = xway_gphy_probe, .handle_interrupt = xway_gphy_handle_interrupt, .config_intr = xway_gphy_config_intr, diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index ae830781824b..5c8461db7b4b 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -6344,6 +6344,7 @@ static int lanphy_write_reg_data(struct phy_device *phydev, data->val); if (ret) break; + data++; } return ret; diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index a1458da8111b..1bbcf46c8356 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -2129,7 +2129,6 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, mutex_lock(&pl->phydev_mutex); mutex_lock(&phy->lock); mutex_lock(&pl->state_mutex); - pl->phydev = phy; pl->phy_state.interface = interface; pl->phy_state.pause = MLO_PAUSE_NONE; pl->phy_state.speed = SPEED_UNKNOWN; @@ -2196,10 +2195,25 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, ret = 0; } - if (ret == 0 && phy_interrupt_is_valid(phy)) + if (ret) + return ret; + + /* Nothing below can fail, so the PHY can be recorded now. Doing it + * here rather than above keeps a failed bringup from leaving + * pl->phydev pointing at a PHY the caller is about to detach. + */ + mutex_lock(&pl->phydev_mutex); + mutex_lock(&phy->lock); + mutex_lock(&pl->state_mutex); + pl->phydev = phy; + mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); + mutex_unlock(&pl->phydev_mutex); + + if (phy_interrupt_is_valid(phy)) phy_request_interrupt(phy); - return ret; + return 0; } static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, diff --git a/drivers/net/usb/catc.c b/drivers/net/usb/catc.c index 96e82f94edcf..39b678f175dd 100644 --- a/drivers/net/usb/catc.c +++ b/drivers/net/usb/catc.c @@ -233,17 +233,26 @@ static void catc_rx_done(struct urb *urb) } do { - if(!catc->is_f5u011) { - pkt_len = le16_to_cpup((__le16*)pkt_start); - if (pkt_len > urb->actual_length) { + int remaining = urb->actual_length - + (pkt_start - (u8 *)urb->transfer_buffer); + + if (!catc->is_f5u011) { + if (remaining < pkt_offset) { catc->netdev->stats.rx_length_errors++; catc->netdev->stats.rx_errors++; break; } + pkt_len = le16_to_cpup((__le16 *)pkt_start); } else { pkt_len = urb->actual_length; } + if (pkt_len < ETH_HLEN || pkt_len + pkt_offset > remaining) { + catc->netdev->stats.rx_length_errors++; + catc->netdev->stats.rx_errors++; + break; + } + if (!(skb = dev_alloc_skb(pkt_len))) return; diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c index 877fb0ed7d3d..a7010a0664c7 100644 --- a/drivers/net/usb/cdc_mbim.c +++ b/drivers/net/usb/cdc_mbim.c @@ -635,6 +635,11 @@ static const struct usb_device_id mbim_devs[] = { .driver_info = (unsigned long)&cdc_mbim_info, }, + /* MeiG Smart SRM821 ZLP conformance */ + { USB_DEVICE_AND_INTERFACE_INFO(0x2dee, 0x4d53, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info, + }, + /* Some Huawei devices, ME906s-158 (12d1:15c1) and E3372 * (12d1:157d), are known to fail unless the NDP is placed * after the IP packets. Applying the quirk to all Huawei diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index cb782d81d84f..5655941f1478 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -5239,10 +5239,12 @@ static bool lan78xx_submit_deferred_urbs(struct lan78xx_net *dev) !netif_carrier_ok(dev->net) || pipe_halted) { lan78xx_release_tx_buf(dev, skb); + usb_put_urb(urb); continue; } ret = usb_submit_urb(urb, GFP_ATOMIC); + usb_put_urb(urb); if (ret == 0) { netif_trans_update(dev->net); diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c index f51cf9cb9421..0e2ab567dd40 100644 --- a/drivers/net/usb/qmi_wwan.c +++ b/drivers/net/usb/qmi_wwan.c @@ -1086,6 +1086,7 @@ static const struct usb_device_id products[] = { {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0125)}, /* Quectel EC25, EC20 R2.0 Mini PCIe */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x013d)}, /* Quectel RG660QB */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0306)}, /* Quectel EP06/EG06/EM06 */ + {QMI_MATCH_FF_FF_FF(0x2c7c, 0x030b)}, /* Quectel EM060K/EG120K-EA */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0512)}, /* Quectel EG12/EM12 */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0620)}, /* Quectel EM160R-GL */ {QMI_MATCH_FF_FF_FF(0x2c7c, 0x0800)}, /* Quectel RM500Q-GL */ diff --git a/drivers/net/usb/sr9700.c b/drivers/net/usb/sr9700.c index 937e6fef3ac6..50981a28376a 100644 --- a/drivers/net/usb/sr9700.c +++ b/drivers/net/usb/sr9700.c @@ -355,7 +355,8 @@ static int sr9700_rx_fixup(struct usbnet *dev, struct sk_buff *skb) /* ignore the CRC length */ len = (skb->data[1] | (skb->data[2] << 8)) - 4; - if (len > ETH_FRAME_LEN || len > skb->len || len < 0) + if (len > ETH_FRAME_LEN || len < 0 || + len > skb->len - SR_RX_OVERHEAD) return 0; /* the last packet of current skb */ diff --git a/drivers/net/veth.c b/drivers/net/veth.c index 6ed3ee81153f..71227d0389aa 100644 --- a/drivers/net/veth.c +++ b/drivers/net/veth.c @@ -1054,6 +1054,7 @@ static int __veth_napi_enable_range(struct net_device *dev, int start, int end) for (i = start; i < end; i++) { struct veth_rq *rq = &priv->rq[i]; + rcu_assign_pointer(rq->xdp_prog, priv->_xdp_prog); napi_enable(&rq->xdp_napi); rcu_assign_pointer(priv->rq[i].napi, &priv->rq[i].xdp_napi); } @@ -1088,6 +1089,7 @@ static void veth_napi_del_range(struct net_device *dev, int start, int end) rcu_assign_pointer(priv->rq[i].napi, NULL); napi_disable(&rq->xdp_napi); + rcu_assign_pointer(rq->xdp_prog, NULL); __netif_napi_del(&rq->xdp_napi); } synchronize_net(); diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index e34c52d059d3..bf82ef9874ab 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -3349,6 +3349,14 @@ static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) else virtqueue_disable_cb(sq->vq); + if (!use_napi && + unlikely(skb_orphan_frags(skb, GFP_ATOMIC))) { + DEV_STATS_INC(dev, tx_dropped); + dev_kfree_skb_any(skb); + kick = !xmit_more || netif_xmit_stopped(txq); + goto kick_vq; + } + /* timestamp packet in software */ skb_tx_timestamp(skb); @@ -3381,6 +3389,7 @@ static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) kick = use_napi ? __netdev_tx_sent_queue(txq, skb->len, xmit_more) : !xmit_more || netif_xmit_stopped(txq); +kick_vq: if (kick) { if (virtqueue_kick_prepare(sq->vq) && virtqueue_notify(sq->vq)) { u64_stats_update_begin(&sq->stats.syncp); diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c index a0557a3a7026..d4dc6d690a75 100644 --- a/drivers/net/vrf.c +++ b/drivers/net/vrf.c @@ -1175,8 +1175,6 @@ static int vrf_prepare_mac_header(struct sk_buff *skb, skb->protocol = eth->h_proto; skb->pkt_type = PACKET_HOST; - skb_postpush_rcsum(skb, skb->data, ETH_HLEN); - skb_pull_inline(skb, ETH_HLEN); return 0; diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index c1d54339fa2b..3390e341d1e5 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -1958,13 +1958,15 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request, struct ipv6hdr *pip6; u8 *daddr; int na_olen = 8; /* opt hdr + ETH_ALEN for target */ + int headroom; int ns_olen; int i, len; if (dev == NULL || !pskb_may_pull(request, request->len)) return NULL; - len = LL_RESERVED_SPACE(dev) + sizeof(struct ipv6hdr) + + headroom = LL_RESERVED_SPACE(dev); + len = headroom + sizeof(struct ipv6hdr) + sizeof(*na) + na_olen + dev->needed_tailroom; reply = alloc_skb(len, GFP_ATOMIC); if (reply == NULL) @@ -1972,7 +1974,7 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request, reply->protocol = htons(ETH_P_IPV6); reply->dev = dev; - skb_reserve(reply, LL_RESERVED_SPACE(request->dev)); + skb_reserve(reply, headroom); skb_push(reply, sizeof(struct ethhdr)); skb_reset_mac_header(reply); diff --git a/drivers/nfc/microread/microread.c b/drivers/nfc/microread/microread.c index dfa2490db545..2bfafa94e83d 100644 --- a/drivers/nfc/microread/microread.c +++ b/drivers/nfc/microread/microread.c @@ -251,9 +251,9 @@ static int microread_start_poll(struct nfc_hci_dev *hdev, param[1] |= (1 << 1); if ((im_protocols | tm_protocols) & NFC_PROTO_NFC_DEP_MASK) { - hdev->gb = nfc_get_local_general_bytes(hdev->ndev, - &hdev->gb_len); - if (hdev->gb == NULL || hdev->gb_len == 0) { + nfc_get_local_general_bytes(hdev->ndev, hdev->gb, + sizeof(hdev->gb), &hdev->gb_len); + if (hdev->gb_len == 0) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; } diff --git a/drivers/nfc/nfcmrvl/fw_dnld.c b/drivers/nfc/nfcmrvl/fw_dnld.c index 2b8f401d8fd7..8b9d5257320d 100644 --- a/drivers/nfc/nfcmrvl/fw_dnld.c +++ b/drivers/nfc/nfcmrvl/fw_dnld.c @@ -263,9 +263,14 @@ static int process_state_fw_dnld(struct nfcmrvl_private *priv, * B8..N: payload */ - /* Remove NCI HDR */ - skb_pull(skb, 3); - if (skb->data[0] != HELPER_CMD_PACKET_FORMAT || skb->len != 5) { + if (skb->len != NCI_DATA_HDR_SIZE + 5) { + nfc_err(priv->dev, "bad command"); + return -EINVAL; + } + + /* Remove NCI header */ + skb_pull(skb, NCI_DATA_HDR_SIZE); + if (skb->data[0] != HELPER_CMD_PACKET_FORMAT) { nfc_err(priv->dev, "bad command"); return -EINVAL; } diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c index f5a6a7c20d5a..b0133e51dce9 100644 --- a/drivers/nfc/pn533/pn533.c +++ b/drivers/nfc/pn533/pn533.c @@ -1357,10 +1357,11 @@ static int pn533_poll_dep(struct nfc_dev *nfc_dev) u8 *next, nfcid3[NFC_NFCID3_MAXSIZE]; u8 passive_data[PASSIVE_DATA_LEN] = {0x00, 0xff, 0xff, 0x00, 0x3}; - if (!dev->gb) { - dev->gb = nfc_get_local_general_bytes(nfc_dev, &dev->gb_len); - - if (!dev->gb || !dev->gb_len) { + if (!dev->gb_len) { + nfc_get_local_general_bytes(nfc_dev, dev->gb, + sizeof(dev->gb), + &dev->gb_len); + if (!dev->gb_len) { dev->poll_dep = 0; queue_work(dev->wq, &dev->rf_work); } @@ -1658,8 +1659,9 @@ static int pn533_start_poll(struct nfc_dev *nfc_dev, } if (tm_protocols) { - dev->gb = nfc_get_local_general_bytes(nfc_dev, &dev->gb_len); - if (dev->gb == NULL) + nfc_get_local_general_bytes(nfc_dev, dev->gb, + sizeof(dev->gb), &dev->gb_len); + if (dev->gb_len == 0) tm_protocols = 0; } diff --git a/drivers/nfc/pn533/pn533.h b/drivers/nfc/pn533/pn533.h index 09e35b8693f5..5ab668e05121 100644 --- a/drivers/nfc/pn533/pn533.h +++ b/drivers/nfc/pn533/pn533.h @@ -6,6 +6,8 @@ * Copyright (C) 2012-2013 Tieto Poland */ +#include <net/nfc/nfc.h> + #define PN533_DEVICE_STD 0x1 #define PN533_DEVICE_PASORI 0x2 #define PN533_DEVICE_ACR122U 0x3 @@ -166,7 +168,7 @@ struct pn533 { struct timer_list listen_timer; int cancel_listen; - u8 *gb; + u8 gb[NFC_MAX_GT_LEN]; size_t gb_len; u8 tgt_available_prots; diff --git a/drivers/nfc/pn533/usb.c b/drivers/nfc/pn533/usb.c index efb07f944fce..972eaac09e59 100644 --- a/drivers/nfc/pn533/usb.c +++ b/drivers/nfc/pn533/usb.c @@ -319,7 +319,9 @@ static bool pn533_acr122_is_rx_frame_valid(void *_frame, struct pn533 *dev) if (frame->ccid.type != 0x83) return false; - if (!frame->ccid.datalen) + if (frame->ccid.datalen < 2 || + frame->ccid.datalen > PN533_ACR122_FRAME_MAX_PAYLOAD_LEN + + PN533_ACR122_RX_FRAME_TAIL_LEN) return false; if (frame->data[frame->ccid.datalen - 2] == 0x63) diff --git a/drivers/nfc/pn544/pn544.c b/drivers/nfc/pn544/pn544.c index 9d0a16ac465e..c4fa70e45c14 100644 --- a/drivers/nfc/pn544/pn544.c +++ b/drivers/nfc/pn544/pn544.c @@ -377,10 +377,9 @@ static int pn544_hci_start_poll(struct nfc_hci_dev *hdev, return r; if ((im_protocols | tm_protocols) & NFC_PROTO_NFC_DEP_MASK) { - hdev->gb = nfc_get_local_general_bytes(hdev->ndev, - &hdev->gb_len); - pr_debug("generate local bytes %p\n", hdev->gb); - if (hdev->gb == NULL || hdev->gb_len == 0) { + nfc_get_local_general_bytes(hdev->ndev, hdev->gb, + sizeof(hdev->gb), &hdev->gb_len); + if (hdev->gb_len == 0) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; } diff --git a/drivers/nfc/port100.c b/drivers/nfc/port100.c index b613f5e2fd57..e769a30b8b5c 100644 --- a/drivers/nfc/port100.c +++ b/drivers/nfc/port100.c @@ -636,6 +636,13 @@ static void port100_recv_response(struct urb *urb) in_frame = dev->in_urb->transfer_buffer; + if (urb->actual_length < PORT100_FRAME_HEADER_LEN || + urb->actual_length < port100_rx_frame_size(in_frame)) { + nfc_err(&dev->interface->dev, "Received a truncated frame\n"); + cmd->status = -EIO; + goto sched_wq; + } + if (!port100_rx_frame_is_valid(in_frame)) { nfc_err(&dev->interface->dev, "Received an invalid frame\n"); cmd->status = -EIO; diff --git a/drivers/nfc/st21nfca/core.c b/drivers/nfc/st21nfca/core.c index fd39a05c9622..b5c1ca3acfbe 100644 --- a/drivers/nfc/st21nfca/core.c +++ b/drivers/nfc/st21nfca/core.c @@ -351,10 +351,10 @@ static int st21nfca_hci_start_poll(struct nfc_hci_dev *hdev, if (r < 0) return r; } else { - hdev->gb = nfc_get_local_general_bytes(hdev->ndev, - &hdev->gb_len); - - if (hdev->gb == NULL || hdev->gb_len == 0) { + nfc_get_local_general_bytes(hdev->ndev, hdev->gb, + sizeof(hdev->gb), + &hdev->gb_len); + if (hdev->gb_len == 0) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK; } @@ -577,9 +577,7 @@ static int st21nfca_get_iso15693_inventory(struct nfc_hci_dev *hdev, if (r < 0) goto exit; - skb_pull(inventory_skb, 2); - - if (inventory_skb->len == 0 || + if (!skb_pull(inventory_skb, 2) || inventory_skb->len < 2 || inventory_skb->len > NFC_ISO15693_UID_MAXSIZE) { r = -EPROTO; goto exit; diff --git a/drivers/nfc/st21nfca/i2c.c b/drivers/nfc/st21nfca/i2c.c index a4c93ff7c5b0..0f44c783bd04 100644 --- a/drivers/nfc/st21nfca/i2c.c +++ b/drivers/nfc/st21nfca/i2c.c @@ -289,27 +289,36 @@ static int check_crc(u8 *buf, int buflen) */ static int st21nfca_hci_i2c_repack(struct sk_buff *skb) { - int i, j, r, size; + int read, write, r, size; - if (skb->len < 1 || (skb->len > 1 && skb->data[1] != 0)) + if (skb->len < ST21NFCA_FRAME_HEADROOM || + !IS_START_OF_FRAME(skb->data)) return -EBADMSG; size = get_frame_size(skb->data, skb->len); if (size > 0) { + if (size < ST21NFCA_FRAME_HEADROOM + 2) + return -EBADMSG; + skb_trim(skb, size); /* remove ST21NFCA byte stuffing for upper layer */ - for (i = 1, j = 0; i < skb->len; i++) { - if (skb->data[i + j] == + for (read = 1, write = 1; read < skb->len;) { + if (skb->data[read] == (u8) ST21NFCA_ESCAPE_BYTE_STUFFING) { - skb->data[i] = skb->data[i + j + 1] - | ST21NFCA_BYTE_STUFFING_MASK; - i++; - j++; + if (read + 1 == skb->len) + return -EBADMSG; + + skb->data[write++] = skb->data[read + 1] + | ST21NFCA_BYTE_STUFFING_MASK; + read += 2; + } else { + skb->data[write++] = skb->data[read++]; } - skb->data[i] = skb->data[i + j]; } /* remove byte stuffing useless byte */ - skb_trim(skb, i - j); + skb_trim(skb, write); + if (skb->len < ST21NFCA_FRAME_HEADROOM + 2) + return -EBADMSG; /* remove ST21NFCA_SOF_EOF from head */ skb_pull(skb, 1); diff --git a/drivers/nfc/trf7970a.c b/drivers/nfc/trf7970a.c index 60883001fa5d..ddfc58c29964 100644 --- a/drivers/nfc/trf7970a.c +++ b/drivers/nfc/trf7970a.c @@ -1997,8 +1997,10 @@ static int trf7970a_startup(struct trf7970a *trf) return ret; ret = trf7970a_update_rx_gain_reduction(trf); - if (ret) + if (ret) { + trf7970a_power_down(trf); return ret; + } pm_runtime_set_active(trf->dev); pm_runtime_enable(trf->dev); diff --git a/drivers/nfc/virtual_ncidev.c b/drivers/nfc/virtual_ncidev.c index 8eeb447ac96e..e51c647b27eb 100644 --- a/drivers/nfc/virtual_ncidev.c +++ b/drivers/nfc/virtual_ncidev.c @@ -195,7 +195,8 @@ static const struct file_operations virtual_ncidev_fops = { .write = virtual_ncidev_write, .open = virtual_ncidev_open, .release = virtual_ncidev_close, - .unlocked_ioctl = virtual_ncidev_ioctl + .unlocked_ioctl = virtual_ncidev_ioctl, + .compat_ioctl = compat_ptr_ioctl, }; static struct miscdevice miscdev = { diff --git a/drivers/pci/of_property.c b/drivers/pci/of_property.c index 75a358f73e69..1500740cc55d 100644 --- a/drivers/pci/of_property.c +++ b/drivers/pci/of_property.c @@ -95,9 +95,13 @@ static int of_pci_prop_bus_range(struct pci_dev *pdev, struct of_changeset *ocs, struct device_node *np) { - u32 bus_range[] = { pdev->subordinate->busn_res.start, - pdev->subordinate->busn_res.end }; + u32 bus_range[2]; + if (!pdev->subordinate) + return 0; + + bus_range[0] = pdev->subordinate->busn_res.start; + bus_range[1] = pdev->subordinate->busn_res.end; return of_changeset_add_prop_u32_array(ocs, np, "bus-range", bus_range, ARRAY_SIZE(bus_range)); } @@ -220,6 +224,9 @@ static int of_pci_prop_intr_map(struct pci_dev *pdev, struct of_changeset *ocs, int ret; u8 pin; + if (!pdev->subordinate) + return 0; + pnode = pci_device_to_OF_node(pdev->bus->self); if (!pnode) pnode = pci_bus_to_OF_node(pdev->bus); diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index e8c94aa1d3c1..ed16ef7c26fa 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -2380,6 +2380,7 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size struct resource *res = pci_resource_n(pdev, resno); struct pci_dev_resource *dev_res; struct pci_bus *bus = pdev->bus; + struct pci_dev *bridge = pci_upstream_bridge(pdev); struct resource *b_win, *r; LIST_HEAD(saved); unsigned int i; @@ -2397,6 +2398,8 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size if (ret) return ret; + down_read(&pci_bus_sem); + pci_dev_for_each_resource(pdev, r, i) { if (i >= PCI_BRIDGE_RESOURCES) break; @@ -2415,13 +2418,21 @@ int pci_do_resource_release_and_resize(struct pci_dev *pdev, int resno, int size pci_resize_resource_set_size(pdev, resno, size); - if (!bus->self) - goto out; + if (bridge) { + ret = pbus_reassign_bridge_resources(bus, res, &saved); + if (ret) + goto restore; + } else { + /* No bridge window to adjust; let the core reassign the bus. */ + pci_bus_assign_resources(bus); - down_read(&pci_bus_sem); - ret = pbus_reassign_bridge_resources(bus, res, &saved); - if (ret) - goto restore; + list_for_each_entry(dev_res, &saved, list) { + if (!resource_assigned(dev_res->res)) { + ret = -ENOSPC; + goto restore; + } + } + } out: up_read(&pci_bus_sem); diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c index 33ee2be9b386..5378fba916cf 100644 --- a/drivers/perf/arm-cmn.c +++ b/drivers/perf/arm-cmn.c @@ -305,9 +305,9 @@ enum cmn_filter_select { SEL_NONE, SEL_OCCUP1_ID, SEL_CLASS_OCCUP_ID, - SEL_CBUSY_SNTHROTTLE_SEL, SEL_HBT_LBT_SEL, SEL_SN_HOME_SEL, + SEL_CBUSY_SNTHROTTLE_SEL, SEL_SNP_VC_SEL, SEL_ENHANCED_HBT_LBT_SEL, SEL_EVICT_STATE_SEL, @@ -978,10 +978,14 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj, _CMN_EVENT_HNS(_model, _name##_all, _event, _sel, 0), \ _CMN_EVENT_HNS(_model, _name##_hbt, _event, _sel, 1), \ _CMN_EVENT_HNS(_model, _name##_lbt, _event, _sel, 2) -#define _CMN_EVENT_HNS_HBT2(_model, _name, _event, _fsel1, f1) \ - _CMN_EVENT_HNS(_model, _name##_all, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 0), \ - _CMN_EVENT_HNS(_model, _name##_hbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 1), \ - _CMN_EVENT_HNS(_model, _name##_lbt, _event, _fsel1, f1, SEL_HBT_LBT_SEL, 2) +#define _CMN_EVENT_HNS_EVICT(_model, _name, _event, _fsel1, f1) \ + _CMN_EVENT_HNS(_model, _name##_all, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 0), \ + _CMN_EVENT_HNS(_model, _name##_eu, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 1), \ + _CMN_EVENT_HNS(_model, _name##_en, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 2), \ + _CMN_EVENT_HNS(_model, _name##_su, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 3), \ + _CMN_EVENT_HNS(_model, _name##_sn, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 4), \ + _CMN_EVENT_HNS(_model, _name##_mu, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 5), \ + _CMN_EVENT_HNS(_model, _name##_mn, _event, _fsel1, f1, SEL_EVICT_STATE_SEL, 6) #define CMN_EVENT_HNS_OCC(_model, _name, _event) \ CMN_EVENT_HN_OCC(_model, hns_##_name, CMN_TYPE_HNS, _event), \ @@ -1014,13 +1018,9 @@ static umode_t arm_cmn_event_attr_is_visible(struct kobject *kobj, _CMN_EVENT_HNS(CMNS3R2, _name##_ccglcn, _event, SEL_ENHANCED_HBT_LBT_SEL, 5), \ _CMN_EVENT_HNS(CMNS3R2, _name##_ccgrn, _event, SEL_ENHANCED_HBT_LBT_SEL, 6) #define CMN_EVENT_HNS_EVICT(_model, _name, _event) \ - _CMN_EVENT_HNS_HBT2(_model, _name##_all, _event, SEL_EVICT_STATE_SEL, 0), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_eu, _event, SEL_EVICT_STATE_SEL, 1), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_en, _event, SEL_EVICT_STATE_SEL, 2), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_su, _event, SEL_EVICT_STATE_SEL, 3), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_sn, _event, SEL_EVICT_STATE_SEL, 4), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_mu, _event, SEL_EVICT_STATE_SEL, 5), \ - _CMN_EVENT_HNS_HBT2(_model, _name##_mn, _event, SEL_EVICT_STATE_SEL, 6) + _CMN_EVENT_HNS_EVICT(_model, _name##_all, _event, SEL_HBT_LBT_SEL, 0), \ + _CMN_EVENT_HNS_EVICT(_model, _name##_hbt, _event, SEL_HBT_LBT_SEL, 1), \ + _CMN_EVENT_HNS_EVICT(_model, _name##_lbt, _event, SEL_HBT_LBT_SEL, 2) #define CMN_EVENT_HNSR0_HBT(_name, _event) \ _CMN_EVENT_HNS_HBT(CMN700 | CMNS3R01, _name, _event, SEL_HBT_LBT_SEL) @@ -1860,10 +1860,12 @@ static void arm_cmn_val_add_event(struct arm_cmn *cmn, struct arm_cmn_val *val, val->dtm_count[dtm]++; + if (sel == SEL_EVICT_STATE_SEL) { + val->filter[dtm][sel] = CMN_EVENT_FILTER2(event) + 1; + sel = SEL_HBT_LBT_SEL; + } if (sel) val->filter[dtm][sel] = CMN_EVENT_FILTER(event) + 1; - if (sel == SEL_EVICT_STATE_SEL) - val->filter[dtm][SEL_HBT_LBT_SEL] = CMN_EVENT_FILTER2(event) + 1; if (type != CMN_TYPE_WP) continue; @@ -1914,14 +1916,17 @@ static int arm_cmn_validate_group(struct arm_cmn *cmn, struct perf_event *event) if (val->dtm_count[dtm] == CMN_DTM_NUM_COUNTERS) goto done; + if (sel == SEL_EVICT_STATE_SEL) { + if (val->filter[dtm][sel] && + val->filter[dtm][sel] != CMN_EVENT_FILTER2(event) + 1) + goto done; + sel = SEL_HBT_LBT_SEL; + } + if (sel && val->filter[dtm][sel] && val->filter[dtm][sel] != CMN_EVENT_FILTER(event) + 1) goto done; - if (sel == SEL_EVICT_STATE_SEL && val->filter[dtm][SEL_HBT_LBT_SEL] && - val->filter[dtm][SEL_HBT_LBT_SEL] != CMN_EVENT_FILTER2(event) + 1) - goto done; - if (type != CMN_TYPE_WP) continue; @@ -1950,7 +1955,7 @@ static enum cmn_filter_select arm_cmn_event_filter(const struct arm_cmn *cmn, for (int i = 0; i < ARRAY_SIZE(arm_cmn_event_attrs) - 1; i++) { e = container_of(arm_cmn_event_attrs[i], typeof(*e), attr.attr); if (e->model & model && e->type == type && e->eventid == eventid) - return e->filter[0].sel; + return e->filter[1].sel ?: e->filter[0].sel; } return SEL_NONE; } @@ -2081,16 +2086,21 @@ static void arm_cmn_event_clear(struct arm_cmn *cmn, struct perf_event *event, static int arm_cmn_set_event_filter(struct arm_cmn_node *dn, struct perf_event *event) { enum cmn_filter_select fsel = to_cmn_hw(event)->filter_sel; + bool evict_state = fsel == SEL_EVICT_STATE_SEL; int ret = 0; + if (evict_state) { + ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER2(event)); + if (ret) + return ret; + fsel = SEL_HBT_LBT_SEL; + } if (fsel) ret = arm_cmn_set_event_sel_hi(dn, fsel, CMN_EVENT_FILTER(event)); - if (fsel == SEL_EVICT_STATE_SEL && !ret) { - ret = arm_cmn_set_event_sel_hi(dn, SEL_HBT_LBT_SEL, CMN_EVENT_FILTER2(event)); - if (ret) - dn->filter[fsel].count--; - } + if (ret && evict_state) + dn->filter[SEL_EVICT_STATE_SEL].count--; + return ret; } diff --git a/drivers/perf/arm_brbe.c b/drivers/perf/arm_brbe.c index ba554e0c846c..254be4da8ae2 100644 --- a/drivers/perf/arm_brbe.c +++ b/drivers/perf/arm_brbe.c @@ -604,7 +604,7 @@ static bool perf_entry_from_brbe_regset(int index, struct perf_branch_entry *ent return false; brbinf = bregs.brbinf; - perf_clear_branch_entry_bitfields(entry); + *entry = (struct perf_branch_entry){ }; if (brbe_record_is_complete(brbinf)) { entry->from = bregs.brbsrc; entry->to = bregs.brbtgt; diff --git a/drivers/pinctrl/meson/pinctrl-meson-s4.c b/drivers/pinctrl/meson/pinctrl-meson-s4.c index 872948699e9f..365dafe457a9 100644 --- a/drivers/pinctrl/meson/pinctrl-meson-s4.c +++ b/drivers/pinctrl/meson/pinctrl-meson-s4.c @@ -854,7 +854,7 @@ static const char * const i2c1_groups[] = { static const char * const i2c2_groups[] = { "i2c2_sda_d", "i2c2_scl_d", "i2c2_sda_h8", "i2c2_scl_h9", - "i2c2_sda_h0", "i2c2_scl_h1l," + "i2c2_sda_h0", "i2c2_scl_h1", }; static const char * const i2c3_groups[] = { diff --git a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c index ea1026a0d22c..92d38e5336de 100644 --- a/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c +++ b/drivers/pinctrl/microchip/pinctrl-mpfs-mssio.c @@ -86,7 +86,7 @@ static struct mpfs_pinctrl_bank_voltage mpfs_pinctrl_bank_voltages[8] = { { .uv = 1800000, .val = 4 }, { .uv = 2500000, .val = 6 }, { .uv = 3300000, .val = 8 }, - { .uv = 0, .val = 0x3f }, // pin unused + { .uv = 0, .val = 0xf }, // pin unused }; static int mpfs_pinctrl_get_drive_strength_ma(u32 drive_strength) @@ -156,10 +156,10 @@ static void mpfs_pinctrl_set_bank_voltage(struct mpfs_pinctrl *pctrl, unsigned i u32 val = FIELD_PREP(MPFS_PINCTRL_BANK_VOLTAGE_MASK, bank_voltage); if (pin < MPFS_PINCTRL_BANK2_START) - regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR, + regmap_update_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK4_CFG_CR, MPFS_PINCTRL_BANK_VOLTAGE_MASK, val); else - regmap_assign_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR, + regmap_update_bits(pctrl->sysreg_regmap, MPFS_PINCTRL_MSSIO_BANK2_CFG_CR, MPFS_PINCTRL_BANK_VOLTAGE_MASK, val); } diff --git a/drivers/pinctrl/pinctrl-generic.c b/drivers/pinctrl/pinctrl-generic.c index fd6bdb74028a..4277c8748513 100644 --- a/drivers/pinctrl/pinctrl-generic.c +++ b/drivers/pinctrl/pinctrl-generic.c @@ -3,8 +3,10 @@ #define pr_fmt(fmt) "generic pinconfig core: " fmt #include <linux/array_size.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/of.h> #include <linux/slab.h> @@ -196,6 +198,8 @@ static int pinctrl_generic_dt_node_to_map(struct pinctrl_dev *pctldev, int ngroups = 0; int ret; + guard(mutex)(&pctldev->mutex); + *maps = NULL; *num_maps = 0; diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c index 4d5f85b7e6bb..e0eb7240a985 100644 --- a/drivers/pinctrl/pinctrl-single.c +++ b/drivers/pinctrl/pinctrl-single.c @@ -1628,7 +1628,8 @@ static int pcs_irq_init_chained_handler(struct pcs_device *pcs, &pcs_irqdomain_ops, pcs_soc); if (!pcs->domain) { - irq_set_chained_handler(pcs_soc->irq, NULL); + pcs_irq_free(pcs); + pcs_soc->irq = -1; return -EINVAL; } diff --git a/drivers/pinctrl/qcom/pinctrl-ipq5210.c b/drivers/pinctrl/qcom/pinctrl-ipq5210.c index 827a4ad07a6b..d5b4eb3e7343 100644 --- a/drivers/pinctrl/qcom/pinctrl-ipq5210.c +++ b/drivers/pinctrl/qcom/pinctrl-ipq5210.c @@ -867,6 +867,7 @@ static const struct of_device_id ipq5210_tlmm_of_match[] = { { .compatible = "qcom,ipq5210-tlmm", }, { }, }; +MODULE_DEVICE_TABLE(of, ipq5210_tlmm_of_match); static int ipq5210_tlmm_probe(struct platform_device *pdev) { diff --git a/drivers/pinctrl/qcom/pinctrl-nord.c b/drivers/pinctrl/qcom/pinctrl-nord.c index 7c21306e77ff..7f37f8e819ba 100644 --- a/drivers/pinctrl/qcom/pinctrl-nord.c +++ b/drivers/pinctrl/qcom/pinctrl-nord.c @@ -570,8 +570,10 @@ enum nord_functions { msm_mux_qup0_se5, msm_mux_qup1_se0, msm_mux_qup1_se1, - msm_mux_qup1_se2, - msm_mux_qup1_se3, + msm_mux_qup1_se2_01, + msm_mux_qup1_se2_23, + msm_mux_qup1_se3_01, + msm_mux_qup1_se3_23, msm_mux_qup1_se4, msm_mux_qup1_se5, msm_mux_qup1_se6, @@ -1152,11 +1154,19 @@ static const char *const qup1_se1_groups[] = { "gpio123", "gpio124", "gpio125", "gpio126", }; -static const char *const qup1_se2_groups[] = { - "gpio127", "gpio128", "gpio129", "gpio130", +static const char *const qup1_se2_01_groups[] = { + "gpio127", "gpio128", }; -static const char *const qup1_se3_groups[] = { +static const char *const qup1_se2_23_groups[] = { + "gpio127", "gpio128", +}; + +static const char *const qup1_se3_01_groups[] = { + "gpio129", "gpio130", +}; + +static const char *const qup1_se3_23_groups[] = { "gpio129", "gpio130", }; @@ -1428,8 +1438,10 @@ static const struct pinfunction nord_functions[] = { MSM_PIN_FUNCTION(qup0_se5), MSM_PIN_FUNCTION(qup1_se0), MSM_PIN_FUNCTION(qup1_se1), - MSM_PIN_FUNCTION(qup1_se2), - MSM_PIN_FUNCTION(qup1_se3), + MSM_PIN_FUNCTION(qup1_se2_01), + MSM_PIN_FUNCTION(qup1_se2_23), + MSM_PIN_FUNCTION(qup1_se3_01), + MSM_PIN_FUNCTION(qup1_se3_23), MSM_PIN_FUNCTION(qup1_se4), MSM_PIN_FUNCTION(qup1_se5), MSM_PIN_FUNCTION(qup1_se6), @@ -1633,13 +1645,13 @@ static const struct msm_pingroup nord_groups[] = { _, _, _, _, _, _, _), [126] = PINGROUP(126, qup1_se1, qup1_se0, ccu_i2c_scl, mdp1_vsync_out, _, atest_usb20, ddr_pxi, _, _, _, _), - [127] = PINGROUP(127, qup1_se2, qup1_se2, _, atest_usb21, ddr_pxi, + [127] = PINGROUP(127, qup1_se2_23, qup1_se2_01, _, atest_usb21, ddr_pxi, _, _, _, _, _, _), - [128] = PINGROUP(128, qup1_se2, qup1_se2, _, atest_usb20, ddr_pxi, + [128] = PINGROUP(128, qup1_se2_23, qup1_se2_01, _, atest_usb20, ddr_pxi, _, _, _, _, _, _), - [129] = PINGROUP(129, qup1_se3, qup1_se3, ccu_i2c_sda, mdp1_vsync_out, + [129] = PINGROUP(129, qup1_se3_23, qup1_se3_01, ccu_i2c_sda, mdp1_vsync_out, _, atest_usb21, ddr_pxi, _, _, _, _), - [130] = PINGROUP(130, qup1_se3, qup1_se3, ccu_i2c_scl, mdp1_vsync_out, + [130] = PINGROUP(130, qup1_se3_23, qup1_se3_01, ccu_i2c_scl, mdp1_vsync_out, _, atest_usb20, ddr_pxi, _, _, _, _), [131] = PINGROUP(131, qup1_se4, qup1_se6, ccu_i2c_sda, mdp1_vsync_out, _, atest_usb21, ddr_pxi, _, _, _, _), diff --git a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c index 462aa1c4a5fa..e27e4945def2 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c +++ b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523-r.c @@ -26,7 +26,7 @@ static const u8 a523_r_irq_bank_muxes[SUNXI_PINCTRL_MAX_BANKS] = static struct sunxi_pinctrl_desc a523_r_pinctrl_data = { .irq_banks = ARRAY_SIZE(a523_r_irq_bank_map), .irq_bank_map = a523_r_irq_bank_map, - .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_SEL, + .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, .pin_base = PL_BASE, }; diff --git a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c index b6f78f1f30ac..88d8acd5bc24 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c +++ b/drivers/pinctrl/sunxi/pinctrl-sun55i-a523.c @@ -26,7 +26,7 @@ static const u8 a523_irq_bank_muxes[SUNXI_PINCTRL_MAX_BANKS] = static struct sunxi_pinctrl_desc a523_pinctrl_data = { .irq_banks = ARRAY_SIZE(a523_irq_bank_map), .irq_bank_map = a523_irq_bank_map, - .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_SEL, + .io_bias_cfg_variant = BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, }; static int a523_pinctrl_probe(struct platform_device *pdev) diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c index 25489beeb312..31cd142ce0f7 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c @@ -728,6 +728,7 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, { unsigned short bank; unsigned long flags; + bool inverted = false; u32 val, reg; int uV; @@ -766,6 +767,9 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, reg &= ~IO_BIAS_MASK; writel(reg | val, pctl->membase + sunxi_grp_config_reg(pin)); return 0; + case BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV: + inverted = true; + fallthrough; case BIAS_VOLTAGE_PIO_POW_MODE_CTL: val = uV > 1800000 && uV <= 2500000 ? BIT(bank) : 0; @@ -780,6 +784,8 @@ static int sunxi_pinctrl_set_io_bias_cfg(struct sunxi_pinctrl *pctl, fallthrough; case BIAS_VOLTAGE_PIO_POW_MODE_SEL: val = uV <= 1800000 ? 1 : 0; + if (inverted) + val = !val; raw_spin_lock_irqsave(&pctl->lock, flags); reg = readl(pctl->membase + pctl->pow_mod_sel_offset); @@ -837,6 +843,21 @@ static void sunxi_pmx_set(struct pinctrl_dev *pctldev, writel((readl(pctl->membase + reg) & ~mask) | config << shift, pctl->membase + reg); + /* + * A pin muxed to gpio_out directly through a pinmux node bypasses + * sunxi_pinctrl_gpio_set() and drives whatever its output latch + * holds. Now that the pin is in output mode the data register + * reads back the latch, so refresh the shadow to keep such pins + * driving their pre-existing level. + */ + if (config == SUN4I_FUNC_OUTPUT) { + u32 *shadow = &pctl->dat_shadow[pin / PINS_PER_BANK]; + + sunxi_data_reg(pctl, pin, ®, &shift, &mask); + *shadow = (*shadow & ~mask) | + (readl(pctl->membase + reg) & mask); + } + raw_spin_unlock_irqrestore(&pctl->lock, flags); } @@ -1017,21 +1038,29 @@ static int sunxi_pinctrl_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { struct sunxi_pinctrl *pctl = gpiochip_get_data(chip); - u32 reg, shift, mask, val; + u32 *shadow = &pctl->dat_shadow[offset / PINS_PER_BANK]; + u32 reg, shift, mask; unsigned long flags; sunxi_data_reg(pctl, offset, ®, &shift, &mask); raw_spin_lock_irqsave(&pctl->lock, flags); - val = readl(pctl->membase + reg); - + /* + * Reading the data register returns the pin level, not the output + * latch, for pins muxed as inputs. A read-modify-write based on + * the register would therefore corrupt the latches of input-muxed + * pins in the same bank (e.g. an emulated open-drain I2C line + * released high), making them drive the wrong level once switched + * to output. Base the read-modify-write on a shadow copy of the + * latches instead. + */ if (value) - val |= mask; + *shadow |= mask; else - val &= ~mask; + *shadow &= ~mask; - writel(val, pctl->membase + reg); + writel(*shadow, pctl->membase + reg); raw_spin_unlock_irqrestore(&pctl->lock, flags); @@ -1572,7 +1601,7 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, struct pinctrl_pin_desc *pins; struct sunxi_pinctrl *pctl; struct pinmux_ops *pmxops; - int i, ret, last_pin, pin_idx; + int i, ret, last_pin, pin_idx, nbanks; struct clk *clk; pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL); @@ -1610,6 +1639,37 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, if (!pctl->irq_array) return -ENOMEM; + /* + * The bus clock has to be enabled before the pinctrl device + * registers, as the pin hogs claimed from there access registers. + */ + ret = of_clk_get_parent_count(node); + clk = devm_clk_get_enabled(&pdev->dev, ret == 1 ? NULL : "apb"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + /* + * Seed the output latch shadow from the hardware so pins the + * bootloader left in output mode keep their state; see + * sunxi_pinctrl_gpio_set() for why a shadow is needed. This must + * happen before the pinctrl device registers, as pin hogs can mux + * pins to gpio_out and thereby update the shadow. + */ + last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number; + nbanks = DIV_ROUND_UP(last_pin + 1 - pctl->desc->pin_base, + PINS_PER_BANK); + pctl->dat_shadow = devm_kcalloc(&pdev->dev, nbanks, + sizeof(*pctl->dat_shadow), GFP_KERNEL); + if (!pctl->dat_shadow) + return -ENOMEM; + + for (i = 0; i < nbanks; i++) { + u32 reg, shift, mask; + + sunxi_data_reg(pctl, i * PINS_PER_BANK, ®, &shift, &mask); + pctl->dat_shadow[i] = readl(pctl->membase + reg); + } + ret = sunxi_pinctrl_build_state(pdev); if (ret) { dev_err(&pdev->dev, "dt probe failed: %d\n", ret); @@ -1665,7 +1725,6 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, if (!pctl->chip) return -ENOMEM; - last_pin = pctl->desc->pins[pctl->desc->npins - 1].pin.number; pctl->chip->owner = THIS_MODULE; pctl->chip->request = gpiochip_generic_request; pctl->chip->free = gpiochip_generic_free; @@ -1699,13 +1758,6 @@ int sunxi_pinctrl_init_with_flags(struct platform_device *pdev, goto gpiochip_error; } - ret = of_clk_get_parent_count(node); - clk = devm_clk_get_enabled(&pdev->dev, ret == 1 ? NULL : "apb"); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - goto gpiochip_error; - } - pctl->irq = devm_kcalloc(&pdev->dev, pctl->desc->irq_banks, sizeof(*pctl->irq), diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.h b/drivers/pinctrl/sunxi/pinctrl-sunxi.h index 0daf7600e2fb..8bd00c6ff628 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.h +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.h @@ -85,6 +85,7 @@ #define IO_BIAS_MASK GENMASK(3, 0) #define SUN4I_FUNC_INPUT 0 +#define SUN4I_FUNC_OUTPUT 1 #define SUN4I_FUNC_IRQ 6 #define SUN4I_FUNC_DISABLED_OLD 7 #define SUN4I_FUNC_DISABLED_NEW 15 @@ -116,8 +117,10 @@ enum sunxi_desc_bias_voltage { * Bias voltage is set through PIO_POW_MOD_SEL_REG * and PIO_POW_MOD_CTL_REG register, as seen on * A100 and D1 SoC, for example. + * Some SoCs invert the encoding for 1.8V vs. 3.3V. */ BIAS_VOLTAGE_PIO_POW_MODE_CTL, + BIAS_VOLTAGE_PIO_POW_MODE_CTL_INV, }; struct sunxi_desc_function { @@ -175,6 +178,12 @@ struct sunxi_pinctrl { int *irq; unsigned *irq_array; raw_spinlock_t lock; + /* + * Output latch shadow, one word per bank. Seeded lockless at + * probe before the pinctrl device registers, protected by @lock + * afterwards. + */ + u32 *dat_shadow; struct pinctrl_dev *pctl_dev; unsigned long flags; u32 bank_mem_size; diff --git a/drivers/pinctrl/tegra/pinctrl-tegra238.c b/drivers/pinctrl/tegra/pinctrl-tegra238.c index ec482365f14f..40aba285944e 100644 --- a/drivers/pinctrl/tegra/pinctrl-tegra238.c +++ b/drivers/pinctrl/tegra/pinctrl-tegra238.c @@ -1744,57 +1744,57 @@ static const char * const tegra238_functions[] = { #define drive_sdmmc1_dat0_pu2 DRV_PINGROUP_ENTRY_Y(0x8034, 28, 2, 30, 2, -1, -1, -1, -1, 0) #define drive_ufs0_rst_n_pv1 DRV_PINGROUP_ENTRY_Y(0x11004, 12, 5, 24, 5, -1, -1, -1, -1, 0) #define drive_ufs0_ref_clk_pv0 DRV_PINGROUP_ENTRY_Y(0x1100c, 12, 5, 24, 5, -1, -1, -1, -1, 0) -#define drive_batt_oc_paa4 DRV_PINGROUP_ENTRY_Y(0x1024, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_bootv_ctl_n_paa0 DRV_PINGROUP_ENTRY_Y(0x102c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_vcomp_alert_paa2 DRV_PINGROUP_ENTRY_Y(0x105c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_hdmi_cec_pbb0 DRV_PINGROUP_ENTRY_Y(0x1064, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_touch_clk_pdd3 DRV_PINGROUP_ENTRY_Y(0x106c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart3_rx_pcc6 DRV_PINGROUP_ENTRY_Y(0x1074, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart3_tx_pcc5 DRV_PINGROUP_ENTRY_Y(0x107c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen8_i2c_sda_pdd2 DRV_PINGROUP_ENTRY_Y(0x1084, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen8_i2c_scl_pdd1 DRV_PINGROUP_ENTRY_Y(0x108c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_mosi_pcc2 DRV_PINGROUP_ENTRY_Y(0x1094, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen2_i2c_scl_pcc7 DRV_PINGROUP_ENTRY_Y(0x109c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_cs0_pcc3 DRV_PINGROUP_ENTRY_Y(0x10a4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_gen2_i2c_sda_pdd0 DRV_PINGROUP_ENTRY_Y(0x10ac, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_sck_pcc0 DRV_PINGROUP_ENTRY_Y(0x10b4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_miso_pcc1 DRV_PINGROUP_ENTRY_Y(0x10bc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio49_pee2 DRV_PINGROUP_ENTRY_Y(0x10c4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio50_pee4 DRV_PINGROUP_ENTRY_Y(0x10cc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio82_pee3 DRV_PINGROUP_ENTRY_Y(0x10d4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio71_pff2 DRV_PINGROUP_ENTRY_Y(0x10dc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio76_pff7 DRV_PINGROUP_ENTRY_Y(0x10e4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio74_pff5 DRV_PINGROUP_ENTRY_Y(0x10ec, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio00_paa1 DRV_PINGROUP_ENTRY_Y(0x10f4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio19_pdd6 DRV_PINGROUP_ENTRY_Y(0x10fc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio86_phh3 DRV_PINGROUP_ENTRY_Y(0x1104, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio72_pff3 DRV_PINGROUP_ENTRY_Y(0x110c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio77_pgg0 DRV_PINGROUP_ENTRY_Y(0x1114, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio80_pff6 DRV_PINGROUP_ENTRY_Y(0x111c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio84_pgg1 DRV_PINGROUP_ENTRY_Y(0x1124, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio83_pee5 DRV_PINGROUP_ENTRY_Y(0x112c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio73_pff4 DRV_PINGROUP_ENTRY_Y(0x1134, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio70_pff1 DRV_PINGROUP_ENTRY_Y(0x113c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio04_paa5 DRV_PINGROUP_ENTRY_Y(0x1144, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio85_pgg6 DRV_PINGROUP_ENTRY_Y(0x114c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio69_pff0 DRV_PINGROUP_ENTRY_Y(0x1154, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio25_paa6 DRV_PINGROUP_ENTRY_Y(0x115c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_soc_gpio26_paa7 DRV_PINGROUP_ENTRY_Y(0x1164, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_tx_pgg7 DRV_PINGROUP_ENTRY_Y(0x116c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_rx_phh0 DRV_PINGROUP_ENTRY_Y(0x1174, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_tx_pgg2 DRV_PINGROUP_ENTRY_Y(0x117c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_rx_pgg3 DRV_PINGROUP_ENTRY_Y(0x1184, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_cts_pgg5 DRV_PINGROUP_ENTRY_Y(0x118c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart2_rts_pgg4 DRV_PINGROUP_ENTRY_Y(0x1194, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_cts_phh2 DRV_PINGROUP_ENTRY_Y(0x119c, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_uart5_rts_phh1 DRV_PINGROUP_ENTRY_Y(0x11a4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm7_pee1 DRV_PINGROUP_ENTRY_Y(0x11ac, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm2_pdd7 DRV_PINGROUP_ENTRY_Y(0x11b4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm3_pee0 DRV_PINGROUP_ENTRY_Y(0x11bc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_pwm1_paa3 DRV_PINGROUP_ENTRY_Y(0x11c4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_spi2_cs1_pcc4 DRV_PINGROUP_ENTRY_Y(0x11cc, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_dmic1_clk_pdd4 DRV_PINGROUP_ENTRY_Y(0x11d4, 12, 5, 20, 5, -1, -1, -1, -1, 1) -#define drive_dmic1_dat_pdd5 DRV_PINGROUP_ENTRY_Y(0x11dc, 12, 5, 20, 5, -1, -1, -1, -1, 1) +#define drive_batt_oc_paa4 DRV_PINGROUP_ENTRY_Y(0x1024, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_bootv_ctl_n_paa0 DRV_PINGROUP_ENTRY_Y(0x102c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_vcomp_alert_paa2 DRV_PINGROUP_ENTRY_Y(0x105c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_hdmi_cec_pbb0 DRV_PINGROUP_ENTRY_Y(0x1064, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_touch_clk_pdd3 DRV_PINGROUP_ENTRY_Y(0x106c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart3_rx_pcc6 DRV_PINGROUP_ENTRY_Y(0x1074, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart3_tx_pcc5 DRV_PINGROUP_ENTRY_Y(0x107c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen8_i2c_sda_pdd2 DRV_PINGROUP_ENTRY_Y(0x1084, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen8_i2c_scl_pdd1 DRV_PINGROUP_ENTRY_Y(0x108c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_mosi_pcc2 DRV_PINGROUP_ENTRY_Y(0x1094, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen2_i2c_scl_pcc7 DRV_PINGROUP_ENTRY_Y(0x109c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_cs0_pcc3 DRV_PINGROUP_ENTRY_Y(0x10a4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_gen2_i2c_sda_pdd0 DRV_PINGROUP_ENTRY_Y(0x10ac, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_sck_pcc0 DRV_PINGROUP_ENTRY_Y(0x10b4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_miso_pcc1 DRV_PINGROUP_ENTRY_Y(0x10bc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio49_pee2 DRV_PINGROUP_ENTRY_Y(0x10c4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio50_pee4 DRV_PINGROUP_ENTRY_Y(0x10cc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio82_pee3 DRV_PINGROUP_ENTRY_Y(0x10d4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio71_pff2 DRV_PINGROUP_ENTRY_Y(0x10dc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio76_pff7 DRV_PINGROUP_ENTRY_Y(0x10e4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio74_pff5 DRV_PINGROUP_ENTRY_Y(0x10ec, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio00_paa1 DRV_PINGROUP_ENTRY_Y(0x10f4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio19_pdd6 DRV_PINGROUP_ENTRY_Y(0x10fc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio86_phh3 DRV_PINGROUP_ENTRY_Y(0x1104, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio72_pff3 DRV_PINGROUP_ENTRY_Y(0x110c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio77_pgg0 DRV_PINGROUP_ENTRY_Y(0x1114, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio80_pff6 DRV_PINGROUP_ENTRY_Y(0x111c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio84_pgg1 DRV_PINGROUP_ENTRY_Y(0x1124, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio83_pee5 DRV_PINGROUP_ENTRY_Y(0x112c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio73_pff4 DRV_PINGROUP_ENTRY_Y(0x1134, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio70_pff1 DRV_PINGROUP_ENTRY_Y(0x113c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio04_paa5 DRV_PINGROUP_ENTRY_Y(0x1144, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio85_pgg6 DRV_PINGROUP_ENTRY_Y(0x114c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio69_pff0 DRV_PINGROUP_ENTRY_Y(0x1154, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio25_paa6 DRV_PINGROUP_ENTRY_Y(0x115c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_soc_gpio26_paa7 DRV_PINGROUP_ENTRY_Y(0x1164, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_tx_pgg7 DRV_PINGROUP_ENTRY_Y(0x116c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_rx_phh0 DRV_PINGROUP_ENTRY_Y(0x1174, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_tx_pgg2 DRV_PINGROUP_ENTRY_Y(0x117c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_rx_pgg3 DRV_PINGROUP_ENTRY_Y(0x1184, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_cts_pgg5 DRV_PINGROUP_ENTRY_Y(0x118c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart2_rts_pgg4 DRV_PINGROUP_ENTRY_Y(0x1194, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_cts_phh2 DRV_PINGROUP_ENTRY_Y(0x119c, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_uart5_rts_phh1 DRV_PINGROUP_ENTRY_Y(0x11a4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm7_pee1 DRV_PINGROUP_ENTRY_Y(0x11ac, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm2_pdd7 DRV_PINGROUP_ENTRY_Y(0x11b4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm3_pee0 DRV_PINGROUP_ENTRY_Y(0x11bc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_pwm1_paa3 DRV_PINGROUP_ENTRY_Y(0x11c4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_spi2_cs1_pcc4 DRV_PINGROUP_ENTRY_Y(0x11cc, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_dmic1_clk_pdd4 DRV_PINGROUP_ENTRY_Y(0x11d4, 12, 5, 20, 5, -1, -1, -1, -1, 0) +#define drive_dmic1_dat_pdd5 DRV_PINGROUP_ENTRY_Y(0x11dc, 12, 5, 20, 5, -1, -1, -1, -1, 0) #define drive_sdmmc1_comp DRV_PINGROUP_ENTRY_N @@ -1961,57 +1961,57 @@ static const struct tegra_pingroup tegra238_groups[] = { }; static const struct tegra_pingroup tegra238_aon_groups[] = { - PINGROUP(bootv_ctl_n_paa0, RSVD0, RSVD1, RSVD2, RSVD3, 0x1028, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio00_paa1, RSVD0, RSVD1, RSVD2, RSVD3, 0x10f0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(vcomp_alert_paa2, SOC_THERM_OC1, RSVD1, RSVD2, RSVD3, 0x1058, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm1_paa3, GP_PWM1, RSVD1, RSVD2, RSVD3, 0x11c0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(batt_oc_paa4, SOC_THERM_OC2, RSVD1, RSVD2, RSVD3, 0x1020, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio04_paa5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1140, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio25_paa6, RSVD0, RSVD1, RSVD2, RSVD3, 0x1158, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio26_paa7, RSVD0, SOC_THERM_OC3, RSVD2, RSVD3, 0x1160, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(hdmi_cec_pbb0, HDMI_CEC, RSVD1, RSVD2, RSVD3, 0x1060, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_sck_pcc0, SPI2_SCK, RSVD1, RSVD2, RSVD3, 0x10b0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_miso_pcc1, SPI2_DIN, RSVD1, RSVD2, RSVD3, 0x10b8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_mosi_pcc2, SPI2_DOUT, RSVD1, RSVD2, RSVD3, 0x1090, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_cs0_pcc3, SPI2_CS0, RSVD1, RSVD2, RSVD3, 0x10a0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(spi2_cs1_pcc4, SPI2_CS1, RSVD1, RSVD2, RSVD3, 0x11c8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(uart3_tx_pcc5, UARTC_TXD, RSVD1, RSVD2, RSVD3, 0x1078, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart3_rx_pcc6, UARTC_RXD, RSVD1, RSVD2, RSVD3, 0x1070, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen2_i2c_scl_pcc7, I2C2_CLK, RSVD1, RSVD2, RSVD3, 0x1098, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen2_i2c_sda_pdd0, I2C2_DAT, RSVD1, RSVD2, RSVD3, 0x10a8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen8_i2c_scl_pdd1, I2C8_CLK, RSVD1, RSVD2, RSVD3, 0x1088, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(gen8_i2c_sda_pdd2, I2C8_DAT, RSVD1, RSVD2, RSVD3, 0x1080, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(touch_clk_pdd3, GP_PWM4, TOUCH_CLK, RSVD2, RSVD3, 0x1068, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(dmic1_clk_pdd4, DMIC1_CLK, RSVD1, DMIC5_CLK, RSVD3, 0x11d0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(dmic1_dat_pdd5, DMIC1_DAT, RSVD1, DMIC5_DAT, RSVD3, 0x11d8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio19_pdd6, RSVD0, WDT_RESET_OUTB, RSVD2, RSVD3, 0x10f8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio49_pee2, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio50_pee4, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c8, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio82_pee3, RSVD0, RSVD1, RSVD2, RSVD3, 0x10d0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio71_pff2, PPC_MODE_1, RSVD1, RSVD2, RSVD3, 0x10d8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio76_pff7, RSVD0, RSVD1, TSC_EDGE_OUT0, TSC_EDGE_OUT0A, 0x10e0, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio74_pff5, PPC_READY, PPC_I2C_DAT, RSVD2, RSVD3, 0x10e8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio86_phh3, RSVD0, SPI5_CS1, TSC_EDGE_OUT3, TSC_EDGE_OUT0D, 0x1100, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio72_pff3, PPC_MODE_2, RSVD1, RSVD2, RSVD3, 0x1108, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio77_pgg0, RSVD0, RSVD1, TSC_EDGE_OUT1, TSC_EDGE_OUT0B, 0x1110, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio80_pff6, RSVD0, PPC_RST_N, RSVD2, RSVD3, 0x1118, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio84_pgg1, RSVD0, RSVD1, TSC_EDGE_OUT2, TSC_EDGE_OUT0C, 0x1120, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio83_pee5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1128, 1, Y, -1, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio73_pff4, PPC_CC, PPC_I2C_CLK, RSVD2, RSVD3, 0x1130, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio70_pff1, PPC_MODE_0, RSVD1, RSVD2, RSVD3, 0x1138, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio85_pgg6, RSVD0, SPI4_CS1, RSVD2, RSVD3, 0x1148, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(soc_gpio69_pff0, PPC_INT_N, RSVD1, RSVD2, RSVD3, 0x1150, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_tx_pgg7, UARTE_TXD, SPI5_SCK, RSVD2, RSVD3, 0x1168, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_rx_phh0, UARTE_RXD, SPI5_MISO, RSVD2, RSVD3, 0x1170, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_tx_pgg2, UARTB_TXD, SPI4_SCK, RSVD2, RSVD3, 0x1178, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_rx_pgg3, UARTB_RXD, SPI4_MISO, RSVD2, RSVD3, 0x1180, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_cts_pgg5, UARTB_CTS, SPI4_CS0, RSVD2, RSVD3, 0x1188, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart2_rts_pgg4, UARTB_RTS, SPI4_MOSI, RSVD2, RSVD3, 0x1190, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_cts_phh2, UARTE_CTS, SPI5_CS0, RSVD2, RSVD3, 0x1198, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(uart5_rts_phh1, UARTE_RTS, SPI5_MOSI, RSVD2, RSVD3, 0x11a0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm2_pdd7, GP_PWM2, LED_BLINK, RSVD2, RSVD3, 0x11b0, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm3_pee0, GP_PWM3, RSVD1, RSVD2, RSVD3, 0x11b8, 1, Y, 5, 7, 6, 8, -1, 10, 12), - PINGROUP(pwm7_pee1, GP_PWM7, RSVD1, RSVD2, RSVD3, 0x11a8, 1, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(bootv_ctl_n_paa0, RSVD0, RSVD1, RSVD2, RSVD3, 0x1028, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio00_paa1, RSVD0, RSVD1, RSVD2, RSVD3, 0x10f0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(vcomp_alert_paa2, SOC_THERM_OC1, RSVD1, RSVD2, RSVD3, 0x1058, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm1_paa3, GP_PWM1, RSVD1, RSVD2, RSVD3, 0x11c0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(batt_oc_paa4, SOC_THERM_OC2, RSVD1, RSVD2, RSVD3, 0x1020, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio04_paa5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1140, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio25_paa6, RSVD0, RSVD1, RSVD2, RSVD3, 0x1158, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio26_paa7, RSVD0, SOC_THERM_OC3, RSVD2, RSVD3, 0x1160, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(hdmi_cec_pbb0, HDMI_CEC, RSVD1, RSVD2, RSVD3, 0x1060, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_sck_pcc0, SPI2_SCK, RSVD1, RSVD2, RSVD3, 0x10b0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_miso_pcc1, SPI2_DIN, RSVD1, RSVD2, RSVD3, 0x10b8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_mosi_pcc2, SPI2_DOUT, RSVD1, RSVD2, RSVD3, 0x1090, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_cs0_pcc3, SPI2_CS0, RSVD1, RSVD2, RSVD3, 0x10a0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(spi2_cs1_pcc4, SPI2_CS1, RSVD1, RSVD2, RSVD3, 0x11c8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(uart3_tx_pcc5, UARTC_TXD, RSVD1, RSVD2, RSVD3, 0x1078, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart3_rx_pcc6, UARTC_RXD, RSVD1, RSVD2, RSVD3, 0x1070, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen2_i2c_scl_pcc7, I2C2_CLK, RSVD1, RSVD2, RSVD3, 0x1098, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen2_i2c_sda_pdd0, I2C2_DAT, RSVD1, RSVD2, RSVD3, 0x10a8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen8_i2c_scl_pdd1, I2C8_CLK, RSVD1, RSVD2, RSVD3, 0x1088, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(gen8_i2c_sda_pdd2, I2C8_DAT, RSVD1, RSVD2, RSVD3, 0x1080, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(touch_clk_pdd3, GP_PWM4, TOUCH_CLK, RSVD2, RSVD3, 0x1068, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(dmic1_clk_pdd4, DMIC1_CLK, RSVD1, DMIC5_CLK, RSVD3, 0x11d0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(dmic1_dat_pdd5, DMIC1_DAT, RSVD1, DMIC5_DAT, RSVD3, 0x11d8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio19_pdd6, RSVD0, WDT_RESET_OUTB, RSVD2, RSVD3, 0x10f8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio49_pee2, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio50_pee4, RSVD0, RSVD1, RSVD2, RSVD3, 0x10c8, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio82_pee3, RSVD0, RSVD1, RSVD2, RSVD3, 0x10d0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio71_pff2, PPC_MODE_1, RSVD1, RSVD2, RSVD3, 0x10d8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio76_pff7, RSVD0, RSVD1, TSC_EDGE_OUT0, TSC_EDGE_OUT0A, 0x10e0, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio74_pff5, PPC_READY, PPC_I2C_DAT, RSVD2, RSVD3, 0x10e8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio86_phh3, RSVD0, SPI5_CS1, TSC_EDGE_OUT3, TSC_EDGE_OUT0D, 0x1100, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio72_pff3, PPC_MODE_2, RSVD1, RSVD2, RSVD3, 0x1108, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio77_pgg0, RSVD0, RSVD1, TSC_EDGE_OUT1, TSC_EDGE_OUT0B, 0x1110, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio80_pff6, RSVD0, PPC_RST_N, RSVD2, RSVD3, 0x1118, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio84_pgg1, RSVD0, RSVD1, TSC_EDGE_OUT2, TSC_EDGE_OUT0C, 0x1120, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio83_pee5, RSVD0, RSVD1, RSVD2, RSVD3, 0x1128, 0, Y, -1, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio73_pff4, PPC_CC, PPC_I2C_CLK, RSVD2, RSVD3, 0x1130, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio70_pff1, PPC_MODE_0, RSVD1, RSVD2, RSVD3, 0x1138, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio85_pgg6, RSVD0, SPI4_CS1, RSVD2, RSVD3, 0x1148, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(soc_gpio69_pff0, PPC_INT_N, RSVD1, RSVD2, RSVD3, 0x1150, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_tx_pgg7, UARTE_TXD, SPI5_SCK, RSVD2, RSVD3, 0x1168, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_rx_phh0, UARTE_RXD, SPI5_MISO, RSVD2, RSVD3, 0x1170, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_tx_pgg2, UARTB_TXD, SPI4_SCK, RSVD2, RSVD3, 0x1178, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_rx_pgg3, UARTB_RXD, SPI4_MISO, RSVD2, RSVD3, 0x1180, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_cts_pgg5, UARTB_CTS, SPI4_CS0, RSVD2, RSVD3, 0x1188, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart2_rts_pgg4, UARTB_RTS, SPI4_MOSI, RSVD2, RSVD3, 0x1190, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_cts_phh2, UARTE_CTS, SPI5_CS0, RSVD2, RSVD3, 0x1198, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(uart5_rts_phh1, UARTE_RTS, SPI5_MOSI, RSVD2, RSVD3, 0x11a0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm2_pdd7, GP_PWM2, LED_BLINK, RSVD2, RSVD3, 0x11b0, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm3_pee0, GP_PWM3, RSVD1, RSVD2, RSVD3, 0x11b8, 0, Y, 5, 7, 6, 8, -1, 10, 12), + PINGROUP(pwm7_pee1, GP_PWM7, RSVD1, RSVD2, RSVD3, 0x11a8, 0, Y, 5, 7, 6, 8, -1, 10, 12), }; static const struct tegra_pinctrl_soc_data tegra238_pinctrl_aon = { diff --git a/drivers/remoteproc/qcom_q6v5_adsp.c b/drivers/remoteproc/qcom_q6v5_adsp.c index 39654206781d..2e5fb5954fa9 100644 --- a/drivers/remoteproc/qcom_q6v5_adsp.c +++ b/drivers/remoteproc/qcom_q6v5_adsp.c @@ -104,6 +104,7 @@ struct qcom_adsp { struct completion stop_done; phys_addr_t mem_phys; + unsigned long iova; phys_addr_t mem_reloc; void __iomem *mem_region; size_t mem_size; @@ -333,7 +334,7 @@ static void adsp_unmap_carveout(struct rproc *rproc) struct qcom_adsp *adsp = rproc->priv; if (adsp->has_iommu) - iommu_unmap(rproc->domain, adsp->mem_phys, adsp->mem_size); + iommu_unmap(rproc->domain, adsp->iova, adsp->mem_size); } static int adsp_map_carveout(struct rproc *rproc) @@ -341,7 +342,6 @@ static int adsp_map_carveout(struct rproc *rproc) struct qcom_adsp *adsp = rproc->priv; struct of_phandle_args args; long long sid; - unsigned long iova; int ret; if (!adsp->has_iommu) @@ -358,9 +358,9 @@ static int adsp_map_carveout(struct rproc *rproc) of_node_put(args.np); /* Add SID configuration for ADSP Firmware to SMMU */ - iova = adsp->mem_phys | (sid << 32); + adsp->iova = adsp->mem_phys | (sid << 32); - ret = iommu_map(rproc->domain, iova, adsp->mem_phys, + ret = iommu_map(rproc->domain, adsp->iova, adsp->mem_phys, adsp->mem_size, IOMMU_READ | IOMMU_WRITE, GFP_KERNEL); if (ret) { diff --git a/drivers/remoteproc/qcom_q6v5_mss.c b/drivers/remoteproc/qcom_q6v5_mss.c index bef198b9ee63..2f71ed2feff6 100644 --- a/drivers/remoteproc/qcom_q6v5_mss.c +++ b/drivers/remoteproc/qcom_q6v5_mss.c @@ -2079,7 +2079,16 @@ static int q6v5_probe(struct platform_device *pdev) if (!desc) return -EINVAL; - if (desc->need_mem_protection && !qcom_pas_is_available()) + /* + * Memory protection is done through qcom_scm_assign_mem(), which needs + * SCM but not PAS. Only the memory setup path issues PAS calls, so + * requiring PAS for every need_mem_protection platform prevents the + * modem from probing at all on TZ firmware that offers no PAS. + */ + if (desc->need_mem_protection && !qcom_scm_is_available()) + return -EPROBE_DEFER; + + if (desc->need_pas_mem_setup && !qcom_pas_is_available()) return -EPROBE_DEFER; mba_image = desc->hexagon_mba_image; diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index ca8e61254c44..a005546c265d 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -405,6 +405,7 @@ static int qcom_pas_stop(struct rproc *rproc) { struct qcom_pas *pas = rproc->priv; int handover; + int dtb_ret; int ret; ret = qcom_q6v5_request_stop(&pas->q6v5, pas->sysmon); @@ -419,9 +420,12 @@ static int qcom_pas_stop(struct rproc *rproc) dev_err(pas->dev, "failed to shutdown: %d\n", ret); if (pas->dtb_pas_id) { - ret = qcom_pas_shutdown(pas->dtb_pas_id); - if (ret) - dev_err(pas->dev, "failed to shutdown dtb: %d\n", ret); + dtb_ret = qcom_pas_shutdown(pas->dtb_pas_id); + if (dtb_ret) + dev_err(pas->dev, "failed to shutdown dtb: %d\n", dtb_ret); + + if (!ret && dtb_ret) + ret = dtb_ret; qcom_pas_unmap_carveout(rproc, pas->dtb_mem_phys, pas->dtb_mem_size); } @@ -524,7 +528,6 @@ static int qcom_pas_attach(struct rproc *rproc) int ret; pas->q6v5.handover_issued = true; - enable_irq(pas->q6v5.handover_irq); pas->q6v5.running = true; ret = irq_get_irqchip_state(pas->q6v5.fatal_irq, @@ -570,7 +573,6 @@ unroll_attach: pas->rproc->state = RPROC_OFFLINE; ret = -EINVAL; disable_running: - disable_irq(pas->q6v5.handover_irq); pas->q6v5.running = false; return ret; diff --git a/drivers/s390/block/dasd_3990_erp.c b/drivers/s390/block/dasd_3990_erp.c index 736459477c19..121cb9ae5c67 100644 --- a/drivers/s390/block/dasd_3990_erp.c +++ b/drivers/s390/block/dasd_3990_erp.c @@ -103,7 +103,7 @@ dasd_3990_erp_int_req(struct dasd_ccw_req * erp) /* first time set initial retry counter and erp_function */ /* and retry once without blocking queue */ - /* (this enables easier enqueing of the cqr) */ + /* (this enables easier enqueuing of the cqr) */ if (erp->function != dasd_3990_erp_int_req) { erp->retries = 256; @@ -302,7 +302,7 @@ dasd_3990_erp_action_4(struct dasd_ccw_req * erp, char *sense) /* first time set initial retry counter and erp_function */ /* and retry once without waiting for state change pending */ - /* interrupt (this enables easier enqueing of the cqr) */ + /* interrupt (this enables easier enqueuing of the cqr) */ if (erp->function != dasd_3990_erp_action_4) { DBF_DEV_EVENT(DBF_INFO, device, "%s", @@ -1078,7 +1078,7 @@ dasd_3990_erp_bus_out(struct dasd_ccw_req * erp) /* first time set initial retry counter and erp_function */ /* and retry once without blocking queue */ - /* (this enables easier enqueing of the cqr) */ + /* (this enables easier enqueuing of the cqr) */ if (erp->function != dasd_3990_erp_bus_out) { erp->retries = 256; erp->function = dasd_3990_erp_bus_out; diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 8d976dd58a6c..9d1b11020b4e 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -2060,7 +2060,7 @@ dasd_eckd_psf_ssc(struct dasd_device *device, int enable_pav, } /* - * Valide storage server of current device. + * Valid storage server of current device. */ static int dasd_eckd_validate_server(struct dasd_device *device, unsigned long flags) @@ -5672,7 +5672,7 @@ static struct dasd_ccw_req *dasd_eckd_build_cp_raw(struct dasd_device *startdev, char *dst; /* - * raw track access needs to be mutiple of 64k and on 64k boundary + * raw track access needs to be multiple of 64k and on 64k boundary * For read requests we can fix an incorrect alignment by padding * the request with dummy pages. */ diff --git a/drivers/s390/char/raw3270.c b/drivers/s390/char/raw3270.c index aa9c4d81225c..6861bf27d15f 100644 --- a/drivers/s390/char/raw3270.c +++ b/drivers/s390/char/raw3270.c @@ -420,7 +420,7 @@ struct raw3270_ua { /* Query Reply structure for Usable Area */ char flags0; char flags1; short w; /* Width of usable area */ - short h; /* Heigth of usavle area */ + short h; /* Height of usavle area */ char units; /* 0x00:in; 0x01:mm */ int xr; int yr; diff --git a/drivers/s390/char/vmlogrdr.c b/drivers/s390/char/vmlogrdr.c index 383e7e2bd69f..005735a89920 100644 --- a/drivers/s390/char/vmlogrdr.c +++ b/drivers/s390/char/vmlogrdr.c @@ -444,7 +444,7 @@ static int vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv) spin_unlock_bh(&priv->priv_lock); /* An rc of 5 indicates that the record was bigger than * the buffer, which is OK for us. A 9 indicates that the - * record was purged befor we could receive it. + * record was purged before we could receive it. */ if (rc == 5) rc = 0; diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c index c890f21a82ce..eaf0527bff6c 100644 --- a/drivers/s390/cio/chp.c +++ b/drivers/s390/cio/chp.c @@ -78,6 +78,9 @@ u8 chp_get_sch_opm(struct subchannel *sch) int opm; int i; + if (!sch->schib.pmcw.dnv) + return 0; + opm = 0; chp_id_init(&chpid); for (i = 0; i < 8; i++) { diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c index 70dc8cc76594..e1c62eb60cca 100644 --- a/drivers/s390/cio/cio.c +++ b/drivers/s390/cio/cio.c @@ -453,7 +453,8 @@ EXPORT_SYMBOL_GPL(cio_commit_config); /** * cio_update_schib - Perform stsch and update schib if subchannel is valid. * @sch: subchannel on which to perform stsch - * Return zero on success, -ENODEV otherwise. + * Return zero on success, -ENODEV if the subchannel is not operational, + * -EACCES if the subchannel has no valid device. */ int cio_update_schib(struct subchannel *sch) { @@ -462,10 +463,12 @@ int cio_update_schib(struct subchannel *sch) if (stsch(sch->schid, &schib)) return -ENODEV; - memcpy(&sch->schib, &schib, sizeof(schib)); - - if (!css_sch_is_valid(&schib)) + if (!css_sch_is_valid(&schib)) { + memset(&sch->schib, 0, sizeof(sch->schib)); return -EACCES; + } + + memcpy(&sch->schib, &schib, sizeof(schib)); return 0; } diff --git a/drivers/s390/cio/cio.h b/drivers/s390/cio/cio.h index bad142c536e1..6d28a62bc67a 100644 --- a/drivers/s390/cio/cio.h +++ b/drivers/s390/cio/cio.h @@ -7,6 +7,7 @@ #include <linux/mod_devicetable.h> #include <asm/chpid.h> #include <asm/cio.h> +#include <asm/dma-types.h> #include <asm/fcx.h> #include <asm/schid.h> #include <asm/tpi.h> @@ -49,7 +50,7 @@ struct pmcw { /* Target SCHIB configuration. */ struct schib_config { - u64 mba; + dma64_t mba; u32 intparm; u16 mbi; u32 isc:3; @@ -66,7 +67,7 @@ struct schib_config { struct schib { struct pmcw pmcw; /* path management control word */ union scsw scsw; /* subchannel status word */ - __u64 mba; /* measurement block address */ + dma64_t mba; /* measurement block address */ __u8 mda[4]; /* model dependent area */ } __attribute__ ((packed,aligned(4))); diff --git a/drivers/s390/cio/cmf.c b/drivers/s390/cio/cmf.c index 92ab3d546fe4..66b14fedbd18 100644 --- a/drivers/s390/cio/cmf.c +++ b/drivers/s390/cio/cmf.c @@ -183,7 +183,7 @@ static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc, sch->config.mbfc = mbfc; /* address can be either a block address or a block index */ if (mbfc) - sch->config.mba = address; + sch->config.mba = address ? virt_to_dma64((void *)address) : 0; else sch->config.mbi = address; diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index fb591118ecb2..68dd4a62975d 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -922,7 +922,7 @@ static int ccw_device_move_to_sch(struct ccw_device *cdev, if (!sch_is_pseudo_sch(old_sch)) { spin_lock_irq(&old_sch->lock); - old_enabled = old_sch->schib.pmcw.ena; + old_enabled = old_sch->schib.pmcw.dnv && old_sch->schib.pmcw.ena; rc = 0; if (old_enabled) rc = cio_disable_subchannel(old_sch); @@ -941,7 +941,7 @@ static int ccw_device_move_to_sch(struct ccw_device *cdev, CIO_MSG_EVENT(0, "device_move(0.%x.%04x,0.%x.%04x)=%d\n", cdev->private->dev_id.ssid, cdev->private->dev_id.devno, sch->schid.ssid, - sch->schib.pmcw.dev, rc); + sch->schid.sch_no, rc); if (old_enabled) { /* Try to re-enable the old subchannel. */ spin_lock_irq(&old_sch->lock); @@ -1207,7 +1207,7 @@ static void io_subchannel_quiesce(struct subchannel *sch) cdev = sch_get_cdev(sch); if (cio_is_console(sch->schid)) goto out_unlock; - if (!sch->schib.pmcw.ena) + if (!sch->schib.pmcw.dnv || !sch->schib.pmcw.ena) goto out_unlock; ret = cio_disable_subchannel(sch); if (ret != -EBUSY) @@ -1254,7 +1254,8 @@ static int recovery_check(struct device *dev, void *data) switch (cdev->private->state) { case DEV_STATE_ONLINE: sch = to_subchannel(cdev->dev.parent); - if ((sch->schib.pmcw.pam & sch->opm) == sch->vpm) + if (sch->schib.pmcw.dnv && + (sch->schib.pmcw.pam & sch->opm) == sch->vpm) break; fallthrough; case DEV_STATE_DISCONNECTED: diff --git a/drivers/s390/cio/device_fsm.c b/drivers/s390/cio/device_fsm.c index ab419d40a8a7..b5686c25c83c 100644 --- a/drivers/s390/cio/device_fsm.c +++ b/drivers/s390/cio/device_fsm.c @@ -170,6 +170,9 @@ __recover_lost_chpids(struct subchannel *sch, int old_lpm) int mask, i; struct chp_id chpid; + if (!sch->schib.pmcw.dnv) + return; + chp_id_init(&chpid); for (i = 0; i<8; i++) { mask = 0x80 >> i; diff --git a/drivers/s390/cio/device_ops.c b/drivers/s390/cio/device_ops.c index 61c07b4a0fe8..1f7e83fd5087 100644 --- a/drivers/s390/cio/device_ops.c +++ b/drivers/s390/cio/device_ops.c @@ -142,6 +142,8 @@ int ccw_device_clear(struct ccw_device *cdev, unsigned long intparm) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -198,6 +200,8 @@ int ccw_device_start_timeout_key(struct ccw_device *cdev, struct ccw1 *cpa, if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -379,6 +383,8 @@ int ccw_device_halt(struct ccw_device *cdev, unsigned long intparm) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -413,6 +419,8 @@ int ccw_device_resume(struct ccw_device *cdev) if (!cdev || !cdev->dev.parent) return -ENODEV; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_NOT_OPER) @@ -482,6 +490,8 @@ struct channel_path_desc_fmt0 *ccw_device_get_chp_desc(struct ccw_device *cdev, struct chp_id chpid; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return NULL; chp_id_init(&chpid); chpid.id = sch->schib.pmcw.chpid[chp_idx]; return chp_get_chp_desc(chpid); @@ -502,9 +512,13 @@ u8 *ccw_device_get_util_str(struct ccw_device *cdev, int chp_idx) struct chp_id chpid; u8 *util_str; + if (!sch->schib.pmcw.dnv) + return NULL; chp_id_init(&chpid); chpid.id = sch->schib.pmcw.chpid[chp_idx]; chp = chpid_to_chp(chpid); + if (!chp) + return NULL; util_str = kmalloc(sizeof(chp->desc_fmt3.util_str), GFP_KERNEL); if (!util_str) @@ -548,6 +562,8 @@ int ccw_device_tm_start_timeout_key(struct ccw_device *cdev, struct tcw *tcw, int rc; sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state == DEV_STATE_VERIFY) { @@ -652,6 +668,9 @@ int ccw_device_get_mdc(struct ccw_device *cdev, u8 mask) struct chp_id chpid; int mdc = 0, i; + if (!sch->schib.pmcw.dnv) + return 0; + /* Adjust requested path mask to excluded varied off paths. */ if (mask) mask &= sch->lpm; @@ -694,6 +713,8 @@ int ccw_device_tm_intrg(struct ccw_device *cdev) { struct subchannel *sch = to_subchannel(cdev->dev.parent); + if (!sch->schib.pmcw.dnv) + return -ENODEV; if (!sch->schib.pmcw.ena) return -EINVAL; if (cdev->private->state != DEV_STATE_ONLINE) @@ -786,6 +807,8 @@ int ccw_device_get_chpid(struct ccw_device *cdev, int chp_idx, u8 *chpid) if ((chp_idx < 0) || (chp_idx > 7)) return -EINVAL; + if (!sch->schib.pmcw.dnv) + return -ENODEV; mask = 0x80 >> chp_idx; if (!(sch->schib.pmcw.pim & mask)) return -ENODEV; diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c index 5fd94e9d5c61..9a000b0231d6 100644 --- a/drivers/s390/cio/vfio_ccw_fsm.c +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -399,7 +399,7 @@ static void fsm_close(struct vfio_ccw_private *private, spin_lock_irq(&sch->lock); - if (!sch->schib.pmcw.ena) + if (!sch->schib.pmcw.dnv || !sch->schib.pmcw.ena) goto err_unlock; ret = cio_disable_subchannel(sch); diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c index 940c0ff668be..4db878c18f41 100644 --- a/drivers/s390/crypto/vfio_ap_ops.c +++ b/drivers/s390/crypto/vfio_ap_ops.c @@ -277,9 +277,9 @@ static void vfio_ap_free_aqic_resources(struct vfio_ap_queue *q) { if (!q) return; - if (q->saved_isc != VFIO_AP_ISC_INVALID && - !WARN_ON(!(q->matrix_mdev && q->matrix_mdev->kvm))) { - kvm_s390_gisc_unregister(q->matrix_mdev->kvm, q->saved_isc); + if (q->saved_isc != VFIO_AP_ISC_INVALID) { + if (!WARN_ON(!q->matrix_mdev) && q->matrix_mdev->kvm) + kvm_s390_gisc_unregister(q->matrix_mdev->kvm, q->saved_isc); q->saved_isc = VFIO_AP_ISC_INVALID; } if (q->saved_iova && !WARN_ON(!q->matrix_mdev)) { @@ -1935,6 +1935,8 @@ static int apq_status_check(int apqn, struct ap_queue_status *status) * a value indicating a reset needs to be performed again. */ return -EAGAIN; + case AP_RESPONSE_Q_NOT_AVAIL: + return -ENODEV; default: WARN(true, "failed to verify reset of queue %02x.%04x: TAPQ rc=%u\n", @@ -1961,6 +1963,10 @@ static void apq_reset_check(struct work_struct *reset_work) ret = apq_status_check(q->apqn, &status); if (ret == -EIO) return; + if (ret == -ENODEV) { + vfio_ap_free_aqic_resources(q); + return; + } if (ret == -EBUSY) { pr_notice_ratelimited(WAIT_MSG, elapsed, AP_QID_CARD(q->apqn), @@ -2004,6 +2010,7 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q) break; case AP_RESPONSE_DECONFIGURED: case AP_RESPONSE_CHECKSTOPPED: + case AP_RESPONSE_Q_NOT_AVAIL: vfio_ap_free_aqic_resources(q); break; default: @@ -2528,12 +2535,15 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev) /* * If the queue is not in the host's AP configuration, then resetting * it will fail with response code 01, (APQN not valid); so, let's make - * sure it is in the host's config. + * sure it is in the host's config. If it is not, free the KVM GISC + * resources. */ if (test_bit_inv(apid, (unsigned long *)matrix_dev->info.apm) && test_bit_inv(apqi, (unsigned long *)matrix_dev->info.aqm)) { vfio_ap_mdev_reset_queue(q); flush_work(&q->reset_work); + } else { + vfio_ap_free_aqic_resources(q); } done: diff --git a/drivers/scsi/leapraid/leapraid_func.h b/drivers/scsi/leapraid/leapraid_func.h index 4c0b9ca728d8..e8a0815bf95f 100644 --- a/drivers/scsi/leapraid/leapraid_func.h +++ b/drivers/scsi/leapraid/leapraid_func.h @@ -554,7 +554,6 @@ struct leapraid_fw_evt_work { /** * struct leapraid_fw_evt_struct - Firmware event handling structure * - * @fw_evt_name: Name of the firmware event. * @fw_evt_thread: Workqueue used for processing firmware events. * @fw_evt_lock: Spinlock protecting access to the firmware event list. * @fw_evt_list: Linked list of pending firmware events. @@ -565,7 +564,6 @@ struct leapraid_fw_evt_work { */ struct leapraid_fw_evt_struct { u32 leapraid_evt_masks[4]; - char fw_evt_name[48]; struct workqueue_struct *fw_evt_thread; spinlock_t fw_evt_lock; /* protects firmware event */ struct list_head fw_evt_list; diff --git a/drivers/scsi/leapraid/leapraid_os.c b/drivers/scsi/leapraid/leapraid_os.c index ee3242779dfd..507f11862276 100644 --- a/drivers/scsi/leapraid/leapraid_os.c +++ b/drivers/scsi/leapraid/leapraid_os.c @@ -2054,12 +2054,10 @@ static int leapraid_probe(struct pci_dev *pdev, const struct pci_device_id *id) shost->transportt = leapraid_transport_template; shost->unique_id = adapter->adapter_attr.id; - snprintf(adapter->fw_evt_s.fw_evt_name, - sizeof(adapter->fw_evt_s.fw_evt_name), - "fw_event_%s%d", LEAPRAID_DRIVER_NAME, - adapter->adapter_attr.id); adapter->fw_evt_s.fw_evt_thread = - alloc_ordered_workqueue(adapter->fw_evt_s.fw_evt_name, 0); + alloc_ordered_workqueue("fw_event_%s%d", 0, + LEAPRAID_DRIVER_NAME, + adapter->adapter_attr.id); if (!adapter->fw_evt_s.fw_evt_thread) { dev_err(&adapter->pdev->dev, "%s: Failed to create fw event workqueue\n", __func__); diff --git a/drivers/scsi/libiscsi_tcp.c b/drivers/scsi/libiscsi_tcp.c index 7223bb18b048..d35f93451ee9 100644 --- a/drivers/scsi/libiscsi_tcp.c +++ b/drivers/scsi/libiscsi_tcp.c @@ -480,6 +480,9 @@ static int iscsi_tcp_data_in(struct iscsi_conn *conn, struct iscsi_task *task) int datasn = be32_to_cpu(rhdr->datasn); unsigned total_in_length = task->sc->sdb.length; + if (task->sc->sc_data_direction != DMA_FROM_DEVICE) + return ISCSI_ERR_PROTO; + /* * lib iscsi will update this in the completion handling if there * is status. diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c b/drivers/scsi/megaraid/megaraid_sas_base.c index b95f187297ae..4f9a53769966 100644 --- a/drivers/scsi/megaraid/megaraid_sas_base.c +++ b/drivers/scsi/megaraid/megaraid_sas_base.c @@ -7886,7 +7886,9 @@ megasas_resume(struct device *dev) goto fail_init_mfi; } - if (megasas_get_ctrl_info(instance) != DCMD_SUCCESS) + scoped_guard(mutex, &instance->reset_mutex) + rval = megasas_get_ctrl_info(instance); + if (rval != DCMD_SUCCESS) goto fail_init_mfi; tasklet_init(&instance->isr_tasklet, instance->instancet->tasklet, diff --git a/drivers/scsi/scsi_devinfo.c b/drivers/scsi/scsi_devinfo.c index 15ffbe93ac72..88a911b35c94 100644 --- a/drivers/scsi/scsi_devinfo.c +++ b/drivers/scsi/scsi_devinfo.c @@ -161,7 +161,7 @@ static struct { {"DGC", "DISK", NULL, BLIST_SPARSELUN}, /* EMC CLARiiON, no storage on LUN 0 */ {"EMC", "Invista", "*", BLIST_SPARSELUN | BLIST_LARGELUN}, {"EMC", "SYMMETRIX", NULL, BLIST_SPARSELUN | BLIST_LARGELUN | - BLIST_REPORTLUN2 | BLIST_RETRY_ITF}, + BLIST_REPORTLUN2 | BLIST_RETRY_ITF | BLIST_SKIP_IO_HINTS}, {"EMULEX", "MD21/S2 ESDI", NULL, BLIST_SINGLELUN}, {"easyRAID", "16P", NULL, BLIST_NOREPORTLUN}, {"easyRAID", "X6P", NULL, BLIST_NOREPORTLUN}, diff --git a/drivers/scsi/sd_zbc.c b/drivers/scsi/sd_zbc.c index 56e455fb5add..456beaf2e769 100644 --- a/drivers/scsi/sd_zbc.c +++ b/drivers/scsi/sd_zbc.c @@ -589,7 +589,7 @@ int sd_zbc_revalidate_zones(struct scsi_disk *sdkp) int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim, u8 buf[SD_BUF_SIZE]) { - unsigned int nr_zones; + u64 nr_zones; u32 zone_blocks = 0; int ret; @@ -621,6 +621,12 @@ int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim, goto err; nr_zones = round_up(sdkp->capacity, zone_blocks) >> ilog2(zone_blocks); + if (nr_zones > INT_MAX) { + sd_printk(KERN_ERR, sdkp, "Too many zones (%llu)\n", + nr_zones); + ret = -EINVAL; + goto err; + } sdkp->early_zone_info.nr_zones = nr_zones; sdkp->early_zone_info.zone_blocks = zone_blocks; diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c index ea277c466d8d..4fa4377f0d42 100644 --- a/drivers/thermal/gov_step_wise.c +++ b/drivers/thermal/gov_step_wise.c @@ -65,14 +65,12 @@ static unsigned long get_target_state(struct thermal_instance *instance, min(instance->lower + 1, instance->upper), instance->upper); } else if (trend == THERMAL_TREND_DROPPING) { - if (cur_state <= instance->lower) - return THERMAL_NO_TARGET; - /* - * If 'throttle' is false, no mitigation is necessary, so - * request the lower state for this instance. + * If 'throttle' is false, no mitigation is necessary and + * passive polling is already deactivated, so clear this + * instance state by returning THERMAL_NO_TARGET. */ - return instance->lower; + return THERMAL_NO_TARGET; } return instance->target; diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 2ba244cf40ac..54f4e7d7de02 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -6817,11 +6817,17 @@ static void ufshcd_err_handling_prepare(struct ufs_hba *hba) } /* Wait for ongoing ufshcd_queuecommand() calls to finish. */ blk_mq_quiesce_tagset(&hba->host->tag_set); + /* + * Internal commands are submitted on the pseudo SCSI device. Let them + * through so that the error handler can recover the link. + */ + blk_mq_unquiesce_queue(hba->host->pseudo_sdev->request_queue); cancel_work_sync(&hba->eeh_work); } static void ufshcd_err_handling_unprepare(struct ufs_hba *hba) { + blk_mq_quiesce_queue_nowait(hba->host->pseudo_sdev->request_queue); blk_mq_unquiesce_tagset(&hba->host->tag_set); ufshcd_release(hba); if (ufshcd_is_clkscaling_supported(hba)) diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c index 5ac7afe75934..169dbc1a75fe 100644 --- a/drivers/ufs/host/ufshcd-pltfrm.c +++ b/drivers/ufs/host/ufshcd-pltfrm.c @@ -206,7 +206,11 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba) dev_dbg(hba->dev, "%s: failed to read lanes-per-direction, ret=%d\n", __func__, ret); - hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION; + /* Old R-Car S4 DTBs lack "lanes-per-direction = <1>" */ + if (of_device_is_compatible(dev->of_node, "renesas,r8a779f0-ufs")) + hba->lanes_per_direction = 1; + else + hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION; } } diff --git a/fs/autofs/inode.c b/fs/autofs/inode.c index 6b15a3717ba7..066c16f2ea56 100644 --- a/fs/autofs/inode.c +++ b/fs/autofs/inode.c @@ -51,6 +51,10 @@ void autofs_kill_sb(struct super_block *sb) if (sbi) { /* Free wait queues, close pipe */ autofs_catatonic_mode(sbi); + if (sbi->pipe) { + fput(sbi->pipe); + sbi->pipe = NULL; + } put_pid(sbi->oz_pgrp); } diff --git a/fs/binfmt_misc_bpf.c b/fs/binfmt_misc_bpf.c index 91576ff05911..a3e26e8a4027 100644 --- a/fs/binfmt_misc_bpf.c +++ b/fs/binfmt_misc_bpf.c @@ -141,8 +141,6 @@ __bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm, len = strnlen(path, path__sz); if (len == path__sz) return -EINVAL; - if (path[0] != '/') - return -EINVAL; if (len >= PATH_MAX) return -ENAMETOOLONG; @@ -150,6 +148,15 @@ __bpf_kfunc int bpf_binprm_set_interp(struct linux_binprm *bprm, if (!interp) return -ENOMEM; + /* + * The program may pass memory that is written to while this runs, + * so check the private copy and not the buffer it was made from. + */ + if (interp[0] != '/') { + kfree(interp); + return -EINVAL; + } + bm_bpf_stage_selection(bprm, interp, NULL); return 0; } @@ -176,6 +183,7 @@ __bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm, const char *name, size_t name__sz) { const struct binfmt_misc_interp *interp; + char buf[BINFMT_MISC_INTERP_NAME_MAX + 1]; size_t len; char *path; @@ -184,8 +192,20 @@ __bpf_kfunc int bpf_binprm_select_interp(struct linux_binprm *bprm, len = strnlen(name, name__sz); if (len == name__sz || !len) return -EINVAL; + /* No entry binds a longer name, so it cannot be found. */ + if (len > BINFMT_MISC_INTERP_NAME_MAX) + return -ENOENT; + + /* + * The program may pass memory that is written to while this runs, + * so look the name up in a private copy and check that instead. + */ + memcpy(buf, name, len); + buf[len] = '\0'; + if (!buf[0]) + return -EINVAL; - interp = binfmt_misc_find_interp(bprm->bpf_interps, name); + interp = binfmt_misc_find_interp(bprm->bpf_interps, buf); if (!interp) return -ENOENT; @@ -228,6 +248,15 @@ __bpf_kfunc int bpf_binprm_set_interp_arg(struct linux_binprm *bprm, if (!val) return -ENOMEM; + /* + * The program may pass memory that is written to while this runs, + * so check the private copy and not the buffer it was made from. + */ + if (!val[0]) { + kfree(val); + return -EINVAL; + } + kfree(bprm->bpf_interp_arg); bprm->bpf_interp_arg = val; return 0; diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index 6cb877267978..357a379ef92a 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -472,10 +472,6 @@ BTF_ID(func, bpf_lsm_inode_rmdir) BTF_ID(func, bpf_lsm_inode_setattr) BTF_ID(func, bpf_lsm_inode_setxattr) BTF_ID(func, bpf_lsm_inode_unlink) -#ifdef CONFIG_SECURITY_PATH -BTF_ID(func, bpf_lsm_path_unlink) -BTF_ID(func, bpf_lsm_path_rmdir) -#endif /* CONFIG_SECURITY_PATH */ BTF_SET_END(d_inode_locked_hooks) bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog) diff --git a/fs/dcache.c b/fs/dcache.c index 1b1a81f10da6..a66be85f9d01 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1916,6 +1916,10 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name) * be overwriting an internal NUL character */ dentry->d_shortname.string[DNAME_INLINE_LEN-1] = 0; + + /* Racy __d_lookup_rcu() walk may read past the NUL; harmless */ + kmsan_unpoison_memory(dentry->d_shortname.string, DNAME_INLINE_LEN); + if (unlikely(!name)) { name = &slash_name; dname = dentry->d_shortname.string; diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index e054e62919ec..a4d08bd3743b 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -368,6 +368,9 @@ static struct dentry *debugfs_start_creating(const char *name, if (!debugfs_enabled) return ERR_PTR(-EPERM); + if (IS_ERR(parent)) + return parent; + if (!debugfs_initialized()) { pr_err("Unable to create file '%s', debugfs is not initialized yet\n", name); @@ -376,9 +379,6 @@ static struct dentry *debugfs_start_creating(const char *name, pr_debug("creating file '%s'\n", name); - if (IS_ERR(parent)) - return parent; - error = simple_pin_fs(&debug_fs_type, &debugfs_mount, &debugfs_mount_count); if (error) { diff --git a/fs/exec.c b/fs/exec.c index 819643408e6d..a5269b5e00df 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -882,6 +882,7 @@ static int exec_mmap(struct linux_binprm *bprm) active_mm = tsk->active_mm; tsk->active_mm = mm; tsk->mm = mm; + sched_cache_exec_mmap(tsk, mm); mm_init_cid(mm, tsk); exec_state = task_exec_state_replace(tsk, exec_state); /* diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index e744f9f9d43f..ea3eb40bf828 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -727,19 +727,34 @@ static bool isw_prepare_wbs_switch(struct bdi_writeback *new_wb, struct inode_switch_wbs_context *isw, struct list_head *list, int *nr) { - struct inode *inode; + struct inode *inode, *tmp; + LIST_HEAD(scanned); + bool full = false; + + /* + * Walk from the oldest end and move scanned inodes to the newest + * end, so the next scan resumes at unscanned inodes instead of + * re-walking an ever-growing run of prepared and skipped ones. + * For b_dirty_time this keeps the oldest unscanned inode at the + * end move_expired_inodes() picks from; b_attached is unordered. + */ + list_for_each_entry_safe_reverse(inode, tmp, list, i_io_list) { + list_move(&inode->i_io_list, &scanned); - list_for_each_entry(inode, list, i_io_list) { if (!inode_prepare_wbs_switch(inode, new_wb)) continue; isw->inodes[*nr] = inode; (*nr)++; - if (*nr >= WB_MAX_INODES_PER_ISW - 1) - return true; + if (*nr >= WB_MAX_INODES_PER_ISW - 1) { + full = true; + break; + } } - return false; + list_splice(&scanned, list); + + return full; } /** diff --git a/fs/inode.c b/fs/inode.c index ba7da39be4a3..a9d37be390a1 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -880,7 +880,6 @@ void evict_inodes(struct super_block *sb) struct inode *inode; LIST_HEAD(dispose); -again: spin_lock(&sb->s_inode_list_lock); list_for_each_entry(inode, &sb->s_inodes, i_sb_list) { if (icount_read_once(inode)) @@ -899,19 +898,19 @@ again: inode_state_set(inode, I_FREEING); inode_lru_list_del(inode); spin_unlock(&inode->i_lock); - list_add(&inode->i_lru, &dispose); /* - * We can have a ton of inodes to evict at unmount time given - * enough memory, check to see if we need to go to sleep for a - * bit so we don't livelock. + * Keep this inode out of dispose so it stays on s_inodes while + * the list lock is dropped. I_FREEING prevents new references + * and leaves eviction to us, so we can resume the walk from it. */ if (need_resched()) { spin_unlock(&sb->s_inode_list_lock); cond_resched(); dispose_list(&dispose); - goto again; + spin_lock(&sb->s_inode_list_lock); } + list_add(&inode->i_lru, &dispose); } spin_unlock(&sb->s_inode_list_lock); diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c index c7ca7603e97a..28741251d56e 100644 --- a/fs/isofs/dir.c +++ b/fs/isofs/dir.c @@ -110,25 +110,21 @@ static int do_isofs_readdir(struct inode *inode, struct file *file, return 0; } - de = (struct iso_directory_record *) (bh->b_data + offset); - - de_len = *(unsigned char *)de; - + de = (struct iso_directory_record *)(bh->b_data + offset); /* - * If the length byte is zero, we should move on to the next - * CDROM sector. If we are at the end of the directory, we - * kick out of the while loop. + * If we are at the end of a block (or at its zero-padded + * tail), move on to the next CDROM sector. If we are at the + * end of the directory, we'll abort the while loop. */ - - if (de_len == 0) { + if (offset >= bufsize || de->length[0] == 0) { brelse(bh); bh = NULL; - ctx->pos = (ctx->pos + ISOFS_BLOCK_SIZE) & ~(ISOFS_BLOCK_SIZE - 1); - block = ctx->pos >> bufbits; + block++; + ctx->pos = (loff_t)block << bufbits; offset = 0; continue; } - + de_len = de->length[0]; block_saved = block; offset_saved = offset; offset += de_len; diff --git a/fs/isofs/namei.c b/fs/isofs/namei.c index 010682f5901a..4fba1bf7f016 100644 --- a/fs/isofs/namei.c +++ b/fs/isofs/namei.c @@ -74,18 +74,20 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry, return 0; } - de = (struct iso_directory_record *) (bh->b_data + offset); - - de_len = *(unsigned char *) de; - if (!de_len) { + de = (struct iso_directory_record *)(bh->b_data + offset); + /* + * If we are at the end of the block or at its zero-padded + * tail, move to the next block. + */ + if (offset >= bufsize || de->length[0] == 0) { brelse(bh); bh = NULL; - f_pos = (f_pos + ISOFS_BLOCK_SIZE) & ~(ISOFS_BLOCK_SIZE - 1); - block = f_pos >> bufbits; + block++; + f_pos = block << bufbits; offset = 0; continue; } - + de_len = de->length[0]; block_saved = bh->b_blocknr; offset_saved = offset; offset += de_len; diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c index f183a96778b9..a57399021c8b 100644 --- a/fs/kernfs/mount.c +++ b/fs/kernfs/mount.c @@ -434,8 +434,8 @@ void kernfs_kill_sb(struct super_block *sb) up_write(&root->kernfs_supers_rwsem); /* - * Remove the superblock from fs_supers/s_instances - * so we can't find it, before freeing kernfs_super_info. + * Mark the superblock dead so sget_fc() can't find it, + * before freeing kernfs_super_info. */ kill_anon_super(sb); kfree(info); diff --git a/fs/namespace.c b/fs/namespace.c index ae5dc64f8b45..580877e46b1a 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -6184,6 +6184,21 @@ struct mnt_namespace init_mnt_ns = { .poll = __WAIT_QUEUE_HEAD_INITIALIZER(init_mnt_ns.poll), }; +static void __init mount_rootfs_on_nullfs(struct vfsmount *mnt, + struct vfsmount *nullfs_mnt) +{ + struct path root = { + .mnt = nullfs_mnt, + .dentry = nullfs_mnt->mnt_root, + }; + + LOCK_MOUNT_EXACT(mp, &root); + if (unlikely(IS_ERR(mp.parent))) + panic("VFS: Failed to mount rootfs on nullfs"); + scoped_guard(mount_writer) + attach_mnt(real_mount(mnt), mp.parent, mp.mp); +} + static void __init init_mount_tree(void) { struct vfsmount *mnt, *nullfs_mnt; @@ -6215,15 +6230,7 @@ static void __init init_mount_tree(void) mnt_root = real_mount(nullfs_mnt); init_mnt_ns.root = mnt_root; - /* Mount mutable rootfs on top of nullfs. */ - root.mnt = nullfs_mnt; - root.dentry = nullfs_mnt->mnt_root; - - LOCK_MOUNT_EXACT(mp, &root); - if (unlikely(IS_ERR(mp.parent))) - panic("VFS: Failed to mount rootfs on nullfs"); - scoped_guard(mount_writer) - attach_mnt(real_mount(mnt), mp.parent, mp.mp); + mount_rootfs_on_nullfs(mnt, nullfs_mnt); pr_info("VFS: Finished mounting rootfs on nullfs\n"); @@ -6294,7 +6301,7 @@ void put_mnt_ns(struct mnt_namespace *ns) guard(namespace_excl)(); emptied_ns = ns; guard(mount_writer)(); - umount_tree(ns->root, UMOUNT_CONNECTED); + umount_tree(ns->root, 0); } struct vfsmount *kern_mount(struct file_system_type *type) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 424df70a5c30..105194de6e13 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -482,15 +482,14 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) struct netfs_group *group = netfs_folio_group(folio); struct netfs_folio *finfo = netfs_folio_info(folio); struct netfs_inode *ctx = netfs_inode(mapping->host); - struct folio *sink = NULL; - struct bio_vec *bvec; + struct bio_vec *bvec = NULL; unsigned int from = finfo->dirty_offset; unsigned int to = from + finfo->dirty_len; - unsigned int off = 0, i = 0; + unsigned int off = 0; size_t flen = folio_size(folio); size_t nr_bvec = flen / PAGE_SIZE + 2; size_t part; - int ret; + int ret, i = 0, sink_from = -1, sink_to = -1; _enter("%lx", folio->index); @@ -515,24 +514,23 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) if (!bvec) goto discard; - sink = folio_alloc(GFP_KERNEL, 0); - if (!sink) { - kfree(bvec); - goto discard; - } - trace_netfs_folio(folio, netfs_folio_trace_read_gaps); - rreq->direct_bv = bvec; - rreq->direct_bv_count = nr_bvec; if (from > 0) { bvec_set_folio(&bvec[i++], folio, from, 0); off = from; } + sink_from = i; while (off < to) { + struct folio *sink = folio_alloc(GFP_KERNEL, 0); + + if (!sink) + goto discard; part = min_t(size_t, to - off, PAGE_SIZE); - bvec_set_folio(&bvec[i++], sink, part, 0); + bvec_set_folio(&bvec[i], sink, part, 0); off += part; + sink_to = i; + i++; } if (to < flen) bvec_set_folio(&bvec[i++], folio, flen - to, to); @@ -553,8 +551,10 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) folio_mark_uptodate(folio); } - if (sink) - folio_put(sink); + if (sink_to >= 0) + for (; sink_from <= sink_to; sink_from++) + folio_put(bvec_folio(&bvec[sink_from])); + kfree(bvec); folio_unlock(folio); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; @@ -563,6 +563,10 @@ discard: netfs_put_failed_request(rreq); alloc_error: folio_unlock(folio); + if (sink_to >= 0) + for (; sink_from <= sink_to; sink_from++) + folio_put(bvec_folio(&bvec[sink_from])); + kfree(bvec); return ret; } diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 7f6a3e912602..ad549daa9c79 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -34,7 +34,7 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, rreq = mempool_alloc(mempool, gfp); } else { - rreq = mempool->alloc(gfp, mempool->pool_data); + rreq = mempool_alloc_noreserve(mempool, gfp); if (!rreq) return ERR_PTR(-ENOMEM); } @@ -214,7 +214,7 @@ struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq struct kmem_cache *cache = mempool->pool_data; if (rreq->gfp == GFP_KERNEL) - subreq = mempool->alloc(rreq->gfp, mempool->pool_data); + subreq = mempool_alloc_noreserve(mempool, rreq->gfp); else subreq = mempool_alloc(mempool, rreq->gfp); if (!subreq) diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 5cf22087d243..a94197ef0181 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -435,6 +435,11 @@ static void netfs_rreq_assess_single(struct netfs_io_request *rreq) netfs_single_mark_inode_dirty(rreq->inode); } + /* To do DIO, the cache has to round the size up, so we need to undo + * the rounding. + */ + rreq->transferred = min(rreq->transferred, rreq->i_size); + if (rreq->iocb) { rreq->iocb->ki_pos += rreq->transferred; if (rreq->iocb->ki_complete) { diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c index 424e77a9a109..d30d5ef6d86e 100644 --- a/fs/netfs/rolling_buffer.c +++ b/fs/netfs/rolling_buffer.c @@ -29,7 +29,7 @@ struct folio_queue *netfs_folioq_alloc(unsigned int rreq_id, gfp_t gfp, struct folio_queue *fq; if (gfp == GFP_KERNEL) - fq = netfs_folioq_pool.alloc(gfp, netfs_folioq_pool.pool_data); + fq = mempool_alloc_noreserve(&netfs_folioq_pool, gfp); else fq = mempool_alloc(&netfs_folioq_pool, gfp); if (fq) { diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 56b4f6469a28..4ac26c80bd34 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -1866,10 +1866,10 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, goto out6; /* - * Call 'd_instantiate' after inode->i_op is set + * Call 'd_instantiate_new' after inode->i_op is set * but before finish_open. */ - d_instantiate(dentry, inode); + d_instantiate_new(dentry, inode); /* Set original time. inode times (i_ctime) may be changed in ntfs_init_acl. */ inode_set_atime_to_ts(inode, ni->i_crtime); @@ -1917,9 +1917,6 @@ out1: if (!fnd) ni_unlock(dir_ni); - if (!err) - unlock_new_inode(inode); - return err; } diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index e9c7774ccf91..58c6061ed983 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -336,13 +336,8 @@ static int ocfs2_mknod(struct mnt_idmap *idmap, goto leave; /* calculate meta data/clusters for setting security and acl xattr */ - status = ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, - &xattr_credits, &want_meta, - &acl_state); - if (status < 0) { - mlog_errno(status); - goto leave; - } + ocfs2_calc_xattr_init(dir, mode, &si, &want_clusters, &xattr_credits, + &want_meta, &acl_state); /* Reserve a cluster if creating an extent based directory. */ if (S_ISDIR(mode) && !ocfs2_supports_inline_data(osb)) { diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 35bcbb0ff607..bfafe059bedf 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -635,12 +635,11 @@ int ocfs2_calc_security_init(struct inode *dir, return ret; } -int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, - struct ocfs2_security_xattr_info *si, - int *want_clusters, int *xattr_credits, - int *want_meta, struct ocfs2_acl_state *acl_state) +void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, + struct ocfs2_security_xattr_info *si, + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state) { - int ret = 0; struct ocfs2_super *osb = OCFS2_SB(dir->i_sb); int s_size = 0, a_size = 0, acl_len = 0, new_clusters; @@ -662,7 +661,7 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, } if (!(s_size + a_size)) - return ret; + return; /* * The max space of security xattr taken inline is @@ -728,8 +727,6 @@ int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, } } } - - return ret; } static int ocfs2_xattr_extend_allocation(struct inode *inode, diff --git a/fs/ocfs2/xattr.h b/fs/ocfs2/xattr.h index 5e18513277f1..887cc1a18b1a 100644 --- a/fs/ocfs2/xattr.h +++ b/fs/ocfs2/xattr.h @@ -59,10 +59,10 @@ int ocfs2_calc_security_init(struct inode *, int *, int *, struct ocfs2_alloc_context **); struct ocfs2_acl_state; -int ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, - struct ocfs2_security_xattr_info *si, - int *want_clusters, int *xattr_credits, - int *want_meta, struct ocfs2_acl_state *acl_state); +void ocfs2_calc_xattr_init(struct inode *dir, umode_t mode, + struct ocfs2_security_xattr_info *si, + int *want_clusters, int *xattr_credits, + int *want_meta, struct ocfs2_acl_state *acl_state); /* * xattrs can live inside an inode, as part of an external xattr block, diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e0d8c6152e9f..7f3558372c59 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -254,8 +254,10 @@ static inline struct dentry *ovl_do_mkdir(struct ovl_fs *ofs, { struct dentry *ret; + /* vfs_mkdir() drops @dentry on failure and may replace it on success */ + pr_debug("mkdir(%pd2, 0%o)\n", dentry, mode); ret = vfs_mkdir(ovl_upper_mnt_idmap(ofs), dir, dentry, mode, NULL); - pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, PTR_ERR_OR_ZERO(ret)); + pr_debug("...mkdir = %i\n", PTR_ERR_OR_ZERO(ret)); return ret; } diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index 88d5e9a32f28..647fa26da4d2 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -8,6 +8,7 @@ #include <linux/namei.h> #include "cifsglob.h" #include "cifsproto.h" +#include "../common/smb2status.h" #include "cifs_debug.h" #include "smb2proto.h" #include "cached_dir.h" @@ -323,25 +324,37 @@ replay_again: rc = compound_send_recv(xid, ses, server, flags, 2, rqst, resp_buftype, rsp_iov); - if (rc) { - if (rc == -EREMCHG) { - tcon->need_reconnect = true; - pr_warn_once("server share %s deleted\n", - tcon->tree_name); - } - goto oshr_free; + if (rc == -EREMCHG) { + tcon->need_reconnect = true; + pr_warn_once("server share %s deleted\n", + tcon->tree_name); } - cfid->is_open = true; - spin_lock(&cfids->cfid_list_lock); + if (!rsp_iov[0].iov_base || rsp_iov[0].iov_len < sizeof(*o_rsp)) { + if (!rc) + rc = -EIO; + goto oshr_free; + } o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base; + if (o_rsp->hdr.Status != STATUS_SUCCESS) { + if (!rc) + rc = -EIO; + goto oshr_free; + } + oparms.fid->persistent_fid = o_rsp->PersistentFileId; oparms.fid->volatile_fid = o_rsp->VolatileFileId; #ifdef CONFIG_CIFS_DEBUG2 oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId); #endif /* CIFS_DEBUG2 */ + cfid->is_open = true; + atomic_inc(&tcon->num_remote_opens); + if (rc) + goto oshr_free; + + spin_lock(&cfids->cfid_list_lock); if (o_rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) { spin_unlock(&cfids->cfid_list_lock); @@ -408,7 +421,6 @@ out: close_cached_dir(cfid); } else { *ret_cfid = cfid; - atomic_inc(&tcon->num_remote_opens); } kfree(utf16_path); diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 29dfd7595941..26956e33037e 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -798,13 +798,13 @@ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl) INIT_LIST_HEAD(head); list_for_each_entry(t, &ce->tlist, list) { - it = kzalloc_obj(*it, GFP_ATOMIC); + it = kzalloc_obj(*it, GFP_KERNEL); if (!it) { rc = -ENOMEM; goto err_free_it; } - it->it_name = kstrdup(t->name, GFP_ATOMIC); + it->it_name = kstrdup(t->name, GFP_KERNEL); if (!it->it_name) { kfree(it); rc = -ENOMEM; diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 6fa6d48fdfd3..1a56fa4d0e89 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -199,7 +199,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, struct tcon_link *tlink, unsigned int oflags, umode_t mode, __u32 *oplock, struct cifs_fid *fid, struct cifs_open_info_data *buf, - struct inode **inode) + struct inode **inode, bool *opened) { int rc = -ENOENT; int create_options = CREATE_NOT_DIR; @@ -216,6 +216,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, __le32 lease_flags = 0; *inode = NULL; + *opened = false; *oplock = 0; if (tcon->ses->server->oplocks) *oplock = REQ_OPLOCK; @@ -232,6 +233,7 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, oflags, oplock, &fid->netfid, xid); switch (rc) { case 0: + *opened = true; if (newinode == NULL) { /* query inode info */ goto cifs_create_get_file_info; @@ -253,11 +255,9 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, /* * The server may allow us to open things like * FIFOs, but the client isn't set up to deal - * with that. If it's not a regular file, just - * close it and proceed as if it were a normal - * lookup. + * with that. Keep the handle until the caller + * can finish the lookup. */ - CIFSSMBClose(xid, tcon, fid->netfid); goto cifs_create_get_file_info; } /* success, no need to query */ @@ -384,6 +384,7 @@ retry_open: } return rc; } + *opened = true; if (rdwr_for_fscache == 2) cifs_invalidate_cache(dir, FSCACHE_INVAL_DIO_WRITE); @@ -475,7 +476,7 @@ cifs_create_set_dentry: return rc; out_err: - if (server->ops->close) + if (*opened && server->ops->close) server->ops->close(xid, tcon, fid); if (newinode) iput(newinode); @@ -487,7 +488,7 @@ static int cifs_do_create(struct inode *dir, struct dentry *direntry, unsigned int oflags, umode_t mode, __u32 *oplock, struct cifs_fid *fid, struct cifs_open_info_data *buf, - struct inode **inode) + struct inode **inode, bool *opened) { void *page = alloc_dentry_path(); const char *full_path; @@ -496,10 +497,11 @@ static int cifs_do_create(struct inode *dir, struct dentry *direntry, full_path = build_path_from_dentry(direntry, page); if (IS_ERR(full_path)) { rc = PTR_ERR(full_path); + *opened = false; } else { rc = __cifs_do_create(dir, direntry, full_path, xid, tlink, oflags, mode, oplock, - fid, buf, inode); + fid, buf, inode, opened); } free_dentry_path(page); return rc; @@ -529,6 +531,8 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, struct inode *inode; unsigned int xid; __u32 oplock; + bool is_regular; + bool opened; int rc; if (unlikely(cifs_forced_shutdown(cifs_sb))) @@ -581,12 +585,26 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, cifs_add_pending_open(&fid, tlink, &open); rc = cifs_do_create(dir, direntry, xid, tlink, oflags, mode, - &oplock, &fid, &buf, &inode); + &oplock, &fid, &buf, &inode, &opened); if (rc) { cifs_del_pending_open(&open); goto out; } + is_regular = S_ISREG(inode->i_mode); + if (!is_regular || !opened) { + if (opened && server->ops->close) + server->ops->close(xid, tcon, &fid); + cifs_del_pending_open(&open); + if (S_ISLNK(inode->i_mode) && + (oflags & (O_NOFOLLOW | __O_REGULAR)) == + (O_NOFOLLOW | __O_REGULAR) && !(oflags & O_EXCL)) { + iput(inode); + rc = -ELOOP; + goto out; + } + } + if (d_in_lookup(direntry)) { alias = d_splice_alias(inode, direntry); if (!IS_ERR_OR_NULL(alias)) @@ -595,9 +613,15 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, d_instantiate(direntry, inode); } - if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) + if (is_regular && opened && + (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) file->f_mode |= FMODE_CREATED; + if (!is_regular || !opened) { + rc = finish_no_open(file, NULL); + goto out; + } + rc = finish_open(file, direntry, generic_file_open); if (rc) { if (server->ops->close) @@ -660,6 +684,7 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; struct cifs_fid fid; __u32 oplock; + bool opened; struct cifs_open_info_data buf = {}; cifs_dbg(FYI, "cifs_create parent inode = 0x%p name is: %pd and dentry = 0x%p\n", @@ -682,10 +707,10 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, server->ops->new_lease_key(&fid); rc = cifs_do_create(dir, direntry, xid, tlink, oflags, - mode, &oplock, &fid, &buf, &inode); + mode, &oplock, &fid, &buf, &inode, &opened); if (!rc) { d_instantiate(direntry, inode); - if (server->ops->close) + if (opened && server->ops->close) server->ops->close(xid, tcon, &fid); } @@ -1078,6 +1103,7 @@ int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, struct inode *inode; unsigned int xid; __u32 oplock; + bool opened; int namelen; int rc; @@ -1116,7 +1142,7 @@ int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, namelen = scnprintf(name, namesize, CIFS_TMPNAME_PREFIX "%x", atomic_inc_return(&cifs_tmpcounter)); rc = __cifs_do_create(dir, dentry, path, xid, tlink, oflags, - mode, &oplock, &fid, NULL, &inode); + mode, &oplock, &fid, NULL, &inode, &opened); if (!rc) { rc = d_mark_tmpfile_name(file, &QSTR_LEN(name, namelen)); if (rc) { diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 13fe8e3b48f3..f46a62eae659 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -598,8 +598,10 @@ finished: /* smb2_parse_contexts() fills idata->fi.IndexNumber */ rc = smb2_parse_contexts(server, &rsp_iov[0], &oparms->fid->epoch, oparms->fid->lease_key, &oplock, &idata->fi, NULL); - if (rc) + if (rc) { cifs_dbg(VFS, "rc: %d parsing context of compound op\n", rc); + tmp_rc = rc; + } } for (i = 0; i < num_cmds; i++) { @@ -1121,7 +1123,7 @@ smb2_unlink(const unsigned int xid, struct cifs_tcon *tcon, const char *name, struct kvec close_iov; int resp_buftype[2]; struct cifs_fid fid; - int flags = 0; + int flags = CIFS_CP_CREATE_CLOSE_OP; __u8 oplock; int rc; diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c index 0cfe60ae42c3..5e5cf92d1eb3 100644 --- a/fs/smb/client/smb2misc.c +++ b/fs/smb/client/smb2misc.c @@ -834,7 +834,8 @@ smb2_cancelled_close_fid(struct work_struct *work) */ static int __smb2_handle_cancelled_cmd(struct cifs_tcon *tcon, __u16 cmd, __u64 mid, - __u64 persistent_fid, __u64 volatile_fid) + __u64 persistent_fid, __u64 volatile_fid, + bool account_remote_open) { struct close_cancelled_open *cancelled; @@ -848,6 +849,8 @@ __smb2_handle_cancelled_cmd(struct cifs_tcon *tcon, __u16 cmd, __u64 mid, cancelled->cmd = cmd; cancelled->mid = mid; INIT_WORK(&cancelled->work, smb2_cancelled_close_fid); + if (account_remote_open) + atomic_inc(&tcon->num_remote_opens); WARN_ON(queue_work(cifsiod_wq, &cancelled->work) == false); return 0; @@ -884,7 +887,7 @@ smb2_handle_cancelled_close(struct cifs_tcon *tcon, __u64 persistent_fid, spin_unlock(&tcon->tc_lock); rc = __smb2_handle_cancelled_cmd(tcon, SMB2_CLOSE_HE, 0, - persistent_fid, volatile_fid); + persistent_fid, volatile_fid, false); if (rc) cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_close); @@ -912,7 +915,7 @@ smb2_handle_cancelled_mid(struct mid_q_entry *mid, struct TCP_Server_Info *serve le16_to_cpu(hdr->Command), le64_to_cpu(hdr->MessageId), rsp->PersistentFileId, - rsp->VolatileFileId); + rsp->VolatileFileId, true); if (rc) cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cancelled_mid); diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 3464470d3297..aa142420dae2 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4572,25 +4572,37 @@ smb3_create_lease_buf(u8 *lease_key, u8 oplock, u8 *parent_lease_key, __le32 fla static __u8 smb2_parse_lease_buf(void *buf, __u16 *epoch, char *lease_key) { - struct create_lease *lc = (struct create_lease *)buf; + struct create_context *cc = buf; + struct lease_context lc; *epoch = 0; /* not used */ - if (lc->lcontext.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) + if (le32_to_cpu(cc->DataLength) != sizeof(lc)) + return 0; + + memcpy(&lc, (u8 *)cc + le16_to_cpu(cc->DataOffset), sizeof(lc)); + if (lc.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) return SMB2_OPLOCK_LEVEL_NOCHANGE; - return le32_to_cpu(lc->lcontext.LeaseState); + return le32_to_cpu(lc.LeaseState); } static __u8 smb3_parse_lease_buf(void *buf, __u16 *epoch, char *lease_key) { - struct create_lease_v2 *lc = (struct create_lease_v2 *)buf; + struct create_context *cc = buf; + struct lease_context_v2 lc; + + if (le32_to_cpu(cc->DataLength) != sizeof(lc)) { + *epoch = 0; + return 0; + } - *epoch = le16_to_cpu(lc->lcontext.Epoch); - if (lc->lcontext.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) + memcpy(&lc, (u8 *)cc + le16_to_cpu(cc->DataOffset), sizeof(lc)); + *epoch = le16_to_cpu(lc.Epoch); + if (lc.LeaseFlags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS_LE) return SMB2_OPLOCK_LEVEL_NOCHANGE; if (lease_key) - memcpy(lease_key, &lc->lcontext.LeaseKey, SMB2_LEASE_KEY_SIZE); - return le32_to_cpu(lc->lcontext.LeaseState); + memcpy(lease_key, lc.LeaseKey, SMB2_LEASE_KEY_SIZE); + return le32_to_cpu(lc.LeaseState); } static unsigned int diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 880ce12f50c4..3d7ead36d1a0 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -2379,23 +2379,32 @@ create_reconnect_durable_buf(struct cifs_fid *fid) static void parse_query_id_ctxt(struct create_context *cc, struct smb2_file_all_info *buf) { - struct create_disk_id_rsp *pdisk_id = (struct create_disk_id_rsp *)cc; + u16 doff = le16_to_cpu(cc->DataOffset); + u32 dlen = le32_to_cpu(cc->DataLength); + u8 *beg; - cifs_dbg(FYI, "parse query id context 0x%llx 0x%llx\n", - pdisk_id->DiskFileId, pdisk_id->VolumeId); - buf->IndexNumber = pdisk_id->DiskFileId; + if (dlen < sizeof(__le64)) + return; + + beg = (u8 *)cc + doff; + memcpy(&buf->IndexNumber, beg, sizeof(__le64)); + cifs_dbg(FYI, "parse query id context 0x%llx\n", + le64_to_cpu(buf->IndexNumber)); } static void parse_posix_ctxt(struct create_context *cc, struct smb2_file_all_info *info, struct create_posix_rsp *posix) { - int sid_len; u8 *beg = (u8 *)cc + le16_to_cpu(cc->DataOffset); - u8 *end = beg + le32_to_cpu(cc->DataLength); + u32 dlen = le32_to_cpu(cc->DataLength); + u8 *end = beg + dlen; + int sid_len; u8 *sid; memset(posix, 0, sizeof(*posix)); + if (dlen < 3 * sizeof(__le32)) + return; posix->nlink = get_unaligned_le32(beg); posix->reparse_tag = get_unaligned_le32(beg + 4); @@ -2431,6 +2440,7 @@ int smb2_parse_contexts(struct TCP_Server_Info *server, struct smb2_create_rsp *rsp = rsp_iov->iov_base; struct create_context *cc; size_t rem, off, len; + size_t cc_len; size_t doff, dlen; size_t noff, nlen; char *name; @@ -2453,29 +2463,41 @@ int smb2_parse_contexts(struct TCP_Server_Info *server, buf->IndexNumber = 0; while (rem >= sizeof(*cc)) { + off = le32_to_cpu(cc->Next); + if (off) { + if ((off & 0x7) || off >= rem || off < sizeof(*cc)) + return -EINVAL; + cc_len = off; + } else { + cc_len = rem; + } + doff = le16_to_cpu(cc->DataOffset); dlen = le32_to_cpu(cc->DataLength); - if (check_add_overflow(doff, dlen, &len) || len > rem) + if (doff < sizeof(*cc) || + check_add_overflow(doff, dlen, &len) || len > cc_len) return -EINVAL; noff = le16_to_cpu(cc->NameOffset); nlen = le16_to_cpu(cc->NameLength); - if (noff + nlen > doff) + if (noff < sizeof(*cc) || + check_add_overflow(noff, nlen, &len) || len > cc_len || + (dlen && len > doff)) return -EINVAL; name = (char *)cc + noff; switch (nlen) { case 4: - if (!strncmp(name, SMB2_CREATE_REQUEST_LEASE, 4)) { + if (dlen && !strncmp(name, SMB2_CREATE_REQUEST_LEASE, 4)) { *oplock = server->ops->parse_lease_buf(cc, epoch, lease_key); - } else if (buf && + } else if (dlen && buf && !strncmp(name, SMB2_CREATE_QUERY_ON_DISK_ID, 4)) { parse_query_id_ctxt(cc, buf); } break; case 16: - if (posix && !memcmp(name, smb3_create_tag_posix, 16)) + if (dlen && posix && !memcmp(name, smb3_create_tag_posix, 16)) parse_posix_ctxt(cc, buf, posix); break; default: @@ -2487,13 +2509,18 @@ int smb2_parse_contexts(struct TCP_Server_Info *server, } off = le32_to_cpu(cc->Next); - if (!off) + if (!off) { + rem = 0; break; + } if (check_sub_overflow(rem, off, &rem)) return -EINVAL; cc = (struct create_context *)((u8 *)cc + off); } + if (rem) + return -EINVAL; + if (rsp->OplockLevel != SMB2_OPLOCK_LEVEL_LEASE) *oplock = rsp->OplockLevel; @@ -3389,6 +3416,9 @@ replay_again: rc = smb2_parse_contexts(server, &rsp_iov, &oparms->fid->epoch, oparms->fid->lease_key, oplock, file_info, posix); + if (rc) + SMB2_close(xid, tcon, oparms->fid->persistent_fid, + oparms->fid->volatile_fid); trace_smb3_open_done(xid, rsp->PersistentFileId, tcon->tid, ses->Suid, oparms->create_options, oparms->desired_access, diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h index ab6c667bebc0..b37a1e3941a1 100644 --- a/fs/smb/client/smb2pdu.h +++ b/fs/smb/client/smb2pdu.h @@ -224,8 +224,7 @@ struct smb2_file_id_extd_directory_info { extern char smb2_padding[7]; /* - * See POSIX-SMB2 2.2.14.2.16 - * Link: https://gitlab.com/samba-team/smb3-posix-spec/-/blob/master/smb3_posix_extensions.md + * See POSIX-SMB2 2.1.3.2.1 */ struct create_posix_rsp { u32 nlink; @@ -238,6 +237,7 @@ struct create_posix_rsp { #define SMB2_QUERY_DIRECTORY_IOV_SIZE 2 /* + * See POSIX-FSCC 2.2.1 * SMB2-only POSIX info level for query dir * * See posix_info_sid_size(), posix_info_extra_size() and @@ -256,13 +256,17 @@ struct smb2_posix_info { __le64 Inode; __le32 DeviceId; __le32 Zero; - /* beginning of POSIX Create Context Response */ + /* + * Beginning of POSIX Create Context Response + * See POSIX-SMB2 2.1.3.2.1 + */ __le32 HardLinks; __le32 ReparseTag; __le32 Mode; /* * var sized owner SID * var sized group SID + * End of POSIX Create Context Response * le32 filenamelength * u8 filename[] */ diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index e266859818a4..6e21b5f8754a 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -805,6 +805,18 @@ cifs_cancelled_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid) release_mid(server, mid); } +static void +cifs_mark_compound_mids_cancelled(struct mid_q_entry **mid, int count) +{ + int i; + + for (i = 0; i < count; i++) { + spin_lock(&mid[i]->mid_lock); + mid[i]->wait_cancelled = true; + spin_unlock(&mid[i]->mid_lock); + } +} + /* * cifs_pick_channel - pick an eligible channel for network operations * @@ -865,6 +877,7 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, int *resp_buf_type, struct kvec *resp_iov) { int i, j, optype, rc = 0; + int num_processed = 0; struct mid_q_entry *mid[MAX_COMPOUND]; bool cancelled_mid[MAX_COMPOUND] = {false}; struct cifs_credits credits[MAX_COMPOUND] = { @@ -965,6 +978,10 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, if (rc < 0) { revert_current_mid(server, num_rqst); server->sequence_number -= 2; + for (i = 0; i < num_rqst; i++) { + delete_mid(server, mid[i]); + cancelled_mid[i] = true; + } } cifs_server_unlock(server); @@ -1011,6 +1028,14 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, break; } if (rc != 0) { + /* + * A completed CREATE earlier in the compound chain may have + * opened a remote handle even though a later wait was + * interrupted. Mark it cancelled so __release_mid() invokes + * the existing unmatched-open cleanup. + */ + cifs_mark_compound_mids_cancelled(mid, i); + for (; i < num_rqst; i++) { cifs_server_dbg(FYI, "Cancelling wait for mid %llu cmd: %d\n", mid[i]->mid, le16_to_cpu(mid[i]->command)); @@ -1033,6 +1058,14 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, rc = cifs_sync_mid_result(mid[i], server); if (rc != 0) { + /* + * A previous CREATE may have completed before this + * response failed. Mark it cancelled so its remote + * handle is closed when the mid is released. + */ + cifs_mark_compound_mids_cancelled(mid, i); + /* Keep their response buffers for cancelled-mid cleanup. */ + num_processed = 0; /* mark this mid as cancelled to not free it below */ cancelled_mid[i] = true; goto out; @@ -1042,13 +1075,24 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, mid[i]->mid_state != MID_RESPONSE_READY) { rc = smb_EIO1(smb_eio_trace_rx_mid_unready, mid[i]->mid_state); cifs_dbg(FYI, "Bad MID state?\n"); + cifs_mark_compound_mids_cancelled(mid, i); + num_processed = 0; goto out; } rc = server->ops->check_receive(mid[i], server, flags & CIFS_LOG_ERROR); + num_processed = i + 1; + } - if (resp_iov) { +out: + /* + * Delay moving response buffers out of their mids until response + * synchronization completes. This lets cancelled-mid cleanup inspect + * an earlier CREATE response if a later MID fails. + */ + if (resp_iov) { + for (i = 0; i < num_processed; i++) { buf = (char *)mid[i]->resp_buf; resp_iov[i].iov_base = buf; resp_iov[i].iov_len = mid[i]->resp_buf_size; @@ -1067,21 +1111,22 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, /* * Compounding is never used during session establish. */ - spin_lock(&ses->ses_lock); - if ((ses->ses_status == SES_NEW) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) { - struct kvec iov = { - .iov_base = resp_iov[0].iov_base, - .iov_len = resp_iov[0].iov_len - }; - spin_unlock(&ses->ses_lock); - cifs_server_lock(server); - smb311_update_preauth_hash(ses, server, &iov, 1); - cifs_server_unlock(server); + if (num_processed == num_rqst) { spin_lock(&ses->ses_lock); + if ((ses->ses_status == SES_NEW) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) { + struct kvec iov = { + .iov_base = resp_iov[0].iov_base, + .iov_len = resp_iov[0].iov_len + }; + spin_unlock(&ses->ses_lock); + cifs_server_lock(server); + smb311_update_preauth_hash(ses, server, &iov, 1); + cifs_server_unlock(server); + spin_lock(&ses->ses_lock); + } + spin_unlock(&ses->ses_lock); } - spin_unlock(&ses->ses_lock); -out: /* * This will dequeue all mids. After this it is important that the * demultiplex_thread will not process any of these mids any further. diff --git a/fs/squashfs/xz_wrapper.c b/fs/squashfs/xz_wrapper.c index 0a4ff3ec9c8c..6610af241449 100644 --- a/fs/squashfs/xz_wrapper.c +++ b/fs/squashfs/xz_wrapper.c @@ -57,10 +57,10 @@ static void *squashfs_xz_comp_opts(struct squashfs_sb_info *msblk, opts->dict_size = le32_to_cpu(comp_opts->dictionary_size); - /* the dictionary size should be 2^n or 2^n+2^(n+1) */ + /* the dictionary size should be positive and 2^n or 2^n+2^(n+1) */ n = ffs(opts->dict_size) - 1; - if (opts->dict_size != (1 << n) && opts->dict_size != (1 << n) + - (1 << (n + 1))) { + if (opts->dict_size <= 0 || (opts->dict_size != (1 << n) && + opts->dict_size != (1 << n) + (1 << (n + 1)))) { err = -EIO; goto out; } diff --git a/fs/super.c b/fs/super.c index 9d4025213521..1d5ccf540a9b 100644 --- a/fs/super.c +++ b/fs/super.c @@ -419,15 +419,19 @@ fail: void put_super(struct super_block *s) { if (refcount_dec_and_test(&s->s_passive)) { + struct file_system_type *type = s->s_type; spin_lock(&sb_lock); list_del_init(&s->s_list); + hlist_del_init(&s->s_instances); spin_unlock(&sb_lock); WARN_ON(s->s_dentry_lru.node); WARN_ON(s->s_inode_lru.node); WARN_ON(s->s_mounts); call_rcu(&s->rcu, destroy_super_rcu); + /* The unlink above may touch type->fs_supers, so drop it last. */ + put_filesystem(type); } } @@ -544,17 +548,6 @@ static void kill_super_notify(struct super_block *sb) if (sb->s_flags & SB_DEAD) return; - /* - * Remove it from @fs_supers so it isn't found by new - * sget_fc() walkers anymore. Any concurrent mounter still - * managing to grab a temporary reference is guaranteed to - * already see SB_DYING and will wait until we notify them about - * SB_DEAD. - */ - spin_lock(&sb_lock); - hlist_del_init(&sb->s_instances); - spin_unlock(&sb_lock); - /* Drop sget_fc()'s claim; a never-registered entry stays with the sb. */ if (sb->s_super_dev->sd_dev) { super_dev_put(sb->s_super_dev); @@ -563,11 +556,15 @@ static void kill_super_notify(struct super_block *sb) /* * Let concurrent mounts know that this thing is really dead. - * We don't need @sb->s_umount here as every concurrent caller - * will see SB_DYING and either discard the superblock or wait - * for SB_DEAD. + * sget_fc() skips SB_DEAD superblocks and calls test() under + * sb_lock, so set it under sb_lock: once we return no test() + * runs on this superblock anymore and none will start. Everyone + * else already saw SB_DYING and either discarded the superblock + * or waits for SB_DEAD. */ + spin_lock(&sb_lock); super_wake(sb, SB_DEAD); + spin_unlock(&sb_lock); } /** @@ -594,7 +591,6 @@ void deactivate_locked_super(struct super_block *s) list_lru_destroy(&s->s_dentry_lru); list_lru_destroy(&s->s_inode_lru); - put_filesystem(fs); put_super(s); } else { super_unlock_excl(s); @@ -781,12 +777,12 @@ void generic_shutdown_super(struct super_block *sb) } /* * Broadcast to everyone that grabbed a temporary reference to this - * superblock before we removed it from @fs_supers that the superblock - * is dying. Every walker of @fs_supers outside of sget_fc() will now - * discard this superblock and treat it as dead. + * superblock that it is dying. Every walker of @fs_supers outside + * of sget_fc() will now discard this superblock and treat it as + * dead. * - * We leave the superblock on @fs_supers so it can be found by - * sget_fc() until we passed sb->kill_sb(). + * sget_fc() keeps finding the superblock until SB_DEAD is set, so + * a concurrent mounter waits until we passed sb->kill_sb(). */ super_wake(sb, SB_DYING); super_unlock_excl(sb); @@ -865,6 +861,9 @@ retry: spin_lock(&sb_lock); if (test) { hlist_for_each_entry(old, &fc->fs_type->fs_supers, s_instances) { + /* Only unlinked at the last passive reference. */ + if (super_flags(old, SB_DEAD)) + continue; if (test(old, fc)) goto share_extant_sb; } diff --git a/fs/xfs/libxfs/xfs_ag.h b/fs/xfs/libxfs/xfs_ag.h index fd22fe598931..ee636b66a72f 100644 --- a/fs/xfs/libxfs/xfs_ag.h +++ b/fs/xfs/libxfs/xfs_ag.h @@ -207,7 +207,7 @@ xfs_perag_next( } /* - * Per-ag geometry infomation and validation + * Per-ag geometry information and validation */ xfs_agblock_t xfs_ag_block_count(struct xfs_mount *mp, xfs_agnumber_t agno); void xfs_agino_range(struct xfs_mount *mp, xfs_agnumber_t agno, diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index d99602bcc16f..f762dcce8d13 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -3487,7 +3487,7 @@ xfs_alloc_read_agf( } /* - * Pre-proces allocation arguments to set initial state that we don't require + * Pre-process allocation arguments to set initial state that we don't require * callers to set up correctly, as well as bounds check the allocation args * that are set up. */ @@ -3608,7 +3608,7 @@ xfs_alloc_vextent_finish( * ABBA AGF deadlocks because a future allocation attempt in this * transaction may attempt to lock a lower number AGF. * - * We can't release the AGF until the transaction is commited, so at + * We can't release the AGF until the transaction is committed, so at * this point we must update the "first allocation" tracker to point at * this AG if the tracker is empty or points to a lower AG. This allows * the next allocation attempt to be modified appropriately to avoid diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index b6288395f853..2c80f4fd0b78 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -1715,7 +1715,7 @@ xfs_attr3_leaf_add_work( /* * This freemap entry starts at the old end of the * leaf entry array, so we need to adjust its base - * upward to accomodate the larger array. + * upward to accommodate the larger array. */ diff = sizeof(struct xfs_attr_leaf_entry); } else if (ichdr->freemap[i].size > 0 && diff --git a/fs/xfs/libxfs/xfs_errortag.h b/fs/xfs/libxfs/xfs_errortag.h index 6de207fed2d8..f0c83f1f0b3b 100644 --- a/fs/xfs/libxfs/xfs_errortag.h +++ b/fs/xfs/libxfs/xfs_errortag.h @@ -83,7 +83,7 @@ #define XFS_RANDOM_DEFAULT 100 /* - * Table of errror injection knobs. The parameters to the XFS_ERRTAG macro are: + * Table of error injection knobs. The parameters to the XFS_ERRTAG macro are: * 1. The XFS_ERRTAG_ flag but without the prefix; * 2. The name of the sysfs knob; and * 3. The default value for the knob. diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 49eda8d0994d..6a66b6075e0a 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -395,7 +395,7 @@ xfs_exchmaps_one_step( /* * Re-add both mappings. We exchange the file offsets between the two * maps and add the opposite map, which has the effect of filling the - * logical offsets we just unmapped, but with with the physical mapping + * logical offsets we just unmapped, but with the physical mapping * information exchanged. */ swap(irec1->br_startoff, irec2->br_startoff); @@ -969,16 +969,6 @@ xmi_can_exchange_reflink_flags( if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) return false; - /* - * The INO1_WRITTEN optimization can skip exchanging hole and - * unwritten mappings, which means we cannot guarantee that all - * shared extents actually moved to the other file. Clearing the - * reflink flag of an inode that still holds shared extents breaks - * the CoW write path, so refuse to exchange the flags in that case. - */ - if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN) - return false; - if (hweight32(reflink_state) != 1) return false; if (req->startoff1 != 0 || req->startoff2 != 0) diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index dd0ed046fbe9..1a7a7e60a170 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -1051,7 +1051,7 @@ enum xfs_dinode_fmt { * block is 1KB in size. * * With XFS_MAX_EXTCNT_DATA_FORK_SMALL representing maximum extent count and - * with 1KB sized blocks, a file can reach upto, + * with 1KB sized blocks, a file can reach up to, * 1KB * (2^31) = 2TB * * This is much larger than the theoretical maximum size of a directory diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index e4c3f7b24e95..0340e2189921 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -626,7 +626,7 @@ xfs_dinode_verify( * have di_nlink track the link count, even if the actual filesystem * only supported V1 inodes (i.e. di_onlink). When writing out the * ondisk inode, it would set both the ondisk di_nlink and di_onlink to - * the the incore di_nlink value, which is why we cannot check for + * the incore di_nlink value, which is why we cannot check for * di_nlink==0 on a V1 inode. V2/3 inodes would get written out with * di_onlink==0, so we can check that. */ diff --git a/fs/xfs/libxfs/xfs_metafile.c b/fs/xfs/libxfs/xfs_metafile.c index 71f004e9dc64..1f54d39003c2 100644 --- a/fs/xfs/libxfs/xfs_metafile.c +++ b/fs/xfs/libxfs/xfs_metafile.c @@ -297,14 +297,14 @@ xfs_metafile_resv_init( goto out_unlock; /* - * Space taken by the per-AG metadata btrees are accounted on-disk as - * used space. We therefore only hide the space that is reserved but - * not used by the trees. + * Space taken by metadata btrees are accounted on-disk as used space. + * We therefore only hide the space that is reserved but not used by + * the trees. */ if (used > target) target = used; else if (target > dblocks_avail) - target = dblocks_avail; + target = max(dblocks_avail, used); hidden_space = target - used; error = xfs_dec_fdblocks(mp, hidden_space, true); diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index e2950dbe2068..dcc89b8e149b 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -617,7 +617,7 @@ xfs_rtrefcountbt_from_disk( fpp = xfs_rtrefcount_droot_ptr_addr(dblock, 1, maxrecs); tpp = xfs_rtrefcount_broot_ptr_addr(mp, rblock, 1, rblocklen); numrecs = be16_to_cpu(dblock->bb_numrecs); - memcpy(tkp, fkp, 2 * sizeof(*fkp) * numrecs); + memcpy(tkp, fkp, sizeof(*fkp) * numrecs); memcpy(tpp, fpp, sizeof(*fpp) * numrecs); } else { frp = xfs_rtrefcount_droot_rec_addr(dblock, 1); @@ -703,7 +703,7 @@ xfs_rtrefcountbt_to_disk( fpp = xfs_rtrefcount_broot_ptr_addr(mp, rblock, 1, rblocklen); tpp = xfs_rtrefcount_droot_ptr_addr(dblock, 1, maxrecs); numrecs = be16_to_cpu(rblock->bb_numrecs); - memcpy(tkp, fkp, 2 * sizeof(*fkp) * numrecs); + memcpy(tkp, fkp, sizeof(*fkp) * numrecs); memcpy(tpp, fpp, sizeof(*fpp) * numrecs); } else { frp = xfs_rtrefcount_rec_addr(rblock, 1); diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index a66b611588c4..ff1b4b361cf2 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1369,7 +1369,7 @@ xrep_iunlink_mark_ondisk( /* * Walk an iunlink bucket's inode list. For each inode that should be on this - * chain, clear its entry in in iunlink_bmp because it's ok and we don't need + * chain, clear its entry in iunlink_bmp because it's ok and we don't need * to touch it further. */ STATIC int diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index 95e318e4f3a6..2398e3819597 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -338,7 +338,7 @@ xrep_cntbt_extent_cmp( } /* - * Sort the free extents by length so so that we can put the records into the + * Sort the free extents by length so that we can put the records into the * cntbt in the correct order. Don't let userspace kill us if we're resorting * after allocating btree blocks. */ diff --git a/fs/xfs/scrub/bitmap.c b/fs/xfs/scrub/bitmap.c index c7fa908d92b2..08f216d26ec6 100644 --- a/fs/xfs/scrub/bitmap.c +++ b/fs/xfs/scrub/bitmap.c @@ -122,8 +122,8 @@ xbitmap64_set( uint64_t start, uint64_t len) { - struct xbitmap64_node *left; - struct xbitmap64_node *right; + struct xbitmap64_node *left = NULL; + struct xbitmap64_node *right = NULL; uint64_t last = start + len - 1; int error; @@ -131,6 +131,7 @@ xbitmap64_set( left = xbitmap64_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) return 0; + left = NULL; /* Clear out everything in the range we want to set. */ error = xbitmap64_clear(bitmap, start, len); @@ -138,11 +139,15 @@ xbitmap64_set( return error; /* Do we have a left-adjacent extent? */ - left = xbitmap64_tree_iter_first(&bitmap->xb_root, start - 1, start - 1); + if (start > 0) + left = xbitmap64_tree_iter_first(&bitmap->xb_root, start - 1, + start - 1); ASSERT(!left || left->bn_last + 1 == start); /* Do we have a right-adjacent extent? */ - right = xbitmap64_tree_iter_first(&bitmap->xb_root, last + 1, last + 1); + if (last < U64_MAX) + right = xbitmap64_tree_iter_first(&bitmap->xb_root, last + 1, + last + 1); ASSERT(!right || right->bn_start == last + 1); if (left && right) { @@ -397,8 +402,8 @@ xbitmap32_set( uint32_t start, uint32_t len) { - struct xbitmap32_node *left; - struct xbitmap32_node *right; + struct xbitmap32_node *left = NULL; + struct xbitmap32_node *right = NULL; uint32_t last = start + len - 1; int error; @@ -406,6 +411,7 @@ xbitmap32_set( left = xbitmap32_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) return 0; + left = NULL; /* Clear out everything in the range we want to set. */ error = xbitmap32_clear(bitmap, start, len); @@ -413,11 +419,15 @@ xbitmap32_set( return error; /* Do we have a left-adjacent extent? */ - left = xbitmap32_tree_iter_first(&bitmap->xb_root, start - 1, start - 1); + if (start > 0) + left = xbitmap32_tree_iter_first(&bitmap->xb_root, start - 1, + start - 1); ASSERT(!left || left->bn_last + 1 == start); /* Do we have a right-adjacent extent? */ - right = xbitmap32_tree_iter_first(&bitmap->xb_root, last + 1, last + 1); + if (last < U32_MAX) + right = xbitmap32_tree_iter_first(&bitmap->xb_root, last + 1, + last + 1); ASSERT(!right || right->bn_start == last + 1); if (left && right) { diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c index 2a037aae904d..19d974c7e2b7 100644 --- a/fs/xfs/scrub/dir.c +++ b/fs/xfs/scrub/dir.c @@ -492,7 +492,7 @@ xchk_directory_data_bestfree( goto out; xchk_buffer_recheck(sc, bp); - if (xfs_has_crc(sc->mp)) { + if (!is_block && xfs_has_crc(sc->mp)) { struct xfs_dir3_data_hdr *hdr3 = bp->b_addr; if (hdr3->pad) diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index 9b0ab2316612..887383d2e941 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -1021,7 +1021,7 @@ out: return error; } -/* Does the directory targetted by this scrub have no parents? */ +/* Does the directory targeted by this scrub have no parents? */ bool xchk_dirtree_parentless(const struct xchk_dirtree *dl) { diff --git a/fs/xfs/scrub/findparent.c b/fs/xfs/scrub/findparent.c index eab3ac2704be..d921fe5a9b0c 100644 --- a/fs/xfs/scrub/findparent.c +++ b/fs/xfs/scrub/findparent.c @@ -473,6 +473,9 @@ xrep_findparent_from_dcache( pip = igrab(d_inode(parent)); dput(parent); + if (!pip) + goto out_dput; + if (S_ISDIR(pip->i_mode)) { ret = pip->i_ino; trace_xrep_findparent_from_dcache(sc->ip, ret); diff --git a/fs/xfs/scrub/health.c b/fs/xfs/scrub/health.c index 2171bcf0f6c1..487ecc5f9f3c 100644 --- a/fs/xfs/scrub/health.c +++ b/fs/xfs/scrub/health.c @@ -202,9 +202,9 @@ xchk_update_health( * there's no sick flag defined for it, so we branch here ahead of the * mask check. */ - if (sc->sm->sm_type == XFS_SCRUB_TYPE_HEALTHY && - !(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) { - xchk_mark_all_healthy(sc->mp); + if (sc->sm->sm_type == XFS_SCRUB_TYPE_HEALTHY) { + if (!(sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)) + xchk_mark_all_healthy(sc->mp); return; } diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c index 65b13e311916..46e9bf4a4317 100644 --- a/fs/xfs/scrub/inode.c +++ b/fs/xfs/scrub/inode.c @@ -607,7 +607,7 @@ xchk_dinode( } /* di_forkoff */ - if (XFS_DFORK_BOFF(dip) >= mp->m_sb.sb_inodesize) + if (dip->di_forkoff >= (XFS_LITINO(mp) >> 3)) xchk_ino_set_corrupt(sc, ino); if (naextents != 0 && dip->di_forkoff == 0) xchk_ino_set_corrupt(sc, ino); diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 8bc508336aa5..b87c22146233 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1702,7 +1702,7 @@ xrep_inode_blockcounts( &acount); if (error) return error; - if (count >= sc->mp->m_sb.sb_dblocks) + if (acount >= sc->mp->m_sb.sb_dblocks) return -EFSCORRUPTED; error = xrep_ino_ensure_extent_count(sc, XFS_ATTR_FORK, nextents); diff --git a/fs/xfs/scrub/newbt.c b/fs/xfs/scrub/newbt.c index c82f4631fd9c..584076b2a6ee 100644 --- a/fs/xfs/scrub/newbt.c +++ b/fs/xfs/scrub/newbt.c @@ -193,9 +193,11 @@ xrep_newbt_add_blocks( struct xrep_newbt_resv *resv; int error; - resv = kmalloc_obj(struct xrep_newbt_resv, XCHK_GFP_FLAGS); - if (!resv) - return -ENOMEM; + /* + * We have no way to clean up the allocated space *and* return an + * ENOMEM if we fail to allocate this control structure. + */ + resv = kmalloc_obj(struct xrep_newbt_resv, GFP_KERNEL | __GFP_NOFAIL); INIT_LIST_HEAD(&resv->list); resv->agbno = XFS_FSB_TO_AGBNO(mp, args->fsbno); diff --git a/fs/xfs/scrub/orphanage.c b/fs/xfs/scrub/orphanage.c index 3aca66869b80..21e31eeaa042 100644 --- a/fs/xfs/scrub/orphanage.c +++ b/fs/xfs/scrub/orphanage.c @@ -192,12 +192,16 @@ xrep_orphanage_create( /* Make sure the orphanage is owned by root. */ error = xrep_chown_orphanage(sc, XFS_I(orphanage_inode)); if (error) - goto out_dput_orphanage; + goto out_rele_orphanage; /* Stash the reference for later and bail out. */ sc->orphanage = XFS_I(orphanage_inode); sc->orphanage_ilock_flags = 0; + orphanage_inode = NULL; +out_rele_orphanage: + if (orphanage_inode) + xchk_irele(sc, XFS_I(orphanage_inode)); out_dput_orphanage: end_creating(orphanage_dentry); out_dput_root: diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index f698b9be3dd1..0dfe61bafc6a 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -172,7 +172,7 @@ static inline bool xreap_is_dirty(const struct xreap_state *rs) } /* - * Decide if we need to roll the transaction to clear out the the log + * Decide if we need to roll the transaction to clear out the log * reservation that we allocated to buffer invalidations. */ static inline bool xreap_want_binval_roll(const struct xreap_state *rs) diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index 11697a8b2a1d..c2a437416227 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -399,6 +399,7 @@ xrep_calc_rtgroup_resblks( struct xfs_mount *mp = sc->mp; struct xfs_scrub_metadata *sm = sc->sm; uint64_t usedlen; + xfs_extlen_t refcbt_sz = 0; xfs_extlen_t rmapbt_sz = 0; if (!(sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR)) @@ -411,13 +412,27 @@ xrep_calc_rtgroup_resblks( usedlen = xfs_rtbxlen_to_blen(mp, xfs_rtgroup_extents(mp, sm->sm_agno)); ASSERT(usedlen <= XFS_MAX_RGBLOCKS); + if (xfs_has_reflink(mp)) + refcbt_sz = xfs_rtrefcountbt_calc_size(mp, usedlen); + if (xfs_has_rmapbt(mp)) rmapbt_sz = xfs_rtrmapbt_calc_size(mp, usedlen); + /* + * Guess how many blocks we need to rebuild the rmapbt. For + * non-reflink filesystems we can't have more records than used blocks. + * However, with reflink it's possible to have more than one rmap + * record per rtgroup block. We don't know how many rmaps there could + * be in the rtgroup, so we start off with what we hope is an generous + * over-estimation. + */ + if (refcbt_sz > 0 && rmapbt_sz > 0) + rmapbt_sz *= 2; + trace_xrep_calc_rtgroup_resblks_btsize(mp, sm->sm_agno, usedlen, - rmapbt_sz); + rmapbt_sz, refcbt_sz); - return rmapbt_sz; + return max(rmapbt_sz, refcbt_sz); } #endif /* CONFIG_XFS_RT */ diff --git a/fs/xfs/scrub/rmap.c b/fs/xfs/scrub/rmap.c index 0cd3eecd2ca5..68e2847c962b 100644 --- a/fs/xfs/scrub/rmap.c +++ b/fs/xfs/scrub/rmap.c @@ -493,11 +493,18 @@ out: * If there's an error, set XFAIL and disable the bitmap * cross-referencing checks, but proceed with the scrub anyway. */ - if (error) - xchk_btree_xref_process_error(sc, sc->sa.rmap_cur, - sc->sa.rmap_cur->bc_nlevels - 1, &error); - else - cr->bitmaps_complete = true; + if (error) { + if (!xchk_btree_xref_process_error(sc, sc->sa.rmap_cur, + sc->sa.rmap_cur->bc_nlevels - 1, &error)) { + /* only set incomplete if we didn't set xfail */ + if (error) + xchk_set_incomplete(sc); + } + + return 0; + } + + cr->bitmaps_complete = true; return 0; } @@ -567,7 +574,8 @@ xchk_rmapbt( if (error) goto out; - xchk_rmapbt_check_bitmaps(sc, cr); + if (cr->bitmaps_complete) + xchk_rmapbt_check_bitmaps(sc, cr); out: xagb_bitmap_destroy(&cr->refcbt_owned); diff --git a/fs/xfs/scrub/rmap_repair.c b/fs/xfs/scrub/rmap_repair.c index 590f9f41856e..725035bf4903 100644 --- a/fs/xfs/scrub/rmap_repair.c +++ b/fs/xfs/scrub/rmap_repair.c @@ -1109,6 +1109,7 @@ xrep_rmap_try_reserve( return error; error = xfs_agfl_walk(sc->mp, agf, agfl_bp, xrep_rmap_walk_agfl, &ra); + xfs_trans_brelse(sc->tp, agfl_bp); if (error) return error; diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h index 737a5d6db15f..b093945f3631 100644 --- a/fs/xfs/scrub/scrub.h +++ b/fs/xfs/scrub/scrub.h @@ -40,7 +40,7 @@ static inline int xchk_maybe_relax(struct xchk_relax *widget) return 0; widget->resched_nr = 0; - if (unlikely(widget->next_resched <= jiffies)) { + if (unlikely(time_after_eq(jiffies, widget->next_resched))) { cond_resched(); widget->next_resched = XCHK_RELAX_NEXT; } diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 0f5adc293962..cb85f75ce101 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -2376,25 +2376,29 @@ TRACE_EVENT(xrep_calc_ag_resblks_btsize, #ifdef CONFIG_XFS_RT TRACE_EVENT(xrep_calc_rtgroup_resblks_btsize, TP_PROTO(struct xfs_mount *mp, xfs_rgnumber_t rgno, - xfs_rgblock_t usedlen, xfs_rgblock_t rmapbt_sz), - TP_ARGS(mp, rgno, usedlen, rmapbt_sz), + xfs_rgblock_t usedlen, xfs_rgblock_t rmapbt_sz, + xfs_rgblock_t refcbt_sz), + TP_ARGS(mp, rgno, usedlen, rmapbt_sz, refcbt_sz), TP_STRUCT__entry( __field(dev_t, dev) __field(xfs_rgnumber_t, rgno) __field(xfs_rgblock_t, usedlen) __field(xfs_rgblock_t, rmapbt_sz) + __field(xfs_rgblock_t, refcbt_sz) ), TP_fast_assign( __entry->dev = mp->m_super->s_dev; __entry->rgno = rgno; __entry->usedlen = usedlen; __entry->rmapbt_sz = rmapbt_sz; + __entry->refcbt_sz = refcbt_sz; ), - TP_printk("dev %d:%d rgno 0x%x usedlen %u rmapbt %u", + TP_printk("dev %d:%d rgno 0x%x usedlen %u rmapbt %u refcountbt %u", MAJOR(__entry->dev), MINOR(__entry->dev), __entry->rgno, __entry->usedlen, - __entry->rmapbt_sz) + __entry->rmapbt_sz, + __entry->refcbt_sz) ); #endif /* CONFIG_XFS_RT */ diff --git a/fs/xfs/xfs_bmap_item.c b/fs/xfs/xfs_bmap_item.c index 89f6e79a955f..aa5b41629747 100644 --- a/fs/xfs/xfs_bmap_item.c +++ b/fs/xfs/xfs_bmap_item.c @@ -339,7 +339,7 @@ xfs_bmap_update_get_group( /* * Bump the intent count on behalf of the deferred rmap and refcount - * intent items that that we can queue when we finish this bmap work. + * intent items that we can queue when we finish this bmap work. * This new intent item will bump the intent count before the bmap * intent drops the intent count, ensuring that the intent count * remains nonzero across the transaction roll. diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c index b4f6c594808c..e696ee36c2e8 100644 --- a/fs/xfs/xfs_dquot.c +++ b/fs/xfs/xfs_dquot.c @@ -139,10 +139,14 @@ xfs_qm_adjust_dqlimits( dq->q_ino.softlimit = defq->ino.soft; if (!dq->q_ino.hardlimit) dq->q_ino.hardlimit = defq->ino.hard; - if (!dq->q_rtb.softlimit) + if (!dq->q_rtb.softlimit) { dq->q_rtb.softlimit = defq->rtb.soft; - if (!dq->q_rtb.hardlimit) + prealloc = 1; + } + if (!dq->q_rtb.hardlimit) { dq->q_rtb.hardlimit = defq->rtb.hard; + prealloc = 1; + } if (prealloc) xfs_dquot_set_prealloc_limits(dq); diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index c69ecd6a19de..fafb4e3f065c 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -633,6 +633,9 @@ xfs_exchrange_prep( if (error) return error; + if (fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN) + return 0; + trace_xfs_exchrange_flush(fxr, ip1, ip2); /* Flush the relevant ranges of both files. */ @@ -709,9 +712,11 @@ xfs_exchrange_contents( * other file write would do. This may involve turning on support for * logged xattrs if either file has security capabilities. */ - error = xfs_exchange_range_finish(fxr); - if (error) - goto out_unlock; + if (!(fxr->flags & XFS_EXCHANGE_RANGE_DRY_RUN)) { + error = xfs_exchange_range_finish(fxr); + if (error) + goto out_unlock; + } out_unlock: xfs_iunlock2_io_mmap(ip1, ip2); @@ -902,7 +907,7 @@ xfs_ioc_commit_range( if (copy_from_user(&args, argp, sizeof(args))) return -EFAULT; - if (args.flags & ~XFS_EXCHANGE_RANGE_ALL_FLAGS) + if (args.pad || (args.flags & ~XFS_EXCHANGE_RANGE_ALL_FLAGS)) return -EINVAL; if (kern_f->magic != XCR_FRESH_MAGIC) return -EBUSY; diff --git a/fs/xfs/xfs_healthmon.c b/fs/xfs/xfs_healthmon.c index c3749675ef19..2bdd747f1ead 100644 --- a/fs/xfs/xfs_healthmon.c +++ b/fs/xfs/xfs_healthmon.c @@ -247,7 +247,9 @@ xfs_healthmon_merge_events( case XFS_HEALTHMON_DIOWRITE: case XFS_HEALTHMON_DATALOST: /* logically adjacent file ranges can merge */ - if (existing->fino != new->fino || existing->fgen != new->fgen) + if (existing->fino != new->fino || + existing->fgen != new->fgen || + existing->error != new->error) return false; if (existing->fpos + existing->flen == new->fpos) { diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c index 82dac88e3c4c..de8be344e987 100644 --- a/fs/xfs/xfs_icache.c +++ b/fs/xfs/xfs_icache.c @@ -1653,7 +1653,7 @@ xfs_blockgc_free_dquots( do_work = true; } - if (XFS_IS_UQUOTA_ENFORCED(mp) && gdqp && xfs_dquot_lowsp(gdqp)) { + if (XFS_IS_GQUOTA_ENFORCED(mp) && gdqp && xfs_dquot_lowsp(gdqp)) { icw.icw_gid = make_kgid(mp->m_super->s_user_ns, gdqp->q_id); icw.icw_flags |= XFS_ICWALK_FLAG_GID; do_work = true; diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 030a7c8f2c12..621513d7215e 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -2669,7 +2669,7 @@ xfs_irele( } /* - * Ensure all commited transactions touching the inode are written to the log. + * Ensure all committed transactions touching the inode are written to the log. */ int xfs_log_force_inode( diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index f9e07a32f60f..9446ac44ba88 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1370,7 +1370,7 @@ xlog_cil_cleanup_whiteouts( * allocation context. However, we do not want to block on memory reclaim * recursing back into the filesystem because this push may have been triggered * by memory reclaim itself. Hence we really need to run under full GFP_NOFS - * contraints here. + * constraints here. */ static void xlog_cil_push_work( diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index e7e49529658b..cf0d610265fe 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -2736,12 +2736,13 @@ xlog_recover_iunlink_bucket( { struct xfs_mount *mp = pag_mount(pag); struct xfs_inode *prev_ip = NULL; - struct xfs_inode *ip; xfs_agino_t prev_agino, agino; int error = 0; agino = be32_to_cpu(agi->agi_unlinked[bucket]); while (agino != NULLAGINO) { + struct xfs_inode *ip; + error = xfs_iget(mp, NULL, xfs_agino_to_ino(pag, agino), 0, 0, &ip); if (error) @@ -2750,11 +2751,11 @@ xlog_recover_iunlink_bucket( ASSERT(VFS_I(ip)->i_nlink == 0); ASSERT(VFS_I(ip)->i_mode != 0); xfs_iflags_clear(ip, XFS_IRECOVERY); - agino = ip->i_next_unlinked; if (prev_ip) { ip->i_prev_unlinked = prev_agino; xfs_irele(prev_ip); + prev_ip = NULL; /* * Ensure the inode is removed from the unlinked list @@ -2766,18 +2767,20 @@ xlog_recover_iunlink_bucket( * complete. */ error = xfs_inodegc_flush(mp); - if (error) - break; + if (error) { + xfs_irele(ip); + return error; + } } prev_agino = agino; + agino = ip->i_next_unlinked; prev_ip = ip; } if (prev_ip) { int error2; - ip->i_prev_unlinked = prev_agino; xfs_irele(prev_ip); error2 = xfs_inodegc_flush(mp); diff --git a/fs/xfs/xfs_platform.h b/fs/xfs/xfs_platform.h index 5d542e95fe44..745d715b4c64 100644 --- a/fs/xfs/xfs_platform.h +++ b/fs/xfs/xfs_platform.h @@ -153,7 +153,7 @@ static inline void delay(long ticks) /* * XFS wrapper structure for sysfs support. It depends on external data * structures and is embedded in various internal data structures to implement - * the XFS sysfs object heirarchy. Define it here for broad access throughout + * the XFS sysfs object hierarchy. Define it here for broad access throughout * the codebase. */ struct xfs_kobj { diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 99a82107b8e6..54d00d543b51 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -1432,16 +1432,22 @@ xfs_qm_flush_one( error = xfs_dquot_use_attached_buf(dqp, &bp); if (error) - goto out_unlock; + goto out_dqflock; if (!bp) { error = -EFSCORRUPTED; - goto out_unlock; + goto out_dqflock; } error = xfs_qm_dqflush(dqp, bp); if (!error) xfs_buf_delwri_queue(bp, buffer_list); xfs_buf_relse(bp); + mutex_unlock(&dqp->q_qlock); + xfs_qm_dqrele(dqp); + return error; + +out_dqflock: + xfs_dqfunlock(dqp); out_unlock: mutex_unlock(&dqp->q_qlock); xfs_qm_dqrele(dqp); diff --git a/fs/xfs/xfs_refcount_item.c b/fs/xfs/xfs_refcount_item.c index 8bccf89a7766..682c6e1b45e3 100644 --- a/fs/xfs/xfs_refcount_item.c +++ b/fs/xfs/xfs_refcount_item.c @@ -508,6 +508,7 @@ xfs_refcount_recover_work( struct xfs_cui_log_item *cuip = CUI_ITEM(lip); struct xfs_trans *tp; struct xfs_mount *mp = lip->li_log->l_mp; + unsigned int dblocks; bool isrt = xfs_cui_item_isrt(lip); int i; int error = 0; @@ -543,8 +544,11 @@ xfs_refcount_recover_work( * full btree split on either end of the refcount range. */ resv = xlog_recover_resv(&M_RES(mp)->tr_itruncate); - error = xfs_trans_alloc(mp, &resv, mp->m_refc_maxlevels * 2, 0, - XFS_TRANS_RESERVE, &tp); + if (isrt) + dblocks = mp->m_rtrefc_maxlevels * 2; + else + dblocks = mp->m_refc_maxlevels * 2; + error = xfs_trans_alloc(mp, &resv, dblocks, 0, XFS_TRANS_RESERVE, &tp); if (error) return error; diff --git a/fs/xfs/xfs_reflink.h b/fs/xfs/xfs_reflink.h index 9d1ed9bb0bee..683c1841e640 100644 --- a/fs/xfs/xfs_reflink.h +++ b/fs/xfs/xfs_reflink.h @@ -48,9 +48,6 @@ extern int xfs_reflink_end_cow(struct xfs_inode *ip, xfs_off_t offset, int xfs_reflink_end_atomic_cow(struct xfs_inode *ip, xfs_off_t offset, xfs_off_t count); extern int xfs_reflink_recover_cow(struct xfs_mount *mp); -extern loff_t xfs_reflink_remap_range(struct file *file_in, loff_t pos_in, - struct file *file_out, loff_t pos_out, loff_t len, - unsigned int remap_flags); extern int xfs_reflink_inode_has_shared_extents(struct xfs_trans *tp, struct xfs_inode *ip, bool *has_shared); extern int xfs_reflink_clear_inode_flag(struct xfs_inode *ip, diff --git a/fs/xfs/xfs_rmap_item.c b/fs/xfs/xfs_rmap_item.c index 2a3a73a8566d..000cff1ce324 100644 --- a/fs/xfs/xfs_rmap_item.c +++ b/fs/xfs/xfs_rmap_item.c @@ -573,6 +573,7 @@ xfs_rmap_recover_work( struct xfs_rui_log_item *ruip = RUI_ITEM(lip); struct xfs_trans *tp; struct xfs_mount *mp = lip->li_log->l_mp; + unsigned int dblocks; bool isrt = xfs_rui_item_isrt(lip); int i; int error = 0; @@ -596,8 +597,11 @@ xfs_rmap_recover_work( } resv = xlog_recover_resv(&M_RES(mp)->tr_itruncate); - error = xfs_trans_alloc(mp, &resv, mp->m_rmap_maxlevels, 0, - XFS_TRANS_RESERVE, &tp); + if (isrt) + dblocks = mp->m_rtrmap_maxlevels; + else + dblocks = mp->m_rmap_maxlevels; + error = xfs_trans_alloc(mp, &resv, dblocks, 0, XFS_TRANS_RESERVE, &tp); if (error) return error; diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index 28c1e48909fa..b75cf3bfe33c 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -820,7 +820,7 @@ xfs_get_cached_zone( spin_unlock(&ip->i_flags_lock); } - if (!atomic_inc_not_zero(&oz->oz_ref)) + if (oz && !atomic_inc_not_zero(&oz->oz_ref)) oz = NULL; out_unlock: rcu_read_unlock(); @@ -828,7 +828,7 @@ out_unlock: } /* - * Stash our zone in the inode so that is is reused for future allocations. + * Stash our zone in the inode so that it is reused for future allocations. * * The open_zone structure will be pinned until either the inode is freed or * until the cached open zone is replaced with a different one because the diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index 5fdcf98a2133..54b70ed2922f 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -46,7 +46,7 @@ * before remapping. * * Once a zone does not contain any valid data, be that through GC or user - * block removal, it is queued for for a zone reset. The reset operation + * block removal, it is queued for a zone reset. The reset operation * carefully ensures that the RT device cache is flushed and all transactions * referencing the rmap have been committed to disk. */ 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/bpf.h b/include/linux/bpf.h index e57af902560c..1d2676782d70 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1651,8 +1651,9 @@ static inline void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags struct bpf_func_info_aux { u16 linkage; bool unreliable; - bool called : 1; - bool verified : 1; + /* Indexed by in_sleepable. */ + bool called[2]; + bool verified[2]; }; enum bpf_jit_poke_reason { diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 36b65797877d..b83ec99f1a13 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -45,18 +45,20 @@ struct bpf_reg_state { union { /* valid when type == PTR_TO_PACKET */ int range; + /* + * Valid when type == PTR_TO_STACK. Inside the callee two registers + * can be both PTR_TO_STACK like R1=fp-8 and R2=fp-8, but one of them + * points to this function stack while another to the caller's stack. + * To differentiate them 'frameno' is used which is an index in + * bpf_verifier_state->frame[] array pointing to bpf_func_state. + */ + u8 frameno; - /* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE | - * PTR_TO_MAP_VALUE_OR_NULL + /* + * For CONST_PTR_TO_MAP, PTR_TO_MAP_KEY, PTR_TO_MAP_VALUE and + * PTR_TO_INSN. */ - struct { - struct bpf_map *map_ptr; - /* To distinguish map lookups from outer map - * the map_uid is non-zero for registers - * pointing to inner maps. - */ - u32 map_uid; - }; + struct bpf_map *map_ptr; /* for PTR_TO_BTF_ID */ struct { @@ -155,13 +157,12 @@ struct bpf_reg_state { * gets parent_id set to the dynptr's id. */ u32 parent_id; - /* Inside the callee two registers can be both PTR_TO_STACK like - * R1=fp-8 and R2=fp-8, but one of them points to this function stack - * while another to the caller's stack. To differentiate them 'frameno' - * is used which is an index in bpf_verifier_state->frame[] array - * pointing to bpf_func_state. + /* + * Distinguishes inner-map lookups and their keys and values. Zero for + * other registers. Kept outside the metadata union for ID remapping + * during state comparisons. */ - u32 frameno; + u32 map_uid; /* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */ bool precise; }; @@ -679,6 +680,7 @@ struct bpf_insn_aux_data { bool nospec_result; /* result is unsafe under speculation, nospec must follow */ bool zext_dst; /* this insn zero extends dst reg */ bool needs_zext; /* alu op needs to clear upper bits */ + bool prevent_zext; /* alu op cannot be zext (already used with 64-bit scalars) */ bool non_sleepable; /* helper/kfunc may be called from non-sleepable context */ bool is_iter_next; /* bpf_iter_<type>_next() kfunc call */ bool call_with_percpu_alloc_ptr; /* {this,per}_cpu_ptr() with prog percpu alloc */ @@ -1197,6 +1199,8 @@ static inline void bpf_trampoline_unpack_key(u64 key, u32 *obj_id, u32 *btf_id) int bpf_prepare_btf_info(struct bpf_verifier_env *env, const union bpf_attr *attr, bpfptr_t uattr); +int bpf_check_core_relo(struct bpf_verifier_env *env, + const union bpf_attr *attr, bpfptr_t uattr); int bpf_check_btf_info(struct bpf_verifier_env *env, const union bpf_attr *attr, bpfptr_t uattr); @@ -1236,11 +1240,18 @@ static inline int bpf_get_spi(s32 off) return (-off - 1) / BPF_REG_SIZE; } +/* + * Return the function state a stack pointer register refers to. frameno + * shares storage with other pointer metadata, so return NULL for any + * other register type instead of indexing frame[] with aliased bytes. + */ static inline struct bpf_func_state *bpf_func(struct bpf_verifier_env *env, const struct bpf_reg_state *reg) { struct bpf_verifier_state *cur = env->cur_state; + if (reg->type != PTR_TO_STACK) + return NULL; return cur->frame[reg->frameno]; } diff --git a/include/linux/btf.h b/include/linux/btf.h index 89d5a5c4f117..7c62ea17b116 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -80,6 +80,7 @@ #define KF_ARENA_ARG2 (1 << 15) /* kfunc takes an arena pointer as its second argument */ #define KF_IMPLICIT_ARGS (1 << 16) /* kfunc has implicit arguments supplied by the verifier */ #define KF_SPINLOCK_SAFE (1 << 17) /* kfunc is allowed inside bpf_spin_lock-ed region */ +#define KF_PERFMON (1 << 18) /* kfunc requires CAP_PERFMON */ /* * Tag marking a kernel function as a kfunc. This is meant to minimize the 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/ethtool.h b/include/linux/ethtool.h index 253600c0eccd..c4c9ce038611 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -944,6 +944,7 @@ struct kernel_ethtool_ts_info { #define ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM BIT(6) #define ETHTOOL_OP_NEEDS_RTNL_RSS BIT(7) #define ETHTOOL_OP_NEEDS_RTNL_GLINK BIT(8) +#define ETHTOOL_OP_NEEDS_RTNL_TEST BIT(9) /** * struct ethtool_ops - optional netdev operations @@ -981,6 +982,7 @@ struct kernel_ethtool_ts_info { * - netdev_update_features() * - netif_set_real_num_tx_queues() * - ethtool_op_get_link() (syncs link watch under rtnl_lock) + * - netif_open() / netif_close() (used by @self_test) * * @get_drvinfo: Report driver/device information. Modern drivers no * longer have to implement this callback. Most fields are 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/linux/if_vlan.h b/include/linux/if_vlan.h index 20cc16ea4e5a..4846032bf4ff 100644 --- a/include/linux/if_vlan.h +++ b/include/linux/if_vlan.h @@ -365,6 +365,9 @@ static inline int __vlan_insert_inner_tag(struct sk_buff *skb, const u8 meta_len = mac_len > ETH_TLEN ? skb_metadata_len(skb) : 0; struct vlan_ethhdr *veth; + if (unlikely(!pskb_may_pull(skb, mac_len))) + return -EINVAL; + if (skb_cow_head(skb, meta_len + VLAN_HLEN) < 0) return -ENOMEM; diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 03bfc92864b6..cf7fe835c4ad 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -2324,13 +2324,18 @@ static inline bool kvm_test_request(int req, struct kvm_vcpu *vcpu) return test_bit(req & KVM_REQUEST_MASK, (void *)&vcpu->requests); } -static inline void kvm_clear_request(int req, struct kvm_vcpu *vcpu) +static __always_inline void kvm_clear_request(int req, struct kvm_vcpu *vcpu) { + BUILD_BUG_ON(req == KVM_REQ_VM_DEAD); + clear_bit(req & KVM_REQUEST_MASK, (void *)&vcpu->requests); } -static inline bool kvm_check_request(int req, struct kvm_vcpu *vcpu) +static __always_inline bool kvm_check_request(int req, struct kvm_vcpu *vcpu) { + /* Once a VM is dead, it needs to stay dead. */ + BUILD_BUG_ON(req == KVM_REQ_VM_DEAD); + if (kvm_test_request(req, vcpu)) { kvm_clear_request(req, vcpu); diff --git a/include/linux/libata.h b/include/linux/libata.h index 313e96173b19..48bde275968d 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -76,7 +76,7 @@ enum ata_quirks { __ATA_QUIRK_MAX_SEC, /* Limit max sectors */ __ATA_QUIRK_MAX_TRIM_128M, /* Limit max trim size to 128M */ __ATA_QUIRK_NO_NCQ_ON_ATI, /* Disable NCQ on ATI chipset */ - __ATA_QUIRK_NO_LPM_ON_ATI, /* Disable LPM on ATI chipset */ + __ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD, /* Disable LPM on ATI and AMD chipsets */ __ATA_QUIRK_NO_ID_DEV_LOG, /* Identify device log missing */ __ATA_QUIRK_NO_LOG_DIR, /* Do not read log directory */ __ATA_QUIRK_NO_FUA, /* Do not use FUA */ @@ -115,7 +115,7 @@ enum { ATA_QUIRK_MAX_SEC = BIT_ULL(__ATA_QUIRK_MAX_SEC), ATA_QUIRK_MAX_TRIM_128M = BIT_ULL(__ATA_QUIRK_MAX_TRIM_128M), ATA_QUIRK_NO_NCQ_ON_ATI = BIT_ULL(__ATA_QUIRK_NO_NCQ_ON_ATI), - ATA_QUIRK_NO_LPM_ON_ATI = BIT_ULL(__ATA_QUIRK_NO_LPM_ON_ATI), + ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD = BIT_ULL(__ATA_QUIRK_NO_LPM_ON_ATI_AND_AMD), ATA_QUIRK_NO_ID_DEV_LOG = BIT_ULL(__ATA_QUIRK_NO_ID_DEV_LOG), ATA_QUIRK_NO_LOG_DIR = BIT_ULL(__ATA_QUIRK_NO_LOG_DIR), ATA_QUIRK_NO_FUA = BIT_ULL(__ATA_QUIRK_NO_FUA), diff --git a/include/linux/mempool.h b/include/linux/mempool.h index a0fa6d43e0dc..6da502aef2f7 100644 --- a/include/linux/mempool.h +++ b/include/linux/mempool.h @@ -70,6 +70,13 @@ int mempool_alloc_bulk_noprof(struct mempool *pool, void **elem, #define mempool_alloc_bulk(...) \ alloc_hooks(mempool_alloc_bulk_noprof(__VA_ARGS__)) +/* + * Allocate a new element without dipping into the pool's reserves or + * waiting. Returns NULL on failure. + */ +#define mempool_alloc_noreserve(_pool, _gfp) \ + alloc_hooks((_pool)->alloc(_gfp, (_pool)->pool_data)) + void *mempool_alloc_preallocated(struct mempool *pool) __malloc; void mempool_free(void *element, struct mempool *pool); unsigned int mempool_free_bulk(struct mempool *pool, void **elem, diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 6d815f6440c9..f3e5a2fadbe5 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -1226,7 +1226,7 @@ struct mm_struct { struct mm_mm_cid mm_cid; /* sched_cache related statistics */ - struct sched_cache_stat sc_stat; + struct sched_cache_group *sched_cache_grp; #ifdef CONFIG_MMU atomic_long_t pgtables_bytes; /* size of all page tables */ #endif @@ -1624,8 +1624,9 @@ static inline unsigned int mm_cid_size(void) #endif /* CONFIG_SCHED_MM_CID */ #ifdef CONFIG_SCHED_CACHE -void mm_init_sched(struct mm_struct *mm, - struct sched_cache_time __percpu *pcpu_sched); +int mm_init_sched(struct mm_struct *mm, + struct sched_cache_time __percpu *pcpu_sched); +void mm_destroy_sched(struct mm_struct *mm); static inline int mm_alloc_sched_noprof(struct mm_struct *mm) { @@ -1635,17 +1636,11 @@ static inline int mm_alloc_sched_noprof(struct mm_struct *mm) if (!pcpu_sched) return -ENOMEM; - mm_init_sched(mm, pcpu_sched); - return 0; + return mm_init_sched(mm, pcpu_sched); } #define mm_alloc_sched(...) alloc_hooks(mm_alloc_sched_noprof(__VA_ARGS__)) -static inline void mm_destroy_sched(struct mm_struct *mm) -{ - free_percpu(mm->sc_stat.pcpu_sched); - mm->sc_stat.pcpu_sched = NULL; -} #else /* !CONFIG_SCHED_CACHE */ static inline int mm_alloc_sched(struct mm_struct *mm) { return 0; } diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 5842552294c1..915c6fd3f084 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1467,23 +1467,6 @@ static inline u32 perf_sample_data_size(struct perf_sample_data *data, return size; } -/* - * Clear all bitfields in the perf_branch_entry. - * The to and from fields are not cleared because they are - * systematically modified by caller. - */ -static inline void perf_clear_branch_entry_bitfields(struct perf_branch_entry *br) -{ - br->mispred = 0; - br->predicted = 0; - br->in_tx = 0; - br->abort = 0; - br->cycles = 0; - br->type = 0; - br->spec = PERF_BR_SPEC_NA; - br->reserved = 0; -} - extern void perf_output_sample(struct perf_output_handle *handle, struct perf_event_header *header, struct perf_sample_data *data, diff --git a/include/linux/sched.h b/include/linux/sched.h index 705970d07614..d35ae49a991f 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1433,6 +1433,7 @@ struct task_struct { #ifdef CONFIG_SCHED_CACHE struct callback_head cache_work; + struct sched_cache_group __rcu *sched_cache_grp; int preferred_llc; /* 1: task was enqueued to its preferred LLC, 0 otherwise */ int pref_llc_queued; @@ -2405,7 +2406,7 @@ struct sched_cache_time { unsigned long epoch; }; -struct sched_cache_stat { +struct sched_cache_group { struct sched_cache_time __percpu *pcpu_sched; raw_spinlock_t lock; unsigned long epoch; @@ -2413,11 +2414,26 @@ struct sched_cache_stat { unsigned long next_scan; unsigned long footprint; int cpu; + refcount_t refcnt; + struct rcu_head rcu; } ____cacheline_aligned_in_smp; +struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp); +struct sched_cache_group *task_cache_group_get(struct task_struct *p); + +void sched_cache_fork(struct task_struct *p); +void sched_cache_fork_cleanup(struct task_struct *p); +void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm); +void sched_cache_exit_mm(struct task_struct *p); + #else -struct sched_cache_stat { }; +struct sched_cache_group { }; + +static inline void sched_cache_fork(struct task_struct *p) { } +static inline void sched_cache_fork_cleanup(struct task_struct *p) { } +static inline void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm) { } +static inline void sched_cache_exit_mm(struct task_struct *p) { } #endif diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h index b5d9d7c2b8ad..f96812d71c51 100644 --- a/include/linux/sched/topology.h +++ b/include/linux/sched/topology.h @@ -281,9 +281,9 @@ static inline int task_node(const struct task_struct *p) } #ifdef CONFIG_SCHED_CACHE -extern void sched_update_llc_bytes(unsigned int cpu); +extern void sched_update_llc_bytes(const struct cpumask *cpus); #else -static inline void sched_update_llc_bytes(unsigned int cpu) { } +static inline void sched_update_llc_bytes(const struct cpumask *cpus) { } #endif #endif /* _LINUX_SCHED_TOPOLOGY_H */ diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 421f6fc45451..84308498a3a8 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -1834,22 +1834,6 @@ static inline void skb_zcopy_set(struct sk_buff *skb, struct ubuf_info *uarg, } } -static inline void skb_zcopy_set_nouarg(struct sk_buff *skb, void *val) -{ - skb_shinfo(skb)->destructor_arg = (void *)((uintptr_t) val | 0x1UL); - skb_shinfo(skb)->flags |= SKBFL_ZEROCOPY_FRAG; -} - -static inline bool skb_zcopy_is_nouarg(struct sk_buff *skb) -{ - return (uintptr_t) skb_shinfo(skb)->destructor_arg & 0x1UL; -} - -static inline void *skb_zcopy_get_nouarg(struct sk_buff *skb) -{ - return (void *)((uintptr_t) skb_shinfo(skb)->destructor_arg & ~0x1UL); -} - static inline void net_zcopy_put(struct ubuf_info *uarg) { if (uarg) @@ -1872,8 +1856,7 @@ static inline void skb_zcopy_clear(struct sk_buff *skb, bool zerocopy_success) struct ubuf_info *uarg = skb_zcopy(skb); if (uarg) { - if (!skb_zcopy_is_nouarg(skb)) - uarg->ops->complete(skb, uarg, zerocopy_success); + uarg->ops->complete(skb, uarg, zerocopy_success); skb_shinfo(skb)->flags &= ~SKBFL_ALL_ZEROCOPY; } @@ -4372,7 +4355,10 @@ skb_header_pointer_careful(const struct sk_buff *skb, int offset, static inline void * __must_check skb_pointer_if_linear(const struct sk_buff *skb, int offset, int len) { - if (likely(skb_headlen(skb) - offset >= len)) + unsigned int uoffset = (unsigned int)offset; + + if (likely(uoffset <= skb_headlen(skb) && + (unsigned int)len <= skb_headlen(skb) - uoffset)) return skb->data + offset; return NULL; } diff --git a/include/net/dst.h b/include/net/dst.h index 307073eae7f8..dbedfe72e1fd 100644 --- a/include/net/dst.h +++ b/include/net/dst.h @@ -455,7 +455,8 @@ static inline unsigned int dst_dev_overhead(struct dst_entry *dst, struct sk_buff *skb) { if (likely(dst)) - return LL_RESERVED_SPACE(dst->dev); + return max_t(unsigned int, skb->mac_len, + LL_RESERVED_SPACE(dst->dev)); return skb->mac_len; } diff --git a/include/net/gue.h b/include/net/gue.h index caefd6da8693..d377155fd0b3 100644 --- a/include/net/gue.h +++ b/include/net/gue.h @@ -84,8 +84,9 @@ static inline size_t guehdr_priv_flags_len(__be32 flags) } /* Validate standard and private flags. Returns non-zero (meaning invalid) - * if there is an unknown standard or private flags, or the options length for - * the flags exceeds the options length specific in hlen of the GUE header. + * if there is an unknown standard or private flags, if the options length for + * the flags exceeds the options length specified in hlen of the GUE header, or + * if a private option contains invalid data. */ static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen) { @@ -103,8 +104,8 @@ static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen) /* Private flags are last four bytes accounted in * guehdr_flags_len */ - __be32 pflags = *(__be32 *)((void *)&guehdr[1] + - len - GUE_LEN_PRIV); + void *data = (void *)&guehdr[1] + len; + __be32 pflags = *(__be32 *)(data - GUE_LEN_PRIV); if (pflags & ~GUE_PFLAGS_ALL) return 1; @@ -112,6 +113,16 @@ static inline int validate_gue_flags(struct guehdr *guehdr, size_t optlen) len += guehdr_priv_flags_len(pflags); if (len > optlen) return 1; + + if (pflags & GUE_PFLAG_REMCSUM) { + __be16 *pd = data; + + /* The field offset pd[1] must not be less + * than the start pd[0]. + */ + if (ntohs(pd[1]) < ntohs(pd[0])) + return 1; + } } return 0; diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index b9e8d2b759e9..0f9b7a260d25 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -101,12 +101,12 @@ static inline struct dst_entry *ip6_route_output(struct net *net, } /* Only conditionally release dst if flags indicates - * !RT6_LOOKUP_F_DST_NOREF or dst is in uncached_list. + * !RT6_LOOKUP_F_DST_NOREF or dst is uncached. */ static inline void ip6_rt_put_flags(struct rt6_info *rt, int flags) { if (!(flags & RT6_LOOKUP_F_DST_NOREF) || - !list_empty(&rt->dst.rt_uncached)) + rt->dst.rt_uncached_list) ip6_rt_put(rt); } diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h index bc42dd0e10e6..c39425e54d87 100644 --- a/include/net/netfilter/nf_conntrack.h +++ b/include/net/netfilter/nf_conntrack.h @@ -185,6 +185,11 @@ static inline void nf_ct_put(struct nf_conn *ct) nf_ct_destroy(&ct->ct_general); } +static inline bool nf_ct_shared(const struct nf_conn *ct) +{ + return refcount_read(&ct->ct_general.use) > 1; +} + /* load module; enable/disable conntrack in this namespace */ int nf_ct_netns_get(struct net *net, u8 nfproto); void nf_ct_netns_put(struct net *net, u8 nfproto); diff --git a/include/net/nfc/hci.h b/include/net/nfc/hci.h index 756c11084f65..86ed63e5d533 100644 --- a/include/net/nfc/hci.h +++ b/include/net/nfc/hci.h @@ -144,7 +144,7 @@ struct nfc_hci_dev { data_exchange_cb_t async_cb; void *async_cb_context; - u8 *gb; + u8 gb[NFC_MAX_GT_LEN]; size_t gb_len; unsigned long quirks; diff --git a/include/net/nfc/nfc.h b/include/net/nfc/nfc.h index c54df042db6b..bcafab5c53e5 100644 --- a/include/net/nfc/nfc.h +++ b/include/net/nfc/nfc.h @@ -273,7 +273,8 @@ struct sk_buff *nfc_alloc_recv_skb(unsigned int size, gfp_t gfp); int nfc_set_remote_general_bytes(struct nfc_dev *dev, const u8 *gt, u8 gt_len); -u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, size_t *gb_len); +u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, u8 *out_gb, + size_t gb_max_len, size_t *gb_len); int nfc_fw_download_done(struct nfc_dev *dev, const char *firmware_name, u32 result); diff --git a/include/net/tcp.h b/include/net/tcp.h index 436495ff2271..4416cdf9bf30 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1232,9 +1232,9 @@ static inline bool tcp_skb_can_collapse_to(const struct sk_buff *skb) static inline bool tcp_skb_can_collapse(const struct sk_buff *to, const struct sk_buff *from) { - /* skb_cmp_decrypted() not needed, use tcp_write_collapse_fence() */ return likely(tcp_skb_can_collapse_to(to) && mptcp_skb_can_collapse(to, from) && + !skb_cmp_decrypted(to, from) && skb_pure_zcopy_same(to, from) && skb_frags_readable(to) == skb_frags_readable(from)); } @@ -2327,7 +2327,7 @@ static inline void tcp_rtx_queue_unlink_and_free(struct sk_buff *skb, struct soc static inline void tcp_write_collapse_fence(struct sock *sk) { - struct sk_buff *skb = tcp_write_queue_tail(sk); + struct sk_buff *skb = tcp_write_queue_tail(sk) ?: tcp_rtx_queue_tail(sk); if (skb) TCP_SKB_CB(skb)->eor = 1; 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 <linux/completion.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> + +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..6c1acc984ecb 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -258,10 +258,16 @@ 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)) + +#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_to_dapm() - Returns the DAPM context associated with a 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) -{ - return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? - &dai->driver->playback : &dai->driver->capture; -} - -#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_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_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) -{ - return dai->stream[stream].dma_data; -} - -#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) +snd_soc_dai_pcm_stream_get_s(const struct snd_soc_dai *dai, struct snd_pcm_substream *substream) { - dai->stream[stream].dma_data = data; + return snd_soc_dai_pcm_stream_get_i(dai, substream->stream); } - -static inline void snd_soc_dai_init_dma_data(struct snd_soc_dai *dai, void *playback, void *capture) +#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_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_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_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) { - snd_soc_dai_dma_data_set_playback(dai, playback); - snd_soc_dai_dma_data_set_capture(dai, capture); + return snd_soc_dai_stream_dma_data_get_i(dai, substream->stream); } - -static inline unsigned int snd_soc_dai_tdm_mask_get(const struct snd_soc_dai *dai, - int 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_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) { - return dai->stream[stream].tdm_mask; + 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_tdm_mask_set(struct snd_soc_dai *dai, int stream, - unsigned int tdm_mask) -{ - dai->stream[stream].tdm_mask = tdm_mask; -} +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_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; -} - -/** - * 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) +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); + +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..553bb7e2be98 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -555,8 +555,6 @@ 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, @@ -1389,19 +1387,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 <sound/soc-dai.h> static inline @@ -1551,4 +1542,11 @@ static inline void _snd_soc_dpcm_mutex_assert_held_r(struct snd_soc_pcm_runtime #include <sound/soc-card.h> #include <sound/soc-jack.h> +/* + * 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 + #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/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 { @@ -207,6 +212,14 @@ struct tasdevice_rca { */ 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 * store common settings for other non-initialization profiles. diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h index b763da613769..7367c76959aa 100644 --- a/include/sound/tas2781.h +++ b/include/sound/tas2781.h @@ -91,6 +91,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/trace/events/landlock.h b/include/trace/events/landlock.h index d05253afaf59..3a43638c9bc2 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -10,14 +10,19 @@ #if !defined(_TRACE_LANDLOCK_H) || defined(TRACE_HEADER_MULTI_READ) #define _TRACE_LANDLOCK_H +#include <linux/in.h> +#include <linux/in6.h> #include <linux/landlock.h> +#include <linux/socket.h> #include <linux/string.h> #include <linux/string_helpers.h> #include <linux/tracepoint.h> #include <linux/trace_seq.h> #include <net/af_unix.h> +enum landlock_request_type; struct dentry; +struct landlock_blockers; struct landlock_domain; struct landlock_hierarchy; struct landlock_rule; @@ -26,6 +31,12 @@ struct path; struct sock; struct task_struct; +static_assert(sizeof(access_mask_t) <= sizeof(u64)); + +TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY); +TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_FS_ACCESS); +TRACE_DEFINE_ENUM(LANDLOCK_REQUEST_NET_ACCESS); + #ifdef CREATE_TRACE_POINTS /* About 6 KiB, leaving about 2 KiB for sibling helpers and fixed fields. */ @@ -108,7 +119,7 @@ __trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len) * Fills the dense per-domain-layer array layers (one access mask per layer, * indexed by level - 1) from rule's sparse layer stack, keeping only the * requested rights (access_request). Layers with no matching rule entry get - * a zero mask. Shared by the check_rule_fs and check_rule_net events. + * a zero mask. Shared by the check_rule_inode and check_rule_net_port events. * * rule->layers is sorted by ascending level, with levels in the domain's * [1, num_layers] range (see landlock_merge_ruleset()), so every entry maps @@ -182,6 +193,9 @@ static inline const char *__trace_landlock_print_layers( /* Maps a shared _LANDLOCK_*_NAMES entry to a __print_flags() pair. */ #define _LANDLOCK_NAME_ENTRY(mask, name) { mask, name } +#define _LANDLOCK_FS_BLOCKER_TYPE_NAMES \ + { LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, "change_topology" } + /** * DOC: Landlock trace events * @@ -192,22 +206,22 @@ static inline const char *__trace_landlock_print_layers( * Decision context * ~~~~~~~~~~~~~~~~ * - * A denial event, together with the lifecycle events, exposes the full - * set of inputs the verdict consumed, so a consumer that tracked domain - * creation (landlock_create_ruleset, landlock_create_domain) can verify - * or reproduce the Landlock decision rather than merely observe it - * happened. In who/what/why terms: who is the denying domain (the domain - * field, always the subject that enforced the policy, never the current - * task), what is the operation and its object, and why is every other - * input the verdict weighed. + * A denial event identifies the domain whose policy denied the request, the + * Landlock operation and policy object that were checked, and the blocker or + * domain relationship responsible for the denial. When tracing starts with + * sandbox construction, ruleset and domain events provide the policy history + * needed to interpret these identifiers. The denying domain is the subject + * that enforced the policy, not necessarily current. Generic tracepoints can + * provide additional operational context. * * Lifecycle consistency * ~~~~~~~~~~~~~~~~~~~~~~ * - * Lifecycle events are balanced: a creation event always has a matching - * deallocation event and vice versa, so an eBPF program can model object - * lifetimes from the trace stream without reconciliation logic. A creation - * event fires while the object is still private to the calling thread + * Lifecycle emission is balanced: a creation event always has a matching + * deallocation event and vice versa. A consumer that observes an object's + * complete lifetime can model it from this pair; one that attaches late or + * loses exported records must reconcile incomplete state. A creation event + * fires while the object is still private to the calling thread * (landlock_create_ruleset fires before the ruleset's file descriptor is * installed, so it cannot race a concurrent :manpage:`close(2)`); if fd * installation later fails and the ruleset is freed, free_ruleset still @@ -243,8 +257,8 @@ static inline const char *__trace_landlock_print_layers( * Field encoding * ~~~~~~~~~~~~~~ * - * Fields that mirror the Landlock UAPI use the same C types and endianness - * (e.g. network ports are __u64 in host endianness, like + * Fields that mirror the Landlock UAPI preserve their widths and endianness + * (e.g. network ports are u64 in host endianness, like * landlock_net_port_attr.port). Per-event details, such as where a value * is byte-swapped, live in the field's own kdoc. * @@ -281,6 +295,16 @@ static inline const char *__trace_landlock_print_layers( * the two parties without kernel-internal state. The ID is a scalar * snapshot, not a live domain pointer that could dangle: an optional * relational referent is a scalar (0 sentinel), not a nullable pointer. + * Nonzero IDs are unique within one boot. For ptrace, same_exec instead + * describes the tracer, even for + * PTRACE_TRACEME, and may differ from the current task. + * + * Blocker fields + * ~~~~~~~~~~~~~~ + * + * The filesystem and network blocker arguments identify the request type + * and carry its final missing access subset when applicable. The type + * determines how to interpret the access value. */ /* @@ -319,8 +343,8 @@ TRACE_EVENT(landlock_create_ruleset, TP_ARGS(ruleset), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u64, ruleset_version ) __field( access_mask_t, handled_fs ) __field( access_mask_t, handled_net ) __field( access_mask_t, scoped ) @@ -334,7 +358,7 @@ TRACE_EVENT(landlock_create_ruleset, __entry->scoped = ruleset->handled_masks.scope; ), - TP_printk("ruleset=%llx.%u handled_fs=%s handled_net=%s scoped=%s", + TP_printk("ruleset=%llx.%llu handled_fs=%s handled_net=%s scoped=%s", __entry->ruleset_id, __entry->ruleset_version, __print_flags(__entry->handled_fs, "|", _LANDLOCK_ACCESS_FS_NAMES), __print_flags(__entry->handled_net, "|", _LANDLOCK_ACCESS_NET_NAMES), @@ -359,8 +383,8 @@ TRACE_EVENT(landlock_free_ruleset, TP_ARGS(ruleset), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u64, ruleset_version ) ), TP_fast_assign( @@ -368,17 +392,19 @@ TRACE_EVENT(landlock_free_ruleset, __entry->ruleset_version = ruleset->version; ), - TP_printk("ruleset=%llx.%u", + TP_printk("ruleset=%llx.%llu", __entry->ruleset_id, __entry->ruleset_version) ); /** - * landlock_add_rule_fs - Filesystem rule added to a ruleset + * landlock_add_rule_path_beneath - Path-beneath rule added to a ruleset * * @ruleset: Source ruleset (never NULL). - * @access_rights: Effective access mask stored in the rule, not the raw - * sys_landlock_add_rule() argument (unhandled rights - * added). + * @flags: Complete validated landlock_add_rule_flags value supplied by this + * successful call, not the rule's accumulated quiet state. + * @access_rights: Canonical per-call access mask passed to + * landlock_insert_rule() after normalization, not the raw + * sys_landlock_add_rule() argument or accumulated rule. * @path: Filesystem path for the rule (never NULL). * @pathname: Resolved absolute path string (never NULL; error placeholder * on resolution failure). @@ -387,17 +413,17 @@ TRACE_EVENT(landlock_free_ruleset, * the reported ruleset is a stable snapshot that no concurrent writer can * change. */ -TRACE_EVENT(landlock_add_rule_fs, +TRACE_EVENT(landlock_add_rule_path_beneath, - TP_PROTO(const struct landlock_ruleset *ruleset, - access_mask_t access_rights, const struct path *path, + TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags, + u64 access_rights, const struct path *path, const char *pathname), - TP_ARGS(ruleset, access_rights, path, pathname), + TP_ARGS(ruleset, flags, access_rights, path, pathname), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u64, ruleset_version ) __field( access_mask_t, access_rights ) __field( dev_t, dev ) __field( ino_t, ino ) @@ -418,7 +444,7 @@ TRACE_EVENT(landlock_add_rule_fs, __assign_str(pathname); ), - TP_printk("ruleset=%llx.%u access_rights=%s dev=%u:%u ino=%lu path=%s", + TP_printk("ruleset=%llx.%llu access_rights=%s dev=%u:%u ino=%lu path=%s", __entry->ruleset_id, __entry->ruleset_version, __print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_FS_NAMES), MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, @@ -427,31 +453,33 @@ TRACE_EVENT(landlock_add_rule_fs, ); /** - * landlock_add_rule_net - Network port rule added to a ruleset + * landlock_add_rule_net_port - Network-port rule added to a ruleset * * @ruleset: Source ruleset (never NULL). - * @access_rights: Effective access mask stored in the rule, not the raw - * sys_landlock_add_rule() argument (unhandled rights - * added). - * @port: Network port, the landlock_net_port_attr.port UAPI value - * forwarded directly. + * @flags: Complete validated landlock_add_rule_flags value supplied by this + * successful call, not the rule's accumulated quiet state. + * @access_rights: Canonical per-call access mask passed to + * landlock_insert_rule() after normalization, not the raw + * sys_landlock_add_rule() argument or accumulated rule. + * @port: Network port in host endianness, forwarded directly from + * &landlock_net_port_attr.port. * * Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so * the reported ruleset is a stable snapshot that no concurrent writer can * change. */ -TRACE_EVENT(landlock_add_rule_net, +TRACE_EVENT(landlock_add_rule_net_port, - TP_PROTO(const struct landlock_ruleset *ruleset, - access_mask_t access_rights, __u64 port), + TP_PROTO(const struct landlock_ruleset *ruleset, u32 flags, + u64 access_rights, u64 port), - TP_ARGS(ruleset, access_rights, port), + TP_ARGS(ruleset, flags, access_rights, port), TP_STRUCT__entry( - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, ruleset_id ) + __field( u64, ruleset_version ) __field( access_mask_t, access_rights ) - __field( __u64, port ) + __field( u64, port ) ), TP_fast_assign( @@ -462,7 +490,7 @@ TRACE_EVENT(landlock_add_rule_net, __entry->port = port; ), - TP_printk("ruleset=%llx.%u access_rights=%s port=%llu", + TP_printk("ruleset=%llx.%llu access_rights=%s port=%llu", __entry->ruleset_id, __entry->ruleset_version, __print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_NET_NAMES), __entry->port) @@ -495,10 +523,10 @@ TRACE_EVENT(landlock_create_domain, TP_ARGS(domain, ruleset), TP_STRUCT__entry( - __field( __u64, domain_id ) - __field( __u64, parent_id ) - __field( __u64, ruleset_id ) - __field( __u32, ruleset_version ) + __field( u64, domain_id ) + __field( u64, parent_id ) + __field( u64, ruleset_id ) + __field( u64, ruleset_version ) ), TP_fast_assign( @@ -510,7 +538,7 @@ TRACE_EVENT(landlock_create_domain, __entry->ruleset_version = ruleset->version; ), - TP_printk("domain=%llx parent=%llx ruleset=%llx.%u", + TP_printk("domain=%llx parent=%llx ruleset=%llx.%llu", __entry->domain_id, __entry->parent_id, __entry->ruleset_id, __entry->ruleset_version) ); @@ -557,7 +585,7 @@ TRACE_EVENT(landlock_enforce_domain, TP_ARGS(domain, complete, process_wide, no_new_privs), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, complete ) __field( bool, process_wide ) __field( bool, no_new_privs ) @@ -595,8 +623,8 @@ TRACE_EVENT(landlock_free_domain, TP_ARGS(hierarchy), TP_STRUCT__entry( - __field( __u64, domain_id ) - __field( __u64, denials ) + __field( u64, domain_id ) + __field( u64, denials ) ), TP_fast_assign( @@ -609,7 +637,7 @@ TRACE_EVENT(landlock_free_domain, ); /** - * landlock_check_rule_fs - Filesystem rule evaluated during access check + * landlock_check_rule_inode - Inode rule evaluated during access check * * @domain: Enforcing domain (never NULL). * @rule: Matching rule with per-layer access masks (never NULL). @@ -622,16 +650,16 @@ TRACE_EVENT(landlock_free_domain, * domain layer. See Documentation/trace/events-landlock.rst for how to * interpret it. */ -TRACE_EVENT(landlock_check_rule_fs, +TRACE_EVENT(landlock_check_rule_inode, TP_PROTO(const struct landlock_domain *domain, const struct landlock_rule *rule, - access_mask_t access_request, const struct dentry *dentry), + u64 access_request, const struct dentry *dentry), TP_ARGS(domain, rule, access_request, dentry), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( access_mask_t, access_request ) __field( dev_t, dev ) __field( ino_t, ino ) @@ -648,7 +676,8 @@ TRACE_EVENT(landlock_check_rule_fs, __trace_landlock_fill_layers(__get_dynamic_array(grants), __get_dynamic_array_len(grants) / sizeof(access_mask_t), - rule, access_request); + rule, + (access_mask_t)access_request); ), TP_printk("domain=%llx access_request=%s dev=%u:%u ino=%lu grants=%s", @@ -659,7 +688,7 @@ TRACE_EVENT(landlock_check_rule_fs, ); /** - * landlock_check_rule_net - Network port rule evaluated during access check + * landlock_check_rule_net_port - Network port rule evaluated * * @domain: Enforcing domain (never NULL). * @rule: Matching rule with per-layer access masks (never NULL). @@ -671,18 +700,18 @@ TRACE_EVENT(landlock_check_rule_fs, * layer. See Documentation/trace/events-landlock.rst for how to * interpret it. */ -TRACE_EVENT(landlock_check_rule_net, +TRACE_EVENT(landlock_check_rule_net_port, TP_PROTO(const struct landlock_domain *domain, const struct landlock_rule *rule, - access_mask_t access_request, __u64 port), + u64 access_request, u64 port), TP_ARGS(domain, rule, access_request, port), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( access_mask_t, access_request ) - __field( __u64, port ) + __field( u64, port ) __dynamic_array(access_mask_t, grants, domain->num_layers) ), @@ -695,7 +724,8 @@ TRACE_EVENT(landlock_check_rule_net, __trace_landlock_fill_layers(__get_dynamic_array(grants), __get_dynamic_array_len(grants) / sizeof(access_mask_t), - rule, access_request); + rule, + (access_mask_t)access_request); ), TP_printk("domain=%llx access_request=%s port=%llu grants=%s", @@ -712,8 +742,7 @@ TRACE_EVENT(landlock_check_rule_net, * domain field. * @same_exec: Whether the current task entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. - * @blockers: Access mask that was blocked (zero for a mount-topology - * change, whose only blocker is the operation itself). + * @blockers: Request type and final missing access subset (never NULL). * @path: Filesystem path that was denied (never NULL). * @pathname: Resolved path string (never NULL; an error placeholder on * resolution failure). @@ -723,16 +752,17 @@ TRACE_EVENT(landlock_check_rule_net, TRACE_EVENT(landlock_deny_access_fs, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, - bool logged, access_mask_t blockers, const struct path *path, - const char *pathname), + bool logged, const struct landlock_blockers *blockers, + const struct path *path, const char *pathname), TP_ARGS(hierarchy, same_exec, logged, blockers, path, pathname), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( access_mask_t, blockers ) + __field( enum landlock_request_type, blockers_type ) + __field( access_mask_t, blockers_access ) __field( dev_t, dev ) __field( ino_t, ino ) __string( pathname, pathname ) @@ -744,7 +774,8 @@ TRACE_EVENT(landlock_deny_access_fs, __entry->domain_id = hierarchy->id; __entry->same_exec = same_exec; __entry->logged = logged; - __entry->blockers = blockers; + __entry->blockers_type = blockers->type; + __entry->blockers_access = blockers->access; __entry->dev = path->dentry->d_sb->s_dev; /* * A negative dentry has no backing inode, so mirror the @@ -756,12 +787,20 @@ TRACE_EVENT(landlock_deny_access_fs, TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s dev=%u:%u ino=%lu path=%s", __entry->domain_id, __entry->same_exec, __entry->logged, - __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_FS_NAMES), + __entry->blockers_type == LANDLOCK_REQUEST_FS_ACCESS ? + __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_FS_NAMES) : + __print_symbolic(__entry->blockers_type, + _LANDLOCK_FS_BLOCKER_TYPE_NAMES), MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, __trace_print_untrusted_str(p, __get_str(pathname), __get_dynamic_array_len(pathname) - 1)) ); +static_assert(offsetof(struct sockaddr_in, sin_port) == + offsetof(struct sockaddr_in6, sin6_port)); +static_assert(sizeof_field(struct sockaddr_in, sin_port) == + sizeof_field(struct sockaddr_in6, sin6_port)); + /** * landlock_deny_access_net - Network access denied * @@ -769,54 +808,69 @@ TRACE_EVENT(landlock_deny_access_fs, * domain field. * @same_exec: Whether the current task entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. - * @blockers: Access mask that was blocked. - * @sk: Socket object (never NULL), read without a socket lock, so its - * fields are a best-effort snapshot. The denied endpoint is not - * available: the hook runs before :manpage:`bind(2)` / - * :manpage:`connect(2)` sets the socket addresses. - * @sport: Source port in host endianness, set for bind denials (zero for - * an autobind/ephemeral port); zero for connect and send denials. - * @dport: Destination port in host endianness, set for connect and send - * denials; zero for bind denials, and also zero for a UDP send to - * an AF_UNSPEC address on an IPv6 socket (indistinguishable from a - * real destination port 0). The bind-vs-connect direction is - * given by @blockers, not by which port is set. - * - * Emitted when a Landlock domain denies a network operation. - * - * The port fields are converted from the socket's network byte order to - * host endianness before emitting. + * @blockers: Request type and final missing access subset (never NULL). + * @sk: Socket object (never NULL), read without a socket lock, so its fields + * are a best-effort snapshot. + * @socket_family: Socket-family snapshot used by the verdict. + * @address: Authoritative address checked by the verdict (never NULL). + * The producer copies @addrlen bytes from the checked address and + * zeroes the remaining storage before emission. The + * &sockaddr_in.sin_port or &sockaddr_in6.sin6_port member, when + * present, remains in network endianness. + * @addrlen: Validated signed length of @address. + * + * Emitted when a Landlock domain denies a network operation. The blocker + * identifies whether the address is a bind or connect/send policy object. + * The flattened port field is converted from the checked address to host + * endianness, or is -1 when no port was checked. Zero is a valid checked + * port. */ TRACE_EVENT(landlock_deny_access_net, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, - bool logged, access_mask_t blockers, const struct sock *sk, - __u64 sport, __u64 dport), + bool logged, const struct landlock_blockers *blockers, + const struct sock *sk, u16 socket_family, + const struct sockaddr_storage *address, int addrlen), - TP_ARGS(hierarchy, same_exec, logged, blockers, sk, sport, dport), + TP_ARGS(hierarchy, same_exec, logged, blockers, sk, socket_family, + address, addrlen), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( access_mask_t, blockers ) - __field( __u64, sport ) - __field( __u64, dport ) + __field( enum landlock_request_type, blockers_type ) + __field( access_mask_t, blockers_access ) + __field( s64, port ) ), TP_fast_assign( + const struct sockaddr *const addr = + (const struct sockaddr *)address; + const bool has_port = + addrlen >= (int)offsetofend(struct sockaddr_in, sin_port) && + (addr->sa_family == AF_INET || + addr->sa_family == AF_INET6 || + (addr->sa_family == AF_UNSPEC && + socket_family == AF_INET)); + __entry->domain_id = hierarchy->id; __entry->same_exec = same_exec; __entry->logged = logged; - __entry->blockers = blockers; - __entry->sport = sport; - __entry->dport = dport; + __entry->blockers_type = blockers->type; + __entry->blockers_access = blockers->access; + __entry->port = + has_port ? + ntohs(((const struct sockaddr_in *)addr)->sin_port) : + -1; ), - TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s sport=%llu dport=%llu", - __entry->domain_id, __entry->same_exec, __entry->logged, - __print_flags(__entry->blockers, "|", _LANDLOCK_ACCESS_NET_NAMES), - __entry->sport, __entry->dport) + TP_printk("domain=%llx same_exec=%d logged=%d blockers=%s port=%lld", + __entry->domain_id, __entry->same_exec, __entry->logged, + __entry->blockers_type == LANDLOCK_REQUEST_NET_ACCESS ? + __print_flags(__entry->blockers_access, "|", _LANDLOCK_ACCESS_NET_NAMES) : + "unknown", + __entry->port) ); /** @@ -824,12 +878,14 @@ TRACE_EVENT(landlock_deny_access_net, * * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the * domain field. - * @same_exec: Whether the current task entered the denying domain itself. + * @same_exec: Whether the tracer entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. * @tracee_domain_id: The tracee's Landlock domain ID, or 0 if the tracee * is unsandboxed. * @tracee: The target task ptrace acted on (never NULL). tracee_pid is * the init-namespace TGID (like audit's opid). + * @tracer: The tracer or proposed tracer (never NULL); for PTRACE_TRACEME + * this is the parent, not the syscall caller. * * Emitted when a Landlock domain denies a ptrace operation. */ @@ -837,15 +893,16 @@ TRACE_EVENT(landlock_deny_ptrace, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, u64 tracee_domain_id, - const struct task_struct *tracee), + const struct task_struct *tracee, + const struct task_struct *tracer), - TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee), + TP_ARGS(hierarchy, same_exec, logged, tracee_domain_id, tracee, tracer), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( __u64, tracee_domain_id) + __field( u64, tracee_domain_id) __field( pid_t, tracee_pid ) __string( tracee_comm, tracee->comm ) ), @@ -872,12 +929,14 @@ TRACE_EVENT(landlock_deny_ptrace, * * @hierarchy: Denying domain's hierarchy node (never NULL); its id is the * domain field. - * @same_exec: Whether the current task entered the denying domain itself. + * @same_exec: Whether the policy subject entered the denying domain itself. * @logged: The domain's audit-logging decision for this denial. * @target_domain_id: The target's Landlock domain ID, or 0 if the target * is unsandboxed. * @target: The task the signal was aimed at (never NULL). target_pid is * the init-namespace TGID (like audit's opid). + * @signal: The signal selected by the denied check. Zero is a permission + * probe, not an absent value. * * Emitted when a Landlock domain denies signal delivery to a scoped-out * target. @@ -886,15 +945,15 @@ TRACE_EVENT(landlock_deny_scope_signal, TP_PROTO(const struct landlock_hierarchy *hierarchy, bool same_exec, bool logged, u64 target_domain_id, - const struct task_struct *target), + const struct task_struct *target, int signal), - TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target), + TP_ARGS(hierarchy, same_exec, logged, target_domain_id, target, signal), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( __u64, target_domain_id) + __field( u64, target_domain_id) __field( pid_t, target_pid ) __string( target_comm, target->comm ) ), @@ -940,10 +999,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, TP_ARGS(hierarchy, same_exec, logged, peer_domain_id, peer), TP_STRUCT__entry( - __field( __u64, domain_id ) + __field( u64, domain_id ) __field( bool, same_exec ) __field( bool, logged ) - __field( __u64, peer_domain_id ) + __field( u64, peer_domain_id ) __field( pid_t, peer_pid ) /* * Abstract socket names are untrusted binary data from @@ -973,11 +1032,10 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, __entry->logged = logged; __entry->peer_domain_id = peer_domain_id; /* - * Best-effort (0 for a datagram peer). sk_peer_pid is - * canonically guarded by sk->sk_peer_lock, but the target - * peer's peercred is set once and not updated concurrently in - * these hooks, so this READ_ONCE() is safe; sun_path is the - * reliable identifier. + * Best-effort (0 for a datagram peer). The caller holds the + * peer's AF_UNIX state lock, serializing published peercred + * updates. The peer socket keeps a reference to sk_peer_pid + * through pid_nr(); sun_path is the reliable identifier. */ peer_pid = READ_ONCE(peer->sk_peer_pid); __entry->peer_pid = peer_pid ? pid_nr(peer_pid) : 0; @@ -991,6 +1049,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, __get_dynamic_array_len(sun_path) - 1)) ); +#undef _LANDLOCK_FS_BLOCKER_TYPE_NAMES #undef _LANDLOCK_NAME_ENTRY #endif /* _TRACE_LANDLOCK_H */ diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 9f33e95d5741..d870bc5e50bc 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -4268,13 +4268,10 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec) { int i; - /* There are three types that signify ownership of some other type: - * kptr_ref, bpf_list_head, bpf_rb_root. - * kptr_ref only supports storing kernel types, which can't store - * references to program allocated local types. - * - * Hence we only need to ensure that bpf_{list_head,rb_root} ownership - * does not form cycles. + /* + * Check fields which require the complete BTF and initialize runtime + * metadata. Ownership relationships are validated after every record has + * been fixed up. */ if (IS_ERR_OR_NULL(rec) || !(rec->field_mask & (BPF_GRAPH_ROOT | BPF_UPTR))) return 0; @@ -4305,51 +4302,88 @@ int btf_check_and_fixup_fields(const struct btf *btf, struct btf_record *rec) if (!meta) return -EFAULT; rec->fields[i].graph_root.value_rec = meta->record; + } + return 0; +} - /* We need to set value_rec for all root types, but no need - * to check ownership cycle for a type unless it's also a - * node type. - */ - if (!(rec->field_mask & BPF_GRAPH_NODE)) +static int btf_owned_type_idx(const struct btf *btf, struct btf_struct_metas *tab, + const struct btf_field *field) +{ + struct btf_struct_meta *meta; + u32 btf_id; + + if (field->type & BPF_GRAPH_ROOT) { + btf_id = field->graph_root.value_btf_id; + } else if (field->type == BPF_KPTR_REF || field->type == BPF_KPTR_PERCPU) { + if (btf_is_kernel(field->kptr.btf)) + return -ENOENT; + btf_id = field->kptr.btf_id; + } else { + return -ENOENT; + } + + meta = btf_find_struct_meta(btf, btf_id); + if (!meta) + return field->type & BPF_GRAPH_ROOT ? -EFAULT : -ENOENT; + return meta - tab->types; +} + +/* + * Each ownership edge adds kernel frames through bpf_obj_free_fields() and + * __bpf_obj_drop_impl(). Keep the bound deliberately small because object + * destruction can itself run below a BPF call chain. A final pointee without + * special fields is not present in the struct metadata table and adds only a + * non-recursing drop. + */ +#define BTF_MAX_OWNERSHIP_DEPTH 8 + +static int btf_ownership_depth(const struct btf *btf, + struct btf_struct_metas *tab, u8 *depth, + int idx, int depth_left) +{ + const struct btf_record *rec = tab->types[idx].record; + int i, ret, max_depth = 0; + + if (!depth_left) + return -ELOOP; + if (depth[idx]) + goto done; + + for (i = 0; i < rec->cnt; i++) { + ret = btf_owned_type_idx(btf, tab, &rec->fields[i]); + if (ret == -ENOENT) continue; + if (ret < 0) + return ret; + ret = btf_ownership_depth(btf, tab, depth, ret, depth_left - 1); + if (ret < 0) + return ret; + max_depth = max(max_depth, ret); + } + depth[idx] = max_depth + 1; +done: + return depth[idx] > depth_left ? -ELOOP : depth[idx]; +} - /* We need to ensure ownership acyclicity among all types. The - * proper way to do it would be to topologically sort all BTF - * IDs based on the ownership edges, since there can be multiple - * bpf_{list_head,rb_node} in a type. Instead, we use the - * following resaoning: - * - * - A type can only be owned by another type in user BTF if it - * has a bpf_{list,rb}_node. Let's call these node types. - * - A type can only _own_ another type in user BTF if it has a - * bpf_{list_head,rb_root}. Let's call these root types. - * - * We ensure that if a type is both a root and node, its - * element types cannot be root types. - * - * To ensure acyclicity: - * - * When A is an root type but not a node, its ownership - * chain can be: - * A -> B -> C - * Where: - * - A is an root, e.g. has bpf_rb_root. - * - B is both a root and node, e.g. has bpf_rb_node and - * bpf_list_head. - * - C is only an root, e.g. has bpf_list_node - * - * When A is both a root and node, some other type already - * owns it in the BTF domain, hence it can not own - * another root type through any of the ownership edges. - * A -> B - * Where: - * - A is both an root and node. - * - B is only an node. - */ - if (meta->record->field_mask & BPF_GRAPH_ROOT) - return -ELOOP; +static int btf_check_ownership_depth(const struct btf *btf, + struct btf_struct_metas *tab) +{ + u8 *depth; + int i, ret = 0; + + depth = kvcalloc(tab->cnt, sizeof(*depth), GFP_KERNEL | __GFP_NOWARN); + if (!depth) + return -ENOMEM; + + for (i = 0; i < tab->cnt; i++) { + ret = btf_ownership_depth(btf, tab, depth, i, + BTF_MAX_OWNERSHIP_DEPTH); + if (ret < 0) + break; + ret = 0; } - return 0; + kvfree(depth); + return ret; } static void __btf_struct_show(const struct btf *btf, const struct btf_type *t, @@ -6044,6 +6078,10 @@ static struct btf *btf_parse(const union bpf_attr *attr, bpfptr_t uattr, if (err < 0) goto errout_meta; } + + err = btf_check_ownership_depth(btf, struct_meta_tab); + if (err < 0) + goto errout_meta; } err = bpf_log_attr_finalize(attr_log, &env->log); @@ -7187,7 +7225,7 @@ again: if (btf_type_is_int(t)) return WALK_SCALAR; - if (!btf_type_is_struct(t)) + if (!btf_type_is_struct(t) || !t->size) goto error; off = (off - moff) % t->size; diff --git a/kernel/bpf/check_btf.c b/kernel/bpf/check_btf.c index 0e8b3ccc7a5b..4c1ed842f661 100644 --- a/kernel/bpf/check_btf.c +++ b/kernel/bpf/check_btf.c @@ -338,9 +338,9 @@ err_free: #define MIN_CORE_RELO_SIZE sizeof(struct bpf_core_relo) #define MAX_CORE_RELO_SIZE MAX_FUNCINFO_REC_SIZE -static int check_core_relo(struct bpf_verifier_env *env, - const union bpf_attr *attr, - bpfptr_t uattr) +int bpf_check_core_relo(struct bpf_verifier_env *env, + const union bpf_attr *attr, + bpfptr_t uattr) { u32 i, nr_core_relo, ncopy, expected_size, rec_size; struct bpf_core_relo core_relo = {}; @@ -414,7 +414,7 @@ int bpf_prepare_btf_info(struct bpf_verifier_env *env, struct btf *btf; int err; - if (!attr->func_info_cnt && !attr->line_info_cnt) { + if (!attr->func_info_cnt && !attr->line_info_cnt && !attr->core_relo_cnt) { if (check_abnormal_return(env)) return -EINVAL; return 0; @@ -455,9 +455,5 @@ int bpf_check_btf_info(struct bpf_verifier_env *env, if (err) return err; - err = check_core_relo(env, attr, uattr); - if (err) - return err; - return 0; } diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 8b294dfc1ad4..2e3bf8113ae9 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -19,6 +19,7 @@ #include <uapi/linux/btf.h> #include <linux/filter.h> +#include <linux/sched/signal.h> #include <linux/skbuff.h> #include <linux/static_call.h> #include <linux/vmalloc.h> @@ -1619,6 +1620,8 @@ struct bpf_prog *bpf_jit_blind_constants(struct bpf_verifier_env *env, struct bp * fix it up here on error. */ bpf_jit_prog_release_other(prog, clone); + if (env && fatal_signal_pending(current)) + return ERR_PTR(-EINTR); return IS_ERR(tmp) ? tmp : ERR_PTR(-ENOMEM); } @@ -2636,11 +2639,14 @@ static struct bpf_prog *bpf_prog_jit_compile(struct bpf_verifier_env *env, struc orig_prog = prog; prog = bpf_jit_blind_constants(env, prog); /* - * If blinding was requested and we failed during blinding, we must fall - * back to the interpreter. + * Fall back to the interpreter after blinding failures, except when + * the loader was killed. */ - if (IS_ERR(prog)) + if (IS_ERR(prog)) { + if (PTR_ERR(prog) == -EINTR) + return prog; goto out_restore; + } prog = bpf_int_jit_compile(env, prog); if (prog->jited) { @@ -2659,6 +2665,8 @@ out_restore: struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct bpf_prog *fp, int *err) { + struct bpf_prog *jit_prog; + /* In case of BPF to BPF calls, verifier did all the prep * work with regards to JITing, etc. */ @@ -2681,7 +2689,12 @@ struct bpf_prog *__bpf_prog_select_runtime(struct bpf_verifier_env *env, struct if (*err) return fp; - fp = bpf_prog_jit_compile(env, fp); + jit_prog = bpf_prog_jit_compile(env, fp); + if (IS_ERR(jit_prog)) { + *err = PTR_ERR(jit_prog); + return fp; + } + fp = jit_prog; bpf_prog_jit_attempt_done(fp); if (!fp->jited && jit_needed) { *err = -ENOTSUPP; diff --git a/kernel/bpf/crypto.c b/kernel/bpf/crypto.c index 51f89cecefb4..3f3fe2450fc6 100644 --- a/kernel/bpf/crypto.c +++ b/kernel/bpf/crypto.c @@ -149,8 +149,9 @@ bpf_crypto_ctx_create(const struct bpf_crypto_params *params, u32 params__sz, const struct bpf_crypto_type *type; struct bpf_crypto_ctx *ctx; - if (!params || params->reserved[0] || params->reserved[1] || - params__sz != sizeof(struct bpf_crypto_params)) { + if (!params || + params__sz != sizeof(struct bpf_crypto_params) || + params->reserved[0] || params->reserved[1]) { *err = -EINVAL; return NULL; } diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c index 52d3cec33672..d6f83521fc78 100644 --- a/kernel/bpf/fixups.c +++ b/kernel/bpf/fixups.c @@ -8,6 +8,7 @@ #include <linux/bsearch.h> #include <linux/sort.h> #include <linux/perf_event.h> +#include <linux/sched/signal.h> #include <net/xdp.h> #include "disasm.h" @@ -306,12 +307,28 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len) } } +/* + * Some post-verification instruction rewriting passes require an + * O(prog->len) operation per instruction. Keep their shared primitives + * killable and preemptible. + */ +static bool bpf_rewrite_must_abort(void) +{ + if (fatal_signal_pending(current)) + return true; + cond_resched(); + return false; +} + struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off, const struct bpf_insn *patch, u32 len) { struct bpf_prog *new_prog; struct bpf_insn_aux_data *new_data = NULL; + if (bpf_rewrite_must_abort()) + return NULL; + if (len > 1) { new_data = vrealloc(env->insn_aux_data, array_size(env->prog->len + len - 1, @@ -523,6 +540,9 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt) unsigned int orig_prog_len = env->prog->len; int err; + if (bpf_rewrite_must_abort()) + return -EINTR; + if (bpf_prog_is_offloaded(env->prog->aux)) bpf_prog_offload_remove_insns(env, off, cnt); @@ -1356,7 +1376,7 @@ int bpf_jit_subprogs(struct bpf_verifier_env *env) } prog = bpf_jit_blind_constants(env, prog); if (IS_ERR(prog)) { - err = -ENOMEM; + err = PTR_ERR(prog); prog = orig_prog; goto out_restore; } @@ -1433,7 +1453,7 @@ int bpf_fixup_call_args(struct bpf_verifier_env *env) err = bpf_jit_subprogs(env); if (err == 0) return 0; - if (err == -EFAULT) + if (err == -EFAULT || err == -EINTR) return err; } #ifndef CONFIG_BPF_JIT_ALWAYS_ON diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c index 6f331c80130d..f9464e566f10 100644 --- a/kernel/bpf/hashtab.c +++ b/kernel/bpf/hashtab.c @@ -1054,14 +1054,17 @@ static void pcpu_init_value(struct bpf_htab *htab, void __percpu *pptr, /* When not setting the initial value on all cpus, zero-fill element * values for other cpus. Otherwise, bpf program has no way to ensure * known initial values for cpus other than current one - * (onallcpus=false always when coming from bpf prog). + * (onallcpus=false always when coming from bpf prog, + * map_flags & BPF_F_CPU when coming from syscall but setting + * only one cpu). */ - if (!onallcpus) { - int current_cpu = raw_smp_processor_id(); + if (!onallcpus || (map_flags & BPF_F_CPU)) { + int init_cpu = (map_flags & BPF_F_CPU) ? map_flags >> 32 : + raw_smp_processor_id(); int cpu; for_each_possible_cpu(cpu) { - if (cpu == current_cpu) + if (cpu == init_cpu) copy_map_value(&htab->map, per_cpu_ptr(pptr, cpu), value); else /* Since elem is preallocated, we cannot touch special fields */ zero_map_value(&htab->map, per_cpu_ptr(pptr, cpu)); @@ -1772,6 +1775,12 @@ static int htab_lru_percpu_map_lookup_and_delete_elem(struct bpf_map *map, flags); } +/* + * Max consecutive empty buckets to walk in one RCU + + * instrumentation-disabled section before rescheduling. + */ +#define HTAB_BATCH_EMPTY_RESCHED 64 + static int __htab_map_lookup_and_delete_batch(struct bpf_map *map, const union bpf_attr *attr, @@ -1793,6 +1802,7 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map, unsigned long flags = 0; bool locked = false; struct htab_elem *l; + u32 empty_cnt = 0; struct bucket *b; int ret = 0; @@ -1971,30 +1981,41 @@ again_nocopy: } next_batch: - /* If we are not copying data, we can go to next bucket and avoid - * unlocking the rcu. + /* + * If we are not copying data, we can go to next bucket and avoid + * unlocking the rcu. Bound the walk though: after + * HTAB_BATCH_EMPTY_RESCHED consecutive empty buckets, fully exit + * the critical section (no locks are held here) and reschedule. */ if (!bucket_cnt && (batch + 1 < htab->n_buckets)) { batch++; - goto again_nocopy; + if (++empty_cnt < HTAB_BATCH_EMPTY_RESCHED) + goto again_nocopy; + empty_cnt = 0; + rcu_read_unlock(); + bpf_enable_instrumentation(); + cond_resched_tasks_rcu_qs(); + goto again; } rcu_read_unlock(); bpf_enable_instrumentation(); - if (bucket_cnt && (copy_to_user(ukeys + total * key_size, keys, - key_size * bucket_cnt) || - copy_to_user(uvalues + total * value_size, values, - value_size * bucket_cnt))) { + if (bucket_cnt && (copy_to_user(ukeys + (size_t)total * key_size, keys, + (size_t)key_size * bucket_cnt) || + copy_to_user(uvalues + (size_t)total * value_size, values, + (size_t)value_size * bucket_cnt))) { ret = -EFAULT; goto after_loop; } total += bucket_cnt; + empty_cnt = 0; batch++; if (batch >= htab->n_buckets) { ret = -ENOENT; goto after_loop; } + cond_resched_tasks_rcu_qs(); goto again; after_loop: diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index b3cc5c8fc875..712dca5a2c5b 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -4883,7 +4883,7 @@ BTF_ID(func, bpf_cgroup_release_dtor) BTF_KFUNCS_START(common_btf_ids) BTF_ID_FLAGS(func, bpf_cast_to_kern_ctx, KF_FASTCALL) -BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL) +BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL | KF_PERFMON) BTF_ID_FLAGS(func, bpf_rcu_read_lock) BTF_ID_FLAGS(func, bpf_rcu_read_unlock) BTF_ID_FLAGS(func, bpf_dynptr_slice, KF_RET_NULL) @@ -4920,26 +4920,26 @@ BTF_ID_FLAGS(func, bpf_wq_set_callback, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_wq_start) BTF_ID_FLAGS(func, bpf_preempt_disable) BTF_ID_FLAGS(func, bpf_preempt_enable) -BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW) +BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW | KF_PERFMON) BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY) -BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_get_kmem_cache) +BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_get_kmem_cache, KF_PERFMON) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE) BTF_ID_FLAGS(func, bpf_local_irq_save) BTF_ID_FLAGS(func, bpf_local_irq_restore) #ifdef CONFIG_BPF_EVENTS -BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr) -BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr) -BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE) -BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE) +BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr, KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE | KF_PERFMON) #endif #ifdef CONFIG_DMA_SHARED_BUFFER BTF_ID_FLAGS(func, bpf_iter_dmabuf_new, KF_ITER_NEW | KF_SLEEPABLE) @@ -4947,26 +4947,26 @@ BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPAB BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE) #endif BTF_ID_FLAGS(func, __bpf_trap) -BTF_ID_FLAGS(func, bpf_strcmp); -BTF_ID_FLAGS(func, bpf_strcasecmp); -BTF_ID_FLAGS(func, bpf_strncasecmp); -BTF_ID_FLAGS(func, bpf_strchr); -BTF_ID_FLAGS(func, bpf_strchrnul); -BTF_ID_FLAGS(func, bpf_strnchr); -BTF_ID_FLAGS(func, bpf_strrchr); -BTF_ID_FLAGS(func, bpf_strlen); -BTF_ID_FLAGS(func, bpf_strnlen); -BTF_ID_FLAGS(func, bpf_strspn); -BTF_ID_FLAGS(func, bpf_strcspn); -BTF_ID_FLAGS(func, bpf_strstr); -BTF_ID_FLAGS(func, bpf_strcasestr); -BTF_ID_FLAGS(func, bpf_strnstr); -BTF_ID_FLAGS(func, bpf_strncasestr); +BTF_ID_FLAGS(func, bpf_strcmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcasecmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strncasecmp, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strchrnul, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strrchr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strlen, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnlen, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strspn, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcspn, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strstr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strcasestr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strnstr, KF_PERFMON); +BTF_ID_FLAGS(func, bpf_strncasestr, KF_PERFMON); #if defined(CONFIG_BPF_LSM) && defined(CONFIG_CGROUPS) BTF_ID_FLAGS(func, bpf_cgroup_read_xattr, KF_RCU) #endif -BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE) -BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE) +BTF_ID_FLAGS(func, bpf_stream_vprintk, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON) +BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS | KF_SPINLOCK_SAFE | KF_PERFMON) BTF_ID_FLAGS(func, bpf_task_work_schedule_signal, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_task_work_schedule_resume, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_dynptr_from_file) diff --git a/kernel/bpf/memalloc.c b/kernel/bpf/memalloc.c index e9662db7198f..8a8f088e83e6 100644 --- a/kernel/bpf/memalloc.c +++ b/kernel/bpf/memalloc.c @@ -119,6 +119,7 @@ struct bpf_mem_cache { struct llist_head waiting_for_gp_ttrace; struct rcu_head rcu_ttrace; atomic_t call_rcu_ttrace_in_progress; + raw_spinlock_t lock; }; struct bpf_mem_caches { @@ -214,25 +215,24 @@ static void alloc_bulk(struct bpf_mem_cache *c, int cnt, int node, bool atomic) gfp = __GFP_NOWARN | __GFP_ACCOUNT; gfp |= atomic ? GFP_NOWAIT : GFP_KERNEL; - for (i = 0; i < cnt; i++) { - /* - * For every 'c' llist_del_first(&c->free_by_rcu_ttrace); is - * done only by one CPU == current CPU. Other CPUs might - * llist_add() and llist_del_all() in parallel. - */ - obj = llist_del_first(&c->free_by_rcu_ttrace); - if (!obj) - break; - add_obj_to_free_list(c, obj); - } - if (i >= cnt) - return; + /* + * c->lock serializes concurrent llist_del_first() against + * llist_del_all() in __free_rcu() and do_call_rcu_ttrace(). + */ + scoped_guard(raw_spinlock_irqsave, &c->lock) { + for (i = 0; i < cnt; i++) { + obj = llist_del_first(&c->free_by_rcu_ttrace); + if (!obj) + break; + add_obj_to_free_list(c, obj); + } - for (; i < cnt; i++) { - obj = llist_del_first(&c->waiting_for_gp_ttrace); - if (!obj) - break; - add_obj_to_free_list(c, obj); + for (; i < cnt; i++) { + obj = llist_del_first(&c->waiting_for_gp_ttrace); + if (!obj) + break; + add_obj_to_free_list(c, obj); + } } if (i >= cnt) return; @@ -279,8 +279,12 @@ static int free_all(struct bpf_mem_cache *c, struct llist_node *llnode, bool per static void __free_rcu(struct rcu_head *head) { struct bpf_mem_cache *c = container_of(head, struct bpf_mem_cache, rcu_ttrace); + struct llist_node *llnode; + + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->waiting_for_gp_ttrace); - free_all(c, llist_del_all(&c->waiting_for_gp_ttrace), !!c->percpu_size); + free_all(c, llnode, !!c->percpu_size); atomic_set(&c->call_rcu_ttrace_in_progress, 0); } @@ -300,7 +304,8 @@ static void do_call_rcu_ttrace(struct bpf_mem_cache *c) if (atomic_xchg(&c->call_rcu_ttrace_in_progress, 1)) { if (unlikely(READ_ONCE(c->draining))) { - llnode = llist_del_all(&c->free_by_rcu_ttrace); + scoped_guard(raw_spinlock_irqsave, &c->lock) + llnode = llist_del_all(&c->free_by_rcu_ttrace); free_all(c, llnode, !!c->percpu_size); } return; @@ -535,6 +540,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } @@ -557,7 +563,7 @@ int bpf_mem_alloc_init(struct bpf_mem_alloc *ma, int size, bool percpu) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; - + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } @@ -609,7 +615,7 @@ int bpf_mem_alloc_percpu_unit_init(struct bpf_mem_alloc *ma, int size) c->objcg = objcg; c->percpu_size = percpu_size; c->tgt = c; - + raw_spin_lock_init(&c->lock); init_refill_work(c); prefill_mem_cache(c, cpu); } diff --git a/kernel/bpf/offload.c b/kernel/bpf/offload.c index 0d6f5569588c..d855399812ee 100644 --- a/kernel/bpf/offload.c +++ b/kernel/bpf/offload.c @@ -698,6 +698,8 @@ static bool __bpf_offload_dev_match(struct bpf_prog *prog, return false; if (offload->netdev == netdev) return true; + if (!bpf_prog_is_offloaded(prog->aux)) + return false; ondev1 = bpf_offload_find_netdev(offload->netdev); ondev2 = bpf_offload_find_netdev(netdev); diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c index 66fb11b6c6a7..012b82513a3b 100644 --- a/kernel/bpf/states.c +++ b/kernel/bpf/states.c @@ -491,7 +491,8 @@ static bool regs_exact(const struct bpf_reg_state *rold, { return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 && check_ids(rold->id, rcur->id, idmap) && - check_ids(rold->parent_id, rcur->parent_id, idmap); + check_ids(rold->parent_id, rcur->parent_id, idmap) && + check_ids(rold->map_uid, rcur->map_uid, idmap); } enum exact_level { @@ -616,7 +617,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off) && check_ids(rold->id, rcur->id, idmap) && - check_ids(rold->parent_id, rcur->parent_id, idmap); + check_ids(rold->parent_id, rcur->parent_id, idmap) && + check_ids(rold->map_uid, rcur->map_uid, idmap); case PTR_TO_PACKET_META: case PTR_TO_PACKET: /* We must have at least as much range as the old ptr @@ -635,14 +637,14 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, /* id relations must be preserved */ if (!check_ids(rold->id, rcur->id, idmap)) return false; + /* Preserve displacements between pointers sharing an ID. */ + if (rold->id && rold->r64.base != rcur->r64.base) + return false; /* new val must satisfy old val knowledge */ return range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off); case PTR_TO_STACK: - /* two stack pointers are equal only if they're pointing to - * the same stack frame, since fp-8 in foo != fp-8 in bar - */ - return regs_exact(rold, rcur, idmap) && rold->frameno == rcur->frameno; + return regs_exact(rold, rcur, idmap); case PTR_TO_ARENA: return true; case PTR_TO_INSN: @@ -1121,7 +1123,7 @@ static bool states_maybe_looping(struct bpf_verifier_state *old, fcur = cur->frame[fr]; for (i = 0; i < MAX_BPF_REG; i++) if (memcmp(&fold->regs[i], &fcur->regs[i], - offsetof(struct bpf_reg_state, frameno))) + offsetof(struct bpf_reg_state, precise))) return false; return true; } diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index c7bc9ba9b331..244a939b9d2d 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2036,7 +2036,7 @@ int generic_map_delete_batch(struct bpf_map *map, for (cp = 0; cp < max_count; cp++) { err = -EFAULT; - if (copy_from_user(key, keys + cp * map->key_size, + if (copy_from_user(key, keys + (size_t)cp * map->key_size, map->key_size)) break; @@ -2098,9 +2098,9 @@ int generic_map_update_batch(struct bpf_map *map, struct file *map_file, for (cp = 0; cp < max_count; cp++) { err = -EFAULT; - if (copy_from_user(key, keys + cp * map->key_size, + if (copy_from_user(key, keys + (size_t)cp * map->key_size, map->key_size) || - copy_from_user(value, values + cp * value_size, value_size)) + copy_from_user(value, values + (size_t)cp * value_size, value_size)) break; err = bpf_map_update_value(map, map_file, key, value, @@ -2179,12 +2179,12 @@ int generic_map_lookup_batch(struct bpf_map *map, if (err) goto free_buf; - if (copy_to_user(keys + cp * map->key_size, key, + if (copy_to_user(keys + (size_t)cp * map->key_size, key, map->key_size)) { err = -EFAULT; goto free_buf; } - if (copy_to_user(values + cp * value_size, value, value_size)) { + if (copy_to_user(values + (size_t)cp * value_size, value, value_size)) { err = -EFAULT; goto free_buf; } @@ -6042,7 +6042,10 @@ struct bpf_link *bpf_link_get_curr_or_next(u32 *id) again: link = idr_get_next(&link_idr, id); if (link) { - link = bpf_link_inc_not_zero(link); + if (link->id) + link = bpf_link_inc_not_zero(link); + else + link = ERR_PTR(-EAGAIN); if (IS_ERR(link)) { (*id)++; goto again; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 72a3f5998dd2..41b49c56e123 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -567,7 +567,7 @@ static int stack_slot_obj_get_spi(struct bpf_verifier_env *env, struct bpf_reg_s } off = reg->var_off.value; - if (off % BPF_REG_SIZE) { + if (off >= 0 || off % BPF_REG_SIZE) { verbose(env, "cannot pass in %s at an offset=%d\n", obj_kind, off); return -EINVAL; } @@ -1864,6 +1864,7 @@ static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm) offsetof(struct bpf_reg_state, var_off) - sizeof(reg->type)); reg->id = 0; reg->parent_id = 0; + reg->map_uid = 0; ___mark_reg_known(reg, imm); } @@ -1925,17 +1926,18 @@ static void refine_map_lookup_value(struct bpf_reg_state *reg) if (map->inner_map_meta) { reg->type = CONST_PTR_TO_MAP | maybe_null; reg->map_ptr = map->inner_map_meta; - /* transfer reg's id which is unique for every map_lookup_elem + /* + * transfer reg's id which is unique for every map_lookup_elem * as UID of the inner map. */ - if (btf_record_has_field(map->inner_map_meta->record, - BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK)) - reg->map_uid = reg->id; + reg->map_uid = reg->id; } else if (map->map_type == BPF_MAP_TYPE_XSKMAP) { reg->type = PTR_TO_XDP_SOCK | maybe_null; + reg->map_uid = 0; } else if (map->map_type == BPF_MAP_TYPE_SOCKMAP || map->map_type == BPF_MAP_TYPE_SOCKHASH) { reg->type = PTR_TO_SOCKET | maybe_null; + reg->map_uid = 0; } } @@ -3042,6 +3044,8 @@ static int check_subprogs(struct bpf_verifier_env *env) subprog[cur_subprog].exit_idx = i; goto next; } + if (insn_is_gotox(&insn[i])) + goto next; off = i + bpf_jmp_offset(&insn[i]) + 1; if (off < subprog_start || off >= subprog_end) { verbose(env, "jump out of range from insn %d to %d\n", i, off); @@ -3061,7 +3065,8 @@ next: */ if (code != (BPF_JMP | BPF_EXIT) && code != (BPF_JMP32 | BPF_JA) && - code != (BPF_JMP | BPF_JA)) { + code != (BPF_JMP | BPF_JA) && + !insn_is_gotox(&insn[i])) { verbose(env, "last insn is not an exit or jmp\n"); bpf_diag_program_structure( env, i, "subprogram can fall through", @@ -3582,7 +3587,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, save_register_state(env, state, spi, reg, size); /* Break the relation on a narrowing spill. */ if (!reg_value_fits) - state->stack[spi].spilled_ptr.id = 0; + clear_scalar_id(&state->stack[spi].spilled_ptr); } else if (!reg && !(off % BPF_REG_SIZE) && is_bpf_st_mem(insn) && env->bpf_capable) { struct bpf_reg_state *tmp_reg = &env->fake_reg[0]; @@ -6453,6 +6458,15 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b return -EACCES; } + if (rdonly_untrusted && !env->allow_ptr_leaks) { + verbose(env, "%s access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n", + reg_type_str(env, reg->type)); + bpf_diag_policy(env, insn_idx, "read from untrusted read-only memory", + "the access requires CAP_PERFMON", + "Load the program with CAP_PERFMON, or avoid dereferencing untrusted pointers."); + return -EPERM; + } + /* * Accesses to untrusted PTR_TO_MEM are done through probe * instructions, hence no need to check bounds in that case. @@ -9736,6 +9750,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog, if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL, NULL)) return -EINVAL; + /* + * PTR_TO_PACKET get passed as PTR_TO_MEM, preventing + * us from adjusting bounds tracking info. + */ + if ((reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) && + sub->changes_pkt_data) { + bpf_log(log, "%s is a packet pointer, but func#%d may change packet data\n", + reg_arg_name(env, argno), subprog); + return -EINVAL; + } if (!(arg->arg_type & PTR_MAYBE_NULL) && (type_may_be_null(reg->type) || bpf_register_is_null(reg))) { bpf_log(log, "%s is expected to be non-NULL\n", @@ -9919,6 +9943,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn, if (err == -EFAULT) return err; if (bpf_subprog_is_global(env, subprog)) { + struct bpf_func_info_aux *sub_aux = subprog_aux(env, subprog); const char *sub_name = bpf_subprog_name(env, subprog); const char *operation; bool returns_void; @@ -9950,11 +9975,10 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn, if (env->log.level & BPF_LOG_LEVEL) verbose(env, "Func#%d ('%s') is global and assumed valid.\n", subprog, sub_name); + sub_aux->called[in_sleepable_context(env)] = true; returns_void = subprog_returns_void(env, subprog); if (env->subprog_info[subprog].changes_pkt_data) clear_all_pkt_pointers(env); - /* mark global subprog for verifying after main prog */ - subprog_aux(env, subprog)->called = true; if (returns_void) bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED); else @@ -10036,6 +10060,7 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr; callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* pointer to stack or null */ callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3]; @@ -10132,6 +10157,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; callee->regs[BPF_REG_3].map_uid = map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); @@ -10250,6 +10276,7 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env, __mark_reg_known_zero(&callee->regs[BPF_REG_3]); callee->regs[BPF_REG_3].map_ptr = map_ptr; callee->regs[BPF_REG_3].map_uid = map_uid; + callee->regs[BPF_REG_3].id = ++env->id_gen; /* unused */ bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]); @@ -10772,11 +10799,7 @@ int bpf_get_helper_proto(struct bpf_verifier_env *env, int func_id, /* Check if we're in a sleepable context. */ static inline bool in_sleepable_context(struct bpf_verifier_env *env) { - return !env->cur_state->active_rcu_locks && - !env->cur_state->active_preempt_locks && - !env->cur_state->active_locks && - !env->cur_state->active_irq_id && - in_sleepable(env); + return !in_rcu_cs(env); } static const char *non_sleepable_context_description(struct bpf_verifier_env *env) @@ -11356,6 +11379,11 @@ static bool is_kfunc_destructive(struct bpf_call_arg_meta *meta) return meta->kfunc_flags & KF_DESTRUCTIVE; } +static bool is_kfunc_perfmon(struct bpf_call_arg_meta *meta) +{ + return meta->kfunc_flags & KF_PERFMON; +} + static bool is_kfunc_rcu(struct bpf_call_arg_meta *meta) { return meta->kfunc_flags & KF_RCU; @@ -13834,6 +13862,15 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, return -EACCES; } + if (is_kfunc_perfmon(&meta) && !env->allow_ptr_leaks) { + verbose(env, "%s is allowed only to CAP_PERFMON and CAP_SYS_ADMIN\n", + func_name); + operation = bpf_diag_fmt(env, "kfunc %s", func_name); + bpf_diag_policy(env, insn_idx, operation, "the kfunc requires CAP_PERFMON", + "Load the program with CAP_PERFMON, or avoid the kfunc."); + return -EPERM; + } + sleepable = bpf_is_kfunc_sleepable(&meta); if (sleepable && !in_sleepable(env)) { verbose(env, "program must be sleepable to call sleepable kfunc %s\n", func_name); @@ -15704,6 +15741,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, struct bpf_reg_state *regs = state->regs, *dst_reg, *src_reg; struct bpf_reg_state *ptr_reg = NULL, off_reg = {0}; bool alu32 = (BPF_CLASS(insn->code) != BPF_ALU64); + struct bpf_insn_aux_data *aux = cur_aux(env); u8 opcode = BPF_OP(insn->code); int err; @@ -15715,13 +15753,24 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, /* Case where at least one operand is an arena. */ if (dst_reg->type == PTR_TO_ARENA || (src_reg && src_reg->type == PTR_TO_ARENA)) { - struct bpf_insn_aux_data *aux = cur_aux(env); if (dst_reg->type != PTR_TO_ARENA) *dst_reg = *src_reg; if (BPF_CLASS(insn->code) == BPF_ALU64) { /* + * Only arena pointers set needs_zext, but doing so + * modifies the instruction at fixup time to an ALU32 + * and makes it unsuitable for 64-bit scalar args. We + * prevent zext from being set if the instruction has + * been previously called with non-arena registers. + */ + if (aux->prevent_zext) { + verbose(env, "same insn cannot be used with and without arena pointer\n"); + return -EINVAL; + } + + /* * 32-bit operations zero upper bits automatically. * 64-bit operations need to be converted to 32. */ @@ -15733,6 +15782,16 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env, return 0; } + /* Prevent the instruction from being used with arena pointers (see above). */ + if (env->prog->aux->arena && BPF_CLASS(insn->code) == BPF_ALU64) { + if (aux->needs_zext) { + verbose(env, "same insn cannot be used with and without arena pointer\n"); + return -EINVAL; + } + + aux->prevent_zext = true; + } + if (dst_reg->type != SCALAR_VALUE) ptr_reg = dst_reg; @@ -19421,13 +19480,14 @@ static void free_states(struct bpf_verifier_env *env) } } -static int do_check_common(struct bpf_verifier_env *env, int subprog) +static int do_check_common(struct bpf_verifier_env *env, int subprog, bool is_sleepable) { bool pop_log = !(env->log.level & BPF_LOG_LEVEL2); struct bpf_subprog_info *sub = subprog_info(env, subprog); struct bpf_prog_aux *aux = env->prog->aux; struct bpf_verifier_state *state; struct bpf_reg_state *regs; + u32 old_insns_total = sub->insns_total; u32 insn_processed = env->insn_processed; int ret, i; @@ -19440,7 +19500,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog) state->curframe = 0; state->speculative = false; state->branches = 1; - state->in_sleepable = env->prog->sleepable; + state->in_sleepable = is_sleepable; state->frame[0] = kzalloc_obj(struct bpf_func_state, GFP_KERNEL_ACCOUNT); if (!state->frame[0]) { kfree(state); @@ -19581,8 +19641,10 @@ out: * not accounted as callees by account_current_path(). * Accumulate their total counts as total counts of the main or * global subprog hosting the async call. + * Start from the saved total of earlier contexts: adding to the current + * total would count this pass's synchronous paths twice. */ - env->subprog_info[subprog].insns_total = env->insn_processed - insn_processed; + sub->insns_total = old_insns_total + (env->insn_processed - insn_processed); return ret; } @@ -19610,14 +19672,19 @@ static int do_check_subprogs(struct bpf_verifier_env *env) { struct bpf_prog_aux *aux = env->prog->aux; struct bpf_func_info_aux *sub_aux; - int i, ret, new_cnt; + int context, i, ret, new_cnt; if (!aux->func_info) return 0; - /* exception callback is presumed to be always called */ - if (env->exception_callback_subprog) - subprog_aux(env, env->exception_callback_subprog)->called = true; + /* + * Callbacks cannot throw, so the exception callback always runs in the + * main program's context. It is presumed to be always called. + */ + if (env->exception_callback_subprog) { + sub_aux = subprog_aux(env, env->exception_callback_subprog); + sub_aux->called[env->prog->sleepable] = true; + } again: new_cnt = 0; @@ -19626,29 +19693,28 @@ again: continue; sub_aux = subprog_aux(env, i); - if (!sub_aux->called || sub_aux->verified) - continue; + for (context = 0; context < ARRAY_SIZE(sub_aux->called); context++) { + if (!sub_aux->called[context] || sub_aux->verified[context]) + continue; - env->insn_idx = env->subprog_info[i].start; - WARN_ON_ONCE(env->insn_idx == 0); - ret = do_check_common(env, i); - if (ret) { - return ret; - } else if (env->log.level & BPF_LOG_LEVEL) { - verbose(env, "Func#%d ('%s') is safe for any args that match its prototype\n", - i, bpf_subprog_name(env, i)); - } + env->insn_idx = env->subprog_info[i].start; + WARN_ON_ONCE(env->insn_idx == 0); + ret = do_check_common(env, i, context); + if (ret) + return ret; + if (env->log.level & BPF_LOG_LEVEL) + verbose(env, "Func#%d ('%s') is safe for any args " + "that match its prototype\n", + i, bpf_subprog_name(env, i)); - /* We verified new global subprog, it might have called some - * more global subprogs that we haven't verified yet, so we - * need to do another pass over subprogs to verify those. - */ - sub_aux->verified = true; - new_cnt++; + sub_aux->verified[context] = true; + new_cnt++; + } } - /* We can't loop forever as we verify at least one global subprog on - * each pass. + /* + * We can't loop forever as each pass verifies at least one new context, + * and there are only two contexts per global subprog. */ if (new_cnt) goto again; @@ -19661,7 +19727,7 @@ static int do_check_main(struct bpf_verifier_env *env) int ret; env->insn_idx = 0; - ret = do_check_common(env, 0); + ret = do_check_common(env, 0, env->prog->sleepable); if (!ret) env->prog->aux->stack_depth = env->subprog_info[0].stack_depth; return ret; @@ -21170,6 +21236,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, ret = bpf_diag_init(env); if (ret) goto err_prep; + if (env->prog->insnsi[env->prog->len - 1].code == (BPF_LD | BPF_IMM | BPF_DW)) { + verbose(env, "invalid bpf_ld_imm64 insn\n"); + ret = -EINVAL; + goto err_prep; + } if (env->signature) { ret = bpf_prog_calc_tag(env->prog); if (ret < 0) @@ -21245,6 +21316,11 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, if (ret < 0) goto skip_full_check; + /* Apply CO-RE before validating the program's instruction layout. */ + ret = bpf_check_core_relo(env, attr, uattr); + if (ret < 0) + goto skip_full_check; + /* Discover all subprograms before validating their layout and BTF. */ ret = add_subprogs(env); if (ret < 0) @@ -21254,7 +21330,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, if (ret < 0) goto skip_full_check; - /* Validate BTF against the complete subprogram layout and apply CO-RE. */ + /* Validate BTF against the complete subprogram layout. */ ret = bpf_check_btf_info(env, attr, uattr); if (ret < 0) goto skip_full_check; diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 2538faac9aba..3f52717c1965 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -1591,10 +1591,11 @@ static int remote_partition_enable(struct cpuset *cs, int new_prs, * above it or remote partition root underneath it is not allowed. */ compute_excpus(cs, tmp->new_cpus); - WARN_ON_ONCE(cpumask_intersects(tmp->new_cpus, subpartitions_cpus)); if (!cpumask_intersects(tmp->new_cpus, cpu_active_mask) || cpumask_subset(top_cpuset.effective_cpus, tmp->new_cpus)) return PERR_INVCPUS; + if (cpumask_intersects(tmp->new_cpus, subpartitions_cpus)) + return PERR_NOCPUS; if (((new_prs == PRS_ISOLATED) && !isolated_cpus_can_update(tmp->new_cpus, NULL)) || prstate_housekeeping_conflict(new_prs, tmp->new_cpus)) diff --git a/kernel/cgroup/pids.c b/kernel/cgroup/pids.c index ecbb839d2acb..78cdc0558d0c 100644 --- a/kernel/cgroup/pids.c +++ b/kernel/cgroup/pids.c @@ -253,6 +253,11 @@ static void pids_event(struct pids_cgroup *pids_forking, } if (!cgroup_subsys_on_dfl(pids_cgrp_subsys) || cgrp_dfl_root.flags & CGRP_ROOT_PIDS_LOCAL_EVENTS) { + /* + * pids.events reports the local counter on legacy hierarchies + * and when pids_localevents is enabled. + */ + cgroup_file_notify(&p->events_file); cgroup_file_notify(&p->events_local_file); return; } diff --git a/kernel/events/core.c b/kernel/events/core.c index db7b76d6b68a..634d2ccbab82 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -3764,6 +3764,9 @@ static void perf_ctx_sched_task_cb(struct perf_event_context *ctx, list_for_each_entry(pmu_ctx, &ctx->pmu_ctx_list, pmu_ctx_entry) { cpc = this_cpc(pmu_ctx->pmu); + if (cpc->task_epc != pmu_ctx) + continue; + if (cpc->sched_cb_usage && pmu_ctx->pmu->sched_task) pmu_ctx->pmu->sched_task(pmu_ctx, task, sched_in); } @@ -3914,7 +3917,7 @@ static void __perf_pmu_sched_task(struct perf_cpu_pmu_context *cpc, perf_ctx_lock(cpuctx, cpuctx->task_ctx); perf_pmu_disable(pmu); - pmu->sched_task(cpc->task_epc, task, sched_in); + pmu->sched_task(&cpc->epc, task, sched_in); perf_pmu_enable(pmu); perf_ctx_unlock(cpuctx, cpuctx->task_ctx); @@ -3924,15 +3927,17 @@ static void perf_pmu_sched_task(struct task_struct *prev, struct task_struct *next, bool sched_in) { - struct perf_cpu_context *cpuctx = this_cpu_ptr(&perf_cpu_context); struct perf_cpu_pmu_context *cpc, *cpc2; - /* cpuctx->task_ctx will be handled in perf_event_context_sched_in/out */ - if (prev == next || cpuctx->task_ctx) + if (prev == next) return; - list_for_each_entry_safe(cpc, cpc2, this_cpu_ptr(&sched_cb_list), sched_cb_entry) + list_for_each_entry_safe(cpc, cpc2, this_cpu_ptr(&sched_cb_list), sched_cb_entry) { + if (cpc->task_epc) + continue; + __perf_pmu_sched_task(cpc, sched_in ? next : prev, sched_in); + } } static void perf_event_switch(struct task_struct *task, @@ -5454,6 +5459,8 @@ attach_task_ctx_data(struct task_struct *task, struct kmem_cache *ctx_cache, } if (refcount_inc_not_zero(&old->refcount)) { + if (global) + old->global = true; free_perf_ctx_data(cd); /* unused */ return 0; } diff --git a/kernel/exit.c b/kernel/exit.c index 424c44a42a4d..282328d2b4cf 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -551,32 +551,6 @@ void mm_update_next_owner(struct mm_struct *mm) } #endif /* CONFIG_MEMCG */ -#if defined(CONFIG_SCHED_CACHE) && defined(CONFIG_NUMA_BALANCING) -/* - * Subtract the memory footprint of the current task from - * mm. - */ -static void exit_mm_sched_cache(struct mm_struct *mm) -{ - unsigned long fp, sub; - - if (!current->total_numa_faults) - return; - /* - * No lock protection due to performance considerations. - * Make sure mm->sc_stat.footprint does not become - * negative. - */ - fp = READ_ONCE(mm->sc_stat.footprint); - sub = min(fp, current->total_numa_faults); - WRITE_ONCE(mm->sc_stat.footprint, fp - sub); -} -#else -static inline void exit_mm_sched_cache(struct mm_struct *mm) -{ -} -#endif /* CONFIG_SCHED_CACHE CONFIG_NUMA_BALANCING */ - /* * Turn us into a lazy TLB process if we * aren't already.. @@ -589,7 +563,7 @@ static void exit_mm(void) if (!mm) return; - exit_mm_sched_cache(mm); + sched_cache_exit_mm(current); mmap_read_lock(mm); mmgrab_lazy_tlb(mm); diff --git a/kernel/fork.c b/kernel/fork.c index 5ef413368912..10f2d05d816a 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1599,6 +1599,7 @@ static int copy_mm(u64 clone_flags, struct task_struct *tsk) tsk->mm = mm; tsk->active_mm = mm; + sched_cache_fork(tsk); return 0; } @@ -2602,6 +2603,7 @@ bad_fork_cleanup_io: bad_fork_cleanup_namespaces: exit_nsproxy_namespaces(p); bad_fork_cleanup_mm: + sched_cache_fork_cleanup(p); if (p->mm) { mm_clear_owner(p->mm, p); mmput(p->mm); diff --git a/kernel/kprobes.c b/kernel/kprobes.c index 6337da5cab9e..4edd8ca5c657 100644 --- a/kernel/kprobes.c +++ b/kernel/kprobes.c @@ -42,6 +42,7 @@ #include <linux/execmem.h> #include <linux/cleanup.h> #include <linux/wait.h> +#include <linux/wait_bit.h> #include <asm/sections.h> #include <asm/cacheflush.h> @@ -526,7 +527,8 @@ enum { OPTIMIZER_ST_FLUSHING = 2, }; -static DECLARE_COMPLETION(optimizer_completion); +/* Bumped at the end of each kprobe_optimizer() pass, under 'kprobe_mutex' */ +static unsigned long optimizer_passes; #define OPTIMIZE_DELAY 5 @@ -654,9 +656,9 @@ static void kprobe_optimizer(void) do_free_cleaned_kprobes(); } - /* Step 5: Kick optimizer again if needed. But if there is a flush requested, */ - if (completion_done(&optimizer_completion)) - complete(&optimizer_completion); + /* Step 5: Wake up flushers, and kick optimizer again if needed. */ + optimizer_passes++; + wake_up_var_locked(&optimizer_passes, &kprobe_mutex); if (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list)) kick_kprobe_optimizer(); /*normal kick*/ @@ -708,7 +710,8 @@ static void wait_for_kprobe_optimizer_locked(void) lockdep_assert_held(&kprobe_mutex); while (!list_empty(&optimizing_list) || !list_empty(&unoptimizing_list)) { - init_completion(&optimizer_completion); + unsigned long passes = optimizer_passes; + /* * Set state to OPTIMIZER_ST_FLUSHING and wake up the thread if it's * idle. If it's already kicked, it will see the state change. @@ -717,9 +720,12 @@ static void wait_for_kprobe_optimizer_locked(void) OPTIMIZER_ST_FLUSHING) != OPTIMIZER_ST_FLUSHING) wake_up(&kprobe_optimizer_wait); - mutex_unlock(&kprobe_mutex); - wait_for_completion(&optimizer_completion); - mutex_lock(&kprobe_mutex); + /* + * kprobe_optimizer() holds 'kprobe_mutex' for a whole pass, which + * this drops while sleeping, so a new count means a full pass ran. + */ + wait_var_event_mutex(&optimizer_passes, + optimizer_passes != passes, &kprobe_mutex); } } diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index d2479c69d71a..c13f68ab7f6e 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -408,9 +408,18 @@ int hibernation_snapshot(int platform_mode) if (error) goto Close; + error = dpm_prepare(PMSG_FREEZE); + if (error) + goto Complete; + + /* Preallocate image memory before freezing kernel threads and shutting down devices. */ + error = hibernate_preallocate_memory(); + if (error) + goto Complete; + error = freeze_kernel_threads(); if (error) - goto Close; + goto Cleanup; if (hibernation_test(TEST_FREEZER)) { @@ -422,15 +431,6 @@ int hibernation_snapshot(int platform_mode) goto Thaw; } - error = dpm_prepare(PMSG_FREEZE); - if (error) - goto Complete; - - /* Preallocate image memory before shutting down devices. */ - error = hibernate_preallocate_memory(); - if (error) - goto Complete; - console_suspend_all(); pm_restrict_gfp_mask(); @@ -464,10 +464,12 @@ int hibernation_snapshot(int platform_mode) platform_end(platform_mode); return error; - Complete: - dpm_complete(PMSG_RECOVER); Thaw: thaw_kernel_threads(); + Cleanup: + swsusp_free(); + Complete: + dpm_complete(PMSG_RECOVER); goto Close; } diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0b846a13c628..1fe40de6ebe3 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5798,7 +5798,7 @@ void sched_tick(void) curr = rq->curr; donor = rq->donor; - psi_account_irqtime(rq, donor, NULL); + psi_account_irqtime(rq, curr, NULL); update_rq_clock(rq); hw_pressure = arch_scale_hw_pressure(cpu_of(rq)); diff --git a/kernel/sched/ext/cid.c b/kernel/sched/ext/cid.c index bc4eee5bb4cb..4b08866d75f6 100644 --- a/kernel/sched/ext/cid.c +++ b/kernel/sched/ext/cid.c @@ -912,30 +912,36 @@ bool scx_cmask_empty(const struct scx_cmask *m) /** * scx_bpf_cid_topo - Copy out per-cid topology info * @cid: cid to look up - * @out__uninit: where to copy the topology info; fully written by this call + * @out: where to copy the topology info + * @out__sz: size of @out, the program's sizeof(struct scx_cid_topo) * @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * - * Fill @out__uninit with the topology info for @cid. Trigger scx_error() if - * @cid is out of range. If @cid is valid but in the no-topo section, all fields - * are set to -1. All fields are also set to -1 when no cid tables have been - * published yet, which a program may observe while racing the root enable. + * Fill @out with the topology info for @cid. Trigger scx_error() if @cid is out + * of range. If @cid is valid but in the no-topo section, all fields are set to + * -1. All fields are also set to -1 when no cid tables have been published yet, + * which a program may observe while racing the root enable. + * + * The program's struct may be older or newer than the kernel's. The smaller of + * @out__sz and the kernel's size is copied and the rest of @out is set to -1. */ -__bpf_kfunc void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out__uninit, +__bpf_kfunc void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out, size_t out__sz, const struct bpf_prog_aux *aux) { + size_t len = min(out__sz, sizeof(*out)); struct scx_cid_topo *topo; struct scx_sched *sch; + /* the error cases and fields the kernel lacks read as -1 */ + memset(out, 0xff, out__sz); + guard(rcu)(); sch = scx_prog_sched(aux); topo = rcu_dereference(scx_cid_topo); - if (unlikely(!sch) || !cid_valid(sch, cid) || unlikely(!topo)) { - *out__uninit = SCX_CID_TOPO_NEG; + if (unlikely(!sch) || !cid_valid(sch, cid) || unlikely(!topo)) return; - } - *out__uninit = topo[cid]; + memcpy(out, &topo[cid], len); } __bpf_kfunc_end_defs(); diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 3219f0da0fe4..e56c3c95018f 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -447,37 +447,45 @@ static void switch_rq_lock(struct rq *from, struct rq *to) DEFINE_STATIC_KEY_FALSE(__scx_is_cid_type); /** - * scx_call_op_set_cpumask - invoke ops.set_cpumask / ops_cid.set_cmask for @task + * scx_fill_cmask_scratch - Build this cpu's arena cmask from @cpumask + * @sch: scx_sched whose scratch to fill + * @cpumask: cpus to translate into cids + * + * The scratch lives in BPF-writable arena memory and its header can't be + * trusted, so it is rewritten from kernel geometry rather than read. Caller + * must hold an rq lock so this cpu is the sole kernel writer for as long as the + * returned address is in use. + */ +static struct scx_cmask *scx_fill_cmask_scratch(struct scx_sched *sch, + const struct cpumask *cpumask) +{ + struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch); + struct scx_cmask_ref ref; + + scx_cmask_ref_init_kern(sch, kern_va, 0, num_possible_cpus(), &ref); + scx_cmask_ref_from_cpumask(&ref, cpumask); + return kern_va; +} + +/** + * scx_call_op_set_cpumask - Invoke the set_cpumask or set_cmask op for @task * @sch: scx_sched being invoked * @rq: rq to update as the currently-locked rq, or NULL * @task: task whose affinity is changing * @cpumask: new cpumask * - * For cid-form schedulers, translate @cpumask to a cmask via the per-cpu - * scratch in cid.c and dispatch through the ops_cid union view. Caller - * must hold @rq's rq lock so this_cpu_ptr is stable across the call. + * For cid-form schedulers, translate @cpumask to a cmask in the per-cpu scratch + * and dispatch through the ops_cid union view. Caller must hold @rq's rq lock. */ static inline void scx_call_op_set_cpumask(struct scx_sched *sch, struct rq *rq, struct task_struct *task, const struct cpumask *cpumask) { - if (scx_is_cid_type()) { - struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch); - struct scx_cmask_ref ref; - - /* - * Build the per-cpu arena cmask from kernel geometry via @ref, - * never reading its BPF-writable header. set_cmask()'s __arena - * argument takes the kernel address and the struct_ops - * trampoline rebases it into BPF's arena pointer form. The rq - * lock makes this cpu the sole kernel writer. - */ - scx_cmask_ref_init_kern(sch, kern_va, 0, num_possible_cpus(), &ref); - scx_cmask_ref_from_cpumask(&ref, cpumask); - SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, kern_va); - } else { + if (scx_is_cid_type()) + SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, + scx_fill_cmask_scratch(sch, cpumask)); + else SCX_CALL_OP_TASK(sch, set_cpumask, rq, task, cpumask); - } } enum scx_dsq_iter_flags { @@ -1499,27 +1507,22 @@ static inline bool task_scx_migrating(struct task_struct *p) return p->scx.sticky_cpu >= 0; } -/* - * Call ops.dequeue() if the task is in BPF custody and not migrating. - * Clears %SCX_TASK_IN_CUSTODY when the callback is invoked. - */ -static void call_task_dequeue(struct scx_sched *sch, struct rq *rq, - struct task_struct *p, u64 deq_flags) +/* Must be called under the lock serializing @p's custody transfers. */ +static bool task_leave_custody(struct task_struct *p) { if (!(p->scx.flags & SCX_TASK_IN_CUSTODY) || task_scx_migrating(p)) - return; - - if (SCX_HAS_OP(sch, dequeue)) - SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags); + return false; p->scx.flags &= ~SCX_TASK_IN_CUSTODY; + return true; } static void rq_owned_post_enq(struct scx_sched *sch, struct rq *rq, struct scx_dispatch_q *dsq, struct task_struct *p, u64 enq_flags) { - call_task_dequeue(sch, rq, p, 0); + if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0); /* * Only local inserts get the wakeup treatment below. Rejects kick the @@ -1705,20 +1708,28 @@ static void scx_dispatch_enqueue(struct scx_sched *sch, struct rq *rq, if (is_rq_owned) { rq_owned_post_enq(sch, rq, dsq, p, enq_flags); } else { + bool call_dequeue = false; + /* * Global and bypass DSQs are terminal - the task leaves the - * scheduler's custody, so ops.dequeue() fires here. It can run + * scheduler's custody, so ops.dequeue() fires. It can run * without @p's rq lock (finish_dispatch() passes the dispatch * rq); that's safe because dequeue_task_scx() waits on * SCX_OPSS_DISPATCHING (see the ops_state note above) and so * can't race it. A non-terminal DSQ keeps the task in custody. + * The custody transfer happens under @dsq->lock so that later + * consumers see the flag clear; the callback runs after + * @dsq->lock is dropped because it may lock a DSQ itself. */ if (dsq->id == SCX_DSQ_GLOBAL || dsq->id == SCX_DSQ_BYPASS) - call_task_dequeue(sch, rq, p, 0); + call_dequeue = task_leave_custody(p); else p->scx.flags |= SCX_TASK_IN_CUSTODY; raw_spin_unlock(&dsq->lock); + + if (call_dequeue && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, 0); } /* @@ -2141,7 +2152,12 @@ static void enqueue_task_scx(struct rq *rq, struct task_struct *p, int core_enq_ int sticky_cpu = p->scx.sticky_cpu; u64 enq_flags = core_enq_flags | rq->scx.remote_activate_enq_flags; - if (enq_flags & ENQUEUE_WAKEUP) + /* + * SCX_RQ_IN_WAKEUP promises a task_woken_scx() call once this enqueue + * returns. Only the core's wakeup path delivers one. The flags stashed + * for a remote activation may carry the wakeup bit without it. + */ + if (core_enq_flags & ENQUEUE_WAKEUP) rq->scx.flags |= SCX_RQ_IN_WAKEUP; /* @@ -2210,7 +2226,7 @@ retry: /* * A queued task must always be in BPF scheduler's custody. If * SCX_TASK_IN_CUSTODY is clear, finish_dispatch() on another - * CPU has already passed call_task_dequeue() (which clears the + * CPU has already passed task_leave_custody() (which clears the * flag), but has not yet written SCX_OPSS_NONE. That final * store does not require this rq's lock, so retrying with * cpu_relax() is bounded: we will observe NONE (or DISPATCHING, @@ -2258,7 +2274,8 @@ retry: * NONE but the task may still have %SCX_TASK_IN_CUSTODY set until * it is enqueued on the destination. */ - call_task_dequeue(sch, rq, p, deq_flags); + if (task_leave_custody(p) && SCX_HAS_OP(sch, dequeue)) + SCX_CALL_OP_TASK(sch, dequeue, rq, p, deq_flags); } static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int core_deq_flags) @@ -2374,14 +2391,10 @@ static void wakeup_preempt_scx(struct rq *rq, struct task_struct *p, int wake_fl } void scx_move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p, - u64 enq_flags, struct scx_dispatch_q *src_dsq, - struct rq *dst_rq) + u64 enq_flags, struct rq *dst_rq) { struct scx_dispatch_q *dst_dsq = scx_resolve_local_dsq(sch, dst_rq, p, &enq_flags); - /* @p is on @dst_rq, an rq-owned @src_dsq is covered by the rq lock */ - if (!dsq_is_rq_owned(src_dsq)) - lockdep_assert_held(&src_dsq->lock); lockdep_assert_rq_held(dst_rq); WARN_ON_ONCE(p->scx.holding_cpu >= 0); @@ -2629,8 +2642,8 @@ static struct rq *move_task_between_dsqs(struct scx_sched *sch, /* @p is going from a non-local DSQ to a local DSQ */ if (src_rq == dst_rq) { scx_task_unlink_from_dsq(p, src_dsq); - scx_move_local_task_to_local_dsq(sch, p, enq_flags, src_dsq, dst_rq); raw_spin_unlock(&src_dsq->lock); + scx_move_local_task_to_local_dsq(sch, p, enq_flags, dst_rq); } else { raw_spin_unlock(&src_dsq->lock); move_remote_task_to_local_dsq(sch, p, enq_flags, src_rq, dst_rq); @@ -2680,8 +2693,8 @@ retry: if (rq == task_rq) { scx_task_unlink_from_dsq(p, dsq); - scx_move_local_task_to_local_dsq(sch, p, enq_flags, dsq, rq); raw_spin_unlock(&dsq->lock); + scx_move_local_task_to_local_dsq(sch, p, enq_flags, rq); return true; } @@ -3629,8 +3642,12 @@ static void set_cpus_allowed_scx(struct task_struct *p, * * Fine-grained memory write control is enforced by BPF making the const * designation pointless. Cast it away when calling the operation. + * + * The cid form receives the initial mask when the task is enabled and + * hears about changes only afterwards, see struct scx_enable_args. */ - if (SCX_HAS_OP(sch, set_cpumask)) + if (SCX_HAS_OP(sch, set_cpumask) && + (!scx_is_cid_type() || scx_get_task_state(p) == SCX_TASK_ENABLED)) scx_call_op_set_cpumask(sch, task_rq(p), p, (struct cpumask *)p->cpus_ptr); } @@ -3939,8 +3956,27 @@ static void __scx_enable_task(struct scx_sched *sch, struct task_struct *p) p->scx.weight = sched_weight_to_cgroup(weight); - if (SCX_HAS_OP(sch, enable)) - SCX_CALL_OP_TASK(sch, enable, rq, p); + if (SCX_HAS_OP(sch, enable)) { + if (scx_is_cid_type()) { + struct scx_cmask *cmask = scx_fill_cmask_scratch(sch, p->cpus_ptr); + struct scx_enable_args args = { + .cmask_arena_addr = scx_kaddr_to_arena(sch, cmask), + }; + + SCX_CALL_CID_OP_TASK(sch, enable, rq, p, &args); + } else { + SCX_CALL_OP_TASK(sch, enable, rq, p); + } + } + + /* + * The initial mask also goes out through set_cmask() so a scheduler can + * track affinity there alone, and before set_weight() so that the mask + * is in place when weight-dependent state is derived, see struct + * scx_enable_args. + */ + if (scx_is_cid_type() && SCX_HAS_OP(sch, set_cpumask)) + scx_call_op_set_cpumask(sch, rq, p, p->cpus_ptr); if (SCX_HAS_OP(sch, set_weight)) SCX_CALL_OP_TASK(sch, set_weight, rq, p, p->scx.weight); @@ -4283,9 +4319,10 @@ static void switching_to_scx(struct rq *rq, struct task_struct *p) /* * set_cpus_allowed_scx() is not called while @p is associated with a - * different scheduler class. Keep the BPF scheduler up-to-date. + * different scheduler class. Keep the BPF scheduler up-to-date. The cid + * form gets its mask from scx_enable_task(). */ - if (SCX_HAS_OP(sch, set_cpumask)) + if (!scx_is_cid_type() && SCX_HAS_OP(sch, set_cpumask)) scx_call_op_set_cpumask(sch, rq, p, (struct cpumask *)p->cpus_ptr); } @@ -4404,6 +4441,17 @@ static bool local_task_should_reenq(struct rq *rq, struct task_struct *p, return *reenq_flags & SCX_REENQ_ANY; } +/* + * The dispatcher stores the final ops_state after dropping the DSQ lock, so @p + * can be found on a DSQ while still %SCX_OPSS_DISPATCHING. Reenqueueing @p + * before that store lands would have it clobber the new %SCX_OPSS_QUEUED. + */ +void scx_reenq_wait_dispatching(struct task_struct *p) +{ + if (unlikely(atomic_long_read_acquire(&p->scx.ops_state) == SCX_OPSS_DISPATCHING)) + wait_ops_state(p, SCX_OPSS_DISPATCHING); +} + static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags) { LIST_HEAD(tasks); @@ -4447,6 +4495,7 @@ static u32 reenq_local(struct scx_sched *sch, struct rq *rq, u64 reenq_flags) if (!local_task_should_reenq(rq, p, &reenq_flags, &reason)) continue; + scx_reenq_wait_dispatching(p); scx_dispatch_dequeue(rq, p); if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK)) @@ -4570,6 +4619,7 @@ static void reenq_user(struct rq *rq, struct scx_dispatch_q *dsq, u64 reenq_flag } /* @p is on @dsq, its rq and @dsq are locked */ + scx_reenq_wait_dispatching(p); dispatch_dequeue_locked(p, dsq); raw_spin_unlock(&dsq->lock); @@ -8356,10 +8406,11 @@ static struct bpf_struct_ops bpf_sched_ext_ops = { /* * cid-form cfi stubs. Stubs whose signatures match the cpu-form (param types * identical, only param names differ across structs) are reused. Some need - * fresh stubs, set_cmask due to an argument type difference and the sub-sched - * notifiers because no cpu-form stub exists to reuse. + * fresh stubs, set_cmask and enable due to argument differences and the + * sub-sched notifiers because no cpu-form stub exists to reuse. */ static void sched_ext_ops_cid__set_cmask(struct task_struct *p, const struct scx_cmask *cmask__arena) {} +static void sched_ext_ops_cid__enable(struct task_struct *p, struct scx_enable_args *args) {} static void sched_ext_ops__sub_caps_updated(const struct scx_cmask *cmask__arena, u64 caps) {} static void sched_ext_ops__sub_ecaps_updated(s32 cid, u64 before, u64 after) {} @@ -8380,7 +8431,7 @@ static struct sched_ext_ops_cid __bpf_ops_sched_ext_ops_cid = { .update_idle = sched_ext_ops__update_idle, .init_task = sched_ext_ops__init_task, .exit_task = sched_ext_ops__exit_task, - .enable = sched_ext_ops__enable, + .enable = sched_ext_ops_cid__enable, .disable = sched_ext_ops__disable, #ifdef CONFIG_EXT_GROUP_SCHED .cpuctl_init = sched_ext_ops__cgroup_init, @@ -10403,8 +10454,7 @@ __bpf_kfunc const void *scx_bpf_online_cmask(const struct bpf_prog_aux *aux) if (unlikely(!online)) return NULL; - /* BPF rebases by the low 32 bits, like __arena callback args */ - return (void *)((unsigned long)online - sch->arena_kern_base); + return (void *)scx_kaddr_to_arena(sch, online); } /** diff --git a/kernel/sched/ext/inlines.h b/kernel/sched/ext/inlines.h index ed423bcc26b8..2ff5479334cb 100644 --- a/kernel/sched/ext/inlines.h +++ b/kernel/sched/ext/inlines.h @@ -129,8 +129,10 @@ scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, * scheduler's ops.dispatch() doesn't yield any tasks. */ if (scx_bypass_dsp_enabled(sch) && - scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) + scx_consume_dispatch_q(sch, rq, scx_bypass_dsq(sch, cpu), 0)) { + __scx_add_event(sch, SCX_EV_SUB_BYPASS_DISPATCH, 1); return SCX_DSP_LOCAL; + } return SCX_DSP_NONE; } diff --git a/kernel/sched/ext/internal.h b/kernel/sched/ext/internal.h index 3464e0f113c1..1df8f583b0ec 100644 --- a/kernel/sched/ext/internal.h +++ b/kernel/sched/ext/internal.h @@ -250,6 +250,31 @@ struct scx_exit_task_args { bool cancelled; }; +/** + * struct scx_enable_args - Argument container for cid-form ops.enable() + * @cmask_arena_addr: BPF arena address of the cmask of cids the task may run on + * + * @cmask_arena_addr is the task's affinity as it enters the scheduler. + * set_cmask() delivers the same mask right after enable(), before set_weight() + * and the first enqueue, then every affinity change afterwards, and is never + * called before enable(). A scheduler may therefore track affinity in + * set_cmask() alone. + * + * The kernel builds the mask in the scheduler arena from its own geometry, so + * the header is valid regardless of what the scheduler last wrote there. The + * memory is per-cpu scratch reused once the callback returns: copy the bits + * out, don't keep the address. The set_cmask() argument follows the same rules. + * + * The address is a plain value rather than a typed pointer because BTF can't + * mark a struct member as an arena pointer yet and a pointer member would reach + * the program typed as a kernel pointer. Cast it to struct scx_cmask __arena * + * before use. Once arena members can be typed, a typed alias will join this + * field in an anonymous union at the same offset. + */ +struct scx_enable_args { + u64 cmask_arena_addr; +}; + /* argument container for ops.cgroup_init() */ struct scx_cgroup_init_args { /* the weight of the cgroup [1..10000] */ @@ -1037,6 +1062,7 @@ struct sched_ext_ops { * - dispatch -> dispatch (cpu arg is now cid) * - update_idle -> update_idle (cpu arg is now cid) * - set_cpumask -> set_cmask (cmask instead of cpumask) + * - enable -> enable (takes struct scx_enable_args) * - cpu_online -> cid_online * - cpu_offline -> cid_offline * - dump_cpu -> dump_cid @@ -1070,7 +1096,7 @@ struct sched_ext_ops_cid { struct scx_init_task_args *args); void (*exit_task)(struct task_struct *p, struct scx_exit_task_args *args); - void (*enable)(struct task_struct *p); + void (*enable)(struct task_struct *p, struct scx_enable_args *args); void (*disable)(struct task_struct *p); void (*dump)(struct scx_dump_ctx *ctx); void (*dump_cid)(struct scx_dump_ctx *ctx, s32 cid, bool idle); @@ -1533,7 +1559,8 @@ struct scx_sched { * by BUILD_BUG_ON in scx_init()). The anonymous union lets the kernel * access either view of the same storage without function-pointer * casts: use .ops for cpu-form and shared fields, .ops_cid for the - * cid-renamed callbacks (set_cmask, select_cid, cid_online, ...). + * callbacks whose cid-form signature differs (set_cmask, enable, + * select_cid, cid_online, ...). */ union { struct sched_ext_ops ops; @@ -1556,9 +1583,9 @@ struct scx_sched { uintptr_t arena_kern_base; /* - * Per-CPU arena cmask used by scx_call_op_set_cpumask() to hand a cmask - * to ops_cid.set_cmask(). The kernel writes through the stored kern_va - * and passes it to the callback's __arena argument. + * Per-CPU arena cmask the kernel fills from a task's cpumask and hands + * to ops_cid.enable() and ops_cid.set_cmask(). The stored pointers are + * the kernel addresses. */ struct scx_cmask * __percpu *set_cmask_scratch; struct scx_cmask *online_cmask; @@ -1669,6 +1696,19 @@ static inline void *scx_arena_to_kaddr(struct scx_sched *sch, const void *bpf_pt return (void *)(sch->arena_kern_base + (u32)(uintptr_t)bpf_ptr); } +/** + * scx_kaddr_to_arena - Translate a kernel arena address to the BPF form + * @sch: scheduler whose arena hosts @kaddr + * @kaddr: kernel address inside @sch's arena + * + * __arena callback arguments need no translation. Addresses handed to BPF any + * other way, such as struct fields and kfunc return values, go through this. + */ +static inline uintptr_t scx_kaddr_to_arena(struct scx_sched *sch, const void *kaddr) +{ + return (uintptr_t)kaddr - sch->arena_kern_base; +} + enum scx_wake_flags { /* expose select WF_* flags as enums */ SCX_WAKE_FORK = WF_FORK, @@ -2065,8 +2105,7 @@ void scx_dispatch_dequeue(struct rq *rq, struct task_struct *p); void scx_do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags, int sticky_cpu); void scx_move_local_task_to_local_dsq(struct scx_sched *sch, struct task_struct *p, - u64 enq_flags, struct scx_dispatch_q *src_dsq, - struct rq *dst_rq); + u64 enq_flags, struct rq *dst_rq); bool scx_consume_dispatch_q(struct scx_sched *sch, struct rq *rq, struct scx_dispatch_q *dsq, u64 enq_flags); bool scx_consume_global_dsq(struct scx_sched *sch, struct rq *rq); @@ -2078,6 +2117,7 @@ void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags); u64 __scx_bpf_now(struct rq *rq); void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq, u64 reenq_flags, struct rq *locked_rq); +void scx_reenq_wait_dispatching(struct task_struct *p); int __scx_init_task(struct scx_sched *sch, struct task_struct *p, struct cgroup *cgrp, bool fork); void scx_enable_task(struct scx_sched *sch, struct task_struct *p); @@ -2302,9 +2342,9 @@ do { \ } while (0) /* - * Dispatch a task op through the cid-form ops_cid table. Only set_cmask() needs - * this: it takes an arena cmask address instead of a cpumask, so it cannot be - * invoked via its cpu-form set_cpumask() slot. + * Dispatch a task op through the cid-form ops_cid table, for the ops whose + * cid-form signature differs from the cpu-form slot: set_cmask() takes an arena + * cmask instead of a cpumask and enable() takes scx_enable_args. */ #define SCX_CALL_CID_OP_TASK(sch, op, locked_rq, task, args...) \ __SCX_CALL_OP_TASK(sch, ops_cid, op, locked_rq, task, ##args) diff --git a/kernel/sched/ext/sub.c b/kernel/sched/ext/sub.c index 34e642a1a403..0472eaf41c7c 100644 --- a/kernel/sched/ext/sub.c +++ b/kernel/sched/ext/sub.c @@ -555,8 +555,8 @@ static void scx_rescue_timerfn(struct timer_list *timer) scx.dsq_list.node); scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq); scx_rescue_admit(rq, p, slice); - scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_IGNORE_CAPS, - &rq->scx.rescue.dsq, rq); + scx_move_local_task_to_local_dsq(scx_task_sched(p), p, + SCX_ENQ_IGNORE_CAPS, rq); if (sched_class_above(&ext_sched_class, rq->curr->sched_class)) resched_curr(rq); } else if (p->scx.dsq && rq->scx.rescue.budget > 2 * scx_rescue_quantum_ns) { @@ -572,7 +572,7 @@ static void scx_rescue_timerfn(struct timer_list *timer) scx_task_unlink_from_dsq(p, &rq->scx.local_dsq); scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_HEAD | SCX_ENQ_PREEMPT | SCX_ENQ_IGNORE_CAPS, - &rq->scx.local_dsq, rq); + rq); } out_arm: scx_rescue_timer_arm(rq); @@ -596,8 +596,8 @@ void scx_rescue_flush(struct rq *rq) /* and flush out all pending ones */ list_for_each_entry_safe(p, n, &rq->scx.rescue.dsq.list, scx.dsq_list.node) { scx_task_unlink_from_dsq(p, &rq->scx.rescue.dsq); - scx_move_local_task_to_local_dsq(scx_task_sched(p), p, SCX_ENQ_IGNORE_CAPS, - &rq->scx.rescue.dsq, rq); + scx_move_local_task_to_local_dsq(scx_task_sched(p), p, + SCX_ENQ_IGNORE_CAPS, rq); } timer_delete(&rq->scx.rescue.timer); @@ -801,6 +801,7 @@ void scx_reenq_reject(struct rq *rq) if (WARN_ON_ONCE(p->migration_pending)) continue; + scx_reenq_wait_dispatching(p); scx_dispatch_dequeue(rq, p); if (WARN_ON_ONCE(p->scx.flags & SCX_TASK_REENQ_REASON_MASK)) diff --git a/kernel/sched/ext/types.h b/kernel/sched/ext/types.h index 943d8d429a2c..139176cf9fc6 100644 --- a/kernel/sched/ext/types.h +++ b/kernel/sched/ext/types.h @@ -70,6 +70,10 @@ enum scx_consts { * smaller shards if the LLC exceeds the target size. No-topo cids are packed * into their own max-sized shards. * + * New fields are appended, never inserted: scx_bpf_cid_topo() copies this + * struct out sized by the program's own layout, and an older program's copy + * must stay a prefix of the kernel's. + * * @core_cid: first cid of this cid's core (smt-sibling group) * @core_idx: global index of that core, in [0, nr_cores_at_init) * @llc_cid: first cid of this cid's LLC diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 7455a83a6a99..57360f5cdde4 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1478,7 +1478,7 @@ static inline int get_sched_cache_scale(int mul) return (1 + (tol - 1) * mul); } -static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) +static bool exceed_llc_capacity(struct sched_cache_group *grp, int cpu) { #ifdef CONFIG_NUMA_BALANCING unsigned long llc, footprint; @@ -1497,7 +1497,7 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) * excluded. */ llc = sd->llc_bytes; - footprint = READ_ONCE(mm->sc_stat.footprint); + footprint = READ_ONCE(grp->footprint); /* * Scale the LLC size by 256*llc_aggr_tolerance @@ -1526,7 +1526,7 @@ static bool exceed_llc_capacity(struct mm_struct *mm, int cpu) return false; } -static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, +static bool invalid_llc_nr(struct sched_cache_group *grp, struct task_struct *p, int cpu) { int scale; @@ -1542,10 +1542,32 @@ static bool invalid_llc_nr(struct mm_struct *mm, struct task_struct *p, if (scale == INT_MAX) return false; - return !fits_capacity((mm->sc_stat.nr_running_avg * cpu_smt_num_threads), + return !fits_capacity((READ_ONCE(grp->nr_running_avg) * cpu_smt_num_threads), (scale * per_cpu(sd_llc_size, cpu))); } +/* + * A task counts in nr_pref_llc_running while it is queued on its preferred + * LLC (pref_llc_queued) and runnable (!sched_delayed), keeping the counter in + * the runnable domain so alb_break_llc() can compare it with h_nr_runnable. + */ +static bool task_pref_llc_runnable(struct task_struct *p) +{ + return p->pref_llc_queued && !p->se.sched_delayed; +} + +static void pref_llc_running_inc(struct rq *rq, struct task_struct *p) +{ + if (task_pref_llc_runnable(p)) + rq->nr_pref_llc_running++; +} + +static void pref_llc_running_dec(struct rq *rq, struct task_struct *p) +{ + if (task_pref_llc_runnable(p)) + rq->nr_pref_llc_running--; +} + static void account_llc_enqueue(struct rq *rq, struct task_struct *p) { int pref_llc, pref_llc_queued; @@ -1557,7 +1579,6 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) pref_llc_queued = (pref_llc == task_llc(p)); rq->nr_llc_running++; - rq->nr_pref_llc_running += pref_llc_queued; /* * Record whether p is enqueued on its preferred @@ -1575,6 +1596,9 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) */ p->pref_llc_queued = pref_llc_queued; + /* Skipped while delayed; clear_delayed() adds it back on wake. */ + pref_llc_running_inc(rq, p); + sd = rcu_dereference_all(rq->sd); if (sd && (unsigned int)pref_llc < sd->llc_max) sd->llc_counts[pref_llc]++; @@ -1591,7 +1615,12 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p) rq->nr_llc_running--; if (p->pref_llc_queued) { - rq->nr_pref_llc_running--; + /* + * Skipped if still delayed (set_delayed() already removed it); + * clearing pref_llc_queued below also stops clear_delayed() + * from re-adding it. + */ + pref_llc_running_dec(rq, p); /* * Update the status in case * other logic might query @@ -1619,12 +1648,20 @@ static void account_llc_dequeue(struct rq *rq, struct task_struct *p) } } -void mm_init_sched(struct mm_struct *mm, - struct sched_cache_time __percpu *_pcpu_sched) +int mm_init_sched(struct mm_struct *mm, + struct sched_cache_time __percpu *_pcpu_sched) { + struct sched_cache_group *grp; unsigned long epoch = 0; int i; + grp = kzalloc_obj(*grp); + if (!grp) { + free_percpu(_pcpu_sched); + mm->sched_cache_grp = NULL; + return -ENOMEM; + } + for_each_possible_cpu(i) { struct sched_cache_time *pcpu_sched = per_cpu_ptr(_pcpu_sched, i); struct rq *rq = cpu_rq(i); @@ -1635,18 +1672,141 @@ void mm_init_sched(struct mm_struct *mm, epoch = rq->cpu_epoch; } - raw_spin_lock_init(&mm->sc_stat.lock); - mm->sc_stat.epoch = epoch; - mm->sc_stat.cpu = -1; - mm->sc_stat.next_scan = jiffies; - mm->sc_stat.nr_running_avg = 0; - mm->sc_stat.footprint = 0; + raw_spin_lock_init(&grp->lock); + grp->epoch = epoch; + grp->cpu = -1; + grp->next_scan = jiffies; + grp->nr_running_avg = 0; + grp->footprint = 0; + refcount_set(&grp->refcnt, 1); /* - * The update to mm->sc_stat should not be reordered - * before initialization to mm's other fields, in case + * The update to grp->pcpu_sched should not be reordered + * before initialization to grp's other fields, in case * the readers may get invalid mm_sched_epoch, etc. */ - smp_store_release(&mm->sc_stat.pcpu_sched, _pcpu_sched); + smp_store_release(&grp->pcpu_sched, _pcpu_sched); + /* + * Publish the group last. Not every reader qualifies it by + * grp->pcpu_sched - can_migrate_llc_task() only checks that the + * pointer is non-NULL before reading grp->footprint and + * grp->nr_running_avg - so a reachable group must already be + * fully initialized. + */ + smp_store_release(&mm->sched_cache_grp, grp); + return 0; +} + +static void sched_cache_group_free_rcu(struct rcu_head *rcu) +{ + struct sched_cache_group *grp = + container_of(rcu, struct sched_cache_group, rcu); + + free_percpu(grp->pcpu_sched); + kfree(grp); +} + +static void sched_cache_group_put(struct sched_cache_group *grp) +{ + if (!grp || !refcount_dec_and_test(&grp->refcnt)) + return; + + call_rcu(&grp->rcu, sched_cache_group_free_rcu); +} + +DEFINE_FREE(sched_cache_group_put, struct sched_cache_group *, + sched_cache_group_put(_T)); + +#define rcu_deref_sched_cache_grp(tsk) \ + rcu_dereference_check((tsk)->sched_cache_grp, (tsk) == current) + +static struct sched_cache_group *sched_cache_replace_grp(struct task_struct *p, + struct sched_cache_group *new) +{ + struct sched_cache_group *old; + + old = rcu_deref_sched_cache_grp(p); + rcu_assign_pointer(p->sched_cache_grp, new); + + return old; +} + +struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp) +{ + /* + * refcount_inc_not_zero() is the acquire primitive for lockless + * (RCU) lookups; plain refcount_inc() would scribble the count if + * it already reached zero. Return NULL in that case. + */ + if (grp && !refcount_inc_not_zero(&grp->refcnt)) + grp = NULL; + + return grp; +} + +struct sched_cache_group *task_cache_group_get(struct task_struct *p) +{ + guard(rcu)(); + return sched_cache_group_get(rcu_dereference(p->sched_cache_grp)); +} + +void sched_cache_fork(struct task_struct *p) +{ + /* + * The child takes its own reference on the mm's cache group, separate + * from the reference held by the mm. @p is not yet visible to readers, + * so a plain initializing store is enough. + */ + RCU_INIT_POINTER(p->sched_cache_grp, + sched_cache_group_get(p->mm->sched_cache_grp)); +} + +void sched_cache_fork_cleanup(struct task_struct *p) +{ + /* + * A fork that fails after sched_cache_fork() never reaches exit_mm(), + * so drop the reference here. @p never became visible, so there are no + * concurrent readers and the reference we hold keeps the group alive. + */ + sched_cache_group_put(rcu_access_pointer(p->sched_cache_grp)); + RCU_INIT_POINTER(p->sched_cache_grp, NULL); +} + +void sched_cache_exec_mmap(struct task_struct *p, struct mm_struct *mm) +{ + struct sched_cache_group *old; + + /* + * Acquire the new reference before publishing the pointer, then drop + * the old one. @p is current and the only writer of its own pointer. + */ + old = sched_cache_replace_grp(p, sched_cache_group_get(mm->sched_cache_grp)); + sched_cache_group_put(old); +} + +void sched_cache_exit_mm(struct task_struct *p) +{ + struct sched_cache_group *grp = sched_cache_replace_grp(p, NULL); + +#ifdef CONFIG_NUMA_BALANCING + /* + * Subtract this task's footprint from the group before dropping the + * reference, so the group footprint converges as its threads exit. + * Unlocked for performance; clamp to avoid underflow. + */ + if (grp && p->total_numa_faults) { + unsigned long fp = READ_ONCE(grp->footprint); + unsigned long sub = min(fp, p->total_numa_faults); + + WRITE_ONCE(grp->footprint, fp - sub); + } +#endif + sched_cache_group_put(grp); +} + +void mm_destroy_sched(struct mm_struct *mm) +{ + sched_cache_group_put(mm->sched_cache_grp); + mm->sched_cache_grp = NULL; } /* because why would C be fully specified */ @@ -1697,14 +1857,14 @@ static unsigned long fraction_mm_sched(struct rq *rq, return div64_u64(NICE_0_LOAD * pcpu_sched->runtime, rq->cpu_runtime + 1); } -static int get_pref_llc(struct task_struct *p, struct mm_struct *mm) +static int get_pref_llc(struct task_struct *p, struct sched_cache_group *grp) { int mm_sched_llc = -1, mm_sched_cpu; - if (!mm) + if (!grp) return -1; - mm_sched_cpu = READ_ONCE(mm->sc_stat.cpu); + mm_sched_cpu = READ_ONCE(grp->cpu); if (mm_sched_cpu != -1) { mm_sched_llc = llc_id(mm_sched_cpu); @@ -1734,8 +1894,8 @@ static unsigned int task_running_on_cpu(int cpu, struct task_struct *p); static inline void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) { + struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp); struct sched_cache_time *pcpu_sched; - struct mm_struct *mm = p->mm; int mm_sched_llc = -1; unsigned long epoch; @@ -1746,12 +1906,18 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) return; /* * init_task, kthreads and user thread created - * by user_mode_thread() don't have mm. + * by user_mode_thread() don't have a cache group. + * In theory a kernel thread does not have any valid + * cache group, because sched_cache_fork() is not + * invoked for a kernel thread - !grp should gate the + * kernel thread. Use the PF_KTHREAD check explicitly + * here for safety reasons, to guard against future + * modifications and to pair with task_tick_cache(). */ - if (!mm || !mm->sc_stat.pcpu_sched) + if (p->flags & PF_KTHREAD || !grp || !grp->pcpu_sched) return; - pcpu_sched = per_cpu_ptr(mm->sc_stat.pcpu_sched, cpu_of(rq)); + pcpu_sched = per_cpu_ptr(grp->pcpu_sched, cpu_of(rq)); scoped_guard (raw_spinlock, &rq->cpu_epoch_lock) { __update_mm_sched(rq, pcpu_sched); @@ -1764,14 +1930,14 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) * If this process hasn't hit task_cache_work() for a while invalidate * its preferred state. */ - if ((long)(epoch - READ_ONCE(mm->sc_stat.epoch)) > llc_epoch_affinity_timeout || - invalid_llc_nr(mm, p, cpu_of(rq)) || - exceed_llc_capacity(mm, cpu_of(rq))) { - if (READ_ONCE(mm->sc_stat.cpu) != -1) - WRITE_ONCE(mm->sc_stat.cpu, -1); + if ((long)(epoch - READ_ONCE(grp->epoch)) > llc_epoch_affinity_timeout || + invalid_llc_nr(grp, p, cpu_of(rq)) || + exceed_llc_capacity(grp, cpu_of(rq))) { + if (READ_ONCE(grp->cpu) != -1) + WRITE_ONCE(grp->cpu, -1); } - mm_sched_llc = get_pref_llc(p, mm); + mm_sched_llc = get_pref_llc(p, grp); /* task not on rq accounted later in account_entity_enqueue() */ if (task_running_on_cpu(rq->cpu, p) && @@ -1784,31 +1950,32 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec) static void task_tick_cache(struct rq *rq, struct task_struct *p) { + struct sched_cache_group *grp = rcu_dereference_all(p->sched_cache_grp); struct callback_head *work = &p->cache_work; - struct mm_struct *mm = p->mm; unsigned long epoch; if (!sched_cache_enabled()) return; - if (!mm || p->flags & PF_KTHREAD || - !mm->sc_stat.pcpu_sched) + if (!grp || p->flags & PF_KTHREAD || + !grp->pcpu_sched) return; epoch = rq->cpu_epoch; /* avoid moving backwards */ - if (time_after_eq(mm->sc_stat.epoch, epoch)) + if (time_after_eq(grp->epoch, epoch)) return; - guard(raw_spinlock)(&mm->sc_stat.lock); + guard(raw_spinlock)(&grp->lock); if (work->next == work) { task_work_add(p, work, TWA_RESUME); - WRITE_ONCE(mm->sc_stat.epoch, epoch); + WRITE_ONCE(grp->epoch, epoch); } } -static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p) +static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p, + struct sched_cache_group *grp) { #ifdef CONFIG_NUMA_BALANCING int cpu, curr_cpu, nid, pref_nid; @@ -1816,7 +1983,7 @@ static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p) if (!static_branch_likely(&sched_numa_balancing)) goto out; - cpu = READ_ONCE(p->mm->sc_stat.cpu); + cpu = READ_ONCE(grp->cpu); if (cpu != -1) nid = cpu_to_node(cpu); curr_cpu = task_cpu(p); @@ -1873,13 +2040,13 @@ static inline void update_avg_scale(u64 *avg, u64 sample) static void task_cache_work(struct callback_head *work) { + struct sched_cache_group *grp __free(sched_cache_group_put) = NULL; + cpumask_var_t cpus __free(free_cpumask_var) = CPUMASK_VAR_NULL; int cpu, m_a_cpu = -1, nr_running = 0, curr_cpu; unsigned long next_scan, now = jiffies; struct task_struct *p = current, *cur; unsigned long curr_m_a_occ = 0; - struct mm_struct *mm = p->mm; unsigned long m_a_occ = 0; - cpumask_var_t cpus; WARN_ON_ONCE(work != &p->cache_work); @@ -1888,21 +2055,30 @@ static void task_cache_work(struct callback_head *work) if (p->flags & PF_EXITING) return; - next_scan = READ_ONCE(mm->sc_stat.next_scan); + /* + * A reference makes sure grp is not released by others. The rcu + * lock can not be held till after zalloc_cpumask_var() below, + * because the latter might sleep. + */ + grp = task_cache_group_get(p); + if (!grp) + return; + + next_scan = READ_ONCE(grp->next_scan); if (time_before(now, next_scan)) return; /* only 1 thread is allowed to scan */ - if (!try_cmpxchg(&mm->sc_stat.next_scan, &next_scan, + if (!try_cmpxchg(&grp->next_scan, &next_scan, now + max_t(unsigned long, READ_ONCE(llc_epoch_period), 1))) return; curr_cpu = task_cpu(p); - if (invalid_llc_nr(mm, p, curr_cpu) || - exceed_llc_capacity(mm, curr_cpu)) { - if (READ_ONCE(mm->sc_stat.cpu) != -1) - WRITE_ONCE(mm->sc_stat.cpu, -1); + if (invalid_llc_nr(grp, p, curr_cpu) || + exceed_llc_capacity(grp, curr_cpu)) { + if (READ_ONCE(grp->cpu) != -1) + WRITE_ONCE(grp->cpu, -1); return; } @@ -1913,7 +2089,7 @@ static void task_cache_work(struct callback_head *work) scoped_guard (cpus_read_lock) { guard(rcu)(); - get_scan_cpumasks(cpus, p); + get_scan_cpumasks(cpus, p, grp); for_each_cpu(cpu, cpus) { /* XXX sched_cluster_active */ @@ -1926,16 +2102,20 @@ static void task_cache_work(struct callback_head *work) for_each_cpu(i, sched_domain_span(sd)) { occ = fraction_mm_sched(cpu_rq(i), - per_cpu_ptr(mm->sc_stat.pcpu_sched, i)); + per_cpu_ptr(grp->pcpu_sched, i)); a_occ += occ; if (occ > m_occ) { m_occ = occ; m_cpu = i; } + /* + * rcu_access_pointer() is used because the + * pointer is only compared, never dereferenced. + */ cur = rcu_dereference_all(cpu_rq(i)->curr); if (cur && !(cur->flags & (PF_EXITING | PF_KTHREAD)) && - cur->mm == mm) + rcu_access_pointer(cur->sched_cache_grp) == grp) nr_running++; } @@ -1959,7 +2139,7 @@ static void task_cache_work(struct callback_head *work) m_a_cpu = m_cpu; } - if (llc_id(cpu) == llc_id(READ_ONCE(mm->sc_stat.cpu))) + if (llc_id(cpu) == llc_id(READ_ONCE(grp->cpu))) curr_m_a_occ = a_occ; cpumask_andnot(cpus, cpus, sched_domain_span(sd)); @@ -1968,7 +2148,7 @@ static void task_cache_work(struct callback_head *work) if (m_a_occ > (2 * curr_m_a_occ)) { /* - * Avoid switching sc_stat.cpu too fast. + * Avoid switching sched_cache_grp->cpu too fast. * The reason to choose 2X is because: * 1. It is better to keep the preferred LLC stable, * rather than changing it frequently and cause migrations @@ -1977,11 +2157,10 @@ static void task_cache_work(struct callback_head *work) * 3. 2X is chosen based on test results, as it delivers * the optimal performance gain so far. */ - WRITE_ONCE(mm->sc_stat.cpu, m_a_cpu); + WRITE_ONCE(grp->cpu, m_a_cpu); } - update_avg_scale(&mm->sc_stat.nr_running_avg, nr_running); - free_cpumask_var(cpus); + update_avg_scale(&grp->nr_running_avg, nr_running); } void init_sched_mm(struct task_struct *p) @@ -1991,10 +2170,18 @@ void init_sched_mm(struct task_struct *p) init_task_work(work, task_cache_work); work->next = work; /* + * dup_task_struct() copies the parent's task_struct, including its + * sched_cache_grp, for which the child holds no reference. Clear it + * here - before copy_mm() runs - so the child never carries a + * borrowed pointer that the fork error path would put. + */ + RCU_INIT_POINTER(p->sched_cache_grp, NULL); + /* * Reset new task's preference to avoid * polluting account_llc_enqueue(). */ p->preferred_llc = -1; + p->pref_llc_queued = 0; } #else /* CONFIG_SCHED_CACHE */ @@ -2016,6 +2203,10 @@ static void account_llc_enqueue(struct rq *rq, struct task_struct *p) {} static void account_llc_dequeue(struct rq *rq, struct task_struct *p) {} +static void pref_llc_running_inc(struct rq *rq, struct task_struct *p) {} + +static void pref_llc_running_dec(struct rq *rq, struct task_struct *p) {} + #endif /* CONFIG_SCHED_CACHE */ /* @@ -3692,6 +3883,7 @@ static int preferred_group_nid(struct task_struct *p, int nid) static void task_numa_placement(struct task_struct *p) __context_unsafe(/* conditional locking */) { + struct sched_cache_group __maybe_unused *grp; int seq, nid, max_nid = NUMA_NO_NODE; unsigned long max_faults = 0; unsigned long fault_types[2] = { 0, 0 }; @@ -3784,19 +3976,24 @@ static void task_numa_placement(struct task_struct *p) * heuristic and occasional lost updates are tolerable. * * If a task exits, its corresponding footprint must - * be subtracted from the mm->sc_stat.footprint, otherwise - * the mm->sc_stat.footprint will not converge: - * the exiting thread's footprint remains unchanged/undecayed - * in mm->sc_stat.footprint. See exit_mm(). + * be subtracted from p->sched_cache_grp->footprint, + * otherwise the footprint will not converge: the + * exiting thread's footprint remains unchanged/undecayed. + * See exit_mm(). * * Lost updates and unsynchronized subtraction * in exit_mm() can cause footprint + diff to * go negative. Clamp to zero to prevent the * unsigned footprint from wrapping. */ - new_fp = (long)READ_ONCE(p->mm->sc_stat.footprint) + diff; - WRITE_ONCE(p->mm->sc_stat.footprint, - max(new_fp, 0L)); + scoped_guard(rcu) { + grp = rcu_dereference(p->sched_cache_grp); + + if (grp) { + new_fp = (long)READ_ONCE(grp->footprint) + diff; + WRITE_ONCE(grp->footprint, max(new_fp, 0L)); + } + } #endif } @@ -6390,15 +6587,27 @@ static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq); static void set_delayed(struct sched_entity *se) { - se->sched_delayed = 1; - /* * Delayed se of cfs_rq have no tasks queued on them. * Do not adjust h_nr_runnable since __dequeue_task() * will account it for blocked tasks. + * + * This check can be removed because when flat pick + * patches get merged as only task can get delayed, + * same for clear_delayed(). */ - if (!entity_is_task(se)) + if (!entity_is_task(se)) { + se->sched_delayed = 1; return; + } + + /* + * Drop a task leaving the runnable set. + * Needs to be called before sched_delayed is set. + * clear_delayed() mirrors this after clearing the flag. + */ + pref_llc_running_dec(rq_of(cfs_rq_of(se)), task_of(se)); + se->sched_delayed = 1; for_each_sched_entity(se) { struct cfs_rq *cfs_rq = cfs_rq_of(se); @@ -6420,6 +6629,13 @@ static void clear_delayed(struct sched_entity *se) if (!entity_is_task(se)) return; + /* + * Re-add on wake, after sched_delayed is cleared. On a final delayed + * dequeue account_llc_dequeue() already cleared pref_llc_queued, so + * this does nothing. + */ + pref_llc_running_inc(rq_of(cfs_rq_of(se)), task_of(se)); + for_each_sched_entity(se) { struct cfs_rq *cfs_rq = cfs_rq_of(se); @@ -10395,6 +10611,7 @@ enum migration_type { #define LBF_SOME_PINNED 0x08 #define LBF_ACTIVE_LB 0x10 #define LBF_LLC_PINNED 0x20 +#define LBF_ACTIVE_LB_LLC 0x40 struct lb_env { struct sched_domain *sd; @@ -10724,7 +10941,7 @@ static inline bool task_misfits_asym_cpu(struct lb_env *env, struct task_struct static enum llc_mig can_migrate_llc_task(struct lb_env *env, struct task_struct *p) { - struct mm_struct *mm; + struct sched_cache_group *grp; bool to_pref; int cpu, src_cpu, dst_cpu; @@ -10733,19 +10950,19 @@ static enum llc_mig can_migrate_llc_task(struct lb_env *env, src_cpu = env->src_cpu; dst_cpu = env->dst_cpu; - mm = p->mm; - if (!mm) + grp = rcu_dereference_all(p->sched_cache_grp); + if (!grp) return mig_unrestricted; - cpu = READ_ONCE(mm->sc_stat.cpu); + cpu = READ_ONCE(grp->cpu); if (cpu < 0 || cpus_share_cache(src_cpu, dst_cpu)) return mig_unrestricted; /* skip cache aware load balance for too many threads */ - if (invalid_llc_nr(mm, p, dst_cpu) || - exceed_llc_capacity(mm, dst_cpu)) { - if (READ_ONCE(mm->sc_stat.cpu) != -1) - WRITE_ONCE(mm->sc_stat.cpu, -1); + if (invalid_llc_nr(grp, p, dst_cpu) || + exceed_llc_capacity(grp, dst_cpu)) { + if (READ_ONCE(grp->cpu) != -1) + WRITE_ONCE(grp->cpu, -1); return mig_unrestricted; } @@ -10814,6 +11031,21 @@ alb_break_llc(struct lb_env *env) } /* + * Returns true if p's preferred LLC does not match the destination CPU + * under migrate_llc_task semantics. Passive LB passes migrate_llc_task + * in env->migration_type, while active LB carries LBF_ACTIVE_LB_LLC in + * env->flags to avoid overwriting env->migration_type. + */ +static inline bool +migrate_llc_task_wrong_dst(struct task_struct *p, struct lb_env *env) +{ + return sched_cache_enabled() && + (env->migration_type == migrate_llc_task || + env->flags & LBF_ACTIVE_LB_LLC) && + READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu); +} + +/* * Check if migrating task p from env->src_cpu to * env->dst_cpu breaks LLC localiy. */ @@ -10841,8 +11073,7 @@ static bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) * run on env->dst_cpu, skip the tasks do not prefer * env->dst_cpu, and find the one that prefers. */ - if (env->migration_type == migrate_llc_task && - READ_ONCE(p->preferred_llc) != llc_id(env->dst_cpu)) + if (migrate_llc_task_wrong_dst(p, env)) return true; if (can_migrate_llc_task(env, p) != mig_forbid) @@ -10865,6 +11096,12 @@ alb_break_llc(struct lb_env *env) } static inline bool +migrate_llc_task_wrong_dst(struct task_struct *p, struct lb_env *env) +{ + return false; +} + +static inline bool migrate_degrades_llc(struct task_struct *p, struct lb_env *env) { return false; @@ -10963,7 +11200,7 @@ int can_migrate_task(struct task_struct *p, struct lb_env *env) * 4) too many balance attempts have failed. */ if (env->flags & LBF_ACTIVE_LB) - return 1; + return !migrate_llc_task_wrong_dst(p, env); degrades = migrate_degrades_locality(p, env); if (!degrades) { @@ -13362,6 +13599,20 @@ static int need_active_balance(struct lb_env *env) } static int active_load_balance_cpu_stop(void *data); +static int active_load_balance_llc_cpu_stop(void *data); + +/* + * migration_type is checked elsewhere to decide migration policy, so + * it shouldn't be repurposed just to flag an LLC-directed active + * balance across the stopper. Pick the callback here instead. + */ +static inline cpu_stop_fn_t alb_stop_fn(struct lb_env *env) +{ + if (env->migration_type == migrate_llc_task) + return active_load_balance_llc_cpu_stop; + + return active_load_balance_cpu_stop; +} static int should_we_balance(struct lb_env *env) { @@ -13707,7 +13958,7 @@ more_balance: } if (active_balance) { stop_one_cpu_nowait(cpu_of(busiest), - active_load_balance_cpu_stop, busiest, + alb_stop_fn(&env), busiest, &busiest->active_balance_work); } preempt_enable(); @@ -13812,7 +14063,7 @@ update_next_balance(struct sched_domain *sd, unsigned long *next_balance) * least 1 task to be running on each physical CPU where possible, and * avoids physical / logical imbalances. */ -static int active_load_balance_cpu_stop(void *data) +static int __active_load_balance_cpu_stop(void *data, unsigned int lb_flags) { struct rq *busiest_rq = data; int busiest_cpu = cpu_of(busiest_rq); @@ -13862,7 +14113,7 @@ static int active_load_balance_cpu_stop(void *data) .src_cpu = busiest_rq->cpu, .src_rq = busiest_rq, .idle = CPU_IDLE, - .flags = LBF_ACTIVE_LB, + .flags = LBF_ACTIVE_LB | lb_flags, }; schedstat_inc(sd->alb_count); @@ -13890,6 +14141,16 @@ out_unlock: return 0; } +static int active_load_balance_cpu_stop(void *data) +{ + return __active_load_balance_cpu_stop(data, 0); +} + +static int active_load_balance_llc_cpu_stop(void *data) +{ + return __active_load_balance_cpu_stop(data, LBF_ACTIVE_LB_LLC); +} + /* * Scale the max sched_balance_rq interval with the number of CPUs in the system. * This trades load-balance latency on larger machines for less cross talk. diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c index 0248227d983a..3dab0253976f 100644 --- a/kernel/sched/topology.c +++ b/kernel/sched/topology.c @@ -985,8 +985,8 @@ void sched_cache_active_set(void) } /* - * Update the bottom sched_domain's llc_bytes for @cpu and all its - * LLC siblings. Called from cacheinfo_cpu_online() or + * Update the bottom sched_domain's llc_bytes for @cpus sharing a physical + * LLC. Called from cacheinfo_cpu_online() or * cacheinfo_cpu_pre_down() with cpu hotplug lock held. * * Note: get_effective_llc_bytes() returns 0 on PowerPC. @@ -996,17 +996,13 @@ void sched_cache_active_set(void) * and does not populates the per-CPU struct cpu_cacheinfo array * that get_cpu_cacheinfo_llc() reads. */ -void sched_update_llc_bytes(unsigned int cpu) +void sched_update_llc_bytes(const struct cpumask *cpus) { struct sched_domain *sd, *sdp; unsigned int i; sched_domains_mutex_lock(); - sdp = rcu_dereference_sched_domain(per_cpu(sd_llc, cpu)); - if (!sdp) - goto unlock; - /* * ci->shared_cpu_map is built incrementally as CPUs come * online, so the first CPU in an LLC initially sees @@ -1014,14 +1010,22 @@ void sched_update_llc_bytes(unsigned int cpu) * get_effective_llc_bytes(). Re-evaluating every LLC * sibling on each online event corrects this once the full * shared_cpu_map is known. + * + * The departing CPU's domains have already been detached when + * cacheinfo removes it. Use the surviving cache siblings instead. + * They may belong to different cpuset partitions, so use each CPU's + * own LLC domain to scale its share of the physical cache. */ - for_each_cpu(i, sched_domain_span(sdp)) { + for_each_cpu(i, cpus) { + sdp = rcu_dereference_sched_domain(per_cpu(sd_llc, i)); + if (!sdp) + continue; + sd = rcu_dereference_sched_domain(cpu_rq(i)->sd); if (sd) sd->llc_bytes = get_effective_llc_bytes(i, sdp); } -unlock: sched_domains_mutex_unlock(); } diff --git a/kernel/trace/fprobe.c b/kernel/trace/fprobe.c index 1e9b00997ff2..9f2d98181779 100644 --- a/kernel/trace/fprobe.c +++ b/kernel/trace/fprobe.c @@ -171,6 +171,11 @@ static inline bool write_fprobe_header(unsigned long *stack, static inline void read_fprobe_header(unsigned long *stack, struct fprobe **fp, unsigned int *size_words) { + if (!*stack) { + *fp = NULL; + *size_words = 0; + return; + } *fp = arch_decode_fprobe_header_fp(*stack); *size_words = arch_decode_fprobe_header_size(*stack); } @@ -203,6 +208,12 @@ static inline void read_fprobe_header(unsigned long *stack, { struct __fprobe_header *fph = (struct __fprobe_header *)stack; + if (!*stack) { + *fp = NULL; + *size_words = 0; + return; + } + *fp = fph->fp; *size_words = fph->size_words; } @@ -635,6 +646,10 @@ static int fprobe_fgraph_entry(struct ftrace_graph_ent *trace, struct fgraph_ops } } + /* Terminate the list, fgraph_reserve_data() does not clear it. */ + if (used && used < reserved_words) + fgraph_data[used] = 0; + /* If any exit_handler is set, data must be used. */ return used != 0; } diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 1ae3732a2c51..959525393739 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3918,7 +3918,7 @@ static void check_flush_dependency(struct workqueue_struct *target_wq, WARN_ONCE(current->flags & PF_MEMALLOC, "workqueue: PF_MEMALLOC task %d(%s) is flushing !WQ_MEM_RECLAIM %s:%ps", current->pid, current->comm, target_wq->name, target_func); - WARN_ONCE(worker && ((worker->current_pwq->wq->flags & + WARN_ONCE(worker && worker->current_pwq && ((worker->current_pwq->wq->flags & (WQ_MEM_RECLAIM | __WQ_LEGACY)) == WQ_MEM_RECLAIM), "workqueue: WQ_MEM_RECLAIM %s:%ps is flushing !WQ_MEM_RECLAIM %s:%ps", worker->current_pwq->wq->name, worker->current_func, diff --git a/mm/backing-dev.c b/mm/backing-dev.c index cecbcf9060a6..18e999053bae 100644 --- a/mm/backing-dev.c +++ b/mm/backing-dev.c @@ -910,8 +910,9 @@ static void cleanup_offline_cgwbs_workfn(struct work_struct *work) continue; spin_unlock_irq(&cgwb_lock); - while (cleanup_offline_cgwb(wb)) - cond_resched(); + do { + cond_resched_tasks_rcu_qs(); + } while (cleanup_offline_cgwb(wb)); spin_lock_irq(&cgwb_lock); wb_put(wb); diff --git a/mm/damon/core.c b/mm/damon/core.c index 644daf5a1656..1764620903f2 100644 --- a/mm/damon/core.c +++ b/mm/damon/core.c @@ -2342,36 +2342,39 @@ static bool damos_skip_charged_region(struct damon_target *t, { struct damos_quota *quota = &s->quota; unsigned long sz_to_skip; + bool skip = false; /* Skip previously charged regions */ if (quota->charge_target_from) { if (t != quota->charge_target_from) return true; - if (r == damon_last_region(t)) { - quota->charge_target_from = NULL; - quota->charge_addr_from = 0; - return true; - } if (quota->charge_addr_from && - r->ar.end <= quota->charge_addr_from) - return true; + r->ar.end <= quota->charge_addr_from) { + skip = true; + goto out; + } if (quota->charge_addr_from && r->ar.start < quota->charge_addr_from) { sz_to_skip = ALIGN_DOWN(quota->charge_addr_from - r->ar.start, min_region_sz); if (!sz_to_skip) { - if (damon_sz_region(r) <= min_region_sz) - return true; + if (damon_sz_region(r) <= min_region_sz) { + skip = true; + goto out; + } sz_to_skip = min_region_sz; } damon_split_region_at(t, r, sz_to_skip); - return true; + skip = true; } + } +out: + if (r == damon_last_region(t)) { quota->charge_target_from = NULL; quota->charge_addr_from = 0; } - return false; + return skip; } static void damos_update_stat(struct damos *s, @@ -3091,6 +3094,7 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s) struct damos_quota *quota = &s->quota; unsigned long throughput; unsigned long esz = ULONG_MAX; + unsigned long esz_time; if (!quota->ms && list_empty("a->goals)) { quota->esz = quota->sz; @@ -3111,8 +3115,8 @@ static void damos_set_effective_quota(struct damon_ctx *ctx, struct damos *s) 1000000, quota->total_charged_ns); else throughput = PAGE_SIZE * 1024; - esz = min(throughput * quota->ms, esz); - esz = max(ctx->min_region_sz, esz); + esz_time = max(throughput * quota->ms, ctx->min_region_sz); + esz = min(esz_time, esz); } if (quota->sz && quota->sz < esz) @@ -3241,8 +3245,15 @@ static void kdamond_apply_schemes(struct damon_ctx *c) max_region_sz = damon_region_sz_limit(c); mutex_lock(&c->walk_control_lock); damon_for_each_target(t, c) { - if (c->ops.target_valid && c->ops.target_valid(t) == false) + if (c->ops.target_valid && c->ops.target_valid(t) == false) { + damon_for_each_scheme(s, c) { + if (s->quota.charge_target_from != t) + continue; + s->quota.charge_target_from = NULL; + s->quota.charge_addr_from = 0; + } continue; + } damos_apply_target(c, t, max_region_sz); } diff --git a/mm/damon/ops-common.c b/mm/damon/ops-common.c index fbda70d8ea4d..8fc61d06d358 100644 --- a/mm/damon/ops-common.c +++ b/mm/damon/ops-common.c @@ -61,7 +61,12 @@ void damon_ptep_mkold(pte_t *pte, struct vm_area_struct *vma, unsigned long addr * device aspects. */ if (likely(pte_present(pteval))) - young |= ptep_test_and_clear_young(vma, addr, pte); + /* + * Arch implementation of ptep_test_and_clear_young() may + * require aligned @addr + */ + young |= ptep_test_and_clear_young(vma, PAGE_ALIGN_DOWN(addr), + pte); young |= mmu_notifier_clear_young(vma->vm_mm, addr, addr + PAGE_SIZE); if (young) folio_set_young(folio); diff --git a/mm/damon/vaddr.c b/mm/damon/vaddr.c index 0648400b2d65..04ee2a2c6a4d 100644 --- a/mm/damon/vaddr.c +++ b/mm/damon/vaddr.c @@ -293,22 +293,29 @@ out: } #ifdef CONFIG_HUGETLB_PAGE +static bool damon_hugetlb_ptep_mkold(pte_t *pte, struct mm_struct *mm, + struct vm_area_struct *vma, unsigned long addr, pte_t *entry) +{ + unsigned long psize = huge_page_size(hstate_vma(vma)); + + if (!pte_young(*entry)) + return false; + *entry = huge_ptep_get_and_clear(mm, addr, pte, psize); + *entry = pte_mkold(*entry); + set_huge_pte_at(mm, addr, pte, *entry, psize); + return true; +} + static void damon_hugetlb_mkold(pte_t *pte, struct mm_struct *mm, struct vm_area_struct *vma, unsigned long addr) { bool referenced = false; pte_t entry = huge_ptep_get(mm, addr, pte); struct folio *folio = pfn_folio(pte_pfn(entry)); - unsigned long psize = huge_page_size(hstate_vma(vma)); folio_get(folio); - if (pte_young(entry)) { - referenced = true; - entry = pte_mkold(entry); - set_huge_pte_at(mm, addr, pte, entry, psize); - } - + referenced = damon_hugetlb_ptep_mkold(pte, mm, vma, addr, &entry); if (mmu_notifier_clear_young(mm, addr, addr + huge_page_size(hstate_vma(vma)))) referenced = true; diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 4f6f58bf3db6..cea25773a6c9 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1967,6 +1967,15 @@ retry: struct hstate *h = folio_hstate(folio); bool adjust_surplus = false; + /* + * remove_hugetlb_folio()/update_and_free_hugetlb_folio() bail + * for gigantic hstates without runtime support, so dissolving one + * here would leave it on the free list and, on vmemmap restore + * failure, the add_hugetlb_folio() rollback corrupts that list. + */ + if (hstate_is_gigantic_no_runtime(h)) + goto out; + if (!available_huge_pages(h)) goto out; @@ -5161,18 +5170,21 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma, hugetlb_vma_lock_write(vma); i_mmap_lock_write(mapping); for (; old_addr < old_end; old_addr += sz, new_addr += sz) { + const unsigned long offset_to_last_entry = + (old_addr | last_addr_mask) - old_addr; + src_pte = hugetlb_walk(vma, old_addr, sz); if (!src_pte) { - old_addr |= last_addr_mask; - new_addr |= last_addr_mask; + old_addr += offset_to_last_entry; + new_addr += offset_to_last_entry; continue; } if (huge_pte_none(huge_ptep_get(mm, old_addr, src_pte))) continue; if (huge_pmd_unshare(&tlb, vma, old_addr, src_pte)) { - old_addr |= last_addr_mask; - new_addr |= last_addr_mask; + old_addr += offset_to_last_entry; + new_addr += offset_to_last_entry; continue; } diff --git a/mm/memblock.c b/mm/memblock.c index 021db49eb7fc..e6fdf95a0534 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -2886,6 +2886,7 @@ static const char * const flagname[] = { [ilog2(MEMBLOCK_RSRV_NOINIT)] = "RSV_NIT", [ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN", [ilog2(MEMBLOCK_KHO_SCRATCH)] = "KHO_SCRATCH", + [ilog2(MEMBLOCK_RSRV_HUGETLB)] = "RSV_HUGETLB", }; static int memblock_debug_show(struct seq_file *m, void *private) diff --git a/mm/rmap.c b/mm/rmap.c index d1819fd69938..f3b21aaa34ee 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -209,7 +209,11 @@ int __anon_vma_prepare(struct vm_area_struct *vma) /* page_table_lock to protect against threads */ spin_lock(&mm->page_table_lock); if (likely(!vma->anon_vma)) { - vma->anon_vma = anon_vma; + /* + * Make anon_vma fields visible before anon_vma is published. + * Paired with an address dependency in reusable_anon_vma(). + */ + smp_store_release(&vma->anon_vma, anon_vma); anon_vma_chain_assign(vma, avc, anon_vma); anon_rmap_tree_insert(avc, anon_vma); anon_vma->num_active_vmas++; @@ -2094,6 +2094,13 @@ static int anon_vma_compatible(struct vm_area_struct *a, struct vm_area_struct * * acceptable for merging, so we can do all of this optimistically. But * we do that READ_ONCE() to make sure that we never re-load the pointer. * + * The READ_ONCE() establishes an address dependency between anon_vma and + * any access to its fields, which pairs with the assignment to + * vma->anon_vma performed with release semantics in __anon_vma_prepare(). + * + * This is especially important as anon_vma's are SLAB_TYPESAFE_BY_RCU so + * accessing an uninitialised anon_vma's fields may result in a UAF. + * * IOW: that the "list_is_singular()" test on the anon_vma_chain only * matters for the 'stable anon_vma' case (ie the thing we want to avoid * is to return an anon_vma that is "complex" due to having gone through @@ -2108,6 +2115,7 @@ static struct anon_vma *reusable_anon_vma(struct vm_area_struct *old, struct vm_area_struct *b) { if (anon_vma_compatible(a, b)) { + /* Paired with a memory barrier in __anon_vma_prepare(). */ struct anon_vma *anon_vma = READ_ONCE(old->anon_vma); if (anon_vma && list_is_singular(&old->anon_vma_chain)) diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index 2859cbac3f26..c949c6a82945 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c @@ -55,6 +55,11 @@ static int vlan_dev_hard_header(struct sk_buff *skb, struct net_device *dev, int rc; if (!(vlan->flags & VLAN_FLAG_REORDER_HDR)) { + unsigned int hlen = READ_ONCE(dev->hard_header_len) + + READ_ONCE(dev->needed_headroom); + + if (skb_cow_head(skb, hlen) < 0) + return -ENOMEM; vhdr = skb_push(skb, VLAN_HLEN); vlan_tci = vlan->vlan_id; diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c index f7d88c33e23e..ad24d2486665 100644 --- a/net/bluetooth/bnep/core.c +++ b/net/bluetooth/bnep/core.c @@ -270,9 +270,14 @@ static int bnep_rx_extension(struct bnep_session *s, struct sk_buff *skb) BT_DBG("type 0x%x len %u", h->type, h->len); + if (skb->len < h->len) { + err = -EILSEQ; + break; + } + switch (h->type & BNEP_TYPE_MASK) { case BNEP_EXT_CONTROL: - bnep_rx_control(s, skb->data, skb->len); + bnep_rx_control(s, skb->data, h->len); break; default: @@ -373,6 +378,11 @@ static int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb) goto badframe; } + if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) { + kfree_skb(skb); + return 0; + } + /* Strip 802.1p header */ if (ntohs(s->eh.h_proto) == ETH_P_8021Q) { if (!skb_pull(skb, 4)) @@ -451,6 +461,11 @@ static int bnep_tx_frame(struct bnep_session *s, struct sk_buff *skb) goto send; } + if (skb->len < ETH_HLEN) { + kfree_skb(skb); + return 0; + } + iv[il++] = (struct kvec) { &type, 1 }; len++; diff --git a/net/bluetooth/bnep/netdev.c b/net/bluetooth/bnep/netdev.c index ee1e39a3daff..b451ef457741 100644 --- a/net/bluetooth/bnep/netdev.c +++ b/net/bluetooth/bnep/netdev.c @@ -166,6 +166,12 @@ static netdev_tx_t bnep_net_xmit(struct sk_buff *skb, BT_DBG("skb %p, dev %p", skb, dev); + if (!pskb_may_pull(skb, ETH_HLEN)) { + dev->stats.tx_dropped++; + kfree_skb(skb); + return NETDEV_TX_OK; + } + #ifdef CONFIG_BT_BNEP_MC_FILTER if (bnep_net_mc_filter(skb, s)) { kfree_skb(skb); @@ -218,7 +224,7 @@ void bnep_net_setup(struct net_device *dev) dev->addr_len = ETH_ALEN; ether_setup(dev); - dev->min_mtu = 0; + dev->min_mtu = ETH_MIN_MTU; dev->max_mtu = ETH_MAX_MTU; dev->priv_flags &= ~IFF_TX_SKB_SHARING; dev->netdev_ops = &bnep_netdev_ops; diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index fa72cf8aaa7a..96195d2fd10f 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -2079,6 +2079,8 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst, cis->conn_timeout = timeout; } + hci_conn_hold(cis); + if (cis->state == BT_CONNECTED) return cis; @@ -2120,7 +2122,6 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst, return ERR_PTR(-EINVAL); } - hci_conn_hold(cis); cis->state = BT_BOUND; return cis; @@ -2374,10 +2375,13 @@ struct hci_conn *hci_bind_bis(struct hci_dev *hdev, bdaddr_t *dst, __u8 sid, parent = hci_conn_hash_lookup_big(hdev, conn->iso_qos.bcast.big); if (parent && parent != conn) { + hci_conn_hold(parent); link = hci_conn_link(parent, conn); hci_conn_drop(conn); - if (!link) + if (!link) { + hci_conn_drop(parent); return ERR_PTR(-ENOLINK); + } } return conn; @@ -2497,6 +2501,12 @@ struct hci_conn *hci_connect_cis(struct hci_dev *hdev, bdaddr_t *dst, return cis; } + /* The existing link already owns the hold on its parent. */ + if (cis->link) { + hci_conn_drop(le); + return cis; + } + link = hci_conn_link(le, cis); hci_conn_drop(cis); if (!link) { diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 070ca388f9ac..6d56c77741e1 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -164,6 +164,7 @@ static bool is_filtered_packet(struct sock *sk, struct sk_buff *skb) { struct hci_filter *flt; int flt_type, flt_event; + u8 event; /* Apply filter */ flt = &hci_pi(sk)->filter; @@ -177,7 +178,11 @@ static bool is_filtered_packet(struct sock *sk, struct sk_buff *skb) if (hci_skb_pkt_type(skb) != HCI_EVENT_PKT) return false; - flt_event = (*(__u8 *)skb->data & HCI_FLT_EVENT_BITS); + if (skb->len < 1) + return true; + + event = *(__u8 *)skb->data; + flt_event = event & HCI_FLT_EVENT_BITS; if (!hci_test_bit(flt_event, &flt->event_mask)) return true; @@ -186,11 +191,17 @@ static bool is_filtered_packet(struct sock *sk, struct sk_buff *skb) if (!flt->opcode) return false; - if (flt_event == HCI_EV_CMD_COMPLETE && + if (event == HCI_EV_CMD_COMPLETE && skb->len < 5) + return true; + + if (event == HCI_EV_CMD_COMPLETE && flt->opcode != get_unaligned((__le16 *)(skb->data + 3))) return true; - if (flt_event == HCI_EV_CMD_STATUS && + if (event == HCI_EV_CMD_STATUS && skb->len < 6) + return true; + + if (event == HCI_EV_CMD_STATUS && flt->opcode != get_unaligned((__le16 *)(skb->data + 4))) return true; @@ -1881,7 +1892,8 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, u16 ocf = hci_opcode_ocf(opcode); if (((ogf > HCI_SFLT_MAX_OGF) || - !hci_test_bit(ocf & HCI_FLT_OCF_BITS, + (ocf > HCI_FLT_OCF_BITS) || + !hci_test_bit(ocf, &hci_sec_filter.ocf_mask[ogf])) && !capable(CAP_NET_RAW)) { err = -EPERM; diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index eb99653f33f9..7657c2a0abbf 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -496,6 +496,7 @@ static int iso_connect_cis(struct sock *sk) struct hci_dev *hdev; bdaddr_t src, dst; u8 src_type; + bool already_attached; int err; lock_sock(sk); @@ -568,8 +569,14 @@ static int iso_connect_cis(struct sock *sk) goto unlock; } + iso_conn_lock(conn); + already_attached = iso_pi(sk)->conn == conn && conn->sk == sk; + iso_conn_unlock(conn); + err = iso_chan_add(conn, sk, NULL); iso_conn_put(conn); + if (already_attached || err == -EBUSY) + hci_conn_drop(hcon); if (err) goto unlock; diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 644e31160d55..aaa2a1cd489a 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -6702,9 +6702,17 @@ static int l2cap_stream_rx(struct l2cap_chan *chan, struct l2cap_ctrl *control, static int l2cap_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb) { struct l2cap_ctrl *control = &bt_cb(skb)->l2cap; - u16 len; + u16 len, min_len; u8 event; + min_len = test_bit(FLAG_EXT_CTRL, &chan->flags) ? + L2CAP_EXT_CTRL_SIZE : L2CAP_ENH_CTRL_SIZE; + if (chan->fcs == L2CAP_FCS_CRC16) + min_len += L2CAP_FCS_SIZE; + + if (skb->len < min_len) + goto drop; + __unpack_control(chan, skb); len = skb->len; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index ac4864e56ec7..41956cdde982 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -496,13 +496,9 @@ static int read_unconf_index_list(struct sock *sk, struct hci_dev *hdev, read_lock(&hci_dev_list_lock); - count = 0; - list_for_each_entry(d, &hci_dev_list, list) { - if (hci_dev_test_flag(d, HCI_UNCONFIGURED)) - count++; - } + count = list_count_nodes(&hci_dev_list); - rp_len = sizeof(*rp) + (2 * count); + rp_len = sizeof(*rp) + (sizeof(__le16) * count); rp = kmalloc(rp_len, GFP_ATOMIC); if (!rp) { read_unlock(&hci_dev_list_lock); @@ -2316,6 +2312,8 @@ static void mesh_send_start_complete(struct hci_dev *hdev, void *data, int err) hci_dev_clear_flag(hdev, HCI_MESH_SENDING); /* Send Complete Error Code for handle */ mesh_send_complete(hdev, mesh_tx, false); + if (err != -ECANCELED) + mesh_next(hdev, NULL, 0); return; } @@ -2425,19 +2423,28 @@ static int send_cancel(struct hci_dev *hdev, void *data) do { mesh_tx = mgmt_mesh_next(hdev, cmd->sk); - if (mesh_tx) - mesh_send_complete(hdev, mesh_tx, false); + if (mesh_tx) { + if (!hci_cmd_sync_dequeue(hdev, mesh_send_sync, + mesh_tx, NULL)) + mesh_send_complete(hdev, mesh_tx, false); + } } while (mesh_tx); } else { mesh_tx = mgmt_mesh_find(hdev, cancel->handle); - if (mesh_tx && mesh_tx->sk == cmd->sk) - mesh_send_complete(hdev, mesh_tx, false); + if (mesh_tx && mesh_tx->sk == cmd->sk) { + if (!hci_cmd_sync_dequeue(hdev, mesh_send_sync, + mesh_tx, NULL)) + mesh_send_complete(hdev, mesh_tx, false); + } } mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_MESH_SEND_CANCEL, 0, NULL, 0); + if (!hci_dev_test_flag(hdev, HCI_MESH_SENDING)) + mesh_next(hdev, NULL, 0); + return 0; } diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index f7463f092283..d91e2a6ee26c 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1817,7 +1817,8 @@ static struct rfcomm_session *rfcomm_recv_frame(struct rfcomm_session *s, return s; } - if (skb->len < sizeof(*hdr) + 1) { + if (skb->len < sizeof(*hdr) + 1 || + (!__test_ea(hdr->len) && skb->len < sizeof(*hdr) + 2)) { kfree_skb(skb); return s; } diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index e2486bc11cbc..fb924d0e34ec 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -786,8 +786,10 @@ static int rfcomm_sock_getsockopt_old(struct socket *sock, int optname, break; case RFCOMM_CONNINFO: - if (sk->sk_state != BT_CONNECTED && - !rfcomm_pi(sk)->dlc->defer_setup) { + if ((sk->sk_state != BT_CONNECTED && + !(sk->sk_state == BT_CONNECT2 && + rfcomm_pi(sk)->dlc->defer_setup)) || + !rfcomm_pi(sk)->dlc->session) { err = -ENOTCONN; break; } diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index 6091c47cb002..d23f9d0729c4 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -2269,6 +2269,23 @@ static u8 smp_cmd_security_req(struct l2cap_conn *conn, struct sk_buff *skb) bt_dev_dbg(hdev, "conn %p", conn); + /* SMP over BR/EDR only covers cross-transport key derivation; the + * Security Request procedure has no BR/EDR counterpart. Reject it + * here, otherwise smp_ltk_encrypt() finds the peer's LE LTK + * (ADDR_LE_DEV_PUBLIC and BDADDR_BREDR are both 0) and issues + * HCI_OP_LE_START_ENC on the ACL handle, which the controller + * rejects and hci_cs_le_start_enc() turns into a disconnect. Reply + * without smp_failure(): this is not an authentication failure, and + * MGMT_EV_AUTH_FAILED would make bluetoothd drop the device. + */ + if (hcon->type != LE_LINK) { + u8 reason = SMP_CMD_NOTSUPP; + + smp_send_cmd(conn, SMP_CMD_PAIRING_FAIL, sizeof(reason), + &reason); + return 0; + } + if (skb->len < sizeof(*rp)) return SMP_INVALID_PARAMS; diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 5d51f6cb7d15..513354e928cb 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -205,8 +205,8 @@ static void xdp_test_run_teardown(struct xdp_test_data *xdp) { xdp_unreg_mem_model(&xdp->mem); page_pool_destroy(xdp->pp); - kfree(xdp->frames); - kfree(xdp->skbs); + kvfree(xdp->frames); + kvfree(xdp->skbs); } static bool frame_was_changed(const struct xdp_page_head *head) diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c index e0c7020b12f5..a01bd280c722 100644 --- a/net/bridge/br_mdb.c +++ b/net/bridge/br_mdb.c @@ -1523,6 +1523,8 @@ static void br_mdb_flush_pgs(struct net_bridge *br, } br_multicast_del_pg(mp, p, pp); + /* br_multicast_del_pg() can remove other groups from this list. */ + pp = &mp->ports; } } diff --git a/net/bridge/br_stp_bpdu.c b/net/bridge/br_stp_bpdu.c index 74ec42ba1e7d..21d092f5acbb 100644 --- a/net/bridge/br_stp_bpdu.c +++ b/net/bridge/br_stp_bpdu.c @@ -52,7 +52,10 @@ static void br_send_bpdu(struct net_bridge_port *p, LLC_SAP_BSPAN, LLC_PDU_CMD); llc_pdu_init_as_ui_cmd(skb); - llc_mac_hdr_init(skb, p->dev->dev_addr, p->br->group_addr); + if (llc_mac_hdr_init(skb, p->dev->dev_addr, p->br->group_addr)) { + kfree_skb(skb); + return; + } skb_reset_mac_header(skb); diff --git a/net/core/dev.c b/net/core/dev.c index c67900354fa6..f660fccfc0db 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -2901,7 +2901,7 @@ int __netif_set_xps_queue(struct net_device *dev, const unsigned long *mask, dev = netdev_get_tx_queue(dev, index)->sb_dev ? : dev; tc = netdev_txq_to_tc(dev, index); - if (tc < 0) + if (tc < 0 || tc >= num_tc) return -EINVAL; } @@ -5376,7 +5376,8 @@ void kick_defer_list_purge(unsigned int cpu) backlog_unlock_irq_restore(sd, flags); } else if (!cmpxchg(&sd->defer_ipi_scheduled, 0, 1)) { - smp_call_function_single_async(cpu, &sd->defer_csd); + if (smp_call_function_single_async(cpu, &sd->defer_csd)) + WRITE_ONCE(sd->defer_ipi_scheduled, 0); } } @@ -6900,25 +6901,35 @@ bool napi_complete_done(struct napi_struct *n, int work_done) } EXPORT_SYMBOL(napi_complete_done); -static void skb_defer_free_flush(void) +static void __skb_defer_free_flush(struct skb_defer_node *sdn, int budget) { struct llist_node *free_list; struct sk_buff *skb, *next; + + if (llist_empty(&sdn->defer_list)) + return; + atomic_long_set(&sdn->defer_count, 0); + free_list = llist_del_all(&sdn->defer_list); + + llist_for_each_entry_safe(skb, next, free_list, ll_node) { + prefetch(next); + napi_consume_skb(skb, budget); + } +} + +void skb_defer_node_flush(struct skb_defer_node *sdn) +{ + __skb_defer_free_flush(sdn, 0); +} + +static void skb_defer_free_flush(void) +{ struct skb_defer_node *sdn; int node; for_each_node(node) { sdn = this_cpu_ptr(net_hotdata.skb_defer_nodes) + node; - - if (llist_empty(&sdn->defer_list)) - continue; - atomic_long_set(&sdn->defer_count, 0); - free_list = llist_del_all(&sdn->defer_list); - - llist_for_each_entry_safe(skb, next, free_list, ll_node) { - prefetch(next); - napi_consume_skb(skb, 1); - } + __skb_defer_free_flush(sdn, 1); } } @@ -12897,6 +12908,7 @@ static int dev_cpu_dead(unsigned int oldcpu) struct sk_buff **list_skb; struct sk_buff *skb; unsigned int cpu; + int node; struct softnet_data *sd, *oldsd, *remsd = NULL; local_irq_disable(); @@ -12957,6 +12969,17 @@ static int dev_cpu_dead(unsigned int oldcpu) rps_input_queue_head_incr(oldsd); } + for_each_node(node) + skb_defer_node_flush(per_cpu_ptr(net_hotdata.skb_defer_nodes, + oldcpu) + node); + node = cpu_to_node(oldcpu); + if (node_possible(node) && + !cpumask_intersects(cpumask_of_node(node), cpu_online_mask)) { + for_each_possible_cpu(cpu) + skb_defer_node_flush(per_cpu_ptr(net_hotdata.skb_defer_nodes, + cpu) + node); + } + return 0; } diff --git a/net/core/dev.h b/net/core/dev.h index b757faead4d1..04fb0e9a571e 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -399,6 +399,8 @@ static inline void napi_assert_will_not_race(const struct napi_struct *napi) WARN_ON(READ_ONCE(napi->list_owner) != -1); } +struct skb_defer_node; +void skb_defer_node_flush(struct skb_defer_node *sdn); void kick_defer_list_purge(unsigned int cpu); int dev_set_hwtstamp_phylib(struct net_device *dev, diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c index a320e264eaaf..164643140a52 100644 --- a/net/core/dev_ioctl.c +++ b/net/core/dev_ioctl.c @@ -276,19 +276,18 @@ int dev_get_hwtstamp_phylib(struct net_device *dev, if (phy_is_default_hwtstamp(dev->phydev)) return phy_hwtstamp_get(dev->phydev, cfg); + if (!dev->netdev_ops->ndo_hwtstamp_get) + return -EOPNOTSUPP; + return dev->netdev_ops->ndo_hwtstamp_get(dev, cfg); } static int dev_get_hwtstamp(struct net_device *dev, struct ifreq *ifr) { - const struct net_device_ops *ops = dev->netdev_ops; struct kernel_hwtstamp_config kernel_cfg = {}; struct hwtstamp_config cfg; int err; - if (!ops->ndo_hwtstamp_get) - return -EOPNOTSUPP; - if (!netif_device_present(dev)) return -ENODEV; @@ -359,12 +358,18 @@ int dev_set_hwtstamp_phylib(struct net_device *dev, cfg->source = phy_ts ? HWTSTAMP_SOURCE_PHYLIB : HWTSTAMP_SOURCE_NETDEV; if (phy_ts && dev->see_all_hwtstamp_requests) { + if (!ops->ndo_hwtstamp_get) + return -EOPNOTSUPP; + err = ops->ndo_hwtstamp_get(dev, &old_cfg); if (err) return err; } if (!phy_ts || dev->see_all_hwtstamp_requests) { + if (!ops->ndo_hwtstamp_set) + return -EOPNOTSUPP; + err = ops->ndo_hwtstamp_set(dev, cfg, extack); if (err) { if (extack->_msg) @@ -390,7 +395,6 @@ int dev_set_hwtstamp_phylib(struct net_device *dev, static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr) { - const struct net_device_ops *ops = dev->netdev_ops; struct kernel_hwtstamp_config kernel_cfg = {}; struct netlink_ext_ack extack = {}; struct hwtstamp_config cfg; @@ -413,9 +417,6 @@ static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr) return err; } - if (!ops->ndo_hwtstamp_set) - return -EOPNOTSUPP; - if (!netif_device_present(dev)) return -ENODEV; @@ -441,15 +442,11 @@ static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr) int generic_hwtstamp_get_lower(struct net_device *dev, struct kernel_hwtstamp_config *kernel_cfg) { - const struct net_device_ops *ops = dev->netdev_ops; int err; if (!netif_device_present(dev)) return -ENODEV; - if (!ops->ndo_hwtstamp_get) - return -EOPNOTSUPP; - netdev_lock_ops(dev); err = dev_get_hwtstamp_phylib(dev, kernel_cfg); netdev_unlock_ops(dev); @@ -462,15 +459,11 @@ int generic_hwtstamp_set_lower(struct net_device *dev, struct kernel_hwtstamp_config *kernel_cfg, struct netlink_ext_ack *extack) { - const struct net_device_ops *ops = dev->netdev_ops; int err; if (!netif_device_present(dev)) return -ENODEV; - if (!ops->ndo_hwtstamp_set) - return -EOPNOTSUPP; - netdev_lock_ops(dev); err = dev_set_hwtstamp_phylib(dev, kernel_cfg, extack); netdev_unlock_ops(dev); diff --git a/net/core/filter.c b/net/core/filter.c index 61940e753552..70dc621672f2 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -3961,12 +3961,6 @@ static u32 __bpf_skb_min_len(const struct sk_buff *skb) if (offset > 0) min_len = offset; } - if (skb->ip_summed == CHECKSUM_PARTIAL) { - offset = skb_checksum_start_offset(skb) + - skb->csum_offset + sizeof(__sum16); - if (offset > 0) - min_len = offset; - } return min_len; } @@ -3983,6 +3977,11 @@ static int bpf_skb_grow_rcsum(struct sk_buff *skb, unsigned int new_len) static int bpf_skb_trim_rcsum(struct sk_buff *skb, unsigned int new_len) { + if (skb->ip_summed == CHECKSUM_PARTIAL && + new_len < skb_checksum_start_offset(skb) + skb->csum_offset + + sizeof(__sum16)) + skb->ip_summed = CHECKSUM_NONE; + return __skb_trim_rcsum(skb, new_len); } @@ -9045,6 +9044,8 @@ static const struct bpf_func_proto * lwt_seg6local_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { switch (func_id) { + case BPF_FUNC_skb_pull_data: + return NULL; #if IS_ENABLED(CONFIG_IPV6_SEG6_BPF) case BPF_FUNC_lwt_seg6_store_bytes: return &bpf_lwt_seg6_store_bytes_proto; @@ -10565,11 +10566,12 @@ u32 bpf_sock_convert_ctx_access(enum bpf_access_type type, target_size)); *insn++ = BPF_JMP_IMM(BPF_JNE, si->dst_reg, NO_QUEUE_MAPPING, 1); - *insn++ = BPF_MOV64_IMM(si->dst_reg, -1); + *insn++ = BPF_MOV32_IMM(si->dst_reg, -1); #else - *insn++ = BPF_MOV64_IMM(si->dst_reg, -1); - *target_size = 2; + *insn++ = BPF_MOV32_IMM(si->dst_reg, -1); #endif + *target_size = sizeof_field(struct bpf_sock, rx_queue_mapping); + break; } @@ -11105,18 +11107,7 @@ static u32 sock_ops_convert_ctx_access(enum bpf_access_type type, break; case offsetof(struct bpf_sock_ops, rtt_min): - BUILD_BUG_ON(sizeof_field(struct tcp_sock, rtt_min) != - sizeof(struct minmax)); - BUILD_BUG_ON(sizeof(struct minmax) < - sizeof(struct minmax_sample)); - - *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF( - struct bpf_sock_ops_kern, sk), - si->dst_reg, si->src_reg, - offsetof(struct bpf_sock_ops_kern, sk)); - *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg, - offsetof(struct tcp_sock, rtt_min) + - sizeof_field(struct minmax_sample, t)); + SOCK_OPS_GET_FIELD(rtt_min, rtt_min.s[0].v, struct tcp_sock); break; case offsetof(struct bpf_sock_ops, bpf_sock_ops_cb_flags): @@ -12912,8 +12903,9 @@ __bpf_kfunc_start_defs(); * @sock: Pointer to socket to be destroyed * * Return: - * On error, may return EPROTONOSUPPORT, EINVAL. - * EPROTONOSUPPORT if protocol specific destroy handler is not supported. + * On error, may return EOPNOTSUPP, or whatever the protocol specific + * destroy handler returns. + * EOPNOTSUPP if protocol specific destroy handler is not supported. * 0 otherwise */ __bpf_kfunc int bpf_sock_destroy(struct sock_common *sock) @@ -12925,8 +12917,12 @@ __bpf_kfunc int bpf_sock_destroy(struct sock_common *sock) * Supporting protocols will need to acquire sock lock in the BPF context * prior to invoking this kfunc. */ - if (!sk->sk_prot->diag_destroy || (sk->sk_protocol != IPPROTO_TCP && - sk->sk_protocol != IPPROTO_UDP)) + if (!sk->sk_prot->diag_destroy) + return -EOPNOTSUPP; + + if (sk_fullsock(sk) && + sk->sk_protocol != IPPROTO_TCP && + sk->sk_protocol != IPPROTO_UDP) return -EOPNOTSUPP; return sk->sk_prot->diag_destroy(sk, ECONNABORTED); diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index cb18db681640..33b9f4eb9565 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1168,6 +1168,12 @@ netdev_find_netmem_tx_dev(struct net_device *dev) return NULL; } +/* Note: NETDEV_CMD_BIND_TX is intentionally unprivileged (no + * GENL_ADMIN_PERM / GENL_UNS_ADMIN_PERM). Unlike bind-rx, which configures + * shared NIC RX queues, bind-tx only DMA-maps the caller's dmabuf so they can + * transmit from it on their own sockets without affecting other traffic or + * device state. + */ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info) { struct net_devmem_dmabuf_binding *binding; diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 609f2c7f4a47..c3042d822afa 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -5977,7 +5977,8 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate) err = skb_maybe_pull_tail(skb, off + sizeof(struct ipv6_opt_hdr), - MAX_IPV6_HDR_LEN); + off + + sizeof(struct ipv6_opt_hdr)); if (err < 0) goto out; @@ -5992,7 +5993,8 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate) err = skb_maybe_pull_tail(skb, off + sizeof(struct ip_auth_hdr), - MAX_IPV6_HDR_LEN); + off + + sizeof(struct ip_auth_hdr)); if (err < 0) goto out; @@ -6007,7 +6009,8 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate) err = skb_maybe_pull_tail(skb, off + sizeof(struct frag_hdr), - MAX_IPV6_HDR_LEN); + off + + sizeof(struct frag_hdr)); if (err < 0) goto out; @@ -7357,8 +7360,8 @@ void skb_attempt_defer_free(struct sk_buff *skb) struct skb_defer_node *sdn; unsigned long defer_count; unsigned int defer_max; + int cpu, my_cpu; bool kick; - int cpu; if (static_branch_unlikely(&skb_defer_disable_key)) goto nodefer; @@ -7368,7 +7371,8 @@ void skb_attempt_defer_free(struct sk_buff *skb) goto nodefer; cpu = skb->alloc_cpu; - if (cpu == raw_smp_processor_id() || + my_cpu = raw_smp_processor_id(); + if (cpu == my_cpu || WARN_ON_ONCE(cpu >= nr_cpu_ids) || !cpu_online(cpu)) { nodefer: kfree_skb_napi_cache(skb); @@ -7379,7 +7383,7 @@ nodefer: kfree_skb_napi_cache(skb); DEBUG_NET_WARN_ON_ONCE(skb->destructor); DEBUG_NET_WARN_ON_ONCE(skb_nfct(skb)); - sdn = per_cpu_ptr(net_hotdata.skb_defer_nodes, cpu) + numa_node_id(); + sdn = per_cpu_ptr(net_hotdata.skb_defer_nodes, cpu) + cpu_to_node(my_cpu); defer_max = READ_ONCE(net_hotdata.sysctl_skb_defer_max); defer_count = atomic_long_inc_return(&sdn->defer_count); @@ -7389,6 +7393,11 @@ nodefer: kfree_skb_napi_cache(skb); llist_add(&skb->ll_node, &sdn->defer_list); + if (unlikely(!cpu_online(cpu) || my_cpu != raw_smp_processor_id())) { + skb_defer_node_flush(sdn); + return; + } + /* Send an IPI every time queue reaches half capacity. */ kick = (defer_count - 1) == (defer_max >> 1); diff --git a/net/core/skmsg.c b/net/core/skmsg.c index 2521b643fa05..df385a5a961e 100644 --- a/net/core/skmsg.c +++ b/net/core/skmsg.c @@ -1000,6 +1000,10 @@ static int sk_psock_verdict_apply(struct sk_psock *psock, struct sk_buff *skb, int err = 0; u32 len, off; + if (verdict == __SK_REDIRECT && skb_bpf_ingress(skb) && + skb_bpf_redirect_fetch(skb) == psock->sk) + verdict = __SK_PASS; + switch (verdict) { case __SK_PASS: err = -EIO; diff --git a/net/core/sock_map.c b/net/core/sock_map.c index ca49bc7f8687..38df84284328 100644 --- a/net/core/sock_map.c +++ b/net/core/sock_map.c @@ -41,6 +41,7 @@ static struct bpf_map *sock_map_alloc(union bpf_attr *attr) struct bpf_stab *stab; if (attr->max_entries == 0 || + attr->max_entries > INT_MAX || attr->key_size != 4 || (attr->value_size != sizeof(u32) && attr->value_size != sizeof(u64)) || diff --git a/net/ethtool/common.h b/net/ethtool/common.h index 4e5356e26f40..ae32e7fdb563 100644 --- a/net/ethtool/common.h +++ b/net/ethtool/common.h @@ -163,6 +163,8 @@ ethtool_ioctl_needs_rtnl(const struct net_device *dev, u32 ethcmd) return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS; case ETHTOOL_GLINK: return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GLINK; + case ETHTOOL_TEST: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_TEST; } return false; } diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c index d409f606aec0..60009d92e071 100644 --- a/net/ipv4/arp.c +++ b/net/ipv4/arp.c @@ -1278,6 +1278,7 @@ int arp_ioctl(struct net *net, unsigned int cmd, void __user *arg) err = copy_from_user(&r, arg, sizeof(struct arpreq)); if (err) return -EFAULT; + r.arp_dev[IFNAMSIZ - 1] = '\0'; break; default: return -EINVAL; diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index a90be57c63be..5b6b11c943e4 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -2117,9 +2117,10 @@ static int inet_validate_link_af(const struct net_device *dev, return err; if (tb[IFLA_INET_CONF]) { - err = nla_parse_nested(nested_tb, IPV4_DEVCONF_MAX, - tb[IFLA_INET_CONF], inet_devconf_policy, - extack); + err = nla_parse(nested_tb, IPV4_DEVCONF_MAX, + nla_data(tb[IFLA_INET_CONF]), + nla_len(tb[IFLA_INET_CONF]), + inet_devconf_policy, extack); if (err < 0) return err; diff --git a/net/ipv4/fib_semantics.c b/net/ipv4/fib_semantics.c index 7a362f2e2c2b..5c9021ea3a79 100644 --- a/net/ipv4/fib_semantics.c +++ b/net/ipv4/fib_semantics.c @@ -2176,6 +2176,15 @@ static bool fib_good_nh(const struct fib_nh *nh) return !!(state & NUD_VALID); } +static __be32 fib_nh_saddr(struct net *net, const struct fib_info *fi, + struct fib_nh *nh, int genid) +{ + if (READ_ONCE(nh->nh_saddr_genid) == genid) + return READ_ONCE(nh->nh_saddr); + + return fib_info_update_nhc_saddr(net, &nh->nh_common, fi->fib_scope); +} + void fib_select_multipath(struct fib_result *res, int hash, const struct flowi4 *fl4) { @@ -2184,6 +2193,7 @@ void fib_select_multipath(struct fib_result *res, int hash, bool use_neigh; int score = -1; __be32 saddr; + int genid; if (unlikely(res->fi->nh)) { nexthop_path_fib_result(res, hash); @@ -2192,6 +2202,7 @@ void fib_select_multipath(struct fib_result *res, int hash, use_neigh = READ_ONCE(net->ipv4.sysctl_fib_multipath_use_neigh); saddr = fl4 ? fl4->saddr : 0; + genid = saddr ? atomic_read(&net->ipv4.dev_addr_genid) : 0; change_nexthops(fi) { int nh_upper_bound, nh_score = 0; @@ -2204,7 +2215,7 @@ void fib_select_multipath(struct fib_result *res, int hash, (use_neigh && !fib_good_nh(nexthop_nh))) continue; - if (saddr && nexthop_nh->nh_saddr == saddr) + if (saddr && fib_nh_saddr(net, fi, nexthop_nh, genid) == saddr) nh_score += 2; if (hash <= nh_upper_bound) nh_score++; diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index 5e867f1b5c1d..076fdca44f51 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -600,6 +600,10 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, /* Initial for fou type */ switch (cfg->type) { case FOU_ENCAP_DIRECT: + if (!cfg->protocol) { + err = -EINVAL; + goto error; + } tunnel_cfg.encap_rcv = fou_udp_recv; tunnel_cfg.gro_receive = fou_gro_receive; tunnel_cfg.gro_complete = fou_gro_complete; diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 6257459bcee2..6a30f1138454 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -1520,7 +1520,8 @@ skip_child_forget: local_bh_enable(); sock_put(child); - cond_resched(); + if (!has_current_bpf_ctx()) + cond_resched(); } if (queue->fastopenq.rskq_rst_head) { /* Free all the reqs queued in rskq_rst_head. */ diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 82309efd417e..e4878e9aa636 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -1464,6 +1464,12 @@ static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[], if (!rtnl_dev_link_net_capable(dev, t->net)) return -EPERM; + if (data && data[IFLA_GRE_COLLECT_METADATA] && !t->collect_md) { + NL_SET_ERR_MSG(extack, + "Enabling collect_md on an existing device is not supported"); + return -EOPNOTSUPP; + } + err = ipgre_newlink_encap_setup(dev, data); if (err) return err; @@ -1496,6 +1502,12 @@ static int erspan_changelink(struct net_device *dev, struct nlattr *tb[], if (!rtnl_dev_link_net_capable(dev, t->net)) return -EPERM; + if (data && data[IFLA_GRE_COLLECT_METADATA] && !t->collect_md) { + NL_SET_ERR_MSG(extack, + "Enabling collect_md on an existing device is not supported"); + return -EOPNOTSUPP; + } + err = ipgre_newlink_encap_setup(dev, data); if (err) return err; diff --git a/net/ipv4/ipconfig.c b/net/ipv4/ipconfig.c index 155db067eaec..1b8585404a41 100644 --- a/net/ipv4/ipconfig.c +++ b/net/ipv4/ipconfig.c @@ -676,6 +676,24 @@ static const u8 ic_bootp_cookie[4] = { 99, 130, 83, 99 }; #ifdef IPCONFIG_DHCP +static bool __init +ic_dhcp_add_option(u8 **options, const u8 *end, u8 type, const void *value, + int len) +{ + u8 *e = *options; + + /* leave room for the option header and the END marker */ + if (len > U8_MAX || end - e < len + 3) + return false; + + *e++ = type; + *e++ = len; + memcpy(e, value, len); + *options = e + len; + + return true; +} + static void __init ic_dhcp_init_options(u8 *options, struct ic_device *d) { @@ -691,6 +709,7 @@ ic_dhcp_init_options(u8 *options, struct ic_device *d) 42, /* NTP servers */ }; u8 mt = (ic_servaddr == NONE) ? DHCPDISCOVER : DHCPREQUEST; + u8 *end = options + sizeof(((struct bootp_pkt *)0)->exten); u8 *e = options; int len; @@ -721,31 +740,19 @@ ic_dhcp_init_options(u8 *options, struct ic_device *d) e += sizeof(ic_req_params); if (ic_host_name_set) { - *e++ = 12; /* host-name */ len = strlen(utsname()->nodename); - *e++ = len; - memcpy(e, utsname()->nodename, len); - e += len; + ic_dhcp_add_option(&e, end, 12, utsname()->nodename, len); } if (*vendor_class_identifier) { - pr_info("DHCP: sending class identifier \"%s\"\n", - vendor_class_identifier); - *e++ = 60; /* Class-identifier */ len = strlen(vendor_class_identifier); - *e++ = len; - memcpy(e, vendor_class_identifier, len); - e += len; + if (ic_dhcp_add_option(&e, end, 60, vendor_class_identifier, len)) + pr_info("DHCP: sending class identifier \"%s\"\n", + vendor_class_identifier); } len = strlen(dhcp_client_identifier + 1); - /* the minimum length of identifier is 2, include 1 byte type, - * and can not be larger than the length of options - */ - if (len >= 1 && len < 312 - (e - options) - 1) { - *e++ = 61; - *e++ = len + 1; - memcpy(e, dhcp_client_identifier, len + 1); - e += len + 1; - } + /* the minimum length of identifier is 2, include 1 byte type */ + if (len >= 1) + ic_dhcp_add_option(&e, end, 61, dhcp_client_identifier, len + 1); *e++ = 255; /* End of the list */ } diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index d960e3de7d50..e0c392e29de5 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -3886,6 +3886,7 @@ void tcp_send_active_reset(struct sock *sk, enum sk_rst_reason reason) */ int tcp_send_synack(struct sock *sk) { + struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *skb; skb = tcp_rtx_queue_head(sk); @@ -3903,6 +3904,8 @@ int tcp_send_synack(struct sock *sk) if (!nskb) return -ENOMEM; INIT_LIST_HEAD(&nskb->tcp_tsorted_anchor); + if (skb == tp->retransmit_skb_hint) + tp->retransmit_skb_hint = nskb; tcp_highest_sack_replace(sk, skb, nskb); tcp_rtx_queue_unlink_and_free(skb, sk); __skb_header_release(nskb); diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index bb8cfc62cb00..b090bd1f59e8 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -617,14 +617,23 @@ void udp_lib_hash4(struct sock *sk, u16 hash) struct net *net = sock_net(sk); struct udp_table *udptable; - /* Connected udp socket can re-connect to another remote address, which - * will be handled by rehash. Thus no need to redo hash4 here. + udptable = net->ipv4.udp_table; + hslot = udp_hashslot(udptable, net, udp_sk(sk)->udp_port_hash); + + /* A connected socket can re-connect to another address. rehash() + * relocates it, but only runs when the local address changes, so a + * socket bound to a specific address would stay filed under the + * previous peer's hash. Move it here. */ - if (udp_hashed4(sk)) + if (udp_hashed4(sk)) { + if (udp_sk(sk)->udp_lrpa_hash != hash) { + spin_lock_bh(&hslot->lock); + udp_rehash4(udptable, sk, hash); + spin_unlock_bh(&hslot->lock); + } return; + } - udptable = net->ipv4.udp_table; - hslot = udp_hashslot(udptable, net, udp_sk(sk)->udp_port_hash); hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash); hslot4 = udp_hashslot4(udptable, hash); udp_sk(sk)->udp_lrpa_hash = hash; @@ -2197,9 +2206,31 @@ int __udp_disconnect(struct sock *sk, int flags) } EXPORT_SYMBOL(__udp_disconnect); +/* __udp_disconnect() takes a socket out of the 4-tuple hash table only via + * ->rehash() or ->unhash(), and neither runs for a socket bound to a + * specific address and port. Remove it here, before its peer is cleared. + */ +static void udp_unhash4_on_disconnect(struct sock *sk) +{ + struct net *net = sock_net(sk); + struct udp_table *udptable; + struct udp_hslot *hslot; + + if (!udp_hashed4(sk)) + return; + + udptable = net->ipv4.udp_table; + hslot = udp_hashslot(udptable, net, udp_sk(sk)->udp_port_hash); + + spin_lock_bh(&hslot->lock); + udp_unhash4(udptable, sk); + spin_unlock_bh(&hslot->lock); +} + int udp_disconnect(struct sock *sk, int flags) { lock_sock(sk); + udp_unhash4_on_disconnect(sk); __udp_disconnect(sk, flags); release_sock(sk); return 0; @@ -3131,6 +3162,7 @@ int udp_abort(struct sock *sk, int err) sk->sk_err = err; sk_error_report(sk); + udp_unhash4_on_disconnect(sk); __udp_disconnect(sk, 0); out: diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c index 9d06d487e8b1..4a9748338cf4 100644 --- a/net/ipv6/exthdrs_core.c +++ b/net/ipv6/exthdrs_core.c @@ -278,6 +278,9 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset, hdrlen = ipv6_optlen(hp); if (!found) { + if (skb->len - start < hdrlen) + return -EBADMSG; + nexthdr = hp->nexthdr; start += hdrlen; } diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 9ea75703b38d..9ff761962b45 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1043,8 +1043,8 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn, struct fib6_table *table = rt->fib6_table; /* Flush all cached dst in exception table */ - rt6_flush_exceptions(rt); fib6_drop_pcpu_from(rt); + rt6_flush_exceptions(rt); if (rt->nh) { spin_lock(&rt->nh->lock); diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index 8ebda0b6a78b..e61cb10b50dc 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -2279,7 +2279,7 @@ static int ip6erspan_changelink(struct net_device *dev, struct nlattr *tb[], return PTR_ERR(t); ip6erspan_set_version(data, &p); - ip6gre_tunnel_unlink_md(ign, t); + ip6erspan_tunnel_unlink_md(ign, t); ip6gre_tunnel_unlink(ign, t); ip6erspan_tnl_change(t, &p, !tb[IFLA_MTU]); ip6erspan_tunnel_link_md(ign, t); diff --git a/net/ipv6/netfilter/ip6t_rpfilter.c b/net/ipv6/netfilter/ip6t_rpfilter.c index 67c87a88cde4..b5def30c3127 100644 --- a/net/ipv6/netfilter/ip6t_rpfilter.c +++ b/net/ipv6/netfilter/ip6t_rpfilter.c @@ -61,7 +61,7 @@ static bool rpfilter_lookup_reverse6(struct net *net, const struct sk_buff *skb, fl6.flowi6_oif = dev->ifindex; rt = (void *)ip6_route_lookup(net, &fl6, skb, lookup_flags); - if (rt->dst.error) + if (rt->dst.error || !rt->rt6i_idev) goto out; if (rt->rt6i_flags & (RTF_REJECT|RTF_ANYCAST)) diff --git a/net/ipv6/netfilter/ip6t_rt.c b/net/ipv6/netfilter/ip6t_rt.c index 8051425213dd..9880faf3cc7d 100644 --- a/net/ipv6/netfilter/ip6t_rt.c +++ b/net/ipv6/netfilter/ip6t_rt.c @@ -96,7 +96,8 @@ static bool rt_mt6(const struct sk_buff *skb, struct xt_action_param *par) unsigned int i = 0; for (temp = 0; - temp < (unsigned int)((hdrlen - 8) / 16); + temp < (unsigned int)((hdrlen - 8) / 16) && + i < rtinfo->addrnr; temp++) { ap = skb_header_pointer(skb, ptr @@ -112,8 +113,6 @@ static bool rt_mt6(const struct sk_buff *skb, struct xt_action_param *par) if (ipv6_addr_equal(ap, &rtinfo->addrs[i])) i++; - if (i == rtinfo->addrnr) - break; } if (i == rtinfo->addrnr) return ret; @@ -162,6 +161,12 @@ static int rt_mt6_check(const struct xt_mtchk_param *par) pr_info_ratelimited("too many addresses specified\n"); return -EINVAL; } + + if ((rtinfo->flags & IP6T_RT_FST_MASK) && !rtinfo->addrnr) { + pr_info_ratelimited("address list match requested but addrnr is 0\n"); + return -EINVAL; + } + if ((rtinfo->flags & (IP6T_RT_RES | IP6T_RT_FST_MASK)) && (!(rtinfo->flags & IP6T_RT_TYP) || (rtinfo->rt_type != 0) || diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 08bd68f1b5bb..153ce16628c1 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -139,6 +139,7 @@ void rt6_uncached_list_add(struct rt6_info *rt) { struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list); + /* Set once and never cleared: non-NULL marks an uncached route. */ rt->dst.rt_uncached_list = ul; spin_lock_bh(&ul->lock); @@ -1729,6 +1730,11 @@ static int rt6_insert_exception(struct rt6_info *nrt, spin_lock_bh(&rt6_exception_lock); + if (f6i->fib6_destroying) { + err = -ENOENT; + goto out; + } + bucket = rcu_dereference_protected(nh->rt6i_exception_bucket, lockdep_is_held(&rt6_exception_lock)); if (!bucket) { @@ -2721,8 +2727,8 @@ struct dst_entry *ip6_route_output_flags(struct net *net, rcu_read_lock(); dst = ip6_route_output_flags_noref(net, sk, fl6, flags); rt6 = dst_rt6_info(dst); - /* For dst cached in uncached_list, refcnt is already taken. */ - if (list_empty(&rt6->dst.rt_uncached) && !dst_hold_safe(dst)) { + /* For an uncached dst, refcnt is already taken. */ + if (!rt6->dst.rt_uncached_list && !dst_hold_safe(dst)) { dst = &net->ipv6.ip6_null_entry->dst; dst_hold(dst); } @@ -2831,7 +2837,7 @@ INDIRECT_CALLABLE_SCOPE struct dst_entry *ip6_dst_check(struct dst_entry *dst, from = rcu_dereference(rt->from); if (from && (rt->rt6i_flags & RTF_PCPU || - unlikely(!list_empty(&rt->dst.rt_uncached)))) + unlikely(rt->dst.rt_uncached_list))) dst_ret = rt6_dst_from_check(rt, from, cookie); else dst_ret = rt6_check(rt, from, cookie); diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c index 62a7eb779202..8c2b156c227a 100644 --- a/net/ipv6/seg6.c +++ b/net/ipv6/seg6.c @@ -138,8 +138,8 @@ out: static struct genl_family seg6_genl_family; static const struct nla_policy seg6_genl_policy[SEG6_ATTR_MAX + 1] = { - [SEG6_ATTR_DST] = { .type = NLA_BINARY, - .len = sizeof(struct in6_addr) }, + [SEG6_ATTR_DST] = + NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), [SEG6_ATTR_DSTLEN] = { .type = NLA_S32, }, [SEG6_ATTR_HMACKEYID] = { .type = NLA_U32, }, [SEG6_ATTR_SECRET] = { .type = NLA_BINARY, }, diff --git a/net/llc/llc_c_ac.c b/net/llc/llc_c_ac.c index 724ecd741d4c..1aa7fe28acdd 100644 --- a/net/llc/llc_c_ac.c +++ b/net/llc/llc_c_ac.c @@ -437,7 +437,7 @@ int llc_conn_ac_resend_i_xxx_x_set_0_or_send_rr(struct sock *sk, if (likely(!rc)) llc_conn_send_pdu(sk, nskb); else - kfree_skb(skb); + kfree_skb(nskb); } if (rc) { nr = LLC_I_GET_NR(pdu); diff --git a/net/llc/llc_s_ac.c b/net/llc/llc_s_ac.c index 98deee560373..831998211b52 100644 --- a/net/llc/llc_s_ac.c +++ b/net/llc/llc_s_ac.c @@ -121,6 +121,8 @@ int llc_sap_action_send_xid_r(struct llc_sap *sap, struct sk_buff *skb) rc = llc_mac_hdr_init(nskb, mac_sa, mac_da); if (likely(!rc)) rc = dev_queue_xmit(nskb); + else + kfree_skb(nskb); out: return rc; } @@ -170,6 +172,8 @@ int llc_sap_action_send_test_r(struct llc_sap *sap, struct sk_buff *skb) rc = llc_mac_hdr_init(nskb, mac_sa, mac_da); if (likely(!rc)) rc = dev_queue_xmit(nskb); + else + kfree_skb(nskb); out: return rc; } diff --git a/net/llc/llc_sap.c b/net/llc/llc_sap.c index 1bd446a21092..3904a1b4ba84 100644 --- a/net/llc/llc_sap.c +++ b/net/llc/llc_sap.c @@ -19,12 +19,12 @@ #include <linux/llc.h> #include <linux/slab.h> -static int llc_mac_header_len(unsigned short devtype) +static int llc_mac_header_len(struct net_device *dev) { - switch (devtype) { + switch (dev->type) { case ARPHRD_ETHER: case ARPHRD_LOOPBACK: - return sizeof(struct ethhdr); + return LL_RESERVED_SPACE(dev); } return 0; } @@ -45,7 +45,7 @@ struct sk_buff *llc_alloc_frame(struct sock *sk, struct net_device *dev, int hlen = type == LLC_PDU_TYPE_U ? 3 : 4; struct sk_buff *skb; - hlen += llc_mac_header_len(dev->type); + hlen += llc_mac_header_len(dev); skb = alloc_skb(hlen + data_size, GFP_ATOMIC); if (skb) { diff --git a/net/mctp/route.c b/net/mctp/route.c index b19c63a5691a..e7c95eeacb48 100644 --- a/net/mctp/route.c +++ b/net/mctp/route.c @@ -825,7 +825,7 @@ static struct mctp_sk_key *mctp_lookup_prealloc_tag(struct mctp_sock *msk, spin_lock_irqsave(&mns->keys_lock, flags); - hlist_for_each_entry(tmp, &mns->keys, hlist) { + hlist_for_each_entry(tmp, &msk->keys, sklist) { if (tmp->net != netid) continue; diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index ba0957798bad..fd503f0efb57 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -1960,6 +1960,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* Ensure the IP header is present in headroom */ if (!pskb_may_pull(skb, hlen_orig)) goto ignore_tunnel; + skb_set_transport_header(skb, hlen_orig); + /* Before now we may used ihl from skb frag, revalidate it after + * copying it into skb head to prevent out-of-bounds access + */ + if (ip_hdr(skb)->ihl * 4 != hlen_orig) + goto ignore_tunnel; IP_VS_DBG(12, "Sending ICMP for %pI4->%pI4: t=%u, c=%u, i=%u\n", &ip_hdr(skb)->saddr, &ip_hdr(skb)->daddr, type, code, ntohl(info)); diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index 579ada063b1b..4e5d7c701436 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3392,7 +3392,8 @@ static bool expect_iter_name(struct nf_conntrack_expect *exp, void *data) struct nf_conntrack_helper *helper; const char *name = data; - helper = rcu_dereference(exp->helper); + helper = rcu_dereference_protected(exp->helper, + lockdep_is_held(&nf_conntrack_expect_lock)); if (!helper) return false; diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c index 801a3dd9ceea..6757fd89c1f1 100644 --- a/net/netfilter/nf_flow_table_offload.c +++ b/net/netfilter/nf_flow_table_offload.c @@ -995,7 +995,6 @@ static void flow_offload_work_del(struct flow_offload_work *offload) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_ORIGINAL); if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags)) flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_REPLY); - set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); } static void flow_offload_tuple_stats(struct flow_offload_work *offload, @@ -1059,6 +1058,12 @@ static void flow_offload_work_handler(struct work_struct *work) } clear_bit(NF_FLOW_HW_PENDING, &offload->flow->flags); + if (offload->cmd == FLOW_CLS_DESTROY) { + /* Publish after the worker's last flow access. */ + smp_mb__before_atomic(); + set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags); + } + kfree(offload); } diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index c0b754a2d45b..b59628e6240c 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -6995,11 +6995,14 @@ static int nft_setelem_catchall_insert(const struct net *net, { struct nft_set_elem_catchall *catchall; u8 genmask = nft_genmask_next(net); + u64 tstamp = nft_net_tstamp(net); struct nft_set_ext *ext; list_for_each_entry(catchall, &set->catchall_list, list) { ext = nft_set_elem_ext(set, catchall->elem); - if (nft_set_elem_active(ext, genmask)) { + if (nft_set_elem_active(ext, genmask) && + !__nft_set_elem_expired(ext, tstamp) && + !nft_set_elem_is_dead(ext)) { *priv = catchall->elem; return -EEXIST; } @@ -7092,11 +7095,14 @@ static int nft_setelem_catchall_deactivate(const struct net *net, struct nft_set_elem *elem) { struct nft_set_elem_catchall *catchall; + u64 tstamp = nft_net_tstamp(net); struct nft_set_ext *ext; list_for_each_entry(catchall, &set->catchall_list, list) { ext = nft_set_elem_ext(set, catchall->elem); - if (!nft_is_active_next(net, ext)) + if (!nft_is_active_next(net, ext) || + __nft_set_elem_expired(ext, tstamp) || + nft_set_elem_is_dead(ext)) continue; kfree(elem->priv); diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c index c727668b0c5b..a3bc00280051 100644 --- a/net/netfilter/nfnetlink_queue.c +++ b/net/netfilter/nfnetlink_queue.c @@ -1593,6 +1593,7 @@ nfqnl_rcv_nl_event(struct notifier_block *this, if (event == NETLINK_URELEASE && n->protocol == NETLINK_NETFILTER) { int i; + nfnl_lock(NFNL_SUBSYS_QUEUE); /* destroy all instances for this portid */ spin_lock(&q->instances_lock); for (i = 0; i < INSTANCE_BUCKETS; i++) { @@ -1606,6 +1607,7 @@ nfqnl_rcv_nl_event(struct notifier_block *this, } } spin_unlock(&q->instances_lock); + nfnl_unlock(NFNL_SUBSYS_QUEUE); } return NOTIFY_DONE; } @@ -1925,9 +1927,9 @@ static int nfqnl_recv_config(struct sk_buff *skb, const struct nfnl_info *info, /* Lookup queue under RCU. After peer_portid check (or for new queue * in BIND case), the queue is owned by the socket sending this message. - * A socket cannot simultaneously send a message and close, so while - * processing this CONFIG message, nfqnl_rcv_nl_event() (triggered by - * socket close) cannot destroy this queue. Safe to use without RCU. + * nfqnl_rcv_nl_event() will block on the nfnl subsys mutex that is + * held by the caller, so the queue cannot be destroyed in parallel, + * even after we drop the RCU read lock. */ rcu_read_lock(); queue = instance_lookup(q, queue_num); diff --git a/net/netfilter/nft_synproxy.c b/net/netfilter/nft_synproxy.c index 9ed288c9d168..554a96a000f4 100644 --- a/net/netfilter/nft_synproxy.c +++ b/net/netfilter/nft_synproxy.c @@ -118,7 +118,8 @@ static void nft_synproxy_do_eval(const struct nft_synproxy *priv, return; } - if (nf_ip_checksum(skb, nft_hook(pkt), thoff, IPPROTO_TCP)) { + if (nf_checksum(skb, nft_hook(pkt), thoff, IPPROTO_TCP, + nft_pf(pkt))) { regs->verdict.code = NF_DROP; return; } diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index 41d37442f186..5cc1037d4917 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c @@ -1656,7 +1656,7 @@ static void *ctrl_dumppolicy_prep(struct sk_buff *skb, } static int ctrl_dumppolicy_put_op(struct sk_buff *skb, - struct netlink_callback *cb, + struct netlink_callback *cb, u32 cmd, struct genl_split_ops *doit, struct genl_split_ops *dumpit) { @@ -1677,7 +1677,7 @@ static int ctrl_dumppolicy_put_op(struct sk_buff *skb, if (!nest_pol) goto err; - nest_op = nla_nest_start(skb, doit->cmd); + nest_op = nla_nest_start(skb, cmd); if (!nest_op) goto err; @@ -1721,7 +1721,8 @@ static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb) &doit, &dumpit))) return -ENOENT; - if (ctrl_dumppolicy_put_op(skb, cb, &doit, &dumpit)) + if (ctrl_dumppolicy_put_op(skb, cb, ctx->op, + &doit, &dumpit)) return skb->len; /* done with the per-op policy index list */ @@ -1730,6 +1731,7 @@ static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb) while (ctx->dump_map) { if (ctrl_dumppolicy_put_op(skb, cb, + ctx->op_iter->cmd, &ctx->op_iter->doit, &ctx->op_iter->dumpit)) return skb->len; diff --git a/net/nfc/core.c b/net/nfc/core.c index a92a6566e6a0..f521669293f0 100644 --- a/net/nfc/core.c +++ b/net/nfc/core.c @@ -279,10 +279,10 @@ static struct nfc_target *nfc_find_target(struct nfc_dev *dev, u32 target_idx) int nfc_dep_link_up(struct nfc_dev *dev, int target_index, u8 comm_mode) { - int rc = 0; - u8 *gb; - size_t gb_len; struct nfc_target *target; + u8 gb[NFC_MAX_GT_LEN]; + size_t gb_len = 0; + int rc = 0; pr_debug("dev_name=%s comm %d\n", dev_name(&dev->dev), comm_mode); @@ -301,7 +301,7 @@ int nfc_dep_link_up(struct nfc_dev *dev, int target_index, u8 comm_mode) goto error; } - gb = nfc_llcp_general_bytes(dev, &gb_len); + nfc_get_local_general_bytes(dev, gb, sizeof(gb), &gb_len); if (gb_len > NFC_MAX_GT_LEN) { rc = -EINVAL; goto error; @@ -644,11 +644,10 @@ int nfc_set_remote_general_bytes(struct nfc_dev *dev, const u8 *gb, u8 gb_len) } EXPORT_SYMBOL(nfc_set_remote_general_bytes); -u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, size_t *gb_len) +u8 *nfc_get_local_general_bytes(struct nfc_dev *dev, u8 *out_gb, + size_t gb_max_len, size_t *gb_len) { - pr_debug("dev_name=%s\n", dev_name(&dev->dev)); - - return nfc_llcp_general_bytes(dev, gb_len); + return nfc_llcp_general_bytes(dev, out_gb, gb_max_len, gb_len); } EXPORT_SYMBOL(nfc_get_local_general_bytes); diff --git a/net/nfc/digital_dep.c b/net/nfc/digital_dep.c index 3982fa084737..968547c306a5 100644 --- a/net/nfc/digital_dep.c +++ b/net/nfc/digital_dep.c @@ -1490,14 +1490,14 @@ static int digital_tg_send_atr_res(struct nfc_digital_dev *ddev, struct digital_atr_req *atr_req) { struct digital_atr_res *atr_res; + u8 gb[NFC_MAX_GT_LEN]; struct sk_buff *skb; - u8 *gb, payload_bits; + u8 payload_bits; size_t gb_len; int rc; - gb = nfc_get_local_general_bytes(ddev->nfc_dev, &gb_len); - if (!gb) - gb_len = 0; + nfc_get_local_general_bytes(ddev->nfc_dev, gb, sizeof(gb), + &gb_len); skb = digital_skb_alloc(ddev, sizeof(struct digital_atr_res) + gb_len); if (!skb) diff --git a/net/nfc/llcp.h b/net/nfc/llcp.h index d8345ed57c95..23ae7a0112d3 100644 --- a/net/nfc/llcp.h +++ b/net/nfc/llcp.h @@ -91,6 +91,7 @@ struct nfc_llcp_local { struct hlist_head pending_sdreqs; struct timer_list sdreq_timer; struct work_struct sdreq_timeout_work; + struct work_struct release_work; u8 sdreq_next_tid; /* sockets array */ diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c index ca89fe967d6a..80a00938c869 100644 --- a/net/nfc/llcp_commands.c +++ b/net/nfc/llcp_commands.c @@ -135,7 +135,7 @@ struct nfc_llcp_sdp_tlv *nfc_llcp_build_sdreq_tlv(u8 tid, const char *uri, { struct nfc_llcp_sdp_tlv *sdreq; - pr_debug("uri: %s, len: %zu\n", uri, uri_len); + pr_debug("uri: %.*s, len: %zu\n", (int)uri_len, uri, uri_len); /* sdreq->tlv_len is u8, takes uri_len, + 3 for header, + 1 for NULL */ if (WARN_ON_ONCE(uri_len > U8_MAX - 4)) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index cac1b5487064..74bf817007cf 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -20,6 +20,8 @@ static LIST_HEAD(llcp_devices); /* Protects llcp_devices list */ static DEFINE_SPINLOCK(llcp_devices_lock); +static struct workqueue_struct *llcp_wq; + static void nfc_llcp_rx_skb(struct nfc_llcp_local *local, struct sk_buff *skb); void nfc_llcp_sock_link(struct llcp_sock_list *l, struct sock *sk) @@ -63,21 +65,33 @@ static void nfc_llcp_socket_purge(struct nfc_llcp_sock *sock) } } +static struct sock *nfc_llcp_sock_list_pop(struct llcp_sock_list *l) +{ + struct sock *sk; + + write_lock(&l->lock); + sk = sk_head(&l->head); + if (sk) { + sock_hold(sk); + sk_del_node_init(sk); + } + write_unlock(&l->lock); + + return sk; +} + static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, int err) { struct sock *sk; - struct hlist_node *tmp; struct nfc_llcp_sock *llcp_sock; skb_queue_purge(&local->tx_queue); - write_lock(&local->sockets.lock); - - sk_for_each_safe(sk, tmp, &local->sockets.head) { + while ((sk = nfc_llcp_sock_list_pop(&local->sockets))) { llcp_sock = nfc_llcp_sock(sk); - bh_lock_sock(sk); + lock_sock(sk); nfc_llcp_socket_purge(llcp_sock); @@ -91,17 +105,27 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue, accept_queue) { - accept_sk = &lsk->sk; - bh_lock_sock(accept_sk); + bool put_creation = false; - nfc_llcp_accept_unlink(accept_sk); - - if (err) - accept_sk->sk_err = err; - accept_sk->sk_state = LLCP_CLOSED; - accept_sk->sk_state_change(sk); + accept_sk = &lsk->sk; + lock_sock_nested(accept_sk, + SINGLE_DEPTH_NESTING); + + if (nfc_llcp_sock(accept_sk)->parent == sk) { + nfc_llcp_accept_unlink(accept_sk); + nfc_llcp_sock_unlink(&local->sockets, accept_sk); + + if (err) + accept_sk->sk_err = err; + accept_sk->sk_state = LLCP_CLOSED; + accept_sk->sk_state_change(accept_sk); + sock_orphan(accept_sk); + put_creation = true; + } - bh_unlock_sock(accept_sk); + release_sock(accept_sk); + if (put_creation) + sock_put(accept_sk); /* creation ref */ } } @@ -110,23 +134,18 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); - bh_unlock_sock(sk); - - sk_del_node_init(sk); + release_sock(sk); + sock_put(sk); } - write_unlock(&local->sockets.lock); - /* If we still have a device, we keep the RAW sockets alive */ if (device == true) return; - write_lock(&local->raw_sockets.lock); - - sk_for_each_safe(sk, tmp, &local->raw_sockets.head) { + while ((sk = nfc_llcp_sock_list_pop(&local->raw_sockets))) { llcp_sock = nfc_llcp_sock(sk); - bh_lock_sock(sk); + lock_sock(sk); nfc_llcp_socket_purge(llcp_sock); @@ -135,26 +154,20 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); - bh_unlock_sock(sk); - - sk_del_node_init(sk); + release_sock(sk); + sock_put(sk); } - - write_unlock(&local->raw_sockets.lock); } static struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local) { - /* Since using nfc_llcp_local may result in usage of nfc_dev, whenever - * we hold a reference to local, we also need to hold a reference to - * the device to avoid UAF. - */ - if (!nfc_get_device(local->dev->idx)) + if (!local) return NULL; - kref_get(&local->ref); + if (kref_get_unless_zero(&local->ref)) + return local; - return local; + return NULL; } static void local_cleanup(struct nfc_llcp_local *local) @@ -172,30 +185,34 @@ static void local_cleanup(struct nfc_llcp_local *local) nfc_llcp_free_sdp_tlv_list(&local->pending_sdreqs); } -static void local_release(struct kref *ref) +static void local_release_work(struct work_struct *work) { struct nfc_llcp_local *local; + struct nfc_dev *dev; - local = container_of(ref, struct nfc_llcp_local, ref); + local = container_of(work, struct nfc_llcp_local, release_work); + dev = local->dev; local_cleanup(local); kfree(local); + nfc_put_device(dev); } -int nfc_llcp_local_put(struct nfc_llcp_local *local) +static void local_release(struct kref *ref) { - struct nfc_dev *dev; - int ret; + struct nfc_llcp_local *local; - if (local == NULL) - return 0; + local = container_of(ref, struct nfc_llcp_local, ref); - dev = local->dev; + queue_work(llcp_wq, &local->release_work); +} - ret = kref_put(&local->ref, local_release); - nfc_put_device(dev); +int nfc_llcp_local_put(struct nfc_llcp_local *local) +{ + if (!local) + return 0; - return ret; + return kref_put(&local->ref, local_release); } static struct nfc_llcp_sock *nfc_llcp_sock_get(struct nfc_llcp_local *local, @@ -341,7 +358,7 @@ static int nfc_llcp_wks_sap(const char *service_name, size_t service_name_len) { int sap, num_wks; - pr_debug("%s\n", service_name); + pr_debug("%.*s\n", (int)service_name_len, service_name); if (service_name == NULL) return -EINVAL; @@ -352,7 +369,8 @@ static int nfc_llcp_wks_sap(const char *service_name, size_t service_name_len) if (wks[sap] == NULL) continue; - if (strncmp(wks[sap], service_name, service_name_len) == 0) + if (strlen(wks[sap]) == service_name_len && + !strncmp(wks[sap], service_name, service_name_len)) return sap; } @@ -635,23 +653,32 @@ out: return ret; } -u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len) +u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *out_gb, size_t gb_max_len, + size_t *general_bytes_len) { struct nfc_llcp_local *local; + if (!out_gb || !general_bytes_len) + return NULL; + local = nfc_llcp_find_local(dev); - if (local == NULL) { + if (!local) { *general_bytes_len = 0; return NULL; } nfc_llcp_build_gb(local); - *general_bytes_len = local->gb_len; + if (local->gb_len) { + *general_bytes_len = min_t(size_t, local->gb_len, gb_max_len); + memcpy(out_gb, local->gb, *general_bytes_len); + } else { + *general_bytes_len = 0; + } nfc_llcp_local_put(local); - return local->gb; + return out_gb; } int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len) @@ -1074,6 +1101,9 @@ static void nfc_llcp_recv_hdlc(struct nfc_llcp_local *local, struct sock *sk; u8 dsap, ssap, ptype, ns, nr; + if (!pskb_may_pull(skb, LLCP_HEADER_SIZE + LLCP_SEQUENCE_SIZE)) + return; + ptype = nfc_llcp_ptype(skb); dsap = nfc_llcp_dsap(skb); ssap = nfc_llcp_ssap(skb); @@ -1251,6 +1281,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, struct nfc_llcp_sock *llcp_sock; struct sock *sk; u8 dsap, ssap, reason; + bool connecting = false; dsap = nfc_llcp_dsap(skb); ssap = nfc_llcp_ssap(skb); @@ -1262,6 +1293,7 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, case LLCP_DM_NOBOUND: case LLCP_DM_REJ: llcp_sock = nfc_llcp_connecting_sock_get(local, dsap); + connecting = true; break; default: @@ -1276,10 +1308,33 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local, sk = &llcp_sock->sk; + lock_sock(sk); + + /* Check if socket was destroyed whilst waiting for the lock */ + if (!sk_hashed(sk)) { + release_sock(sk); + nfc_llcp_sock_put(llcp_sock); + return; + } + + /* + * For DM(NOBOUND)/DM(REJ) the socket is still linked on the + * connecting_sockets list. Unlink it here, under the socket lock, + * before moving it to LLCP_CLOSED: llcp_sock_release() selects the + * list to unlink from by sk_state, so leaving a connecting socket + * in the CLOSED state would make it unlink from the wrong list and + * corrupt the connecting_sockets list / desync the socket refcount. + * This mirrors nfc_llcp_recv_cc(). + */ + if (connecting) + nfc_llcp_sock_unlink(&local->connecting_sockets, sk); + sk->sk_err = ENXIO; sk->sk_state = LLCP_CLOSED; sk->sk_state_change(sk); + release_sock(sk); + nfc_llcp_sock_put(llcp_sock); } @@ -1680,6 +1735,7 @@ int nfc_llcp_register_device(struct nfc_dev *ndev) INIT_WORK(&local->rx_work, nfc_llcp_rx_work); INIT_WORK(&local->timeout_work, nfc_llcp_timeout_work); + INIT_WORK(&local->release_work, local_release_work); rwlock_init(&local->sockets.lock); rwlock_init(&local->connecting_sockets.lock); @@ -1723,10 +1779,23 @@ void nfc_llcp_unregister_device(struct nfc_dev *dev) int __init nfc_llcp_init(void) { - return nfc_llcp_sock_init(); + int ret; + + llcp_wq = alloc_workqueue("nfc_llcp_wq", WQ_UNBOUND, 0); + if (!llcp_wq) + return -ENOMEM; + + ret = nfc_llcp_sock_init(); + if (ret) { + destroy_workqueue(llcp_wq); + return ret; + } + + return 0; } void nfc_llcp_exit(void) { nfc_llcp_sock_exit(); + destroy_workqueue(llcp_wq); } diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index 5558d8a4d48b..1e5ee4bcde68 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -392,11 +392,12 @@ void nfc_llcp_accept_unlink(struct sock *sk) pr_debug("state %d\n", sk->sk_state); - list_del_init(&llcp_sock->accept_queue); - sk_acceptq_removed(llcp_sock->parent); - llcp_sock->parent = NULL; - - sock_put(sk); + if (llcp_sock->parent) { + list_del_init(&llcp_sock->accept_queue); + sk_acceptq_removed(llcp_sock->parent); + llcp_sock->parent = NULL; + sock_put(sk); + } } void nfc_llcp_accept_enqueue(struct sock *parent, struct sock *sk) @@ -423,12 +424,20 @@ struct sock *nfc_llcp_accept_dequeue(struct sock *parent, list_for_each_entry_safe(lsk, n, &llcp_parent->accept_queue, accept_queue) { + struct nfc_llcp_local *local; + sk = &lsk->sk; - lock_sock(sk); + lock_sock_nested(sk, SINGLE_DEPTH_NESTING); if (sk->sk_state == LLCP_CLOSED) { - release_sock(sk); + local = nfc_llcp_sock(sk)->local; + nfc_llcp_accept_unlink(sk); + if (local) + nfc_llcp_sock_unlink(&local->sockets, sk); + sock_orphan(sk); + release_sock(sk); + sock_put(sk); continue; } @@ -464,7 +473,7 @@ static int llcp_sock_accept(struct socket *sock, struct socket *newsock, pr_debug("parent %p\n", sk); - lock_sock_nested(sk, SINGLE_DEPTH_NESTING); + lock_sock(sk); if (sk->sk_state != LLCP_LISTEN) { ret = -EBADFD; @@ -490,7 +499,12 @@ static int llcp_sock_accept(struct socket *sock, struct socket *newsock, release_sock(sk); timeo = schedule_timeout(timeo); - lock_sock_nested(sk, SINGLE_DEPTH_NESTING); + lock_sock(sk); + + if (sk->sk_state != LLCP_LISTEN) { + ret = -EBADFD; + break; + } } __set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); @@ -629,13 +643,24 @@ static int llcp_sock_release(struct socket *sock) list_for_each_entry_safe(lsk, n, &llcp_sock->accept_queue, accept_queue) { + bool put_creation = false; + accept_sk = &lsk->sk; - lock_sock(accept_sk); + lock_sock_nested(accept_sk, SINGLE_DEPTH_NESTING); + + if (nfc_llcp_sock(accept_sk)->parent == sk) { + nfc_llcp_send_disconnect(lsk); + nfc_llcp_accept_unlink(accept_sk); + nfc_llcp_sock_unlink(&local->sockets, accept_sk); - nfc_llcp_send_disconnect(lsk); - nfc_llcp_accept_unlink(accept_sk); + accept_sk->sk_state = LLCP_CLOSED; + sock_orphan(accept_sk); + put_creation = true; + } release_sock(accept_sk); + if (put_creation) + sock_put(accept_sk); /* creation ref */ } } @@ -734,12 +759,16 @@ static int llcp_sock_connect(struct socket *sock, struct sockaddr_unsized *_addr llcp_sock->service_name_len = min_t(unsigned int, addr->service_name_len, NFC_LLCP_MAX_SERVICE_NAME); - llcp_sock->service_name = kmemdup(addr->service_name, - llcp_sock->service_name_len, - GFP_KERNEL); - if (!llcp_sock->service_name) { - ret = -ENOMEM; - goto sock_llcp_release; + if (llcp_sock->service_name_len == 0) { + llcp_sock->service_name = NULL; + } else { + llcp_sock->service_name = kmemdup(addr->service_name, + llcp_sock->service_name_len, + GFP_KERNEL); + if (!llcp_sock->service_name) { + ret = -ENOMEM; + goto sock_llcp_release; + } } nfc_llcp_sock_link(&local->connecting_sockets, sk); diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c index 5f46c4b5720f..73e3a96470ac 100644 --- a/net/nfc/nci/core.c +++ b/net/nfc/nci/core.c @@ -780,15 +780,15 @@ static int nci_set_local_general_bytes(struct nfc_dev *nfc_dev) { struct nci_dev *ndev = nfc_get_drvdata(nfc_dev); struct nci_set_config_param param; + u8 gb[NFC_MAX_GT_LEN]; int rc; - param.val = nfc_get_local_general_bytes(nfc_dev, ¶m.len); - if ((param.val == NULL) || (param.len == 0)) + nfc_get_local_general_bytes(nfc_dev, gb, sizeof(gb), + ¶m.len); + if (param.len == 0) return 0; - if (param.len > NFC_MAX_GT_LEN) - return -EINVAL; - + param.val = gb; param.id = NCI_PN_ATR_REQ_GEN_BYTES; rc = nci_request(ndev, nci_set_config_req, ¶m, diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c index 0c58824cb150..224bdfa2dd0d 100644 --- a/net/nfc/netlink.c +++ b/net/nfc/netlink.c @@ -1181,7 +1181,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) if (rc != 0) { rc = -EINVAL; - goto put_local; + goto free_list; } if (!sdp_attrs[NFC_SDP_ATTR_URI]) @@ -1200,7 +1200,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) sdreq = nfc_llcp_build_sdreq_tlv(tid, uri, uri_len); if (sdreq == NULL) { rc = -ENOMEM; - goto put_local; + goto free_list; } tlvs_len += sdreq->tlv_len; @@ -1215,6 +1215,9 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) rc = nfc_llcp_send_snl_sdreq(local, &sdreq_list, tlvs_len); +free_list: + nfc_llcp_free_sdp_tlv_list(&sdreq_list); + put_local: nfc_llcp_local_put(local); diff --git a/net/nfc/nfc.h b/net/nfc/nfc.h index 0b1e6466f4fb..82c5dfdad10e 100644 --- a/net/nfc/nfc.h +++ b/net/nfc/nfc.h @@ -49,7 +49,8 @@ void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx, int nfc_llcp_register_device(struct nfc_dev *dev); void nfc_llcp_unregister_device(struct nfc_dev *dev); int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len); -u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len); +u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *out_gb, size_t gb_max_len, + size_t *general_bytes_len); int nfc_llcp_data_received(struct nfc_dev *dev, struct sk_buff *skb); struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev); int nfc_llcp_local_put(struct nfc_llcp_local *local); diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c index 0f433688e17b..d3326edcabf7 100644 --- a/net/openvswitch/conntrack.c +++ b/net/openvswitch/conntrack.c @@ -734,6 +734,18 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, enum ip_conntrack_info ctinfo; struct nf_conn *ct; + /* If the ct entry is not confirmed and shared with some other skb, + * e.g., a cloned one, we can't just modify it with the commit as we + * must not modify the extension set. Reset. + */ + if (cached && info->commit) { + ct = nf_ct_get(skb, &ctinfo); + if (ct && !nf_ct_is_confirmed(ct) && nf_ct_shared(ct)) { + nf_reset_ct(skb); + cached = false; + } + } + if (!cached) { struct nf_hook_state state = { .hook = NF_INET_PRE_ROUTING, @@ -766,8 +778,6 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, ct = nf_ct_get(skb, &ctinfo); if (ct) { - bool add_helper = false; - /* Packets starting a new connection must be NATted before the * helper, so that the helper knows about the NAT. We enforce * this by delaying both NAT and helper calls for unconfirmed @@ -799,7 +809,6 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, GFP_ATOMIC); if (err) return err; - add_helper = true; /* helper installed, add seqadj if NAT is required */ if (info->nat && !nfct_seqadj(ct)) { @@ -808,14 +817,14 @@ static int __ovs_ct_lookup(struct net *net, struct sw_flow_key *key, } } - /* Call the helper only if: - * - nf_conntrack_in() was executed above ("!cached") or a - * helper was just attached ("add_helper") for a confirmed - * connection, or - * - When committing an unconfirmed connection. + /* Call the helper only if nf_conntrack_in() was executed + * above ("!cached"). + * + * For unconfirmed connections it will be called later during + * commit as we need to have all the other extensions allocated + * before the call. */ - if ((nf_ct_is_confirmed(ct) ? !cached || add_helper : - info->commit)) { + if (nf_ct_is_confirmed(ct) && !cached) { int err = nf_ct_helper(skb, ct, ctinfo, info->family); err = verdict_to_errno(err); @@ -1019,6 +1028,14 @@ static int ovs_ct_commit(struct net *net, struct sw_flow_key *key, return err; nf_conn_act_ct_ext_add(skb, ct, ctinfo); + + /* Call the helpers now. We couldn't do this before as + * all the extensions must be allocated before the call. + */ + err = nf_ct_helper(skb, ct, ctinfo, info->family); + err = verdict_to_errno(err); + if (err) + return err; } else if (IS_ENABLED(CONFIG_NF_CONNTRACK_LABELS) && labels_nonzero(&info->labels.mask)) { err = ovs_ct_set_labels(ct, key, &info->labels.value, diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 50cae32ae269..7c83e01526ed 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -617,7 +617,7 @@ static int prb_calc_retire_blk_tmo(struct packet_sock *po, return DEFAULT_PRB_RETIRE_TOV; div = ecmd.base.speed / 1000; - mbits = (blk_size_in_bytes * 8) / (1024 * 1024); + mbits = (u64)blk_size_in_bytes * 8 / (1024 * 1024); if (div) mbits /= div; @@ -2530,26 +2530,6 @@ drop_n_account: goto drop_n_restore; } -static void tpacket_destruct_skb(struct sk_buff *skb) -{ - struct packet_sock *po = pkt_sk(skb->sk); - - if (likely(po->tx_ring.pg_vec)) { - void *ph; - __u32 ts; - - ph = skb_zcopy_get_nouarg(skb); - - ts = __packet_set_timestamp(po, ph, skb); - __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts); - - packet_dec_pending(&po->tx_ring); - complete(&po->skb_completion); - } - - sock_wfree(skb); -} - static int __packet_snd_vnet_parse(struct virtio_net_hdr *vnet_hdr, size_t len) { if ((vnet_hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) && @@ -2589,27 +2569,56 @@ static int packet_snd_vnet_parse(struct msghdr *msg, size_t *len, return 0; } +struct tpacket_uarg { + struct ubuf_info ubuf; + struct packet_sock *po; + void *ph; +}; + +static void tpacket_ubuf_complete(struct sk_buff *skb, struct ubuf_info *uarg, + bool success) +{ + struct tpacket_uarg *tu = container_of(uarg, struct tpacket_uarg, ubuf); + struct packet_sock *po = tu->po; + void *ph = tu->ph; + __u32 ts; + + DEBUG_NET_WARN_ON_ONCE(!skb); + + if (!refcount_dec_and_test(&uarg->refcnt)) + return; + + ts = __packet_set_timestamp(po, ph, skb); + __packet_set_status(po, ph, TP_STATUS_AVAILABLE | ts); + + packet_dec_pending(&po->tx_ring); + complete(&po->skb_completion); + + kfree(tu); + sk_free(&po->sk); +} + +static const struct ubuf_info_ops tpacket_ubuf_ops = { + .complete = tpacket_ubuf_complete, +}; + static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, - void *frame, struct net_device *dev, void *data, int tp_len, + struct net_device *dev, void *data, int tp_len, __be16 proto, unsigned char *addr, int hlen, int copylen, int hard_header_len, const struct sockcm_cookie *sockc) { - union tpacket_uhdr ph; int to_write, offset, len, nr_frags, len_max; struct socket *sock = po->sk.sk_socket; struct page *page; int err; - ph.raw = frame; - skb->protocol = proto; skb->dev = dev; skb->priority = sockc->priority; skb->mark = sockc->mark; skb_set_delivery_type_by_clockid(skb, sockc->transmit_time, po->sk.sk_clockid); skb_setup_tx_timestamp(skb, sockc); - skb_zcopy_set_nouarg(skb, ph.raw); skb_reserve(skb, hlen); skb_reset_network_header(skb); @@ -2749,6 +2758,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) struct virtio_net_hdr vnet_hdr; bool has_vnet_hdr = false; struct sockcm_cookie sockc; + struct tpacket_uarg *uarg; __be16 proto; int err, reserve = 0; void *ph; @@ -2876,7 +2886,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) err = len_sum; goto out_status; } - tp_len = tpacket_fill_skb(po, skb, ph, dev, data, tp_len, proto, + tp_len = tpacket_fill_skb(po, skb, dev, data, tp_len, proto, addr, hlen, copylen, hard_header_len, &sockc); if (likely(tp_len >= 0) && @@ -2908,7 +2918,24 @@ tpacket_error: virtio_net_hdr_set_proto(skb, &vnet_hdr); } - skb->destructor = tpacket_destruct_skb; + uarg = kmalloc(sizeof(*uarg), GFP_KERNEL); + if (unlikely(!uarg)) { + if (likely(len_sum > 0)) + err = len_sum; + else + err = -ENOMEM; + goto out_status; + } + uarg->po = po; + uarg->ph = ph; + uarg->ubuf.ops = &tpacket_ubuf_ops; + uarg->ubuf.flags = SKBFL_ZEROCOPY_FRAG; + refcount_set(&uarg->ubuf.refcnt, 1); + + /* Hold a sk_wmem_alloc reference until completion */ + refcount_inc(&po->sk.sk_wmem_alloc); + skb_zcopy_init(skb, &uarg->ubuf); + __packet_set_status(po, ph, TP_STATUS_SENDING); packet_inc_pending(&po->tx_ring); @@ -4486,21 +4513,20 @@ static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order, bool tx_ring vec->len = block_nr; pg_vec = vec->pg_vec; + if (tx_ring) { + vec->deferred = kzalloc_obj(*vec->deferred, + GFP_KERNEL | __GFP_NOWARN); + if (!vec->deferred) + goto out_free_pgvec; + vec->deferred->vec = vec; + INIT_DELAYED_WORK(&vec->deferred->work, + packet_free_pg_vec_work); + } + for (i = 0; i < block_nr; i++) { pg_vec[i].buffer = alloc_one_pg_vec_page(order); if (unlikely(!pg_vec[i].buffer)) goto out_free_pgvec; - - if (tx_ring && !vec->deferred && - is_vmalloc_addr(pg_vec[i].buffer)) { - vec->deferred = kzalloc_obj(*vec->deferred, - GFP_KERNEL | __GFP_NOWARN); - if (!vec->deferred) - goto out_free_pgvec; - vec->deferred->vec = vec; - INIT_DELAYED_WORK(&vec->deferred->work, - packet_free_pg_vec_work); - } } out: diff --git a/net/rds/connection.c b/net/rds/connection.c index b6c4beb50eaf..c752a8623cfc 100644 --- a/net/rds/connection.c +++ b/net/rds/connection.c @@ -276,6 +276,12 @@ static struct rds_connection *__rds_conn_create(struct net *net, conn->c_trans = trans; + /* The transport may just have been swapped for loopback; size the + * set of paths - which is also what rds_conn_destroy() tears down + * again - by the transport the connection actually uses. + */ + npaths = (trans->t_mp_capable ? RDS_MPATH_WORKERS : 1); + init_waitqueue_head(&conn->c_hs_waitq); for (i = 0; i < npaths; i++) { __rds_conn_path_init(conn, &conn->c_path[i], diff --git a/net/rds/ib_frmr.c b/net/rds/ib_frmr.c index bd861191157b..8397aa4a17ac 100644 --- a/net/rds/ib_frmr.c +++ b/net/rds/ib_frmr.c @@ -204,19 +204,16 @@ static int rds_ib_map_frmr(struct rds_ib_device *rds_ibdev, */ rds_ib_teardown_mr(ibmr); - ibmr->sg = sg; - ibmr->sg_len = sg_len; - ibmr->sg_dma_len = 0; frmr->sg_byte_len = 0; - WARN_ON(ibmr->sg_dma_len); - ibmr->sg_dma_len = ib_dma_map_sg(dev, ibmr->sg, ibmr->sg_len, + ibmr->sg_dma_len = ib_dma_map_sg(dev, sg, sg_len, DMA_BIDIRECTIONAL); if (unlikely(!ibmr->sg_dma_len)) { pr_warn("RDS/IB: %s failed!\n", __func__); return -EBUSY; } - frmr->sg_byte_len = 0; + ibmr->sg = sg; + ibmr->sg_len = sg_len; frmr->dma_npages = 0; len = 0; @@ -264,6 +261,8 @@ out_unmap: ib_dma_unmap_sg(rds_ibdev->dev, ibmr->sg, ibmr->sg_len, DMA_BIDIRECTIONAL); ibmr->sg_dma_len = 0; + ibmr->sg = NULL; + ibmr->sg_len = 0; return ret; } diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 3f653721c45f..e45a63be397c 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -758,7 +758,7 @@ static int tcf_idr_delete_index(struct tcf_idrinfo *idrinfo, u32 index) mutex_lock(&idrinfo->lock); p = idr_find(&idrinfo->action_idr, index); - if (!p) { + if (IS_ERR_OR_NULL(p)) { mutex_unlock(&idrinfo->lock); return -ENOENT; } diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index 9080cb386c16..411e3dd92d07 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -432,11 +432,10 @@ static void tcf_ct_flow_table_add(struct tcf_ct_flow_table *ct_ft, if (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status)) return; + /* NULL if ct is dying (raced flush) or the atomic alloc failed. */ entry = flow_offload_alloc(ct); - if (!entry) { - WARN_ON_ONCE(1); + if (!entry) goto err_alloc; - } if (tcp) { ct->proto.tcp.seen[0].flags |= IP_CT_TCP_FLAG_BE_LIBERAL; @@ -980,14 +979,13 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, struct tcf_result *res) { struct net *net = dev_net(skb->dev); + bool cached, commit, clear, nat; enum ip_conntrack_info ctinfo; struct tcf_ct *c = to_ct(a); struct nf_conn *tmpl = NULL; struct nf_hook_state state; - bool cached, commit, clear; int nh_ofs, err, retval; struct tcf_ct_params *p; - bool add_helper = false; bool skb_is_ours = false; bool skip_add = false; bool defrag = false; @@ -999,6 +997,7 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, retval = p->action; commit = p->ct_action & TCA_CT_ACT_COMMIT; clear = p->ct_action & TCA_CT_ACT_CLEAR; + nat = p->ct_action & TCA_CT_ACT_NAT; tmpl = p->tmpl; tcf_lastuse_update(&c->tcf_tm); @@ -1047,6 +1046,19 @@ TC_INDIRECT_SCOPE int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, * different zone. */ cached = tcf_ct_skb_nfct_cached(net, skb, p); + + /* If the ct entry is not confirmed and shared with some other skb, + * e.g., a cloned one, we can't just modify it with a commit or nat + * as we must not modify the extension set. Reset. + */ + if (cached && (commit || nat)) { + ct = nf_ct_get(skb, &ctinfo); + if (ct && !nf_ct_is_confirmed(ct) && nf_ct_shared(ct)) { + nf_reset_ct(skb); + cached = false; + } + } + if (!cached) { if (tcf_ct_flow_table_lookup(p, skb, family)) { skip_add = true; @@ -1083,26 +1095,32 @@ do_nat: err = __nf_ct_try_assign_helper(ct, p->tmpl, GFP_ATOMIC); if (err) goto drop; - add_helper = true; - if (p->ct_action & TCA_CT_ACT_NAT && !nfct_seqadj(ct)) { + + if (nat && !nfct_seqadj(ct)) { if (!nfct_seqadj_ext_add(ct)) goto drop; } } - if (nf_ct_is_confirmed(ct) ? ((!cached && !skip_add) || add_helper) : commit) { - err = nf_ct_helper(skb, ct, ctinfo, family); - if (err != NF_ACCEPT) - goto nf_error; - } - if (commit) { tcf_ct_act_set_mark(ct, p->mark, p->mark_mask); tcf_ct_act_set_labels(ct, p->labels, p->labels_mask); if (!nf_ct_is_confirmed(ct)) nf_conn_act_ct_ext_add(skb, ct, ctinfo); + } + /* Run helpers for the connection if nf_conntrack_in() was executed + * or if we're about to commit. This has to be done after all the + * extensions are already added. + */ + if (nf_ct_is_confirmed(ct) ? (!cached && !skip_add) : commit) { + err = nf_ct_helper(skb, ct, ctinfo, family); + if (err != NF_ACCEPT) + goto nf_error; + } + + if (commit) { /* This will take care of sending queued events * even if the connection is already confirmed. */ diff --git a/net/sched/act_gate.c b/net/sched/act_gate.c index 5d228a402204..6d6d45e03c07 100644 --- a/net/sched/act_gate.c +++ b/net/sched/act_gate.c @@ -681,7 +681,35 @@ static void tcf_gate_stats_update(struct tc_action *a, u64 bytes, u64 packets, static size_t tcf_gate_get_fill_size(const struct tc_action *act) { - return nla_total_size(sizeof(struct tc_gate)); + struct tcf_gate *gact = to_gate(act); + const struct tcf_gate_params *p; + struct tcfg_gate_entry *entry; + size_t size = nla_total_size(sizeof(struct tc_gate)) /* TCA_GATE_PARMS */ + + 3 * nla_total_size_64bit(sizeof(u64)) /* TCA_GATE_BASE_TIME + * TCA_GATE_CYCLE_TIME + * TCA_GATE_CYCLE_TIME_EXT + */ + + nla_total_size(sizeof(s32)) /* TCA_GATE_CLOCKID */ + + nla_total_size(sizeof(u32)) /* TCA_GATE_FLAGS */ + + nla_total_size(sizeof(s32)) /* TCA_GATE_PRIORITY */ + + nla_total_size(0); /* TCA_GATE_ENTRY_LIST */ + /* TCA_GATE_TM is budgeted by tcf_action_shared_attrs_size() */ + + rcu_read_lock(); + p = rcu_dereference(gact->param); + if (p) { + list_for_each_entry_rcu(entry, &p->entries, list) + /* TCA_GATE_ONE_ENTRY nest and its attributes */ + size += nla_total_size(0) + + nla_total_size(sizeof(u32)) /* TCA_GATE_ENTRY_INDEX */ + + nla_total_size(0) /* TCA_GATE_ENTRY_GATE */ + + nla_total_size(sizeof(u32)) /* TCA_GATE_ENTRY_INTERVAL */ + + nla_total_size(sizeof(s32)) /* TCA_GATE_ENTRY_MAX_OCTETS */ + + nla_total_size(sizeof(s32)); /* TCA_GATE_ENTRY_IPV */ + } + rcu_read_unlock(); + + return size; } static void tcf_gate_entry_destructor(void *priv) diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index 9cea71fc1db3..2afd68983ece 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -737,6 +737,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, u8 *curr_data; u16 mtype; u16 dlen; + int ret; curr_data = ife_tlv_meta_decode(tlv_data, ifehdr_end, &mtype, &dlen, NULL); @@ -745,13 +746,19 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, return TC_ACT_SHOT; } - if (find_decode_metaid(skb, p, mtype, dlen, curr_data)) { - /* abuse overlimits to count when we receive metadata - * but dont have an ops for it + ret = find_decode_metaid(skb, p, mtype, dlen, curr_data); + if (ret < 0) { + /* abuse overlimits to count metadata we cannot + * decode: no ops for it, or the decoder rejected it */ - pr_info_ratelimited("Unknown metaid %d dlen %d\n", - mtype, dlen); qstats_cpu_overlimit_inc(ife->common.cpu_qstats); + + if (ret == -ENOENT) + pr_info_ratelimited("Unknown metaid %d dlen %d\n", + mtype, dlen); + else + pr_info_ratelimited("Failed to decode metaid %d dlen %d err %d\n", + mtype, dlen, ret); } } diff --git a/net/sched/act_meta_mark.c b/net/sched/act_meta_mark.c index ea0573cb8b2d..e2f61b22bf0f 100644 --- a/net/sched/act_meta_mark.c +++ b/net/sched/act_meta_mark.c @@ -10,6 +10,7 @@ #include <linux/string.h> #include <linux/errno.h> #include <linux/skbuff.h> +#include <linux/unaligned.h> #include <linux/rtnetlink.h> #include <linux/module.h> #include <linux/init.h> @@ -28,9 +29,10 @@ static int skbmark_encode(struct sk_buff *skb, void *skbdata, static int skbmark_decode(struct sk_buff *skb, void *data, u16 len) { - u32 ifemark = *(u32 *)data; + if (len != sizeof(u32)) + return -EINVAL; - skb->mark = ntohl(ifemark); + skb->mark = get_unaligned_be32(data); return 0; } diff --git a/net/sched/act_meta_skbprio.c b/net/sched/act_meta_skbprio.c index 2df3133ce5ad..5cdb57931eab 100644 --- a/net/sched/act_meta_skbprio.c +++ b/net/sched/act_meta_skbprio.c @@ -10,6 +10,7 @@ #include <linux/string.h> #include <linux/errno.h> #include <linux/skbuff.h> +#include <linux/unaligned.h> #include <linux/rtnetlink.h> #include <linux/module.h> #include <linux/init.h> @@ -33,9 +34,10 @@ static int skbprio_encode(struct sk_buff *skb, void *skbdata, static int skbprio_decode(struct sk_buff *skb, void *data, u16 len) { - u32 ifeprio = *(u32 *)data; + if (len != sizeof(u32)) + return -EINVAL; - skb->priority = ntohl(ifeprio); + skb->priority = get_unaligned_be32(data); return 0; } diff --git a/net/sched/act_meta_skbtcindex.c b/net/sched/act_meta_skbtcindex.c index 44547caead46..8803710c0905 100644 --- a/net/sched/act_meta_skbtcindex.c +++ b/net/sched/act_meta_skbtcindex.c @@ -10,6 +10,7 @@ #include <linux/string.h> #include <linux/errno.h> #include <linux/skbuff.h> +#include <linux/unaligned.h> #include <linux/rtnetlink.h> #include <linux/module.h> #include <linux/init.h> @@ -28,9 +29,10 @@ static int skbtcindex_encode(struct sk_buff *skb, void *skbdata, static int skbtcindex_decode(struct sk_buff *skb, void *data, u16 len) { - u16 ifetc_index = *(u16 *)data; + if (len != sizeof(u16)) + return -EINVAL; - skb->tc_index = ntohs(ifetc_index); + skb->tc_index = get_unaligned_be16(data); return 0; } diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index a3e65c8cf29e..76ce2d124079 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -1003,8 +1003,16 @@ static int u32_change(struct net *net, struct sk_buff *in_skb, return -ENOMEM; } } else { - err = idr_alloc_u32(&tp_c->handle_idr, ht, &handle, - handle, GFP_KERNEL); + /* The IDR is keyed on the mapped id, and that is + * what the destroy paths remove. Ask for it here, + * so a manual handle colliding with the + * auto-allocated id space is rejected (-ENOSPC) + * instead of aliasing a future auto id. + */ + u32 id = handle2id(handle); + + err = idr_alloc_u32(&tp_c->handle_idr, ht, &id, id, + GFP_KERNEL); if (err) { kfree(ht); return err; diff --git a/net/sched/em_text.c b/net/sched/em_text.c index 343f1aebeec2..4132f8c3c5fc 100644 --- a/net/sched/em_text.c +++ b/net/sched/em_text.c @@ -113,7 +113,7 @@ static void em_text_destroy(struct tcf_ematch *m) static int em_text_dump(struct sk_buff *skb, struct tcf_ematch *m) { struct text_match *tm = EM_TEXT_PRIV(m); - struct tcf_em_text conf; + struct tcf_em_text conf = {}; strscpy(conf.algo, tm->config->ops->name); conf.from_offset = tm->from_offset; diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c index e87f5021a199..284490fd6ca9 100644 --- a/net/sched/sch_hfsc.c +++ b/net/sched/sch_hfsc.c @@ -386,6 +386,15 @@ cftree_update(struct hfsc_class *cl) #define SM_MASK ((1ULL << SM_SHIFT) - 1) #define ISM_MASK ((1ULL << ISM_SHIFT) - 1) +/* + * Cap on the non-descending hops a classify walk may take before its + * filter chain is treated as misconfigured. A flowid binding that was + * legal at bind time can become lateral once hfsc_adjust_levels() + * raises a class level; a few such hops are legitimate, an unbounded + * run means the chain cycles. + */ +#define HFSC_CLASSIFY_MAX_DRIFT 8 + static inline u64 seg_x2y(u64 x, u64 sm) { @@ -1133,6 +1142,7 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) struct hfsc_class *head, *cl; struct tcf_result res; struct tcf_proto *tcf; + unsigned int drift; int result; if (TC_H_MAJ(skb->priority ^ sch->handle) == 0 && @@ -1142,6 +1152,7 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) *qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS; head = &q->root; + drift = HFSC_CLASSIFY_MAX_DRIFT; tcf = rcu_dereference_bh(q->root.filter_list); while (tcf && (result = tcf_classify_qdisc(skb, tcf, &res, false)) >= 0) { #ifdef CONFIG_NET_CLS_ACT @@ -1167,6 +1178,17 @@ hfsc_classify(struct sk_buff *skb, struct Qdisc *sch, int *qerr) if (cl->level == 0) return cl; /* hit leaf class */ + /* + * flowid binds skip the level check above (res.class is set + * at bind time and levels drift after), so a walk can follow + * lateral hops without descending; a bounded number of them + * is legal, more means the chain cycles. + */ + if (cl->level >= head->level && drift-- == 0) { + pr_warn_ratelimited("hfsc: classify hop budget exhausted, dropping packet\n"); + return NULL; + } + /* apply inner filter chain */ tcf = rcu_dereference_bh(cl->filter_list); head = cl; diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index 9e52afc2d980..409ce50cc0db 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -265,14 +265,11 @@ __teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res, } if (neigh_event_send(n, skb_res) == 0) { - int err; char haddr[MAX_ADDR_LEN]; neigh_ha_snapshot(haddr, n, dev); - err = dev_hard_header(skb, dev, ntohs(skb_protocol(skb, false)), - haddr, NULL, skb->len); - - if (err < 0) + if (dev_hard_header(skb, dev, ntohs(skb_protocol(skb, false)), + haddr, NULL, skb->len) < 0) err = -EINVAL; } else { err = (skb_res == NULL) ? -EAGAIN : 1; diff --git a/net/sctp/associola.c b/net/sctp/associola.c index c0512c827d0f..4521be3bd85a 100644 --- a/net/sctp/associola.c +++ b/net/sctp/associola.c @@ -1289,18 +1289,19 @@ void sctp_assoc_update_retran_path(struct sctp_association *asoc) /* Manually skip the head element. */ if (&trans->transports == &asoc->peer.transport_addr_list) continue; - if (trans->state == SCTP_UNCONFIRMED) - continue; - trans_next = sctp_trans_elect_best(trans, trans_next); - /* Active is good enough for immediate return. */ - if (trans_next->state == SCTP_ACTIVE) - break; + if (trans->state != SCTP_UNCONFIRMED) { + trans_next = sctp_trans_elect_best(trans, trans_next); + /* Active is good enough for immediate return. */ + if (trans_next->state == SCTP_ACTIVE) + break; + } /* We've reached the end, time to update path. */ if (trans == asoc->peer.retran_path) break; } - asoc->peer.retran_path = trans_next; + if (trans_next) + asoc->peer.retran_path = trans_next; pr_debug("%s: association:%p updated new path to addr:%pISpc\n", __func__, asoc, &asoc->peer.retran_path->ipaddr.sa); diff --git a/net/sctp/input.c b/net/sctp/input.c index 864741fae418..9494cfa51106 100644 --- a/net/sctp/input.c +++ b/net/sctp/input.c @@ -436,9 +436,10 @@ void sctp_icmp_proto_unreachable(struct sock *sk, if (timer_pending(&t->proto_unreach_timer)) return; else { - if (!mod_timer(&t->proto_unreach_timer, - jiffies + (HZ/20))) - sctp_transport_hold(t); + sctp_transport_hold(t); + if (mod_timer(&t->proto_unreach_timer, + jiffies + (HZ / 20))) + sctp_transport_put(t); } } else { struct net *net = sock_net(sk); diff --git a/net/sctp/sm_sideeffect.c b/net/sctp/sm_sideeffect.c index 0d99b7e8c082..35f540fb15fc 100644 --- a/net/sctp/sm_sideeffect.c +++ b/net/sctp/sm_sideeffect.c @@ -244,8 +244,9 @@ void sctp_generate_t3_rtx_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->T3_rtx_timer, jiffies + (HZ/20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->T3_rtx_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -280,8 +281,9 @@ static void sctp_generate_timeout_event(struct sctp_association *asoc, timeout_type); /* Try again later. */ - if (!mod_timer(&asoc->timers[timeout_type], jiffies + (HZ/20))) - sctp_association_hold(asoc); + sctp_association_hold(asoc); + if (mod_timer(&asoc->timers[timeout_type], jiffies + (HZ / 20))) + sctp_association_put(asoc); goto out_unlock; } @@ -378,8 +380,9 @@ void sctp_generate_heartbeat_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->hb_timer, jiffies + (HZ/20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->hb_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -388,8 +391,9 @@ void sctp_generate_heartbeat_event(struct timer_list *t) timeout = sctp_transport_timeout(transport); if (elapsed < timeout) { elapsed = timeout - elapsed; - if (!mod_timer(&transport->hb_timer, jiffies + elapsed)) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->hb_timer, jiffies + elapsed)) + sctp_transport_put(transport); goto out_unlock; } @@ -422,9 +426,10 @@ void sctp_generate_proto_unreach_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->proto_unreach_timer, - jiffies + (HZ/20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->proto_unreach_timer, + jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -458,8 +463,9 @@ void sctp_generate_reconf_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->reconf_timer, jiffies + (HZ / 20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->reconf_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } @@ -495,8 +501,9 @@ void sctp_generate_probe_event(struct timer_list *t) pr_debug("%s: sock is busy\n", __func__); /* Try again later. */ - if (!mod_timer(&transport->probe_timer, jiffies + (HZ / 20))) - sctp_transport_hold(transport); + sctp_transport_hold(transport); + if (mod_timer(&transport->probe_timer, jiffies + (HZ / 20))) + sctp_transport_put(transport); goto out_unlock; } diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index c701cff6ea67..8dc65498763c 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c @@ -2654,6 +2654,8 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale( sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply)); + sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET, SCTP_NULL()); + return SCTP_DISPOSITION_CONSUME; nomem: diff --git a/net/smc/smc_core.c b/net/smc/smc_core.c index 04aedd957543..9974149659c2 100644 --- a/net/smc/smc_core.c +++ b/net/smc/smc_core.c @@ -1849,6 +1849,7 @@ void smcr_port_err(struct smc_ib_device *smcibdev, u8 ibport) struct smc_link_group *lgr, *n; int i; + spin_lock_bh(&smc_lgr_list.lock); list_for_each_entry_safe(lgr, n, &smc_lgr_list.list, list) { if (strncmp(smcibdev->pnetid[ibport - 1], lgr->pnet_id, SMC_MAX_PNETID_LEN)) @@ -1863,6 +1864,7 @@ void smcr_port_err(struct smc_ib_device *smcibdev, u8 ibport) smcr_link_down_cond_sched(lnk); } } + spin_unlock_bh(&smc_lgr_list.lock); } static void smc_link_down_work(struct work_struct *work) diff --git a/net/smc/smc_ib.c b/net/smc/smc_ib.c index 9bb495707445..daaa8a72da90 100644 --- a/net/smc/smc_ib.c +++ b/net/smc/smc_ib.c @@ -333,6 +333,7 @@ static bool smc_ib_check_link_gid(u8 gid[SMC_GID_SIZE], bool smcrv2, static void smc_ib_gid_check(struct smc_ib_device *smcibdev, u8 ibport) { struct smc_link_group *lgr; + bool stale_gid = false; int i; spin_lock_bh(&smc_lgr_list.lock); @@ -348,11 +349,16 @@ static void smc_ib_gid_check(struct smc_ib_device *smcibdev, u8 ibport) continue; if (!smc_ib_check_link_gid(lgr->lnk[i].gid, lgr->smc_version == SMC_V2, - smcibdev, ibport)) - smcr_port_err(smcibdev, ibport); + smcibdev, ibport)) { + stale_gid = true; + goto out; + } } } +out: spin_unlock_bh(&smc_lgr_list.lock); + if (stale_gid) + smcr_port_err(smcibdev, ibport); } static int smc_ib_remember_port_attr(struct smc_ib_device *smcibdev, u8 ibport) diff --git a/net/tipc/group.c b/net/tipc/group.c index 14e6732624e2..74f6d3dac078 100644 --- a/net/tipc/group.c +++ b/net/tipc/group.c @@ -797,10 +797,10 @@ void tipc_group_proto_rcv(struct tipc_group *grp, bool *usr_wakeup, tipc_group_open(m, usr_wakeup); return; case GRP_ACK_MSG: - if (!m) + if (!m || !grp->bc_ackers) return; acked = msg_grp_bc_acked(hdr); - if (less_eq(acked, m->bc_acked)) + if (acked != grp->bc_snd_nxt || m->bc_acked == acked) return; m->bc_acked = acked; if (--grp->bc_ackers) diff --git a/net/tipc/monitor.c b/net/tipc/monitor.c index a94b9b36a700..1a438e312d66 100644 --- a/net/tipc/monitor.c +++ b/net/tipc/monitor.c @@ -632,9 +632,10 @@ static void mon_timeout(struct timer_list *t) { struct tipc_monitor *mon = timer_container_of(mon, t, timer); struct tipc_peer *self; - int best_member_cnt = dom_size(mon->peer_cnt) - 1; + int best_member_cnt; write_lock_bh(&mon->lock); + best_member_cnt = dom_size(mon->peer_cnt) - 1; self = mon->self; if (self && (best_member_cnt != self->applied)) { mon_update_local_domain(mon); diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index f840498b58af..9b71479a2b29 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -2889,6 +2889,9 @@ static int vsock_net_child_mode_string(const struct ctl_table *table, int write, net = container_of(table->data, struct net, vsock.child_ns_mode); + if (!*lenp) + return 0; + ret = __vsock_net_mode_string(table, write, buffer, lenp, ppos, vsock_net_child_mode(net), &new_mode); if (ret) diff --git a/net/xdp/xskmap.c b/net/xdp/xskmap.c index 3bff346308d0..bf00d6463c19 100644 --- a/net/xdp/xskmap.c +++ b/net/xdp/xskmap.c @@ -124,7 +124,7 @@ static int xsk_map_gen_lookup(struct bpf_map *map, struct bpf_insn *insn_buf) struct bpf_insn *insn = insn_buf; *insn++ = BPF_LDX_MEM(BPF_W, ret, index, 0); - *insn++ = BPF_JMP_IMM(BPF_JGE, ret, map->max_entries, 5); + *insn++ = BPF_JMP32_IMM(BPF_JGE, ret, map->max_entries, 5); *insn++ = BPF_ALU64_IMM(BPF_LSH, ret, ilog2(sizeof(struct xsk_sock *))); *insn++ = BPF_ALU64_IMM(BPF_ADD, mp, offsetof(struct xsk_map, xsk_map)); *insn++ = BPF_ALU64_REG(BPF_ADD, ret, mp); diff --git a/scripts/coccinelle/hid/ff_race.cocci b/scripts/coccinelle/hid/ff_race.cocci index 479f5d1e3184..e861de00cb10 100644 --- a/scripts/coccinelle/hid/ff_race.cocci +++ b/scripts/coccinelle/hid/ff_race.cocci @@ -7,18 +7,19 @@ virtual report -@r@ +@r exists@ identifier probe_fn; -expression hdev, flags; +identifier hdev; +expression flags; position p1, p2; @@ probe_fn(struct hid_device *hdev, ...) { - <... + ... when any hid_hw_start@p1(hdev, flags) ... - \(input_ff_create\|input_ff_create_memless\)@p2(...) - ...> + \(input_ff_create@p2\|input_ff_create_memless@p2\)(...) + ... when any } @script:python depends on report@ diff --git a/security/ipe/eval.c b/security/ipe/eval.c index 21439c5be336..4a7be96c84f0 100644 --- a/security/ipe/eval.c +++ b/security/ipe/eval.c @@ -134,10 +134,14 @@ static bool evaluate_boot_verified(const struct ipe_eval_ctx *const ctx) static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, struct ipe_prop *p) { - return !!ctx->ipe_bdev && - !!ctx->ipe_bdev->root_hash && - ipe_digest_eval(p->value, - ctx->ipe_bdev->root_hash); + const struct digest_info *root_hash; + + if (!ctx->ipe_bdev) + return false; + + root_hash = rcu_dereference(ctx->ipe_bdev->root_hash); + + return root_hash && ipe_digest_eval(p->value, root_hash); } #else static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, diff --git a/security/ipe/eval.h b/security/ipe/eval.h index fef65a36468c..b26d1147d070 100644 --- a/security/ipe/eval.h +++ b/security/ipe/eval.h @@ -27,7 +27,7 @@ struct ipe_bdev { #ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE bool dm_verity_signed; #endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */ - struct digest_info *root_hash; + struct digest_info __rcu *root_hash; }; #endif /* CONFIG_IPE_PROP_DM_VERITY */ diff --git a/security/ipe/fs.c b/security/ipe/fs.c index 076c111c85c8..847a76afb93d 100644 --- a/security/ipe/fs.c +++ b/security/ipe/fs.c @@ -159,18 +159,16 @@ static ssize_t new_policy(struct file *f, const char __user *data, } rc = ipe_new_policyfs_node(p); - if (rc) - goto out; out: kfree(copy); if (rc < 0) { ipe_free_policy(p); ipe_audit_policy_load(ERR_PTR(rc)); - } else { - ipe_audit_policy_load(p); + return rc; } - return (rc < 0) ? rc : len; + + return len; } static const struct file_operations np_fops = { diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c index 0ae54a880405..d878b52ceec8 100644 --- a/security/ipe/hooks.c +++ b/security/ipe/hooks.c @@ -9,6 +9,7 @@ #include <linux/binfmts.h> #include <linux/mman.h> #include <linux/blk_types.h> +#include <linux/rcupdate.h> #include "ipe.h" #include "hooks.h" @@ -232,7 +233,20 @@ void ipe_bdev_free_security(struct block_device *bdev) { struct ipe_bdev *blob = ipe_bdev(bdev); - ipe_digest_free(blob->root_hash); + ipe_digest_free(rcu_access_pointer(blob->root_hash)); +} + +static void ipe_set_dmverity_roothash(struct ipe_bdev *blob, + struct digest_info *info) +{ + struct digest_info *old; + + /* Protected by device-mapper's md->suspend_lock */ + old = rcu_replace_pointer(blob->root_hash, info, true); + if (old) { + synchronize_rcu(); + ipe_digest_free(old); + } } #ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE @@ -280,8 +294,7 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ return -EINVAL; if (!value) { - ipe_digest_free(blob->root_hash); - blob->root_hash = NULL; + ipe_set_dmverity_roothash(blob, NULL); return 0; } @@ -301,8 +314,7 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ info->digest_len = digest->digest_len; - ipe_digest_free(blob->root_hash); - blob->root_hash = info; + ipe_set_dmverity_roothash(blob, info); return 0; err: diff --git a/security/ipe/policy_fs.c b/security/ipe/policy_fs.c index 9d92d8a14b13..a7aeb57483c6 100644 --- a/security/ipe/policy_fs.c +++ b/security/ipe/policy_fs.c @@ -481,6 +481,9 @@ int ipe_new_policyfs_node(struct ipe_policy *p) inode_lock(root); p->policyfs = policyfs; root->i_private = p; + /* Only audit signed policies from userspace */ + if (p->pkcs7) + ipe_audit_policy_load(p); inode_unlock(root); return 0; diff --git a/security/landlock/access.h b/security/landlock/access.h index bbbb41f41147..f843835851d0 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -61,6 +61,10 @@ union access_masks_all { static_assert(sizeof(typeof_member(union access_masks_all, masks)) == sizeof(typeof_member(union access_masks_all, all))); +#define _LANDLOCK_LAYER_MASK_PADDING \ + (BITS_PER_TYPE(access_mask_t) - LANDLOCK_NUM_ACCESS_MAX - \ + IS_ENABLED(CONFIG_SECURITY_LANDLOCK_LOG)) + /** * struct layer_mask - The access rights and rule flags for a layer. * @@ -81,6 +85,10 @@ struct layer_mask { */ access_mask_t quiet : 1; #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + /** + * @__pad: Padding for the compiler's bitfield initialization. + */ + access_mask_t __pad : _LANDLOCK_LAYER_MASK_PADDING; } __packed __aligned(sizeof(access_mask_t)); /* diff --git a/security/landlock/domain.h b/security/landlock/domain.h index 5ce2f91488d5..caa3d19d2c43 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -243,7 +243,8 @@ struct landlock_domain { * overlapping access rights. These layers are set once * and never changed for the lifetime of the domain. */ - struct access_masks handled_masks[]; + struct access_masks + handled_masks[] __counted_by(num_layers); }; }; }; diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 330a1871bf94..cab43892ec2f 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -356,14 +356,14 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, * held for BTF consistency (enforced by lockdep_assert_held in * TP_fast_assign). */ - if (!err && trace_landlock_add_rule_fs_enabled()) { + if (!err && trace_landlock_add_rule_path_beneath_enabled()) { char *buffer __free(__putname) = __getname(); const char *pathname = buffer ? resolve_path_for_trace(path, buffer) : "<no_mem>"; - trace_landlock_add_rule_fs(ruleset, access_rights, path, - pathname); + trace_landlock_add_rule_path_beneath( + ruleset, flags, access_rights, path, pathname); } mutex_unlock(&ruleset->lock); @@ -423,8 +423,8 @@ static bool unmask_layers_fs(const struct landlock_domain *const domain, ret = landlock_unmask_layers(domain, id, masks, &rule); if (rule) - trace_landlock_check_rule_fs(domain, rule, access_request, - dentry); + trace_landlock_check_rule_inode(domain, rule, access_request, + dentry); return ret; } diff --git a/security/landlock/log.h b/security/landlock/log.h index e0a6e44f3ddd..faa30e26e42a 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -15,6 +15,8 @@ struct landlock_cred_security; struct landlock_hierarchy; +struct sockaddr; +struct task_struct; enum landlock_request_type { LANDLOCK_REQUEST_PTRACE = 1, @@ -25,6 +27,31 @@ enum landlock_request_type { LANDLOCK_REQUEST_SCOPE_SIGNAL, }; +struct landlock_blockers { + access_mask_t access; + enum landlock_request_type type; +}; + +#ifdef CONFIG_TRACEPOINTS + +struct landlock_net_trace { + const struct sockaddr *address; + int addrlen; + u16 socket_family; +}; + +struct landlock_ptrace_trace { + u64 tracee_domain_id; + const struct task_struct *tracer; +}; + +struct landlock_signal_trace { + u64 target_domain_id; + int signal; +}; + +#endif /* CONFIG_TRACEPOINTS */ + /* * We should be careful to only use a variable of this type for * landlock_log_denial(). This way, the compiler can remove it entirely if @@ -52,13 +79,24 @@ struct landlock_request { deny_masks_t deny_masks; optional_access_t quiet_optional_accesses; - /* - * Other-party domain ID for a relational (scope/ptrace) denial, or 0 if - * that party is unsandboxed. An ID, not a pointer: the other task can - * replace its credential and free the domain it referenced. Trace path - * only; audit ignores it. - */ - u64 other_domain_id; +#ifdef CONFIG_TRACEPOINTS + union { + /* + * Other-party domain ID for an abstract UNIX socket scope + * denial, or 0 if that party is unsandboxed. Store an ID, not + * a pointer: the other task can replace its credential and free + * the domain it referenced. + */ + u64 other_domain_id; + + /* Synchronous context for a network denial. */ + const struct landlock_net_trace *trace_net; + /* Synchronous context for a ptrace denial. */ + const struct landlock_ptrace_trace *trace_ptrace; + /* Synchronous context for a signal denial. */ + const struct landlock_signal_trace *trace_signal; + }; +#endif /* CONFIG_TRACEPOINTS */ }; #ifdef CONFIG_SECURITY_LANDLOCK_LOG diff --git a/security/landlock/net.c b/security/landlock/net.c index 8f2aaac54b33..6fe0dbde3b78 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -47,7 +47,8 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, * TP_fast_assign). */ if (!err) - trace_landlock_add_rule_net(ruleset, access_rights, port); + trace_landlock_add_rule_net_port(ruleset, flags, access_rights, + port); mutex_unlock(&ruleset->lock); return err; @@ -63,7 +64,7 @@ static bool unmask_layers_net(const struct landlock_domain *const domain, ret = landlock_unmask_layers(domain, id, masks, &rule); if (rule) - trace_landlock_check_rule_net( + trace_landlock_check_rule_net_port( domain, rule, access_request, ntohs((__force __be16)id.key.data)); return ret; @@ -92,7 +93,7 @@ static int current_check_access_socket(struct socket *const sock, return 0; /* Checks for minimal header length to safely read sa_family. */ - if (addrlen < offsetofend(typeof(*address), sa_family)) + if (addrlen < (int)offsetofend(typeof(*address), sa_family)) return -EINVAL; /* @@ -144,6 +145,17 @@ static int current_check_access_socket(struct socket *const sock, .audit.u.net = &audit_net, .access = access_request, .layer_masks = &layer_masks, +#ifdef CONFIG_TRACEPOINTS + .trace_net = + &(struct landlock_net_trace){ + .address = + address, + .addrlen = + addrlen, + .socket_family = + sock_family, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EACCES; } @@ -275,14 +287,22 @@ static int current_check_access_socket(struct socket *const sock, audit_net.family = address->sa_family; audit_net.sk = sock->sk; - landlock_log_denial(subject, - &(struct landlock_request){ - .type = LANDLOCK_REQUEST_NET_ACCESS, - .audit.type = LSM_AUDIT_DATA_NET, - .audit.u.net = &audit_net, - .access = access_request, - .layer_masks = &layer_masks, - }); + landlock_log_denial( + subject, &(struct landlock_request){ + .type = LANDLOCK_REQUEST_NET_ACCESS, + .audit.type = LSM_AUDIT_DATA_NET, + .audit.u.net = &audit_net, + .access = access_request, + .layer_masks = &layer_masks, +#ifdef CONFIG_TRACEPOINTS + .trace_net = + &(struct landlock_net_trace){ + .address = address, + .addrlen = addrlen, + .socket_family = sock_family, + }, +#endif /* CONFIG_TRACEPOINTS */ + }); return -EACCES; } diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index b536fa0425b7..cf77f1806a95 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -171,9 +171,10 @@ struct landlock_ruleset { * @version: Counter incremented on each successful * landlock_add_rule(2), including when it only extends an existing * rule's access rights. Used by tracepoints to correlate a domain with - * the exact ruleset state it was created from. Protected by @lock. + * the exact successful rule history it was created from. Protected by + * @lock. */ - u32 version; + u64 version; /** * @id: Unique identifier for this ruleset, used for tracing. */ diff --git a/security/landlock/task.c b/security/landlock/task.c index 4491ce31ae04..d9eae86fc552 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -88,7 +88,9 @@ static int hook_ptrace_access_check(struct task_struct *const child, const unsigned int mode) { const struct landlock_cred_security *parent_subject; +#ifdef CONFIG_TRACEPOINTS u64 tracee_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ int err; /* Quick return for non-landlocked tasks. */ @@ -100,10 +102,10 @@ static int hook_ptrace_access_check(struct task_struct *const child, const struct landlock_domain *const child_dom = landlock_get_task_domain(child); err = domain_ptrace(parent_subject->domain, child_dom); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (child_dom) tracee_domain_id = child_dom->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!err) @@ -121,7 +123,12 @@ static int hook_ptrace_access_check(struct task_struct *const child, .u.tsk = child, }, .layer_plus_one = parent_subject->domain->num_layers, - .other_domain_id = tracee_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_ptrace = &(struct landlock_ptrace_trace) { + .tracee_domain_id = tracee_domain_id, + .tracer = current, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return err; @@ -142,7 +149,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent) { const struct landlock_cred_security *parent_subject; const struct landlock_domain *child_dom; - u64 tracee_domain_id = 0; int err; child_dom = landlock_get_current_domain(); @@ -154,12 +160,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent) if (!err) return 0; -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - /* The tracee is the current task; its domain is stable here. */ - if (child_dom) - tracee_domain_id = child_dom->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - /* * For the ptrace_traceme case, we log the domain which is the cause of * the denial, which means the parent domain instead of the current @@ -174,7 +174,13 @@ static int hook_ptrace_traceme(struct task_struct *const parent) .u.tsk = current, }, .layer_plus_one = parent_subject->domain->num_layers, - .other_domain_id = tracee_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_ptrace = &(struct landlock_ptrace_trace) { + /* The current task's domain is stable here. */ + .tracee_domain_id = child_dom ? child_dom->hierarchy->id : 0, + .tracer = parent, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return err; } @@ -250,8 +256,7 @@ static bool domain_is_scoped(const struct landlock_domain *const client, } static bool sock_is_scoped(struct sock *const other, - const struct landlock_domain *const domain, - u64 *const peer_domain_id) + const struct landlock_domain *const domain) { const struct landlock_domain *dom_other; @@ -269,13 +274,23 @@ static bool sock_is_scoped(struct sock *const other, return false; dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - *peer_domain_id = dom_other ? dom_other->hierarchy->id : 0; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ return domain_is_scoped(domain, dom_other, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); } +#ifdef CONFIG_TRACEPOINTS + +static u64 get_socket_domain_id(const struct sock *const other) +{ + const struct landlock_domain *domain; + + lockdep_assert_held(&unix_sk(other)->lock); + domain = landlock_cred(other->sk_socket->file->f_cred)->domain; + return domain ? domain->hierarchy->id : 0; +} + +#endif /* CONFIG_TRACEPOINTS */ + static bool is_abstract_socket(struct sock *const sock) { struct unix_address *addr = unix_sk(sock)->addr; @@ -299,7 +314,6 @@ static int hook_unix_stream_connect(struct sock *const sock, struct sock *const newsk) { size_t handle_layer; - u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -311,7 +325,7 @@ static int hook_unix_stream_connect(struct sock *const sock, if (!is_abstract_socket(other)) return 0; - if (!sock_is_scoped(other, subject->domain, &peer_domain_id)) + if (!sock_is_scoped(other, subject->domain)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -323,7 +337,9 @@ static int hook_unix_stream_connect(struct sock *const sock, }, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = peer_domain_id, +#ifdef CONFIG_TRACEPOINTS + .other_domain_id = get_socket_domain_id(other), +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -332,7 +348,6 @@ static int hook_unix_may_send(struct socket *const sock, struct socket *const other) { size_t handle_layer; - u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -350,7 +365,7 @@ static int hook_unix_may_send(struct socket *const sock, if (!is_abstract_socket(other->sk)) return 0; - if (!sock_is_scoped(other->sk, subject->domain, &peer_domain_id)) + if (!sock_is_scoped(other->sk, subject->domain)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -362,7 +377,9 @@ static int hook_unix_may_send(struct socket *const sock, }, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = peer_domain_id, +#ifdef CONFIG_TRACEPOINTS + .other_domain_id = get_socket_domain_id(other->sk), +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -377,7 +394,9 @@ static int hook_task_kill(struct task_struct *const p, { bool is_scoped; size_t handle_layer; +#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ const struct landlock_cred_security *subject; if (!cred) { @@ -409,10 +428,10 @@ static int hook_task_kill(struct task_struct *const p, is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!is_scoped) @@ -425,7 +444,12 @@ static int hook_task_kill(struct task_struct *const p, .u.tsk = p, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = target_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_signal = &(struct landlock_signal_trace) { + .target_domain_id = target_domain_id, + .signal = sig, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -435,7 +459,9 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, { const struct landlock_cred_security *subject; bool is_scoped = false; +#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ /* Lock already held by send_sigio() and send_sigurg(). */ lockdep_assert_held(&fown->lock); @@ -468,10 +494,10 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!is_scoped) @@ -486,7 +512,12 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, #ifdef CONFIG_SECURITY_LANDLOCK_LOG .layer_plus_one = landlock_file(fown->file)->fown_layer + 1, #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - .other_domain_id = target_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_signal = &(struct landlock_signal_trace) { + .target_domain_id = target_domain_id, + .signal = signum ? signum : SIGIO, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 8c21e5de6f0d..225dbf37bab0 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -12,6 +12,7 @@ #include <linux/err.h> #include <linux/fs.h> #include <linux/lsm_audit.h> +#include <linux/socket.h> #include <net/sock.h> #include "access.h" @@ -61,8 +62,8 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy * * @request: Detail of the user space request. * @youngest_denied: The youngest hierarchy node that denied the access. - * @missing: The set of denied access rights. - * @same_exec: Whether the current task is the same executable that called + * @missing: The final missing access subset, when applicable. + * @same_exec: Whether the policy subject is the same executable that called * landlock_restrict_self() for the denying domain, as computed * by landlock_log_denial(). * @logged: Whether the domain's policy selects this denial for logging, as @@ -83,6 +84,10 @@ void landlock_trace_denial( case LANDLOCK_REQUEST_FS_ACCESS: case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: if (trace_landlock_deny_access_fs_enabled()) { + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; char *buf __free(__putname) = __getname(); struct path dentry_path; const char *pathname; @@ -147,29 +152,67 @@ void landlock_trace_denial( trace_landlock_deny_access_fs(youngest_denied, same_exec, logged, - missing, path, pathname); + &blockers, path, + pathname); } break; case LANDLOCK_REQUEST_NET_ACCESS: - if (trace_landlock_deny_access_net_enabled()) + if (trace_landlock_deny_access_net_enabled()) { + const struct landlock_net_trace *const trace_net = + request->trace_net; + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; + struct sockaddr_storage address = {}; + + if (WARN_ON_ONCE(!trace_net || !trace_net->address)) + return; + + if (WARN_ON_ONCE( + trace_net->addrlen < + (int)offsetofend(struct sockaddr, + sa_family) || + trace_net->addrlen > (int)sizeof(address))) + return; + + memcpy(&address, trace_net->address, + trace_net->addrlen); trace_landlock_deny_access_net( - youngest_denied, same_exec, logged, missing, + youngest_denied, same_exec, logged, &blockers, request->audit.u.net->sk, - ntohs(request->audit.u.net->sport), - ntohs(request->audit.u.net->dport)); + trace_net->socket_family, &address, + trace_net->addrlen); + } break; case LANDLOCK_REQUEST_PTRACE: - if (trace_landlock_deny_ptrace_enabled()) - trace_landlock_deny_ptrace(youngest_denied, same_exec, - logged, - request->other_domain_id, - request->audit.u.tsk); + if (trace_landlock_deny_ptrace_enabled()) { + const struct landlock_ptrace_trace *const trace_ptrace = + request->trace_ptrace; + + if (WARN_ON_ONCE(!trace_ptrace || + !trace_ptrace->tracer)) + return; + + trace_landlock_deny_ptrace( + youngest_denied, same_exec, logged, + trace_ptrace->tracee_domain_id, + request->audit.u.tsk, trace_ptrace->tracer); + } break; case LANDLOCK_REQUEST_SCOPE_SIGNAL: - if (trace_landlock_deny_scope_signal_enabled()) + if (trace_landlock_deny_scope_signal_enabled()) { + const struct landlock_signal_trace *const trace_signal = + request->trace_signal; + + if (WARN_ON_ONCE(!trace_signal)) + return; + trace_landlock_deny_scope_signal( youngest_denied, same_exec, logged, - request->other_domain_id, request->audit.u.tsk); + trace_signal->target_domain_id, + request->audit.u.tsk, trace_signal->signal); + } break; case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: if (trace_landlock_deny_scope_abstract_unix_socket_enabled()) 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/soc/amd/acp-es8336.c b/sound/soc/amd/acp-es8336.c index 9f3f11256788..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; @@ -198,7 +198,7 @@ static int st_es8336_late_probe(struct snd_soc_card *card) if (!adev) return -ENODEV; - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_err(card->dev, "can not find codec dev\n"); @@ -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..b7b7b4f4a55c 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,7 @@ 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_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/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c index 3a640e652314..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 <sound/soc-dapm.h> #include <sound/jack.h> #include <sound/soc-acpi.h> +#include <acpi/acpi_bus.h> #include <linux/clk.h> #include <linux/gpio/consumer.h> #include <linux/module.h> @@ -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, }; @@ -428,7 +434,7 @@ static int acp3x_es83xx_probe(struct snd_soc_card *card) return -ENXIO; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_warn(dev, "Error cannot find codec device, will defer probe\n"); @@ -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/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 3930cc46fa58..17e4e97fb3d1 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -23,6 +23,19 @@ #define ACP71_PCI_REV 0x71 #define ACP72_PCI_REV 0x72 +/** + * struct amd_pdm_pdata - platform data passed via mach->pdata to machine driver + * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), + * non-zero when a PDM controller was identified via ACPI _DSD + * + * Carries the PDM controller selection for ACP7.B/7.F platforms, derived + * from the acp-audio-ep-port ACPI _DSD property and passed via mach->pdata + * to the machine driver. + */ +struct amd_pdm_pdata { + unsigned int pdm_sel; +}; + #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) #define ASOC_SDW_ACP_DMIC BIT(5) 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/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 cc174ec3a1f8..0830d7d2dd7c 100644 --- a/sound/soc/codecs/adau1372.c +++ b/sound/soc/codecs/adau1372.c @@ -881,12 +881,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 <lars@metafoo.de> - * based on an inital version by Cliff Cai <cliff.cai@analog.com> + * based on an initial version by Cliff Cai <cliff.cai@analog.com> */ #include <linux/module.h> @@ -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 @@ err: 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/cpcap.c b/sound/soc/codecs/cpcap.c index fc41e1547bda..dda68165b817 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,12 @@ 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 +1651,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 +1665,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; - - 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); + if (cpcap->mb2irq < 0) { + err = cpcap->mb2irq; + goto err_free_hsirq; + } + + 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 +1693,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 +1708,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 +1757,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..57982b8ae645 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -51,6 +51,8 @@ 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_ASP_ENABLES1, 0x00000000 }, { CS35L45_ASP_CONTROL1, 0x00000028 }, { CS35L45_ASP_CONTROL2, 0x18180200 }, @@ -134,6 +136,8 @@ 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_ASP_ENABLES1: case CS35L45_ASP_CONTROL1: case CS35L45_ASP_CONTROL2: diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index a032bb23b4ac..9eef7c8b1837 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -6,6 +6,7 @@ // // Author: James Schulman <james.schulman@cirrus.com> +#include <linux/bitfield.h> #include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/pm_runtime.h> @@ -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,19 @@ 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_property_config(struct cs35l45_private *cs35l45) { struct device_node *node = cs35l45->dev->of_node; @@ -1110,10 +1194,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) { diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index 7a790d2acac7..f8a4713ae92f 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -35,6 +35,8 @@ #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_DCM_CFG 0x0000380C #define CS35L45_BOOST_OV_CFG 0x0000382C @@ -183,6 +185,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 +224,22 @@ #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) + /* ASP_ENABLES_1 */ #define CS35L45_ASP_RX2_EN_SHIFT 17 #define CS35L45_ASP_RX1_EN_SHIFT 16 @@ -320,6 +340,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 @@ -493,6 +514,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; 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 35d210626627..74129fcc79dc 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -613,12 +613,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..b5d69d5516a6 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -1122,6 +1122,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 +1136,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 +1682,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 9d6bcfbbf0b2..5f033556099a 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 4d9a22a1029c..ad45e8fdb393 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 @@ err: 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/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..c9b6172ac0a1 100644 --- a/sound/soc/codecs/da732x.c +++ b/sound/soc/codecs/da732x.c @@ -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 @@ pll_err: 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); + if (ret) + return ret; + /* reset internal clock state */ - if (reg == 0x05) - regmap_write(es8326->regmap, ES8326_CLK_CTL, ES8326_CLK_ON); - else + if (reg == 0x05) { + ret = regmap_write(es8326->regmap, ES8326_CLK_CTL, + ES8326_CLK_ON); + if (ret) + return ret; + } else { es8326_init(component); + } - regcache_sync(es8326->regmap); + 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..80707d2768c6 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); - - regcache_sync(es8375->regmap); + } else { + ret = es8375_set_bias_level(component, SND_SOC_BIAS_ON); + if (ret) + return ret; + } - return 0; + return regcache_sync(es8375->regmap); } static int es8375_codec_probe(struct snd_soc_component *component) 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 <karl@asseily.com> +// +// 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 <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +/* ------------------------------------------------- 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 <karl@asseily.com>"); +MODULE_LICENSE("GPL"); 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 @@ tag_power_down: 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..084659106d5d 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) 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/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 c8d4c68af562..b62bd98f2252 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.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..2c95ed00a681 100644 --- a/sound/soc/codecs/nau8325.c +++ b/sound/soc/codecs/nau8325.c @@ -628,10 +628,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 <ctlin0@nuvoton.com> +// Seven Lee <wtli@nuvoton.com> +// John Hsu <kchsu0@nuvoton.com> +// Neo Chang <ylchang2@nuvoton.com> + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/bitfield.h> +#include <sound/soc.h> + +#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 <ctlin0@nuvoton.com> +// Seven Lee <wtli@nuvoton.com> +// John Hsu <kchsu0@nuvoton.com> +// Neo Chang <ylchang2@nuvoton.com> + +#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 <ctlin0@nuvoton.com> +// Seven Lee <wtli@nuvoton.com> +// John Hsu <kchsu0@nuvoton.com> +// Neo Chang <ylchang2@nuvoton.com> + + +#include <linux/unaligned.h> +#include <linux/acpi.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/units.h> +#include <linux/workqueue.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#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 <ctlin0@nuvoton.com>"); +MODULE_AUTHOR("Seven Lee <wtli@nuvoton.com>"); +MODULE_AUTHOR("John Hsu <kchsu0@nuvoton.com>"); +MODULE_AUTHOR("Neo Chang <ylchang2@nuvoton.com>"); +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 <ctlin0@nuvoton.com> +// Seven Lee <wtli@nuvoton.com> +// John Hsu <kchsu0@nuvoton.com> +// Neo Chang <ylchang2@nuvoton.com> + +#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..d4211a76b096 100644 --- a/sound/soc/codecs/nau8540.c +++ b/sound/soc/codecs/nau8540.c @@ -623,12 +623,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 +908,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..ecadbaee1037 100644 --- a/sound/soc/codecs/nau8821.c +++ b/sound/soc/codecs/nau8821.c @@ -981,11 +981,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 830164e991a7..db1931d7b901 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..853eb9472af9 100644 --- a/sound/soc/codecs/nau8824.c +++ b/sound/soc/codecs/nau8824.c @@ -1580,11 +1580,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..63d927f31409 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. */ @@ -1303,7 +1303,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 +1507,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 +2897,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/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 <linux/gpio/consumer.h> #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <sound/soc.h> +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..abed31ba660c 100644 --- a/sound/soc/codecs/rk3308_codec.c +++ b/sound/soc/codecs/rk3308_codec.c @@ -679,9 +679,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 = { 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..15d21097ed0c 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -2209,7 +2209,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 +3487,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 +3501,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 +3544,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[] = { 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..389be7cbfe05 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[] = { 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.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..0f80e4c32f57 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]; 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..f2f91dd087bf 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -395,6 +395,7 @@ static void rt712_sdca_sdw_remove(struct sdw_slave *slave) 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..a98537e23c21 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 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 <linux/soundwire/sdw_type.h> #include <sound/soc.h> #include <linux/workqueue.h> +#include <linux/gpio/driver.h> 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..a8c7b42898db 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; @@ -79,6 +91,7 @@ static bool rt721_sdca_mbq_readable_register(struct device *dev, unsigned int re case 0x5810037: case 0x5810038: case 0x5810039: + case 0x5810100: case 0x5b10018: case 0x5b10019: case 0x5f00045: @@ -155,6 +168,7 @@ static bool rt721_sdca_mbq_volatile_register(struct device *dev, unsigned int re case 0x5810037: case 0x5810038: case 0x5810039: + case 0x5810100: case 0x5b10018: case 0x5b10019: case 0x6100006: @@ -436,6 +450,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.c b/sound/soc/codecs/rt721-sdca.c index 738644018fb9..ef003c93f533 100644 --- a/sound/soc/codecs/rt721-sdca.c +++ b/sound/soc/codecs/rt721-sdca.c @@ -142,151 +142,312 @@ io_error: 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); + if (rt721->wf_id == RT721_S) { + 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); + 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); - - regmap_write(rt721->regmap, 0x2f51, 0x00); - rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, - RT721_MISC_CTL, 0x0004); + unsigned int jack_func_status; + 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 & 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_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); + regmap_write(rt721->regmap, 0x2f51, 0x00); + rt_sdca_index_write(rt721->mbq_regmap, RT721_HDA_SDCA_FLOAT, + RT721_MISC_CTL, 0x0004); + /* 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 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 +853,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 +909,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 +1417,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 +1683,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,6 +1701,7 @@ 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; @@ -1540,12 +1733,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..16cf40cd88de 100644 --- a/sound/soc/codecs/rt721-sdca.h +++ b/sound/soc/codecs/rt721-sdca.h @@ -40,6 +40,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 { @@ -214,6 +216,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 +232,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 +265,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 +275,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/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c index 5a1c1e10e6d7..28b4118e05c9 100644 --- a/sound/soc/codecs/rt766-sdca.c +++ b/sound/soc/codecs/rt766-sdca.c @@ -1297,7 +1297,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/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 @@ out: 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 <linux/bitops.h> +#include <linux/delay.h> +#include <linux/find.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw_type.h> + +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/sdca.h> +#include <sound/sdca_asoc.h> +#include <sound/sdca_function.h> +#include <sound/sdca_regmap.h> +#include <sound/sdw.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#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 <linux/mutex.h> +#include <linux/soundwire/sdw.h> +#include <linux/workqueue.h> +#include <sound/sdca_function.h> +#include <sound/soc.h> + +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..2865a07be395 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 */ @@ -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..d2b6f73d5e10 100644 --- a/sound/soc/codecs/sta350.c +++ b/sound/soc/codecs/sta350.c @@ -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 @@ err: 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..dd86fc90a7d0 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 <shenghao-ding@ti.com> @@ -131,7 +131,10 @@ 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); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(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..e4c8a04a9187 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 <shenghao-ding@ti.com> @@ -34,7 +34,10 @@ 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); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(reg), ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -62,7 +65,10 @@ 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); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(reg), ret); } else dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, chn); @@ -88,7 +94,10 @@ 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); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(reg), ret); } else { ret = -EINVAL; dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, @@ -118,7 +127,10 @@ 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); + dev_err(tas_priv->dev, "%s, Bx%02xPx%02xRx%02x E=%d\n", + __func__, TASDEVICE_BOOK_ID(reg), + TASDEVICE_PAGE_ID(reg), + TASDEVICE_PAGE_REG(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..e8ceeb027e1b 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 cofig = %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 cofig 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,6 +1989,8 @@ 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)) tas_priv->fw_parse_fct_param_address = 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/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..528c44446482 100644 --- a/sound/soc/codecs/tlv320adc3xxx.c +++ b/sound/soc/codecs/tlv320adc3xxx.c @@ -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..7fec94768afb 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) { @@ -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/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..442624cf2a31 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_init_chip(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[] = { @@ -2154,15 +2174,9 @@ static struct snd_soc_dai_driver twl4030_dai[] = { static int twl4030_soc_probe(struct snd_soc_component *component) { - struct twl4030_priv *twl4030; + struct twl4030_priv *twl4030 = dev_get_drvdata(component->dev); - 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); } @@ -2184,6 +2198,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 <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw_type.h> +#include <linux/workqueue.h> +#include <sound/pcm.h> +#include <sound/sdca.h> +#include <sound/sdca_class.h> +#include <sound/sdca_function.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#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 <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> + +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 <linux/module.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#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/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..f7ecbac386e5 100644 --- a/sound/soc/codecs/wm5100.c +++ b/sound/soc/codecs/wm5100.c @@ -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..7d40ed21f569 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -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..9bc5d1e795dc 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -860,6 +860,22 @@ static int wm8580_playback_startup(struct snd_pcm_substream *substream, #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 +885,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 +895,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..72b7d09503a9 100644 --- a/sound/soc/codecs/wm8737.c +++ b/sound/soc/codecs/wm8737.c @@ -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..b0b444793830 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -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/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..017ec2e37d1e 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -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 ad8064bbaaac..25cc433c7653 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..3366166a2b77 100644 --- a/sound/soc/codecs/wm8983.c +++ b/sound/soc/codecs/wm8983.c @@ -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..b5ca4af715c0 100644 --- a/sound/soc/codecs/wm8985.c +++ b/sound/soc/codecs/wm8985.c @@ -1024,23 +1024,30 @@ static int wm8985_set_bias_level(struct snd_soc_component *component, return 0; } -static int wm8985_probe(struct snd_soc_component *component) +static int wm8985_get_regulators(struct device *dev, + struct wm8985_priv *wm8985) { size_t i; - struct wm8985_priv *wm8985; int ret; - 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 = 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; + struct wm8985_priv *wm8985; + int ret; + + wm8985 = snd_soc_component_get_drvdata(component); ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies), wm8985->supplies); @@ -1072,6 +1079,22 @@ err_reg_enable: 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..c8fef187f540 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -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..2cb7fb9962af 100644 --- a/sound/soc/codecs/wm8991.c +++ b/sound/soc/codecs/wm8991.c @@ -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..b8881b260a4b 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -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 @@ err_reg_enable: #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..019940589e51 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; 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..ade89c196a79 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -464,6 +464,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 +479,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) 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..a5e3670349f9 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; @@ -502,19 +503,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, + }, + { + .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, }, - .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-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 <linux/bitrev.h> #include <linux/clk.h> +#include <linux/devm-helpers.h> #include <linux/firmware.h> #include <linux/interrupt.h> #include <linux/module.h> @@ -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"); + /* + * 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; + /* get IRQs */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + xcvr->irq = platform_get_irq(pdev, 0); + if (xcvr->irq < 0) + return xcvr->irq; - ret = devm_request_irq(dev, irq, irq0_isr, 0, pdev->name, xcvr); + 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/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 @@ end: } 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 @@ end: 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/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 ad25bd355769..2d7ced953fde 100644 --- a/sound/soc/intel/avs/pcm.c +++ b/sound/soc/intel/avs/pcm.c @@ -1065,7 +1065,7 @@ finalize: return 0; err_load_libs: - avs_remove_topology(component); + snd_soc_tplg_component_remove(component); return ret; } @@ -1083,7 +1083,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); } @@ -1614,7 +1614,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 +1622,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/topology.c b/sound/soc/intel/avs/topology.c index 5d70be63a4a7..1085090d5a9c 100644 --- a/sound/soc/intel/avs/topology.c +++ b/sound/soc/intel/avs/topology.c @@ -2226,28 +2226,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/boards/Kconfig b/sound/soc/intel/boards/Kconfig index b795bcade8e9..919d3f5f5328 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -536,6 +536,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/sof_cirrus_common.c b/sound/soc/intel/boards/sof_cirrus_common.c index 88fc6cb2bfd4..ec4bda40ae00 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/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-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-<platform>-<function>-id<BE id number>.tplg + * where <platform> 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_mask |= BIT(tplg_dev); + tplg_file = get_tplg_filename(card->dev, prefix, platform, tplg_dev_name, + dai_link->id, tplg_dev); + if (!tplg_file) + return -ENOMEM; - /* - * The tplg file naming rule is sof-<platform>-<function>-id<BE id number>.tplg - * where <platform> 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; + /* 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; } - if (!(*tplg_files)[tplg_num]) - return -ENOMEM; + + 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); - /* 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); + 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 { - 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"); + /* 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_sdw_get_tplg_files); +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 <linux/atomic.h> +#include <linux/list.h> +#include <linux/regmap.h> +#include <sound/soc.h> + +#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 <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> + +#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 <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/regmap.h> +#include <sound/pcm_params.h> +#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 <linux/module.h> +#include <sound/soc.h> + +#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..948f606cbaf2 100644 --- a/sound/soc/mediatek/common/mtk-afe-fe-dai.c +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -204,11 +204,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 +224,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 +234,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 +252,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/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 @@ CLK_INFRA_SYS_AUD_26M_ERR: 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 @@ ERR_SELECT_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: -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 @@ ERR_SELECT_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: -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..4010879ad1d3 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c +++ b/sound/soc/mediatek/mt8188/mt8188-afe-pcm.c @@ -3030,22 +3030,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 +3210,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 +3232,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 +3268,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 +3301,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 +3362,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/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..c0f340a6cc09 100644 --- a/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c +++ b/sound/soc/mediatek/mt8189/mt8189-afe-pcm.c @@ -2332,20 +2332,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 +2441,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 +2454,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 +2474,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 +2513,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 +2588,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-pcm.c b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c index bc0a63457cd7..52c3381e6766 100644 --- a/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c +++ b/sound/soc/mediatek/mt8195/mt8195-afe-pcm.c @@ -3013,7 +3013,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"); diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c index a1ae8322d8b6..dcee037991aa 100644 --- a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c +++ b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c @@ -2309,11 +2309,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 +2322,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) 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/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 <dt-bindings/sound/qcom,q6afe.h> #include <sound/soc.h> -#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/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/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c index 4f09fdd40905..cd4f82bb8cc7 100644 --- a/sound/soc/qcom/qdsp6/q6asm-dai.c +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -551,7 +551,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/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 4d48e1012cd4..b5d367486d56 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -58,6 +58,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 +90,8 @@ 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_prepare)(struct snd_pcm_substream *substream); }; @@ -83,6 +104,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 +147,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 +191,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 +201,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,10 +220,13 @@ 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; + dai_data = sc8280xp_get_dai_data(data->priv, cpu_dai->id); + switch (cpu_dai->id) { case WSA_CODEC_DMA_RX_0: case WSA_CODEC_DMA_RX_1: @@ -209,10 +256,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, @@ -251,15 +312,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 +347,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 +355,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 +363,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 +372,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 +445,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 +474,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; } @@ -488,6 +579,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, @@ -567,6 +680,7 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = { { .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..f0fbec9c8df2 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 @@ -112,8 +117,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 +133,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 +157,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 +169,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, @@ -380,7 +387,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 +412,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: diff --git a/sound/soc/renesas/rz-ssi.c b/sound/soc/renesas/rz-ssi.c index 9fe8a639c47c..b1587f499cb8 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 = { 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 @@ epclkget: 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/rk3399_gru_sound.c b/sound/soc/rockchip/rk3399_gru_sound.c index b80acb221d24..04e7bed8275d 100644 --- a/sound/soc/rockchip/rk3399_gru_sound.c +++ b/sound/soc/rockchip/rk3399_gru_sound.c @@ -505,8 +505,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..ef3f6586af77 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -537,13 +537,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_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..9dfe46ffe0ff 100644 --- a/sound/soc/rockchip/rockchip_sai.c +++ b/sound/soc/rockchip/rockchip_sai.c @@ -957,16 +957,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 <sound/sdca_function.h> #include <sound/sdca_interrupts.h> #include <sound/sdca_regmap.h> -#include "sdca_class.h" +#include <sound/sdca_class.h> #define CLASS_SDW_ATTACH_TIMEOUT_MS 5000 @@ -136,18 +136,35 @@ err: 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 void class_sdw_remove(struct sdw_slave *sdw) +static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id) { - struct device *dev = &sdw->dev; - struct sdca_class_drv *drv = dev_get_drvdata(dev); + 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 int class_suspend(struct device *dev) +static void class_sdw_remove(struct sdw_slave *sdw) +{ + struct sdca_class_drv *drv = dev_get_drvdata(&sdw->dev); + + sdca_class_remove(drv); +} + +/** + * 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 @@ err: return ret; } +EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_resume, "SND_SOC_SDCA_CLASS"); + +/* + * 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 class_pm_ops = { - SYSTEM_SLEEP_PM_OPS(class_suspend, class_resume) - RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL) +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 <linux/completion.h> -#include <linux/mutex.h> -#include <linux/workqueue.h> - -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 <sound/soc-component.h> #include <sound/soc-dai.h> #include <sound/soc.h> -#include "sdca_class.h" +#include <sound/sdca_class.h> #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 <sound/sdca.h> #include <sound/sdca_function.h> +#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 <linux/device.h> +#include <linux/errno.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/soc_sdw_utils.h> + +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 <linux/device.h> +#include <linux/errno.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <sound/soc_sdw_utils.h> + +/* 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 <linux/device.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <sound/soc_sdw_utils.h> + +/* 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 <linux/device.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/control.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include <sound/soc_sdw_utils.h> + +/* + * 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 d2eeef4931c6..14aee10b40ba 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), @@ -678,6 +687,52 @@ struct asoc_sdw_codec_info codec_info_list[] = { }, { .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, .name_prefix = "rt722", .version_id = 3, @@ -725,6 +780,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, .name_prefix = "Left", @@ -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-component.c b/sound/soc/soc-component.c index dc7d203cb76a..463ea227cac8 100644 --- a/sound/soc/soc-component.c +++ b/sound/soc/soc-component.c @@ -315,8 +315,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; } diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index b8402802ae78..093a70a93d2b 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -20,6 +20,7 @@ #include <sound/initval.h> #include <sound/soc-dpcm.h> #include <sound/soc-link.h> +#include "soc-internal.h" static int snd_soc_compr_components_open(struct snd_compr_stream *cstream) { @@ -69,10 +70,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); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index b9c70268d49f..06167a198591 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -41,11 +41,13 @@ #include <sound/soc-topology.h> #include <sound/soc-link.h> #include <sound/initval.h> +#include "soc-internal.h" #define CREATE_TRACE_POINTS #include <trace/events/asoc.h> -static DEFINE_MUTEX(client_mutex); +DEFINE_MUTEX(client_mutex); + static LIST_HEAD(component_list); static LIST_HEAD(unbind_card_list); @@ -232,22 +234,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 +249,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) { @@ -868,7 +810,7 @@ static int snd_soc_is_matching_component( struct snd_soc_dai *dai; for_each_component_dais(component, dai) - if (snd_soc_is_matching_dai(dlc, dai)) + if (snd_soc_dai_matches_dlc(dai, dlc)) return 1; return 0; } @@ -928,7 +870,7 @@ struct snd_soc_dai *snd_soc_find_dai( for_each_component(component) if (snd_soc_is_matching_component(dlc, component)) 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; @@ -1859,7 +1801,7 @@ match: } 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 +2285,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; @@ -2403,26 +2345,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 * * @card: Card to register @@ -2480,7 +2402,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 +2451,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 +2463,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 * @@ -2724,7 +2577,7 @@ static int soc_component_initialize(struct snd_soc_component *component, mutex_init(&component->io_mutex); if (!component->name) { - component->name = fmt_single_name(dev, NULL); + component->name = snd_soc_fmt_single_name(dev, NULL); if (!component->name) { dev_err(dev, "ASoC: Failed to allocate name\n"); return -ENOMEM; @@ -3477,7 +3330,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 +3405,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 <kuninori.morimoto.gx@renesas.com> // +#include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dai.h> #include <sound/soc-link.h> +#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..74be5e209493 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -37,6 +37,7 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/initval.h> +#include "soc-internal.h" #include <trace/events/asoc.h> @@ -4010,7 +4011,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 +4022,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 +4151,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..f6206ecaf26f 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( diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h new file mode 100644 index 000000000000..31559918355f --- /dev/null +++ b/sound/soc/soc-internal.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * soc-internal.h + * + * Copyright (c) 2026 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + */ +#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); + +#endif /* __SOC_INTERNAL_H */ diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c index c935571d43e9..142f407e3939 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. */ 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 <sound/soc-dpcm.h> #include <sound/soc-link.h> #include <sound/initval.h> +#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 @@ found: 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/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/acp-common.c b/sound/soc/sof/amd/acp-common.c index df656cdc1527..33540f7c421b 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 <sound/sof/xtensa.h> +#include "../../amd/acp/soc_amd_sdw_common.h" /** * amd_sof_ipc_dump() - This function is called when IPC tx times out. @@ -177,6 +178,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 +190,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..3984cd627db6 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -156,5 +156,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.c b/sound/soc/sof/amd/acp.c index f89ad86260b4..b059039c9e0d 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, @@ -581,30 +590,196 @@ 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); + unsigned int ext_intr_stat1; + unsigned int sdw_pme_stat, sdw_wake_en; + unsigned int 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); + + 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 (irq_flag) + if (dsp_irq) + return IRQ_WAKE_THREAD; + + if (irq_flag || wake_irq_flag) return IRQ_HANDLED; return IRQ_NONE; @@ -728,6 +903,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 +948,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 +970,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 +1059,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) { @@ -1038,6 +1269,8 @@ skip_soundwire: 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 +1281,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 +1301,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; @@ -1120,7 +1354,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; @@ -1158,6 +1412,8 @@ int amd_sof_acp7x_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; @@ -1171,6 +1427,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 +1437,44 @@ 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; + u32 sdw_wake_en; + u32 pme_reg; + u32 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 +1483,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 +1499,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 +1509,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,30 +1539,40 @@ 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: - snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); - break; - default: - break; - } + if (acp_data->is_sdw_dev) + handle_amd_sof_acp7x_sdw_pme_event(sdev); return 0; } -EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); +EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); -int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) +int amd_sof_acp7x_resume(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"); + struct acp_dev_data *acp_data; + int ret; -int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) -{ - return amd_sof_acp7x_resume(sdev); + 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"); + 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_runtime, "SND_SOC_SOF_AMD_COMMON"); +EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("AMD ACP sof driver"); diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 16d66b2eaa70..1bc6b572448d 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -125,6 +125,17 @@ #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 +/* ACP7X PDM controller selection values for acp_dev_data.pdm_sel; 0 = not set */ +#define ACP7X_PDM_DMIC0 1 +#define ACP7X_PDM_DMIC1 2 #define ACP_DSP_MSG_SET 1 #define ACP_DSP_ACK_SET 1 @@ -279,6 +290,11 @@ 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; int acp_sof_signed_firmware_image; }; 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 <Vijendar.Mukunda@amd.com> + */ + +#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/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 532e15313795..0594c1f3deb6 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -40,6 +40,8 @@ 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, }; 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/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-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/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/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index a8cdf9bc750b..61dd48282dd3 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,6 +79,7 @@ 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 * * @load_library: Callback function for platform dependent library loading * @pipeline_state_mutex: Mutex to protect pipeline triggers, ref counts, states and deletion @@ -81,7 +87,6 @@ struct sof_ipc4_fw_library { 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 +95,7 @@ struct sof_ipc4_fw_data { u32 max_libs_count; bool fw_context_save; bool libraries_restored; + struct list_head nhlt_list; int (*load_library)(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib, bool reload); diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 24a1175dd690..f6617dcbf789 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1844,12 +1844,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 +1881,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 +1901,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 +1942,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, @@ -4069,6 +4084,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; @@ -4104,13 +4120,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, + /* 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 (!ipc4_data->nhlt) + 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", 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/topology.c b/sound/soc/sof/topology.c index 8338133899ab..145bad472118 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -2517,7 +2517,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..a59ad9442372 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 = { 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 e0fef47a227e..0ece54740773 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 @@ done: 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/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/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 <asm/mach-types.h> - -#include <linux/module.h> -#include <linux/io.h> -#include <linux/spi/spi.h> -#include <linux/of.h> - -#include <sound/soc.h> -#include <sound/initval.h> - -#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 <ola.o.lilja@stericsson.com>, - * Kristoffer Karlsson <kristoffer.karlsson@stericsson.com> - * for ST-Ericsson. - */ - -#include <linux/module.h> -#include <linux/device.h> -#include <linux/io.h> -#include <linux/clk.h> -#include <linux/mutex.h> - -#include <sound/soc.h> -#include <sound/soc-dapm.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> - -#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 <ola.o.lilja@stericsson.com> - * 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/tools/arch/riscv/include/asm/csr.h b/tools/arch/riscv/include/asm/csr.h index 21d8cee04638..8df64314d613 100644 --- a/tools/arch/riscv/include/asm/csr.h +++ b/tools/arch/riscv/include/asm/csr.h @@ -163,12 +163,24 @@ #define HGATP_MODE_SHIFT HGATP32_MODE_SHIFT #endif -/* VSIP & HVIP relation */ +/* + * VSIP & HVIP relation + * + * The bit positions are same between VSIP and HVIP for interrupt + * numbers 13-63, where there's a shift for the SSI, STI and SEI. + */ #define VSIP_TO_HVIP_SHIFT (IRQ_VS_SOFT - IRQ_S_SOFT) -#define VSIP_VALID_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ +#define VSIP_BIAS_MASK ((_AC(1, UL) << IRQ_S_SOFT) | \ (_AC(1, UL) << IRQ_S_TIMER) | \ - (_AC(1, UL) << IRQ_S_EXT) | \ - (_AC(1, UL) << IRQ_PMU_OVF)) + (_AC(1, UL) << IRQ_S_EXT)) +#define VSIP_NO_BIAS_MASK (_AC(1, UL) << IRQ_PMU_OVF) +#define VSIP_VALID_MASK (VSIP_BIAS_MASK | VSIP_NO_BIAS_MASK) +#define vsip_to_hvip(_vsip) ((((_vsip) & VSIP_BIAS_MASK) << \ + VSIP_TO_HVIP_SHIFT) | \ + ((_vsip) & VSIP_NO_BIAS_MASK)) +#define hvip_to_vsip(_hvip) ((((_hvip) >> VSIP_TO_HVIP_SHIFT) & \ + VSIP_BIAS_MASK) | \ + ((_hvip) & VSIP_NO_BIAS_MASK)) /* AIA CSR bits */ #define TOPI_IID_SHIFT 16 diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index b749c01742ee..bfa64ae6c94d 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -6206,6 +6206,13 @@ bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path) return -EINVAL; insn = &prog->insns[insn_idx]; + if (is_ldimm64_insn(insn) && (size_t)insn_idx + 1 >= prog->insns_cnt) { + pr_warn("prog '%s': relo #%d: insn #%d (LDIMM64) is truncated\n", + prog->name, i, insn_idx); + err = -EINVAL; + goto out; + } + err = record_relo_core(prog, rec, insn_idx); if (err) { pr_warn("prog '%s': relo #%d: failed to record relocation: %s\n", diff --git a/tools/lib/bpf/relo_core.c b/tools/lib/bpf/relo_core.c index 8ad2715721cf..2672623a4198 100644 --- a/tools/lib/bpf/relo_core.c +++ b/tools/lib/bpf/relo_core.c @@ -980,23 +980,30 @@ done: } /* - * Turn instruction for which CO_RE relocation failed into invalid one with + * Turn instruction for which CO-RE relocation failed into invalid one with * distinct signature. */ -static void bpf_core_poison_insn(const char *prog_name, int relo_idx, - int insn_idx, struct bpf_insn *insn) +static int bpf_core_poison_insn(const char *prog_name, int relo_idx, + struct bpf_insn *insn, int insn_idx) { - pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n", - prog_name, relo_idx, insn_idx); - insn->code = BPF_JMP | BPF_CALL; - insn->dst_reg = 0; - insn->src_reg = 0; - insn->off = 0; - /* if this instruction is reachable (not a dead code), - * verifier will complain with the following message: - * invalid func unknown#195896080 - */ - insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */ + int insn_cnt = is_ldimm64_insn(insn) ? 2 : 1; + int i; + + for (i = 0; i < insn_cnt; i++) { + pr_debug("prog '%s': relo #%d: substituting insn #%d w/ invalid insn\n", + prog_name, relo_idx, insn_idx + i); + insn[i].code = BPF_JMP | BPF_CALL; + insn[i].dst_reg = 0; + insn[i].src_reg = 0; + insn[i].off = 0; + /* + * If this instruction is reachable (not dead code), the verifier + * will complain with "invalid func unknown#195896080". + */ + insn[i].imm = 195896080; /* => 0xbad2310 => "bad relo" */ + } + + return 0; } static int insn_bpf_size_to_bytes(struct bpf_insn *insn) @@ -1047,17 +1054,6 @@ int bpf_core_patch_insn(const char *prog_name, struct bpf_insn *insn, class = BPF_CLASS(insn->code); - if (res->poison) { -poison: - /* poison second part of ldimm64 to avoid confusing error from - * verifier about "unknown opcode 00" - */ - if (is_ldimm64_insn(insn)) - bpf_core_poison_insn(prog_name, relo_idx, insn_idx + 1, insn + 1); - bpf_core_poison_insn(prog_name, relo_idx, insn_idx, insn); - return 0; - } - orig_val = res->orig_val; new_val = res->new_val; @@ -1065,7 +1061,9 @@ poison: case BPF_ALU: case BPF_ALU64: if (BPF_SRC(insn->code) != BPF_K) - return -EINVAL; + goto bad_insn; + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); if (res->validate && insn->imm != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (ALU/ALU64) value: got %d, exp %llu -> %llu\n", prog_name, relo_idx, @@ -1082,6 +1080,8 @@ poison: case BPF_LDX: case BPF_ST: case BPF_STX: + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); if (res->validate && insn->off != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDX/ST/STX) value: got %d, exp %llu -> %llu\n", prog_name, relo_idx, insn_idx, insn->off, (unsigned long long)orig_val, @@ -1097,7 +1097,7 @@ poison: pr_warn("prog '%s': relo #%d: insn #%d (LDX/ST/STX) accesses field incorrectly. " "Make sure you are accessing pointers, unsigned integers, or fields of matching type and size.\n", prog_name, relo_idx, insn_idx); - goto poison; + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); } orig_val = insn->off; @@ -1140,6 +1140,9 @@ poison: return -EINVAL; } + if (res->poison) + return bpf_core_poison_insn(prog_name, relo_idx, insn, insn_idx); + imm = (__u32)insn[0].imm | ((__u64)insn[1].imm << 32); if (res->validate && imm != orig_val) { pr_warn("prog '%s': relo #%d: unexpected insn #%d (LDIMM64) value: got %llu, exp %llu -> %llu\n", @@ -1157,6 +1160,7 @@ poison: break; } default: +bad_insn: pr_warn("prog '%s': relo #%d: trying to relocate unrecognized insn #%d, code:0x%x, src:0x%x, dst:0x%x, off:0x%x, imm:0x%x\n", prog_name, relo_idx, insn_idx, insn->code, (unsigned)insn->src_reg, (unsigned)insn->dst_reg, (unsigned)insn->off, (unsigned)insn->imm); diff --git a/tools/sched_ext/include/scx/common.bpf.h b/tools/sched_ext/include/scx/common.bpf.h index 2ddb01a059fd..22f24ebef8a9 100644 --- a/tools/sched_ext/include/scx/common.bpf.h +++ b/tools/sched_ext/include/scx/common.bpf.h @@ -106,7 +106,7 @@ u64 scx_bpf_now(void) __ksym __weak; void scx_bpf_events(struct scx_event_stats *events, size_t events__sz) __ksym __weak; s32 scx_bpf_cpu_to_cid(s32 cpu) __ksym __weak; s32 scx_bpf_cid_to_cpu(s32 cid) __ksym __weak; -void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out) __ksym __weak; +void scx_bpf_cid_topo(s32 cid, struct scx_cid_topo *out, size_t out__sz) __ksym __weak; void scx_bpf_kick_cid(s32 cid, u64 flags) __ksym __weak; s32 scx_bpf_task_cid(const struct task_struct *p) __ksym __weak; s32 scx_bpf_this_cid(void) __ksym __weak; diff --git a/tools/sched_ext/scx_qmap.bpf.c b/tools/sched_ext/scx_qmap.bpf.c index 67b7c01cae55..2f3653199842 100644 --- a/tools/sched_ext/scx_qmap.bpf.c +++ b/tools/sched_ext/scx_qmap.bpf.c @@ -961,9 +961,6 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init_task, struct task_struct *p, taskc->highpri = false; taskc->core_sched_seq = 0; cmask_init(&taskc->cpus_allowed, 0, scx_bpf_nr_cids()); - bpf_rcu_read_lock(); - cmask_from_cpumask(&taskc->cpus_allowed, p->cpus_ptr); - bpf_rcu_read_unlock(); v = bpf_task_storage_get(&task_ctx_stor, p, NULL, BPF_LOCAL_STORAGE_GET_F_CREATE); diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 2d960626750e..273853937c25 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -35,8 +35,11 @@ TARGETS += fchmodat2 TARGETS += filesystems TARGETS += filesystems/binderfs TARGETS += filesystems/epoll +TARGETS += filesystems/eventfd TARGETS += filesystems/failfs TARGETS += filesystems/fat +TARGETS += filesystems/openat2 +TARGETS += filesystems/open_tree_ns TARGETS += filesystems/overlayfs TARGETS += filesystems/statmount TARGETS += filesystems/mount-notify @@ -46,7 +49,7 @@ TARGETS += filesystems/move_mount TARGETS += filesystems/empty_mntns TARGETS += filesystems/fsmount_ns TARGETS += filesystems/fscontext_ns -TARGETS += filesystems/mntns_cleanup +TARGETS += filesystems/xattr TARGETS += firmware TARGETS += fpu TARGETS += ftrace @@ -103,7 +106,6 @@ TARGETS += prctl TARGETS += proc TARGETS += pstore TARGETS += ptrace -TARGETS += openat2 TARGETS += rdma TARGETS += resctrl TARGETS += riscv diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c index 78566b817fd7..490e15e7126d 100644 --- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c +++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c @@ -13,7 +13,7 @@ struct { } cb_refs_tests[] = { { "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" }, { "leak_prog", "Possibly NULL pointer passed to helper R2" }, - { "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */ + { "nested_cb", "Unreleased reference id=5 alloc_insn=2" }, /* alloc_insn=2{4,5} */ { "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */ }; diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c index a18d3680fb16..51f42b02a267 100644 --- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c +++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c @@ -14,6 +14,197 @@ static char log[16 * 1024]; +static int load_core_relo_insns(int btf_fd, struct bpf_insn *insns, int insn_cnt, + struct bpf_func_info *funcs, int func_cnt, + int enum_id, int access_str_off, int insn_idx, + bool relocate) +{ + struct bpf_core_relo relo = { + .insn_off = insn_idx * sizeof(struct bpf_insn), + .type_id = enum_id, + .access_str_off = access_str_off, + .kind = BPF_CORE_ENUMVAL_VALUE, + }; + union bpf_attr attr = { + .prog_type = BPF_PROG_TYPE_SOCKET_FILTER, + .insn_cnt = insn_cnt, + .insns = (__u64)insns, + .license = (__u64)"GPL", + .log_buf = (__u64)log, + .log_size = sizeof(log), + .log_level = 2, + .prog_btf_fd = btf_fd, + .func_info_rec_size = sizeof(struct bpf_func_info), + .func_info = (__u64)funcs, + .func_info_cnt = func_cnt, + }; + + if (relocate) { + attr.core_relo_cnt = 1; + attr.core_relos = (__u64)&relo; + attr.core_relo_rec_size = sizeof(relo); + } + memset(log, 0, sizeof(log)); + return sys_bpf_prog_load(&attr, sizeof(attr), 1); +} + +static void test_early_core_relo(void) +{ + static const char unrecognized[] = "trying to relocate unrecognized insn #2"; + static const struct { + const char *name; + struct bpf_insn insns[2]; + const char *err_msg; + } tests[] = { + { "poison_exit", { BPF_EXIT_INSN() }, unrecognized }, + { "poison_ja", { BPF_JMP_A(1) }, unrecognized }, + { "poison_jmp", { BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized }, + { "poison_jmp32", { BPF_JMP32_IMM(BPF_JEQ, BPF_REG_0, 0, 1) }, unrecognized }, + { "poison_call", { BPF_EMIT_CALL(BPF_FUNC_get_prandom_u32) }, unrecognized }, + { "poison_alu_reg", { BPF_MOV32_REG(BPF_REG_0, BPF_REG_1) }, unrecognized }, + { "poison_alu64_reg", { BPF_MOV64_REG(BPF_REG_0, BPF_REG_1) }, unrecognized }, + { "poison_ld_abs", { BPF_LD_ABS(BPF_W, 0) }, + "insn #2 (LDIMM64) has unexpected form" }, + { "poison_alu_imm", { BPF_MOV32_IMM(BPF_REG_0, 0) } }, + { "poison_alu64_imm", { BPF_MOV64_IMM(BPF_REG_0, 0) } }, + { "poison_ldx", { BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_1, 0) } }, + { "poison_st", { BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0) } }, + { "poison_stx", { BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4) } }, + { "poison_ldimm64", { BPF_LD_IMM64(BPF_REG_0, 0) } }, + }; + struct test_btf { + struct btf_header hdr; + __u32 types[18]; + char strings[64]; + } raw_btf = { + .hdr = { + .magic = BTF_MAGIC, + .version = BTF_VERSION, + .hdr_len = sizeof(struct btf_header), + .type_off = 0, + .type_len = sizeof(raw_btf.types), + .str_off = offsetof(struct test_btf, strings) - + offsetof(struct test_btf, types), + .str_len = sizeof(raw_btf.strings), + }, + .types = { + BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4), /* [1] int */ + BTF_FUNC_PROTO_ENC(1, 0), /* [2] int (*)(void) */ + BTF_FUNC_ENC(5, 2), /* [3] main_fn */ + BTF_FUNC_ENC(13, 2), /* [4] sub_fn */ + BTF_TYPE_ENC(20, BTF_INFO_ENC(BTF_KIND_ENUM, 0, 1), 4), /* [5] enum */ + BTF_ENUM_ENC(45, 0), /* value = 0 */ + }, + .strings = "\0int\0main_fn\0sub_fn\0core_relo_poison_missing\0value\0" "0", + }; + struct bpf_func_info funcs[] = { + { .insn_off = 0, .type_id = 3 }, + { .insn_off = 3, .type_id = 4 }, + }; + struct bpf_insn core_only[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + struct bpf_insn subprog[] = { + BPF_CALL_REL(2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + struct bpf_insn truncated_ldimm64[] = { + BPF_RAW_INSN(BPF_LD | BPF_IMM | BPF_DW, 0, 0, 0, 0), + }; + int access_str_off = 51; /* offset of "0" */ + int enum_id = 5; + int btf_fd, prog_fd = -1, i; + + btf_fd = bpf_btf_load(&raw_btf, sizeof(raw_btf), NULL); + if (!ASSERT_GE(btf_fd, 0, "btf_load")) + goto cleanup; + + if (test__start_subtest("without_func_info")) { + prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0, + enum_id, access_str_off, 2, false); + if (!ASSERT_GE(prog_fd, 0, "control_load")) + goto cleanup; + close(prog_fd); + prog_fd = load_core_relo_insns(btf_fd, core_only, ARRAY_SIZE(core_only), NULL, 0, + enum_id, access_str_off, 2, true); + if (!ASSERT_GE(prog_fd, 0, "poisoned_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + close(prog_fd); + prog_fd = -1; + } + + if (test__start_subtest("before_subprog_validation")) { + prog_fd = load_core_relo_insns(btf_fd, subprog, ARRAY_SIZE(subprog), funcs, 2, + enum_id, access_str_off, 2, true); + if (!ASSERT_LT(prog_fd, 0, "poisoned_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + ASSERT_HAS_SUBSTR(log, "last insn is not an exit or jmp", "poisoned_load_log"); + } + + if (test__start_subtest("truncated_ldimm64")) { + prog_fd = load_core_relo_insns(btf_fd, truncated_ldimm64, + ARRAY_SIZE(truncated_ldimm64), NULL, 0, + enum_id, access_str_off, 0, true); + if (!ASSERT_LT(prog_fd, 0, "truncated_load")) + goto cleanup; + ASSERT_HAS_SUBSTR(log, "invalid bpf_ld_imm64 insn", "truncated_load_log"); + } + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + struct bpf_insn insns[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 1), + tests[i].insns[0], + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + bool is_ldimm64 = insns[2].code == (BPF_LD | BPF_DW | BPF_IMM); + + if (!test__start_subtest(tests[i].name)) + continue; + if (is_ldimm64) { + insns[1].off = 2; + insns[3] = tests[i].insns[1]; + } + prog_fd = load_core_relo_insns(btf_fd, insns, ARRAY_SIZE(insns), funcs, 1, + enum_id, access_str_off, 2, false); + if (!ASSERT_GE(prog_fd, 0, "control_load")) + goto cleanup; + close(prog_fd); + prog_fd = load_core_relo_insns(btf_fd, insns, ARRAY_SIZE(insns), funcs, 1, + enum_id, access_str_off, 2, true); + if (!tests[i].err_msg) { + ASSERT_GE(prog_fd, 0, "dead_poison_load"); + ASSERT_HAS_SUBSTR(log, "substituting insn #2", "poison_log"); + if (is_ldimm64) + ASSERT_HAS_SUBSTR(log, "substituting insn #3", "poison_ldimm64_log"); + } else { + ASSERT_LT(prog_fd, 0, "invalid_poison_load"); + ASSERT_HAS_SUBSTR(log, tests[i].err_msg, "invalid_poison_log"); + ASSERT_NULL(strstr(log, "substituting insn"), "invalid_poison_substitution"); + } + close(prog_fd); + prog_fd = -1; + } + +cleanup: + if (env.verbosity > VERBOSE_NORMAL && log[0]) { + printf("-------- program load log start --------\n"); + printf("%s", log); + printf("-------- program load log end ----------\n"); + } + close(prog_fd); + close(btf_fd); +} + /* Check that verifier rejects BPF program containing relocation * pointing to non-existent BTF type. */ @@ -120,6 +311,7 @@ out: void test_core_reloc_raw(void) { + test_early_core_relo(); if (test__start_subtest("bad_local_id")) test_bad_local_id(); } diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testing/selftests/bpf/prog_tests/dynptr.c index 5fda11590708..4396560365e8 100644 --- a/tools/testing/selftests/bpf/prog_tests/dynptr.c +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c @@ -9,6 +9,7 @@ enum test_setup_type { SETUP_SYSCALL_SLEEP, SETUP_SKB_PROG, + SETUP_SKB_PROG_NONLINEAR, SETUP_SKB_PROG_TP, SETUP_XDP_PROG, }; @@ -32,6 +33,7 @@ static struct { {"test_ringbuf", SETUP_SYSCALL_SLEEP}, {"test_skb_readonly", SETUP_SKB_PROG}, {"test_dynptr_skb_data", SETUP_SKB_PROG}, + {"test_dynptr_skb_slice_non_linear", SETUP_SKB_PROG_NONLINEAR}, {"test_dynptr_skb_meta_data", SETUP_SKB_PROG}, {"test_dynptr_skb_meta_flags", SETUP_SKB_PROG}, {"test_adjust", SETUP_SYSCALL_SLEEP}, @@ -94,7 +96,9 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ bpf_link__destroy(link); break; case SETUP_SKB_PROG: + case SETUP_SKB_PROG_NONLINEAR: { + struct __sk_buff ctx = {}; int prog_fd; char buf[64]; @@ -106,6 +110,12 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ .repeat = 1, ); + if (setup_type == SETUP_SKB_PROG_NONLINEAR) { + ctx.data_end = ETH_HLEN + sizeof(struct iphdr); + topts.ctx_in = &ctx; + topts.ctx_size_in = sizeof(ctx); + } + prog_fd = bpf_program__fd(prog); if (!ASSERT_GE(prog_fd, 0, "prog_fd")) goto cleanup; diff --git a/tools/testing/selftests/bpf/prog_tests/exceptions.c b/tools/testing/selftests/bpf/prog_tests/exceptions.c index 3588d6f97fd4..639866ce09a9 100644 --- a/tools/testing/selftests/bpf/prog_tests/exceptions.c +++ b/tools/testing/selftests/bpf/prog_tests/exceptions.c @@ -55,6 +55,7 @@ static void test_exceptions_success(void) RUN_SUCCESS(exception_ext, 0); RUN_SUCCESS(exception_ext_mod_cb_runtime, 35); RUN_SUCCESS(exception_throw_subprog, 1); + RUN_SUCCESS(exception_throw_subprog_stack_cb, 0x1234); RUN_SUCCESS(exception_assert_nz_gfunc, 1); RUN_SUCCESS(exception_assert_zero_gfunc, 1); RUN_SUCCESS(exception_assert_neg_gfunc, 1); diff --git a/tools/testing/selftests/bpf/prog_tests/linked_list.c b/tools/testing/selftests/bpf/prog_tests/linked_list.c index c3d133c6a00d..52fabbee3dd5 100644 --- a/tools/testing/selftests/bpf/prog_tests/linked_list.c +++ b/tools/testing/selftests/bpf/prog_tests/linked_list.c @@ -714,7 +714,7 @@ static void test_btf(void) break; err = btf__load_into_kernel(btf); - ASSERT_EQ(err, -ELOOP, "check btf"); + ASSERT_EQ(err, 0, "check btf"); btf__free(btf); break; } @@ -773,7 +773,7 @@ static void test_btf(void) break; err = btf__load_into_kernel(btf); - ASSERT_EQ(err, -ELOOP, "check btf"); + ASSERT_EQ(err, 0, "check btf"); btf__free(btf); break; } diff --git a/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c b/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c new file mode 100644 index 000000000000..a487aa68f2ee --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/local_kptr_ownership.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include <bpf/btf.h> +#include <linux/btf.h> +#include <test_progs.h> + +#define SPIN_LOCK 2 +#define LIST_HEAD 3 +#define LIST_NODE 4 +/* Keep in sync with BTF_MAX_OWNERSHIP_DEPTH. */ +#define MAX_OWNERSHIP_DEPTH 8 + +static struct btf *init_btf(void) +{ + struct btf *btf; + int id; + + btf = btf__new_empty(); + if (!ASSERT_OK_PTR(btf, "btf__new_empty")) + return NULL; + id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED); + if (!ASSERT_EQ(id, 1, "btf__add_int")) + goto err_out; + id = btf__add_struct(btf, "bpf_spin_lock", 4); + if (!ASSERT_EQ(id, SPIN_LOCK, "btf__add_struct bpf_spin_lock")) + goto err_out; + id = btf__add_struct(btf, "bpf_list_head", 16); + if (!ASSERT_EQ(id, LIST_HEAD, "btf__add_struct bpf_list_head")) + goto err_out; + id = btf__add_struct(btf, "bpf_list_node", 24); + if (!ASSERT_EQ(id, LIST_NODE, "btf__add_struct bpf_list_node")) + goto err_out; + return btf; + +err_out: + btf__free(btf); + return NULL; +} + +static int add_local_kptr(struct btf *btf, int pointee_id, const char *tag) +{ + int id; + + id = btf__add_type_tag(btf, tag, pointee_id); + if (!ASSERT_GT(id, 0, "btf__add_type_tag")) + return id; + id = btf__add_ptr(btf, id); + ASSERT_GT(id, 0, "btf__add_ptr"); + return id; +} + +static void test_self_cycle(const char *tag, int expected_err) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 7, tag); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "self_cycle", 8); + if (!ASSERT_EQ(id, 7, "btf__add_struct self_cycle")) + goto out; + err = btf__add_field(btf, "next", 6, 0, 0); + if (!ASSERT_OK(err, "btf__add_field self_cycle::next")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +static void test_aba_cycle(void) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 10, "kptr"); + if (id <= 0) + goto out; + id = add_local_kptr(btf, 9, "kptr"); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "cycle_a", 8); + if (!ASSERT_EQ(id, 9, "btf__add_struct cycle_a")) + goto out; + err = btf__add_field(btf, "b", 6, 0, 0); + if (!ASSERT_OK(err, "btf__add_field cycle_a::b")) + goto out; + id = btf__add_struct(btf, "cycle_b", 8); + if (!ASSERT_EQ(id, 10, "btf__add_struct cycle_b")) + goto out; + err = btf__add_field(btf, "a", 8, 0, 0); + if (!ASSERT_OK(err, "btf__add_field cycle_b::a")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, -ELOOP, "check btf"); +out: + btf__free(btf); +} + +static void test_mixed_cycle(void) +{ + struct btf *btf; + int id, err; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + id = add_local_kptr(btf, 7, "kptr"); + if (id <= 0) + goto out; + id = btf__add_struct(btf, "mixed_owner", 20); + if (!ASSERT_EQ(id, 7, "btf__add_struct mixed_owner")) + goto out; + err = btf__add_field(btf, "root", LIST_HEAD, 0, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_owner::root")) + goto out; + err = btf__add_field(btf, "lock", SPIN_LOCK, 128, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_owner::lock")) + goto out; + id = btf__add_decl_tag(btf, "contains:mixed_node:node", 7, 0); + if (!ASSERT_EQ(id, 8, "btf__add_decl_tag mixed_owner")) + goto out; + id = btf__add_struct(btf, "mixed_node", 32); + if (!ASSERT_EQ(id, 9, "btf__add_struct mixed_node")) + goto out; + err = btf__add_field(btf, "node", LIST_NODE, 0, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_node::node")) + goto out; + err = btf__add_field(btf, "owner", 6, 192, 0); + if (!ASSERT_OK(err, "btf__add_field mixed_node::owner")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, -ELOOP, "check btf"); +out: + btf__free(btf); +} + +static void test_acyclic_depth(int depth, bool child_first, bool shared_suffix, int expected_err) +{ + int ptr_id[MAX_OWNERSHIP_DEPTH + 1]; + int first_struct_id; + struct btf *btf; + int id, err, i, n, pointee_id; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + first_struct_id = 5 + 2 * depth; + for (i = 0; i < depth; i++) { + if (i == depth - 1) + pointee_id = first_struct_id + depth; + else + pointee_id = first_struct_id + (child_first ? depth - 2 - i : i + 1); + ptr_id[i] = add_local_kptr(btf, pointee_id, "kptr"); + if (ptr_id[i] <= 0) + goto out; + } + for (n = 0; n < depth; n++) { + char name[32]; + int offset = 0; + + i = child_first ? depth - 1 - n : n; + snprintf(name, sizeof(name), "owner_%d", i); + id = btf__add_struct(btf, name, shared_suffix && !i ? 16 : 8); + if (!ASSERT_EQ(id, first_struct_id + n, "btf__add_struct owner")) + goto out; + if (shared_suffix && !i) { + /* + * Visit the shared suffix through the shorter path before + * reaching it again with less remaining depth. + */ + err = btf__add_field(btf, "suffix", ptr_id[1], 0, 0); + if (!ASSERT_OK(err, "btf__add_field owner::suffix")) + goto out; + offset = 64; + } + err = btf__add_field(btf, "next", ptr_id[i], offset, 0); + if (!ASSERT_OK(err, "btf__add_field owner::next")) + goto out; + } + id = btf__add_struct(btf, "plain_leaf", 4); + if (!ASSERT_EQ(id, first_struct_id + depth, "btf__add_struct plain_leaf")) + goto out; + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +static void test_graph_depth(bool rbtree, int depth, int expected_err) +{ + int root_type = LIST_HEAD, node_type = LIST_NODE, node_size = 24; + int id, err, i, lock_off, root_off, size; + struct btf *btf; + + btf = init_btf(); + if (!ASSERT_OK_PTR(btf, "init_btf")) + return; + if (rbtree) { + root_type = btf__add_struct(btf, "bpf_rb_root", 16); + if (!ASSERT_GT(root_type, 0, "btf__add_struct bpf_rb_root")) + goto out; + node_type = btf__add_struct(btf, "bpf_rb_node", 32); + if (!ASSERT_GT(node_type, 0, "btf__add_struct bpf_rb_node")) + goto out; + node_size = 32; + } + + for (i = 0; i < depth; i++) { + char name[32], tag[64]; + + lock_off = i ? node_size : 0; + root_off = lock_off + 8; + size = i == depth - 1 ? node_size : root_off + 16; + snprintf(name, sizeof(name), "graph_owner_%d", i); + id = btf__add_struct(btf, name, size); + if (!ASSERT_GT(id, 0, "btf__add_struct graph_owner")) + goto out; + if (i) { + err = btf__add_field(btf, "node", node_type, 0, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::node")) + goto out; + } + if (i == depth - 1) + continue; + err = btf__add_field(btf, "lock", SPIN_LOCK, lock_off * 8, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::lock")) + goto out; + err = btf__add_field(btf, "root", root_type, root_off * 8, 0); + if (!ASSERT_OK(err, "btf__add_field graph_owner::root")) + goto out; + snprintf(tag, sizeof(tag), "contains:graph_owner_%d:node", i + 1); + err = btf__add_decl_tag(btf, tag, id, i ? 2 : 1); + if (!ASSERT_GT(err, 0, "btf__add_decl_tag graph_owner")) + goto out; + } + + err = btf__load_into_kernel(btf); + ASSERT_EQ(err, expected_err, "check btf"); +out: + btf__free(btf); +} + +void test_local_kptr_ownership(void) +{ + if (test__start_subtest("self_cycle")) + test_self_cycle("kptr", -ELOOP); + if (test__start_subtest("untrusted_self_cycle")) + test_self_cycle("kptr_untrusted", 0); + if (test__start_subtest("percpu_self_cycle")) + test_self_cycle("percpu_kptr", -ELOOP); + if (test__start_subtest("ABA_cycle")) + test_aba_cycle(); + if (test__start_subtest("mixed_graph_root_cycle")) + test_mixed_cycle(); + if (test__start_subtest("max_acyclic")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, false, 0); + if (test__start_subtest("too_deep_acyclic")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, false, -ELOOP); + if (test__start_subtest("max_acyclic_child_first")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, true, false, 0); + if (test__start_subtest("too_deep_acyclic_child_first")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, true, false, -ELOOP); + if (test__start_subtest("max_acyclic_shared_suffix")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH, false, true, 0); + if (test__start_subtest("too_deep_acyclic_shared_suffix")) + test_acyclic_depth(MAX_OWNERSHIP_DEPTH + 1, false, true, -ELOOP); + if (test__start_subtest("list_three_types")) + test_graph_depth(false, 3, 0); + if (test__start_subtest("list_four_types")) + test_graph_depth(false, 4, 0); + if (test__start_subtest("list_max_depth")) + test_graph_depth(false, MAX_OWNERSHIP_DEPTH, 0); + if (test__start_subtest("list_too_deep")) + test_graph_depth(false, MAX_OWNERSHIP_DEPTH + 1, -ELOOP); + if (test__start_subtest("rbtree_three_types")) + test_graph_depth(true, 3, 0); + if (test__start_subtest("rbtree_four_types")) + test_graph_depth(true, 4, 0); + if (test__start_subtest("rbtree_max_depth")) + test_graph_depth(true, MAX_OWNERSHIP_DEPTH, 0); + if (test__start_subtest("rbtree_too_deep")) + test_graph_depth(true, MAX_OWNERSHIP_DEPTH + 1, -ELOOP); +} diff --git a/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c b/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c index a72ae0b29f6e..7b4a1e24363b 100644 --- a/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c +++ b/tools/testing/selftests/bpf/prog_tests/percpu_alloc.c @@ -1,4 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <sched.h> #include <test_progs.h> #include "cgroup_helpers.h" #include "percpu_alloc_array.skel.h" @@ -350,6 +352,103 @@ static void test_lru_percpu_hash_cpu_flag(void) test_percpu_map_cpu_flag(BPF_MAP_TYPE_LRU_PERCPU_HASH); } +/* + * A BPF_F_CPU update that creates an element must zero the value on the other + * cpus, rather than leave them holding whatever the recycled element last + * contained. max_entries is 1 so the second key can only reuse the element + * the first one released. + */ +static void test_percpu_map_cpu_flag_create(enum bpf_map_type map_type, __u32 map_flags) +{ + LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = map_flags); + const u32 stale = 0xDEADC0DE, fresh = 0xC0FFEE; + int nr_cpus, cpu, map_fd, err, key; + int pinned_cpu, value_cpu; + cpu_set_t old_mask, new_mask; + bool restore_mask = false; + u32 value; + u64 flags; + + nr_cpus = libbpf_num_possible_cpus(); + if (!ASSERT_GT(nr_cpus, 0, "libbpf_num_possible_cpus")) + return; + + if (nr_cpus < 2) { + test__skip(); + return; + } + + map_fd = bpf_map_create(map_type, "cpu_flag_create", sizeof(key), sizeof(value), 1, &opts); + if (!ASSERT_GE(map_fd, 0, "bpf_map_create")) + return; + + /* NO_PREALLOC recycles per cpu, so keep the delete and the create on one cpu. */ + err = sched_getaffinity(0, sizeof(old_mask), &old_mask); + if (!ASSERT_OK(err, "sched_getaffinity")) + goto out; + + pinned_cpu = sched_getcpu(); + if (!ASSERT_GE(pinned_cpu, 0, "sched_getcpu")) + goto out; + + CPU_ZERO(&new_mask); + CPU_SET(pinned_cpu, &new_mask); + err = sched_setaffinity(0, sizeof(new_mask), &new_mask); + if (!ASSERT_OK(err, "sched_setaffinity")) + goto out; + restore_mask = true; + + value_cpu = pinned_cpu ? 0 : 1; + + key = 1; + value = stale; + err = bpf_map_update_elem(map_fd, &key, &value, BPF_F_ALL_CPUS); + if (!ASSERT_OK(err, "bpf_map_update_elem all_cpus")) + goto out; + + err = bpf_map_delete_elem(map_fd, &key); + if (!ASSERT_OK(err, "bpf_map_delete_elem")) + goto out; + + key = 2; + value = fresh; + flags = (u64)value_cpu << 32 | BPF_F_CPU; + err = bpf_map_update_elem(map_fd, &key, &value, flags); + if (!ASSERT_OK(err, "bpf_map_update_elem specified cpu")) + goto out; + + for (cpu = 0; cpu < nr_cpus; cpu++) { + value = 0; + flags = (u64)cpu << 32 | BPF_F_CPU; + err = bpf_map_lookup_elem_flags(map_fd, &key, &value, flags); + if (!ASSERT_OK(err, "bpf_map_lookup_elem_flags specified cpu")) + goto out; + if (!ASSERT_EQ(value, cpu == value_cpu ? fresh : 0, "value on specified cpu")) + goto out; + } + +out: + if (restore_mask) + sched_setaffinity(0, sizeof(old_mask), &old_mask); + close(map_fd); +} + +static void test_percpu_hash_cpu_flag_create(void) +{ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, 0); +} + +static void test_percpu_hash_cpu_flag_create_malloc(void) +{ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_PERCPU_HASH, BPF_F_NO_PREALLOC); +} + +static void test_lru_percpu_hash_cpu_flag_create(void) +{ + /* lru without prealloc is -ENOTSUPP, so there is no malloc variant */ + test_percpu_map_cpu_flag_create(BPF_MAP_TYPE_LRU_PERCPU_HASH, 0); +} + static void test_percpu_cgroup_storage_cpu_flag(void) { struct percpu_alloc_array *skel = NULL; @@ -454,6 +553,12 @@ void test_percpu_alloc(void) test_percpu_hash_cpu_flag(); if (test__start_subtest("cpu_flag_lru_percpu_hash")) test_lru_percpu_hash_cpu_flag(); + if (test__start_subtest("cpu_flag_create_percpu_hash")) + test_percpu_hash_cpu_flag_create(); + if (test__start_subtest("cpu_flag_create_percpu_hash_malloc")) + test_percpu_hash_cpu_flag_create_malloc(); + if (test__start_subtest("cpu_flag_create_lru_percpu_hash")) + test_lru_percpu_hash_cpu_flag_create(); if (test__start_subtest("cpu_flag_percpu_cgroup_storage")) test_percpu_cgroup_storage_cpu_flag(); if (test__start_subtest("cpu_flag_array")) diff --git a/tools/testing/selftests/bpf/prog_tests/sock_destroy.c b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c index 9c11938fe597..78d642a02bdb 100644 --- a/tools/testing/selftests/bpf/prog_tests/sock_destroy.c +++ b/tools/testing/selftests/bpf/prog_tests/sock_destroy.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include <poll.h> #include <test_progs.h> #include <bpf/bpf_endian.h> @@ -110,6 +111,122 @@ cleanup: close(serv); } +static void test_tcp_listen_pending(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n, serv_port; + struct pollfd pfd = { .events = POLLIN }; + char buf[1]; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + serv_port = get_socket_local_port(serv); + if (!ASSERT_GE(serv_port, 0, "get_sock_local_port")) + goto cleanup; + skel->bss->serv_port = (__be16)serv_port; + + /* + * Connect but never accept, so the child sits in the accept queue + * of the listener. Wait until it's actually there. + */ + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + pfd.fd = serv; + if (!ASSERT_EQ(poll(&pfd, 1, -1), 1, "poll listener")) + goto cleanup; + + /* Run iterator program that destroys server sockets. */ + start_iter_sockets(skel->progs.iter_tcp6_server); + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_LT(accept_serv, 0, "accept on destroyed listener")) + goto cleanup; + ASSERT_EQ(errno, EINVAL, "error code on destroyed listener"); + + /* The unaccepted child was reset along with the listener. */ + n = recv(clien, buf, sizeof(buf), 0); + if (!ASSERT_LT(n, 0, "client recv on reset child")) + goto cleanup; + ASSERT_EQ(errno, ECONNRESET, "error code on reset child"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + +static void test_tcp_timewait(struct sock_destroy_prog *skel) +{ + int serv = -1, clien = -1, accept_serv = -1, n; + struct timeval tv = {}; + char buf[1]; + + serv = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); + if (!ASSERT_GE(serv, 0, "start_server")) + goto cleanup; + + clien = connect_to_fd(serv, 0); + if (!ASSERT_GE(clien, 0, "connect_to_fd")) + goto cleanup; + + accept_serv = accept(serv, NULL, NULL); + if (!ASSERT_GE(accept_serv, 0, "serv accept")) + goto cleanup; + + /* + * Active close from the client, then close the server side. Once + * recv() sees EOF the server FIN has been processed and the client + * sock is in TIME_WAIT. Block without timeout so a loaded CI box + * can't race us. + */ + if (!ASSERT_OK(setsockopt(clien, SOL_SOCKET, SO_RCVTIMEO, &tv, + sizeof(tv)), "clear rcvtimeo")) + goto cleanup; + if (!ASSERT_OK(shutdown(clien, SHUT_WR), "client shutdown")) + goto cleanup; + + /* + * Make sure the server has seen the client FIN before it closes, + * so the two FINs never cross. + */ + n = recv(accept_serv, buf, sizeof(buf), 0); + if (!ASSERT_EQ(n, 0, "server recv EOF")) + goto cleanup; + + close(accept_serv); + accept_serv = -1; + + /* block until return EOF */ + n = recv(clien, buf, sizeof(buf), 0); + if (!ASSERT_EQ(n, 0, "client recv EOF")) + goto cleanup; + + /* Run iterator program that destroys the timewait client sock. */ + skel->bss->tw_found = 0; + start_iter_sockets(skel->progs.iter_tcp6_timewait); + if (!ASSERT_EQ(skel->bss->tw_found, 1, "timewait sock found")) + goto cleanup; + + ASSERT_OK(skel->bss->tw_destroy_err, "destroy timewait sock"); + + /* The destroyed timewait sock must be gone. */ + skel->bss->tw_found = 0; + start_iter_sockets(skel->progs.iter_tcp6_timewait); + ASSERT_EQ(skel->bss->tw_found, 0, "timewait sock destroyed"); + +cleanup: + if (clien != -1) + close(clien); + if (accept_serv != -1) + close(accept_serv); + if (serv != -1) + close(serv); +} + static void test_udp_client(struct sock_destroy_prog *skel) { int serv = -1, clien = -1, n = 0; @@ -204,6 +321,10 @@ void test_sock_destroy(void) test_tcp_client(skel); if (test__start_subtest("tcp_server")) test_tcp_server(skel); + if (test__start_subtest("tcp_listen_pending")) + test_tcp_listen_pending(skel); + if (test__start_subtest("tcp_timewait")) + test_tcp_timewait(skel); if (test__start_subtest("udp_client")) test_udp_client(skel); if (test__start_subtest("udp_server")) diff --git a/tools/testing/selftests/bpf/prog_tests/spin_lock.c b/tools/testing/selftests/bpf/prog_tests/spin_lock.c index 5c3579438427..e368370262c8 100644 --- a/tools/testing/selftests/bpf/prog_tests/spin_lock.c +++ b/tools/testing/selftests/bpf/prog_tests/spin_lock.c @@ -54,6 +54,8 @@ static struct { { "lock_global_sleepable_helper_subprog", "global function calls are not allowed while holding a lock" }, { "lock_global_sleepable_kfunc_subprog", "global function calls are not allowed while holding a lock" }, { "lock_global_sleepable_subprog_indirect", "global function calls are not allowed while holding a lock" }, + { "callback_value_lock_identity", "bpf_spin_unlock of different lock" }, + { "callback_inner_map_value_lock_identity", "bpf_spin_unlock of different lock" }, }; static int match_regex(const char *pattern, const char *string) diff --git a/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c b/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c new file mode 100644 index 000000000000..7acdbd5757a9 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tc_change_tail_pmtu.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <netinet/tcp.h> + +#include "test_progs.h" +#include "network_helpers.h" +#include "test_tc_change_tail_pmtu.skel.h" + +#define CLIENT_NS "tc-change-tail-cli-ns" +#define SERVER_NS "tc-change-tail-srv-ns" +#define CLIENT_IP "192.168.1.1" +#define SERVER_IP "192.168.1.2" + +#define TEST_PMTU 1000 +#define TEST_MSS_MAX (TEST_PMTU - 20 - 20) +#define TIMEOUT_MS 3000 +#define XFER_BYTES 8192 + +void test_tc_change_tail_pmtu(void) +{ + LIBBPF_OPTS(bpf_tcx_opts, tcx_opts); + int mss_before = 0, mss_after = 0, ifindex, port; + int srv_fd = -1, srv_conn_fd = -1, cli_fd = -1; + struct test_tc_change_tail_pmtu *skel = NULL; + struct nstoken *nstoken = NULL; + static char buf[XFER_BYTES]; + socklen_t optlen; + ssize_t bytes; + size_t total; + + if (!ASSERT_OK(make_netns(CLIENT_NS), "make client ns")) + return; + if (!ASSERT_OK(make_netns(SERVER_NS), "make server ns")) + goto out_client_ns; + + nstoken = open_netns(CLIENT_NS); + if (!ASSERT_OK_PTR(nstoken, "open client ns")) + goto out; + SYS(out, "ip link add veth1 type veth peer name veth2 netns " SERVER_NS); + SYS(out, "ip -4 addr add " CLIENT_IP "/24 dev veth1"); + SYS(out, "ip link set veth1 up"); + ifindex = if_nametoindex("veth1"); + if (!ASSERT_NEQ(ifindex, 0, "if_nametoindex")) + goto out; + close_netns(nstoken); + nstoken = NULL; + + nstoken = open_netns(SERVER_NS); + if (!ASSERT_OK_PTR(nstoken, "open server ns")) + goto out; + SYS(out, "ip -4 addr add " SERVER_IP "/24 dev veth2"); + SYS(out, "ip link set veth2 up"); + srv_fd = start_server(AF_INET, SOCK_STREAM, SERVER_IP, 0, TIMEOUT_MS); + if (!ASSERT_OK_FD(srv_fd, "start server")) + goto out; + close_netns(nstoken); + nstoken = NULL; + + skel = test_tc_change_tail_pmtu__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open and load skeleton")) + goto out; + + port = get_socket_local_port(srv_fd); + if (!ASSERT_GE(port, 0, "get server port")) + goto out; + + skel->bss->server_port = port; + skel->bss->pmtu = TEST_PMTU; + + nstoken = open_netns(CLIENT_NS); + if (!ASSERT_OK_PTR(nstoken, "open client ns")) + goto out; + + skel->links.change_tail_icmp = + bpf_program__attach_tcx(skel->progs.change_tail_icmp, ifindex, + &tcx_opts); + if (!ASSERT_OK_PTR(skel->links.change_tail_icmp, "attach tcx")) + goto out; + + cli_fd = connect_to_fd(srv_fd, TIMEOUT_MS); + if (!ASSERT_OK_FD(cli_fd, "connect to server")) + goto out; + srv_conn_fd = accept(srv_fd, NULL, NULL); + if (!ASSERT_OK_FD(srv_conn_fd, "accept connection")) + goto out; + if (!ASSERT_OK(settimeo(srv_conn_fd, TIMEOUT_MS), "set server timeout")) + goto out; + + optlen = sizeof(mss_before); + if (!ASSERT_OK(getsockopt(cli_fd, IPPROTO_TCP, TCP_MAXSEG, &mss_before, + &optlen), "get mss before")) + goto out; + + bytes = send(cli_fd, buf, sizeof(buf), 0); + if (!ASSERT_EQ(bytes, (ssize_t)sizeof(buf), "send data")) + goto out; + + for (total = 0; total < sizeof(buf); total += bytes) { + bytes = recv(srv_conn_fd, buf, sizeof(buf), 0); + if (bytes <= 0) + break; + } + + ASSERT_EQ(total, sizeof(buf), "receive data"); + ASSERT_OK(skel->data->change_tail_ret, "change tail"); + ASSERT_OK(skel->bss->adjust_room_ret, "adjust room"); + ASSERT_TRUE(skel->bss->icmp_sent, "icmp sent"); + + optlen = sizeof(mss_after); + if (!ASSERT_OK(getsockopt(cli_fd, IPPROTO_TCP, TCP_MAXSEG, &mss_after, + &optlen), "get mss after")) + goto out; + + ASSERT_LT(mss_after, mss_before, "mss reduced"); + ASSERT_LE(mss_after, TEST_MSS_MAX, "mss below pmtu"); +out: + close(srv_conn_fd); + close(cli_fd); + close(srv_fd); + test_tc_change_tail_pmtu__destroy(skel); + close_netns(nstoken); + remove_netns(SERVER_NS); +out_client_ns: + remove_netns(CLIENT_NS); +} diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index 64ac49ad67e6..8b439e194bcc 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -23,6 +23,7 @@ #include "verifier_bpf_trap.skel.h" #include "verifier_bswap.skel.h" #include "verifier_btf_ctx_access.skel.h" +#include "verifier_btf_flex_array.skel.h" #include "verifier_btf_unreliable_prog.skel.h" #include "verifier_call_large_imm.skel.h" #include "verifier_cfg.skel.h" @@ -53,6 +54,7 @@ #include "verifier_iterating_callbacks.skel.h" #include "verifier_jeq_infer_not_null.skel.h" #include "verifier_jit_convergence.skel.h" +#include "verifier_kfunc_perfmon.skel.h" #include "verifier_ld_ind.skel.h" #include "verifier_ldsx.skel.h" #include "verifier_leak_ptr.skel.h" @@ -186,6 +188,7 @@ void test_verifier_bpf_get_stack(void) { RUN(verifier_bpf_get_stack); } void test_verifier_bpf_trap(void) { RUN(verifier_bpf_trap); } void test_verifier_bswap(void) { RUN(verifier_bswap); } void test_verifier_btf_ctx_access(void) { RUN(verifier_btf_ctx_access); } +void test_verifier_btf_flex_array(void) { RUN(verifier_btf_flex_array); } void test_verifier_btf_unreliable_prog(void) { RUN(verifier_btf_unreliable_prog); } void test_verifier_call_large_imm(void) { RUN(verifier_call_large_imm); } void test_verifier_cfg(void) { RUN(verifier_cfg); } @@ -216,6 +219,7 @@ void test_verifier_int_ptr(void) { RUN(verifier_int_ptr); } void test_verifier_iterating_callbacks(void) { RUN(verifier_iterating_callbacks); } void test_verifier_jeq_infer_not_null(void) { RUN(verifier_jeq_infer_not_null); } void test_verifier_jit_convergence(void) { RUN(verifier_jit_convergence); } +void test_verifier_kfunc_perfmon(void) { RUN(verifier_kfunc_perfmon); } void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); } void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); } void test_verifier_ldsx(void) { RUN(verifier_ldsx); } diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c index e0745b6e467e..b668ebd61fc7 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_success.c +++ b/tools/testing/selftests/bpf/progs/dynptr_success.c @@ -10,6 +10,7 @@ #include "errno.h" #define PAGE_SIZE_64K 65536 +#define TEST_SKB_LINEAR_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr)) char _license[] SEC("license") = "GPL"; @@ -212,6 +213,25 @@ int test_dynptr_skb_data(struct __sk_buff *skb) } SEC("?tc") +int test_dynptr_skb_slice_non_linear(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + void *data; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) { + err = 1; + return 1; + } + + /* Ensure we cannot read past the end of the buffer. */ + data = bpf_dynptr_slice(&ptr, TEST_SKB_LINEAR_SIZE + 1, NULL, 1); + if (data) + err = 2; + + return 1; +} + +SEC("?tc") int test_dynptr_skb_meta_data(struct __sk_buff *skb) { struct bpf_dynptr meta; diff --git a/tools/testing/selftests/bpf/progs/exceptions.c b/tools/testing/selftests/bpf/progs/exceptions.c index c8d716fbd419..91c81971e58c 100644 --- a/tools/testing/selftests/bpf/progs/exceptions.c +++ b/tools/testing/selftests/bpf/progs/exceptions.c @@ -212,6 +212,36 @@ int exception_throw_subprog(struct __sk_buff *ctx) return 0; } +u64 exception_cb_stack_src = 0x1234; + +/* + * The address handed to the helper has to be this callback's own stack + * slot, not one from a frame that is already gone. + */ +__noinline int exception_cb_stack(u64 cookie) +{ + volatile u64 val = 0xdead; + + bpf_probe_read_kernel((void *)&val, sizeof(val), &exception_cb_stack_src); + return val; +} + +/* Throws from a subprogram that has a stack of its own. */ +__noinline static int throwing_subprog_stack(struct __sk_buff *ctx) +{ + volatile u64 pad[4] = {}; + + bpf_throw(pad[0]); + return 0; +} + +SEC("tc") +__exception_cb(exception_cb_stack) +int exception_throw_subprog_stack_cb(struct __sk_buff *ctx) +{ + return throwing_subprog_stack(ctx); +} + __noinline int assert_nz_gfunc(u64 c) { volatile u64 cookie = c; diff --git a/tools/testing/selftests/bpf/progs/iters_state_safety.c b/tools/testing/selftests/bpf/progs/iters_state_safety.c index 646026430e9b..e5bb9fe6d5e5 100644 --- a/tools/testing/selftests/bpf/progs/iters_state_safety.c +++ b/tools/testing/selftests/bpf/progs/iters_state_safety.c @@ -52,6 +52,28 @@ int create_and_destroy(void *ctx) return 0; } +/* fp+0 is not a stack slot. bpf_get_spi(0) used to alias spi 0 (fp-8). */ +SEC("?raw_tp") +__failure __msg("cannot pass in iter at an offset=0") +int destroy_fp0_fail(void *ctx) +{ + struct bpf_iter_num iter; + + asm volatile ("r1 = %[iter];" + "r2 = 0;" + "r3 = 1000;" + "call %[bpf_iter_num_new];" + /* r10 is fp+0, one byte above the top of the BPF stack */ + "r1 = r10;" + "call %[bpf_iter_num_destroy];" + : + : __imm_ptr(iter), ITER_HELPERS + : __clobber_common + ); + + return 0; +} + SEC("?raw_tp") __failure __msg("Unreleased reference id=1") int create_and_forget_to_destroy_fail(void *ctx) diff --git a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c index 9e0bf7a54cec..0a8887543218 100644 --- a/tools/testing/selftests/bpf/progs/sock_destroy_prog.c +++ b/tools/testing/selftests/bpf/progs/sock_destroy_prog.c @@ -7,6 +7,8 @@ #include "bpf_tracing_net.h" __be16 serv_port = 0; +int tw_found = 0; +int tw_destroy_err = 0; int bpf_sock_destroy(struct sock_common *sk) __ksym; @@ -100,6 +102,34 @@ int iter_tcp6_server(struct bpf_iter__tcp *ctx) return 0; } +SEC("iter/tcp") +int iter_tcp6_timewait(struct bpf_iter__tcp *ctx) +{ + struct sock_common *sk_common = ctx->sk_common; + __u64 *val; + int key = 0; + + if (!sk_common) + return 0; + + if (sk_common->skc_family != AF_INET6) + return 0; + + if (!bpf_skc_to_tcp_timewait_sock(sk_common)) + return 0; + + val = bpf_map_lookup_elem(&tcp_conn_sockets, &key); + if (!val) + return 0; + /* The timewait sock inherits the cookie of the closed client sock. */ + if (bpf_get_socket_cookie(sk_common) != *val) + return 0; + + tw_found++; + tw_destroy_err = bpf_sock_destroy(sk_common); + + return 0; +} SEC("iter/udp") int iter_udp6_client(struct bpf_iter__udp *ctx) diff --git a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c index f678ee6bd7ea..55282f20fa32 100644 --- a/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c +++ b/tools/testing/selftests/bpf/progs/test_spin_lock_fail.c @@ -14,17 +14,18 @@ struct array_map { __type(key, int); __type(value, struct foo); __uint(max_entries, 1); -} array_map SEC(".maps"); +} array_map SEC(".maps"), array_map_b SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS); - __uint(max_entries, 1); + __uint(max_entries, 2); __type(key, int); __type(value, int); __array(values, struct array_map); } map_of_maps SEC(".maps") = { .values = { [0] = &array_map, + [1] = &array_map_b, }, }; @@ -314,4 +315,66 @@ int lock_global_sleepable_subprog_indirect(struct __sk_buff *ctx) return ret; } +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 2); + __type(key, int); + __type(value, struct foo); +} callback_array_map SEC(".maps"); + +struct callback_ctx { + struct foo *value; +}; + +static long lock_different_value(struct bpf_map *map, int *key, + struct foo *value, struct callback_ctx *ctx) +{ + bpf_spin_lock(&value->lock); + bpf_spin_unlock(&ctx->value->lock); + return 0; +} + +static long nest_lock_different_value(struct bpf_map *map, int *key, + struct foo *value, void *data) +{ + struct callback_ctx ctx = { .value = value }; + + bpf_for_each_map_elem(&callback_array_map, lock_different_value, &ctx, 0); + return 0; +} + +SEC("?tc") +int callback_value_lock_identity(void *ctx) +{ + bpf_for_each_map_elem(&callback_array_map, nest_lock_different_value, NULL, 0); + return 0; +} + +static long nest_lock_different_inner_value(struct bpf_map *map, int *key, + struct foo *value, void *data) +{ + struct callback_ctx ctx = { .value = value }; + int inner_key = 1; + void *inner_map; + + inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key); + if (!inner_map) + return 0; + bpf_for_each_map_elem(inner_map, lock_different_value, &ctx, 0); + return 0; +} + +SEC("?tc") +int callback_inner_map_value_lock_identity(void *ctx) +{ + int inner_key = 0; + void *inner_map; + + inner_map = bpf_map_lookup_elem(&map_of_maps, &inner_key); + if (!inner_map) + return 0; + bpf_for_each_map_elem(inner_map, nest_lock_different_inner_value, NULL, 0); + return 0; +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c new file mode 100644 index 000000000000..5c4c07545bc9 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_tc_change_tail_pmtu.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <stdbool.h> +#include <stddef.h> + +#include <linux/bpf.h> +#include <linux/icmp.h> +#include <linux/if_ether.h> +#include <linux/in.h> +#include <linux/ip.h> +#include <linux/tcp.h> + +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_endian.h> + +#define ICMP_SAMPLE_LEN (sizeof(struct iphdr) + 8) +#define ICMP_HDRS_LEN (sizeof(struct iphdr) + sizeof(struct icmphdr)) + +__be16 server_port = 0; +__u16 pmtu = 0; + +long change_tail_ret = 1; +long adjust_room_ret = 0; +bool icmp_sent = false; +bool icmp_err = false; + +static __always_inline __sum16 csum_fold(__wsum csum) +{ + csum = (csum & 0xffff) + (csum >> 16); + csum = (csum & 0xffff) + (csum >> 16); + + return (__sum16)~csum; +} + +SEC("tc/egress") +int change_tail_icmp(struct __sk_buff *skb) +{ + __u8 smac[ETH_ALEN], dmac[ETH_ALEN]; + void *data, *data_end; + struct icmphdr *icmp; + struct ethhdr *eth; + struct tcphdr *tcp; + __be32 saddr, daddr; + struct iphdr *ip; + __wsum csum; + + if (icmp_sent || icmp_err) + return TCX_PASS; + + data = (void *)(long)skb->data; + data_end = (void *)(long)skb->data_end; + + eth = data; + if ((void *)(eth + 1) > data_end) + return TCX_PASS; + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return TCX_PASS; + + ip = (void *)(eth + 1); + if ((void *)(ip + 1) > data_end) + return TCX_PASS; + if (ip->ihl != 5 || ip->protocol != IPPROTO_TCP) + return TCX_PASS; + + tcp = (void *)(ip + 1); + if ((void *)(tcp + 1) > data_end) + return TCX_PASS; + if (tcp->dest != server_port) + return TCX_PASS; + if (bpf_ntohs(ip->tot_len) <= sizeof(*ip) + tcp->doff * 4) + return TCX_PASS; + + __builtin_memcpy(smac, eth->h_source, ETH_ALEN); + __builtin_memcpy(dmac, eth->h_dest, ETH_ALEN); + saddr = ip->saddr; + daddr = ip->daddr; + + change_tail_ret = bpf_skb_change_tail(skb, ETH_HLEN + ICMP_SAMPLE_LEN, 0); + if (change_tail_ret) { + icmp_err = true; + return TCX_PASS; + } + + adjust_room_ret = bpf_skb_adjust_room(skb, ICMP_HDRS_LEN, + BPF_ADJ_ROOM_MAC, + BPF_F_ADJ_ROOM_NO_CSUM_RESET); + if (adjust_room_ret) { + icmp_err = true; + return TCX_DROP; + } + + data = (void *)(long)skb->data; + data_end = (void *)(long)skb->data_end; + + eth = data; + ip = (void *)(eth + 1); + icmp = (void *)(ip + 1); + if ((void *)icmp + sizeof(*icmp) + ICMP_SAMPLE_LEN > data_end) { + icmp_err = true; + return TCX_DROP; + } + + __builtin_memcpy(eth->h_dest, smac, ETH_ALEN); + __builtin_memcpy(eth->h_source, dmac, ETH_ALEN); + + __builtin_memset(icmp, 0, sizeof(*icmp)); + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_FRAG_NEEDED; + icmp->un.frag.mtu = bpf_htons(pmtu); + + __builtin_memset(ip, 0, sizeof(*ip)); + ip->version = 4; + ip->ihl = 5; + ip->ttl = 64; + ip->protocol = IPPROTO_ICMP; + ip->tot_len = bpf_htons(ICMP_HDRS_LEN + ICMP_SAMPLE_LEN); + ip->saddr = daddr; + ip->daddr = saddr; + + csum = bpf_csum_diff(NULL, 0, (__be32 *)icmp, + sizeof(*icmp) + ICMP_SAMPLE_LEN, 0); + icmp->checksum = csum_fold(csum); + csum = bpf_csum_diff(NULL, 0, (__be32 *)ip, sizeof(*ip), 0); + ip->check = csum_fold(csum); + icmp_sent = true; + return bpf_redirect(skb->ifindex, BPF_F_INGRESS); +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_arena.c b/tools/testing/selftests/bpf/progs/verifier_arena.c index 815f342eb4b0..3e33766547c0 100644 --- a/tools/testing/selftests/bpf/progs/verifier_arena.c +++ b/tools/testing/selftests/bpf/progs/verifier_arena.c @@ -562,6 +562,55 @@ int arena_ptr_add_arena_ptr(void *ctx) } SEC("syscall") +__failure __msg("same insn cannot be used with and without arena pointer") +int mixed_arena_scalar_alu64_scalar_first(void *ctx) +{ + volatile register __u64 reg asm("r3"); + __u32 pick_arena = bpf_get_prandom_u32(); + + reg = 1ULL << 32; + + if (pick_arena) { + asm volatile ( + "r9 = %[arena] ll;" + "%[reg] = 0;" + "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);" + : [reg] "=r"(reg) + : __imm_addr(arena) + : "r9" + ); + } + + reg += 1; + + return 0; +} + +SEC("syscall") +__failure __msg("same insn cannot be used with and without arena pointer") +int mixed_arena_scalar_alu64_arena_first(void *ctx) +{ + volatile register __u64 reg asm("r3"); + __u32 pick_scalar = bpf_get_prandom_u32(); + + asm volatile ( + "r9 = %[arena] ll;" + "%[reg] = 0;" + "%[reg] = addr_space_cast(%[reg], 0x0, 0x1);" + : [reg] "=r"(reg) + : __imm_addr(arena) + : "r9" + ); + + if (pick_scalar) + reg = 1ULL << 32; + + reg += 1; + + return 0; +} + +SEC("syscall") __success __retval(0) int scalar_xor_arena_ptr(void *ctx) { diff --git a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c index e0926767bbd3..1b653bfb63eb 100644 --- a/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c +++ b/tools/testing/selftests/bpf/progs/verifier_async_cb_context.c @@ -9,6 +9,11 @@ char _license[] SEC("license") = "GPL"; +struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym; +void bpf_task_release(struct task_struct *p) __ksym; +void bpf_rcu_read_lock(void) __ksym; +void bpf_rcu_read_unlock(void) __ksym; + /* Timer tests */ struct timer_elem { @@ -164,6 +169,7 @@ int syscall_btf_find_prog(void *ctx) struct wq_elem { struct bpf_wq w; + struct task_struct __kptr *task; }; struct { @@ -217,6 +223,106 @@ int wq_sleepable_prog(void *ctx) return 0; } +__noinline int wq_global_acquire(void) +{ + struct task_struct *task, *acquired; + struct wq_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + task = val->task; + if (!task) + return 0; + + acquired = bpf_task_acquire(task); + if (acquired) + bpf_task_release(acquired); + return 0; +} + +static int wq_global_rcu_cb(void *map, int *key, void *value) +{ + wq_global_acquire(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__failure __msg("R1 must be a rcu pointer") +int wq_global_rcu_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_rcu_cb, 0); + return 0; +} + +static int wq_global_rcu_lock_cb(void *map, int *key, void *value) +{ + bpf_rcu_read_lock(); + wq_global_acquire(); + bpf_rcu_read_unlock(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__success +int wq_global_rcu_lock_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + /* Verify the same global subprog in non-sleepable and protected contexts. */ + wq_global_acquire(); + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_rcu_lock_cb, 0); + return 0; +} + +__weak __noinline int wq_global_no_rcu(void) +{ + return 0; +} + +static int wq_global_no_rcu_cb(void *map, int *key, void *value) +{ + wq_global_no_rcu(); + return 0; +} + +SEC("fentry/bpf_fentry_test1") +__success __log_level(4) +__msg("subprog {{[0-9]+}} (wq_global_no_rcu) global insns_self 4 insns_total 4 stack 0") +int wq_global_no_rcu_prog(void *ctx) +{ + struct wq_elem *val; + int key = 0; + + /* Verify the same global in non-sleepable and unprotected contexts. */ + wq_global_no_rcu(); + + val = bpf_map_lookup_elem(&wq_map, &key); + if (!val) + return 0; + + bpf_wq_init(&val->w, &wq_map, 0); + bpf_wq_set_callback(&val->w, wq_global_no_rcu_cb, 0); + return 0; +} + /* Task work tests */ struct task_work_elem { diff --git a/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c new file mode 100644 index 000000000000..59b84261f622 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_btf_flex_array.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <vmlinux.h> +#include <bpf/bpf_helpers.h> + +#include "bpf_experimental.h" +#include "bpf_misc.h" + +struct test_empty_event {}; + +struct test_flex_batch { + int nr; + struct test_empty_event events[]; +}; + +struct map_value { + struct test_flex_batch __kptr *batch; +}; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, int); + __type(value, struct map_value); + __uint(max_entries, 1); +} batches SEC(".maps"); + +SEC("syscall") +__description("btf walk into flexible array of zero-sized elements") +__failure __msg("access beyond struct test_flex_batch at off 4 size 1") +int stash_and_peek(void *ctx) +{ + struct test_flex_batch *b, *old; + struct map_value *v; + int key = 0; + + v = bpf_map_lookup_elem(&batches, &key); + if (!v) + return 0; + + b = bpf_obj_new(struct test_flex_batch); + if (!b) + return 0; + b->nr = 1; + + old = bpf_kptr_xchg(&v->batch, b); + if (old) + bpf_obj_drop(old); + + b = v->batch; + if (!b) + return 0; + + return b->nr + *(char *)&b->events[0]; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c index a3d2af8dc839..dcc2dd46751a 100644 --- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c +++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c @@ -350,4 +350,57 @@ int anything_to_untrusted_mem(void *ctx) return 0; } +struct pkt_arg { + __u64 x; + __u8 pad[56]; +}; + +__weak int subprog_pkt_ptr_no_change(struct pkt_arg *p) +{ + if (!p) + return 0; + + return p->x; +} + +SEC("?tc") +__success +int pkt_ptr_to_global_mem_arg_no_change(struct __sk_buff *skb) +{ + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + struct pkt_arg *p = data; + + if ((void *)(p + 1) > data_end) + return 0; + + return subprog_pkt_ptr_no_change(p); +} + +__weak int subprog_pkt_ptr_changes_data(struct __sk_buff *skb __arg_ctx, + struct pkt_arg *p) +{ + if (!p) + return 0; + + bpf_skb_pull_data(skb, 0); + return p->x; +} + +SEC("?tc") +__failure __log_level(2) +__msg("R2 is a packet pointer, but func#{{[0-9]+}} may change packet data") +__msg("Caller passes invalid args into func#{{[0-9]+}} ('subprog_pkt_ptr_changes_data')") +int pkt_ptr_to_global_mem_arg_changes_data(struct __sk_buff *skb) +{ + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + struct pkt_arg *p = data; + + if ((void *)(p + 1) > data_end) + return 0; + + return subprog_pkt_ptr_changes_data(skb, p); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_gotox.c b/tools/testing/selftests/bpf/progs/verifier_gotox.c index 5b18c9a27717..0e27c2c79c57 100644 --- a/tools/testing/selftests/bpf/progs/verifier_gotox.c +++ b/tools/testing/selftests/bpf/progs/verifier_gotox.c @@ -47,6 +47,54 @@ DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_src_reg, BPF_REG_1, 0, 0, __fa DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_off, BPF_REG_0, 1, 0, __failure __msg("BPF_JA|BPF_X uses reserved fields")) DEFINE_SIMPLE_JUMP_TABLE_PROG(reserved_field_non_zero_imm, BPF_REG_0, 0, 1, __failure __msg("BPF_JA|BPF_X uses reserved fields")) +#define DEFINE_TERMINAL_GOTOX_PROG(NAME, BASE) \ + __naked void NAME(void) \ + { \ + asm volatile (" \ + .pushsection .jumptables,\"\",@progbits; \ +jt0_%=: \ + .quad ret0_%= - " BASE "; \ + .size jt0_%=, 8; \ + .global jt0_%=; \ + .popsection; \ + \ + r0 = jt0_%= ll; \ + r0 = *(u64 *)(r0 + 0); \ + goto end_%=; \ +ret0_%=: \ + r0 = 0; \ + exit; \ +end_%=: \ + .8byte %[gotox_r0]; \ +" : \ + : __imm_insn(gotox_r0, BPF_RAW_INSN(BPF_JMP | BPF_JA | BPF_X, \ + BPF_REG_0, 0, 0, 0)) \ + : __clobber_all); \ + } + +SEC("socket") +__success __retval(0) +DEFINE_TERMINAL_GOTOX_PROG(jump_table_terminal_gotox, "socket") + +static __noinline __used +DEFINE_TERMINAL_GOTOX_PROG(terminal_gotox_subprog1, ".text") + +static __noinline __used int terminal_gotox_subprog2(void) +{ + return 0; +} + +SEC("socket") +__success __retval(0) +__naked void jump_table_terminal_gotox_subprog(void) +{ + asm volatile (" \ + call terminal_gotox_subprog1; \ + call terminal_gotox_subprog2; \ + exit; \ +" ::: __clobber_all); +} + /* * Gotox is forbidden when there is no jump table loaded * which points to the sub-function where the gotox is used diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c new file mode 100644 index 000000000000..76c39ef30e96 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_perfmon.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <vmlinux.h> +#include <bpf/bpf_helpers.h> +#include "bpf_misc.h" + +void *user_ptr; +char dynptr_buf[8]; +u64 kaddr; + +extern struct kmem_cache *bpf_get_kmem_cache(u64 addr) __ksym; + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_rdonly_cast is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int rdonly_cast_noperfmon(void *ctx) +{ + char *p = bpf_rdonly_cast(0, 0); + + return p[0x7fff]; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_probe_read_kernel_dynptr is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int probe_read_kernel_dynptr_noperfmon(void *ctx) +{ + struct bpf_dynptr dptr; + + bpf_dynptr_from_mem(dynptr_buf, sizeof(dynptr_buf), 0, &dptr); + bpf_probe_read_kernel_dynptr(&dptr, 0, sizeof(dynptr_buf), user_ptr); + return 0; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_stream_vprintk is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int stream_vprintk_noperfmon(void *ctx) +{ + bpf_stream_printk(BPF_STDOUT, "%pB", (void *)kaddr); + return 0; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("bpf_get_kmem_cache is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int get_kmem_cache_noperfmon(void *ctx) +{ + return !!bpf_get_kmem_cache(kaddr); +} + +__weak int subprog_untrusted_read(void *p __arg_untrusted) +{ + return *(char *)p; +} + +SEC("socket") +__success +__caps_unpriv(CAP_BPF) +__failure_unpriv +__msg_unpriv("rdonly_untrusted_mem access is allowed only to CAP_PERFMON and CAP_SYS_ADMIN") +int arg_untrusted_read_noperfmon(void *ctx) +{ + return subprog_untrusted_read(0); +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_loops1.c b/tools/testing/selftests/bpf/progs/verifier_loops1.c index d248ce877f14..48a966cda199 100644 --- a/tools/testing/selftests/bpf/progs/verifier_loops1.c +++ b/tools/testing/selftests/bpf/progs/verifier_loops1.c @@ -303,4 +303,40 @@ __naked void maybe_exit_scc_bug1(void) ::: __clobber_all); } +/* + * The loop reads zero from the caller's stack on its first iteration and + * one from the callee's stack on its second iteration. At the loop header, + * only the frame number of the pointer in r1 changes. + */ +static __naked __noinline __used +void loop_stack_frames_reg(void) +{ + asm volatile ( + "*(u64 *)(r10 - 8) = 1;" +"1:" + "r0 = *(u64 *)(r1 + 0);" + "if r0 != 0 goto 2f;" + "r1 = r10;" + "r1 += -8;" + "goto 1b;" +"2:" + "exit;" + ::: __clobber_all); +} + +SEC("xdp") +__description("bounded loop changing stack frame in a register") +__success __retval(1) +__flag(BPF_F_TEST_STATE_FREQ) +__naked void bounded_loop_stack_frames_reg(void) +{ + asm volatile ( + "*(u64 *)(r10 - 8) = 0;" + "r1 = r10;" + "r1 += -8;" + "call loop_stack_frames_reg;" + "exit;" + ::: __clobber_all); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_sock.c b/tools/testing/selftests/bpf/progs/verifier_sock.c index 4f2f3209eec8..bf9f6fb6582c 100644 --- a/tools/testing/selftests/bpf/progs/verifier_sock.c +++ b/tools/testing/selftests/bpf/progs/verifier_sock.c @@ -88,6 +88,44 @@ l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_family]); \ : __clobber_all); } +SEC("socket") +__description("skb->sk: sk->rx_queue_mapping [no sign extension]") +__success __success_unpriv __retval(0) +__naked void sk_rx_queue_mapping_no_sign_ext(void) +{ + asm volatile (" \ + r1 = *(u64*)(r1 + %[__sk_buff_sk]); \ + if r1 != 0 goto l0_%=; \ + r0 = 0xdead; \ + exit; \ +l0_%=: r0 = *(u32*)(r1 + %[bpf_sock_rx_queue_mapping]); \ + r0 >>= 32; \ + exit; \ +" : + : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)), + __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping)) + : __clobber_all); +} + +SEC("socket") +__description("skb->sk: sk->rx_queue_mapping [narrow load mask]") +__success __success_unpriv __retval(0) +__naked void sk_rx_queue_mapping_narrow_load_mask(void) +{ + asm volatile (" \ + r1 = *(u64*)(r1 + %[__sk_buff_sk]); \ + if r1 != 0 goto l0_%=; \ + r0 = 0xdead; \ + exit; \ +l0_%=: r0 = *(u16*)(r1 + %[bpf_sock_rx_queue_mapping]); \ + r0 >>= 16; \ + exit; \ +" : + : __imm_const(__sk_buff_sk, offsetof(struct __sk_buff, sk)), + __imm_const(bpf_sock_rx_queue_mapping, offsetof(struct bpf_sock, rx_queue_mapping)) + : __clobber_all); +} + SEC("cgroup/skb") __description("skb->sk: sk->type [fullsock field]") __failure __msg("invalid sock_common access") diff --git a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c index 0b86d95a4133..9866bc154194 100644 --- a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c +++ b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c @@ -1719,4 +1719,39 @@ l0_%=: r0 = 0; \ : __clobber_all); } +SEC("xdp") +__description("XDP pkt regsafe preserves packet pointer class displacement") +__failure __msg("R2 min value is outside of the allowed memory range") +__flag(BPF_F_ANY_ALIGNMENT) __flag(BPF_F_TEST_STATE_FREQ) +__naked void pkt_regsafe_class_displacement(void) +{ + asm volatile (" \ + r8 = *(u32 *)(r1 + %[xdp_md_data_end]); \ + r9 = *(u32 *)(r1 + %[xdp_md_data]); \ + r4 = *(u32 *)(r1 + %[xdp_md_rx_queue_index]); \ + r4 &= 15; \ + r0 = *(u32 *)(r1 + %[xdp_md_ingress_ifindex]); \ + if r0 != 0 goto l0_%=; \ + r2 = r9; \ + r2 += r4; \ + r3 = r2; \ + r3 += 8; \ + goto l1_%=; \ +l0_%=: r4 &= 3; \ + r4 += 8; \ + r2 = r9; \ + r2 += r4; \ + r3 = r2; \ +l1_%=: if r3 > r8 goto l2_%=; \ + r0 = *(u64 *)(r2 + 0); \ +l2_%=: r0 = 0; \ + exit; \ +" : + : __imm_const(xdp_md_data, offsetof(struct xdp_md, data)), + __imm_const(xdp_md_data_end, offsetof(struct xdp_md, data_end)), + __imm_const(xdp_md_rx_queue_index, offsetof(struct xdp_md, rx_queue_index)), + __imm_const(xdp_md_ingress_ifindex, offsetof(struct xdp_md, ingress_ifindex)) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index 131d8b4551ef..7efd5e645767 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -298,6 +298,8 @@ TEST_MATRIX=( " C0-4:X2-4 C1-4:X2-4:P2 C2-4:X4:P1 \ . . . X1 . 0 A1:0-1|A2:2-4|A3:2-4 \ A1:P0|A2:P2|A3:P-1 2-4" + " CX1-3:P1 CX1-3 CX1-3 . . . P1 . 0 A1:1-3|A2:1-3|A3:1-3 \ + A1:P1|A2:P0|A3:P-1" # Remote partition offline tests " C0-3 C1-3 C2-3 . X2-3 X2-3 X2-3:P2:O2=0 . 0 A1:0-1|A2:1|A3:3 A1:P0|A3:P2 2-3" diff --git a/tools/testing/selftests/cgroup/test_memcontrol.c b/tools/testing/selftests/cgroup/test_memcontrol.c index 3a84d068fbf3..0ed82347044e 100644 --- a/tools/testing/selftests/cgroup/test_memcontrol.c +++ b/tools/testing/selftests/cgroup/test_memcontrol.c @@ -30,7 +30,7 @@ static int page_size; int get_temp_fd(void) { - return open(".", O_TMPFILE | O_RDWR | O_EXCL); + return open(".", O_TMPFILE | O_RDWR | O_EXCL, 0600); } int alloc_pagecache(int fd, size_t size) diff --git a/tools/testing/selftests/cgroup/test_zswap.c b/tools/testing/selftests/cgroup/test_zswap.c index 609c48f38524..8df54b59513a 100644 --- a/tools/testing/selftests/cgroup/test_zswap.c +++ b/tools/testing/selftests/cgroup/test_zswap.c @@ -20,6 +20,7 @@ static int page_size; #define PATH_ZSWAP "/sys/module/zswap" #define PATH_ZSWAP_ENABLED "/sys/module/zswap/parameters/enabled" +#define PATH_ZSWAP_SHRINKER_ENABLED "/sys/module/zswap/parameters/shrinker_enabled" #define PATH_ZSWAP_STORED_PAGES "/sys/kernel/debug/zswap/stored_pages" static int read_int(const char *path, size_t *value) @@ -444,6 +445,16 @@ static int test_zswap_writeback_disabled(const char *root) return test_zswap_writeback(root, false); } +static bool zswap_shrinker_enabled(void) +{ + char value[2]; + + if (read_text(PATH_ZSWAP_SHRINKER_ENABLED, value, sizeof(value)) <= 0) + return 0; + + return value[0] == 'Y'; +} + /* * When trying to store a memcg page in zswap, if the memcg hits its memory * limit in zswap, writeback should affect only the zswapped pages of that @@ -453,6 +464,7 @@ static int test_no_invasive_cgroup_shrink(const char *root) { int ret = KSFT_FAIL; unsigned int off; + long zswpwb_before, zswpwb_after, zswpwb_target; size_t allocation_size = page_size * 1024; unsigned int nr_pages = allocation_size / page_size; char zswap_max_buf[32], mem_max_buf[32]; @@ -488,6 +500,14 @@ static int test_no_invasive_cgroup_shrink(const char *root) if (cg_read_key_long(zw_group, "memory.stat", "zswapped") < 1) goto out; + /* If the shrinker is enabled, try to let the writebacks finish first */ + if (zswap_shrinker_enabled()) + sleep(5); + + zswpwb_before = get_cg_wb_count(zw_group); + if (zswpwb_before < 0) + goto out; + /* Push wb_group memory into zswap with hard-to-compress data to trigger wb */ if (cg_enter_current(wb_group)) goto out; @@ -500,9 +520,13 @@ static int test_no_invasive_cgroup_shrink(const char *root) getrandom(&wb_allocation[off], page_size/4, 0); } - /* Verify that only zswapped memory from gwb_group has been written back */ - if (wait_for_writeback(wb_group, 5000) > 0 && get_cg_wb_count(zw_group) == 0) + /* Verify that only zswapped memory from wb_group has been written back */ + zswpwb_target = wait_for_writeback(wb_group, 5000); + zswpwb_after = get_cg_wb_count(zw_group); + + if (zswpwb_target > 0 && zswpwb_before == zswpwb_after) ret = KSFT_PASS; + out: cg_enter_current(root); if (zw_group) { diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore b/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore deleted file mode 100644 index 493fbcf8d9ec..000000000000 --- a/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -mntns_cleanup_test diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/Makefile b/tools/testing/selftests/filesystems/mntns_cleanup/Makefile deleted file mode 100644 index 0e09e7030a5c..000000000000 --- a/tools/testing/selftests/filesystems/mntns_cleanup/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -TEST_GEN_PROGS := mntns_cleanup_test - -CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) - -include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c b/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c deleted file mode 100644 index 5209712568b1..000000000000 --- a/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#define _GNU_SOURCE -#include <errno.h> -#include <fcntl.h> -#include <sched.h> -#include <sys/mount.h> -#include <sys/stat.h> -#include <unistd.h> - -#include "../../kselftest_harness.h" - -FIXTURE(mntns_cleanup) { -}; - -FIXTURE_SETUP(mntns_cleanup) -{ - if (geteuid() != 0) - SKIP(return, "test requires CAP_SYS_ADMIN"); - - ASSERT_EQ(unshare(CLONE_NEWNS), 0); - ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); - - rmdir("/mnt_dir"); - ASSERT_EQ(mkdir("/mnt_dir", 0755), 0); - ASSERT_EQ(mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL), 0); - ASSERT_EQ(mkdir("/mnt_dir/hidden", 0755), 0); - ASSERT_EQ(mkdir("/mnt_dir/hidden/secret", 0755), 0); - ASSERT_EQ(mount("tmpfs", "/mnt_dir/hidden", "tmpfs", 0, NULL), 0); -} - -FIXTURE_TEARDOWN(mntns_cleanup) -{ -} - -/* Mounts must stay connected when a mount namespace is cleaned up. */ -TEST_F(mntns_cleanup, keeps_mounts_connected) -{ - int fd, sfd, err; - - fd = open("/mnt_dir", O_PATH | O_DIRECTORY | O_CLOEXEC); - ASSERT_GE(fd, 0); - - /* Destroy the namespace; the fd keeps /mnt_dir alive. */ - ASSERT_EQ(unshare(CLONE_NEWNS), 0); - - sfd = openat(fd, "hidden/secret", O_RDONLY); - err = errno; - if (sfd >= 0) - close(sfd); - close(fd); - - ASSERT_LT(sfd, 0) - TH_LOG("mount namespace teardown revealed what the overmount covered"); - ASSERT_EQ(err, ENOENT); -} - -TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc index bc9514428dba..07b1177c1634 100644 --- a/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc +++ b/tools/testing/selftests/ftrace/test.d/kprobe/kprobe_non_uniq_symbol.tc @@ -6,7 +6,7 @@ SYMBOL='name_show' # We skip this test on kernel where SYMBOL is unique or does not exist. -if [ "$(grep -c -E "[[:alnum:]]+ t ${SYMBOL}" /proc/kallsyms)" -le '1' ]; then +if [ "$(grep -c -E "[[:alnum:]]+ t ${SYMBOL}$" /proc/kallsyms)" -le '1' ]; then exit_unsupported fi diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 7ab86296ff23..32d81ba15a25 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -5,7 +5,7 @@ #include <bpf/bpf.h> struct hid_hw_request_syscall_args { - __u8 data[10]; + __u8 data[MAX_BUF_SIZE]; unsigned int hid; int retval; size_t size; @@ -54,11 +54,27 @@ FIXTURE_TEARDOWN(hid_bpf) { hid_bpf_teardown(_metadata, self, variant); \ } while (0) +FIXTURE_VARIANT(hid_bpf) { + __u8 *rdesc; + size_t rdesc_size; +}; + +FIXTURE_VARIANT_ADD(hid_bpf, numbered) { + .rdesc = rdesc, + .rdesc_size = sizeof(rdesc), +}; + +FIXTURE_VARIANT_ADD(hid_bpf, unnumbered) { + .rdesc = fido2_rdesc, + .rdesc_size = sizeof(fido2_rdesc), +}; + FIXTURE_SETUP(hid_bpf) { int err; - err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, rdesc, sizeof(rdesc)); + err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, + variant->rdesc, variant->rdesc_size); ASSERT_OK(err); } @@ -175,7 +191,7 @@ TEST_F(hid_bpf, raw_event) const struct test_program progs[] = { { .name = "hid_first_event" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -226,7 +242,7 @@ TEST_F(hid_bpf, subprog_raw_event) const struct test_program progs[] = { { .name = "hid_subprog_first_event" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -284,7 +300,7 @@ TEST_F(hid_bpf, test_attach_detach) { .name = "hid_second_event" }, }; struct bpf_link *link; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err, link_fd; LOAD_PROGRAMS(progs); @@ -369,7 +385,7 @@ TEST_F(hid_bpf, test_hid_change_report) const struct test_program progs[] = { { .name = "hid_change_report_id" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -396,21 +412,24 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) { struct hid_hw_request_syscall_args args = { .retval = -1, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, .ctx_size_in = sizeof(args), ); - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err, prog_fd; LOAD_BPF; args.hid = self->hid.hid_id; args.data[0] = 1; /* report ID */ - args.data[1] = 2; /* report ID */ - args.data[2] = 42; /* report ID */ + args.data[1] = 2; + args.data[2] = 42; + + if (variant->rdesc == fido2_rdesc) + args.data[0] = 0; prog_fd = bpf_program__fd(self->skel->progs.hid_user_input_report); @@ -428,8 +447,13 @@ TEST_F(hid_bpf, test_hid_user_input_report_call) /* read the data from hidraw */ memset(buf, 0, sizeof(buf)); err = read(self->hidraw_fd, buf, sizeof(buf)); - ASSERT_EQ(err, 6) TH_LOG("read_hidraw"); - ASSERT_EQ(buf[0], 1); + if (variant->rdesc == rdesc) { + ASSERT_EQ(err, 6) TH_LOG("read_hidraw"); + } else { + ASSERT_EQ(err, 64) + TH_LOG("read_hidraw"); + } + ASSERT_EQ(buf[0], args.data[0]); ASSERT_EQ(buf[1], 2); ASSERT_EQ(buf[2], 42); } @@ -442,7 +466,7 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) { struct hid_hw_request_syscall_args args = { .retval = -1, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, @@ -455,8 +479,11 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) args.hid = self->hid.hid_id; args.data[0] = 1; /* report ID */ - args.data[1] = 2; /* report ID */ - args.data[2] = 42; /* report ID */ + args.data[1] = 2; + args.data[2] = 42; + + if (variant->rdesc == fido2_rdesc) + args.data[0] = 0; prog_fd = bpf_program__fd(self->skel->progs.hid_user_output_report); @@ -472,9 +499,14 @@ TEST_F(hid_bpf, test_hid_user_output_report_call) ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts"); ASSERT_OK(cond_err) TH_LOG("error while calling waiting for the condition"); - ASSERT_EQ(args.retval, 3); + if (variant->rdesc == rdesc) { + ASSERT_EQ(args.retval, 3); + } else if (variant->rdesc == fido2_rdesc) { + ASSERT_EQ(args.retval, 65) + TH_LOG("report size error, should have 64 + 1 extra byte for the report ID 0"); + } - ASSERT_EQ(output_report[0], 1); + ASSERT_EQ(output_report[0], args.data[0]); ASSERT_EQ(output_report[1], 2); ASSERT_EQ(output_report[2], 42); @@ -491,7 +523,7 @@ TEST_F(hid_bpf, test_hid_user_raw_request_call) .retval = -1, .type = HID_FEATURE_REPORT, .request_type = HID_REQ_GET_REPORT, - .size = 10, + .size = MAX_BUF_SIZE, }; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, .ctx_in = &args, @@ -524,7 +556,7 @@ TEST_F(hid_bpf, test_hid_filter_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_filter_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -577,7 +609,7 @@ TEST_F(hid_bpf, test_hid_change_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_hidraw_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -603,7 +635,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_raw_request_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_raw_request" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -626,7 +658,7 @@ TEST_F(hid_bpf, test_hid_filter_output_report_call) const struct test_program progs[] = { { .name = "hid_test_filter_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -679,7 +711,7 @@ TEST_F(hid_bpf, test_hid_change_output_report_call) const struct test_program progs[] = { { .name = "hid_test_hidraw_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -703,7 +735,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_output_report_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_output_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -729,7 +761,7 @@ TEST_F(hid_bpf, test_multiply_events_wq) const struct test_program progs[] = { { .name = "hid_test_multiply_events_wq" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -767,7 +799,7 @@ TEST_F(hid_bpf, test_multiply_events) const struct test_program progs[] = { { .name = "hid_test_multiply_events" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -801,7 +833,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_input_report_call) const struct test_program progs[] = { { .name = "hid_test_infinite_loop_input_report" }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -855,7 +887,7 @@ TEST_F(hid_bpf, test_hid_attach_flags) .insert_head = 0, }, }; - __u8 buf[10] = {0}; + __u8 buf[MAX_BUF_SIZE] = {0}; int err; LOAD_PROGRAMS(progs); @@ -886,6 +918,9 @@ TEST_F(hid_bpf, test_rdesc_fixup) }; int err, desc_size; + if (variant->rdesc != rdesc) + SKIP(return, "not compatible report descriptor"); + LOAD_PROGRAMS(progs); /* check that hid_rdesc_fixup() was executed */ diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selftests/hid/hid_common.h index e3b267446fa0..b7890ba2878f 100644 --- a/tools/testing/selftests/hid/hid_common.h +++ b/tools/testing/selftests/hid/hid_common.h @@ -13,6 +13,7 @@ #include <linux/uhid.h> #define SHOW_UHID_DEBUG 0 +#define MAX_BUF_SIZE 128 #define min(a, b) \ ({ __typeof__(a) _a = (a); \ @@ -97,6 +98,28 @@ static unsigned char rdesc[] = { static __u8 feature_data[] = { 1, 2 }; +static __maybe_unused unsigned char fido2_rdesc[] = { + 0x06, 0xd0, 0xf1, /* Usage Page (FIDO Alliance) */ + 0x09, 0x01, /* Usage (U2F Authenticator Device) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x09, 0x20, /* Usage (Input Report Data) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (64) */ + 0x81, 0x02, /* Input (Data,Var,Abs) */ + 0x09, 0x21, /* Usage (Output Report Data) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x40, /* Report Count (64) */ + 0x91, 0x02, /* Output (Data,Var,Abs) */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x09, 0x22, /* Usage (Vendor Usage 0x22) */ + 0xb1, 0x02, /* Feature (Data,Var,Abs) */ + 0xc0, /* End Collection */ +}; + #define ASSERT_OK(data) ASSERT_FALSE(data) #define ASSERT_OK_PTR(ptr) ASSERT_NE(NULL, ptr) @@ -110,7 +133,7 @@ static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER; static pthread_mutex_t uhid_output_mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t uhid_output_cond = PTHREAD_COND_INITIALIZER; -static unsigned char output_report[10]; +static unsigned char output_report[MAX_BUF_SIZE]; /* no need to protect uhid_stopped, only one thread accesses it */ static bool uhid_stopped; diff --git a/tools/testing/selftests/hid/progs/hid.c b/tools/testing/selftests/hid/progs/hid.c index 361dc7eaad22..48aa8088cc53 100644 --- a/tools/testing/selftests/hid/progs/hid.c +++ b/tools/testing/selftests/hid/progs/hid.c @@ -98,7 +98,7 @@ struct hid_bpf_ops change_report_id = { struct hid_hw_request_syscall_args { /* data needs to come at offset 0 so we can use it in calls */ - __u8 data[10]; + __u8 data[128]; unsigned int hid; int retval; size_t size; diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 96bab7002d39..6a1482e3a286 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -189,6 +189,7 @@ TEST_GEN_PROGS_arm64 += arm64/stage2_block_transitions TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config TEST_GEN_PROGS_arm64 += arm64/vgic_init TEST_GEN_PROGS_arm64 += arm64/vgic_irq +TEST_GEN_PROGS_arm64 += arm64/vgic_its_save TEST_GEN_PROGS_arm64 += arm64/vgic_lpi_stress TEST_GEN_PROGS_arm64 += arm64/vgic_v5 TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access diff --git a/tools/testing/selftests/kvm/arm64/smccc_filter.c b/tools/testing/selftests/kvm/arm64/smccc_filter.c index 21e41880261b..a41ed3e016ba 100644 --- a/tools/testing/selftests/kvm/arm64/smccc_filter.c +++ b/tools/testing/selftests/kvm/arm64/smccc_filter.c @@ -140,6 +140,10 @@ static void test_invalid_nr_functions(void) TEST_ASSERT(r < 0 && errno == EINVAL, "Attempt to filter 0 functions should return EINVAL"); + r = __set_smccc_filter(vm, 0, 0, KVM_SMCCC_FILTER_DENY); + TEST_ASSERT(r < 0 && errno == EINVAL, + "Attempt to filter 0 functions at base 0 should return EINVAL"); + kvm_vm_free(vm); } diff --git a/tools/testing/selftests/kvm/arm64/vgic_its_save.c b/tools/testing/selftests/kvm/arm64/vgic_its_save.c new file mode 100644 index 000000000000..864da01539f3 --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/vgic_its_save.c @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * vgic_its_save - KVM_DEV_ARM_ITS_SAVE_TABLES against tables a guest broke. + * + * Both cases are reachable by a guest on its own, and neither may fail a save + * that userspace has to be able to issue: + * + * - Changing GITS_BASER<coll> drops the collections it described, so the save + * writes nothing but the terminating invalid entry. + * - A device the device table can no longer address is skipped, and the saved + * DTE chain skips it too rather than pointing at an entry never written. + * + * Both cases then reset and restore, which is what the save exists for. + * + * Copyright (c) 2026 Google LLC + * Author: Fuad Tabba <fuad.tabba@linux.dev> + */ + +#include <endian.h> +#include <linux/align.h> +#include <linux/bitfield.h> +#include <linux/sizes.h> + +#include "kvm_util.h" +#include "gic.h" +#include "gic_v3.h" +#include "gic_v3_its.h" +#include "processor.h" +#include "ucall.h" +#include "vgic.h" + +#define TEST_MEMSLOT_INDEX 1 + +/* All three ITS table entry sizes are 8 bytes in ABI 0. */ +#define ESZ 8 +#define ENTRIES_PER_PAGE (SZ_64K / ESZ) + +/* CTE and DTE layout, mirroring KVM's KVM_ITS_* in arch/arm64/kvm/vgic/vgic.h */ +#define CTE_VALID_MASK BIT_ULL(63) +#define DTE_VALID_MASK BIT_ULL(63) +#define DTE_NEXT_SHIFT 49 +#define DTE_NEXT_MASK GENMASK_ULL(62, 49) + +/* L1 entry of an indirect table: valid bit plus a 64K aligned L2 address. */ +#define L1E_VALID_MASK BIT_ULL(63) +#define L1E_ADDR_MASK GENMASK_ULL(51, 16) + +#define GITS_BASER_PAGES_MASK GENMASK_ULL(7, 0) + +#define POISON 0xdeadbeefdeadbeefULL + +/* The collection table starts at two pages and is shrunk to one. */ +#define COLL_TBL_PAGES 2 +#define COLL_TBL_SZ (COLL_TBL_PAGES * SZ_64K) + +/* One more collection than the shrunken table can hold. */ +#define NR_COLLECTIONS (ENTRIES_PER_PAGE + 1) + +/* Two devices, one per L2 block of the indirect device table. */ +#define DEVICE_A_ID 0 +#define DEVICE_B_ID ENTRIES_PER_PAGE + +/* + * its_send_mapd_cmd() encodes ilog2(itt_size) - 1 as num_eventid_bits, and + * vgic_its_restore_itt() scans BIT_ULL(num_eventid_bits) * ESZ, so the size + * handed to MAPD has to match the ITT allocated for it. + */ +#define ITT_EVENTID_BITS 13 +#define ITT_MAPD_SIZE BIT_ULL(ITT_EVENTID_BITS + 1) +#define ITT_SZ (BIT_ULL(ITT_EVENTID_BITS) * ESZ) + +static struct kvm_vm *vm; +static struct kvm_vcpu *vcpu; +static int its_fd; +static gpa_t gpa_base; + +static struct test_data { + gpa_t device_table; + gpa_t collection_table; + gpa_t cmdq_base; + void *cmdq_base_va; + + gpa_t lpi_prop_table; + gpa_t lpi_pend_table; + + void *device_l1_va; + gpa_t device_l2[2]; + gpa_t itt_tables; +} test_data; + +static unsigned long its_baser_offset(unsigned int type) +{ + int i; + + for (i = 0; i < GITS_BASER_NR_REGS; i++) { + unsigned long offset = GITS_BASER + (i * sizeof(u64)); + u64 baser = readq_relaxed(GITS_BASE_GVA + offset); + + if (GITS_BASER_TYPE(baser) == type) + return offset; + } + + GUEST_FAIL("Couldn't find an ITS BASER of type %u", type); + return -1; +} + +static void its_set_enable(bool enable) +{ + u32 ctlr = readl_relaxed(GITS_BASE_GVA + GITS_CTLR); + + if (enable) + ctlr |= GITS_CTLR_ENABLE; + else + ctlr &= ~GITS_CTLR_ENABLE; + + writel_relaxed(ctlr, GITS_BASE_GVA + GITS_CTLR); +} + +/* + * Shrink the collection table to a single page, leaving VALID set. BASER + * writes are ignored while the ITS is enabled. + */ +static void guest_shrink_coll_table(void) +{ + unsigned long offset = its_baser_offset(GITS_BASER_TYPE_COLLECTION); + u64 baser; + + its_set_enable(false); + + baser = readq_relaxed(GITS_BASE_GVA + offset); + baser &= ~GITS_BASER_PAGES_MASK; + writeq_relaxed(baser, GITS_BASE_GVA + offset); +} + +static void guest_baser_change(void) +{ + u32 coll_id; + + gic_init(GIC_V3, 1); + gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K, + test_data.lpi_pend_table); + + its_init(test_data.collection_table, COLL_TBL_SZ, + test_data.device_table, SZ_64K, + test_data.cmdq_base, SZ_64K); + + for (coll_id = 0; coll_id < NR_COLLECTIONS; coll_id++) + its_send_mapc_cmd(test_data.cmdq_base_va, 0, coll_id, true); + + guest_shrink_coll_table(); + + GUEST_DONE(); +} + +/* Turn the already installed device table into an indirect one. */ +static void guest_make_device_table_indirect(void) +{ + unsigned long offset = its_baser_offset(GITS_BASER_TYPE_DEVICE); + u64 baser; + + its_set_enable(false); + + baser = readq_relaxed(GITS_BASE_GVA + offset); + writeq_relaxed(baser | GITS_BASER_INDIRECT, GITS_BASE_GVA + offset); + + its_set_enable(true); +} + +static void guest_unreachable_device(void) +{ + u64 *l1; + + gic_init(GIC_V3, 1); + gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K, + test_data.lpi_pend_table); + + its_init(test_data.collection_table, SZ_64K, + test_data.device_table, SZ_64K, + test_data.cmdq_base, SZ_64K); + + guest_make_device_table_indirect(); + + /* Both L2 blocks present, so both MAPDs are in range. */ + l1 = test_data.device_l1_va; + l1[0] = L1E_VALID_MASK | (test_data.device_l2[0] & L1E_ADDR_MASK); + l1[1] = L1E_VALID_MASK | (test_data.device_l2[1] & L1E_ADDR_MASK); + + its_send_mapd_cmd(test_data.cmdq_base_va, DEVICE_A_ID, + test_data.itt_tables, ITT_MAPD_SIZE, true); + its_send_mapd_cmd(test_data.cmdq_base_va, DEVICE_B_ID, + test_data.itt_tables + ITT_SZ, ITT_MAPD_SIZE, true); + + /* + * Drop the block holding device B. No ITS command and no GITS_BASER + * write is involved, so nothing tells KVM the device is now + * unreachable. + */ + l1[1] = 0; + + GUEST_DONE(); +} + +static void run_guest(void) +{ + struct ucall uc; + + vcpu_run(vcpu); + switch (get_ucall(vcpu, &uc)) { + case UCALL_DONE: + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + default: + TEST_FAIL("Unexpected ucall: %lu", uc.cmd); + } +} + +static int save_tables(void) +{ + return __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_ITS_SAVE_TABLES, NULL); +} + +static u64 its_reg_get(unsigned long offset) +{ + u64 val; + + kvm_device_attr_get(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, offset, + &val); + return val; +} + +static void its_reg_set(unsigned long offset, u64 val) +{ + kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, offset, + &val); +} + +/* + * What a migration target does with the saved tables, in the order + * Documentation/virt/kvm/devices/arm-vgic-its.rst gives: the GITS_ registers + * first, then the tables. The reset in between clears GITS_BASER<n>.Valid, + * which is why the registers have to be written back before the restore. + */ +static void reset_and_restore_tables(void) +{ + u64 baser[GITS_BASER_NR_REGS]; + int ret, i; + + for (i = 0; i < GITS_BASER_NR_REGS; i++) + baser[i] = its_reg_get(GITS_BASER + (i * sizeof(u64))); + + ret = __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_ITS_CTRL_RESET, NULL); + TEST_ASSERT(!ret, "Expected the reset to succeed, got ret %d errno %d", + ret, errno); + + for (i = 0; i < GITS_BASER_NR_REGS; i++) + its_reg_set(GITS_BASER + (i * sizeof(u64)), baser[i]); + + ret = __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, + KVM_DEV_ARM_ITS_RESTORE_TABLES, NULL); + TEST_ASSERT(!ret, "Expected the restore to succeed, got ret %d errno %d", + ret, errno); +} + +static void poison_range(gpa_t base, size_t size) +{ + u64 *entry = addr_gpa2hva(vm, base); + size_t i; + + for (i = 0; i < size / ESZ; i++) + entry[i] = POISON; +} + +static void setup_memslot(size_t sz) +{ + size_t pages = sz / vm->page_size; + + gpa_base = ((vm_compute_max_gfn(vm) + 1) * vm->page_size) - sz; + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa_base, + TEST_MEMSLOT_INDEX, pages, 0); +} + +static gpa_t alloc_64k(size_t nr) +{ + size_t pages_per_64k = vm_calc_num_guest_pages(vm->mode, SZ_64K); + gpa_t gpa = vm_phy_pages_alloc(vm, nr * pages_per_64k, gpa_base, + TEST_MEMSLOT_INDEX); + + TEST_ASSERT(IS_ALIGNED(gpa, SZ_64K), + "Allocation at 0x%lx is not 64K aligned, GITS_BASER cannot address it", + gpa); + return gpa; +} + +static void map_to_guest(gpa_t gpa, size_t nr) +{ + size_t pages_per_64k = vm_calc_num_guest_pages(vm->mode, SZ_64K); + + virt_map(vm, gpa, gpa, nr * pages_per_64k); +} + +static void setup_common(void) +{ + test_data.cmdq_base = alloc_64k(1); + map_to_guest(test_data.cmdq_base, 1); + test_data.cmdq_base_va = (void *)test_data.cmdq_base; + + test_data.lpi_prop_table = alloc_64k(1); + test_data.lpi_pend_table = alloc_64k(1); +} + +static void teardown(void) +{ + close(its_fd); + kvm_vm_free(vm); + memset(&test_data, 0, sizeof(test_data)); +} + +/* + * A GITS_BASER<coll> write that changes the table drops the collections it + * described. The save then has an empty list, so it writes the terminating + * invalid entry and nothing else. + */ +static void test_baser_change_drops_collections(void) +{ + u64 *cte; + int ret, i; + + pr_info("Testing that a GITS_BASER change drops the collections\n"); + + vm = vm_create_with_one_vcpu(&vcpu, guest_baser_change); + setup_memslot((4 + COLL_TBL_PAGES) * SZ_64K); + its_fd = vgic_its_setup(vm); + + test_data.device_table = alloc_64k(1); + test_data.collection_table = alloc_64k(COLL_TBL_PAGES); + setup_common(); + + sync_global_to_guest(vm, test_data); + run_guest(); + + /* Anything KVM writes is then the only thing that changed. */ + poison_range(test_data.collection_table, COLL_TBL_SZ); + + ret = save_tables(); + TEST_ASSERT(!ret, "Expected the save to succeed, got %d errno %d", + ret, errno); + + cte = addr_gpa2hva(vm, test_data.collection_table); + + /* + * Finding the terminator at the head of the table is also what proves + * the reads below landed in the saved table rather than elsewhere. + */ + TEST_ASSERT(le64toh(cte[0]) == 0, + "CTE 0: expected the terminating invalid entry, got 0x%llx", + (unsigned long long)le64toh(cte[0])); + + for (i = 1; i < COLL_TBL_SZ / ESZ; i++) + TEST_ASSERT(cte[i] == POISON, + "CTE %d: expected it untouched, got 0x%llx", + i, (unsigned long long)cte[i]); + + reset_and_restore_tables(); + + teardown(); +} + +/* + * A device whose L2 block the guest dropped is skipped by the save, and the + * DTE chain skips it too: left alone, the surviving device would point at an + * entry the save never wrote. + */ +static void test_unreachable_device_skipped(void) +{ + u64 dte; + int ret; + + pr_info("Testing that an unreachable device is skipped by the save\n"); + + vm = vm_create_with_one_vcpu(&vcpu, guest_unreachable_device); + setup_memslot(9 * SZ_64K); + its_fd = vgic_its_setup(vm); + + test_data.device_table = alloc_64k(1); + test_data.collection_table = alloc_64k(1); + test_data.device_l2[0] = alloc_64k(1); + test_data.device_l2[1] = alloc_64k(1); + test_data.itt_tables = alloc_64k(2); + setup_common(); + + map_to_guest(test_data.device_table, 1); + test_data.device_l1_va = (void *)test_data.device_table; + + sync_global_to_guest(vm, test_data); + run_guest(); + + poison_range(test_data.device_l2[0], SZ_64K); + poison_range(test_data.device_l2[1], SZ_64K); + + ret = save_tables(); + TEST_ASSERT(!ret, "Expected the save to succeed, got %d errno %d", + ret, errno); + + dte = le64toh(*(u64 *)addr_gpa2hva(vm, test_data.device_l2[0])); + + /* Device A is still reachable, so it is saved. */ + TEST_ASSERT(dte & DTE_VALID_MASK, + "Device A: expected a valid DTE, got 0x%llx", + (unsigned long long)dte); + + /* + * Device B is the only device after it and was skipped, so nothing + * follows A in the saved chain. + */ + TEST_ASSERT(FIELD_GET(DTE_NEXT_MASK, dte) == 0, + "Device A: expected no next device, got offset %llu", + (unsigned long long)FIELD_GET(DTE_NEXT_MASK, dte)); + + /* And nothing was written into the block the guest dropped. */ + TEST_ASSERT(*(u64 *)addr_gpa2hva(vm, test_data.device_l2[1]) == POISON, + "Device B: expected its entry untouched"); + + reset_and_restore_tables(); + + teardown(); +} + +int main(void) +{ + TEST_REQUIRE(kvm_supports_vgic_v3()); + + test_baser_change_drops_collections(); + test_unreachable_device_skipped(); + + pr_info("All ok!\n"); + return 0; +} diff --git a/tools/testing/selftests/kvm/hardware_disable_test.c b/tools/testing/selftests/kvm/hardware_disable_test.c index 43a36ef3ead8..1c20892d6782 100644 --- a/tools/testing/selftests/kvm/hardware_disable_test.c +++ b/tools/testing/selftests/kvm/hardware_disable_test.c @@ -37,7 +37,7 @@ static void *run_vcpu(void *arg) struct kvm_vcpu *vcpu = arg; struct kvm_run *run = vcpu->run; -#ifndef _GNU_SOURCE +#ifndef __GLIBC__ kvm_sched_setaffinity(0, sizeof(cpu_set_t), &threads_cpu_set); #endif @@ -51,7 +51,7 @@ static void *sleeping_thread(void *arg) { int fd; -#ifndef _GNU_SOURCE +#ifndef __GLIBC__ kvm_sched_setaffinity(0, sizeof(cpu_set_t), &threads_cpu_set); #endif @@ -71,7 +71,7 @@ static void run_test(u32 run) u32 i, j; TEST_ASSERT_EQ(pthread_attr_init(&attr), 0); -#ifdef _GNU_SOURCE +#ifdef __GLIBC__ TEST_ASSERT_EQ(pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &threads_cpu_set), 0); #endif diff --git a/tools/testing/selftests/kvm/steal_time.c b/tools/testing/selftests/kvm/steal_time.c index bc3c62b72c58..785d19f9ee8f 100644 --- a/tools/testing/selftests/kvm/steal_time.c +++ b/tools/testing/selftests/kvm/steal_time.c @@ -27,6 +27,9 @@ static void *st_gva[NR_VCPUS]; static u64 guest_stolen_time[NR_VCPUS]; +static struct kvm_vm *vm_create_steal_time(u32 nr_vcpus, void *guest_code, + struct kvm_vcpu *vcpus[]); + #if defined(__x86_64__) /* steal_time must have 64-byte alignment */ @@ -210,17 +213,14 @@ static void check_steal_time_uapi(void) u64 st_ipa; int ret; - vm = vm_create_with_one_vcpu(&vcpu, NULL); - struct kvm_device_attr dev = { .group = KVM_ARM_VCPU_PVTIME_CTRL, .attr = KVM_ARM_VCPU_PVTIME_IPA, .addr = (u64)&st_ipa, }; + vm = vm_create_steal_time(1, NULL, &vcpu); vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &dev); - vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, 1, 0); - virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, 1); st_ipa = (ulong)ST_GPA_BASE | 1; ret = __vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev); @@ -500,13 +500,27 @@ static void run_vcpu(struct kvm_vcpu *vcpu) } } +static struct kvm_vm *vm_create_steal_time(u32 nr_vcpus, void *guest_code, + struct kvm_vcpu *vcpus[]) +{ + unsigned int gpages; + struct kvm_vm *vm; + + /* Create a VM and an identity mapped memslot for the steal time structure */ + vm = vm_create_with_vcpus(nr_vcpus, guest_code, vcpus); + gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE * nr_vcpus); + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0); + virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, gpages); + + return vm; +} + int main(int ac, char **av) { struct kvm_vcpu *vcpus[NR_VCPUS]; struct kvm_vm *vm; pthread_t thread; cpu_set_t cpuset; - unsigned int gpages; long stolen_time; long run_delay; bool verbose; @@ -517,11 +531,7 @@ int main(int ac, char **av) /* Set CPU affinity so we can force preemption of the VCPU */ cpu = pin_self_to_any_cpu(); - /* Create a VM and an identity mapped memslot for the steal time structure */ - vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus); - gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE * NR_VCPUS); - vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0); - virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, gpages); + vm = vm_create_steal_time(NR_VCPUS, guest_code, vcpus); ksft_print_header(); TEST_REQUIRE(is_steal_time_supported(vcpus[0])); diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 18dbdb99aeba..6e979cef884d 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -10493,9 +10493,9 @@ FIXTURE_TEARDOWN_PARENT(trace_layout1) } /* - * Verifies that check_rule_fs events include correct field values: domain, dev, - * ino, access_request, and grants. All values are verified against stat() of - * the rule path on a deterministic tmpfs layout. + * Verifies that check_rule_inode events include correct field values: domain, + * dev, ino, access_request, and grants. All values are verified against stat() + * of the rule path on a deterministic tmpfs layout. */ TEST_F(trace_layout1, check_rule_fs_fields) { @@ -10529,7 +10529,7 @@ TEST_F(trace_layout1, check_rule_fs_fields) EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK))) { - TH_LOG("Expected 1 check_rule_fs event\n%s", buf); + TH_LOG("Expected 1 check_rule_inode event\n%s", buf); } ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK), @@ -10570,8 +10570,8 @@ TEST_F(trace_layout1, check_rule_fs_fields) } /* - * Verifies check_rule_fs behavior with multiple rules. With rules at s1d1 and - * s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the + * Verifies check_rule_inode behavior with multiple rules. With rules at s1d1 + * and s1d2 (a child of s1d1), accessing s1d2 produces only 1 event because the * pathwalk short-circuits after the first rule fully unmasks the single layer. */ TEST_F(trace_layout1, check_rule_fs_multiple_rules) @@ -10643,14 +10643,14 @@ TEST_F(trace_layout1, check_rule_fs_multiple_rules) ASSERT_NE(NULL, buf); /* - * Only 1 check_rule_fs event: the rule on dir_s1d2 fully unmasked the - * single layer, so the pathwalk short-circuits before reaching the + * Only one check_rule_inode event: the rule on dir_s1d2 fully unmasks + * the single layer, so the pathwalk short-circuits before reaching the * dir_s1d1 rule. */ count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count, + TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count, buf); } @@ -10777,7 +10777,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access) count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 check_rule_fs event, got %d\n%s", count, + TH_LOG("Expected 1 check_rule_inode event, got %d\n%s", count, buf); } @@ -10796,7 +10796,7 @@ TEST_F(trace_layout1, check_rule_fs_optional_access) } /* - * Verifies that check_rule_fs fires for a rule that matches the inode even when + * Verifies that check_rule_inode fires for a rule matching the inode even when * it grants none of the requested rights, so the grants set is empty. Landlock * cannot know a rule ignores the request before reading it, so the event is * still emitted (grants={}), which lets a tracer see that the rule matched. @@ -10884,7 +10884,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant) count = tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); EXPECT_EQ(2, count) { - TH_LOG("Expected 2 check_rule_fs events, got %d\n%s", count, + TH_LOG("Expected 2 check_rule_inode events, got %d\n%s", count, buf); } @@ -10894,7 +10894,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant) tracefs_count_matches( buf, TRACE_PREFIX( - TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ " + TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ " "access_request=read_dir " "dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ " "grants={}$")) @@ -10908,7 +10908,7 @@ TEST_F(trace_layout1, check_rule_fs_empty_grant) tracefs_count_matches( buf, TRACE_PREFIX( - TRACE_TASK) "landlock_check_rule_fs: domain=[0-9a-f]\\+ " + TRACE_TASK) "landlock_check_rule_inode: domain=[0-9a-f]\\+ " "access_request=read_dir " "dev=[0-9]\\+:[0-9]\\+ ino=[0-9]\\+ " "grants={read_dir}$")) diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index a18761e0fd82..16afbfdf06bb 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -3481,13 +3481,14 @@ TEST_F(trace_net, deny_access_net_bind) } /* - * Anchors the denial fields shared by every deny_access_net event so a field - * test proves more than sport/dport: the denying domain, the same-exec bit, the - * audit-logging verdict, and the blocked access all stay populated. + * Anchors the denial fields shared by every deny_access_net event so a port + * test also proves the denying domain, execution status, logging verdict, and + * exact blocked access. */ static void expect_net_deny_common_fields(struct __test_metadata *const _metadata, - const char *const buf) + const char *const buf, + const char *const expected_blockers) { char field[64]; @@ -3511,18 +3512,21 @@ expect_net_deny_common_fields(struct __test_metadata *const _metadata, ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), "blockers", field, sizeof(field))); - EXPECT_STRNE("", field); + EXPECT_STREQ(expected_blockers, field); } -/* Connect and field-check tests use a separate fixture without variants. */ +enum trace_net_operation { + TRACE_NET_BIND, + TRACE_NET_SEND, +}; /* clang-format off */ -FIXTURE(trace_net_connect) { +FIXTURE(trace_net_address) { /* clang-format on */ int tracefs_ok; }; -FIXTURE_SETUP(trace_net_connect) +FIXTURE_SETUP(trace_net_address) { int ret; @@ -3547,7 +3551,7 @@ FIXTURE_SETUP(trace_net_connect) clear_cap(_metadata, CAP_SYS_ADMIN); } -FIXTURE_TEARDOWN(trace_net_connect) +FIXTURE_TEARDOWN(trace_net_address) { if (!self->tracefs_ok) return; @@ -3559,160 +3563,183 @@ FIXTURE_TEARDOWN(trace_net_connect) } /* clang-format off */ -FIXTURE_VARIANT(trace_net_connect) { +FIXTURE_VARIANT(trace_net_address) { /* clang-format on */ - /* handled_access_net, also the access allowed on the base port. */ - __u64 handled; - /* Bind the allowed base port before the denied operation. */ - bool bind_base_first; - /* Denied operation on the next port: connect (true) or bind (false). */ - bool deny_connect; + int socket_family; + int socket_type; + enum trace_net_operation operation; + int address_family; + socklen_t addrlen; + __u64 handled_access; + const char *expected_blockers; + bool address_port_zero; + bool expected_address_port; + int expected_port; }; /* clang-format off */ - -/* Denied connect(): sport=0, dport=<denied port>. */ -FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) { - .handled = LANDLOCK_ACCESS_NET_CONNECT_TCP, - .bind_base_first = false, - .deny_connect = true, +FIXTURE_VARIANT_ADD(trace_net_address, ipv4_tcp_bind) { + /* clang-format on */ + .socket_family = AF_INET, + .socket_type = SOCK_STREAM, + .operation = TRACE_NET_BIND, + .address_family = AF_INET, + .addrlen = sizeof(struct sockaddr_in), + .handled_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .expected_blockers = "bind_tcp", + .expected_address_port = true, }; -/* Denied bind(): sport=<denied port>, dport=0. */ -FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) { - .handled = LANDLOCK_ACCESS_NET_BIND_TCP, - .bind_base_first = false, - .deny_connect = false, +/* Explicit bind(0) has a checked zero port. */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(trace_net_address, ipv4_udp_bind_zero) { + /* clang-format on */ + .socket_family = AF_INET, + .socket_type = SOCK_DGRAM, + .operation = TRACE_NET_BIND, + .address_family = AF_INET, + .addrlen = sizeof(struct sockaddr_in), + .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP, + .expected_blockers = "bind_udp", + .address_port_zero = true, + .expected_port = 0, +}; + +/* A UDP send can deny its synthetic unspecified bind endpoint. */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(trace_net_address, ipv6_udp_autobind) { + /* clang-format on */ + .socket_family = AF_INET6, + .socket_type = SOCK_DGRAM, + .operation = TRACE_NET_SEND, + .address_family = AF_INET6, + .addrlen = sizeof(struct sockaddr_in6), + .handled_access = LANDLOCK_ACCESS_NET_BIND_UDP, + .expected_blockers = "bind_udp", + .expected_port = 0, }; -/* Denied connect() after an allowed bind(): the connect fields (sport=0). */ -FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) { - .handled = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, - .bind_base_first = true, - .deny_connect = true, +/* A family-only address has no checked port. */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(trace_net_address, ipv6_unspec_udp_send_min) { + /* clang-format on */ + .socket_family = AF_INET6, + .socket_type = SOCK_DGRAM, + .operation = TRACE_NET_SEND, + .address_family = AF_UNSPEC, + .addrlen = sizeof(sa_family_t), + .handled_access = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP, + .expected_blockers = "connect_send_udp", + .expected_port = -1, }; -/* clang-format on */ +static void set_trace_net_address(struct sockaddr_storage *const storage, + const int socket_family, + const int address_family, + const unsigned short port) +{ + memset(storage, 0, sizeof(*storage)); -/* - * A denied TCP bind(2) or connect(2) emits one deny_access_net event. The port - * is reported in the field matching the denied operation, in host endianness - * (the UAPI landlock_net_port_attr.port convention): a connect denial reports - * sport=0 dport=<port>, a bind denial reports sport=<port> dport=0, so a - * byte-order or field-swap bug is caught. A prior allowed bind - * (connect_after_bind) does not change the connect denial's fields. - */ -TEST_F(trace_net_connect, deny_access_net) + if (socket_family == AF_INET) { + struct sockaddr_in *const addr4 = (struct sockaddr_in *)storage; + + addr4->sin_family = address_family; + addr4->sin_port = htons(port); + addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } else { + struct sockaddr_in6 *const addr6 = + (struct sockaddr_in6 *)storage; + + addr6->sin6_family = address_family; + addr6->sin6_port = htons(port); + addr6->sin6_addr = in6addr_loopback; + } +} + +/* Verifies the actionable signed port for representative checked shapes. */ +TEST_F(trace_net_address, deny_access_net) { - pid_t child; - int status; - char *buf; + const char *const event_regex = REGEX_DENY_ACCESS_NET(TRACE_TASK); + const unsigned short address_port = + variant->address_port_zero ? 0 : sock_port_start + 1; + const int expected_port = variant->expected_address_port ? + address_port : + variant->expected_port; + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = variant->handled_access, + }; + struct sockaddr_storage address; char field[64], expected[16]; + char *buf; + int count, ret, ruleset_fd, socket_fd, status; + pid_t child; if (!self->tracefs_ok) SKIP(return, "tracefs not available"); + set_trace_net_address(&address, variant->socket_family, + variant->address_family, address_port); + socket_fd = socket(variant->socket_family, + variant->socket_type | SOCK_CLOEXEC, 0); + ASSERT_LE(0, socket_fd); + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, tracefs_clear_buf()); + child = fork(); ASSERT_LE(0, child); - if (child == 0) { - struct landlock_ruleset_attr ruleset_attr = { - .handled_access_net = variant->handled, - }; - struct landlock_net_port_attr port_attr = { - .allowed_access = variant->handled, - .port = sock_port_start, - }; - struct sockaddr_in addr = { - .sin_family = AF_INET, - .sin_addr.s_addr = htonl(INADDR_LOOPBACK), - }; - int ruleset_fd, sock_fd, optval = 1, ret; - - ruleset_fd = landlock_create_ruleset(&ruleset_attr, - sizeof(ruleset_attr), 0); - if (ruleset_fd < 0) + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) _exit(1); - if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, - &port_attr, 0)) { - close(ruleset_fd); - _exit(1); - } - prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); - if (landlock_restrict_self(ruleset_fd, 0)) { - close(ruleset_fd); - _exit(1); - } + if (landlock_restrict_self(ruleset_fd, 0)) + _exit(2); close(ruleset_fd); - sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (sock_fd < 0) - _exit(1); - - /* Bind the allowed base port first (succeeds, no event). */ - if (variant->bind_base_first) { - setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, - sizeof(optval)); - addr.sin_port = htons(sock_port_start); - if (bind(sock_fd, (struct sockaddr *)&addr, - sizeof(addr))) { - close(sock_fd); - _exit(1); - } - } - - /* Denied operation on the next port. */ - addr.sin_port = htons(sock_port_start + 1); - if (variant->deny_connect) - ret = connect(sock_fd, (struct sockaddr *)&addr, - sizeof(addr)); - else - ret = bind(sock_fd, (struct sockaddr *)&addr, - sizeof(addr)); - if (ret == 0) { - close(sock_fd); - _exit(2); - } - if (errno != EACCES) { - close(sock_fd); + switch (variant->operation) { + case TRACE_NET_BIND: + ret = bind(socket_fd, (const struct sockaddr *)&address, + variant->addrlen); + break; + case TRACE_NET_SEND: + ret = sendto(socket_fd, "A", 1, MSG_NOSIGNAL, + (const struct sockaddr *)&address, + variant->addrlen); + break; + default: _exit(3); } - close(sock_fd); + if (ret >= 0 || errno != EACCES) + _exit(4); + close(socket_fd); + _exit(0); } + close(ruleset_fd); + close(socket_fd); ASSERT_EQ(child, waitpid(child, &status, 0)); ASSERT_TRUE(WIFEXITED(status)); - EXPECT_EQ(0, WEXITSTATUS(status)); + ASSERT_EQ(0, WEXITSTATUS(status)); buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - - EXPECT_EQ(1, tracefs_count_matches(buf, - REGEX_DENY_ACCESS_NET(TRACE_TASK))); - - expect_net_deny_common_fields(_metadata, buf); - - /* - * The denied operation's port field carries the port; the other is 0. - */ - snprintf(expected, sizeof(expected), "%llu", - (unsigned long long)(sock_port_start + 1)); - - ASSERT_EQ(0, - tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), - "sport", field, sizeof(field))); - EXPECT_STREQ(variant->deny_connect ? "0" : expected, field); - - ASSERT_EQ(0, - tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK), - "dport", field, sizeof(field))); - EXPECT_STREQ(variant->deny_connect ? expected : "0", field); + count = tracefs_count_matches(buf, event_regex); + if (count != 1) + TH_LOG("Expected 1 denial event, got %d\n%s", count, buf); + ASSERT_EQ(1, count); + expect_net_deny_common_fields(_metadata, buf, + variant->expected_blockers); + + ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "port", field, + sizeof(field))); + snprintf(expected, sizeof(expected), "%d", expected_port); + EXPECT_STREQ(expected, field); free(buf); } -/* Field verification for the check_rule_net event on an allowed access. */ +/* Field verification for the check_rule_net_port event on an allowed access. */ /* clang-format off */ FIXTURE(trace_net_check_rule) { @@ -3757,10 +3784,11 @@ FIXTURE_TEARDOWN(trace_net_check_rule) /* * Verifies that an allowed bind matching a net-port rule emits exactly one - * landlock_check_rule_net event with the enforcing domain, the requested + * landlock_check_rule_net_port event with the enforcing domain, the requested * access, the checked port (host endianness), and the per-layer grants. The - * whole event is anchored to exact values so a revert of the check_rule_net - * emit (or a byte-order or field-plumbing regression) fails the test. + * whole event is anchored to exact values so removing the check_rule_net_port + * emission or introducing a byte-order or field-plumbing regression fails the + * test. */ TEST_F(trace_net_check_rule, check_rule_net_fields) { @@ -3832,7 +3860,7 @@ TEST_F(trace_net_check_rule, check_rule_net_fields) EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_CHECK_RULE_NET(TRACE_TASK))) { - TH_LOG("Expected 1 check_rule_net event\n%s", buf); + TH_LOG("Expected 1 check_rule_net_port event\n%s", buf); } ASSERT_EQ(0, @@ -3866,11 +3894,4 @@ TEST_F(trace_net_check_rule, check_rule_net_fields) free(buf); } -/* - * IPv6 network trace tests are intentionally elided. IPv6 hook dispatch uses - * the same current_check_access_socket() code path as IPv4, validated by the - * audit tests in this file. The trace events use the same blockers/sport/dport - * fields regardless of address family. - */ - TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/landlock/trace.h b/tools/testing/selftests/landlock/trace.h index ba0c5e92001f..2ec863362173 100644 --- a/tools/testing/selftests/landlock/trace.h +++ b/tools/testing/selftests/landlock/trace.h @@ -27,14 +27,14 @@ TRACEFS_LANDLOCK_DIR "/landlock_create_domain/enable" #define TRACEFS_ENFORCE_DOMAIN_ENABLE \ TRACEFS_LANDLOCK_DIR "/landlock_enforce_domain/enable" -#define TRACEFS_ADD_RULE_FS_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_add_rule_fs/enable" -#define TRACEFS_ADD_RULE_NET_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net/enable" +#define TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE \ + TRACEFS_LANDLOCK_DIR "/landlock_add_rule_path_beneath/enable" +#define TRACEFS_ADD_RULE_NET_PORT_ENABLE \ + TRACEFS_LANDLOCK_DIR "/landlock_add_rule_net_port/enable" #define TRACEFS_CHECK_RULE_FS_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_check_rule_fs/enable" + TRACEFS_LANDLOCK_DIR "/landlock_check_rule_inode/enable" #define TRACEFS_CHECK_RULE_NET_ENABLE \ - TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net/enable" + TRACEFS_LANDLOCK_DIR "/landlock_check_rule_net_port/enable" #define TRACEFS_DENY_ACCESS_FS_ENABLE \ TRACEFS_LANDLOCK_DIR "/landlock_deny_access_fs/enable" #define TRACEFS_DENY_ACCESS_NET_ENABLE \ @@ -79,18 +79,18 @@ */ #define KWORKER_TASK "kworker/[0-9]\\+:[0-9]\\+" -#define REGEX_ADD_RULE_FS(task) \ - TRACE_PREFIX(task) \ - "landlock_add_rule_fs: " \ - "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ - "access_rights=[a-z_|]* " \ - "dev=[0-9]\\+:[0-9]\\+ " \ - "ino=[0-9]\\+ " \ +#define REGEX_ADD_RULE_PATH_BENEATH(task) \ + TRACE_PREFIX(task) \ + "landlock_add_rule_path_beneath: " \ + "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ + "access_rights=[a-z_|]* " \ + "dev=[0-9]\\+:[0-9]\\+ " \ + "ino=[0-9]\\+ " \ "path=[^ ]\\+$" -#define REGEX_ADD_RULE_NET(task) \ +#define REGEX_ADD_RULE_NET_PORT(task) \ TRACE_PREFIX(task) \ - "landlock_add_rule_net: " \ + "landlock_add_rule_net_port: " \ "ruleset=[0-9a-f]\\+\\.[0-9]\\+ " \ "access_rights=[a-z_|]* " \ "port=[0-9]\\+$" @@ -110,21 +110,21 @@ "parent=[0-9a-f]\\+ " \ "ruleset=[0-9a-f]\\+\\.[0-9]\\+$" -#define REGEX_CHECK_RULE_FS(task) \ - TRACE_PREFIX(task) \ - "landlock_check_rule_fs: " \ - "domain=[0-9a-f]\\+ " \ - "access_request=[a-z_|]* " \ - "dev=[0-9]\\+:[0-9]\\+ " \ - "ino=[0-9]\\+ " \ +#define REGEX_CHECK_RULE_FS(task) \ + TRACE_PREFIX(task) \ + "landlock_check_rule_inode: " \ + "domain=[0-9a-f]\\+ " \ + "access_request=[a-z_|]* " \ + "dev=[0-9]\\+:[0-9]\\+ " \ + "ino=[0-9]\\+ " \ "grants={[a-z_|,]*}$" -#define REGEX_CHECK_RULE_NET(task) \ - TRACE_PREFIX(task) \ - "landlock_check_rule_net: " \ - "domain=[0-9a-f]\\+ " \ - "access_request=[a-z_|]* " \ - "port=[0-9]\\+ " \ +#define REGEX_CHECK_RULE_NET(task) \ + TRACE_PREFIX(task) \ + "landlock_check_rule_net_port: " \ + "domain=[0-9a-f]\\+ " \ + "access_request=[a-z_|]* " \ + "port=[0-9]\\+ " \ "grants={[a-z_|,]*}$" #define REGEX_DENY_ACCESS_FS(task) \ @@ -145,8 +145,7 @@ "same_exec=[01] " \ "logged=[01] " \ "blockers=[a-z_|]* " \ - "sport=[0-9]\\+ " \ - "dport=[0-9]\\+$" + "port=-\\?[0-9]\\+$" #define REGEX_DENY_PTRACE(task) \ TRACE_PREFIX(task) \ diff --git a/tools/testing/selftests/landlock/trace_fs_test.c b/tools/testing/selftests/landlock/trace_fs_test.c index 4543a25c1f55..64014ade3a0e 100644 --- a/tools/testing/selftests/landlock/trace_fs_test.c +++ b/tools/testing/selftests/landlock/trace_fs_test.c @@ -121,7 +121,8 @@ FIXTURE_SETUP(trace_fs) } self->tracefs_ok = 1; - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, + true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true)); ASSERT_EQ(0, tracefs_clear()); @@ -134,7 +135,7 @@ FIXTURE_TEARDOWN(trace_fs) return; set_cap(_metadata, CAP_SYS_ADMIN); - tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false); + tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false); tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false); tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false); tracefs_fixture_teardown(); @@ -183,11 +184,11 @@ TEST_F(trace_fs, unsandboxed) } /* - * Verifies that adding a filesystem rule emits a landlock_add_rule_fs trace - * event with the expected path and field values: ruleset ID is non-zero, - * access_rights is non-zero, and path matches. + * Verifies that adding a filesystem rule emits a landlock_add_rule_path_beneath + * event with the expected path and field values: the ruleset ID and + * access_rights are non-zero, and the path matches. */ -TEST_F(trace_fs, add_rule_fs) +TEST_F(trace_fs, add_rule_path_beneath) { struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | @@ -215,28 +216,30 @@ TEST_F(trace_fs, add_rule_fs) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + count = tracefs_count_matches(buf, + REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf); + TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s", + count, buf); } /* Ruleset ID should be non-zero. */ - ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "ruleset", field_buf, - sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "ruleset", field_buf, sizeof(field_buf))); EXPECT_STRNE("0", field_buf); /* Access rights should be non-zero. */ - ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "access_rights", field_buf, - sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "access_rights", field_buf, sizeof(field_buf))); EXPECT_STRNE("", field_buf); /* Path should be /usr. */ - ASSERT_EQ(0, - tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "path", field_buf, sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "path", field_buf, sizeof(field_buf))); EXPECT_STREQ("/usr", field_buf); free(buf); @@ -246,7 +249,7 @@ TEST_F(trace_fs, add_rule_fs) * Verifies that a path whose escaping exceeds the trace scratch sequence does * not corrupt a sibling symbolic field. */ -TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) +TEST_F(trace_fs, add_rule_path_beneath_escaped_path_overflow) { static const char access_prefix[] = "execute|write_file|read_file|"; static const char access_suffix[] = "|ioctl_dev|resolve_unix"; @@ -277,10 +280,12 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - count = tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + count = tracefs_count_matches(buf, + REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)); EXPECT_EQ(1, count) { - TH_LOG("Expected 1 add_rule_fs event, got %d\n%s", count, buf); + TH_LOG("Expected 1 add_rule_path_beneath event, got %d\n%s", + count, buf); } /* @@ -288,9 +293,9 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) * field also catches scratch-sequence poisoning when the compiler * evaluates the overflowing path first, as GCC currently does. */ - ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_FS(TRACE_TASK), - "access_rights", field_buf, - sizeof(field_buf))); + ASSERT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK), + "access_rights", field_buf, sizeof(field_buf))); EXPECT_EQ(0, strncmp(field_buf, access_prefix, sizeof(access_prefix) - 1)); EXPECT_EQ(NULL, strstr(field_buf, "|refer|")); @@ -298,7 +303,8 @@ TEST_F(trace_fs, add_rule_fs_escaped_path_overflow) ASSERT_LE(sizeof(access_suffix) - 1, field_len); EXPECT_STREQ(access_suffix, field_buf + field_len - (sizeof(access_suffix) - 1)); - expect_truncated_path(_metadata, buf, REGEX_ADD_RULE_FS(TRACE_TASK)); + expect_truncated_path(_metadata, buf, + REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK)); free(buf); } @@ -542,7 +548,8 @@ TEST_F(trace_fs, check_rule_nested) */ TEST_F(trace_fs, deny_access_fs_denied) { - char *buf; + const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK); + char *buf, blockers[64]; int count; ASSERT_EQ(0, tracefs_clear_buf()); @@ -558,8 +565,77 @@ TEST_F(trace_fs, deny_access_fs_denied) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - count = tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK)); - EXPECT_LE(1, count); + count = tracefs_count_matches(buf, event_regex); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 access denial, got %d\n%s", count, buf); + } + ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers", + blockers, sizeof(blockers))); + EXPECT_STREQ("read_dir", blockers); + + free(buf); +} + +/* + * Verifies that a denied mount reports the singleton topology blocker rather + * than an empty access mask. + */ +TEST_F(trace_fs, deny_change_topology) +{ + const char *const event_regex = REGEX_DENY_ACCESS_FS(TRACE_TASK); + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_REFER, + }; + char *buf, blockers[64]; + int count, ruleset_fd, status; + pid_t pid; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, tracefs_clear_buf()); + + /* Ensure that Landlock is the only expected mount denial. */ + set_cap(_metadata, CAP_SYS_ADMIN); + pid = fork(); + ASSERT_LE(0, pid); + if (pid == 0) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + close(ruleset_fd); + _exit(1); + } + if (landlock_restrict_self(ruleset_fd, 0)) { + close(ruleset_fd); + _exit(2); + } + close(ruleset_fd); + + if (mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL) != -1) + _exit(3); + + if (errno != EPERM) + _exit(4); + + _exit(0); + } + close(ruleset_fd); + clear_cap(_metadata, CAP_SYS_ADMIN); + + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + buf = tracefs_read_buf(); + ASSERT_NE(NULL, buf); + count = tracefs_count_matches(buf, event_regex); + EXPECT_EQ(1, count) + { + TH_LOG("Expected 1 topology denial, got %d\n%s", count, buf); + } + ASSERT_EQ(0, tracefs_extract_field(buf, event_regex, "blockers", + blockers, sizeof(blockers))); + EXPECT_STREQ("change_topology", blockers); free(buf); } diff --git a/tools/testing/selftests/landlock/trace_test.c b/tools/testing/selftests/landlock/trace_test.c index afdaf8511b3a..f9b293a9dd56 100644 --- a/tools/testing/selftests/landlock/trace_test.c +++ b/tools/testing/selftests/landlock/trace_test.c @@ -49,8 +49,10 @@ FIXTURE_SETUP(trace) ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, true)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, + true)); + ASSERT_EQ(0, + tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true)); @@ -72,8 +74,8 @@ FIXTURE_TEARDOWN(trace) tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false); tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false); tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false); - tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false); - tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false); + tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, false); + tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, false); tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false); tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false); tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false); @@ -103,8 +105,10 @@ TEST_F(trace, no_trace_when_disabled) ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false)); - ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_PATH_BENEATH_ENABLE, + false)); + ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_PORT_ENABLE, + false)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false)); @@ -265,10 +269,11 @@ TEST_F(trace, ruleset_version) ASSERT_NE(0, !!dot); EXPECT_STREQ("0", dot + 1); - /* Verify 2 add_rule_fs events were emitted. */ - EXPECT_EQ(2, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK))) + /* Verify two add_rule_path_beneath events were emitted. */ + EXPECT_EQ(2, tracefs_count_matches( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK))) { - TH_LOG("Expected 2 add_rule_fs events\n%s", buf); + TH_LOG("Expected 2 add_rule_path_beneath events\n%s", buf); } /* @@ -373,7 +378,7 @@ TEST_F(trace, create_domain) tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); ASSERT_LE(1, check_count) { - TH_LOG("Expected check_rule_fs events\n%s", buf); + TH_LOG("Expected check_rule_inode events\n%s", buf); } EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK), @@ -508,9 +513,11 @@ TEST_F(trace, add_rule_invalid_fd) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - EXPECT_EQ(0, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK))) + EXPECT_EQ(0, tracefs_count_matches( + buf, REGEX_ADD_RULE_PATH_BENEATH(TRACE_TASK))) { - TH_LOG("No add_rule_fs event expected on invalid fd\n%s", buf); + TH_LOG("No add_rule_path_beneath event expected on invalid fd\n%s", + buf); } free(buf); @@ -902,10 +909,10 @@ TEST_F(trace, non_audit_visible_denial_counting) } /* - * Verifies that landlock_add_rule_net emits a trace event with the correct port - * and allowed access mask fields. + * Verifies that landlock_add_rule_net_port emits a trace event with the correct + * port and allowed access mask fields. */ -TEST_F(trace, add_rule_net_fields) +TEST_F(trace, add_rule_net_port_fields) { struct landlock_ruleset_attr ruleset_attr = { .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, @@ -931,9 +938,10 @@ TEST_F(trace, add_rule_net_fields) buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); - EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_ADD_RULE_NET(TRACE_TASK))) + EXPECT_EQ(1, tracefs_count_matches(buf, + REGEX_ADD_RULE_NET_PORT(TRACE_TASK))) { - TH_LOG("Expected 1 add_rule_net event\n%s", buf); + TH_LOG("Expected 1 add_rule_net_port event\n%s", buf); } /* @@ -941,7 +949,8 @@ TEST_F(trace, add_rule_net_fields) * (landlock_net_port_attr.port). On little-endian, htons(8080) is * 36895, so this comparison catches byte-order bugs. */ - EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK), + EXPECT_EQ(0, tracefs_extract_field(buf, + REGEX_ADD_RULE_NET_PORT(TRACE_TASK), "port", field, sizeof(field))); EXPECT_STREQ("8080", field); /* @@ -950,9 +959,9 @@ TEST_F(trace, add_rule_net_fields) * net access bits are unhandled because the ruleset only handles * BIND_TCP). */ - EXPECT_EQ(0, - tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK), - "access_rights", field, sizeof(field))); + EXPECT_EQ(0, tracefs_extract_field( + buf, REGEX_ADD_RULE_NET_PORT(TRACE_TASK), + "access_rights", field, sizeof(field))); EXPECT_STREQ("bind_tcp|connect_tcp|bind_udp|connect_send_udp", field); free(buf); diff --git a/tools/testing/selftests/nci/nci_dev.c b/tools/testing/selftests/nci/nci_dev.c index 312f84ee0444..07427fa42888 100644 --- a/tools/testing/selftests/nci/nci_dev.c +++ b/tools/testing/selftests/nci/nci_dev.c @@ -8,6 +8,7 @@ #include <stdlib.h> #include <errno.h> +#include <stdint.h> #include <string.h> #include <sys/ioctl.h> #include <fcntl.h> @@ -87,6 +88,16 @@ struct msgtemplate { char buf[MAX_MSG_SIZE]; }; +static int join_thread_status(pthread_t thread) +{ + void *thread_ret = NULL; + + if (pthread_join(thread, &thread_ret)) + return -1; + + return (int)(intptr_t)thread_ret; +} + static int create_nl_socket(void) { int fd; @@ -182,7 +193,7 @@ static int get_family_id(int sd, __u32 pid, __u32 *event_group) } ans; struct nlattr *na; int resp_len; - __u16 id; + __u16 id = 0; int len; int rc; @@ -438,13 +449,13 @@ FIXTURE_SETUP(NCI) else rc = pthread_create(&thread_t, NULL, virtual_dev_open, (void *)&self->virtual_nci_fd); - ASSERT_GT(rc, -1); + ASSERT_EQ(rc, 0); rc = send_cmd_with_idx(self->sd, self->fid, self->pid, NFC_CMD_DEV_UP, self->dev_idex); EXPECT_EQ(rc, 0); - pthread_join(thread_t, (void **)&status); + status = join_thread_status(thread_t); ASSERT_EQ(status, 0); self->open_state = true; } @@ -509,12 +520,12 @@ FIXTURE_TEARDOWN(NCI) rc = pthread_create(&thread_t, NULL, virtual_deinit, (void *)&self->virtual_nci_fd); - ASSERT_GT(rc, -1); + ASSERT_EQ(rc, 0); rc = send_cmd_with_idx(self->sd, self->fid, self->pid, NFC_CMD_DEV_DOWN, self->dev_idex); EXPECT_EQ(rc, 0); - pthread_join(thread_t, (void **)&status); + status = join_thread_status(thread_t); ASSERT_EQ(status, 0); } @@ -585,12 +596,11 @@ int start_polling(int dev_idx, int proto, int virtual_fd, int sd, int fid, int p void *nla_start_poll_data[2] = {&dev_idx, &proto}; int nla_start_poll_len[2] = {4, 4}; pthread_t thread_t; - int status; int rc; rc = pthread_create(&thread_t, NULL, virtual_poll_start, (void *)&virtual_fd); - if (rc < 0) + if (rc) return rc; rc = send_cmd_mt_nla(sd, fid, pid, NFC_CMD_START_POLL, 2, nla_start_poll_type, @@ -598,19 +608,17 @@ int start_polling(int dev_idx, int proto, int virtual_fd, int sd, int fid, int p if (rc != 0) return rc; - pthread_join(thread_t, (void **)&status); - return status; + return join_thread_status(thread_t); } int stop_polling(int dev_idx, int virtual_fd, int sd, int fid, int pid) { pthread_t thread_t; - int status; int rc; rc = pthread_create(&thread_t, NULL, virtual_poll_stop, (void *)&virtual_fd); - if (rc < 0) + if (rc) return rc; rc = send_cmd_with_idx(sd, fid, pid, @@ -618,8 +626,7 @@ int stop_polling(int dev_idx, int virtual_fd, int sd, int fid, int pid) if (rc != 0) return rc; - pthread_join(thread_t, (void **)&status); - return status; + return join_thread_status(thread_t); } TEST_F(NCI, start_poll) @@ -830,10 +837,14 @@ int disconnect_tag(int nfc_sock, int virtual_fd) status = pthread_create(&thread_t, NULL, virtual_deactivate_proc, (void *)&virtual_fd); + if (status) + return status; close(nfc_sock); - pthread_join(thread_t, (void **)&status); - return status; + if (status) + return -1; + + return join_thread_status(thread_t); } TEST_F(NCI, t4t_tag_read) @@ -874,13 +885,13 @@ TEST_F(NCI, deinit) else rc = pthread_create(&thread_t, NULL, virtual_deinit, (void *)&self->virtual_nci_fd); - ASSERT_GT(rc, -1); + ASSERT_EQ(rc, 0); rc = send_cmd_with_idx(self->sd, self->fid, self->pid, NFC_CMD_DEV_DOWN, self->dev_idex); EXPECT_EQ(rc, 0); - pthread_join(thread_t, (void **)&status); + status = join_thread_status(thread_t); self->open_state = 0; ASSERT_EQ(status, 0); diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index 30d5fcb09a83..737e7e6327b3 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -118,10 +118,10 @@ CONFIG_NFT_NAT=m CONFIG_NUMA=y CONFIG_OPENVSWITCH=m CONFIG_PAGE_POOL_STATS=y -CONFIG_SYSCTL=y CONFIG_PSAMPLE=m CONFIG_RPS=y CONFIG_SYN_COOKIES=y +CONFIG_SYSCTL=y CONFIG_SYSFS=y CONFIG_TAP=m CONFIG_TCP_CONG_DCTCP=y diff --git a/tools/testing/selftests/net/nl_nlctrl.py b/tools/testing/selftests/net/nl_nlctrl.py index fe1f66dc9435..237b3d273260 100755 --- a/tools/testing/selftests/net/nl_nlctrl.py +++ b/tools/testing/selftests/net/nl_nlctrl.py @@ -9,40 +9,86 @@ from lib.py import ksft_run, ksft_exit from lib.py import ksft_eq, ksft_ge, ksft_true, ksft_in, ksft_not_in from lib.py import NetdevFamily, EthtoolFamily, NlctrlFamily +# Families we can expect to always be around, and which between them +# cover ops with a do, with a dump, and with both. +FAMILIES = ('nlctrl', 'netdev') -def getfamily_do(ctrl) -> None: - """Query a single family by name and validate its ops.""" - fam = ctrl.getfamily({'family-name': 'netdev'}) - ksft_eq(fam['family-name'], 'netdev') + +def _get_ops(ctrl, name): + """Get the ops of a family, keyed by command id.""" + fam = ctrl.getfamily({'family-name': name}) + ksft_eq(fam['family-name'], name) ksft_true(fam['family-id'] > 0) # The format of ops is quite odd, [{$idx: {"id"...}}, {$idx: {"id"...}}] # Discard the indices and re-key by command id. ops_by_id = {v['id']: v for op in fam['ops'] for v in op.values()} - ksft_eq(len(ops_by_id), len(fam['ops'])) + ksft_eq(len(ops_by_id), len(fam['ops']), + comment=f"{name} lists a command twice") + return ops_by_id + + +def _get_policy_map(ctrl, req): + """ + The policy map in the Netlink replies looks like this: + + [{'family-id': 16, 'op-policy': {'do': 0, 'dump': 0, 'op-id': 3}}, + {'family-id': 16, 'op-policy': {'dump': 1, 'op-id': 4}}, ...] + + Return the mapping: + + {3:{'do','dump'}, 4:{'dump'}} + + The policy itself is discarded here, only return which command has policy. + """ + pol_map = {} + for msg in ctrl.getpolicy(req, dump=True): + if 'op-policy' not in msg: + continue + modes = dict(msg['op-policy']) + cmd = modes.pop('op-id') + ksft_not_in(cmd, pol_map, comment=f"command {cmd} reported twice") + pol_map[cmd] = set(modes.keys()) + return pol_map + + +def getfamily_do(ctrl) -> None: + """Query single families by name and validate their ops.""" + ops = {name: _get_ops(ctrl, name) for name in FAMILIES} + + for name, ops_by_id in ops.items(): + for op in ops_by_id.values(): + # All ops in nlctrl and netdev have a policy + ksft_in('cmd-cap-haspol', op['flags'], + comment=f"{name} op {op['id']} missing haspol") + ksft_true(op['flags'] & {'cmd-cap-do', 'cmd-cap-dump'}, + comment=f"{name} op {op['id']} has no handler") - # All ops should have a policy (either do or dump has one) - for op in ops_by_id.values(): - ksft_in('cmd-cap-haspol', op['flags'], - comment=f"op {op['id']} missing haspol") + # nlctrl getfamily (id 3) does both, getpolicy (id 10) is dump-only + ksft_in('cmd-cap-do', ops['nlctrl'][3]['flags']) + ksft_in('cmd-cap-dump', ops['nlctrl'][3]['flags']) + ksft_not_in('cmd-cap-do', ops['nlctrl'][10]['flags']) + ksft_in('cmd-cap-dump', ops['nlctrl'][10]['flags']) + + netdev = ops['netdev'] # dev-get (id 1) should support both do and dump - ksft_in('cmd-cap-do', ops_by_id[1]['flags']) - ksft_in('cmd-cap-dump', ops_by_id[1]['flags']) + ksft_in('cmd-cap-do', netdev[1]['flags']) + ksft_in('cmd-cap-dump', netdev[1]['flags']) # qstats-get (id 12) is dump-only - ksft_not_in('cmd-cap-do', ops_by_id[12]['flags']) - ksft_in('cmd-cap-dump', ops_by_id[12]['flags']) + ksft_not_in('cmd-cap-do', netdev[12]['flags']) + ksft_in('cmd-cap-dump', netdev[12]['flags']) # napi-set (id 14) is do-only and requires admin - ksft_in('cmd-cap-do', ops_by_id[14]['flags']) - ksft_not_in('cmd-cap-dump', ops_by_id[14]['flags']) - ksft_in('admin-perm', ops_by_id[14]['flags']) + ksft_in('cmd-cap-do', netdev[14]['flags']) + ksft_not_in('cmd-cap-dump', netdev[14]['flags']) + ksft_in('admin-perm', netdev[14]['flags']) # Notification-only commands (dev-add/del/change-ntf etc.) must # not appear in the ops list since they have no do/dump handlers. for ntf_id in [2, 3, 4, 6, 7, 8]: - ksft_not_in(ntf_id, ops_by_id, + ksft_not_in(ntf_id, netdev, comment=f"ntf-only cmd {ntf_id} should not be in ops") @@ -103,6 +149,41 @@ def getpolicy_dump(_ctrl) -> None: comment="linkinfo-set should not have a dump policy") +def getpolicy_op_map(ctrl) -> None: + """Check the op-to-policy map consistency. Each op with 'haspol' flag + has to have a policy. The policy back-references must name only + real ops that exist, have given modes (do vs dump) and have 'haspol'. + """ + for name in FAMILIES: + ops_by_id = _get_ops(ctrl, name) + haspol = {cmd for cmd, op in ops_by_id.items() + if 'cmd-cap-haspol' in op['flags']} + + pol_map = _get_policy_map(ctrl, {'family-name': name}) + ksft_eq(set(pol_map), haspol, + comment=f"{name} policy map does not match the op list") + + # Walk the op list rather than the map, the map may be missing + # the very op we are after. Asking for a command the family does + # not have is an error, so it must not come from the map either. + for cmd in sorted(haspol): + modes = pol_map.get(cmd, set()) + + # The kernel only reports a mode the op actually has. + if 'do' in modes: + ksft_in('cmd-cap-do', ops_by_id[cmd]['flags'], + comment=f"{name} cmd {cmd} has no do") + if 'dump' in modes: + ksft_in('cmd-cap-dump', ops_by_id[cmd]['flags'], + comment=f"{name} cmd {cmd} has no dump") + + # Asking for one op builds the map in a different place in + # the kernel, it has to report what the full dump did. + single = _get_policy_map(ctrl, {'family-name': name, 'op': cmd}) + ksft_eq(single, {cmd: modes}, + comment=f"{name} cmd {cmd} policy differs from the dump") + + def getpolicy_by_op(_ctrl) -> None: """Query policy for specific ops, check attr names are resolved.""" ndev = NetdevFamily() @@ -122,6 +203,7 @@ def main() -> None: ksft_run([getfamily_do, getfamily_dump, getpolicy_dump, + getpolicy_op_map, getpolicy_by_op], args=(ctrl, )) ksft_exit() diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 2d844eb3aa6e..5e9c81e885e6 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -136,6 +136,19 @@ ovpn_create_ns() { ip netns add "ovpn_peer${1}" } +ovpn_peer_vpn_addr() { + local peer="$1" + local file + + if [ "${OVPN_PROTO}" == "UDP" ]; then + file="${OVPN_UDP_PEERS_FILE}" + else + file="${OVPN_TCP_PEERS_FILE}" + fi + + awk -v peer="${peer}" '$1 == peer {print $NF; exit}' "${file}" +} + ovpn_setup_ns() { local peer="ovpn_peer${1}" local server_ns="ovpn_peer0" diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index f4effa7580c0..3b612a8a18fe 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -650,6 +650,26 @@ err: return ret; } +static int ovpn_nl_put_vpn_addr(struct nl_msg *msg, + const struct ovpn_ctx *ovpn) +{ + if (!ovpn->peer_ip_set) + return 0; + + switch (ovpn->peer_ip.in4.sin_family) { + case AF_INET: + return nla_put_u32(msg, OVPN_A_PEER_VPN_IPV4, + ovpn->peer_ip.in4.sin_addr.s_addr); + case AF_INET6: + return nla_put(msg, OVPN_A_PEER_VPN_IPV6, + sizeof(struct in6_addr), + &ovpn->peer_ip.in6.sin6_addr); + default: + fprintf(stderr, "Invalid family for peer address\n"); + return -EAFNOSUPPORT; + } +} + static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp) { struct nlattr *attr; @@ -691,22 +711,9 @@ static int ovpn_new_peer(struct ovpn_ctx *ovpn, bool is_tcp) } } - if (ovpn->peer_ip_set) { - switch (ovpn->peer_ip.in4.sin_family) { - case AF_INET: - NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_VPN_IPV4, - ovpn->peer_ip.in4.sin_addr.s_addr); - break; - case AF_INET6: - NLA_PUT(ctx->nl_msg, OVPN_A_PEER_VPN_IPV6, - sizeof(struct in6_addr), - &ovpn->peer_ip.in6.sin6_addr); - break; - default: - fprintf(stderr, "Invalid family for peer address\n"); - goto nla_put_failure; - } - } + ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn); + if (ret) + goto nla_put_failure; nla_nest_end(ctx->nl_msg, attr); @@ -732,6 +739,10 @@ static int ovpn_set_peer(struct ovpn_ctx *ovpn) ovpn->keepalive_interval); NLA_PUT_U32(ctx->nl_msg, OVPN_A_PEER_KEEPALIVE_TIMEOUT, ovpn->keepalive_timeout); + + ret = ovpn_nl_put_vpn_addr(ctx->nl_msg, ovpn); + if (ret) + goto nla_put_failure; nla_nest_end(ctx->nl_msg, attr); ret = ovpn_nl_msg_send(ctx, NULL); @@ -1730,13 +1741,14 @@ static void usage(const char *cmd) fprintf(stderr, "\tmark: socket FW mark value\n"); fprintf(stderr, - "* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout>: set peer attributes\n"); + "* set_peer <iface> <peer_id> <keepalive_interval> <keepalive_timeout> [vpnaddr]: set peer attributes\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tpeer_id: peer ID of the peer to modify\n"); fprintf(stderr, "\tkeepalive_interval: interval for sending ping messages\n"); fprintf(stderr, "\tkeepalive_timeout: time after which a peer is timed out\n"); + fprintf(stderr, "\tvpnaddr: peer VPN IP\n"); fprintf(stderr, "* del_peer <iface> <peer_id>: delete peer\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); @@ -2090,6 +2102,8 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) return ret; ret = ovpn_new_peer(ovpn, false); + if (ret < 0) + return ret; ovpn_waitbg(); break; case CMD_NEW_MULTI_PEER: @@ -2331,6 +2345,12 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) "keepalive interval value out of range\n"); return -1; } + + if (argc > 6) { + ret = ovpn_parse_remote(ovpn, NULL, NULL, argv[6]); + if (ret < 0) + return -1; + } break; case CMD_DEL_PEER: if (argc < 4) diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index 9b5610837032..392109d5e14e 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -56,6 +56,76 @@ ovpn_prepare_network() { done } +ovpn_new_test_peer() { + local peer_id="$1" + + shift + ip netns exec ovpn_peer0 "${OVPN_CLI}" new_peer tun0 \ + "${peer_id}" none 65000 10.10.1.2 1 "$@" +} + +ovpn_set_peer_vpn_addr() { + ip netns exec ovpn_peer0 "${OVPN_CLI}" set_peer tun0 \ + "$1" 60 120 "$2" +} + +ovpn_run_vpn_addr_validation() { + local addr + local peer1_addr4 + local test_peer_id=$((OVPN_NUM_PEERS + 1)) + local test_peer_addr6="2001:db8::2" + # Do not include 0.0.0.0 or :: here. They are invalid on creation, but + # clear one address family on update and are valid if the other remains. + local -a invalid_addrs=( + "127.0.0.1" + "224.0.0.1" + "255.255.255.255" + "::1" + "::192.0.2.1" + "::ffff:192.0.2.1" + "ff02::1" + ) + + peer1_addr4=$(ovpn_peer_vpn_addr 1) + + ovpn_cmd_fail "reject peer without VPN address" \ + ovpn_new_test_peer "${test_peer_id}" + + for addr in "0.0.0.0" "::" "${invalid_addrs[@]}"; do + ovpn_cmd_fail "reject new peer VPN address ${addr}" \ + ovpn_new_test_peer "${test_peer_id}" "${addr}" + done + + ovpn_cmd_fail "reject duplicate IPv4 address on peer creation" \ + ovpn_new_test_peer "${test_peer_id}" "${peer1_addr4}" + ovpn_cmd_fail "reject clearing the last peer VPN address" \ + ovpn_set_peer_vpn_addr 1 0.0.0.0 + + for addr in "${invalid_addrs[@]}"; do + ovpn_cmd_fail "reject updated peer VPN address ${addr}" \ + ovpn_set_peer_vpn_addr 1 "${addr}" + done + + ovpn_cmd_fail "reject duplicate IPv4 address on peer update" \ + ovpn_set_peer_vpn_addr 2 "${peer1_addr4}" + + ovpn_cmd_ok "add peer IPv6 address" \ + ovpn_set_peer_vpn_addr 1 "${test_peer_addr6}" + ovpn_cmd_fail "reject duplicate IPv6 address on peer creation" \ + ovpn_new_test_peer "${test_peer_id}" "${test_peer_addr6}" + ovpn_cmd_fail "reject duplicate IPv6 address on peer update" \ + ovpn_set_peer_vpn_addr 2 "${test_peer_addr6}" + + ovpn_cmd_ok "clear peer IPv4 address" \ + ovpn_set_peer_vpn_addr 1 0.0.0.0 + ovpn_cmd_fail "reject clearing the remaining peer IPv6 address" \ + ovpn_set_peer_vpn_addr 1 :: + ovpn_cmd_ok "restore peer IPv4 address" \ + ovpn_set_peer_vpn_addr 1 "${peer1_addr4}" + ovpn_cmd_ok "clear peer IPv6 address" \ + ovpn_set_peer_vpn_addr 1 :: +} + ovpn_run_basic_traffic() { local p local header1 @@ -293,15 +363,16 @@ trap ovpn_stage_err ERR ktap_print_header if [ "${OVPN_FLOAT}" == "1" ]; then - ktap_set_plan 13 + ktap_set_plan 14 else - ktap_set_plan 12 + ktap_set_plan 13 fi ovpn_cleanup modprobe -q ovpn || true ovpn_run_stage "setup network topology" ovpn_prepare_network +ovpn_run_stage "validate peer VPN addresses" ovpn_run_vpn_addr_validation ovpn_run_stage "run baseline data traffic" ovpn_run_basic_traffic ovpn_run_stage "run LAN traffic behind peer1" ovpn_run_lan_traffic [ "${OVPN_FLOAT}" == "1" ] && ovpn_run_stage "run floating peer checks" \ diff --git a/tools/testing/selftests/net/packetdrill/config b/tools/testing/selftests/net/packetdrill/config index 83dde525c53c..b7df8bf30920 100644 --- a/tools/testing/selftests/net/packetdrill/config +++ b/tools/testing/selftests/net/packetdrill/config @@ -4,8 +4,8 @@ CONFIG_IPV6=y CONFIG_NET_NS=y CONFIG_NET_SCH_FIFO=y CONFIG_NET_SCH_FQ=y -CONFIG_SYSCTL=y CONFIG_SYN_COOKIES=y +CONFIG_SYSCTL=y CONFIG_TCP_CONG_CUBIC=y CONFIG_TCP_MD5SIG=y CONFIG_TUN=y diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 3cfe90e0f34f..4e06d0baaeec 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -164,10 +164,12 @@ all_test_bpfprogs := $(foreach prog,$(wildcard *.bpf.c),$(INCLUDE_DIR)/$(patsubs auto-test-targets := \ create_dsq \ dequeue \ + dequeue_iter \ enq_last_no_enq_fails \ ddsp_bogus_dsq_fail \ ddsp_vtimelocal_fail \ dsp_local_on \ + enable_cmask \ enq_select_cpu \ exit \ hotplug \ diff --git a/tools/testing/selftests/sched_ext/dequeue_iter.bpf.c b/tools/testing/selftests/sched_ext/dequeue_iter.bpf.c new file mode 100644 index 000000000000..76c2c71a90b6 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_iter.bpf.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ops.dequeue() of this scheduler iterates the user DSQ it consumes + * tasks from with bpf_iter_scx_dsq, which takes the DSQ lock. + * On a kernel that still runs ops.dequeue() with that lock held, the + * iteration self-deadlocks the CPU - this test wedges the system on + * unfixed kernels instead of failing cleanly. + * + * Copyright (c) 2026 fangqiurong <fangqiurong@kylinos.cn> + */ + +#include <scx/common.bpf.h> + +char _license[] SEC("license") = "GPL"; + +UEI_DEFINE(uei); + +#define TEST_DSQ_ID 1000 + +u64 dq_count; + +s32 BPF_STRUCT_OPS_SLEEPABLE(dequeue_iter_init) +{ + return scx_bpf_create_dsq(TEST_DSQ_ID, -1); +} + +s32 BPF_STRUCT_OPS(dequeue_iter_select_cpu, struct task_struct *p, + s32 prev_cpu, u64 wake_flags) +{ + return prev_cpu; +} + +void BPF_STRUCT_OPS(dequeue_iter_enqueue, struct task_struct *p, u64 enq_flags) +{ + scx_bpf_dsq_insert(p, TEST_DSQ_ID, SCX_SLICE_DFL, enq_flags); +} + +void BPF_STRUCT_OPS(dequeue_iter_dispatch, s32 cpu, struct task_struct *task) +{ + scx_bpf_dsq_move_to_local(TEST_DSQ_ID, 0); +} + +void BPF_STRUCT_OPS(dequeue_iter_dequeue, struct task_struct *p, u64 deq_flags) +{ + struct bpf_iter_scx_dsq it; + struct task_struct *t; + + if (!bpf_iter_scx_dsq_new(&it, TEST_DSQ_ID, 0)) { + while ((t = bpf_iter_scx_dsq_next(&it))) + ; + } + bpf_iter_scx_dsq_destroy(&it); + + __sync_fetch_and_add(&dq_count, 1); +} + +void BPF_STRUCT_OPS(dequeue_iter_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); + scx_bpf_destroy_dsq(TEST_DSQ_ID); +} + +SEC(".struct_ops.link") +struct sched_ext_ops dequeue_iter_ops = { + .init = (void *)dequeue_iter_init, + .select_cpu = (void *)dequeue_iter_select_cpu, + .enqueue = (void *)dequeue_iter_enqueue, + .dispatch = (void *)dequeue_iter_dispatch, + .dequeue = (void *)dequeue_iter_dequeue, + .exit = (void *)dequeue_iter_exit, + .timeout_ms = 1000U, + .name = "dequeue_iter", +}; diff --git a/tools/testing/selftests/sched_ext/dequeue_iter.c b/tools/testing/selftests/sched_ext/dequeue_iter.c new file mode 100644 index 000000000000..f60711bf3b39 --- /dev/null +++ b/tools/testing/selftests/sched_ext/dequeue_iter.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 fangqiurong <fangqiurong@kylinos.cn> + */ +#include <bpf/bpf.h> +#include <scx/common.h> +#include <time.h> +#include <unistd.h> +#include "dequeue_iter.bpf.skel.h" +#include "scx_test.h" + +#define DQ_TARGET 10 +#define DQ_DEADLINE_MS 3000 + +static unsigned long long now_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + return ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000; +} + +static enum scx_test_status setup(void **ctx) +{ + struct dequeue_iter *skel; + + skel = dequeue_iter__open(); + SCX_FAIL_IF(!skel, "Failed to open"); + SCX_ENUM_INIT(skel); + SCX_FAIL_IF(dequeue_iter__load(skel), "Failed to load skel"); + + *ctx = skel; + + return SCX_TEST_PASS; +} + +static enum scx_test_status run(void *ctx) +{ + struct dequeue_iter *skel = ctx; + struct bpf_link *link; + unsigned long long end; + + link = bpf_map__attach_struct_ops(skel->maps.dequeue_iter_ops); + SCX_FAIL_IF(!link, "Failed to attach scheduler"); + + end = now_ms() + DQ_DEADLINE_MS; + while (skel->bss->dq_count < DQ_TARGET && !UEI_EXITED(skel, uei) && + now_ms() < end) + usleep(100); + + bpf_link__destroy(link); + + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG)); + + if (skel->bss->dq_count < DQ_TARGET) { + SCX_ERR("ops.dequeue() fired only %llu times", + (unsigned long long)skel->bss->dq_count); + return SCX_TEST_FAIL; + } + + return SCX_TEST_PASS; +} + +static void cleanup(void *ctx) +{ + struct dequeue_iter *skel = ctx; + + dequeue_iter__destroy(skel); +} + +struct scx_test dequeue_iter = { + .name = "dequeue_iter", + .description = "Verify ops.dequeue() can iterate its source user DSQ", + .setup = setup, + .run = run, + .cleanup = cleanup, +}; +REGISTER_SCX_TEST(&dequeue_iter) diff --git a/tools/testing/selftests/sched_ext/enable_cmask.bpf.c b/tools/testing/selftests/sched_ext/enable_cmask.bpf.c new file mode 100644 index 000000000000..0068f3c7ab3c --- /dev/null +++ b/tools/testing/selftests/sched_ext/enable_cmask.bpf.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A cid-form scheduler checking the cmask cid-form ops.enable() receives: the + * header, every cid bit against p->cpus_ptr, and that set_cmask() follows with + * the same mask before set_weight() and before the task first becomes runnable, + * and never runs before enable(). + * + * Copyright (c) 2026 Tejun Heo <tj@kernel.org> + */ +#include <scx/common.bpf.h> + +char _license[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_ARENA); + __uint(map_flags, BPF_F_MMAPABLE); + __uint(max_entries, 1 << 16); +} arena SEC(".maps"); + +struct task_ctx { + u64 enable_fp; /* fingerprint of the mask enable() received */ + bool enabled; + bool pending; /* enable() ran, the initial set_cmask() hasn't */ +}; + +struct { + __uint(type, BPF_MAP_TYPE_TASK_STORAGE); + __uint(map_flags, BPF_F_NO_PREALLOC); + __type(key, int); + __type(value, struct task_ctx); +} task_ctx_stor SEC(".maps"); + +/* details of a cid bit mismatch, filled by check_mask() */ +struct mask_mismatch { + s32 cid; + bool want; + bool got; +}; + +u64 nr_enable, nr_initial_set_cmask, nr_set_cmask, nr_set_weight; + +UEI_DEFINE(uei); + +static struct task_ctx *lookup_task_ctx(struct task_struct *p) +{ + struct task_ctx *tctx; + + tctx = bpf_task_storage_get(&task_ctx_stor, p, 0, 0); + if (!tctx) + scx_bpf_error("task_ctx lookup failed for %s[%d]", p->comm, p->pid); + return tctx; +} + +/* + * Verify @m's header and every cid bit against @p's cpumask and fingerprint the + * bits into @fp. Return 0 on success, -EINVAL on a bad header, -ENOENT on a cid + * without a cpu and -EIO on a bit mismatch with the details in @mm. + */ +static int check_mask(struct task_struct *p, const struct scx_cmask __arena *m, u64 *fp, + struct mask_mismatch *mm) +{ + u32 nr_cids = scx_bpf_nr_cids(); + u64 h = 0; + s32 cid; + + if (m->base || m->nr_cids != nr_cids || m->alloc_words != CMASK_NR_WORDS(nr_cids)) + return -EINVAL; + + bpf_for(cid, 0, nr_cids) { + bool want, got; + s32 cpu; + + cpu = scx_bpf_cid_to_cpu(cid); + if (cpu < 0) + return -ENOENT; + want = bpf_cpumask_test_cpu(cpu, p->cpus_ptr); + got = cmask_test(cid, m); + if (want != got) { + mm->cid = cid; + mm->want = want; + mm->got = got; + return -EIO; + } + h = h * 31 + got; + } + + *fp = h; + return 0; +} + +s32 BPF_STRUCT_OPS_SLEEPABLE(enable_cmask_init_task, struct task_struct *p, + struct scx_init_task_args *args) +{ + if (!bpf_task_storage_get(&task_ctx_stor, p, 0, BPF_LOCAL_STORAGE_GET_F_CREATE)) + return -ENOMEM; + return 0; +} + +void BPF_STRUCT_OPS(enable_cmask_enable, struct task_struct *p, struct scx_enable_args *args) +{ + struct scx_cmask __arena *m = (struct scx_cmask __arena *)args->cmask_arena_addr; + struct mask_mismatch mm = {}; + struct task_ctx *tctx; + int ret; + + asm volatile("" :: "r"(&arena)); + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + __sync_fetch_and_add(&nr_enable, 1); + if (tctx->enabled || tctx->pending) { + scx_bpf_error("enable: %s[%d] enabled twice", p->comm, p->pid); + return; + } + + ret = check_mask(p, m, &tctx->enable_fp, &mm); + if (ret) { + scx_bpf_error("enable: %s[%d] cmask check failed %d cid=%d want=%d got=%d", + p->comm, p->pid, ret, mm.cid, mm.want, mm.got); + return; + } + tctx->enabled = true; + tctx->pending = true; +} + +void BPF_STRUCT_OPS(enable_cmask_set_cmask, struct task_struct *p, + struct scx_cmask __arena *m) +{ + struct mask_mismatch mm = {}; + struct task_ctx *tctx; + u64 fp; + int ret; + + asm volatile("" :: "r"(&arena)); + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + __sync_fetch_and_add(&nr_set_cmask, 1); + if (!tctx->enabled) { + scx_bpf_error("set_cmask: %s[%d] not enabled", p->comm, p->pid); + return; + } + + ret = check_mask(p, m, &fp, &mm); + if (ret) { + scx_bpf_error("set_cmask: %s[%d] cmask check failed %d cid=%d want=%d got=%d", + p->comm, p->pid, ret, mm.cid, mm.want, mm.got); + return; + } + + if (tctx->pending) { + if (fp != tctx->enable_fp) { + scx_bpf_error("set_cmask: %s[%d] initial mask differs from enable()", + p->comm, p->pid); + return; + } + tctx->pending = false; + __sync_fetch_and_add(&nr_initial_set_cmask, 1); + } +} + +void BPF_STRUCT_OPS(enable_cmask_set_weight, struct task_struct *p, u32 weight) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + __sync_fetch_and_add(&nr_set_weight, 1); + if (tctx->pending) + scx_bpf_error("set_weight: %s[%d] before the initial set_cmask()", p->comm, + p->pid); +} + +void BPF_STRUCT_OPS(enable_cmask_runnable, struct task_struct *p, u64 enq_flags) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + if (tctx->pending) + scx_bpf_error("runnable: %s[%d] before the initial set_cmask()", p->comm, + p->pid); +} + +void BPF_STRUCT_OPS(enable_cmask_disable, struct task_struct *p) +{ + struct task_ctx *tctx; + + tctx = lookup_task_ctx(p); + if (!tctx) + return; + + tctx->enabled = false; + tctx->pending = false; +} + +void BPF_STRUCT_OPS(enable_cmask_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + +SCX_OPS_CID_DEFINE(enable_cmask_ops, + .init_task = (void *)enable_cmask_init_task, + .enable = (void *)enable_cmask_enable, + .set_cmask = (void *)enable_cmask_set_cmask, + .set_weight = (void *)enable_cmask_set_weight, + .runnable = (void *)enable_cmask_runnable, + .disable = (void *)enable_cmask_disable, + .exit = (void *)enable_cmask_exit, + .flags = SCX_OPS_SWITCH_PARTIAL, + .name = "enable_cmask"); diff --git a/tools/testing/selftests/sched_ext/enable_cmask.c b/tools/testing/selftests/sched_ext/enable_cmask.c new file mode 100644 index 000000000000..556bcad4431d --- /dev/null +++ b/tools/testing/selftests/sched_ext/enable_cmask.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Tejun Heo <tj@kernel.org> */ +#define _GNU_SOURCE +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <sys/wait.h> +#include <bpf/bpf.h> +#include <scx/common.h> +#include "enable_cmask.bpf.skel.h" +#include "scx_test.h" + +#define SCHED_EXT 7 +#define NR_CHILDREN 8 +#define MAX_CPUS 1024 + +static int cpus[MAX_CPUS]; +static int nr_cpus; + +static void spin_ms(int ms) +{ + struct timespec start, now; + + clock_gettime(CLOCK_MONOTONIC, &start); + do { + clock_gettime(CLOCK_MONOTONIC, &now); + } while ((now.tv_sec - start.tv_sec) * 1000 + + (now.tv_nsec - start.tv_nsec) / 1000000 < ms); +} + +static int pin(pid_t pid, int idx) +{ + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpus[idx % nr_cpus], &set); + return sched_setaffinity(pid, sizeof(set), &set); +} + +/* + * Pin, switch to SCHED_EXT for a class-switch enable, fork a grandchild that + * inherits the policy for a fork-path enable, then change affinity a few times + * while running for set_cmask() on live tasks. + */ +static int child(int idx) +{ + struct sched_param param = {}; + int i, status; + pid_t pid; + + if (pin(0, idx) || sched_setscheduler(0, SCHED_EXT, ¶m)) + return 1; + + pid = fork(); + if (pid < 0) + return 1; + if (!pid) { + spin_ms(20); + return 0; + } + + for (i = 1; i <= 4; i++) { + if (pin(0, idx + i)) + return 1; + spin_ms(5); + } + + return waitpid(pid, &status, 0) == pid && !status ? 0 : 1; +} + +static enum scx_test_status run(void *ctx) +{ + struct enable_cmask *skel; + struct bpf_link *link; + pid_t pids[NR_CHILDREN]; + cpu_set_t set; + int i, status, failed = 0; + + if (!__COMPAT_struct_has_field("scx_enable_args", "cmask_arena_addr")) + return SCX_TEST_SKIP; + + SCX_FAIL_IF(sched_getaffinity(0, sizeof(set), &set), "Failed to read affinity"); + for (i = 0; i < MAX_CPUS && i < CPU_SETSIZE; i++) + if (CPU_ISSET(i, &set)) + cpus[nr_cpus++] = i; + if (nr_cpus < 2) + return SCX_TEST_SKIP; + + skel = enable_cmask__open(); + SCX_FAIL_IF(!skel, "Failed to open"); + SCX_ENUM_INIT(skel); + SCX_FAIL_IF(enable_cmask__load(skel), "Failed to load skel"); + + link = bpf_map__attach_struct_ops(skel->maps.enable_cmask_ops); + SCX_FAIL_IF(!link, "Failed to attach struct_ops"); + + for (i = 0; i < NR_CHILDREN; i++) { + pids[i] = fork(); + SCX_FAIL_IF(pids[i] < 0, "Failed to fork"); + if (!pids[i]) + exit(child(i)); + } + + /* affinity changes from the outside race with the children's own */ + for (i = 0; i < NR_CHILDREN; i++) + pin(pids[i], i + NR_CHILDREN); + + for (i = 0; i < NR_CHILDREN; i++) { + if (waitpid(pids[i], &status, 0) != pids[i] || status) + failed++; + } + + bpf_link__destroy(link); + + SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_UNREG)); + SCX_EQ(failed, 0); + SCX_GE(skel->bss->nr_enable, 2 * NR_CHILDREN); + SCX_EQ(skel->bss->nr_initial_set_cmask, skel->bss->nr_enable); + SCX_GT(skel->bss->nr_set_cmask, skel->bss->nr_initial_set_cmask); + SCX_GE(skel->bss->nr_set_weight, skel->bss->nr_enable); + printf("enable=%lu initial_set_cmask=%lu set_cmask=%lu set_weight=%lu\n", + (unsigned long)skel->bss->nr_enable, + (unsigned long)skel->bss->nr_initial_set_cmask, + (unsigned long)skel->bss->nr_set_cmask, + (unsigned long)skel->bss->nr_set_weight); + + enable_cmask__destroy(skel); + return SCX_TEST_PASS; +} + +struct scx_test enable_cmask = { + .name = "enable_cmask", + .description = "Check the cid-form ops.enable() cmask and the set_cmask() after it", + .run = run, +}; +REGISTER_SCX_TEST(&enable_cmask) diff --git a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json index e2b03f2b5e89..edc5148a8d97 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json +++ b/tools/testing/selftests/tc-testing/tc-tests/filters/u32.json @@ -376,5 +376,53 @@ "teardown": [ "$TC qdisc del dev $DUMMY clsact" ] + }, + { + "id": "35fc", + "name": "u32 manual table then auto table: auto allocation must not alias a live manual handle", + "category": [ + "filter", + "u32" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 ingress protocol ip pref 1 handle 801: u32 divisor 16" + ], + "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol ip pref 2 u32 divisor 16", + "expExitCode": "0", + "verifyCmd": "$TC -d filter show dev $DEV1 ingress", + "matchPattern": "fh 801:", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] + }, + { + "id": "a6e8", + "name": "u32 manual table add/del does not leak its idr entry (re-adding the same handle succeeds)", + "category": [ + "filter", + "u32" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DEV1 ingress", + "$TC filter add dev $DEV1 ingress protocol ip pref 1 u32 divisor 16", + "$TC filter add dev $DEV1 ingress protocol ip pref 5 handle 901: u32 divisor 1", + "$TC filter del dev $DEV1 ingress protocol ip pref 5 handle 901: u32" + ], + "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol ip pref 6 handle 901: u32 divisor 1", + "expExitCode": "0", + "verifyCmd": "$TC -d filter show dev $DEV1 ingress", + "matchPattern": "fh 901: ht divisor 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 ingress" + ] } ] diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json index c98c339424d4..4f6bbb8b57f9 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/hfsc.json @@ -169,5 +169,39 @@ "teardown": [ "$TC qdisc del dev $DUMMY handle 1: root" ] + }, + { + "id": "8c39", + "name": "HFSC classify walk still reaches leaf after lateral drift", + "category": [ + "qdisc", + "hfsc" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "ip link set lo up", + "$TC qdisc add dev lo handle 1: root hfsc default 30", + "$TC class add dev lo parent 1: classid 1:1 hfsc rt m2 100kbit", + "$TC class add dev lo parent 1:1 classid 1:10 hfsc rt m2 50kbit", + "$TC class add dev lo parent 1: classid 1:2 hfsc rt m2 100kbit", + "$TC filter add dev lo parent 1: protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:1", + "$TC filter add dev lo parent 1:1 protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:2", + "$TC class add dev lo parent 1:2 classid 1:20 hfsc rt m2 10kbit", + "$TC class add dev lo parent 1: classid 1:3 hfsc rt m2 100kbit", + "$TC filter add dev lo parent 1:2 protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:3", + "$TC class add dev lo parent 1:3 classid 1:30 hfsc rt m2 10kbit", + "$TC class add dev lo parent 1:3 classid 1:31 hfsc rt m2 100kbit", + "$TC filter add dev lo parent 1:3 protocol ip prio 1 u32 match u8 0 0 at 0 flowid 1:31" + ], + "cmdUnderTest": "ping -n -c 10 -W 1 127.0.0.1", + "expExitCode": "0", + "verifyCmd": "$TC -s class show dev lo", + "matchPattern": "class hfsc 1:31 parent 1:3 rt[^\\n]*\\n Sent [0-9]+ bytes [1-9][0-9]* pkt", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev lo handle 1: root" + ] } ] diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 65eb26a0520d..85f42289748d 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1117,7 +1117,7 @@ static struct kvm *kvm_create_vm(unsigned long type, const char *fdname) rcuwait_init(&kvm->mn_memslots_update_rcuwait); xa_init(&kvm->vcpu_array); #ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES - xa_init(&kvm->mem_attr_array); + xa_init_flags(&kvm->mem_attr_array, XA_FLAGS_ACCOUNT); #endif INIT_LIST_HEAD(&kvm->gpc_list); @@ -2447,14 +2447,36 @@ bool kvm_range_has_memory_attributes(struct kvm *kvm, gfn_t start, gfn_t end, return (kvm_get_memory_attributes(kvm, start) & mask) == attrs; guard(rcu)(); - if (!attrs) - return !xas_find(&xas, end - 1); + /* + * Lookup the entry for each index instead of iterating over the xarray + * as KVM deletes/nullifies entries to represent "no attributes", and + * the xas index is effectively invalid when no entry is found. I.e. + * matching non-zero attributes for *every* entry effectively requires + * a manually lookup for each index. + * + * Skip pre-allocated, reserved entries, or restart the lookup if the + * xarray was concurrently modified, via xas_retry() ("retry" means the + * entry holds an internal xarray value, i.e. is either invalid or NULL + * from the caller's perspective). + * + * Use xas_next() when looking for non-zero attributes to optimize for + * the case where the start of the range (or the entire range) doesn't + * have any attributes, as xas_next() returns literally the next entry, + * whereas xas_next_entry() returns the next non-NULL entry (bounded by + * a maximum index). + */ for (index = start; index < end; index++) { do { - entry = xas_next(&xas); + entry = attrs ? xas_next(&xas) : + xas_next_entry(&xas, end - 1); } while (xas_retry(&xas, entry)); + if (!entry) + return !attrs; + + WARN_ON_ONCE(!xa_to_value(entry)); + if (xas.xa_index != index || (xa_to_value(entry) & mask) != attrs) return false; @@ -2571,9 +2593,10 @@ static int kvm_vm_set_mem_attributes(struct kvm *kvm, gfn_t start, gfn_t end, /* * Reserve memory ahead of time to avoid having to deal with failures - * partway through setting the new attributes. + * partway through setting the new attributes. Storing NULL never + * allocates, so no reservations are needed when clearing. */ - for (i = start; i < end; i++) { + for (i = start; entry && i < end; i++) { r = xa_reserve(&kvm->mem_attr_array, i, GFP_KERNEL_ACCOUNT); if (r) goto out_unlock; |
