From 70fcdc82cf4a10cf1d220e036db787cd2d33e65a Mon Sep 17 00:00:00 2001 From: Charan Pedumuru Date: Tue, 27 Jan 2026 17:42:13 +0000 Subject: [PATCH 001/176] dt-bindings: usb: ti,omap4-musb: convert to DT schema Convert OMAP MUSB USB OTG Controller binding to DT schema. Changes during conversion: - Include "interrupts" and "interrupt-names" properties in the YAML, as they are used by many in-tree DTS files. - Extend the "power" property to allow the value 150 (in addition to existing values), since this is present in several in-tree DTS examples. - Drop the ti,hwmods property, as it is not used by any in-tree DTS files. Signed-off-by: Charan Pedumuru Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260127-ti-usb-v2-1-9dd6a65b43df@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/ti,omap4-musb.yaml | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/ti,omap4-musb.yaml diff --git a/Documentation/devicetree/bindings/usb/ti,omap4-musb.yaml b/Documentation/devicetree/bindings/usb/ti,omap4-musb.yaml new file mode 100644 index 000000000000..a3d15f217658 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ti,omap4-musb.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ti,omap4-musb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments OMAP MUSB USB OTG Controller + +maintainers: + - Felipe Balbi + +description: + Texas Instruments glue layer for the Mentor Graphics MUSB OTG controller. + Handles SoC-specific integration including PHY interface bridging(ULPI/ + UTMI), interrupt aggregation, DMA engine coordination (internal/ + external), VBUS/session control via control module mailbox, and + clock/reset management. Provides fixed hardware configuration parameters + to the generic MUSB core driver. + +properties: + compatible: + enum: + - ti,omap3-musb + - ti,omap4-musb + + reg: + maxItems: 1 + + interrupts: + minItems: 1 + maxItems: 2 + + interrupt-names: + minItems: 1 + items: + - const: mc + - const: dma + + multipoint: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Indicates the MUSB controller supports multipoint. This is a MUSB + configuration-specific setting. + const: 1 + + num-eps: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Specifies the number of endpoints. This is a MUSB configuration + specific setting. + const: 16 + + ram-bits: + description: Specifies the RAM address size. + const: 12 + + interface-type: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Describes the type of interface between the controller and the PHY. + 0 for ULPI, 1 for UTMI. + enum: [0, 1] + + mode: + $ref: /schemas/types.yaml#/definitions/uint32 + description: 1 for HOST, 2 for PERIPHERAL, 3 for OTG. + enum: [1, 2, 3] + + power: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Indicates the maximum current the controller can supply when + operating in host mode. A value of 50 corresponds to 100 mA, and a + value of 150 corresponds to 300 mA. + enum: [50, 150] + + phys: + maxItems: 1 + + phy-names: + const: usb2-phy + + usb-phy: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: Phandle for the PHY device. + deprecated: true + + ctrl-module: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle of the control module this glue uses to write to mailbox. + +required: + - reg + - compatible + - interrupts + - interrupt-names + +unevaluatedProperties: false + +examples: + - | + #include + usb@4a0ab000 { + compatible = "ti,omap4-musb"; + reg = <0x4a0ab000 0x1000>; + interrupts = , + ; + interrupt-names = "mc", "dma"; + multipoint = <1>; + num-eps = <16>; + ram-bits = <12>; + ctrl-module = <&omap_control_usb>; + phys = <&usb2_phy>; + phy-names = "usb2-phy"; + interface-type = <1>; + mode = <3>; + power = <50>; + }; +... From ca47d656ad6a8250a0dd2c60fb8d6c94d9cc56e1 Mon Sep 17 00:00:00 2001 From: Charan Pedumuru Date: Tue, 27 Jan 2026 17:42:14 +0000 Subject: [PATCH 002/176] dt-bindings: usb: ti,dwc3: convert to DT schema Convert OMAP DWC3 USB Glue Layer binding to DT schema. Changes made during the conversion: - Drop the ti,hwmods property, as it is not used by any in-tree DTS files. Signed-off-by: Charan Pedumuru Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260127-ti-usb-v2-2-9dd6a65b43df@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/omap-usb.txt | 80 -------------- .../devicetree/bindings/usb/ti,dwc3.yaml | 100 ++++++++++++++++++ 2 files changed, 100 insertions(+), 80 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/omap-usb.txt create mode 100644 Documentation/devicetree/bindings/usb/ti,dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/omap-usb.txt b/Documentation/devicetree/bindings/usb/omap-usb.txt deleted file mode 100644 index f0dbc5ae45ae..000000000000 --- a/Documentation/devicetree/bindings/usb/omap-usb.txt +++ /dev/null @@ -1,80 +0,0 @@ -OMAP GLUE AND OTHER OMAP SPECIFIC COMPONENTS - -OMAP MUSB GLUE - - compatible : Should be "ti,omap4-musb" or "ti,omap3-musb" - - ti,hwmods : must be "usb_otg_hs" - - multipoint : Should be "1" indicating the musb controller supports - multipoint. This is a MUSB configuration-specific setting. - - num-eps : Specifies the number of endpoints. This is also a - MUSB configuration-specific setting. Should be set to "16" - - ram-bits : Specifies the ram address size. Should be set to "12" - - interface-type : This is a board specific setting to describe the type of - interface between the controller and the phy. It should be "0" or "1" - specifying ULPI and UTMI respectively. - - mode : Should be "3" to represent OTG. "1" signifies HOST and "2" - represents PERIPHERAL. - - power : Should be "50". This signifies the controller can supply up to - 100mA when operating in host mode. - - usb-phy : the phandle for the PHY device - - phys : the phandle for the PHY device (used by generic PHY framework) - - phy-names : the names of the PHY corresponding to the PHYs present in the - *phy* phandle. - -Optional properties: - - ctrl-module : phandle of the control module this glue uses to write to - mailbox - -SOC specific device node entry -usb_otg_hs: usb_otg_hs@4a0ab000 { - compatible = "ti,omap4-musb"; - ti,hwmods = "usb_otg_hs"; - multipoint = <1>; - num-eps = <16>; - ram-bits = <12>; - ctrl-module = <&omap_control_usb>; - phys = <&usb2_phy>; - phy-names = "usb2-phy"; -}; - -Board specific device node entry -&usb_otg_hs { - interface-type = <1>; - mode = <3>; - power = <50>; -}; - -OMAP DWC3 GLUE - - compatible : Should be - * "ti,dwc3" for OMAP5 and DRA7 - * "ti,am437x-dwc3" for AM437x - - ti,hwmods : Should be "usb_otg_ss" - - reg : Address and length of the register set for the device. - - interrupts : The irq number of this device that is used to interrupt the - MPU - - #address-cells, #size-cells : Must be present if the device has sub-nodes - - utmi-mode : controls the source of UTMI/PIPE status for VBUS and OTG ID. - It should be set to "1" for HW mode and "2" for SW mode. - - ranges: the child address space are mapped 1:1 onto the parent address space - -Optional Properties: - - extcon : phandle for the extcon device omap dwc3 uses to detect - connect/disconnect events. - - vbus-supply : phandle to the regulator device tree node if needed. - -Sub-nodes: -The dwc3 core should be added as subnode to omap dwc3 glue. -- dwc3 : - The binding details of dwc3 can be found in: - Documentation/devicetree/bindings/usb/snps,dwc3.yaml - -omap_dwc3 { - compatible = "ti,dwc3"; - ti,hwmods = "usb_otg_ss"; - reg = <0x4a020000 0x1ff>; - interrupts = <0 93 4>; - #address-cells = <1>; - #size-cells = <1>; - utmi-mode = <2>; - ranges; -}; - diff --git a/Documentation/devicetree/bindings/usb/ti,dwc3.yaml b/Documentation/devicetree/bindings/usb/ti,dwc3.yaml new file mode 100644 index 000000000000..77ac11c3b2db --- /dev/null +++ b/Documentation/devicetree/bindings/usb/ti,dwc3.yaml @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/ti,dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments OMAP DWC3 USB Glue Layer + +maintainers: + - Felipe Balbi + +description: + Texas Instruments glue layer for Synopsys DesignWare USB3 (DWC3) + controller on OMAP and AM43xx SoCs. Manages SoC-specific integration + including register mapping, interrupt routing, UTMI/PIPE interface mode + selection (HW/SW), and child DWC3 core instantiation via address space + translation. Supports both legacy single-instance and multi-instance + (numbered) configurations. + +properties: + compatible: + enum: + - ti,dwc3 + - ti,am437x-dwc3 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + utmi-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Controls the source of UTMI/PIPE status for VBUS and OTG ID. + 1 for HW mode, 2 for SW mode. + enum: [1, 2] + + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + + ranges: true + + extcon: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle for the extcon device used to detect connect/ + disconnect events. + + vbus-supply: + description: Phandle to the regulator device tree node if needed. + +patternProperties: + "^usb@[0-9a-f]+$": + type: object + $ref: snps,dwc3.yaml# + unevaluatedProperties: false + +required: + - reg + - compatible + - interrupts + - "#address-cells" + - "#size-cells" + - utmi-mode + - ranges + +unevaluatedProperties: false + +examples: + - | + #include + omap_dwc3_1@0 { + compatible = "ti,dwc3"; + reg = <0x0 0x10000>; + interrupts = ; + #address-cells = <1>; + #size-cells = <1>; + utmi-mode = <2>; + ranges = <0 0 0x20000>; + + usb@10000 { + compatible = "snps,dwc3"; + reg = <0x10000 0x17000>; + interrupts = , + , + ; + interrupt-names = "peripheral", "host", "otg"; + phys = <&usb2_phy1>, <&usb3_phy1>; + phy-names = "usb2-phy", "usb3-phy"; + maximum-speed = "super-speed"; + dr_mode = "otg"; + snps,dis_u3_susphy_quirk; + snps,dis_u2_susphy_quirk; + }; + }; +... From 9ac1befac36c47f419d29c96bd7ba589dfe94422 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 12 Feb 2026 17:40:26 +0800 Subject: [PATCH 003/176] dt-bindings: usb: introduce nxp,imx-dwc3 The i.MX USB glue and DWC3 core are closely coupled. Describe the i.MX USB block in a single block will bring more benefits than a parent-child relation. The new binding is used to describe flattened usb controller node. It's a copy of the legacy binding fsl,imx8mp-dwc3.yaml with the needed modifications. Reviewed-by: Rob Herring (Arm) Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260212-add-flatten-dts-based-dwc3-imx-driver-v5-1-ff04a75ce221@nxp.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/fsl,imx8mp-dwc3.yaml | 2 + .../devicetree/bindings/usb/nxp,imx-dwc3.yaml | 123 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/nxp,imx-dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml b/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml index 73e7a60a0060..66d368e65c0a 100644 --- a/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/fsl,imx8mp-dwc3.yaml @@ -10,6 +10,8 @@ title: NXP iMX8MP Soc USB Controller maintainers: - Li Jun +deprecated: true + properties: compatible: oneOf: diff --git a/Documentation/devicetree/bindings/usb/nxp,imx-dwc3.yaml b/Documentation/devicetree/bindings/usb/nxp,imx-dwc3.yaml new file mode 100644 index 000000000000..1911e71f01eb --- /dev/null +++ b/Documentation/devicetree/bindings/usb/nxp,imx-dwc3.yaml @@ -0,0 +1,123 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2026 NXP +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/nxp,imx-dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP i.MX Soc USB Controller + +maintainers: + - Xu Yang + +properties: + compatible: + oneOf: + - items: + - enum: + - nxp,imx94-dwc3 + - nxp,imx95-dwc3 + - const: nxp,imx8mp-dwc3 + - const: nxp,imx8mp-dwc3 + + reg: + items: + - description: DWC3 core registers + - description: HSIO Block Control registers + - description: Wrapper registers of dwc3 core + + reg-names: + items: + - const: core + - const: blkctl + - const: glue + + interrupts: + items: + - description: DWC3 controller interrupt + - description: Wakeup interrupt from glue logic + + interrupt-names: + items: + - const: dwc_usb3 + - const: wakeup + + iommus: + maxItems: 1 + + clocks: + items: + - description: System hsio root clock + - description: SoC Bus Clock for AHB/AXI/Native + - description: Reference clock for generating ITP when UTMI/ULPI PHY is suspended + - description: Suspend clock used for usb wakeup logic + + clock-names: + items: + - const: hsio + - const: bus_early + - const: ref + - const: suspend + + fsl,permanently-attached: + type: boolean + description: + Indicates if the device attached to a downstream port is + permanently attached + + fsl,disable-port-power-control: + type: boolean + description: + Indicates whether the host controller implementation includes port + power control. Defines Bit 3 in capability register (HCCPARAMS) + + fsl,over-current-active-low: + type: boolean + description: + Over current signal polarity is active low + + fsl,power-active-low: + type: boolean + description: + Power pad (PWR) polarity is active low + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - power-domains + +allOf: + - $ref: snps,dwc3-common.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + usb@4c100000 { + compatible = "nxp,imx94-dwc3", "nxp,imx8mp-dwc3"; + reg = <0x4c100000 0x10000>, + <0x4c010010 0x04>, + <0x4c1f0000 0x20>; + reg-names = "core", "blkctl", "glue"; + clocks = <&scmi_clk 74>, //IMX94_CLK_HSIO + <&scmi_clk 74>, //IMX94_CLK_HSIO + <&scmi_clk 2>, //IMX94_CLK_24M + <&scmi_clk 1>; //IMX94_CLK_32K + clock-names = "hsio", "bus_early", "ref", "suspend"; + interrupts = , + ; + interrupt-names = "dwc_usb3", "wakeup"; + power-domains = <&scmi_devpd 13>; //IMX94_PD_HSIO_TOP + phys = <&usb3_phy>, <&usb3_phy>; + phy-names = "usb2-phy", "usb3-phy"; + snps,gfladj-refclk-lpm-sel-quirk; + snps,parkmode-disable-ss-quirk; + }; From a717321ad7c4046eec60fa9469e1401d20071d5a Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 12 Feb 2026 17:40:27 +0800 Subject: [PATCH 004/176] usb: dwc3: add needs_full_reinit flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current design assumes that the controller remains powered when wakeup is enabled. However, some SoCs provide wakeup capability even when the controller itself is powered down, using separate dedicated wakeup logic. This allows additional power savings, but requires the controller to be fully re‑initialized after system resume. To support these SoCs, introduce a flag needs_full_reinit for the purpose. And the glue layer needs to indicate if the controller needs this behavior by setting a same flag needs_full_reinit in dwc3_properties. Reviewed-by: Frank Li Signed-off-by: Xu Yang Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260212-add-flatten-dts-based-dwc3-imx-driver-v5-2-ff04a75ce221@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 9 +++++++-- drivers/usb/dwc3/core.h | 3 +++ drivers/usb/dwc3/glue.h | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 161a4d58b2ce..cacc4ec9f7ce 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1675,6 +1675,9 @@ static void dwc3_get_software_properties(struct dwc3 *dwc, u16 gsbuscfg0_reqinfo; int ret; + if (properties->needs_full_reinit) + dwc->needs_full_reinit = true; + dwc->gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED; if (properties->gsbuscfg0_reqinfo != @@ -2479,7 +2482,8 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) dwc3_core_exit(dwc); break; case DWC3_GCTL_PRTCAP_HOST: - if (!PMSG_IS_AUTO(msg) && !device_may_wakeup(dwc->dev)) { + if (!PMSG_IS_AUTO(msg) && + (!device_may_wakeup(dwc->dev) || dwc->needs_full_reinit)) { dwc3_core_exit(dwc); break; } @@ -2542,7 +2546,8 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) dwc3_gadget_resume(dwc); break; case DWC3_GCTL_PRTCAP_HOST: - if (!PMSG_IS_AUTO(msg) && !device_may_wakeup(dwc->dev)) { + if (!PMSG_IS_AUTO(msg) && + (!device_may_wakeup(dwc->dev) || dwc->needs_full_reinit)) { ret = dwc3_core_init_for_resume(dwc); if (ret) return ret; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index a35b3db1f9f3..67bcc8dccc89 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1119,6 +1119,8 @@ struct dwc3_glue_ops { * @usb3_lpm_capable: set if hadrware supports Link Power Management * @usb2_lpm_disable: set to disable usb2 lpm for host * @usb2_gadget_lpm_disable: set to disable usb2 lpm for gadget + * @needs_full_reinit: set to indicate the core may lose power and need full + initialization during system pm * @disable_scramble_quirk: set if we enable the disable scramble quirk * @u2exit_lfps_quirk: set if we enable u2exit lfps quirk * @u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk @@ -1373,6 +1375,7 @@ struct dwc3 { unsigned usb3_lpm_capable:1; unsigned usb2_lpm_disable:1; unsigned usb2_gadget_lpm_disable:1; + unsigned needs_full_reinit:1; unsigned disable_scramble_quirk:1; unsigned u2exit_lfps_quirk:1; diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h index df86e14cb706..d738e1739ae0 100644 --- a/drivers/usb/dwc3/glue.h +++ b/drivers/usb/dwc3/glue.h @@ -12,9 +12,12 @@ /** * dwc3_properties: DWC3 core properties * @gsbuscfg0_reqinfo: Value to be programmed in the GSBUSCFG0.REQINFO field + * @needs_full_reinit: indicate the controller may not remain power during system + * pm and need full initialization */ struct dwc3_properties { u32 gsbuscfg0_reqinfo; + unsigned needs_full_reinit:1; }; #define DWC3_DEFAULT_PROPERTIES ((struct dwc3_properties){ \ From 76fc9452a6bfb63a297fa0410d5368a691ed411b Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 12 Feb 2026 17:40:28 +0800 Subject: [PATCH 005/176] usb: dwc3: introduce flatten model driver of i.MX Soc The i.MX USB glue and DWC3 core are closely coupled. Describe the i.MX USB block in a single block will bring more benefits than a parent- child relation. To support the flatten model devicetree, DWC3 USB core driver already support to directly register and initialize the core in glue layer using one device. And many notification can be received in glue layer timely and proper actions can be executed accordingly. To align with mainstream, introduce a new driver to support flatten dwc3 devicetree model for i.MX Soc. Besides this driver disables wakeup irq when system is active, no other function change in this version compared to dwc3-imx8mp.c. After this new driver is settled, only maintenance fixes will be added to dwc3-imx8mp.c, new features will only be added to this new driver. Once all users switch to this new one, the legacy driver will be removed at proper time. Signed-off-by: Xu Yang Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260212-add-flatten-dts-based-dwc3-imx-driver-v5-3-ff04a75ce221@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/Kconfig | 12 + drivers/usb/dwc3/Makefile | 1 + drivers/usb/dwc3/dwc3-imx.c | 442 ++++++++++++++++++++++++++++++++++++ 3 files changed, 455 insertions(+) create mode 100644 drivers/usb/dwc3/dwc3-imx.c diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 240b15bc52cb..18169727a413 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -150,6 +150,18 @@ config USB_DWC3_IMX8MP functionality. Say 'Y' or 'M' if you have one such device. +config USB_DWC3_IMX + tristate "NXP iMX Platform" + depends on OF && COMMON_CLK + depends on (ARCH_MXC && ARM64) || COMPILE_TEST + default USB_DWC3 + help + NXP iMX SoC use DesignWare Core IP for USB2/3 + functionality. + This driver also handles the wakeup feature outside + of DesignWare Core. + Say 'Y' or 'M' if you have one such device. + config USB_DWC3_XILINX tristate "Xilinx Platforms" depends on (ARCH_ZYNQMP || COMPILE_TEST) && OF diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 073bef5309b5..f37971197203 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom-legacy.o obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o +obj-$(CONFIG_USB_DWC3_IMX) += dwc3-imx.o obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o diff --git a/drivers/usb/dwc3/dwc3-imx.c b/drivers/usb/dwc3/dwc3-imx.c new file mode 100644 index 000000000000..303708f7d79a --- /dev/null +++ b/drivers/usb/dwc3/dwc3-imx.c @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-imx.c - NXP i.MX Soc USB3 Specific Glue layer + * + * Copyright 2026 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "glue.h" + +/* USB wakeup registers */ +#define USB_WAKEUP_CTRL 0x00 + +/* Global wakeup interrupt enable, also used to clear interrupt */ +#define USB_WAKEUP_EN BIT(31) +/* Wakeup from connect or disconnect, only for superspeed */ +#define USB_WAKEUP_SS_CONN BIT(5) +/* 0 select vbus_valid, 1 select sessvld */ +#define USB_WAKEUP_VBUS_SRC_SESS_VAL BIT(4) +/* Enable signal for wake up from u3 state */ +#define USB_WAKEUP_U3_EN BIT(3) +/* Enable signal for wake up from id change */ +#define USB_WAKEUP_ID_EN BIT(2) +/* Enable signal for wake up from vbus change */ +#define USB_WAKEUP_VBUS_EN BIT(1) +/* Enable signal for wake up from dp/dm change */ +#define USB_WAKEUP_DPDM_EN BIT(0) + +#define USB_WAKEUP_EN_MASK GENMASK(5, 0) + +/* USB glue registers */ +#define USB_CTRL0 0x00 +#define USB_CTRL1 0x04 + +#define USB_CTRL0_PORTPWR_EN BIT(12) /* 1 - PPC enabled (default) */ +#define USB_CTRL0_USB3_FIXED BIT(22) /* 1 - USB3 permanent attached */ +#define USB_CTRL0_USB2_FIXED BIT(23) /* 1 - USB2 permanent attached */ + +#define USB_CTRL1_OC_POLARITY BIT(16) /* 0 - HIGH / 1 - LOW */ +#define USB_CTRL1_PWR_POLARITY BIT(17) /* 0 - HIGH / 1 - LOW */ + +struct dwc3_imx { + struct dwc3 dwc; + struct device *dev; + void __iomem *blkctl_base; + void __iomem *glue_base; + struct clk *hsio_clk; + struct clk *suspend_clk; + int irq; + bool pm_suspended; + bool wakeup_pending; + unsigned permanent_attached:1; + unsigned disable_pwr_ctrl:1; + unsigned overcur_active_low:1; + unsigned power_active_low:1; +}; + +#define to_dwc3_imx(d) container_of((d), struct dwc3_imx, dwc) + +static void dwc3_imx_get_property(struct dwc3_imx *dwc_imx) +{ + struct device *dev = dwc_imx->dev; + + dwc_imx->permanent_attached = + device_property_read_bool(dev, "fsl,permanently-attached"); + dwc_imx->disable_pwr_ctrl = + device_property_read_bool(dev, "fsl,disable-port-power-control"); + dwc_imx->overcur_active_low = + device_property_read_bool(dev, "fsl,over-current-active-low"); + dwc_imx->power_active_low = + device_property_read_bool(dev, "fsl,power-active-low"); +} + +static void dwc3_imx_configure_glue(struct dwc3_imx *dwc_imx) +{ + u32 value; + + if (!dwc_imx->glue_base) + return; + + value = readl(dwc_imx->glue_base + USB_CTRL0); + + if (dwc_imx->permanent_attached) + value |= USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED; + else + value &= ~(USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED); + + if (dwc_imx->disable_pwr_ctrl) + value &= ~USB_CTRL0_PORTPWR_EN; + else + value |= USB_CTRL0_PORTPWR_EN; + + writel(value, dwc_imx->glue_base + USB_CTRL0); + + value = readl(dwc_imx->glue_base + USB_CTRL1); + if (dwc_imx->overcur_active_low) + value |= USB_CTRL1_OC_POLARITY; + else + value &= ~USB_CTRL1_OC_POLARITY; + + if (dwc_imx->power_active_low) + value |= USB_CTRL1_PWR_POLARITY; + else + value &= ~USB_CTRL1_PWR_POLARITY; + + writel(value, dwc_imx->glue_base + USB_CTRL1); +} + +static void dwc3_imx_wakeup_enable(struct dwc3_imx *dwc_imx, pm_message_t msg) +{ + struct dwc3 *dwc = &dwc_imx->dwc; + u32 val; + + val = readl(dwc_imx->blkctl_base + USB_WAKEUP_CTRL); + + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && dwc->xhci) { + val |= USB_WAKEUP_EN | USB_WAKEUP_DPDM_EN; + if (PMSG_IS_AUTO(msg)) + val |= USB_WAKEUP_SS_CONN | USB_WAKEUP_U3_EN; + } else { + val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN | + USB_WAKEUP_VBUS_SRC_SESS_VAL; + } + + writel(val, dwc_imx->blkctl_base + USB_WAKEUP_CTRL); +} + +static void dwc3_imx_wakeup_disable(struct dwc3_imx *dwc_imx) +{ + u32 val; + + val = readl(dwc_imx->blkctl_base + USB_WAKEUP_CTRL); + val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK); + writel(val, dwc_imx->blkctl_base + USB_WAKEUP_CTRL); +} + +static irqreturn_t dwc3_imx_interrupt(int irq, void *data) +{ + struct dwc3_imx *dwc_imx = data; + struct dwc3 *dwc = &dwc_imx->dwc; + + if (!dwc_imx->pm_suspended) + return IRQ_HANDLED; + + disable_irq_nosync(dwc_imx->irq); + dwc_imx->wakeup_pending = true; + + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && dwc->xhci) + pm_runtime_resume(&dwc->xhci->dev); + else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) + pm_runtime_get(dwc->dev); + + return IRQ_HANDLED; +} + +static void dwc3_imx_pre_set_role(struct dwc3 *dwc, enum usb_role role) +{ + if (role == USB_ROLE_HOST) + /* + * For xhci host, we need disable dwc core auto + * suspend, because during this auto suspend delay(5s), + * xhci host RUN_STOP is cleared and wakeup is not + * enabled, if device is inserted, xhci host can't + * response the connection. + */ + pm_runtime_dont_use_autosuspend(dwc->dev); + else + pm_runtime_use_autosuspend(dwc->dev); +} + +static struct dwc3_glue_ops dwc3_imx_glue_ops = { + .pre_set_role = dwc3_imx_pre_set_role, +}; + +static const struct property_entry dwc3_imx_properties[] = { + PROPERTY_ENTRY_BOOL("xhci-missing-cas-quirk"), + PROPERTY_ENTRY_BOOL("xhci-skip-phy-init-quirk"), + {}, +}; + +static const struct software_node dwc3_imx_swnode = { + .properties = dwc3_imx_properties, +}; + +static int dwc3_imx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dwc3_imx *dwc_imx; + struct dwc3 *dwc; + struct resource *res; + const char *irq_name; + struct dwc3_probe_data probe_data = {}; + int ret, irq; + + dwc_imx = devm_kzalloc(dev, sizeof(*dwc_imx), GFP_KERNEL); + if (!dwc_imx) + return -ENOMEM; + + platform_set_drvdata(pdev, dwc_imx); + dwc_imx->dev = dev; + + dwc3_imx_get_property(dwc_imx); + + dwc_imx->blkctl_base = devm_platform_ioremap_resource_byname(pdev, "blkctl"); + if (IS_ERR(dwc_imx->blkctl_base)) + return PTR_ERR(dwc_imx->blkctl_base); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "glue"); + if (!res) { + dev_warn(dev, "Base address for glue layer missing\n"); + } else { + dwc_imx->glue_base = devm_ioremap_resource(dev, res); + if (IS_ERR(dwc_imx->glue_base)) + return PTR_ERR(dwc_imx->glue_base); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); + if (!res) + return dev_err_probe(dev, -ENODEV, "missing core memory resource\n"); + + dwc_imx->hsio_clk = devm_clk_get_enabled(dev, "hsio"); + if (IS_ERR(dwc_imx->hsio_clk)) + return dev_err_probe(dev, PTR_ERR(dwc_imx->hsio_clk), + "Failed to get hsio clk\n"); + + dwc_imx->suspend_clk = devm_clk_get_enabled(dev, "suspend"); + if (IS_ERR(dwc_imx->suspend_clk)) + return dev_err_probe(dev, PTR_ERR(dwc_imx->suspend_clk), + "Failed to get suspend clk\n"); + + irq = platform_get_irq_byname(pdev, "wakeup"); + if (irq < 0) + return irq; + dwc_imx->irq = irq; + + irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", dev_name(dev)); + if (!irq_name) + return dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n"); + + ret = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx_interrupt, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + irq_name, dwc_imx); + if (ret) + return dev_err_probe(dev, ret, "failed to request IRQ #%d\n", irq); + + ret = device_add_software_node(dev, &dwc3_imx_swnode); + if (ret) + return dev_err_probe(dev, ret, "failed to add software node\n"); + + dwc3_imx_configure_glue(dwc_imx); + + dwc = &dwc_imx->dwc; + dwc->dev = dev; + dwc->glue_ops = &dwc3_imx_glue_ops; + + probe_data.res = res; + probe_data.dwc = dwc; + probe_data.properties = DWC3_DEFAULT_PROPERTIES; + probe_data.properties.needs_full_reinit = true; + + ret = dwc3_core_probe(&probe_data); + if (ret) { + device_remove_software_node(dev); + return ret; + } + + device_set_wakeup_capable(dev, true); + return 0; +} + +static void dwc3_imx_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dwc3 *dwc = dev_get_drvdata(dev); + + dwc3_core_remove(dwc); + device_remove_software_node(dev); +} + +static void dwc3_imx_suspend(struct dwc3_imx *dwc_imx, pm_message_t msg) +{ + if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc_imx->dev)) + dwc3_imx_wakeup_enable(dwc_imx, msg); + + enable_irq(dwc_imx->irq); + dwc_imx->pm_suspended = true; +} + +static void dwc3_imx_resume(struct dwc3_imx *dwc_imx, pm_message_t msg) +{ + struct dwc3 *dwc = &dwc_imx->dwc; + + dwc_imx->pm_suspended = false; + if (!dwc_imx->wakeup_pending) + disable_irq_nosync(dwc_imx->irq); + + dwc3_imx_wakeup_disable(dwc_imx); + + /* Upon power loss any previous configuration is lost, restore it */ + dwc3_imx_configure_glue(dwc_imx); + + if (dwc_imx->wakeup_pending) { + dwc_imx->wakeup_pending = false; + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) + pm_runtime_put_autosuspend(dwc->dev); + else + /* + * Add wait for xhci switch from suspend + * clock to normal clock to detect connection. + */ + usleep_range(9000, 10000); + } +} + +static int dwc3_imx_runtime_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); + int ret; + + ret = dwc3_runtime_suspend(dwc); + if (ret) + return ret; + + dwc3_imx_suspend(dwc_imx, PMSG_AUTO_SUSPEND); + return 0; +} + +static int dwc3_imx_runtime_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); + + dwc3_imx_resume(dwc_imx, PMSG_AUTO_RESUME); + return dwc3_runtime_resume(dwc); +} + +static int dwc3_imx_runtime_idle(struct device *dev) +{ + return dwc3_runtime_idle(dev_get_drvdata(dev)); +} + +static int dwc3_imx_pm_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); + int ret; + + ret = dwc3_pm_suspend(dwc); + if (ret) + return ret; + + dwc3_imx_suspend(dwc_imx, PMSG_SUSPEND); + + if (device_may_wakeup(dev)) { + enable_irq_wake(dwc_imx->irq); + device_set_out_band_wakeup(dev); + } else { + clk_disable_unprepare(dwc_imx->suspend_clk); + } + + clk_disable_unprepare(dwc_imx->hsio_clk); + + return 0; +} + +static int dwc3_imx_pm_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc); + int ret; + + if (device_may_wakeup(dwc_imx->dev)) { + disable_irq_wake(dwc_imx->irq); + } else { + ret = clk_prepare_enable(dwc_imx->suspend_clk); + if (ret) + return ret; + } + + ret = clk_prepare_enable(dwc_imx->hsio_clk); + if (ret) { + clk_disable_unprepare(dwc_imx->suspend_clk); + return ret; + } + + dwc3_imx_resume(dwc_imx, PMSG_RESUME); + + ret = dwc3_pm_resume(dwc); + if (ret) + return ret; + + return 0; +} + +static void dwc3_imx_complete(struct device *dev) +{ + dwc3_pm_complete(dev_get_drvdata(dev)); +} + +static int dwc3_imx_prepare(struct device *dev) +{ + return dwc3_pm_prepare(dev_get_drvdata(dev)); +} + +static const struct dev_pm_ops dwc3_imx_dev_pm_ops = { + SYSTEM_SLEEP_PM_OPS(dwc3_imx_pm_suspend, dwc3_imx_pm_resume) + RUNTIME_PM_OPS(dwc3_imx_runtime_suspend, dwc3_imx_runtime_resume, + dwc3_imx_runtime_idle) + .complete = pm_sleep_ptr(dwc3_imx_complete), + .prepare = pm_sleep_ptr(dwc3_imx_prepare), +}; + +static const struct of_device_id dwc3_imx_of_match[] = { + { .compatible = "nxp,imx8mp-dwc3", }, + {}, +}; +MODULE_DEVICE_TABLE(of, dwc3_imx_of_match); + +static struct platform_driver dwc3_imx_driver = { + .probe = dwc3_imx_probe, + .remove = dwc3_imx_remove, + .driver = { + .name = "imx-dwc3", + .pm = pm_ptr(&dwc3_imx_dev_pm_ops), + .of_match_table = dwc3_imx_of_match, + }, +}; + +module_platform_driver(dwc3_imx_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DesignWare USB3 i.MX Glue Layer"); From bb375c251ab40bdbc5272008fcf2bc6cd5266610 Mon Sep 17 00:00:00 2001 From: Charan Pedumuru Date: Tue, 24 Feb 2026 13:32:38 +0000 Subject: [PATCH 006/176] dt-bindings: usb: st,st-ohci-300x: convert to DT schema Convert STMicroelectronics USB OHCI Controller binding to DT schema. Signed-off-by: Charan Pedumuru Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260224-st-usb-v2-1-e8b7cb6524c6@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/ohci-st.txt | 36 -------- .../bindings/usb/st,st-ohci-300x.yaml | 85 +++++++++++++++++++ 2 files changed, 85 insertions(+), 36 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/ohci-st.txt create mode 100644 Documentation/devicetree/bindings/usb/st,st-ohci-300x.yaml diff --git a/Documentation/devicetree/bindings/usb/ohci-st.txt b/Documentation/devicetree/bindings/usb/ohci-st.txt deleted file mode 100644 index 1c735573abc0..000000000000 --- a/Documentation/devicetree/bindings/usb/ohci-st.txt +++ /dev/null @@ -1,36 +0,0 @@ -ST USB OHCI controller - -Required properties: - - - compatible : must be "st,st-ohci-300x" - - reg : physical base addresses of the controller and length of memory mapped - region - - interrupts : one OHCI controller interrupt should be described here - - clocks : phandle list of usb clocks - - clock-names : should be "ic" for interconnect clock and "clk48" -See: Documentation/devicetree/bindings/clock/clock-bindings.txt - - - phys : phandle for the PHY device - - phy-names : should be "usb" - - - resets : phandle to the powerdown and reset controller for the USB IP - - reset-names : should be "power" and "softreset". -See: Documentation/devicetree/bindings/reset/st,stih407-powerdown.yaml -See: Documentation/devicetree/bindings/reset/reset.txt - -Example: - - ohci0: usb@fe1ffc00 { - compatible = "st,st-ohci-300x"; - reg = <0xfe1ffc00 0x100>; - interrupts = ; - clocks = <&clk_s_a1_ls 0>, - <&clockgen_b0 0>; - clock-names = "ic", "clk48"; - phys = <&usb2_phy>; - phy-names = "usb"; - - resets = <&powerdown STIH416_USB0_POWERDOWN>, - <&softreset STIH416_USB0_SOFTRESET>; - reset-names = "power", "softreset"; - }; diff --git a/Documentation/devicetree/bindings/usb/st,st-ohci-300x.yaml b/Documentation/devicetree/bindings/usb/st,st-ohci-300x.yaml new file mode 100644 index 000000000000..a225bf5a2ee4 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/st,st-ohci-300x.yaml @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/st,st-ohci-300x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: STMicroelectronics USB OHCI Controller + +maintainers: + - Peter Griffin + +description: + The STMicroelectronics USB Open Host Controller Interface (OHCI) + compliant USB host controller found in ST platforms. The controller + provides full- and low-speed USB host functionality and interfaces + with an external USB PHY. It requires dedicated clock, reset, and + interrupt resources for proper operation. + +allOf: + - $ref: /schemas/usb/usb-hcd.yaml# + +properties: + compatible: + const: st,st-ohci-300x + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: ic + - const: clk48 + + phys: + maxItems: 1 + + phy-names: + items: + - const: usb + + resets: + maxItems: 2 + + reset-names: + items: + - const: power + - const: softreset + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - phys + - phy-names + - resets + - reset-names + +unevaluatedProperties: false + +examples: + - | + #include + #include + usb@fe1ffc00 { + compatible = "st,st-ohci-300x"; + reg = <0xfe1ffc00 0x100>; + interrupts = ; + clocks = <&clk_s_a1_ls 0>, + <&clockgen_b0 0>; + clock-names = "ic", "clk48"; + phys = <&usb2_phy>; + phy-names = "usb"; + resets = <&powerdown STIH407_USB2_PORT0_POWERDOWN>, + <&softreset STIH407_USB2_PORT0_SOFTRESET>; + reset-names = "power", "softreset"; + }; +... From 4573add760b8dd52a215fd134effb76da10ebcf5 Mon Sep 17 00:00:00 2001 From: Rene Sapiens Date: Fri, 6 Feb 2026 16:25:56 -0800 Subject: [PATCH 007/176] thunderbolt: Read router NVM version before applying quirks The router NVM version is currently only available after the NVMem devices have been registered. This is too late for firmware-dependent quirks that are evaluated during tb_switch_add() before device registration. Split router NVM handling into two phases: - tb_switch_nvm_init() allocates the NVM object and reads the version - tb_switch_nvm_add() registers the NVMem devices using the pre-read NVM This makes the NVM major/minor version available before tb_check_quirks() without changing when the NVMem devices are registered. Signed-off-by: Rene Sapiens Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index e5b48a331c58..c2ad58b19e7b 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -347,7 +347,7 @@ static int nvm_write(void *priv, unsigned int offset, void *val, size_t bytes) return ret; } -static int tb_switch_nvm_add(struct tb_switch *sw) +static int tb_switch_nvm_init(struct tb_switch *sw) { struct tb_nvm *nvm; int ret; @@ -365,6 +365,26 @@ static int tb_switch_nvm_add(struct tb_switch *sw) if (ret) goto err_nvm; + sw->nvm = nvm; + return 0; + +err_nvm: + tb_sw_dbg(sw, "NVM upgrade disabled\n"); + sw->no_nvm_upgrade = true; + if (!IS_ERR(nvm)) + tb_nvm_free(nvm); + + return ret; +} + +static int tb_switch_nvm_add(struct tb_switch *sw) +{ + struct tb_nvm *nvm = sw->nvm; + int ret; + + if (!nvm) + return 0; + /* * If the switch is in safe-mode the only accessible portion of * the NVM is the non-active one where userspace is expected to @@ -383,14 +403,12 @@ static int tb_switch_nvm_add(struct tb_switch *sw) goto err_nvm; } - sw->nvm = nvm; return 0; err_nvm: tb_sw_dbg(sw, "NVM upgrade disabled\n"); sw->no_nvm_upgrade = true; - if (!IS_ERR(nvm)) - tb_nvm_free(nvm); + tb_nvm_free(nvm); return ret; } @@ -3311,6 +3329,10 @@ int tb_switch_add(struct tb_switch *sw) return ret; } + ret = tb_switch_nvm_init(sw); + if (ret) + return ret; + if (!sw->safe_mode) { tb_switch_credits_init(sw); From 59b03d12b1f6d14d936a3ebec225f8d914dc3b70 Mon Sep 17 00:00:00 2001 From: Rene Sapiens Date: Fri, 6 Feb 2026 16:25:57 -0800 Subject: [PATCH 008/176] thunderbolt: Disable CLx on Titan Ridge-based devices with old firmware Thunderbolt 3 devices based on Titan Ridge routers with NVM firmware version < 0x65 have been observed to become unstable when CL states are enabled. This can lead to link disconnect events and the device failing to enumerate. Enable CLx on Titan Ridge only when the running NVM firmware version is >= 0x65. Signed-off-by: Rene Sapiens Signed-off-by: Mika Westerberg --- drivers/thunderbolt/quirks.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c index e81de9c30eac..9f7914ac2f48 100644 --- a/drivers/thunderbolt/quirks.c +++ b/drivers/thunderbolt/quirks.c @@ -23,6 +23,9 @@ static void quirk_dp_credit_allocation(struct tb_switch *sw) static void quirk_clx_disable(struct tb_switch *sw) { + if (tb_switch_is_titan_ridge(sw) && sw->nvm && sw->nvm->major >= 0x65) + return; + sw->quirks |= QUIRK_NO_CLX; tb_sw_dbg(sw, "disabling CL states\n"); } @@ -61,6 +64,10 @@ static const struct tb_quirk tb_quirks[] = { /* Dell WD19TB supports self-authentication on unplug */ { 0x0000, 0x0000, 0x00d4, 0xb070, quirk_force_power_link }, { 0x0000, 0x0000, 0x00d4, 0xb071, quirk_force_power_link }, + + /* Intel Titan Ridge CLx is unstable on early firmware versions */ + { 0x8086, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE, 0x0000, 0x0000, + quirk_clx_disable }, /* * Intel Goshen Ridge NVM 27 and before report wrong number of * DP buffers. From f791145abcb83faa6ba580f2b7a6cefef37b9cf3 Mon Sep 17 00:00:00 2001 From: Dave Hansen Date: Thu, 5 Mar 2026 09:18:37 -0800 Subject: [PATCH 009/176] MAINTAINERS: Remove bouncing maintainer, Mika takes over DMA test driver This maintainer's email is now bouncing. Since Mika maintains the core Thunderbolt/USB4 driver he can take over this one too. Signed-off-by: Dave Hansen [mw: Put me as maintainer instead of orphaning it] Signed-off-by: Mika Westerberg --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 61bf550fd37c..1c5b16d80fdb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26300,7 +26300,7 @@ F: drivers/media/i2c/thp7312.c F: include/uapi/linux/thp7312.h THUNDERBOLT DMA TRAFFIC TEST DRIVER -M: Isaac Hazan +M: Mika Westerberg L: linux-usb@vger.kernel.org S: Maintained F: drivers/thunderbolt/dma_test.c From ee11e0c45954d1d91f4d92c92d2e394e0032b2d4 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Thu, 26 Feb 2026 16:38:50 +0100 Subject: [PATCH 010/176] dt-bindings: usb: cdns,usb3: support USB devices in DT Reference usb-xhci.yaml in host mode in order to support on-board USB hubs. Signed-off-by: Alexander Stein Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260226153859.665901-2-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/cdns,usb3.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml index f454ddd9bbaa..a199e5ba6416 100644 --- a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml +++ b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml @@ -85,6 +85,7 @@ required: allOf: - $ref: usb-drd.yaml# + - $ref: usb-xhci.yaml# unevaluatedProperties: false From 45955006cc29bfb7fd436b8e7e2197d9bdc30af9 Mon Sep 17 00:00:00 2001 From: Anjelique Melendez Date: Mon, 9 Feb 2026 12:49:14 -0800 Subject: [PATCH 011/176] usb: typec: ucsi: ucsi_glink: Add support for Glymur and Kaanapali Add Glymur and Kaanapali compatible strings which both need UCSI_DELAY_DEVICE_PDOS quirk. Signed-off-by: Anjelique Melendez Reviewed-by: Dmitry Baryshkov Reviewed-by: Heikki Krogerus Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260209204915.1983997-5-anjelique.melendez@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_glink.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index 11b3e24e34e2..c7878ea0d37a 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -373,6 +373,8 @@ static unsigned long quirk_sc8280xp = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_P static unsigned long quirk_sm8450 = UCSI_DELAY_DEVICE_PDOS; static const struct of_device_id pmic_glink_ucsi_of_quirks[] = { + { .compatible = "qcom,glymur-pmic-glink", .data = &quirk_sm8450, }, + { .compatible = "qcom,kaanapali-pmic-glink", .data = &quirk_sm8450, }, { .compatible = "qcom,qcm6490-pmic-glink", .data = &quirk_sc8280xp, }, { .compatible = "qcom,sc8180x-pmic-glink", .data = &quirk_sc8180x, }, { .compatible = "qcom,sc8280xp-pmic-glink", .data = &quirk_sc8280xp, }, From d0ef3c4a9fa3b4694a187f277527664919c2ecb4 Mon Sep 17 00:00:00 2001 From: Diogo Ivo Date: Tue, 27 Jan 2026 15:11:48 +0000 Subject: [PATCH 012/176] usb: xhci: tegra: Remove redundant mutex when setting phy mode As the PHY subsystem already synchronizes concurrent accesses to a PHY instance with a core-internal mutex remove the driver specific mutex synchronization. Signed-off-by: Diogo Ivo Link: https://patch.msgid.link/20260127-diogo-tegra_phy-v2-2-787b9eed3ed5@tecnico.ulisboa.pt Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 3f6aa2440b05..ed4b11f8d298 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -1357,15 +1357,11 @@ static void tegra_xhci_id_work(struct work_struct *work) dev_dbg(tegra->dev, "host mode %s\n", str_on_off(tegra->host_mode)); - mutex_lock(&tegra->lock); - if (tegra->host_mode) phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_HOST); else phy_set_mode_ext(phy, PHY_MODE_USB_OTG, USB_ROLE_NONE); - mutex_unlock(&tegra->lock); - tegra->otg_usb3_port = tegra_xusb_padctl_get_usb3_companion(tegra->padctl, tegra->otg_usb2_port); From dc3cf736a5cb63dea1158c267e168ca410d02fa4 Mon Sep 17 00:00:00 2001 From: Conor Dooley Date: Tue, 3 Mar 2026 16:37:39 +0000 Subject: [PATCH 013/176] dt-bindings: usb: mpfs-musb: permit resets The musb IP on mpfs and pic64gx has a reset pin, but until now this has been undocumented because platform firmware takes the block out of reset on first-party boards (or those using modified versions of the vendor firmware), but not all boards may take this approach. Permit providing a reset in devicetree for Linux, or other devicetree-consuming software, to use. Signed-off-by: Conor Dooley Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260303-backspace-unhearing-c6cc8cbddbba@spud Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml b/Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml index a812317d8089..c4e1c2d73bdb 100644 --- a/Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml +++ b/Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml @@ -37,6 +37,9 @@ properties: clocks: maxItems: 1 + resets: + maxItems: 1 + microchip,ext-vbus-drv: description: Some ULPI USB PHYs do not support an internal VBUS supply and driving From bb24a1c09d7f848fb5a453b0ffc95a29b888907d Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Sat, 21 Feb 2026 10:02:37 +0100 Subject: [PATCH 014/176] usb: ehci-orion: remove optional PHY handling code remnants Since the USB core code handles the generic USB PHYs automatically, the optional PHY handling code has been removed from the 'ehci-orion' driver entirely by commit e04585184dcf ("usb: ehci-orion: avoid double PHY initialization"). However, the devm_phy_optional_get() call has been kept so the driver still gets the PHY even though it is not used for anything in the driver. Drop the remaining code, and also remove the 'phy' member of the 'orion_ehci_hcd' structure to simplify the code. Acked-by: Alan Stern Signed-off-by: Gabor Juhos Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260221-ehci-orion-drop-phy-handling-v2-1-5e26aa73790b@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-orion.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index 34abff8669f8..eaaa49712a8c 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -60,7 +59,6 @@ struct orion_ehci_hcd { struct clk *clk; - struct phy *phy; }; static struct hc_driver __read_mostly ehci_orion_hc_driver; @@ -276,13 +274,6 @@ static int ehci_orion_drv_probe(struct platform_device *pdev) goto err_put_hcd; } - priv->phy = devm_phy_optional_get(&pdev->dev, "usb"); - if (IS_ERR(priv->phy)) { - err = PTR_ERR(priv->phy); - if (err != -ENOSYS) - goto err_dis_clk; - } - /* * (Re-)program MBUS remapping windows if we are asked to. */ From 282b8eec8a4eab9a3ff3addf6dad2ce699594fe8 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Tue, 10 Feb 2026 13:22:08 +0100 Subject: [PATCH 015/176] net: cdc-ncm: cleanup device descriptor Flags are boolean values, hence they should be typed as bool, not as u8. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260210122208.29244-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/cdc_ncm.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/usb/cdc_ncm.h b/include/linux/usb/cdc_ncm.h index 4ac082a63173..97ef37a1ff4a 100644 --- a/include/linux/usb/cdc_ncm.h +++ b/include/linux/usb/cdc_ncm.h @@ -118,8 +118,8 @@ struct cdc_ncm_ctx { u32 timer_interval; u32 max_ndp_size; - u8 is_ndp16; - u8 filtering_supported; + bool is_ndp16; + bool filtering_supported; union { struct usb_cdc_ncm_ndp16 *delayed_ndp16; struct usb_cdc_ncm_ndp32 *delayed_ndp32; From fc12cd6bce1da3f1048f00ce6b6080cce47144b0 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 17 Feb 2026 14:12:00 +0400 Subject: [PATCH 016/176] usb: misc: onboard_usb_dev: Add support for requesting VBUS for Type-A ports Add a regulator-only entry matching OF-described USB Type-A connectors. This allows platforms to explicitly model VBUS supply regulators for these ports instead of calling them PHY supplies or making the respective regulators always-on in their device trees. Type-A ports won't typically need a dedicated driver, as there is nothing to configure apart from the power supply, so there is no controller driver to traverse the OF graph and request the VBUS regulator, unlike for Type-C ports. Thus make it an onboard USB device, which it kind of really is. Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260217-typea-vbus-v1-1-657b4e55a4c2@flipper.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_dev.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h index 1a1e86e60e04..35d15b034664 100644 --- a/drivers/usb/misc/onboard_usb_dev.h +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -108,6 +108,11 @@ static const struct onboard_dev_pdata genesys_gl852g_data = { .is_hub = true, }; +static const struct onboard_dev_pdata usb_a_conn_data = { + .num_supplies = 1, + .supply_names = { "vbus" }, +}; + static const struct onboard_dev_pdata vialab_vl817_data = { .reset_us = 10, .num_supplies = 1, @@ -130,6 +135,7 @@ static const struct onboard_dev_pdata xmos_xvf3500_data = { }; static const struct of_device_id onboard_dev_match[] = { + { .compatible = "usb-a-connector", .data = &usb_a_conn_data, }, { .compatible = "usb424,2412", .data = µchip_usb424_data, }, { .compatible = "usb424,2514", .data = µchip_usb2514_data, }, { .compatible = "usb424,2517", .data = µchip_usb424_data, }, From d740dcd1fa7b9cd86f10f1badc173b952fdc375f Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 4 Mar 2026 17:07:30 +0100 Subject: [PATCH 017/176] usb: uss720: unify error handling in probe There is a lot of code duplication. Unify it. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260304160734.1742200-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/uss720.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c index ec8bd968c4de..616c92ce5e1a 100644 --- a/drivers/usb/misc/uss720.c +++ b/drivers/usb/misc/uss720.c @@ -677,35 +677,32 @@ static int uss720_probe(struct usb_interface *intf, struct parport_uss720_private *priv; struct parport *pp; unsigned char reg; - int ret; + int ret = -ENODEV; dev_dbg(&intf->dev, "probe: vendor id 0x%x, device id 0x%x\n", le16_to_cpu(usbdev->descriptor.idVendor), le16_to_cpu(usbdev->descriptor.idProduct)); /* our known interfaces have 3 alternate settings */ - if (intf->num_altsetting != 3) { - usb_put_dev(usbdev); - return -ENODEV; - } + if (intf->num_altsetting != 3) + goto bail_out_early; + ret = usb_set_interface(usbdev, intf->altsetting->desc.bInterfaceNumber, 2); dev_dbg(&intf->dev, "set interface result %d\n", ret); interface = intf->cur_altsetting; - if (interface->desc.bNumEndpoints < 2) { - usb_put_dev(usbdev); - return -ENODEV; - } + if (interface->desc.bNumEndpoints < 2) + goto bail_out_early; /* * Allocate parport interface */ + ret = -ENOMEM; priv = kzalloc_obj(struct parport_uss720_private); - if (!priv) { - usb_put_dev(usbdev); - return -ENOMEM; - } + if (!priv) + goto bail_out_early; + priv->pp = NULL; priv->usbdev = usbdev; kref_init(&priv->ref_count); @@ -752,6 +749,10 @@ static int uss720_probe(struct usb_interface *intf, kill_all_async_requests_priv(priv); kref_put(&priv->ref_count, destroy_priv); return -ENODEV; + +bail_out_early: + usb_put_dev(usbdev); + return ret; } static void uss720_disconnect(struct usb_interface *intf) From a28de63356575612954d4e5d5f48a2488f50e16d Mon Sep 17 00:00:00 2001 From: Ingo Rohloff Date: Thu, 5 Mar 2026 13:14:52 +0100 Subject: [PATCH 018/176] usb: dwc3: Support USB3340x ULPI PHY high-speed negotiation. The Microchip USB3340x ULPI PHY requires a delay when switching to the high-speed transmitter. See: http://ww1.microchip.com/downloads/en/DeviceDoc/80000645A.pdf Module 2 "Device Enumeration Failure with Link IP Systems" For details on the behavior and fix, refer to the AMD (formerly Xilinx) forum post: "USB stuck in full speed mode with USB3340 ULPI PHY, ZynqMP." This patch uses the USB PHY Vendor-ID and Product-ID to detect the USB3340 PHY and then applies the necessary fix if this PHY is found. Signed-off-by: Ingo Rohloff Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260305121452.54082-2-ingo.rohloff@lauterbach.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.c | 20 ++++++++++++++++++++ drivers/usb/dwc3/core.h | 4 ++++ drivers/usb/dwc3/ulpi.c | 25 +++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index cacc4ec9f7ce..58899b1fa96d 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -782,6 +782,24 @@ static int dwc3_hs_phy_setup(struct dwc3 *dwc, int index) return 0; } +static void dwc3_ulpi_setup(struct dwc3 *dwc) +{ + int index; + u32 reg; + + /* Don't do anything if there is no ULPI PHY */ + if (!dwc->ulpi) + return; + + if (dwc->enable_usb2_transceiver_delay) { + for (index = 0; index < dwc->num_usb2_ports; index++) { + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(index)); + reg |= DWC3_GUSB2PHYCFG_XCVRDLY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(index), reg); + } + } +} + /** * dwc3_phy_setup - Configure USB PHY Interface of DWC3 Core * @dwc: Pointer to our controller context structure @@ -1363,6 +1381,8 @@ int dwc3_core_init(struct dwc3 *dwc) dwc->ulpi_ready = true; } + dwc3_ulpi_setup(dwc); + if (!dwc->phys_ready) { ret = dwc3_core_get_phy(dwc); if (ret) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 67bcc8dccc89..7d0845184223 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -302,6 +302,7 @@ #define DWC3_GUSB2PHYCFG_SUSPHY BIT(6) #define DWC3_GUSB2PHYCFG_ULPI_UTMI BIT(4) #define DWC3_GUSB2PHYCFG_ENBLSLPM BIT(8) +#define DWC3_GUSB2PHYCFG_XCVRDLY BIT(9) #define DWC3_GUSB2PHYCFG_PHYIF(n) (n << 3) #define DWC3_GUSB2PHYCFG_PHYIF_MASK DWC3_GUSB2PHYCFG_PHYIF(1) #define DWC3_GUSB2PHYCFG_USBTRDTIM(n) (n << 10) @@ -1163,6 +1164,8 @@ struct dwc3_glue_ops { * 3 - Reserved * @dis_metastability_quirk: set to disable metastability quirk. * @dis_split_quirk: set to disable split boundary. + * @enable_usb2_transceiver_delay: Set to insert a delay before the + * assertion of the TxValid signal during a HS Chirp. * @sys_wakeup: set if the device may do system wakeup. * @wakeup_configured: set if the device is configured for remote wakeup. * @suspended: set to track suspend event due to U3/L2. @@ -1406,6 +1409,7 @@ struct dwc3 { unsigned dis_metastability_quirk:1; unsigned dis_split_quirk:1; + unsigned enable_usb2_transceiver_delay:1; unsigned async_callbacks:1; unsigned sys_wakeup:1; unsigned wakeup_configured:1; diff --git a/drivers/usb/dwc3/ulpi.c b/drivers/usb/dwc3/ulpi.c index 57daad15f502..a256b7f5d78b 100644 --- a/drivers/usb/dwc3/ulpi.c +++ b/drivers/usb/dwc3/ulpi.c @@ -10,10 +10,13 @@ #include #include #include +#include #include "core.h" #include "io.h" +#define USB_VENDOR_MICROCHIP 0x0424 + #define DWC3_ULPI_ADDR(a) \ ((a >= ULPI_EXT_VENDOR_SPECIFIC) ? \ DWC3_GUSB2PHYACC_ADDR(ULPI_ACCESS_EXTENDED) | \ @@ -83,6 +86,26 @@ static const struct ulpi_ops dwc3_ulpi_ops = { .write = dwc3_ulpi_write, }; +static void dwc3_ulpi_detect_config(struct dwc3 *dwc) +{ + struct ulpi *ulpi = dwc->ulpi; + + switch (ulpi->id.vendor) { + case USB_VENDOR_MICROCHIP: + switch (ulpi->id.product) { + case 0x0009: + /* Microchip USB3340 ULPI PHY */ + dwc->enable_usb2_transceiver_delay = true; + break; + default: + break; + } + break; + default: + break; + } +} + int dwc3_ulpi_init(struct dwc3 *dwc) { /* Register the interface */ @@ -92,6 +115,8 @@ int dwc3_ulpi_init(struct dwc3 *dwc) return PTR_ERR(dwc->ulpi); } + dwc3_ulpi_detect_config(dwc); + return 0; } From 26cf0917142897f052e2966e265917e4593340d5 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 5 Mar 2026 19:16:39 -0800 Subject: [PATCH 019/176] usb: typec: tcpm: kzalloc + kcalloc to kzalloc_flex Simplifies allocation and allows using __counted_by for extra runtime analysis. Signed-off-by: Rosen Penev Reviewed-by: Gustavo A. R. Silva Link: https://patch.msgid.link/20260306031639.46942-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 1d2f3af034c5..272f9187b12d 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -605,9 +605,9 @@ struct altmode_vdm_event { struct kthread_work work; struct tcpm_port *port; u32 header; - u32 *data; int cnt; enum tcpm_transmit_type tx_sop_type; + u32 data[] __counted_by(cnt); }; static const char * const pd_rev[] = { @@ -1653,7 +1653,6 @@ static void tcpm_queue_vdm_work(struct kthread_work *work) tcpm_queue_vdm(port, event->header, event->data, event->cnt, event->tx_sop_type); port_unlock: - kfree(event->data); kfree(event); mutex_unlock(&port->lock); } @@ -1662,35 +1661,27 @@ static int tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header, const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type) { struct altmode_vdm_event *event; - u32 *data_cpy; int ret = -ENOMEM; - event = kzalloc_obj(*event); + event = kzalloc_flex(*event, data, cnt); if (!event) goto err_event; - data_cpy = kcalloc(cnt, sizeof(u32), GFP_KERNEL); - if (!data_cpy) - goto err_data; - kthread_init_work(&event->work, tcpm_queue_vdm_work); + event->cnt = cnt; event->port = port; event->header = header; - memcpy(data_cpy, data, sizeof(u32) * cnt); - event->data = data_cpy; - event->cnt = cnt; + memcpy(event->data, data, sizeof(u32) * cnt); event->tx_sop_type = tx_sop_type; ret = kthread_queue_work(port->wq, &event->work); if (!ret) { ret = -EBUSY; - goto err_queue; + goto err_data; } return 0; -err_queue: - kfree(data_cpy); err_data: kfree(event); err_event: From afcba2ced16676c7771d4c2e2ccdb62a8bb33d43 Mon Sep 17 00:00:00 2001 From: Pengyu Luo Date: Thu, 5 Mar 2026 22:40:36 +0800 Subject: [PATCH 020/176] usb: ucsi: huawei_gaokun: make gaokun_ucsi_ops static The gaokun_ucsi_ops structure is only used within its translation unit and is not referenced from any other file. Add the 'static' qualifier to avoid unnecessary symbol export. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202603050203.KD4RWA00-lkp@intel.com Signed-off-by: Pengyu Luo Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260305144054.27848-1-mitltlatltl@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c index c5965656baba..ca749fde49bd 100644 --- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -193,7 +193,7 @@ static void gaokun_ucsi_connector_status(struct ucsi_connector *con) gaokun_set_orientation(con, &uec->ports[idx]); } -const struct ucsi_operations gaokun_ucsi_ops = { +static const struct ucsi_operations gaokun_ucsi_ops = { .read_version = gaokun_ucsi_read_version, .read_cci = gaokun_ucsi_read_cci, .poll_cci = gaokun_ucsi_read_cci, From 5d79c525405dcf9611c1f019b0aa05d72f0186ae Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 10 Mar 2026 18:17:34 +0100 Subject: [PATCH 021/176] usb: typec: fusb302: add DRM DP HPD bridge support Add support to use fusb302 based USB-C connectors with the DP altmode helper code on devicetree based platforms. To get this working there must be a DRM bridge chain from the DisplayPort controller to the USB-C connector. E.g. on Rockchip RK3576: root@rk3576 # cat /sys/kernel/debug/dri/0/encoder-0/bridges bridge[0]: dw_dp_bridge_funcs refcount: 7 type: [10] DP OF: /soc/dp@27e40000:rockchip,rk3576-dp ops: [0x47] detect edid hpd bridge[1]: drm_aux_bridge_funcs refcount: 4 type: [0] Unknown OF: /soc/phy@2b010000:rockchip,rk3576-usbdp-phy ops: [0x0] bridge[2]: drm_aux_hpd_bridge_funcs refcount: 5 type: [10] DP OF: /soc/i2c@2ac50000/typec-portc@22/connector:usb-c-connector ops: [0x4] hpd Signed-off-by: Sebastian Reichel Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260310-fusb302-drm-dp-hpd-bridge-v1-1-ffd41ef9afe3@collabora.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/Kconfig | 2 ++ drivers/usb/typec/tcpm/fusb302.c | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig index 8cdd84ca5d6f..00baa7503d45 100644 --- a/drivers/usb/typec/tcpm/Kconfig +++ b/drivers/usb/typec/tcpm/Kconfig @@ -58,6 +58,8 @@ config TYPEC_FUSB302 tristate "Fairchild FUSB302 Type-C chip driver" depends on I2C depends on EXTCON || !EXTCON + depends on DRM || DRM=n + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF help The Fairchild FUSB302 Type-C chip driver that works with Type-C Port Controller Manager to provide USB PD and USB diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 19ff8217818e..ce7069fb4be6 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -5,6 +5,7 @@ * Fairchild FUSB302 Type-C Chip Driver */ +#include #include #include #include @@ -1689,6 +1690,7 @@ static int fusb302_probe(struct i2c_client *client) { struct fusb302_chip *chip; struct i2c_adapter *adapter = client->adapter; + struct auxiliary_device *bridge_dev; struct device *dev = &client->dev; const char *name; int ret = 0; @@ -1747,6 +1749,13 @@ static int fusb302_probe(struct i2c_client *client) goto destroy_workqueue; } + bridge_dev = devm_drm_dp_hpd_bridge_alloc(chip->dev, to_of_node(chip->tcpc_dev.fwnode)); + if (IS_ERR(bridge_dev)) { + ret = PTR_ERR(bridge_dev); + dev_err_probe(chip->dev, ret, "failed to alloc bridge\n"); + goto destroy_workqueue; + } + chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev); if (IS_ERR(chip->tcpm_port)) { fwnode_handle_put(chip->tcpc_dev.fwnode); @@ -1764,6 +1773,10 @@ static int fusb302_probe(struct i2c_client *client) enable_irq_wake(chip->gpio_int_n_irq); i2c_set_clientdata(client, chip); + ret = devm_drm_dp_hpd_bridge_add(chip->dev, bridge_dev); + if (ret) + return ret; + return ret; tcpm_unregister_port: From 7cc9508563f0b9ab2b54d1f1167040bd81373bf6 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Wed, 4 Mar 2026 19:39:15 +0800 Subject: [PATCH 022/176] usb: dwc3: fix a kernel-doc issue Add '*' to needs_full_reinit comment line to fix a kernel-doc issue. Signed-off-by: Xu Yang Acked-by: Thinh Nguyen Reviewed-by: Daniel Baluta Link: https://patch.msgid.link/20260304113916.856841-1-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 7d0845184223..d5c6da62bb6a 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1121,7 +1121,7 @@ struct dwc3_glue_ops { * @usb2_lpm_disable: set to disable usb2 lpm for host * @usb2_gadget_lpm_disable: set to disable usb2 lpm for gadget * @needs_full_reinit: set to indicate the core may lose power and need full - initialization during system pm + * initialization during system pm * @disable_scramble_quirk: set if we enable the disable scramble quirk * @u2exit_lfps_quirk: set if we enable u2exit lfps quirk * @u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk From fa305f884c84e889d40806a1b09aeab0081ff12c Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Wed, 4 Mar 2026 19:39:16 +0800 Subject: [PATCH 023/176] usb: dwc3: fix a typo 'HishSpeed' It should be 'HighSpeed' instead of 'HishSpeed'. Signed-off-by: Xu Yang Reviewed-by: Daniel Baluta Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260304113916.856841-2-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index d5c6da62bb6a..e0dee9d28740 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1152,7 +1152,7 @@ struct dwc3_glue_ops { * VBUS with an external supply. * @parkmode_disable_ss_quirk: set if we need to disable all SuperSpeed * instances in park mode. - * @parkmode_disable_hs_quirk: set if we need to disable all HishSpeed + * @parkmode_disable_hs_quirk: set if we need to disable all HighSpeed * instances in park mode. * @gfladj_refclk_lpm_sel: set if we need to enable SOF/ITP counter * running based on ref_clk From 04e82f27d4c53542bdfb0d0d65985b339e32f884 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 5 Mar 2026 11:28:31 +0100 Subject: [PATCH 024/176] usb: image: microtek: cleanup ep handling We now have macros for endpoint numbers, types and directions. Use them. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260305102905.2392512-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/microtek.c | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 45dc209f5fe5..3d58166f9d61 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -688,28 +688,20 @@ static int mts_usb_probe(struct usb_interface *intf, } for( i = 0; i < altsetting->desc.bNumEndpoints; i++ ) { - if ((altsetting->endpoint[i].desc.bmAttributes & - USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) { - - MTS_WARNING( "can only deal with bulk endpoints; endpoint %d is not bulk.\n", - (int)altsetting->endpoint[i].desc.bEndpointAddress ); - } else { - if (altsetting->endpoint[i].desc.bEndpointAddress & - USB_DIR_IN) - *ep_in_current++ - = altsetting->endpoint[i].desc.bEndpointAddress & - USB_ENDPOINT_NUMBER_MASK; - else { - if ( ep_out != -1 ) { - MTS_WARNING( "can only deal with one output endpoints. Bailing out." ); - return -ENODEV; - } - - ep_out = altsetting->endpoint[i].desc.bEndpointAddress & - USB_ENDPOINT_NUMBER_MASK; + if (usb_endpoint_is_bulk_in(&altsetting->endpoint[i].desc)) { + *ep_in_current++ = usb_endpoint_num(&altsetting->endpoint[i].desc); + } else if (usb_endpoint_is_bulk_out(&altsetting->endpoint[i].desc)) { + if (ep_out == -1) { + ep_out = usb_endpoint_num(&altsetting->endpoint[i].desc); + } else { + MTS_WARNING( "can only deal with bulk endpoints; endpoint %d is not bulk.\n", + usb_endpoint_num(&altsetting->endpoint[i].desc)); + return -ENODEV; } + } else { + MTS_WARNING( "can only deal with bulk endpoints; endpoint %d is not bulk.\n", + (int)altsetting->endpoint[i].desc.bEndpointAddress ); } - } if (ep_in_current != &ep_in_set[2]) { From 9197136fe4b0ac637ee40c84be45baa6c2a9ed22 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 5 Mar 2026 11:28:32 +0100 Subject: [PATCH 025/176] usb: image: microtek: remove function trace macro This functionality has been obsoleted by ftrace Remove it Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260305102905.2392512-2-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/microtek.c | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 3d58166f9d61..5a5999c7e2cb 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -184,11 +184,8 @@ static struct usb_driver mts_usb_driver = { #define MTS_DEBUG(x...) \ printk( KERN_DEBUG MTS_NAME x ) -#define MTS_DEBUG_GOT_HERE() \ - MTS_DEBUG("got to %s:%d (%s)\n", __FILE__, (int)__LINE__, __func__ ) #define MTS_DEBUG_INT() \ - do { MTS_DEBUG_GOT_HERE(); \ - MTS_DEBUG("transfer = 0x%x context = 0x%x\n",(int)transfer,(int)context ); \ + do { MTS_DEBUG("transfer = 0x%x context = 0x%x\n",(int)transfer,(int)context ); \ MTS_DEBUG("status = 0x%x data-length = 0x%x sent = 0x%x\n",transfer->status,(int)context->data_length, (int)transfer->actual_length ); \ mts_debug_dump(context->instance);\ } while(0) @@ -197,7 +194,6 @@ static struct usb_driver mts_usb_driver = { #define MTS_NUL_STATEMENT do { } while(0) #define MTS_DEBUG(x...) MTS_NUL_STATEMENT -#define MTS_DEBUG_GOT_HERE() MTS_NUL_STATEMENT #define MTS_DEBUG_INT() MTS_NUL_STATEMENT #endif @@ -316,7 +312,6 @@ static inline void mts_debug_dump(struct mts_desc* dummy) #endif static inline void mts_urb_abort(struct mts_desc* desc) { - MTS_DEBUG_GOT_HERE(); mts_debug_dump(desc); usb_kill_urb( desc->urb ); @@ -332,8 +327,6 @@ static int mts_scsi_abort(struct scsi_cmnd *srb) { struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); - MTS_DEBUG_GOT_HERE(); - mts_urb_abort(desc); return FAILED; @@ -344,7 +337,6 @@ static int mts_scsi_host_reset(struct scsi_cmnd *srb) struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); int result; - MTS_DEBUG_GOT_HERE(); mts_debug_dump(desc); result = usb_lock_device_for_reset(desc->usb_dev, desc->usb_intf); @@ -452,12 +444,9 @@ static void mts_command_done( struct urb *transfer ) if ( unlikely(status) ) { if (status == -ENOENT) { /* We are being killed */ - MTS_DEBUG_GOT_HERE(); set_host_byte(context->srb, DID_ABORT); } else { /* A genuine error has occurred */ - MTS_DEBUG_GOT_HERE(); - set_host_byte(context->srb, DID_ERROR); } mts_transfer_cleanup(transfer); @@ -523,8 +512,6 @@ mts_build_transfer_context(struct scsi_cmnd *srb, struct mts_desc* desc) { int pipe; - MTS_DEBUG_GOT_HERE(); - desc->context.instance = desc; desc->context.srb = srb; @@ -565,7 +552,6 @@ static enum scsi_qc_status mts_scsi_queuecommand_lck(struct scsi_cmnd *srb) struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); int res; - MTS_DEBUG_GOT_HERE(); mts_show_command(srb); mts_debug_dump(desc); @@ -666,19 +652,15 @@ static int mts_usb_probe(struct usb_interface *intf, /* the current altsetting on the interface we're probing */ struct usb_host_interface *altsetting; - MTS_DEBUG_GOT_HERE(); MTS_DEBUG( "usb-device descriptor at %x\n", (int)dev ); MTS_DEBUG( "product id = 0x%x, vendor id = 0x%x\n", le16_to_cpu(dev->descriptor.idProduct), le16_to_cpu(dev->descriptor.idVendor) ); - MTS_DEBUG_GOT_HERE(); - /* the current altsetting on the interface we're probing */ altsetting = intf->cur_altsetting; - /* Check if the config is sane */ if ( altsetting->desc.bNumEndpoints != MTS_EP_TOTAL ) { From ca1f98576e4b363da96c32f7130af0bb29d85912 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 5 Mar 2026 11:28:33 +0100 Subject: [PATCH 026/176] usb: image: microtek: remove outdated comment The comment is no longer true Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260305102905.2392512-3-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/microtek.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 5a5999c7e2cb..4de1de31681a 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -55,7 +55,6 @@ * Status: * * Untested with multiple scanners. - * Untested on SMP. * Untested on a bigendian machine. * * History: From ef0c4a4f04cbd4126311e2056d553c8c953d112d Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 5 Mar 2026 11:28:34 +0100 Subject: [PATCH 027/176] usb: image: microtek: remove unused macro No users left. Remove it. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260305102905.2392512-4-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/microtek.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 4de1de31681a..cd68aa27639e 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -175,8 +175,6 @@ static struct usb_driver mts_usb_driver = { printk( KERN_ERR MTS_NAME x ) #define MTS_INT_ERROR(x...) \ MTS_ERROR(x) -#define MTS_MESSAGE(x...) \ - printk( KERN_INFO MTS_NAME x ) #if defined MTS_DO_DEBUG From 3222f3ed0b12056d66d2b00c751dafa5ba614822 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 5 Mar 2026 11:28:35 +0100 Subject: [PATCH 028/176] usb: image: microtek: use dev_warn and dev_err Do not use useless private macros Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260305102905.2392512-5-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/image/microtek.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index cd68aa27639e..1f0b3c44d388 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -169,13 +169,6 @@ static struct usb_driver mts_usb_driver = { #define MTS_VERSION "0.4.3" #define MTS_NAME "microtek usb (rev " MTS_VERSION "): " -#define MTS_WARNING(x...) \ - printk( KERN_WARNING MTS_NAME x ) -#define MTS_ERROR(x...) \ - printk( KERN_ERR MTS_NAME x ) -#define MTS_INT_ERROR(x...) \ - MTS_ERROR(x) - #if defined MTS_DO_DEBUG #define MTS_DEBUG(x...) \ @@ -375,7 +368,8 @@ void mts_int_submit_urb (struct urb* transfer, res = usb_submit_urb( transfer, GFP_ATOMIC ); if ( unlikely(res) ) { - MTS_INT_ERROR( "could not submit URB! Error was %d\n",(int)res ); + dev_err(&context->instance->usb_dev->dev, + "could not submit URB! Error was %d\n",(int)res ); set_host_byte(context->srb, DID_ERROR); mts_transfer_cleanup(transfer); } @@ -584,7 +578,7 @@ static enum scsi_qc_status mts_scsi_queuecommand_lck(struct scsi_cmnd *srb) res=usb_submit_urb(desc->urb, GFP_ATOMIC); if(unlikely(res)){ - MTS_ERROR("error %d submitting URB\n",(int)res); + dev_err(&desc->usb_dev->dev, "error %d submitting URB\n",(int)res); set_host_byte(srb, DID_ERROR); if(likely(callback != NULL)) @@ -661,7 +655,7 @@ static int mts_usb_probe(struct usb_interface *intf, /* Check if the config is sane */ if ( altsetting->desc.bNumEndpoints != MTS_EP_TOTAL ) { - MTS_WARNING( "expecting %d got %d endpoints! Bailing out.\n", + dev_warn(&dev->dev, "expecting %d got %d endpoints! Bailing out.\n", (int)MTS_EP_TOTAL, (int)altsetting->desc.bNumEndpoints ); return -ENODEV; } @@ -673,23 +667,23 @@ static int mts_usb_probe(struct usb_interface *intf, if (ep_out == -1) { ep_out = usb_endpoint_num(&altsetting->endpoint[i].desc); } else { - MTS_WARNING( "can only deal with bulk endpoints; endpoint %d is not bulk.\n", + dev_warn(&dev->dev, "can only deal with bulk endpoints; endpoint %d is not bulk.\n", usb_endpoint_num(&altsetting->endpoint[i].desc)); return -ENODEV; } } else { - MTS_WARNING( "can only deal with bulk endpoints; endpoint %d is not bulk.\n", - (int)altsetting->endpoint[i].desc.bEndpointAddress ); + dev_warn(&dev->dev, "can only deal with bulk endpoints; endpoint %d is not bulk.\n", + usb_endpoint_num(&altsetting->endpoint[i].desc)); } } if (ep_in_current != &ep_in_set[2]) { - MTS_WARNING("couldn't find two input bulk endpoints. Bailing out.\n"); + dev_warn(&dev->dev, "couldn't find two input bulk endpoints. Bailing out.\n"); return -ENODEV; } if ( ep_out == -1 ) { - MTS_WARNING( "couldn't find an output bulk endpoint. Bailing out.\n" ); + dev_warn(&dev->dev, "couldn't find an output bulk endpoint. Bailing out.\n" ); return -ENODEV; } @@ -715,15 +709,15 @@ static int mts_usb_probe(struct usb_interface *intf, new_desc->ep_image = ep_in_set[1]; if ( new_desc->ep_out != MTS_EP_OUT ) - MTS_WARNING( "will this work? Command EP is not usually %d\n", + dev_warn(&dev->dev, "will this work? Command EP is not usually %d\n", (int)new_desc->ep_out ); if ( new_desc->ep_response != MTS_EP_RESPONSE ) - MTS_WARNING( "will this work? Response EP is not usually %d\n", + dev_warn(&dev->dev, "will this work? Response EP is not usually %d\n", (int)new_desc->ep_response ); if ( new_desc->ep_image != MTS_EP_IMAGE ) - MTS_WARNING( "will this work? Image data EP is not usually %d\n", + dev_warn(&dev->dev, "will this work? Image data EP is not usually %d\n", (int)new_desc->ep_image ); new_desc->host = scsi_host_alloc(&mts_scsi_host_template, From 70cd7459ea42ac4ee059ee9522e23f6028dfd1bd Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 5 Mar 2026 12:15:07 +0100 Subject: [PATCH 029/176] USB: cypress_cy7c63: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260305111511.18386-2-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/cypress_cy7c63.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c index 99185fc3e9df..4a7f955ba85b 100644 --- a/drivers/usb/misc/cypress_cy7c63.c +++ b/drivers/usb/misc/cypress_cy7c63.c @@ -215,7 +215,7 @@ static int cypress_probe(struct usb_interface *interface, if (!dev) goto error_mem; - dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->udev = interface_to_usbdev(interface); /* save our data pointer in this interface device */ usb_set_intfdata(interface, dev); @@ -239,8 +239,6 @@ static void cypress_disconnect(struct usb_interface *interface) * device files have been removed */ usb_set_intfdata(interface, NULL); - usb_put_dev(dev->udev); - dev_info(&interface->dev, "Cypress CY7C63xxx device now disconnected\n"); From bb49af3c56a60af57abdd5e6c753da87d30306fe Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 5 Mar 2026 12:15:08 +0100 Subject: [PATCH 030/176] USB: cytherm: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260305111511.18386-3-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/cytherm.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/misc/cytherm.c b/drivers/usb/misc/cytherm.c index 2bf082474e9d..b183df9826bc 100644 --- a/drivers/usb/misc/cytherm.c +++ b/drivers/usb/misc/cytherm.c @@ -311,7 +311,7 @@ static int cytherm_probe(struct usb_interface *interface, if (!dev) goto error_mem; - dev->udev = usb_get_dev(udev); + dev->udev = udev; usb_set_intfdata(interface, dev); @@ -334,8 +334,6 @@ static void cytherm_disconnect(struct usb_interface *interface) /* first remove the files, then NULL the pointer */ usb_set_intfdata(interface, NULL); - usb_put_dev(dev->udev); - kfree(dev); dev_info(&interface->dev, "Cypress thermometer now disconnected\n"); From 9970f8388642410354d6e4e783c655a50fba8ca6 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 5 Mar 2026 12:15:09 +0100 Subject: [PATCH 031/176] USB: ljca: drop redundant interface reference Driver core holds a reference to the USB interface while it is bound to a driver and there is no need to take another reference unless the interface is needed after disconnect. Drop the redundant interface reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Acked-by: Sakari Ailus Link: https://patch.msgid.link/20260305111511.18386-4-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usb-ljca.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/usb/misc/usb-ljca.c b/drivers/usb/misc/usb-ljca.c index 7e85fd12da56..c60121faa3da 100644 --- a/drivers/usb/misc/usb-ljca.c +++ b/drivers/usb/misc/usb-ljca.c @@ -776,7 +776,7 @@ static int ljca_probe(struct usb_interface *interface, init_completion(&adap->cmd_completion); INIT_LIST_HEAD(&adap->client_list); - adap->intf = usb_get_intf(interface); + adap->intf = interface; adap->usb_dev = usb_dev; adap->dev = dev; @@ -787,7 +787,7 @@ static int ljca_probe(struct usb_interface *interface, ret = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL); if (ret) { dev_err(dev, "bulk endpoints not found\n"); - goto err_put; + goto err_destroy_mutex; } adap->rx_pipe = usb_rcvbulkpipe(usb_dev, usb_endpoint_num(ep_in)); adap->tx_pipe = usb_sndbulkpipe(usb_dev, usb_endpoint_num(ep_out)); @@ -797,14 +797,14 @@ static int ljca_probe(struct usb_interface *interface, adap->rx_buf = devm_kzalloc(dev, adap->rx_len, GFP_KERNEL); if (!adap->rx_buf) { ret = -ENOMEM; - goto err_put; + goto err_destroy_mutex; } /* alloc rx urb */ adap->rx_urb = usb_alloc_urb(0, GFP_KERNEL); if (!adap->rx_urb) { ret = -ENOMEM; - goto err_put; + goto err_destroy_mutex; } usb_fill_bulk_urb(adap->rx_urb, usb_dev, adap->rx_pipe, adap->rx_buf, adap->rx_len, ljca_recv, adap); @@ -836,10 +836,7 @@ static int ljca_probe(struct usb_interface *interface, err_free: usb_free_urb(adap->rx_urb); - -err_put: - usb_put_intf(adap->intf); - +err_destroy_mutex: mutex_destroy(&adap->mutex); return ret; @@ -864,8 +861,6 @@ static void ljca_disconnect(struct usb_interface *interface) usb_free_urb(adap->rx_urb); - usb_put_intf(adap->intf); - mutex_destroy(&adap->mutex); } From 58221c728d087ece9671d4d9f6fd0a321531d2de Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 5 Mar 2026 12:15:10 +0100 Subject: [PATCH 032/176] USB: trancevibrator: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260305111511.18386-5-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/trancevibrator.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/misc/trancevibrator.c b/drivers/usb/misc/trancevibrator.c index 6aaec2db360b..37f6b79889a6 100644 --- a/drivers/usb/misc/trancevibrator.c +++ b/drivers/usb/misc/trancevibrator.c @@ -92,7 +92,7 @@ static int tv_probe(struct usb_interface *interface, goto error; } - dev->udev = usb_get_dev(udev); + dev->udev = udev; usb_set_intfdata(interface, dev); return 0; @@ -108,7 +108,6 @@ static void tv_disconnect(struct usb_interface *interface) dev = usb_get_intfdata (interface); usb_set_intfdata(interface, NULL); - usb_put_dev(dev->udev); kfree(dev); } From 9b91cafe8084d83c14d0303fc5897c4ff43122c0 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 5 Mar 2026 12:15:11 +0100 Subject: [PATCH 033/176] USB: usbsevseg: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260305111511.18386-6-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usbsevseg.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/misc/usbsevseg.c b/drivers/usb/misc/usbsevseg.c index b37bf53dd54f..89d25fcef642 100644 --- a/drivers/usb/misc/usbsevseg.c +++ b/drivers/usb/misc/usbsevseg.c @@ -312,7 +312,7 @@ static int sevseg_probe(struct usb_interface *interface, if (!mydev) goto error_mem; - mydev->udev = usb_get_dev(udev); + mydev->udev = udev; mydev->intf = interface; usb_set_intfdata(interface, mydev); @@ -338,7 +338,6 @@ static void sevseg_disconnect(struct usb_interface *interface) mydev = usb_get_intfdata(interface); usb_set_intfdata(interface, NULL); - usb_put_dev(mydev->udev); kfree(mydev); dev_info(&interface->dev, "USB 7 Segment now disconnected\n"); } From 7afa83a7a8bf3f1d6984c29fe77a2fb44d9f049d Mon Sep 17 00:00:00 2001 From: Zeeshan Ahmad Date: Wed, 25 Feb 2026 11:51:57 +0500 Subject: [PATCH 034/176] usb: dwc3: qcom: simplify error check in dwc3_qcom_find_num_ports() The platform_get_irq_byname_optional() function returns a non-zero IRQ number on success and a negative error code on failure. It never returns zero. The current implementation in the modern dwc3-qcom driver checks for a return value less than or equal to zero. Since zero is not a valid return value, simplify the check to only look for negative error codes. This aligns the logic with the standard return contract of the platform IRQ APIs. Signed-off-by: Zeeshan Ahmad Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260225065157.8952-1-zeeshanahmad022019@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-qcom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 9ac75547820d..f43f73ac36ff 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -526,14 +526,14 @@ static int dwc3_qcom_find_num_ports(struct platform_device *pdev) int irq; irq = platform_get_irq_byname_optional(pdev, "dp_hs_phy_1"); - if (irq <= 0) + if (irq < 0) return 1; for (port_num = 2; port_num <= DWC3_QCOM_MAX_PORTS; port_num++) { sprintf(irq_name, "dp_hs_phy_%d", port_num); irq = platform_get_irq_byname_optional(pdev, irq_name); - if (irq <= 0) + if (irq < 0) return port_num - 1; } From 79a860ad214d034d1a5be8dc83811bd97e9aafb4 Mon Sep 17 00:00:00 2001 From: Zeeshan Ahmad Date: Fri, 6 Mar 2026 14:06:43 +0500 Subject: [PATCH 035/176] usb: dwc3: gadget: use explicit 0 for success in __dwc3_gadget_kick_transfer() Smatch warns that __dwc3_gadget_kick_transfer() might be missing an error code when returning 'ret' at line 1691. While 'ret' is guaranteed to be 0 at this point, returning an explicit 0 improves readability by removing a level of indirection and clarifies the intent that this is a successful "no-op" path. This change also silences the Smatch warning. Suggested-by: Dan Carpenter Signed-off-by: Zeeshan Ahmad Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260306090643.47383-1-zeeshanahmad022019@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/gadget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 0a688904ce8c..3d4ca68e584c 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1688,7 +1688,7 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep) * transfer, there's no need to update the transfer. */ if (!ret && !starting) - return ret; + return 0; req = next_request(&dep->started_list); if (!req) { From 99df63d20dabda8d7ae01bcca7cdb1e92110a555 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Mon, 9 Mar 2026 12:56:51 +0200 Subject: [PATCH 036/176] dt-bindings: usb: qcom,dwc3: Allow high-speed interrupt on Glymur, Hamoa and Milos Some of the controllers found of these platforms can be tied up to a single high-speed PHY, basically rendering them as USB 2.0 controllers. So in this case, the interrupt to the Synopsys DesignWare Core is coming from the high-speed PHY, so allow the interrupt to reflect that. Acked-by: Rob Herring (Arm) Signed-off-by: Abel Vesa Tested-by: Pankaj Patil Link: https://patch.msgid.link/20260309-dts-qcom-glymur-add-usb-support-v4-1-6bdc41f58d18@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index 7d784a648b7d..f879e2e104c4 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -500,7 +500,7 @@ allOf: - const: pwr_event - const: dp_hs_phy_irq - const: dm_hs_phy_irq - - const: ss_phy_irq + - enum: [hs_phy_irq, ss_phy_irq] - if: properties: From 78bf06db167b1cddc7f46c2d30c11cca8e32b5d8 Mon Sep 17 00:00:00 2001 From: Loic Poulain Date: Tue, 17 Feb 2026 11:34:03 +0100 Subject: [PATCH 037/176] usb: typec: ucsi: Invoke ucsi_run_command tracepoint The ucsi_run_command trace event is exposed in tracefs, but it never produces any output because the UCSI core never invokes the associated tracepoint. As a result, enabling the event under events/ucsi/ yields no traces, preventing users from inspecting UCSI command sequencing. Wire the tracepoint into the UCSI command path so that commands are properly reported. Example: 50.692342: ucsi_run_command: GET_CONNECTOR_STATUS -> OK (err=0) 50.692345: ucsi_connector_change: port0 status: change=4800, ... 51.750298: ucsi_run_command: GET_CABLE_PROPERTY -> FAIL (err=-5) 51.773360: ucsi_run_command: GET_CONNECTOR_STATUS -> OK (err=0) Signed-off-by: Loic Poulain Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260217103403.1956-1-loic.poulain@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index f38a4d7ebc42..4efbe41d7714 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -235,6 +235,8 @@ static int ucsi_send_command_common(struct ucsi *ucsi, u64 cmd, if (cci & UCSI_CCI_ERROR) ret = ucsi_read_error(ucsi, connector_num); + trace_ucsi_run_command(cmd, ret); + mutex_unlock(&ucsi->ppm_lock); return ret; } From 0313023f19054f2d267382e04d0c00988640e9f0 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 16 Feb 2026 12:04:04 +0100 Subject: [PATCH 038/176] USB: typec: tcpci: Make tcpci_pm_ops variable static File-scope 'tcpci_pm_ops' is not used outside of this unit, so make it static to silence sparse warning: tcpm/tcpci.c:1002:1: warning: symbol 'tcpci_pm_ops' was not declared. Should it be static? Signed-off-by: Krzysztof Kozlowski Reviewed-by: Heikki Krogerus Reviewed-by: Badhri Jagan Sridharan Link: https://patch.msgid.link/20260216110403.159945-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 2a951c585e92..8b7e6eb92ca2 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -999,7 +999,7 @@ static int tcpci_resume(struct device *dev) return ret; } -DEFINE_SIMPLE_DEV_PM_OPS(tcpci_pm_ops, tcpci_suspend, tcpci_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(tcpci_pm_ops, tcpci_suspend, tcpci_resume); static const struct i2c_device_id tcpci_id[] = { { "tcpci" }, From f2529d08fcb429ea01bb87c326342f41483f8b2f Mon Sep 17 00:00:00 2001 From: Ethan Tidmore Date: Wed, 18 Feb 2026 15:46:21 -0600 Subject: [PATCH 039/176] usb: typec: Fix error pointer dereference The variable tps->partner is checked for an error pointer and then if it is, it sends an error message but does not return and then immediately dereferenced a few lines below: tps->partner = typec_register_partner(tps->port, &desc); if (IS_ERR(tps->partner)) dev_warn(tps->dev, "%s: failed to register partnet\n", __func__); if (desc.identity) { typec_partner_set_identity(tps->partner); cd321x->cur_partner_identity = st.partner_identity; } Add early return and fix spelling mistake in error message. Detected by Smatch: drivers/usb/typec/tipd/core.c:827 cd321x_update_work() error: 'tps->partner' dereferencing possible ERR_PTR() Fixes: 82432bbfb9e83 ("usb: typec: tipd: Handle mode transitions for CD321x") Signed-off-by: Ethan Tidmore Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260218214621.38154-1-ethantidmore06@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index e2b26af2b84a..43faec794b95 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -820,8 +820,10 @@ static void cd321x_update_work(struct work_struct *work) desc.identity = &st.partner_identity; tps->partner = typec_register_partner(tps->port, &desc); - if (IS_ERR(tps->partner)) - dev_warn(tps->dev, "%s: failed to register partnet\n", __func__); + if (IS_ERR(tps->partner)) { + dev_warn(tps->dev, "%s: failed to register partner\n", __func__); + return; + } if (desc.identity) { typec_partner_set_identity(tps->partner); From a53b4f9c51a90a556bca129d632b81f49b1a4061 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 23 Feb 2026 19:27:38 +0100 Subject: [PATCH 040/176] usb: typec: mux: avoid duplicated orientation switches Some devices use combo PHYs (i.e. USB3 + DisplayPort), which also handle the orientation mux. These PHYs are referenced twice from the USB-C connector (USB super-speed lines and SBU/AUX lines) resulting in the switch being configured twice. Avoid this by dropping duplicates. Signed-off-by: Sebastian Reichel Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260223-typec-mux-duplication-fix-v2-1-0402fefc222e@collabora.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 58fb97ea6877..9b908c46bd7d 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -35,7 +35,9 @@ static int switch_fwnode_match(struct device *dev, const void *fwnode) static void *typec_switch_match(const struct fwnode_handle *fwnode, const char *id, void *data) { + struct typec_switch_dev **sw_devs = data; struct device *dev; + int i; /* * Device graph (OF graph) does not give any means to identify the @@ -56,6 +58,13 @@ static void *typec_switch_match(const struct fwnode_handle *fwnode, dev = class_find_device(&typec_mux_class, NULL, fwnode, switch_fwnode_match); + /* Skip duplicates */ + for (i = 0; i < TYPEC_MUX_MAX_DEVS; i++) + if (to_typec_switch_dev(dev) == sw_devs[i]) { + put_device(dev); + return NULL; + } + return dev ? to_typec_switch_dev(dev) : ERR_PTR(-EPROBE_DEFER); } @@ -80,7 +89,8 @@ struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode) if (!sw) return ERR_PTR(-ENOMEM); - count = fwnode_connection_find_matches(fwnode, "orientation-switch", NULL, + count = fwnode_connection_find_matches(fwnode, "orientation-switch", + (void **)sw_devs, typec_switch_match, (void **)sw_devs, ARRAY_SIZE(sw_devs)); From b145c3f29d62f71cc9d2d714e2d4ae4c8d3f863d Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 23 Feb 2026 19:27:39 +0100 Subject: [PATCH 041/176] usb: typec: mux: avoid duplicated mux switches Some devices use combo PHYs (i.e. USB3 + DisplayPort), which also handle the lane muxing. These PHYs are referenced twice from the USB-C connector (USB super-speed lines and SBU/AUX lines) resulting in the mux being configured twice. Avoid this by dropping duplicates. Signed-off-by: Sebastian Reichel Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260223-typec-mux-duplication-fix-v2-2-0402fefc222e@collabora.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 9b908c46bd7d..db5e4a4c0a99 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -275,7 +275,9 @@ static int mux_fwnode_match(struct device *dev, const void *fwnode) static void *typec_mux_match(const struct fwnode_handle *fwnode, const char *id, void *data) { + struct typec_mux_dev **mux_devs = data; struct device *dev; + int i; /* * Device graph (OF graph) does not give any means to identify the @@ -291,6 +293,14 @@ static void *typec_mux_match(const struct fwnode_handle *fwnode, dev = class_find_device(&typec_mux_class, NULL, fwnode, mux_fwnode_match); + /* Skip duplicates */ + for (i = 0; i < TYPEC_MUX_MAX_DEVS; i++) + if (to_typec_mux_dev(dev) == mux_devs[i]) { + put_device(dev); + return NULL; + } + + return dev ? to_typec_mux_dev(dev) : ERR_PTR(-EPROBE_DEFER); } @@ -316,7 +326,8 @@ struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode) return ERR_PTR(-ENOMEM); count = fwnode_connection_find_matches(fwnode, "mode-switch", - NULL, typec_mux_match, + (void **)mux_devs, + typec_mux_match, (void **)mux_devs, ARRAY_SIZE(mux_devs)); if (count <= 0) { From c384f7ad44f940c2d054bbe4c06840e2073af788 Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 10 Mar 2026 17:44:29 +0800 Subject: [PATCH 042/176] USB: serial: ti_usb_3410_5052: Use safer strscpy() instead of strcpy() Use a safer function strscpy() instead of strcpy() for copying to arrays. Only idiomatic code replacement, and no functional changes. Signed-off-by: Ai Chao Link: https://patch.msgid.link/20260310094434.3639602-2-aichao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/ti_usb_3410_5052.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index 658b54d8fcef..b3591d6d7645 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -1600,29 +1600,29 @@ static int ti_download_firmware(struct ti_device *tdev) if (le16_to_cpu(dev->descriptor.idVendor) == MTS_VENDOR_ID) { switch (le16_to_cpu(dev->descriptor.idProduct)) { case MTS_CDMA_PRODUCT_ID: - strcpy(buf, "mts_cdma.fw"); + strscpy(buf, "mts_cdma.fw"); break; case MTS_GSM_PRODUCT_ID: - strcpy(buf, "mts_gsm.fw"); + strscpy(buf, "mts_gsm.fw"); break; case MTS_EDGE_PRODUCT_ID: - strcpy(buf, "mts_edge.fw"); + strscpy(buf, "mts_edge.fw"); break; case MTS_MT9234MU_PRODUCT_ID: - strcpy(buf, "mts_mt9234mu.fw"); + strscpy(buf, "mts_mt9234mu.fw"); break; case MTS_MT9234ZBA_PRODUCT_ID: - strcpy(buf, "mts_mt9234zba.fw"); + strscpy(buf, "mts_mt9234zba.fw"); break; case MTS_MT9234ZBAOLD_PRODUCT_ID: - strcpy(buf, "mts_mt9234zba.fw"); + strscpy(buf, "mts_mt9234zba.fw"); break; } } if (buf[0] == '\0') { if (tdev->td_is_3410) - strcpy(buf, "ti_3410.fw"); + strscpy(buf, "ti_3410.fw"); else - strcpy(buf, "ti_5052.fw"); + strscpy(buf, "ti_5052.fw"); } status = request_firmware(&fw_p, buf, &dev->dev); } From 786bf7ef564e97f195a2fca379fa7866a4c3ea08 Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 10 Mar 2026 17:44:30 +0800 Subject: [PATCH 043/176] usb: musb: Use safer strscpy() instead of strcpy() Use a safer function strscpy() instead of strcpy() for copying to arrays. Only idiomatic code replacement, and no functional changes. Signed-off-by: Ai Chao Link: https://patch.msgid.link/20260310094434.3639602-3-aichao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 0acc62569ae5..73ac25f53698 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -1600,7 +1600,7 @@ static int musb_core_init(u16 musb_type, struct musb *musb) /* log core options (read using indexed model) */ reg = musb_read_configdata(mbase); - strcpy(aInfo, (reg & MUSB_CONFIGDATA_UTMIDW) ? "UTMI-16" : "UTMI-8"); + strscpy(aInfo, (reg & MUSB_CONFIGDATA_UTMIDW) ? "UTMI-16" : "UTMI-8"); if (reg & MUSB_CONFIGDATA_DYNFIFO) { strcat(aInfo, ", dyn FIFOs"); musb->dyn_fifo = true; From 224fb8661f66a58530564a2cdce42b219adde4cb Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 10 Mar 2026 17:44:31 +0800 Subject: [PATCH 044/176] usb: gadget: functionfs: Use safer strscpy() instead of strcpy() Use a safer function strscpy() instead of strcpy() for copying to arrays. Only idiomatic code replacement, and no functional changes. Signed-off-by: Ai Chao Link: https://patch.msgid.link/20260310094434.3639602-4-aichao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi2.c | 6 +++--- drivers/usb/gadget/function/u_serial.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index b5f0defde95d..19fdac024343 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -1541,9 +1541,9 @@ static int f_midi2_create_card(struct f_midi2 *midi2) return err; midi2->card = card; - strcpy(card->driver, "f_midi2"); - strcpy(card->shortname, "MIDI 2.0 Gadget"); - strcpy(card->longname, "MIDI 2.0 Gadget"); + strscpy(card->driver, "f_midi2"); + strscpy(card->shortname, "MIDI 2.0 Gadget"); + strscpy(card->longname, "MIDI 2.0 Gadget"); id = 0; for (i = 0; i < midi2->num_eps; i++) { diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index e43ad6373846..cdd1dfc666c4 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -1086,7 +1086,7 @@ static int gs_console_init(struct gs_port *port) if (!cons) return -ENOMEM; - strcpy(cons->console.name, "ttyGS"); + strscpy(cons->console.name, "ttyGS"); cons->console.write = gs_console_write; cons->console.device = gs_console_device; cons->console.flags = CON_PRINTBUFFER; From 2131540de4adc8eb7960ebea9915694ec0ce430d Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 10 Mar 2026 17:44:32 +0800 Subject: [PATCH 045/176] usb: typec: tcpm: Use safer strscpy() instead of strcpy() Use a safer function strscpy() instead of strcpy() for copying to arrays. Only idiomatic code replacement, and no functional changes. Signed-off-by: Ai Chao Link: https://patch.msgid.link/20260310094434.3639602-5-aichao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 272f9187b12d..3295d804cf87 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -725,7 +725,7 @@ static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args) if (tcpm_log_full(port)) { port->logbuffer_head = max(port->logbuffer_head - 1, 0); - strcpy(tmpbuffer, "overflow"); + strscpy(tmpbuffer, "overflow"); } if (port->logbuffer_head < 0 || @@ -841,10 +841,10 @@ static void tcpm_log_source_caps(struct tcpm_port *port) pdo_spr_avs_apdo_15v_to_20v_max_current_ma(pdo), pdo_spr_avs_apdo_src_peak_current(pdo)); else - strcpy(msg, "undefined APDO"); + strscpy(msg, "undefined APDO"); break; default: - strcpy(msg, "undefined"); + strscpy(msg, "undefined"); break; } tcpm_log(port, " PDO %d: type %d, %s", From 9b4051a47da5050ba349b630494cf5ee3d5aa1e1 Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 10 Mar 2026 17:44:33 +0800 Subject: [PATCH 046/176] usb: gadget: udc: Use safer strscpy() instead of strcpy() Use a safer function strscpy() instead of strcpy() for copying to arrays. Only idiomatic code replacement, and no functional changes. Signed-off-by: Ai Chao Link: https://patch.msgid.link/20260310094434.3639602-6-aichao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/snps_udc_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c index 5f9514623956..0e0db68e0b27 100644 --- a/drivers/usb/gadget/udc/snps_udc_core.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -3151,7 +3151,7 @@ int udc_probe(struct udc *dev) tmp, dev->phys_addr, dev->chiprev, (dev->chiprev == UDC_HSA0_REV) ? "A0" : "B1"); - strcpy(tmp, UDC_DRIVER_VERSION_STRING); + strscpy(tmp, UDC_DRIVER_VERSION_STRING); if (dev->chiprev == UDC_HSA0_REV) { dev_err(dev->dev, "chip revision is A0; too old\n"); retval = -ENODEV; From 8f196a359e1b4f80d360c57ed32bec15d5dd8e0e Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 10 Mar 2026 17:44:34 +0800 Subject: [PATCH 047/176] usbip: vhci_sysfs: Use safer strscpy() instead of strcpy() Use a safer function strscpy() instead of strcpy() for copying to arrays. Only idiomatic code replacement, and no functional changes. Signed-off-by: Ai Chao Link: https://patch.msgid.link/20260310094434.3639602-7-aichao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci_sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index bfc10f665e52..5bc8c47788d4 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -463,7 +463,7 @@ static void set_status_attr(int id) status = status_attrs + id; if (id == 0) - strcpy(status->name, "status"); + strscpy(status->name, "status"); else snprintf(status->name, MAX_STATUS_NAME+1, "status.%d", id); status->attr.attr.name = status->name; From cd763789d31adac7f38131c5b2892d7a5562a1ee Mon Sep 17 00:00:00 2001 From: Yuanshen Cao Date: Fri, 20 Feb 2026 06:22:40 +0000 Subject: [PATCH 048/176] dt-bindings: usb: document the Etek ET7304 USB Type-C Port Controller Document the ETEK Micro ET7304 USB Type-C Port Controller with USB-PD. Signed-off-by: Yuanshen Cao Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260220-et7304-v3-1-ede2d9634957@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml | 3 ++- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml index ae611f7e57ca..1eb611f35998 100644 --- a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml +++ b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml @@ -19,10 +19,11 @@ description: | properties: compatible: enum: + - etekmicro,et7304 - richtek,rt1711h - richtek,rt1715 description: - RT1711H support PD20, RT1715 support PD30 except Fast Role Swap. + RT1711H support PD20, ET7304 and RT1715 support PD30 except Fast Role Swap. reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index ee7fd3cfe203..5e504cebbcda 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -541,6 +541,8 @@ patternProperties: description: ESTeem Wireless Modems "^eswin,.*": description: Beijing ESWIN Technology Group Co. Ltd. + "^etekmicro,.*": + description: Wuxi ETEK Micro-Electronics Co.,Ltd. "^ettus,.*": description: NI Ettus Research "^eukrea,.*": From ec53fe37a56044a1a8e7751d05b13385fb30741f Mon Sep 17 00:00:00 2001 From: Yuanshen Cao Date: Fri, 20 Feb 2026 06:22:41 +0000 Subject: [PATCH 049/176] usb: typec: tcpm: Add vid and chip info for Etek ET7304 Move VID field to chip info to accommodate different VIDs. Add chip info for Etek Micro ET7304. ET7304 is functionally identical to the Richtek RT1715, with the only difference being the VID. Signed-off-by: Yuanshen Cao Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260220-et7304-v3-2-ede2d9634957@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_rt1711h.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c index 88c50b984e8a..37cf55ad74f8 100644 --- a/drivers/usb/typec/tcpm/tcpci_rt1711h.c +++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c @@ -19,9 +19,11 @@ #include #define RT1711H_VID 0x29CF +#define ET7304_VID 0x6DCF #define RT1711H_PID 0x1711 #define RT1711H_DID 0x2171 #define RT1715_DID 0x2173 +#define ET7304_DID 0x2173 #define RT1711H_PHYCTRL1 0x80 #define RT1711H_PHYCTRL2 0x81 @@ -55,6 +57,7 @@ struct rt1711h_chip_info { u32 rxdz_sel; + u16 vid; u16 did; bool enable_pd30_extended_message; }; @@ -308,7 +311,7 @@ static int rt1711h_check_revision(struct i2c_client *i2c, struct rt1711h_chip *c ret = i2c_smbus_read_word_data(i2c, TCPC_VENDOR_ID); if (ret < 0) return ret; - if (ret != RT1711H_VID) { + if (ret != chip->info->vid) { dev_err(&i2c->dev, "vid is not correct, 0x%04x\n", ret); return -ENODEV; } @@ -405,17 +408,27 @@ static void rt1711h_remove(struct i2c_client *client) tcpci_unregister_port(chip->tcpci); } +static const struct rt1711h_chip_info et7304 = { + .rxdz_sel = RT1711H_BMCIO_RXDZSEL, + .vid = ET7304_VID, + .did = ET7304_DID, + .enable_pd30_extended_message = true, +}; + static const struct rt1711h_chip_info rt1711h = { + .vid = RT1711H_VID, .did = RT1711H_DID, }; static const struct rt1711h_chip_info rt1715 = { .rxdz_sel = RT1711H_BMCIO_RXDZSEL, + .vid = RT1711H_VID, .did = RT1715_DID, .enable_pd30_extended_message = true, }; static const struct i2c_device_id rt1711h_id[] = { + { "et7304", (kernel_ulong_t)&et7304 }, { "rt1711h", (kernel_ulong_t)&rt1711h }, { "rt1715", (kernel_ulong_t)&rt1715 }, {} @@ -423,6 +436,7 @@ static const struct i2c_device_id rt1711h_id[] = { MODULE_DEVICE_TABLE(i2c, rt1711h_id); static const struct of_device_id rt1711h_of_match[] = { + { .compatible = "etekmicro,et7304", .data = &et7304 }, { .compatible = "richtek,rt1711h", .data = &rt1711h }, { .compatible = "richtek,rt1715", .data = &rt1715 }, {} From d6a093c3bf0e4e073b87022ac34b261979325228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 23 Feb 2026 22:20:33 +0100 Subject: [PATCH 050/176] usb: endpoint: drop custom sysfs attribute structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nowadays the USB endpoints use device attributes, so the custom structure is unused. Drop it. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260223-sysfs-const-usb-v1-1-54c4434d83c8@weissschuh.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/endpoint.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/usb/core/endpoint.c b/drivers/usb/core/endpoint.c index 4137ab47f1cd..e00eaf9e22cd 100644 --- a/drivers/usb/core/endpoint.c +++ b/drivers/usb/core/endpoint.c @@ -26,14 +26,6 @@ struct ep_device { #define to_ep_device(_dev) \ container_of(_dev, struct ep_device, dev) -struct ep_attribute { - struct attribute attr; - ssize_t (*show)(struct usb_device *, - struct usb_endpoint_descriptor *, char *); -}; -#define to_ep_attribute(_attr) \ - container_of(_attr, struct ep_attribute, attr) - #define usb_ep_attr(field, format_string) \ static ssize_t field##_show(struct device *dev, \ struct device_attribute *attr, \ From ef22555fbee7c284a6ab55238fcbe4eea9dbb2a4 Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Mon, 23 Feb 2026 20:05:37 +0000 Subject: [PATCH 051/176] dt-bindings: connector: Add sink properties to comply with PD 3.1 spec Add additional properties for ports supporting sink mode. The properties define certain hardware and electrical properties such as sink load step, sink load characteristics, sink compliance and charging adapter Power Delivery Profile (PDP) for the connector. These properties need to be defined for a Type-C port in compliance with the PD 3.1 spec. Signed-off-by: Amit Sunil Dhamne Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260223-skedb-v2-1-60675765bc7e@google.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/connector/usb-connector.yaml | 34 +++++++++++++++++++ .../bindings/usb/maxim,max33359.yaml | 4 +++ include/dt-bindings/usb/pd.h | 18 ++++++++++ 3 files changed, 56 insertions(+) diff --git a/Documentation/devicetree/bindings/connector/usb-connector.yaml b/Documentation/devicetree/bindings/connector/usb-connector.yaml index 11e40d225b9f..901986de3e2b 100644 --- a/Documentation/devicetree/bindings/connector/usb-connector.yaml +++ b/Documentation/devicetree/bindings/connector/usb-connector.yaml @@ -300,6 +300,40 @@ properties: $ref: /schemas/types.yaml#/definitions/uint8-array maxItems: 4 + sink-load-step: + description: Indicates the preferred load step slew rate in mA/usec for + the port (in sink mode). This property is defined in "6.5.13.7" of + "USB Power Delivery Specification Revision 3.1 Version 1.8". + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [150, 500] + default: 150 + + sink-load-characteristics: + description: Indicates the port's (in sink mode) preferred load + characteristics. Users can leverage SINK_LOAD_CHAR() defined in + dt-bindings/usb/pd.h to populate this field. This property is defined in + "6.5.13.8" of "USB Power Delivery Specification Revision 3.1 Version 1.8". + $ref: /schemas/types.yaml#/definitions/uint16 + + sink-compliance: + description: Represents the types of sources the sink device has been tested + and certified with. This property is defined in "6.5.13.9" of + "USB Power Delivery Specification Revision 3.1 Version 1.8" + Bit 0 when set indicates it has been tested on LPS compliant source + Bit 1 when set indicates it has been tested on PS1 compliant source + Bit 2 when set indicates it has been tested on PS2 compliant source + $ref: /schemas/types.yaml#/definitions/uint8 + maximum: 7 + + charging-adapter-pdp-milliwatt: + description: This corresponds to the Power Delivery Profile rating of the + charging adapter shipped or recommended for use with the connector port. + This property is a requirement to infer the USB PD property + "SPR Sink Operational PDP" given in "6.5.13.14" of + "USB Power Delivery Specification Revision 3.1 Version 1.8". + minimum: 0 + maximum: 100000 + dependencies: sink-vdos-v1: [ sink-vdos ] sink-vdos: [ sink-vdos-v1 ] diff --git a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml index 3de4dc40b791..46a3748c8be4 100644 --- a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml +++ b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml @@ -75,6 +75,10 @@ examples: PDO_FIXED(9000, 2000, 0)>; sink-bc12-completion-time-ms = <500>; pd-revision = /bits/ 8 <0x03 0x01 0x01 0x08>; + sink-load-step = <150>; + sink-load-characteristics = /bits/ 16 ; + sink-compliance = /bits/ 8 <(COMPLIANCE_LPS | COMPLIANCE_PS1)>; + charging-adapter-pdp-milliwatt = <18000>; }; }; }; diff --git a/include/dt-bindings/usb/pd.h b/include/dt-bindings/usb/pd.h index e6526b138174..6cff2339bda3 100644 --- a/include/dt-bindings/usb/pd.h +++ b/include/dt-bindings/usb/pd.h @@ -465,4 +465,22 @@ | ((vbm) & 0x3) << 15 | (curr) << 14 | ((vbi) & 0x3f) << 7 \ | ((gi) & 0x3f) << 1 | (ct)) +/* + * Sink Load Characteristics + * ------------------------- + * <15> :: Can tolerate vbus voltage droop + * <11:14> :: Duty cycle in 5% increments when bits 4:0 are non-zero + * <10:5> :: Overload period in 20ms when bits 4:0 are non-zero + * <4:0> :: Percent overload in 10% increments. Values higher than 25 are + * clipped to 250% + */ +#define SINK_LOAD_CHAR(vdroop, duty_cycle, period, percent_ol) \ + (((vdroop) & 0x1) << 15 | ((duty_cycle) & 0xf) << 11 | \ + ((period) & 0x3f) << 5 | ((percent_ol) & 0x1f)) + +/* Compliance */ +#define COMPLIANCE_LPS (1 << 0) +#define COMPLIANCE_PS1 (1 << 1) +#define COMPLIANCE_PS2 (1 << 2) + #endif /* __DT_POWER_DELIVERY_H */ From b558a9cc107287bd49bd9256e5d965afa80acfd6 Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Mon, 23 Feb 2026 20:05:38 +0000 Subject: [PATCH 052/176] usb: typec: tcpm: add support for Sink Cap Extended msg response Add support for responding to Sink Cap Extended msg request. To achieve this, include parsing support for DT properties related to Sink Cap Extended. The request for Sink Cap Ext is a control message while the response is an extended message (chunked). As the Sink Caps Extended Data Block size (24 Byte) is less than MaxExtendedMsgChunkLen (26 Byte), a single chunk is sufficient to complete this AMS. Supporting sink cap extended messages while responding to a Get_Sink_Caps_Extended request when port is in Sink role is required in order to be compliant with at least USB PD Rev3.1 Ver1.8. Signed-off-by: Amit Sunil Dhamne Reviewed-by: Badhri Jagan Sridharan Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260223-skedb-v2-2-60675765bc7e@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 253 +++++++++++++++++++++++++++++++++- include/linux/usb/pd.h | 82 ++++++++++- 2 files changed, 332 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 3295d804cf87..5ea0b0e99e4d 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -188,7 +189,8 @@ S(STRUCTURED_VDMS), \ S(COUNTRY_INFO), \ S(COUNTRY_CODES), \ - S(REVISION_INFORMATION) + S(REVISION_INFORMATION), \ + S(GETTING_SINK_EXTENDED_CAPABILITIES) #define GENERATE_ENUM(e) e #define GENERATE_STRING(s) #s @@ -229,6 +231,7 @@ enum pd_msg_request { PD_MSG_DATA_SINK_CAP, PD_MSG_DATA_SOURCE_CAP, PD_MSG_DATA_REV, + PD_MSG_EXT_SINK_CAP_EXT }; enum adev_actions { @@ -337,6 +340,42 @@ struct pd_timings { u32 snk_bc12_cmpletion_time; }; +/* Convert microwatt to watt */ +#define UW_TO_W(pow) ((pow) / 1000000) + +/* + * struct pd_identifier - Contains info about PD identifiers + * @vid: Vendor ID (assigned by USB-IF) + * @pid: Product ID (assigned by manufacturer) + * @xid: Value assigned by USB-IF for product + */ +struct pd_identifier { + u16 vid; + u16 pid; + u32 xid; +}; + +/* + * struct sink_caps_ext_data - Sink extended capability data + * @load_step: Indicates the load step slew rate. Value of 0 indicates 150mA/us + * & 1 indicates 500 mA/us + * @load_char: Snk overload characteristics + * @compliance: Types of sources the sink has been tested & certified on + * @modes: Charging caps & power sources supported + * @spr_min_pdp: Sink Minimum PDP for SPR mode (in Watts) + * @spr_op_pdp: Sink Operational PDP for SPR mode (in Watts) + * @spr_max_pdp: Sink Maximum PDP for SPR mode (in Watts) + */ +struct sink_caps_ext_data { + u8 load_step; + u16 load_char; + u8 compliance; + u8 modes; + u8 spr_min_pdp; + u8 spr_op_pdp; + u8 spr_max_pdp; +}; + struct tcpm_port { struct device *dev; @@ -585,6 +624,9 @@ struct tcpm_port { /* Indicates maximum (revision, version) supported */ struct pd_revision_info pd_rev; + + struct pd_identifier pd_ident; + struct sink_caps_ext_data sink_caps_ext; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -1367,6 +1409,64 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); } +static int tcpm_pd_send_sink_cap_ext(struct tcpm_port *port) +{ + u16 operating_snk_watt = port->operating_snk_mw / 1000; + struct sink_caps_ext_data *data = &port->sink_caps_ext; + struct pd_identifier *pd_ident = &port->pd_ident; + struct sink_caps_ext_msg skedb = {0}; + struct pd_message msg; + u8 data_obj_cnt; + + if (!port->self_powered) + data->spr_op_pdp = operating_snk_watt; + + /* + * SPR Sink Minimum PDP indicates the minimum power required to operate + * a sink device in its lowest level of functionality without requiring + * power from the battery. We can use the operating_snk_watt value to + * populate it, as operating_snk_watt indicates device's min operating + * power. + */ + data->spr_min_pdp = operating_snk_watt; + + if (data->spr_op_pdp < data->spr_min_pdp || + data->spr_max_pdp < data->spr_op_pdp) { + tcpm_log(port, + "Invalid PDP values, Min PDP:%u, Op PDP:%u, Max PDP:%u", + data->spr_min_pdp, data->spr_op_pdp, data->spr_max_pdp); + return -EOPNOTSUPP; + } + + memset(&msg, 0, sizeof(msg)); + skedb.vid = cpu_to_le16(pd_ident->vid); + skedb.pid = cpu_to_le16(pd_ident->pid); + skedb.xid = cpu_to_le32(pd_ident->xid); + skedb.skedb_ver = SKEDB_VER_1_0; + skedb.load_step = data->load_step; + skedb.load_char = cpu_to_le16(data->load_char); + skedb.compliance = data->compliance; + skedb.modes = data->modes; + skedb.spr_min_pdp = data->spr_min_pdp; + skedb.spr_op_pdp = data->spr_op_pdp; + skedb.spr_max_pdp = data->spr_max_pdp; + memcpy(msg.ext_msg.data, &skedb, sizeof(skedb)); + msg.ext_msg.header = PD_EXT_HDR_LE(sizeof(skedb), + 0, /* Denotes if request chunk */ + 0, /* Chunk Number */ + 1 /* Chunked */); + + data_obj_cnt = count_chunked_data_objs(sizeof(skedb)); + msg.header = cpu_to_le16(PD_HEADER(PD_EXT_SINK_CAP_EXT, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + data_obj_cnt, + 1 /* Denotes if ext header */)); + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + static void mod_tcpm_delayed_work(struct tcpm_port *port, unsigned int delay_ms) { if (delay_ms) { @@ -3646,6 +3746,19 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); break; + case PD_CTRL_GET_SINK_CAP_EXT: + /* This is an unsupported message if port type is SRC */ + if (port->negotiated_rev >= PD_REV30 && + port->port_type != TYPEC_PORT_SRC) + tcpm_pd_handle_msg(port, PD_MSG_EXT_SINK_CAP_EXT, + GETTING_SINK_EXTENDED_CAPABILITIES); + else + tcpm_pd_handle_msg(port, + port->negotiated_rev < PD_REV30 ? + PD_MSG_CTRL_REJECT : + PD_MSG_CTRL_NOT_SUPP, + NONE_AMS); + break; default: tcpm_pd_handle_msg(port, port->negotiated_rev < PD_REV30 ? @@ -3898,6 +4011,16 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) ret); tcpm_ams_finish(port); break; + case PD_MSG_EXT_SINK_CAP_EXT: + ret = tcpm_pd_send_sink_cap_ext(port); + if (ret == -EOPNOTSUPP) + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP); + else if (ret < 0) + tcpm_log(port, + "Unable to transmit sink cap extended, ret=%d", + ret); + tcpm_ams_finish(port); + break; default: break; } @@ -7282,6 +7405,129 @@ static void tcpm_fw_get_timings(struct tcpm_port *port, struct fwnode_handle *fw port->timings.snk_bc12_cmpletion_time = val; } +static void tcpm_fw_get_pd_ident(struct tcpm_port *port) +{ + struct pd_identifier *pd_ident = &port->pd_ident; + u32 *vdo; + + /* First 3 vdo values contain info regarding USB PID, VID & XID */ + if (port->nr_snk_vdo >= 3) + vdo = port->snk_vdo; + else if (port->nr_snk_vdo_v1 >= 3) + vdo = port->snk_vdo_v1; + else + return; + + pd_ident->vid = PD_IDH_VID(vdo[0]); + pd_ident->pid = PD_PRODUCT_PID(vdo[2]); + pd_ident->xid = PD_CSTAT_XID(vdo[1]); + tcpm_log(port, "vid:%#x pid:%#x xid:%#x", + pd_ident->vid, pd_ident->pid, pd_ident->xid); +} + +static void tcpm_parse_snk_pdos(struct tcpm_port *port) +{ + struct sink_caps_ext_data *caps = &port->sink_caps_ext; + u32 max_mv, max_ma; + u8 avs_tier1_pdp, avs_tier2_pdp; + int i, pdo_itr; + u32 *snk_pdos; + + for (i = 0; i < port->pd_count; ++i) { + snk_pdos = port->pd_list[i]->sink_desc.pdo; + for (pdo_itr = 0; pdo_itr < PDO_MAX_OBJECTS && snk_pdos[pdo_itr]; + ++pdo_itr) { + u32 pdo = snk_pdos[pdo_itr]; + u8 curr_snk_pdp = 0; + + switch (pdo_type(pdo)) { + case PDO_TYPE_FIXED: + max_mv = pdo_fixed_voltage(pdo); + max_ma = pdo_fixed_current(pdo); + curr_snk_pdp = UW_TO_W(max_mv * max_ma); + break; + case PDO_TYPE_BATT: + curr_snk_pdp = UW_TO_W(pdo_max_power(pdo)); + break; + case PDO_TYPE_VAR: + max_mv = pdo_max_voltage(pdo); + max_ma = pdo_max_current(pdo); + curr_snk_pdp = UW_TO_W(max_mv * max_ma); + break; + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) { + max_mv = pdo_pps_apdo_max_voltage(pdo); + max_ma = pdo_pps_apdo_max_current(pdo); + curr_snk_pdp = UW_TO_W(max_mv * max_ma); + caps->modes |= SINK_MODE_PPS; + } else if (pdo_apdo_type(pdo) == + APDO_TYPE_SPR_AVS) { + avs_tier1_pdp = UW_TO_W(SPR_AVS_TIER1_MAX_VOLT_MV + * pdo_spr_avs_apdo_9v_to_15v_max_current_ma(pdo)); + avs_tier2_pdp = UW_TO_W(SPR_AVS_TIER2_MAX_VOLT_MV + * pdo_spr_avs_apdo_15v_to_20v_max_current_ma(pdo)); + curr_snk_pdp = max(avs_tier1_pdp, avs_tier2_pdp); + caps->modes |= SINK_MODE_AVS; + } + break; + default: + tcpm_log(port, "Invalid source PDO type, ignoring"); + continue; + } + + caps->spr_max_pdp = max(caps->spr_max_pdp, + curr_snk_pdp); + } + } +} + +static void tcpm_fw_get_sink_caps_ext(struct tcpm_port *port, + struct fwnode_handle *fwnode) +{ + struct sink_caps_ext_data *caps = &port->sink_caps_ext; + int ret; + u32 val; + + /* + * Load step represents the change in current per usec that a given + * source can tolerate while maintaining Vbus within the vSrcValid + * range. For a sink this represents the "preferred" load-step value. It + * can only have 2 values (150 mA/usec or 500 mA/usec) with 150 mA/usec + * being the default. + */ + ret = fwnode_property_read_u32(fwnode, "sink-load-step", &val); + if (!ret) + caps->load_step = val == 500 ? 1 : 0; + + fwnode_property_read_u16(fwnode, "sink-load-characteristics", + &caps->load_char); + fwnode_property_read_u8(fwnode, "sink-compliance", &caps->compliance); + caps->modes = SINK_MODE_VBUS; + + /* + * As per "6.5.13.14" SPR Sink Operational PDP definition, for battery + * powered devices, this value will correspond to the PDP of the + * charging adapter either shipped or recommended for use with it. For + * batteryless sink devices SPR Operational PDP indicates the power + * required to operate all the device's functional modes. Hence, this + * value may be considered equal to port's operating_snk_mw. As + * operating_sink_mw can change as per the pd set used thus, OP PDP + * is determined when populating Sink Caps Extended Data Block. + */ + if (port->self_powered) { + fwnode_property_read_u32(fwnode, "charging-adapter-pdp-milliwatt", + &val); + caps->spr_op_pdp = (u8)(val / 1000); + caps->modes |= SINK_MODE_BATT; + } + + tcpm_parse_snk_pdos(port); + tcpm_log(port, + "load-step:%#x load-char:%#x compl:%#x op-pdp:%#x max-pdp:%#x", + caps->load_step, caps->load_char, caps->compliance, + caps->spr_op_pdp, caps->spr_max_pdp); +} + static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode) { struct fwnode_handle *capabilities, *caps = NULL; @@ -7455,6 +7701,9 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode } } + if (port->port_type != TYPEC_PORT_SRC) + tcpm_fw_get_sink_caps_ext(port, fwnode); + put_caps: if (caps != fwnode) fwnode_handle_put(caps); @@ -7497,6 +7746,8 @@ static int tcpm_fw_get_snk_vdos(struct tcpm_port *port, struct fwnode_handle *fw return ret; } + tcpm_fw_get_pd_ident(port); + return 0; } diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 6ccd1b2af993..5a98983195cb 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -34,7 +34,8 @@ enum pd_ctrl_msg_type { PD_CTRL_FR_SWAP = 19, PD_CTRL_GET_PPS_STATUS = 20, PD_CTRL_GET_COUNTRY_CODES = 21, - /* 22-23 Reserved */ + PD_CTRL_GET_SINK_CAP_EXT = 22, + /* 23 Reserved */ PD_CTRL_GET_REVISION = 24, /* 25-31 Reserved */ }; @@ -72,7 +73,8 @@ enum pd_ext_msg_type { PD_EXT_PPS_STATUS = 12, PD_EXT_COUNTRY_INFO = 13, PD_EXT_COUNTRY_CODES = 14, - /* 15-31 Reserved */ + PD_EXT_SINK_CAP_EXT = 15, + /* 16-31 Reserved */ }; #define PD_REV10 0x0 @@ -205,6 +207,72 @@ struct pd_message { }; } __packed; +/* + * count_chunked_data_objs - Helper to calculate number of Data Objects on a 4 + * byte boundary. + * @size: Size of data block for extended message. Should *not* include extended + * header size. + */ +static inline u8 count_chunked_data_objs(u32 size) +{ + size += offsetof(struct pd_chunked_ext_message_data, data); + return ((size / 4) + (size % 4 ? 1 : 0)); +} + +/* Sink Caps Extended Data Block Version */ +#define SKEDB_VER_1_0 1 + +/* Sink Caps Extended Sink Modes */ +#define SINK_MODE_PPS BIT(0) +#define SINK_MODE_VBUS BIT(1) +#define SINK_MODE_AC_SUPPLY BIT(2) +#define SINK_MODE_BATT BIT(3) +#define SINK_MODE_BATT_UL BIT(4) /* Unlimited battery power supply */ +#define SINK_MODE_AVS BIT(5) + +/** + * struct sink_caps_ext_msg - Sink extended capability PD message + * @vid: Vendor ID + * @pid: Product ID + * @xid: Value assigned by USB-IF for product + * @fw: Firmware version + * @hw: Hardware version + * @skedb_ver: Sink Caps Extended Data Block (SKEDB) Version + * @load_step: Indicates the load step slew rate. + * @load_char: Sink overload characteristics + * @compliance: Types of sources the sink has been tested & certified on + * @touch_temp: Indicates the IEC standard to which the touch temperature + * conforms to (if applicable). + * @batt_info: Indicates number batteries and hot swappable ports + * @modes: Charging caps & power sources supported + * @spr_min_pdp: Sink Minimum PDP for SPR mode + * @spr_op_pdp: Sink Operational PDP for SPR mode + * @spr_max_pdp: Sink Maximum PDP for SPR mode + * @epr_min_pdp: Sink Minimum PDP for EPR mode + * @epr_op_pdp: Sink Operational PDP for EPR mode + * @epr_max_pdp: Sink Maximum PDP for EPR mode + */ +struct sink_caps_ext_msg { + __le16 vid; + __le16 pid; + __le32 xid; + u8 fw; + u8 hw; + u8 skedb_ver; + u8 load_step; + __le16 load_char; + u8 compliance; + u8 touch_temp; + u8 batt_info; + u8 modes; + u8 spr_min_pdp; + u8 spr_op_pdp; + u8 spr_max_pdp; + u8 epr_min_pdp; + u8 epr_op_pdp; + u8 epr_max_pdp; +} __packed; + /* PDO: Power Data Object */ #define PDO_MAX_OBJECTS 7 @@ -329,6 +397,11 @@ enum pd_apdo_type { #define PDO_SPR_AVS_APDO_9V_TO_15V_MAX_CURR GENMASK(19, 10) /* 10mA unit */ #define PDO_SPR_AVS_APDO_15V_TO_20V_MAX_CURR GENMASK(9, 0) /* 10mA unit */ +/* SPR AVS has two different current ranges 9V - 15V, 15V - 20V */ +#define SPR_AVS_TIER1_MIN_VOLT_MV 9000 +#define SPR_AVS_TIER1_MAX_VOLT_MV 15000 +#define SPR_AVS_TIER2_MAX_VOLT_MV 20000 + static inline enum pd_pdo_type pdo_type(u32 pdo) { return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK; @@ -339,6 +412,11 @@ static inline unsigned int pdo_fixed_voltage(u32 pdo) return ((pdo >> PDO_FIXED_VOLT_SHIFT) & PDO_VOLT_MASK) * 50; } +static inline unsigned int pdo_fixed_current(u32 pdo) +{ + return ((pdo >> PDO_FIXED_CURR_SHIFT) & PDO_CURR_MASK) * 10; +} + static inline unsigned int pdo_min_voltage(u32 pdo) { return ((pdo >> PDO_VAR_MIN_VOLT_SHIFT) & PDO_VOLT_MASK) * 50; From 322a81d35ecdf9997c3bbf676e3547d75f38935a Mon Sep 17 00:00:00 2001 From: Rodrigo Gobbi Date: Tue, 24 Feb 2026 22:23:20 -0300 Subject: [PATCH 053/176] dt-bindings: usb: maxim,max3421: convert to DT schema Convert legacy maxim,max3421.txt to proper format. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Rodrigo Gobbi Link: https://patch.msgid.link/20260225014751.9121-1-rodrigo.gobbi.7@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/maxim,max3421.txt | 23 ------- .../bindings/usb/maxim,max3421.yaml | 67 +++++++++++++++++++ 2 files changed, 67 insertions(+), 23 deletions(-) delete mode 100644 Documentation/devicetree/bindings/usb/maxim,max3421.txt create mode 100644 Documentation/devicetree/bindings/usb/maxim,max3421.yaml diff --git a/Documentation/devicetree/bindings/usb/maxim,max3421.txt b/Documentation/devicetree/bindings/usb/maxim,max3421.txt deleted file mode 100644 index 90495b1aeec2..000000000000 --- a/Documentation/devicetree/bindings/usb/maxim,max3421.txt +++ /dev/null @@ -1,23 +0,0 @@ -Maxim Integrated SPI-based USB 2.0 host controller MAX3421E - -Required properties: - - compatible: Should be "maxim,max3421" - - spi-max-frequency: maximum frequency for this device must not exceed 26 MHz. - - reg: chip select number to which this device is connected. - - maxim,vbus-en-pin: - GPOUTx is the number (1-8) of the GPOUT pin of MAX3421E to drive Vbus. - ACTIVE_LEVEL is 0 or 1. - - interrupts: the interrupt line description for the interrupt controller. - The driver configures MAX3421E for active low level triggered interrupts, - configure your interrupt line accordingly. - -Example: - - usb@0 { - compatible = "maxim,max3421"; - reg = <0>; - maxim,vbus-en-pin = <3 1>; - spi-max-frequency = <26000000>; - interrupt-parent = <&PIC>; - interrupts = <42>; - }; diff --git a/Documentation/devicetree/bindings/usb/maxim,max3421.yaml b/Documentation/devicetree/bindings/usb/maxim,max3421.yaml new file mode 100644 index 000000000000..4639be7ab059 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/maxim,max3421.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/maxim,max3421.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MAXIM MAX3421e USB Peripheral/Host Controller + +maintainers: + - David Mosberger + +description: | + The controller provides USB2.0 compliant with Full Speed or Low Speed when in + the host mode. At peripheral, it operates at Full Speed. At both cases, it + uses a SPI interface. + Datasheet at: + https://www.analog.com/media/en/technical-documentation/data-sheets/max3421e.pdf + +properties: + compatible: + const: maxim,max3421 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + spi-max-frequency: + maximum: 26000000 + + maxim,vbus-en-pin: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + One of eight GPOUT pins to control external VBUS power and the polarity + of the active level. It's an array of GPIO number and the active level of it. + minItems: 2 + maxItems: 2 + +required: + - compatible + - reg + - interrupts + - maxim,vbus-en-pin + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + + usb@0 { + compatible = "maxim,max3421"; + reg = <0>; + maxim,vbus-en-pin = <3 1>; + spi-max-frequency = <26000000>; + interrupt-parent = <&gpio>; + interrupts = <42>; + }; + }; From 5a6a33b56402167ef019fd2520728e8c25614f74 Mon Sep 17 00:00:00 2001 From: Ai Chao Date: Tue, 10 Mar 2026 17:44:29 +0800 Subject: [PATCH 054/176] USB: serial: ti_usb_3410_5052: use strscpy() instead of strcpy() Use a safer function strscpy() instead of strcpy() for copying to arrays. Only idiomatic code replacement, and no functional changes. Signed-off-by: Ai Chao Signed-off-by: Johan Hovold --- drivers/usb/serial/ti_usb_3410_5052.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index 658b54d8fcef..b3591d6d7645 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -1600,29 +1600,29 @@ static int ti_download_firmware(struct ti_device *tdev) if (le16_to_cpu(dev->descriptor.idVendor) == MTS_VENDOR_ID) { switch (le16_to_cpu(dev->descriptor.idProduct)) { case MTS_CDMA_PRODUCT_ID: - strcpy(buf, "mts_cdma.fw"); + strscpy(buf, "mts_cdma.fw"); break; case MTS_GSM_PRODUCT_ID: - strcpy(buf, "mts_gsm.fw"); + strscpy(buf, "mts_gsm.fw"); break; case MTS_EDGE_PRODUCT_ID: - strcpy(buf, "mts_edge.fw"); + strscpy(buf, "mts_edge.fw"); break; case MTS_MT9234MU_PRODUCT_ID: - strcpy(buf, "mts_mt9234mu.fw"); + strscpy(buf, "mts_mt9234mu.fw"); break; case MTS_MT9234ZBA_PRODUCT_ID: - strcpy(buf, "mts_mt9234zba.fw"); + strscpy(buf, "mts_mt9234zba.fw"); break; case MTS_MT9234ZBAOLD_PRODUCT_ID: - strcpy(buf, "mts_mt9234zba.fw"); + strscpy(buf, "mts_mt9234zba.fw"); break; } } if (buf[0] == '\0') { if (tdev->td_is_3410) - strcpy(buf, "ti_3410.fw"); + strscpy(buf, "ti_3410.fw"); else - strcpy(buf, "ti_5052.fw"); + strscpy(buf, "ti_5052.fw"); } status = request_firmware(&fw_p, buf, &dev->dev); } From 500e54d449f60e9692e2622ad2ba4f1e79590e87 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 13 Mar 2026 14:41:37 -0700 Subject: [PATCH 055/176] thunderbolt: dma_port: kmalloc_array + kzalloc to flex Use a single allocation with a flexible array member. Simplifies allocation and freeing. Signed-off-by: Rosen Penev Signed-off-by: Mika Westerberg --- drivers/thunderbolt/dma_port.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/thunderbolt/dma_port.c b/drivers/thunderbolt/dma_port.c index 334fefe21255..c7c2942fa7be 100644 --- a/drivers/thunderbolt/dma_port.c +++ b/drivers/thunderbolt/dma_port.c @@ -55,7 +55,7 @@ struct tb_dma_port { struct tb_switch *sw; u8 port; u32 base; - u8 *buf; + u8 buf[]; }; /* @@ -209,16 +209,10 @@ struct tb_dma_port *dma_port_alloc(struct tb_switch *sw) if (port < 0) return NULL; - dma = kzalloc_obj(*dma); + dma = kzalloc_flex(*dma, buf, MAIL_DATA_DWORDS); if (!dma) return NULL; - dma->buf = kmalloc_array(MAIL_DATA_DWORDS, sizeof(u32), GFP_KERNEL); - if (!dma->buf) { - kfree(dma); - return NULL; - } - dma->sw = sw; dma->port = port; dma->base = DMA_PORT_CAP; @@ -232,10 +226,7 @@ struct tb_dma_port *dma_port_alloc(struct tb_switch *sw) */ void dma_port_free(struct tb_dma_port *dma) { - if (dma) { - kfree(dma->buf); - kfree(dma); - } + kfree(dma); } static int dma_port_wait_for_completion(struct tb_dma_port *dma, From 0888c3371ad229ce76675754ce43ae04bb4945e1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 5 Mar 2026 14:38:50 +0100 Subject: [PATCH 056/176] USB: apple-mfi-fastcharge: drop redundant device reference Driver core holds a reference to the USB device while it is bound to a driver and there is no need to take additional references unless the structure is needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Reviewed-by: Bastien Nocera Link: https://patch.msgid.link/20260305133851.2952-2-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/apple-mfi-fastcharge.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/misc/apple-mfi-fastcharge.c b/drivers/usb/misc/apple-mfi-fastcharge.c index 339f6cd2e9b2..af266e19f2fd 100644 --- a/drivers/usb/misc/apple-mfi-fastcharge.c +++ b/drivers/usb/misc/apple-mfi-fastcharge.c @@ -210,7 +210,7 @@ static int mfi_fc_probe(struct usb_device *udev) goto err_free_name; } - mfi->udev = usb_get_dev(udev); + mfi->udev = udev; dev_set_drvdata(&udev->dev, mfi); return 0; @@ -231,7 +231,6 @@ static void mfi_fc_disconnect(struct usb_device *udev) power_supply_unregister(mfi->battery); kfree(mfi->battery_desc.name); dev_set_drvdata(&udev->dev, NULL); - usb_put_dev(mfi->udev); kfree(mfi); } From a1678c4e57e0004a94363127309fcc8b388f11ba Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 5 Mar 2026 14:38:51 +0100 Subject: [PATCH 057/176] USB: usbip: drop redundant device reference Driver core holds a reference to the USB device while it is bound to a driver and there is no need to take additional references unless the structure is needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260305133851.2952-3-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/stub_dev.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/usbip/stub_dev.c b/drivers/usb/usbip/stub_dev.c index 34990b7e2d18..abfa11d6bde7 100644 --- a/drivers/usb/usbip/stub_dev.c +++ b/drivers/usb/usbip/stub_dev.c @@ -267,7 +267,7 @@ static struct stub_device *stub_device_alloc(struct usb_device *udev) if (!sdev) return NULL; - sdev->udev = usb_get_dev(udev); + sdev->udev = udev; /* * devid is defined with devnum when this driver is first allocated. @@ -409,7 +409,6 @@ static int stub_probe(struct usb_device *udev) put_busid_priv(busid_priv); sdev_free: - usb_put_dev(udev); stub_device_free(sdev); return rc; @@ -488,8 +487,6 @@ static void stub_disconnect(struct usb_device *udev) /* shutdown the current connection */ shutdown_busid(busid_priv); - usb_put_dev(sdev->udev); - /* we already have busid_priv, just lock busid_lock */ spin_lock(&busid_priv->busid_lock); /* free sdev */ From 341434a444024f70f1f7c2355bb1ae8dc5fb15fe Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 11 Mar 2026 16:20:43 -0700 Subject: [PATCH 058/176] usb: renesas_usbhs: use kzalloc_flex Removes one allocation and one free by using a flexible array member. Also added __counted_by for extra runtime analysis. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260311232043.18025-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/mod_gadget.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c index 1539e8e6901d..0c7fe109d5c7 100644 --- a/drivers/usb/renesas_usbhs/mod_gadget.c +++ b/drivers/usb/renesas_usbhs/mod_gadget.c @@ -40,7 +40,6 @@ struct usbhsg_gpriv { struct usb_gadget gadget; struct usbhs_mod mod; - struct usbhsg_uep *uep; int uep_size; struct usb_gadget_driver *driver; @@ -53,6 +52,7 @@ struct usbhsg_gpriv { #define USBHSG_STATUS_WEDGE (1 << 2) #define USBHSG_STATUS_SELF_POWERED (1 << 3) #define USBHSG_STATUS_SOFT_CONNECT (1 << 4) + struct usbhsg_uep uep[] __counted_by(uep_size); }; struct usbhsg_recip_handle { @@ -1084,15 +1084,11 @@ int usbhs_mod_gadget_probe(struct usbhs_priv *priv) int i; int ret; - gpriv = kzalloc_obj(struct usbhsg_gpriv); + gpriv = kzalloc_flex(*gpriv, uep, pipe_size); if (!gpriv) return -ENOMEM; - uep = kzalloc_objs(struct usbhsg_uep, pipe_size); - if (!uep) { - ret = -ENOMEM; - goto usbhs_mod_gadget_probe_err_gpriv; - } + gpriv->uep_size = pipe_size; gpriv->transceiver = devm_usb_get_phy(dev, USB_PHY_TYPE_UNDEFINED); dev_info(dev, "%stransceiver found\n", @@ -1115,8 +1111,6 @@ int usbhs_mod_gadget_probe(struct usbhs_priv *priv) gpriv->mod.name = "gadget"; gpriv->mod.start = usbhsg_start; gpriv->mod.stop = usbhsg_stop; - gpriv->uep = uep; - gpriv->uep_size = pipe_size; usbhsg_status_init(gpriv); /* @@ -1175,9 +1169,6 @@ int usbhs_mod_gadget_probe(struct usbhs_priv *priv) return 0; err_add_udc: - kfree(gpriv->uep); - -usbhs_mod_gadget_probe_err_gpriv: kfree(gpriv); return ret; @@ -1189,6 +1180,5 @@ void usbhs_mod_gadget_remove(struct usbhs_priv *priv) usb_del_gadget_udc(&gpriv->gadget); - kfree(gpriv->uep); kfree(gpriv); } From 03cd4fd620bdfc33ea25f2f7504f0d49c7dd1f9e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Thu, 12 Mar 2026 17:34:55 -0700 Subject: [PATCH 059/176] usb: fhci: use kzalloc_flex for priv struct Convert kzalloc_obj(s) to kzalloc_flex to save an allocation. Add __counted_by to get extra runtime analysis. Move counting variable assignment immediately after allocation as required by __counted_by. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260313003456.124270-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/fhci-hcd.c | 15 +++------------ drivers/usb/host/fhci.h | 3 ++- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c index 271bcbe9b326..71e785f445a3 100644 --- a/drivers/usb/host/fhci-hcd.c +++ b/drivers/usb/host/fhci-hcd.c @@ -426,16 +426,11 @@ static int fhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, } /* allocate the private part of the URB */ - urb_priv = kzalloc_obj(*urb_priv, mem_flags); + urb_priv = kzalloc_flex(*urb_priv, tds, size, mem_flags); if (!urb_priv) return -ENOMEM; - /* allocate the private part of the URB */ - urb_priv->tds = kzalloc_objs(*urb_priv->tds, size, mem_flags); - if (!urb_priv->tds) { - kfree(urb_priv); - return -ENOMEM; - } + urb_priv->num_of_tds = size; spin_lock_irqsave(&fhci->lock, flags); @@ -444,8 +439,6 @@ static int fhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, goto err; /* fill the private part of the URB */ - urb_priv->num_of_tds = size; - urb->status = -EINPROGRESS; urb->actual_length = 0; urb->error_count = 0; @@ -453,10 +446,8 @@ static int fhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, fhci_queue_urb(fhci, urb); err: - if (ret) { - kfree(urb_priv->tds); + if (ret) kfree(urb_priv); - } spin_unlock_irqrestore(&fhci->lock, flags); return ret; } diff --git a/drivers/usb/host/fhci.h b/drivers/usb/host/fhci.h index 1f57b0989485..e221b28e8608 100644 --- a/drivers/usb/host/fhci.h +++ b/drivers/usb/host/fhci.h @@ -387,9 +387,10 @@ struct urb_priv { int tds_cnt; int state; - struct td **tds; struct ed *ed; struct timer_list time_out; + + struct td *tds[] __counted_by(num_of_tds); }; struct endpoint { From 916aeaffad2526b9723387b6bd449ec76dcc8d44 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 12 Mar 2026 13:34:27 +0100 Subject: [PATCH 060/176] USB: uas: give the error handler the correct name A UAS device can in principle contain multiple busses. A reset on the USB level will reset them all. We cannot reset a single bus. In practical terms this does not matter, as only one method of reset is implemented, but we should not lie. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260312123435.2015029-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/uas.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index 0a9902d2b118..265162981269 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c @@ -772,7 +772,7 @@ static int uas_eh_abort_handler(struct scsi_cmnd *cmnd) return FAILED; } -static int uas_eh_device_reset_handler(struct scsi_cmnd *cmnd) +static int uas_eh_host_reset_handler(struct scsi_cmnd *cmnd) { struct scsi_device *sdev = cmnd->device; struct uas_dev_info *devinfo = sdev->hostdata; @@ -918,7 +918,7 @@ static const struct scsi_host_template uas_host_template = { .sdev_init = uas_sdev_init, .sdev_configure = uas_sdev_configure, .eh_abort_handler = uas_eh_abort_handler, - .eh_device_reset_handler = uas_eh_device_reset_handler, + .eh_host_reset_handler = uas_eh_host_reset_handler, .this_id = -1, .skip_settle_delay = 1, /* From 56dd29088c9d9510c48a8ebad2465248fde36551 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 12 Mar 2026 10:45:27 +0100 Subject: [PATCH 061/176] usb: iowarrior: remove inherent race with minor number The driver saves the minor number it gets upon registration in its descriptor for debugging purposes. However, there is inevitably a window between registration and saving the correct minor in a descriptor. During this window the debugging output will be wrong. As wrong debug output is worse than no debug output, just remove it. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260312094619.1590556-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/iowarrior.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 18670dfed2e7..5b31e5669d53 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -74,7 +74,6 @@ struct iowarrior { struct mutex mutex; /* locks this structure */ struct usb_device *udev; /* save off the usb device pointer */ struct usb_interface *interface; /* the interface for this device */ - unsigned char minor; /* the starting minor number for this device */ struct usb_endpoint_descriptor *int_out_endpoint; /* endpoint for reading (needed for IOW56 only) */ struct usb_endpoint_descriptor *int_in_endpoint; /* endpoint for reading */ struct urb *int_in_urb; /* the urb for reading data */ @@ -246,7 +245,6 @@ static void iowarrior_write_callback(struct urb *urb) */ static inline void iowarrior_delete(struct iowarrior *dev) { - dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor); kfree(dev->int_in_buffer); usb_free_urb(dev->int_in_urb); kfree(dev->read_queue); @@ -297,9 +295,6 @@ static ssize_t iowarrior_read(struct file *file, char __user *buffer, goto exit; } - dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n", - dev->minor, count); - /* read count must be packet size (+ time stamp) */ if ((count != dev->report_size) && (count != (dev->report_size + 1))) { @@ -379,8 +374,6 @@ static ssize_t iowarrior_write(struct file *file, retval = -ENODEV; goto exit; } - dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n", - dev->minor, count); /* if count is 0 we're already done */ if (count == 0) { retval = 0; @@ -523,9 +516,6 @@ static long iowarrior_ioctl(struct file *file, unsigned int cmd, goto error_out; } - dev_dbg(&dev->interface->dev, "minor %d, cmd 0x%.4x, arg %ld\n", - dev->minor, cmd, arg); - retval = 0; switch (cmd) { case IOW_WRITE: @@ -671,8 +661,6 @@ static int iowarrior_release(struct inode *inode, struct file *file) if (!dev) return -ENODEV; - dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor); - /* lock our device */ mutex_lock(&dev->mutex); @@ -775,6 +763,7 @@ static int iowarrior_probe(struct usb_interface *interface, struct usb_host_interface *iface_desc; int retval = -ENOMEM; int res; + int minor; /* allocate memory for our device state and initialize it */ dev = kzalloc_obj(struct iowarrior); @@ -890,12 +879,12 @@ static int iowarrior_probe(struct usb_interface *interface, goto error; } - dev->minor = interface->minor; + minor = interface->minor; /* let the user know what node this device is now attached to */ dev_info(&interface->dev, "IOWarrior product=0x%x, serial=%s interface=%d " "now attached to iowarrior%d\n", dev->product_id, dev->chip_serial, - iface_desc->desc.bInterfaceNumber, dev->minor - IOWARRIOR_MINOR_BASE); + iface_desc->desc.bInterfaceNumber, minor - IOWARRIOR_MINOR_BASE); return retval; error: From bcbdfc7fadf8018552fd55e57114a77637395684 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 12 Mar 2026 10:45:28 +0100 Subject: [PATCH 062/176] iowarrior: use interruptible lock in iowarrior_write() The function itself, if it has to wait to perform IO, use interruptible sleep. Hence the sleep needed to avoid the write code path racing with itself should also use interruptible sleep. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260312094619.1590556-2-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/iowarrior.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 5b31e5669d53..0b377204374f 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -362,13 +362,16 @@ static ssize_t iowarrior_write(struct file *file, size_t count, loff_t *ppos) { struct iowarrior *dev; - int retval = 0; + int retval; char *buf = NULL; /* for IOW24 and IOW56 we need a buffer */ struct urb *int_out_urb = NULL; dev = file->private_data; - mutex_lock(&dev->mutex); + retval = mutex_lock_interruptible(&dev->mutex); + if (retval < 0) + return -EINTR; + /* verify that the device wasn't unplugged */ if (!dev->present) { retval = -ENODEV; From 849fbecdf7e1d4b91a31e6aa72d15e8938bddc5c Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 12 Mar 2026 10:53:20 +0100 Subject: [PATCH 063/176] iowarrior: use normal memory in write path There is just no point in using coherent memory. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260312095328.1594015-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/iowarrior.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 0b377204374f..54f1bb0f7123 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -233,8 +233,7 @@ static void iowarrior_write_callback(struct urb *urb) "nonzero write bulk status received: %d\n", status); } /* free up our allocated buffer */ - usb_free_coherent(urb->dev, urb->transfer_buffer_length, - urb->transfer_buffer, urb->transfer_dma); + kfree(urb->transfer_buffer); /* tell a waiting writer the interrupt-out-pipe is available again */ atomic_dec(&dev->write_busy); wake_up_interruptible(&dev->write_wait); @@ -439,8 +438,7 @@ static ssize_t iowarrior_write(struct file *file, retval = -ENOMEM; goto error_no_urb; } - buf = usb_alloc_coherent(dev->udev, dev->report_size, - GFP_KERNEL, &int_out_urb->transfer_dma); + buf = kmalloc(dev->report_size, GFP_KERNEL); if (!buf) { retval = -ENOMEM; dev_dbg(&dev->interface->dev, @@ -453,7 +451,6 @@ static ssize_t iowarrior_write(struct file *file, buf, dev->report_size, iowarrior_write_callback, dev, dev->int_out_endpoint->bInterval); - int_out_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; if (copy_from_user(buf, user_buffer, count)) { retval = -EFAULT; goto error; @@ -479,8 +476,7 @@ static ssize_t iowarrior_write(struct file *file, goto exit; } error: - usb_free_coherent(dev->udev, dev->report_size, buf, - int_out_urb->transfer_dma); + kfree(buf); error_no_buffer: usb_free_urb(int_out_urb); error_no_urb: From 0c8ee850572b6850a78ef4c83af2acd4909a4519 Mon Sep 17 00:00:00 2001 From: Krishna Kurapati Date: Thu, 12 Mar 2026 15:44:31 +0530 Subject: [PATCH 064/176] usb: typec: ucsi: Add UCSI_USB4_IMPLIES_USB quirk for X1E80100 On X1E80100, when we connect a USB4 capable dock, the PARTNER_FLAGS indicate USB4_GEN3 being set whilst keeping the PARTNER_FLAGS_USB cleared. Due to this, during ucsi_partner_change call, the usb role is marked as ROLE_NONE and passed to DWC3 controller the same way. Fix this by adding UCSI_USB4_IMPLIES_USB quirk and check for it to decide and pass on proper ROLE information to DWC3 layer. Signed-off-by: Krishna Kurapati Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260312101431.2375709-1-krishna.kurapati@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 6 ++++-- drivers/usb/typec/ucsi/ucsi.h | 3 +++ drivers/usb/typec/ucsi/ucsi_glink.c | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 4efbe41d7714..fe1fb8a68a1d 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1184,8 +1184,10 @@ static void ucsi_partner_change(struct ucsi_connector *con) } } - /* Only notify USB controller if partner supports USB data */ - if (!(UCSI_CONSTAT(con, PARTNER_FLAG_USB))) + if ((!UCSI_CONSTAT(con, PARTNER_FLAG_USB)) && + ((con->ucsi->quirks & UCSI_USB4_IMPLIES_USB) && + (!(UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN3) || + UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN4))))) u_role = USB_ROLE_NONE; ret = usb_role_switch_set_role(con->usb_role_sw, u_role); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 43a0d01ade8f..cff9ddc2ae21 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -497,6 +497,9 @@ struct ucsi { unsigned long quirks; #define UCSI_NO_PARTNER_PDOS BIT(0) /* Don't read partner's PDOs */ #define UCSI_DELAY_DEVICE_PDOS BIT(1) /* Reading PDOs fails until the parter is in PD mode */ + +/* USB4 connection can imply that USB communcation is supported */ +#define UCSI_USB4_IMPLIES_USB BIT(2) }; #define UCSI_MAX_DATA_LENGTH(u) (((u)->version < UCSI_VERSION_2_0) ? 0x10 : 0xff) diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index c7878ea0d37a..12e07b9fe622 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -371,6 +371,7 @@ static void pmic_glink_ucsi_destroy(void *data) static unsigned long quirk_sc8180x = UCSI_NO_PARTNER_PDOS; static unsigned long quirk_sc8280xp = UCSI_NO_PARTNER_PDOS | UCSI_DELAY_DEVICE_PDOS; static unsigned long quirk_sm8450 = UCSI_DELAY_DEVICE_PDOS; +static unsigned long quirk_x1e80100 = UCSI_DELAY_DEVICE_PDOS | UCSI_USB4_IMPLIES_USB; static const struct of_device_id pmic_glink_ucsi_of_quirks[] = { { .compatible = "qcom,glymur-pmic-glink", .data = &quirk_sm8450, }, @@ -381,6 +382,7 @@ static const struct of_device_id pmic_glink_ucsi_of_quirks[] = { { .compatible = "qcom,sm8350-pmic-glink", .data = &quirk_sc8180x, }, { .compatible = "qcom,sm8450-pmic-glink", .data = &quirk_sm8450, }, { .compatible = "qcom,sm8550-pmic-glink", .data = &quirk_sm8450, }, + { .compatible = "qcom,x1e80100-pmic-glink", .data = &quirk_x1e80100, }, {} }; From f2e9bc030d848f3a3d7d1cec23dd0308152eb551 Mon Sep 17 00:00:00 2001 From: Zhaoyang Yu <2426767509@qq.com> Date: Thu, 12 Mar 2026 11:03:29 +0000 Subject: [PATCH 065/176] USB: pxa27x_udc: check return value of clk_enable clk_enable() may fail according to the API contract. Previously, udc_enable() ignored its return value and returned void. Modify udc_enable() to return the error code. Additionally, update all of its callers (pxa_udc_pullup, pxa_udc_vbus_session, pxa27x_udc_start, pxa_udc_probe, and pxa_udc_resume) to check this return value and handle the failure properly with necessary cleanups or rollbacks. Signed-off-by: Zhaoyang Yu <2426767509@qq.com> Link: https://patch.msgid.link/tencent_46693FE6DB434ACFB7412B16F6078AC01A06@qq.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/pxa27x_udc.c | 68 ++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index 4fac477d24c0..1abea0d48c35 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -1462,7 +1462,7 @@ static int pxa_udc_wakeup(struct usb_gadget *_gadget) return 0; } -static void udc_enable(struct pxa_udc *udc); +static int udc_enable(struct pxa_udc *udc); static void udc_disable(struct pxa_udc *udc); /** @@ -1519,14 +1519,20 @@ static int should_disable_udc(struct pxa_udc *udc) static int pxa_udc_pullup(struct usb_gadget *_gadget, int is_active) { struct pxa_udc *udc = to_gadget_udc(_gadget); + int ret; if (!udc->gpiod && !udc->udc_command) return -EOPNOTSUPP; dplus_pullup(udc, is_active); - if (should_enable_udc(udc)) - udc_enable(udc); + if (should_enable_udc(udc)) { + ret = udc_enable(udc); + if (ret) { + dplus_pullup(udc, !is_active); + return ret; + } + } if (should_disable_udc(udc)) udc_disable(udc); return 0; @@ -1545,10 +1551,16 @@ static int pxa_udc_pullup(struct usb_gadget *_gadget, int is_active) static int pxa_udc_vbus_session(struct usb_gadget *_gadget, int is_active) { struct pxa_udc *udc = to_gadget_udc(_gadget); + int ret; udc->vbus_sensed = is_active; - if (should_enable_udc(udc)) - udc_enable(udc); + if (should_enable_udc(udc)) { + ret = udc_enable(udc); + if (ret) { + udc->vbus_sensed = !is_active; + return ret; + } + } if (should_disable_udc(udc)) udc_disable(udc); @@ -1691,12 +1703,18 @@ static void udc_init_data(struct pxa_udc *dev) * Enables the udc device : enables clocks, udc interrupts, control endpoint * interrupts, sets usb as UDC client and setups endpoints. */ -static void udc_enable(struct pxa_udc *udc) +static int udc_enable(struct pxa_udc *udc) { - if (udc->enabled) - return; + int ret; - clk_enable(udc->clk); + if (udc->enabled) + return 0; + + ret = clk_enable(udc->clk); + if (ret) { + dev_err(udc->dev, "clk_enable failed: %d\n", ret); + return ret; + } udc_writel(udc, UDCICR0, 0); udc_writel(udc, UDCICR1, 0); udc_clear_mask_UDCCR(udc, UDCCR_UDE); @@ -1726,6 +1744,8 @@ static void udc_enable(struct pxa_udc *udc) pio_irq_enable(&udc->pxa_ep[0]); udc->enabled = 1; + + return 0; } /** @@ -1761,10 +1781,16 @@ static int pxa27x_udc_start(struct usb_gadget *g, } } - if (should_enable_udc(udc)) - udc_enable(udc); + if (should_enable_udc(udc)) { + retval = udc_enable(udc); + if (retval) + goto fail_enable; + } return 0; +fail_enable: + if (!IS_ERR_OR_NULL(udc->transceiver)) + otg_set_peripheral(udc->transceiver->otg, NULL); fail: udc->driver = NULL; return retval; @@ -2430,10 +2456,16 @@ static int pxa_udc_probe(struct platform_device *pdev) goto err_add_gadget; pxa_init_debugfs(udc); - if (should_enable_udc(udc)) - udc_enable(udc); + if (should_enable_udc(udc)) { + retval = udc_enable(udc); + if (retval) + goto err_enable; + } return 0; +err_enable: + usb_del_gadget_udc(&udc->gadget); + pxa_cleanup_debugfs(udc); err_add_gadget: if (!IS_ERR_OR_NULL(udc->transceiver)) usb_unregister_notifier(udc->transceiver, &pxa27x_udc_phy); @@ -2509,13 +2541,19 @@ static int pxa_udc_resume(struct platform_device *_dev) { struct pxa_udc *udc = platform_get_drvdata(_dev); struct pxa_ep *ep; + int ret; ep = &udc->pxa_ep[0]; udc_ep_writel(ep, UDCCSR, udc->udccsr0 & (UDCCSR0_FST | UDCCSR0_DME)); dplus_pullup(udc, udc->pullup_resume); - if (should_enable_udc(udc)) - udc_enable(udc); + if (should_enable_udc(udc)) { + ret = udc_enable(udc); + if (ret) { + dplus_pullup(udc, !udc->pullup_resume); + return ret; + } + } /* * We do not handle OTG yet. * From b0dd9345c3484f6d5963dee1cd4e0f3b5149947e Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 11 Mar 2026 19:20:21 +0400 Subject: [PATCH 066/176] dt-bindings: vendor-prefixes: Add Shenzhen Corechips Microelectronics Add Shenzhen Corechips Microelectronics Co., Ltd., which is a company producing chips for USB accessories Link: http://www.corechip-sz.com/enproducts.asp Signed-off-by: Alexey Charkov Acked-by: Conor Dooley Link: https://patch.msgid.link/20260311-sl6341-v1-1-0a890056f054@flipper.net Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 5e504cebbcda..db654fd97b1d 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -361,6 +361,8 @@ patternProperties: description: CORERIVER Semiconductor Co.,Ltd. "^corpro,.*": description: Chengdu Corpro Technology Co., Ltd. + "^corechips,.*": + description: Shenzhen Corechips Microelectronics Co., Ltd. "^cortina,.*": description: Cortina Systems, Inc. "^cosmic,.*": From bfcb86e58f3a58d05b95970d81b94cb011982780 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 11 Mar 2026 19:20:22 +0400 Subject: [PATCH 067/176] dt-bindings: usb: Add Corechips SL6341 USB2.0/3.0 hub controller Corechips SL6341 is a 4-port low-power USB 3.2 Gen 1x1 hub controller supporting SS, HS, FS and LS connections and integrating a 5V to 3.3V built-in LDO to enable its IO to be powered directly from the 5V USB VBUS. External 1v1 VDD supply is still required for its core power. Signed-off-by: Alexey Charkov Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260311-sl6341-v1-2-0a890056f054@flipper.net Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/corechips,sl6341.yaml | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/corechips,sl6341.yaml diff --git a/Documentation/devicetree/bindings/usb/corechips,sl6341.yaml b/Documentation/devicetree/bindings/usb/corechips,sl6341.yaml new file mode 100644 index 000000000000..82996791aaf1 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/corechips,sl6341.yaml @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/corechips,sl6341.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Corechips SL6341 USB 2.0/3.0 Hub Controller + +maintainers: + - Alexey Charkov + +allOf: + - $ref: usb-hub.yaml# + +properties: + compatible: + enum: + - usb3431,6241 + - usb3431,6341 + + reg: true + + peer-hub: true + + reset-gpios: + description: GPIO controlling the RSTN pin. + + vdd1v1-supply: + description: + The regulator that provides 1.1V core power to the hub. + + vdd3v3-supply: + description: + The regulator that provides 3.3V IO power to the hub. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + patternProperties: + '^port@': + $ref: /schemas/graph.yaml#/properties/port + + properties: + reg: + minimum: 1 + maximum: 4 + +required: + - compatible + - reg + - vdd1v1-supply + +unevaluatedProperties: false + +examples: + - | + #include + usb { + #address-cells = <1>; + #size-cells = <0>; + + /* 2.0 hub */ + hub_2_0: hub@1 { + compatible = "usb3431,6241"; + reg = <1>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio0 20 GPIO_ACTIVE_LOW>; + vdd1v1-supply = <&vdd1v1_hub>; + }; + + /* 3.0 hub */ + hub_3_0: hub@2 { + compatible = "usb3431,6341"; + reg = <2>; + peer-hub = <&hub_2_0>; + reset-gpios = <&gpio0 20 GPIO_ACTIVE_LOW>; + vdd1v1-supply = <&vdd1v1_hub>; + }; + }; From 0b9570c4ba5cbd2c0f9a282f649e4bcfce0d52af Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 11 Mar 2026 19:20:23 +0400 Subject: [PATCH 068/176] usb: misc: onboard_usb_dev: Add Corechips SL6341 USB 2.0/3.0 hub Add the ID entries and platform data for the Corechips SL6341 onboard USB 2.0/3.0 hub controller, which requires a reset pin and a power supply for proper operation. Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260311-sl6341-v1-3-0a890056f054@flipper.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_dev.c | 3 +++ drivers/usb/misc/onboard_usb_dev.h | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index ba37eb99efba..6dd73f23e9be 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -565,6 +565,7 @@ static struct platform_driver onboard_dev_driver = { /************************** USB driver **************************/ #define VENDOR_ID_BISON 0x5986 +#define VENDOR_ID_CORECHIPS 0x3431 #define VENDOR_ID_CYPRESS 0x04b4 #define VENDOR_ID_GENESYS 0x05e3 #define VENDOR_ID_MICROCHIP 0x0424 @@ -649,6 +650,8 @@ static void onboard_dev_usbdev_disconnect(struct usb_device *udev) static const struct usb_device_id onboard_dev_id_table[] = { { USB_DEVICE(VENDOR_ID_BISON, 0x1198) }, /* Bison Electronics Inc. Integrated Camera */ + { USB_DEVICE(VENDOR_ID_CORECHIPS, 0x6241) }, /* SL6341 2.0 HUB */ + { USB_DEVICE(VENDOR_ID_CORECHIPS, 0x6341) }, /* SL6341 3.0 HUB */ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6500) }, /* CYUSB330x 3.0 HUB */ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6502) }, /* CYUSB330x 2.0 HUB */ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6503) }, /* CYUSB33{0,1}x 2.0 HUB, Vendor Mode */ diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h index 35d15b034664..88f297fd796a 100644 --- a/drivers/usb/misc/onboard_usb_dev.h +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -80,6 +80,13 @@ static const struct onboard_dev_pdata bison_intcamera_data = { .is_hub = false, }; +static const struct onboard_dev_pdata corechips_sl6341_data = { + .reset_us = 10000, + .num_supplies = 2, + .supply_names = { "vdd1v1", "vdd3v3" }, + .is_hub = true, +}; + static const struct onboard_dev_pdata cypress_hx3_data = { .reset_us = 10000, .num_supplies = 2, @@ -165,6 +172,8 @@ static const struct of_device_id onboard_dev_match[] = { { .compatible = "usb2109,817", .data = &vialab_vl817_data, }, { .compatible = "usb2109,2817", .data = &vialab_vl817_data, }, { .compatible = "usb20b1,0013", .data = &xmos_xvf3500_data, }, + { .compatible = "usb3431,6241", .data = &corechips_sl6341_data, }, + { .compatible = "usb3431,6341", .data = &corechips_sl6341_data, }, { .compatible = "usb5986,1198", .data = &bison_intcamera_data, }, {} }; From 1f50332c60c2f63118b6c8b41a61d0e43d707743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20Cs=C3=B3k=C3=A1s?= Date: Sun, 15 Mar 2026 12:24:44 +0100 Subject: [PATCH 069/176] USB: core: Use krealloc() in usb_cache_string() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of "shrinking" the allocation by kmalloc()ing a new, smaller buffer, utilize krealloc() to shrink the existing allocation. This saves a memcpy(), as well as eliminates the temporary `smallbuf` allocation, which guards against allocation failure under extreme memory pressure. Signed-off-by: Bence Csókás Link: https://patch.msgid.link/20260315-usb-krealloc-v2-1-32f83e090409@sch.bme.hu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 2ab120ce2fa8..75e2bfd744a9 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1063,7 +1063,7 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) } EXPORT_SYMBOL_GPL(usb_string); -/* one UTF-8-encoded 16-bit character has at most three bytes */ +/* one 16-bit character, when UTF-8-encoded, has at most three bytes */ #define MAX_USB_STRING_SIZE (127 * 3 + 1) /** @@ -1084,16 +1084,18 @@ char *usb_cache_string(struct usb_device *udev, int index) return NULL; buf = kmalloc(MAX_USB_STRING_SIZE, GFP_NOIO); - if (buf) { - len = usb_string(udev, index, buf, MAX_USB_STRING_SIZE); - if (len > 0) { - smallbuf = kmalloc(++len, GFP_NOIO); - if (!smallbuf) - return buf; - memcpy(smallbuf, buf, len); - } + if (!buf) + return NULL; + + len = usb_string(udev, index, buf, MAX_USB_STRING_SIZE); + if (len <= 0) { kfree(buf); + return NULL; } + + smallbuf = krealloc(buf, len + 1, GFP_NOIO); + if (unlikely(!smallbuf)) + return buf; return smallbuf; } EXPORT_SYMBOL_GPL(usb_cache_string); From 8020c41b39f514941d93f3322b598afdce487064 Mon Sep 17 00:00:00 2001 From: Sean Rhodes Date: Sun, 15 Mar 2026 22:34:33 +0000 Subject: [PATCH 070/176] usb: core: allow ACPI-managed hard-wired ports to power off USB core only relaxes the default PM_QOS_FLAG_NO_POWER_OFF policy when an upstream hub reports switchable port power. That misses internal ports whose power is managed by platform firmware instead of the USB hub descriptor. Allow the port-poweroff policy to be exposed for hard-wired ports with an ACPI-managed power resource. The existing runtime PM path still requires the child usage count to drop and remote wakeup to be clear before it will power the port down. This lets internal devices such as CNVi Bluetooth use the existing USB ACPI runtime power path even when the root hub reports no USB-standard port power switching. Signed-off-by: Sean Rhodes Link: https://patch.msgid.link/20260315223433.23452-1-sean@starlabs.systems Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/port.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 44e38f922bc5..b6cdea7e9521 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -21,6 +21,20 @@ static int usb_port_block_power_off; static const struct attribute_group *port_dev_group[]; +static bool usb_port_allow_power_off(struct usb_device *hdev, + struct usb_hub *hub, + struct usb_port *port_dev) +{ + if (hub_is_port_power_switchable(hub)) + return true; + + if (!IS_ENABLED(CONFIG_ACPI)) + return false; + + return port_dev->connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED && + usb_acpi_power_manageable(hdev, port_dev->portnum - 1); +} + static ssize_t early_stop_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -805,10 +819,10 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) device_enable_async_suspend(&port_dev->dev); /* - * Keep hidden the ability to enable port-poweroff if the hub - * does not support power switching. + * Keep hidden the ability to enable port-poweroff if neither the + * USB hub nor platform firmware can manage downstream port power. */ - if (!hub_is_port_power_switchable(hub)) + if (!usb_port_allow_power_off(hdev, hub, port_dev)) return 0; /* Attempt to let userspace take over the policy. */ From b84cc80610a8ce036deb987f056ce3196ead7f1e Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Mon, 16 Mar 2026 17:50:42 +0800 Subject: [PATCH 071/176] usb: port: add delay after usb_hub_set_port_power() When a port is disabled, an attached device will be disconnected. This causes a port-status-change event, which will race with hub autosuspend (if the disabled port was the only connected port on its hub), causing an immediate resume and a second autosuspend. Both of these can be avoided by adding a short delay after the call to usb_hub_set_port_power(). Below log shows what is happening: $ echo 1 > usb1-port1/disable [ 37.958239] usb 1-1: USB disconnect, device number 2 [ 37.964101] usb 1-1: unregistering device [ 37.970070] hub 1-0:1.0: hub_suspend [ 37.971305] hub 1-0:1.0: state 7 ports 1 chg 0000 evt 0002 [ 37.974412] usb usb1: bus auto-suspend, wakeup 1 [ 37.988175] usb usb1: suspend raced with wakeup event <--- [ 37.993947] usb usb1: usb auto-resume [ 37.998401] hub 1-0:1.0: hub_resume [ 38.105688] usb usb1-port1: status 0000, change 0000, 12 Mb/s [ 38.112399] hub 1-0:1.0: state 7 ports 1 chg 0000 evt 0000 [ 38.118645] hub 1-0:1.0: hub_suspend [ 38.122963] usb usb1: bus auto-suspend, wakeup 1 [ 38.200368] usb usb1: usb wakeup-resume [ 38.204982] usb usb1: usb auto-resume [ 38.209376] hub 1-0:1.0: hub_resume [ 38.213676] usb usb1-port1: status 0101 change 0001 [ 38.321552] hub 1-0:1.0: state 7 ports 1 chg 0002 evt 0000 [ 38.327978] usb usb1-port1: status 0101, change 0000, 12 Mb/s [ 38.457429] usb 1-1: new high-speed USB device number 3 using ci_hdrc Then, port change bit will be fixed to the final state and usb_clear_port_feature() can correctly clear it after this period. This will also avoid usb runtime suspend routine to run because usb_autopm_put_interface() not run yet. Fixes: f061f43d7418 ("usb: hub: port: add sysfs entry to switch port power") Cc: stable@kernel.org Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260316095042.1559882-1-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/port.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index b6cdea7e9521..b1364f0c384c 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -155,6 +155,7 @@ static ssize_t disable_store(struct device *dev, struct device_attribute *attr, usb_disconnect(&port_dev->child); rc = usb_hub_set_port_power(hdev, hub, port1, !disabled); + msleep(2 * hub_power_on_good_delay(hub)); if (disabled) { usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); From 381133848a033c2086cf9cafb226f425bd0414ff Mon Sep 17 00:00:00 2001 From: Mostafa Saleh Date: Fri, 13 Mar 2026 15:55:34 +0000 Subject: [PATCH 072/176] usb: typec: ps883x: Fix Oops at unbind When trying to unbind a device in order to bind to it vfio-platform as: echo bc0000.geniqup > /sys/bus/platform/devices/bc0000.geniqup/driver/unbind I get the following Oops: [ 436.478639] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000020 [ 436.487762] Mem abort info: [ 436.490716] ESR = 0x0000000096000004 [ 436.494595] EC = 0x25: DABT (current EL), IL = 32 bits [ 436.500071] SET = 0, FnV = 0 [ 436.503250] EA = 0, S1PTW = 0 [ 436.506505] FSC = 0x04: level 0 translation fault [ 436.511533] Data abort info: [ 436.514558] ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000 [ 436.520215] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 436.525436] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 436.530918] user pgtable: 4k pages, 48-bit VAs, pgdp=00000008861a9000 [ 436.537554] [0000000000000020] pgd=0000000000000000, p4d=0000000000000000 [ 436.544548] Internal error: Oops: 0000000096000004 [#1] SMP [ 436.550374] Modules linked in: [ 436.553542] CPU: 2 UID: 0 PID: 671 Comm: bash Tainted: G W 7.0.0-rc3-g56fcdd0911a5-dirty #2 PREEMPT [ 436.564440] Tainted: [W]=WARN [ 436.567515] Hardware name: LENOVO 91B6CTO1WW/3796, BIOS O6NKT3BA 05/02/2025 [ 436.574675] pstate: 21400005 (nzCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) [ 436.581841] pc : ps883x_retimer_remove+0x14/0x94 [ 436.586605] lr : i2c_device_remove+0x28/0x84 [ 436.591017] sp : ffff8000847137c0 That's because the ps883x_retimer_remove() retrieves the driver data from i2c_get_clientdata() which was never set at probe. So, add i2c_set_clientdata() at the end of the probe. Signed-off-by: Mostafa Saleh Reviewed-by: Konrad Dybcio Fixes: 257a087c8b52 ("usb: typec: Add support for Parade PS8830 Type-C Retimer") Link: https://patch.msgid.link/20260313155534.1916773-1-smostafa@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/mux/ps883x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/typec/mux/ps883x.c b/drivers/usb/typec/mux/ps883x.c index 5f2879749769..1256252eceed 100644 --- a/drivers/usb/typec/mux/ps883x.c +++ b/drivers/usb/typec/mux/ps883x.c @@ -444,6 +444,7 @@ static int ps883x_retimer_probe(struct i2c_client *client) goto err_switch_unregister; } + i2c_set_clientdata(client, retimer); return 0; err_switch_unregister: From 351eb14411f748341a9cb32e81f4973407a8350c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 18 Mar 2026 09:46:27 +0100 Subject: [PATCH 073/176] usb: misc: onboard_dev: Remove duplicated static structures Static structure "ti_tusb8041_data" is exactly the same as "ti_tusb8020b_data" and "cypress_hx2vl_data" is the same as "microchip_usb424_data". Drop the duplicated structures to reduce driver size and memory usage without affecting functionality. Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260318084626.34314-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_dev.h | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h index 88f297fd796a..56cb5b162141 100644 --- a/drivers/usb/misc/onboard_usb_dev.h +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -66,13 +66,6 @@ static const struct onboard_dev_pdata ti_tusb8020b_data = { .is_hub = true, }; -static const struct onboard_dev_pdata ti_tusb8041_data = { - .reset_us = 3000, - .num_supplies = 1, - .supply_names = { "vdd" }, - .is_hub = true, -}; - static const struct onboard_dev_pdata bison_intcamera_data = { .reset_us = 1000, .num_supplies = 1, @@ -94,13 +87,6 @@ static const struct onboard_dev_pdata cypress_hx3_data = { .is_hub = true, }; -static const struct onboard_dev_pdata cypress_hx2vl_data = { - .reset_us = 1, - .num_supplies = 1, - .supply_names = { "vdd" }, - .is_hub = true, -}; - static const struct onboard_dev_pdata genesys_gl850g_data = { .reset_us = 3, .num_supplies = 1, @@ -150,13 +136,13 @@ static const struct of_device_id onboard_dev_match[] = { { .compatible = "usb424,5744", .data = µchip_usb5744_data, }, { .compatible = "usb451,8025", .data = &ti_tusb8020b_data, }, { .compatible = "usb451,8027", .data = &ti_tusb8020b_data, }, - { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, - { .compatible = "usb451,8142", .data = &ti_tusb8041_data, }, - { .compatible = "usb451,8440", .data = &ti_tusb8041_data, }, - { .compatible = "usb451,8442", .data = &ti_tusb8041_data, }, + { .compatible = "usb451,8140", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8142", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8440", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8442", .data = &ti_tusb8020b_data, }, { .compatible = "usb4b4,6504", .data = &cypress_hx3_data, }, { .compatible = "usb4b4,6506", .data = &cypress_hx3_data, }, - { .compatible = "usb4b4,6570", .data = &cypress_hx2vl_data, }, + { .compatible = "usb4b4,6570", .data = µchip_usb424_data, }, { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, }, { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, }, { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, }, From f8a5f6934f30b9ee334256347dd70a7ba0f8be7f Mon Sep 17 00:00:00 2001 From: Aldo Conte Date: Wed, 11 Mar 2026 17:33:20 +0100 Subject: [PATCH 074/176] usb: typec: Document priority and mode_selection fields in struct typec_altmode The fields 'priority' and 'mode_selection' in struct typec_altmode are missing from the kernel-doc comment, which results in warnings when building the documentation with 'make htmldocs'. WARNING: ./include/linux/usb/typec_altmode.h:44 struct member 'priority' not described in 'typec_altmode' WARNING: ./include/linux/usb/typec_altmode.h:44 struct member 'mode_selection' not described in 'typec_altmode' Document both fields to keep the kernel-doc comment aligned with the structure definition. Signed-off-by: Aldo Conte Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260311163320.61534-1-aldocontelk@gmail.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/typec_altmode.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h index 0513d333b797..b90cc5cfff8d 100644 --- a/include/linux/usb/typec_altmode.h +++ b/include/linux/usb/typec_altmode.h @@ -26,6 +26,9 @@ struct typec_altmode_ops; * @mode: Index of the Mode * @vdo: VDO returned by Discover Modes USB PD command * @active: Tells has the mode been entered or not + * @priority: Priority used by the automatic alternate mode selection process + * @mode_selection: Whether entry to this alternate mode is managed by the + * automatic alternate mode selection process or by the specific driver * @desc: Optional human readable description of the mode * @ops: Operations vector from the driver * @cable_ops: Cable operations vector from the driver. From 9270102a00aabbe4d1bbb6890d514b01f1c42989 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Mon, 16 Mar 2026 15:02:59 +0000 Subject: [PATCH 075/176] dt-bindings: connector: Add SPR AVS Sink APDO definitions USB Power Delivery 3.2 introduces a new power supply type SPR AVS. Add macro definitions for the USB Power Delivery (PD) Standard Power Range (SPR) Adjustable Voltage Supply (AVS) as a Sink Augmented Power Data Object (APDO) in the device tree bindings. Signed-off-by: Badhri Jagan Sridharan Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260316150301.3892223-2-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/connector/usb-connector.yaml | 5 +++-- include/dt-bindings/usb/pd.h | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/connector/usb-connector.yaml b/Documentation/devicetree/bindings/connector/usb-connector.yaml index 901986de3e2b..a00b239960a3 100644 --- a/Documentation/devicetree/bindings/connector/usb-connector.yaml +++ b/Documentation/devicetree/bindings/connector/usb-connector.yaml @@ -364,8 +364,9 @@ $defs: "Universal Serial Bus Power Delivery Specification" chapter 6.4.1.3 Sink Capabilities Message, the order of each entry(PDO) should follow the PD spec chapter 6.4.1. Required for power sink and power dual role. User - can specify the sink PDO array via PDO_FIXED/BATT/VAR/PPS_APDO() defined - in dt-bindings/usb/pd.h. + can specify the sink PDO array via + PDO_FIXED/BATT/VAR/PPS_APDO/SPR_AVS_SNK_APDO() defined in + dt-bindings/usb/pd.h. minItems: 1 maxItems: 7 $ref: /schemas/types.yaml#/definitions/uint32-array diff --git a/include/dt-bindings/usb/pd.h b/include/dt-bindings/usb/pd.h index 6cff2339bda3..1e64a1f563f9 100644 --- a/include/dt-bindings/usb/pd.h +++ b/include/dt-bindings/usb/pd.h @@ -60,6 +60,7 @@ PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma)) #define APDO_TYPE_PPS 0 +#define APDO_TYPE_SPR_AVS 2 #define PDO_APDO_TYPE_SHIFT 28 /* Only valid value currently is 0x0 - PPS */ #define PDO_APDO_TYPE_MASK 0x3 @@ -85,6 +86,23 @@ PDO_PPS_APDO_MIN_VOLT(min_mv) | PDO_PPS_APDO_MAX_VOLT(max_mv) | \ PDO_PPS_APDO_MAX_CURR(max_ma)) +#define PDO_SPR_AVS_APDO_9V_TO_15V_MAX_CURR_SHIFT 10 /* 10mA units */ +#define PDO_SPR_AVS_APDO_15V_TO_20V_MAX_CURR_SHIFT 0 /* 10mA units */ +#define PDO_SPR_AVS_APDO_MAX_CURR_MASK 0x3ff + +#define PDO_SPR_AVS_APDO_9V_TO_15V_MAX_CURR(max_cur_9v_to_15v_ma) \ + ((((max_cur_9v_to_15v_ma) / 10) & PDO_SPR_AVS_APDO_MAX_CURR_MASK) << \ + PDO_SPR_AVS_APDO_9V_TO_15V_MAX_CURR_SHIFT) + +#define PDO_SPR_AVS_APDO_15V_TO_20V_MAX_CURR(max_cur_15v_to_20v_ma) \ + ((((max_cur_15v_to_20v_ma) / 10) & PDO_SPR_AVS_APDO_MAX_CURR_MASK) << \ + PDO_SPR_AVS_APDO_15V_TO_20V_MAX_CURR_SHIFT) + +#define PDO_SPR_AVS_SNK_APDO(max_cur_9v_to_15v_ma, max_cur_15v_to_20v_ma) \ + (PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(APDO_TYPE_SPR_AVS) | \ + PDO_SPR_AVS_APDO_9V_TO_15V_MAX_CURR(max_cur_9v_to_15v_ma) | \ + PDO_SPR_AVS_APDO_15V_TO_20V_MAX_CURR(max_cur_15v_to_20v_ma)) + /* * Based on "Table 6-14 Fixed Supply PDO - Sink" of "USB Power Delivery Specification Revision 3.0, * Version 1.2" From a43dd4f6f91ed1a1d16595cb0c550b283e9b2298 Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Mon, 16 Mar 2026 15:03:00 +0000 Subject: [PATCH 076/176] power: supply: Add PD SPR AVS support to USB type enum Add two new members to the power_supply_usb_type to represent the USB Power Delivery (PD) Standard Power Range (SPR) Adjustable Voltage Supply (AVS) charging types: POWER_SUPPLY_USB_TYPE_PD_SPR_AVS: For devices supporting only the PD SPR AVS type. POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS: For devices that support both PD Programmable Power Supply (PPS) and PD SPR AVS. Signed-off-by: Badhri Jagan Sridharan Link: https://patch.msgid.link/20260316150301.3892223-3-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-power | 3 ++- drivers/power/supply/power_supply_sysfs.c | 2 ++ include/linux/power_supply.h | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 4b21d5d23251..32697b926cc8 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -675,7 +675,8 @@ Description: Valid values: "Unknown", "SDP", "DCP", "CDP", "ACA", "C", "PD", - "PD_DRP", "PD_PPS", "BrickID" + "PD_DRP", "PD_PPS", "BrickID", "PD_SPR_AVS", + "PD_PPS_SPR_AVS" **Device Specific Properties** diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index dd3a48d72d2b..f30a7b9ccd5e 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -70,6 +70,8 @@ static const char * const POWER_SUPPLY_USB_TYPE_TEXT[] = { [POWER_SUPPLY_USB_TYPE_PD] = "PD", [POWER_SUPPLY_USB_TYPE_PD_DRP] = "PD_DRP", [POWER_SUPPLY_USB_TYPE_PD_PPS] = "PD_PPS", + [POWER_SUPPLY_USB_TYPE_PD_SPR_AVS] = "PD_SPR_AVS", + [POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS] = "PD_PPS_SPR_AVS", [POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID] = "BrickID", }; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 360ffdf272da..7a5e4c3242a0 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -210,6 +210,9 @@ enum power_supply_usb_type { POWER_SUPPLY_USB_TYPE_PD, /* Power Delivery Port */ POWER_SUPPLY_USB_TYPE_PD_DRP, /* PD Dual Role Port */ POWER_SUPPLY_USB_TYPE_PD_PPS, /* PD Programmable Power Supply */ + /* PD Standard Power Range Adjustable Voltage Supply */ + POWER_SUPPLY_USB_TYPE_PD_SPR_AVS, + POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS, /* Supports both PD PPS + SPR AVS */ POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */ }; From d3d959404e6c72e09db8de8893a970edf0ac565d Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Mon, 16 Mar 2026 15:03:01 +0000 Subject: [PATCH 077/176] tcpm: Implement sink support for PD SPR AVS negotiation Add support to enable TCPM to negotiate with USB PD Standard Power Range Adjustable Voltage Supply (SPR AVS) when acting as a power sink. * Added support to the tcpm power supply properties, allowing userspace to enable and control the dynamic limits (voltage and current) specific to the SPR AVS contract. * Implemented tcpm_pd_select_spr_avs_apdo() to select the appropriate APDO and validate the requested voltage/current against both the Source and Sink capabilities. * Implemented tcpm_pd_build_spr_avs_request() to construct the Request Data Object (RDO) for SPR AVS. * Added SNK_NEGOTIATE_SPR_AVS_CAPABILITIES state to the state machine to handle negotiation for SPR AVS. * Updated the SNK_TRANSITION_SINK state to implement the SPR AVS-specific VBUS transition rules, including reducing current draw to PD_I_SNK_STBY_MA for large voltage changes, as required by USB PD spec. Log stub captured when enabling AVS: $ echo 3 > /sys/class/power_supply/tcpm-source-psy-1-0025/online $ cat /d/usb/tcpm-1-0025/log [ 358.895775] request to set AVS online [ 358.895792] AMS POWER_NEGOTIATION start [ 358.895806] state change SNK_READY -> AMS_START [rev3 POWER_NEGOTIATION] [ 358.895850] state change AMS_START -> SNK_NEGOTIATE_SPR_AVS_CAPABILITIES [rev3 POWER_NEGOTIATION] [ 358.895866] SPR AVS src_pdo_index:4 snk_pdo_index:2 req_op_curr_ma roundup:2200 req_out_volt_mv roundup:9000 [ 358.895880] Requesting APDO SPR AVS 4: 9000 mV, 2200 mA [ 358.896405] set_auto_vbus_discharge_threshold mode:0 pps_active:n vbus:0 pps_apdo_min_volt:0 ret:0 [ 358.896422] PD TX, header: 0x1a82 [ 358.900158] PD TX complete, status: 0 [ 358.900205] pending state change SNK_NEGOTIATE_SPR_AVS_CAPABILITIES -> HARD_RESET_SEND @ 60 ms [rev3 POWER_NEGOTIATION] [ 358.904832] PD RX, header: 0x1a3 [1] [ 358.904854] state change SNK_NEGOTIATE_SPR_AVS_CAPABILITIES -> SNK_TRANSITION_SINK [rev3 POWER_NEGOTIATION] [ 358.904888] pending state change SNK_TRANSITION_SINK -> HARD_RESET_SEND @ 700 ms [rev3 POWER_NEGOTIATION] [ 359.021530] PD RX, header: 0x3a6 [1] [ 359.021546] Setting voltage/current limit 9000 mV 2200 mA [ 359.023035] set_auto_vbus_discharge_threshold mode:3 pps_active:n vbus:9000 pps_apdo_min_volt:0 ret:0 [ 359.023053] state change SNK_TRANSITION_SINK -> SNK_READY [rev3 POWER_NEGOTIATION] [ 359.023090] AMS POWER_NEGOTIATION finished $ cat /sys/class/power_supply/tcpm-source-psy-1-0025/online 3 Log stub captured when increasing voltage: $ echo 9100000 > /sys/class/power_supply/tcpm-source-psy-1-0025/voltage_now $ cat /d/usb/tcpm-1-0025/log [ 632.116714] AMS POWER_NEGOTIATION start [ 632.116728] state change SNK_READY -> AMS_START [rev3 POWER_NEGOTIATION] [ 632.116779] state change AMS_START -> SNK_NEGOTIATE_SPR_AVS_CAPABILITIES [rev3 POWER_NEGOTIATION] [ 632.116798] SPR AVS src_pdo_index:4 snk_pdo_index:2 req_op_curr_ma roundup:2200 req_out_volt_mv roundup:9100 [ 632.116811] Requesting APDO SPR AVS 4: 9100 mV, 2200 mA [ 632.117315] set_auto_vbus_discharge_threshold mode:0 pps_active:n vbus:0 pps_apdo_min_volt:0 ret:0 [ 632.117328] PD TX, header: 0x1c82 [ 632.121007] PD TX complete, status: 0 [ 632.121052] pending state change SNK_NEGOTIATE_SPR_AVS_CAPABILITIES -> HARD_RESET_SEND @ 60 ms [rev3 POWER_NEGOTIATION] [ 632.124572] PD RX, header: 0x5a3 [1] [ 632.124594] state change SNK_NEGOTIATE_SPR_AVS_CAPABILITIES -> SNK_TRANSITION_SINK [rev3 POWER_NEGOTIATION] [ 632.124623] pending state change SNK_TRANSITION_SINK -> HARD_RESET_SEND @ 700 ms [rev3 POWER_NEGOTIATION] [ 632.149256] PD RX, header: 0x7a6 [1] [ 632.149271] Setting voltage/current limit 9100 mV 2200 mA [ 632.150770] set_auto_vbus_discharge_threshold mode:3 pps_active:n vbus:9100 pps_apdo_min_volt:0 ret:0 [ 632.150787] state change SNK_TRANSITION_SINK -> SNK_READY [rev3 POWER_NEGOTIATION] [ 632.150823] AMS POWER_NEGOTIATION finished $ cat /sys/class/power_supply/tcpm-source-psy-1-0025/voltage_now 9100000 Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Amit Sunil Dhamne Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260316150301.3892223-4-badhri@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 611 ++++++++++++++++++++++++++++------ include/linux/usb/pd.h | 32 +- include/linux/usb/tcpm.h | 2 +- 3 files changed, 537 insertions(+), 108 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 63a75b94743d..dfbb94ddc98a 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -62,6 +62,7 @@ S(SNK_WAIT_CAPABILITIES_TIMEOUT), \ S(SNK_NEGOTIATE_CAPABILITIES), \ S(SNK_NEGOTIATE_PPS_CAPABILITIES), \ + S(SNK_NEGOTIATE_SPR_AVS_CAPABILITIES), \ S(SNK_TRANSITION_SINK), \ S(SNK_TRANSITION_SINK_VBUS), \ S(SNK_READY), \ @@ -308,6 +309,51 @@ struct pd_pps_data { bool active; }; +enum spr_avs_status { + SPR_AVS_UNKNOWN, + SPR_AVS_NOT_SUPPORTED, + SPR_AVS_SUPPORTED +}; + +static const char * const spr_avs_status_strings[] = { + [SPR_AVS_UNKNOWN] = "Unknown", + [SPR_AVS_SUPPORTED] = "Supported", + [SPR_AVS_NOT_SUPPORTED] = "Not Supported", +}; + +/* + * Standard Power Range Adjustable Voltage Supply (SPR - AVS) data + * @max_current_ma_9v_to_15v: Max current for 9V to 15V range derived from + * source cap & sink cap + * @max_current_ma_15v_to_20v: Max current for 15V to 20V range derived from + * source cap & sink cap + * @req_op_curr_ma: Requested operating current to the port partner acting as source + * @req_out_volt_mv: Requested output voltage to the port partner acting as source + * @max_out_volt_mv: Max SPR voltage supported by the port and the port partner + * @max_current_ma; MAX SPR current supported by the port and the port partner + * @port_partner_src_status: SPR AVS status of port partner acting as source + * @port_partner_src_pdo_index: PDO index of SPR AVS cap of the port partner + * acting as source. Valid only when + * port_partner_src_status is SPR_AVS_SUPPORTED. + * @port_snk_status: SPR AVS status of the local port acting as sink. + * @port_snk_pdo_index: PDO index of SPR AVS cap of local port acting as sink + * @active: True when the local port acting as the sink has negotiated SPR AVS + * with the partner acting as source. + */ +struct pd_spr_avs_data { + u32 max_current_ma_9v_to_15v; + u32 max_current_ma_15v_to_20v; + u32 req_op_curr_ma; + u32 req_out_volt_mv; + u32 max_out_volt_mv; + u32 max_current_ma; + enum spr_avs_status port_partner_src_status; + unsigned int port_partner_src_pdo_index; + enum spr_avs_status port_snk_status; + unsigned int port_snk_pdo_index; + bool active; +}; + struct pd_data { struct usb_power_delivery *pd; struct usb_power_delivery_capabilities *source_cap; @@ -376,6 +422,11 @@ struct sink_caps_ext_data { u8 spr_max_pdp; }; +enum aug_req_type { + PD_PPS, + PD_SPR_AVS, +}; + struct tcpm_port { struct device *dev; @@ -538,9 +589,14 @@ struct tcpm_port { /* PPS */ struct pd_pps_data pps_data; - struct completion pps_complete; - bool pps_pending; - int pps_status; + + /* SPR AVS */ + struct pd_spr_avs_data spr_avs_data; + + /* Augmented supply request - PPS; SPR_AVS */ + struct completion aug_supply_req_complete; + bool aug_supply_req_pending; + int aug_supply_req_status; /* Alternate mode data */ struct pd_mode_data mode_data; @@ -3285,6 +3341,7 @@ static void tcpm_pd_data_request(struct tcpm_port *port, switch (type) { case PD_DATA_SOURCE_CAP: + port->spr_avs_data.port_partner_src_status = SPR_AVS_UNKNOWN; for (i = 0; i < cnt; i++) port->source_caps[i] = le32_to_cpu(msg->payload[i]); @@ -3456,12 +3513,12 @@ static void tcpm_pd_data_request(struct tcpm_port *port, } } -static void tcpm_pps_complete(struct tcpm_port *port, int result) +static void tcpm_aug_supply_req_complete(struct tcpm_port *port, int result) { - if (port->pps_pending) { - port->pps_status = result; - port->pps_pending = false; - complete(&port->pps_complete); + if (port->aug_supply_req_pending) { + port->aug_supply_req_status = result; + port->aug_supply_req_pending = false; + complete(&port->aug_supply_req_complete); } } @@ -3559,7 +3616,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, /* Revert data back from any requested PPS updates */ port->pps_data.req_out_volt = port->supply_voltage; port->pps_data.req_op_curr = port->current_limit; - port->pps_status = (type == PD_CTRL_WAIT ? + port->aug_supply_req_status = (type == PD_CTRL_WAIT ? -EAGAIN : -EOPNOTSUPP); /* Threshold was relaxed before sending Request. Restore it back. */ @@ -3567,6 +3624,20 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, port->pps_data.active, port->supply_voltage); + tcpm_set_state(port, SNK_READY, 0); + break; + case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES: + /* Revert data back from any requested SPR AVS updates */ + port->spr_avs_data.req_out_volt_mv = port->supply_voltage; + port->spr_avs_data.req_op_curr_ma = port->current_limit; + port->aug_supply_req_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + + /* Threshold was relaxed before sending Request. Restore it back. */ + tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD, + port->spr_avs_data.active, + port->supply_voltage); + tcpm_set_state(port, SNK_READY, 0); break; case DR_SWAP_SEND: @@ -3621,6 +3692,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, switch (port->state) { case SNK_NEGOTIATE_CAPABILITIES: port->pps_data.active = false; + port->spr_avs_data.active = false; tcpm_set_state(port, SNK_TRANSITION_SINK, 0); break; case SNK_NEGOTIATE_PPS_CAPABILITIES: @@ -3633,6 +3705,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, power_supply_changed(port->psy); tcpm_set_state(port, SNK_TRANSITION_SINK, 0); break; + case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES: + port->spr_avs_data.active = true; + port->req_supply_voltage = port->spr_avs_data.req_out_volt_mv; + port->req_current_limit = port->spr_avs_data.req_op_curr_ma; + power_supply_changed(port->psy); + tcpm_set_state(port, SNK_TRANSITION_SINK, 0); + break; case SOFT_RESET_SEND: if (port->ams == SOFT_RESET_AMS) tcpm_ams_finish(port); @@ -4130,9 +4209,9 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, case PDO_TYPE_APDO: if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) { port->pps_data.supported = true; - port->usb_type = - POWER_SUPPLY_USB_TYPE_PD_PPS; - power_supply_changed(port->psy); + } else if (pdo_apdo_type(pdo) == APDO_TYPE_SPR_AVS) { + port->spr_avs_data.port_partner_src_status = SPR_AVS_SUPPORTED; + port->spr_avs_data.port_partner_src_pdo_index = i; } continue; default: @@ -4170,6 +4249,10 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, min_snk_mv = pdo_min_voltage(pdo); break; case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_SPR_AVS) { + port->spr_avs_data.port_snk_status = SPR_AVS_SUPPORTED; + port->spr_avs_data.port_snk_pdo_index = j; + } continue; default: tcpm_log(port, "Invalid sink PDO type, ignoring"); @@ -4191,6 +4274,23 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, } } + if (port->spr_avs_data.port_snk_status == SPR_AVS_UNKNOWN) + port->spr_avs_data.port_snk_status = SPR_AVS_NOT_SUPPORTED; + + if (port->spr_avs_data.port_partner_src_status == SPR_AVS_UNKNOWN) + port->spr_avs_data.port_partner_src_status = SPR_AVS_NOT_SUPPORTED; + + if (port->pps_data.supported && + port->spr_avs_data.port_partner_src_status == SPR_AVS_SUPPORTED) + port->usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS; + else if (port->pps_data.supported) + port->usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS; + else if (port->spr_avs_data.port_partner_src_status == SPR_AVS_SUPPORTED) + port->usb_type = POWER_SUPPLY_USB_TYPE_PD_SPR_AVS; + + if (port->usb_type != POWER_SUPPLY_USB_TYPE_PD) + power_supply_changed(port->psy); + return ret; } @@ -4241,6 +4341,88 @@ static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port) return src_pdo; } +static int tcpm_pd_select_spr_avs_apdo(struct tcpm_port *port) +{ + u32 req_out_volt_mv, req_op_curr_ma, src_max_curr_ma = 0, source_cap; + u32 snk_max_curr_ma = 0, src_pdo_index, snk_pdo_index, snk_pdo; + + if (port->spr_avs_data.port_snk_status != SPR_AVS_SUPPORTED || + port->spr_avs_data.port_partner_src_status != + SPR_AVS_SUPPORTED) { + tcpm_log(port, "SPR AVS not supported. port:%s partner:%s", + spr_avs_status_strings[port->spr_avs_data.port_snk_status], + spr_avs_status_strings[port->spr_avs_data.port_partner_src_status]); + return -EOPNOTSUPP; + } + + /* Round up to SPR_AVS_VOLT_MV_STEP */ + req_out_volt_mv = port->spr_avs_data.req_out_volt_mv; + if (req_out_volt_mv % SPR_AVS_VOLT_MV_STEP) { + req_out_volt_mv += SPR_AVS_VOLT_MV_STEP - + (req_out_volt_mv % SPR_AVS_VOLT_MV_STEP); + port->spr_avs_data.req_out_volt_mv = req_out_volt_mv; + } + + /* Round up to RDO_SPR_AVS_CURR_MA_STEP */ + req_op_curr_ma = port->spr_avs_data.req_op_curr_ma; + if (req_op_curr_ma % RDO_SPR_AVS_CURR_MA_STEP) { + req_op_curr_ma += RDO_SPR_AVS_CURR_MA_STEP - + (req_op_curr_ma % RDO_SPR_AVS_CURR_MA_STEP); + port->spr_avs_data.req_op_curr_ma = req_op_curr_ma; + } + + src_pdo_index = port->spr_avs_data.port_partner_src_pdo_index; + snk_pdo_index = port->spr_avs_data.port_snk_pdo_index; + source_cap = port->source_caps[src_pdo_index]; + snk_pdo = port->snk_pdo[snk_pdo_index]; + tcpm_log(port, + "SPR AVS src_pdo_index:%d snk_pdo_index:%d req_op_curr_ma roundup:%u req_out_volt_mv roundup:%u", + src_pdo_index, snk_pdo_index, req_op_curr_ma, req_out_volt_mv); + + if (req_out_volt_mv >= SPR_AVS_TIER1_MIN_VOLT_MV && + req_out_volt_mv <= SPR_AVS_TIER1_MAX_VOLT_MV) { + src_max_curr_ma = + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(source_cap); + snk_max_curr_ma = + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(snk_pdo); + } else if (req_out_volt_mv > SPR_AVS_TIER1_MAX_VOLT_MV && + req_out_volt_mv <= SPR_AVS_TIER2_MAX_VOLT_MV) { + src_max_curr_ma = + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(source_cap); + snk_max_curr_ma = + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(snk_pdo); + } else { + tcpm_log(port, "Invalid SPR AVS req_volt:%umV", req_out_volt_mv); + return -EINVAL; + } + + if (req_op_curr_ma > src_max_curr_ma || + req_op_curr_ma > snk_max_curr_ma) { + tcpm_log(port, + "Invalid SPR AVS request. req_volt:%umV req_curr:%umA src_max_cur:%umA snk_max_cur:%umA", + req_out_volt_mv, req_op_curr_ma, src_max_curr_ma, + snk_max_curr_ma); + return -EINVAL; + } + + /* Max SPR voltage based on both the port and the partner caps */ + if (pdo_spr_avs_apdo_15v_to_20v_max_current_ma(snk_pdo) && + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(source_cap)) + port->spr_avs_data.max_out_volt_mv = SPR_AVS_TIER2_MAX_VOLT_MV; + else + port->spr_avs_data.max_out_volt_mv = SPR_AVS_TIER1_MAX_VOLT_MV; + + /* + * Max SPR AVS curr based on 9V to 15V. This should be higher than or + * equal to 15V to 20V range. + */ + port->spr_avs_data.max_current_ma = + min(pdo_spr_avs_apdo_9v_to_15v_max_current_ma(source_cap), + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(snk_pdo)); + + return src_pdo_index; +} + static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) { unsigned int mv, ma, mw, flags; @@ -4408,13 +4590,74 @@ static int tcpm_pd_build_pps_request(struct tcpm_port *port, u32 *rdo) return 0; } -static int tcpm_pd_send_pps_request(struct tcpm_port *port) +static int tcpm_pd_build_spr_avs_request(struct tcpm_port *port, u32 *rdo) +{ + u32 out_mv, op_ma, flags, snk_pdo_index, source_cap; + unsigned int src_power_mw, snk_power_mw; + int src_pdo_index; + u32 snk_pdo; + + src_pdo_index = tcpm_pd_select_spr_avs_apdo(port); + if (src_pdo_index < 0) + return src_pdo_index; + snk_pdo_index = port->spr_avs_data.port_snk_pdo_index; + source_cap = port->source_caps[src_pdo_index]; + snk_pdo = port->snk_pdo[snk_pdo_index]; + out_mv = port->spr_avs_data.req_out_volt_mv; + op_ma = port->spr_avs_data.req_op_curr_ma; + + flags = RDO_USB_COMM | RDO_NO_SUSPEND; + + /* + * Set capability mismatch when the maximum power needs in the current + * requested AVS voltage tier range is greater than + * port->operating_snk_mw, however, the maximum power offered by the + * source at the current requested AVS voltage tier is less than + * port->operating_sink_mw. + */ + if (out_mv > SPR_AVS_TIER1_MAX_VOLT_MV) { + src_power_mw = + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(source_cap) * + SPR_AVS_TIER2_MAX_VOLT_MV / 1000; + snk_power_mw = + pdo_spr_avs_apdo_15v_to_20v_max_current_ma(snk_pdo) * + SPR_AVS_TIER2_MAX_VOLT_MV / 1000; + } else { + src_power_mw = + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(source_cap) * + SPR_AVS_TIER1_MAX_VOLT_MV / 1000; + snk_power_mw = + pdo_spr_avs_apdo_9v_to_15v_max_current_ma(snk_pdo) * + SPR_AVS_TIER1_MAX_VOLT_MV / 1000; + } + + if (snk_power_mw >= port->operating_snk_mw && + src_power_mw < port->operating_snk_mw) + flags |= RDO_CAP_MISMATCH; + + *rdo = RDO_AVS(src_pdo_index + 1, out_mv, op_ma, flags); + + tcpm_log(port, "Requesting APDO SPR AVS %d: %u mV, %u mA", + src_pdo_index, out_mv, op_ma); + + return 0; +} + +static int tcpm_pd_send_aug_supply_request(struct tcpm_port *port, + enum aug_req_type type) { struct pd_message msg; int ret; u32 rdo; - ret = tcpm_pd_build_pps_request(port, &rdo); + if (type == PD_PPS) { + ret = tcpm_pd_build_pps_request(port, &rdo); + } else if (type == PD_SPR_AVS) { + ret = tcpm_pd_build_spr_avs_request(port, &rdo); + } else { + tcpm_log(port, "Invalid aug_req_type %d", type); + ret = -EOPNOTSUPP; + } if (ret < 0) return ret; @@ -4637,6 +4880,14 @@ static void tcpm_set_partner_usb_comm_capable(struct tcpm_port *port, bool capab port->tcpc->set_partner_usb_comm_capable(port->tcpc, capable); } +static void tcpm_partner_source_caps_reset(struct tcpm_port *port) +{ + usb_power_delivery_unregister_capabilities(port->partner_source_caps); + port->partner_source_caps = NULL; + port->spr_avs_data.port_partner_src_status = SPR_AVS_UNKNOWN; + port->spr_avs_data.active = false; +} + static void tcpm_reset_port(struct tcpm_port *port) { tcpm_enable_auto_vbus_discharge(port, false); @@ -4676,8 +4927,7 @@ static void tcpm_reset_port(struct tcpm_port *port) usb_power_delivery_unregister_capabilities(port->partner_sink_caps); port->partner_sink_caps = NULL; - usb_power_delivery_unregister_capabilities(port->partner_source_caps); - port->partner_source_caps = NULL; + tcpm_partner_source_caps_reset(port); usb_power_delivery_unregister(port->partner_pd); port->partner_pd = NULL; } @@ -5169,7 +5419,7 @@ static void run_state_machine(struct tcpm_port *port) case SNK_UNATTACHED: if (!port->non_pd_role_swap) tcpm_swap_complete(port, -ENOTCONN); - tcpm_pps_complete(port, -ENOTCONN); + tcpm_aug_supply_req_complete(port, -ENOTCONN); tcpm_snk_detach(port); if (port->potential_contaminant) { tcpm_set_state(port, CHECK_CONTAMINANT, 0); @@ -5400,13 +5650,16 @@ static void run_state_machine(struct tcpm_port *port) } break; case SNK_NEGOTIATE_PPS_CAPABILITIES: - ret = tcpm_pd_send_pps_request(port); + case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES: + ret = tcpm_pd_send_aug_supply_request(port, port->state == + SNK_NEGOTIATE_PPS_CAPABILITIES ? + PD_PPS : PD_SPR_AVS); if (ret < 0) { /* Restore back to the original state */ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD, port->pps_data.active, port->supply_voltage); - port->pps_status = ret; + port->aug_supply_req_status = ret; /* * If this was called due to updates to sink * capabilities, and pps is no longer valid, we should @@ -5422,23 +5675,58 @@ static void run_state_machine(struct tcpm_port *port) } break; case SNK_TRANSITION_SINK: - /* From the USB PD spec: - * "The Sink Shall transition to Sink Standby before a positive or - * negative voltage transition of VBUS. During Sink Standby - * the Sink Shall reduce its power draw to pSnkStdby." - * - * This is not applicable to PPS though as the port can continue - * to draw negotiated power without switching to standby. - */ - if (port->supply_voltage != port->req_supply_voltage && !port->pps_data.active && - port->current_limit * port->supply_voltage / 1000 > PD_P_SNK_STDBY_MW) { - u32 stdby_ma = PD_P_SNK_STDBY_MW * 1000 / port->supply_voltage; + if (port->spr_avs_data.active) { + if (abs(port->req_supply_voltage - port->supply_voltage) > + SPR_AVS_AVS_SMALL_STEP_V * 1000) { + /* + * The Sink Shall reduce its current draw to + * iSnkStdby within tSnkStdby. The reduction to + * iSnkStdby is not required if the voltage + * increase is less than or equal to + * vAvsSmallStep. + */ + tcpm_log(port, + "SPR AVS Setting iSnkstandby. Req vol: %u mV Curr vol: %u mV", + port->req_supply_voltage, + port->supply_voltage); + tcpm_set_current_limit(port, PD_I_SNK_STBY_MA, + port->supply_voltage); + } + /* + * Although tAvsSrcTransSmall is expected to be used + * for voltage transistions smaller than 1V, using + * tAvsSrcTransLarge to be resilient against chargers + * which strictly cannot honor tAvsSrcTransSmall to + * improve interoperability. + */ + tcpm_set_state(port, hard_reset_state(port), + PD_T_AVS_SRC_TRANS_LARGE); + /* + * From the USB PD spec: + * "The Sink Shall transition to Sink Standby before a + * positive ornegative voltage transition of VBUS. + * During Sink Standby the Sink Shall reduce its power + * draw to pSnkStdby." + * + * This is not applicable to PPS though as the port can + * continue to draw negotiated power without switching + * to standby. + */ + } else if (port->supply_voltage != port->req_supply_voltage && + !port->pps_data.active && + (port->current_limit * port->supply_voltage / 1000 > + PD_P_SNK_STDBY_MW)) { + u32 stdby_ma = PD_P_SNK_STDBY_MW * 1000 / + port->supply_voltage; tcpm_log(port, "Setting standby current %u mV @ %u mA", port->supply_voltage, stdby_ma); - tcpm_set_current_limit(port, stdby_ma, port->supply_voltage); + tcpm_set_current_limit(port, stdby_ma, + port->supply_voltage); + tcpm_set_state(port, hard_reset_state(port), + PD_T_PS_TRANSITION); } - fallthrough; + break; case SNK_TRANSITION_SINK_VBUS: tcpm_set_state(port, hard_reset_state(port), PD_T_PS_TRANSITION); @@ -5458,7 +5746,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_typec_connect(port); if (port->pd_capable && port->source_caps[0] & PDO_FIXED_DUAL_ROLE) mod_enable_frs_delayed_work(port, 0); - tcpm_pps_complete(port, port->pps_status); + tcpm_aug_supply_req_complete(port, port->aug_supply_req_status); if (port->ams != NONE_AMS) tcpm_ams_finish(port); @@ -5645,8 +5933,7 @@ static void run_state_machine(struct tcpm_port *port) port->message_id = 0; port->rx_msgid = -1; /* remove existing capabilities */ - usb_power_delivery_unregister_capabilities(port->partner_source_caps); - port->partner_source_caps = NULL; + tcpm_partner_source_caps_reset(port); tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); tcpm_ams_finish(port); if (port->pwr_role == TYPEC_SOURCE) { @@ -5679,8 +5966,7 @@ static void run_state_machine(struct tcpm_port *port) port->message_id = 0; port->rx_msgid = -1; /* remove existing capabilities */ - usb_power_delivery_unregister_capabilities(port->partner_source_caps); - port->partner_source_caps = NULL; + tcpm_partner_source_caps_reset(port); if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP)) tcpm_set_state_cond(port, hard_reset_state(port), 0); else @@ -5817,8 +6103,7 @@ static void run_state_machine(struct tcpm_port *port) break; case PR_SWAP_SNK_SRC_SINK_OFF: /* will be source, remove existing capabilities */ - usb_power_delivery_unregister_capabilities(port->partner_source_caps); - port->partner_source_caps = NULL; + tcpm_partner_source_caps_reset(port); /* * Prevent vbus discharge circuit from turning on during PR_SWAP * as this is not a disconnect. @@ -5966,7 +6251,7 @@ static void run_state_machine(struct tcpm_port *port) break; case ERROR_RECOVERY: tcpm_swap_complete(port, -EPROTO); - tcpm_pps_complete(port, -EPROTO); + tcpm_aug_supply_req_complete(port, -EPROTO); tcpm_set_state(port, PORT_RESET, 0); break; case PORT_RESET: @@ -6940,7 +7225,7 @@ static int tcpm_try_role(struct typec_port *p, int role) return ret; } -static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 req_op_curr) +static int tcpm_aug_set_op_curr(struct tcpm_port *port, u16 req_op_curr_ma) { unsigned int target_mw; int ret; @@ -6948,7 +7233,19 @@ static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 req_op_curr) mutex_lock(&port->swap_lock); mutex_lock(&port->lock); - if (!port->pps_data.active) { + if (port->pps_data.active) { + req_op_curr_ma = req_op_curr_ma - + (req_op_curr_ma % RDO_PROG_CURR_MA_STEP); + if (req_op_curr_ma > port->pps_data.max_curr) { + ret = -EINVAL; + goto port_unlock; + } + target_mw = (req_op_curr_ma * port->supply_voltage) / 1000; + if (target_mw < port->operating_snk_mw) { + ret = -EINVAL; + goto port_unlock; + } + } else if (!port->spr_avs_data.active) { ret = -EOPNOTSUPP; goto port_unlock; } @@ -6958,38 +7255,31 @@ static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 req_op_curr) goto port_unlock; } - if (req_op_curr > port->pps_data.max_curr) { - ret = -EINVAL; - goto port_unlock; - } + if (port->pps_data.active) + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + else + port->upcoming_state = SNK_NEGOTIATE_SPR_AVS_CAPABILITIES; - target_mw = (req_op_curr * port->supply_voltage) / 1000; - if (target_mw < port->operating_snk_mw) { - ret = -EINVAL; - goto port_unlock; - } - - port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; ret = tcpm_ams_start(port, POWER_NEGOTIATION); if (ret == -EAGAIN) { port->upcoming_state = INVALID_STATE; goto port_unlock; } - /* Round down operating current to align with PPS valid steps */ - req_op_curr = req_op_curr - (req_op_curr % RDO_PROG_CURR_MA_STEP); - - reinit_completion(&port->pps_complete); - port->pps_data.req_op_curr = req_op_curr; - port->pps_status = 0; - port->pps_pending = true; + reinit_completion(&port->aug_supply_req_complete); + if (port->pps_data.active) + port->pps_data.req_op_curr = req_op_curr_ma; + else + port->spr_avs_data.req_op_curr_ma = req_op_curr_ma; + port->aug_supply_req_status = 0; + port->aug_supply_req_pending = true; mutex_unlock(&port->lock); - if (!wait_for_completion_timeout(&port->pps_complete, - msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT))) + if (!wait_for_completion_timeout(&port->aug_supply_req_complete, + msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT))) ret = -ETIMEDOUT; else - ret = port->pps_status; + ret = port->aug_supply_req_status; goto swap_unlock; @@ -7001,7 +7291,7 @@ static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 req_op_curr) return ret; } -static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 req_out_volt) +static int tcpm_aug_set_out_volt(struct tcpm_port *port, u16 req_out_volt_mv) { unsigned int target_mw; int ret; @@ -7009,7 +7299,16 @@ static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 req_out_volt) mutex_lock(&port->swap_lock); mutex_lock(&port->lock); - if (!port->pps_data.active) { + if (port->pps_data.active) { + req_out_volt_mv = req_out_volt_mv - (req_out_volt_mv % + RDO_PROG_VOLT_MV_STEP); + /* Round down output voltage to align with PPS valid steps */ + target_mw = (port->current_limit * req_out_volt_mv) / 1000; + if (target_mw < port->operating_snk_mw) { + ret = -EINVAL; + goto port_unlock; + } + } else if (!port->spr_avs_data.active) { ret = -EOPNOTSUPP; goto port_unlock; } @@ -7019,33 +7318,31 @@ static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 req_out_volt) goto port_unlock; } - target_mw = (port->current_limit * req_out_volt) / 1000; - if (target_mw < port->operating_snk_mw) { - ret = -EINVAL; - goto port_unlock; - } + if (port->pps_data.active) + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + else + port->upcoming_state = SNK_NEGOTIATE_SPR_AVS_CAPABILITIES; - port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; ret = tcpm_ams_start(port, POWER_NEGOTIATION); if (ret == -EAGAIN) { port->upcoming_state = INVALID_STATE; goto port_unlock; } - /* Round down output voltage to align with PPS valid steps */ - req_out_volt = req_out_volt - (req_out_volt % RDO_PROG_VOLT_MV_STEP); - - reinit_completion(&port->pps_complete); - port->pps_data.req_out_volt = req_out_volt; - port->pps_status = 0; - port->pps_pending = true; + reinit_completion(&port->aug_supply_req_complete); + if (port->pps_data.active) + port->pps_data.req_out_volt = req_out_volt_mv; + else + port->spr_avs_data.req_out_volt_mv = req_out_volt_mv; + port->aug_supply_req_status = 0; + port->aug_supply_req_pending = true; mutex_unlock(&port->lock); - if (!wait_for_completion_timeout(&port->pps_complete, - msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT))) + if (!wait_for_completion_timeout(&port->aug_supply_req_complete, + msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT))) ret = -ETIMEDOUT; else - ret = port->pps_status; + ret = port->aug_supply_req_status; goto swap_unlock; @@ -7088,9 +7385,9 @@ static int tcpm_pps_activate(struct tcpm_port *port, bool activate) goto port_unlock; } - reinit_completion(&port->pps_complete); - port->pps_status = 0; - port->pps_pending = true; + reinit_completion(&port->aug_supply_req_complete); + port->aug_supply_req_status = 0; + port->aug_supply_req_pending = true; /* Trigger PPS request or move back to standard PDO contract */ if (activate) { @@ -7099,11 +7396,75 @@ static int tcpm_pps_activate(struct tcpm_port *port, bool activate) } mutex_unlock(&port->lock); - if (!wait_for_completion_timeout(&port->pps_complete, - msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT))) + if (!wait_for_completion_timeout(&port->aug_supply_req_complete, + msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT))) ret = -ETIMEDOUT; else - ret = port->pps_status; + ret = port->aug_supply_req_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + +static int tcpm_spr_avs_activate(struct tcpm_port *port, bool activate) +{ + int ret = 0; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->spr_avs_data.port_snk_status == SPR_AVS_NOT_SUPPORTED || + port->spr_avs_data.port_partner_src_status == SPR_AVS_NOT_SUPPORTED) { + tcpm_log(port, "SPR_AVS not supported"); + ret = -EOPNOTSUPP; + goto port_unlock; + } + + /* Trying to deactivate SPR AVS when already deactivated so just bail */ + if (!port->spr_avs_data.active && !activate) + goto port_unlock; + + if (port->state != SNK_READY) { + tcpm_log(port, + "SPR_AVS cannot be activated. Port not in SNK_READY"); + ret = -EAGAIN; + goto port_unlock; + } + + if (activate) + port->upcoming_state = SNK_NEGOTIATE_SPR_AVS_CAPABILITIES; + else + port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES; + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + tcpm_log(port, "SPR_AVS cannot be %s. AMS start failed", + activate ? "activated" : "deactivated"); + port->upcoming_state = INVALID_STATE; + goto port_unlock; + } + + reinit_completion(&port->aug_supply_req_complete); + port->aug_supply_req_status = 0; + port->aug_supply_req_pending = true; + + /* Trigger AVS request or move back to standard PDO contract */ + if (activate) { + port->spr_avs_data.req_out_volt_mv = port->supply_voltage; + port->spr_avs_data.req_op_curr_ma = port->current_limit; + } + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->aug_supply_req_complete, + msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->aug_supply_req_status; goto swap_unlock; @@ -7259,16 +7620,26 @@ static int tcpm_pd_set(struct typec_port *p, struct usb_power_delivery *pd) break; case SNK_NEGOTIATE_CAPABILITIES: case SNK_NEGOTIATE_PPS_CAPABILITIES: + case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES: case SNK_READY: case SNK_TRANSITION_SINK: case SNK_TRANSITION_SINK_VBUS: - if (port->pps_data.active) + if (port->pps_data.active) { port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; - else if (port->pd_capable) + } else if (port->pd_capable) { port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES; - else + if (port->spr_avs_data.active) { + /* + * De-activate AVS and fallback to PD to + * re-evaluate whether AVS is supported in the + * current sink cap set. + */ + port->spr_avs_data.active = false; + port->spr_avs_data.port_snk_status = SPR_AVS_UNKNOWN; + } + } else { break; - + } port->update_sink_caps = true; ret = tcpm_ams_start(port, POWER_NEGOTIATION); @@ -7778,7 +8149,8 @@ static void tcpm_fw_get_pd_revision(struct tcpm_port *port, struct fwnode_handle enum tcpm_psy_online_states { TCPM_PSY_OFFLINE = 0, TCPM_PSY_FIXED_ONLINE, - TCPM_PSY_PROG_ONLINE, + TCPM_PSY_PPS_ONLINE, + TCPM_PSY_SPR_AVS_ONLINE, }; static enum power_supply_property tcpm_psy_props[] = { @@ -7796,7 +8168,9 @@ static int tcpm_psy_get_online(struct tcpm_port *port, { if (port->vbus_charge) { if (port->pps_data.active) - val->intval = TCPM_PSY_PROG_ONLINE; + val->intval = TCPM_PSY_PPS_ONLINE; + else if (port->spr_avs_data.active) + val->intval = TCPM_PSY_SPR_AVS_ONLINE; else val->intval = TCPM_PSY_FIXED_ONLINE; } else { @@ -7811,6 +8185,8 @@ static int tcpm_psy_get_voltage_min(struct tcpm_port *port, { if (port->pps_data.active) val->intval = port->pps_data.min_volt * 1000; + else if (port->spr_avs_data.active) + val->intval = SPR_AVS_TIER1_MIN_VOLT_MV * 1000; else val->intval = port->supply_voltage * 1000; @@ -7822,6 +8198,8 @@ static int tcpm_psy_get_voltage_max(struct tcpm_port *port, { if (port->pps_data.active) val->intval = port->pps_data.max_volt * 1000; + else if (port->spr_avs_data.active) + val->intval = port->spr_avs_data.max_out_volt_mv * 1000; else val->intval = port->supply_voltage * 1000; @@ -7841,6 +8219,8 @@ static int tcpm_psy_get_current_max(struct tcpm_port *port, { if (port->pps_data.active) val->intval = port->pps_data.max_curr * 1000; + else if (port->spr_avs_data.active) + val->intval = port->spr_avs_data.max_current_ma * 1000; else val->intval = port->current_limit * 1000; @@ -7916,17 +8296,41 @@ static int tcpm_psy_get_prop(struct power_supply *psy, return ret; } +static int tcpm_disable_pps_avs(struct tcpm_port *port) +{ + int ret = 0; + + if (port->pps_data.active) + ret = tcpm_pps_activate(port, false); + else if (port->spr_avs_data.active) + ret = tcpm_spr_avs_activate(port, false); + + return ret; +} + static int tcpm_psy_set_online(struct tcpm_port *port, const union power_supply_propval *val) { - int ret; + int ret = 0; switch (val->intval) { case TCPM_PSY_FIXED_ONLINE: - ret = tcpm_pps_activate(port, false); + ret = tcpm_disable_pps_avs(port); break; - case TCPM_PSY_PROG_ONLINE: - ret = tcpm_pps_activate(port, true); + case TCPM_PSY_PPS_ONLINE: + if (port->spr_avs_data.active) + ret = tcpm_spr_avs_activate(port, false); + if (!ret) + ret = tcpm_pps_activate(port, true); + break; + case TCPM_PSY_SPR_AVS_ONLINE: + tcpm_log(port, "request to set AVS online"); + if (port->spr_avs_data.active) + return 0; + ret = tcpm_disable_pps_avs(port); + if (ret) + break; + ret = tcpm_spr_avs_activate(port, true); break; default: ret = -EINVAL; @@ -7955,13 +8359,10 @@ static int tcpm_psy_set_prop(struct power_supply *psy, ret = tcpm_psy_set_online(port, val); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = tcpm_pps_set_out_volt(port, val->intval / 1000); + ret = tcpm_aug_set_out_volt(port, val->intval / 1000); break; case POWER_SUPPLY_PROP_CURRENT_NOW: - if (val->intval > port->pps_data.max_curr * 1000) - ret = -EINVAL; - else - ret = tcpm_pps_set_op_curr(port, val->intval / 1000); + ret = tcpm_aug_set_op_curr(port, val->intval / 1000); break; default: ret = -EINVAL; @@ -8006,7 +8407,9 @@ static int devm_tcpm_psy_register(struct tcpm_port *port) port->psy_desc.type = POWER_SUPPLY_TYPE_USB; port->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) | BIT(POWER_SUPPLY_USB_TYPE_PD) | - BIT(POWER_SUPPLY_USB_TYPE_PD_PPS); + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) | + BIT(POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS) | + BIT(POWER_SUPPLY_USB_TYPE_PD_SPR_AVS); port->psy_desc.properties = tcpm_psy_props; port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props); port->psy_desc.get_property = tcpm_psy_get_prop; @@ -8101,7 +8504,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) init_completion(&port->tx_complete); init_completion(&port->swap_complete); - init_completion(&port->pps_complete); + init_completion(&port->aug_supply_req_complete); tcpm_debugfs_init(port); err = tcpm_fw_get_caps(port, tcpc->fwnode); diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 5a98983195cb..337a5485af7c 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -398,9 +398,30 @@ enum pd_apdo_type { #define PDO_SPR_AVS_APDO_15V_TO_20V_MAX_CURR GENMASK(9, 0) /* 10mA unit */ /* SPR AVS has two different current ranges 9V - 15V, 15V - 20V */ -#define SPR_AVS_TIER1_MIN_VOLT_MV 9000 -#define SPR_AVS_TIER1_MAX_VOLT_MV 15000 -#define SPR_AVS_TIER2_MAX_VOLT_MV 20000 +#define SPR_AVS_TIER1_MIN_VOLT_MV 9000 +#define SPR_AVS_TIER1_MAX_VOLT_MV 15000 +#define SPR_AVS_TIER2_MAX_VOLT_MV 20000 + +#define SPR_AVS_AVS_SMALL_STEP_V 1 +/* vAvsStep - 100mv */ +#define SPR_AVS_VOLT_MV_STEP 100 +/* SPR AVS RDO Operating Current is in 50mA step */ +#define RDO_SPR_AVS_CURR_MA_STEP 50 +/* SPR AVS RDO Output voltage is in 25mV step */ +#define RDO_SPR_AVS_OUT_VOLT_MV_STEP 25 + +#define RDO_SPR_AVS_VOLT GENMASK(20, 9) +#define RDO_SPR_AVS_CURR GENMASK(6, 0) + +#define RDO_SPR_AVS_OUT_VOLT(mv) \ + FIELD_PREP(RDO_SPR_AVS_VOLT, ((mv) / RDO_SPR_AVS_OUT_VOLT_MV_STEP)) + +#define RDO_SPR_AVS_OP_CURR(ma) \ + FIELD_PREP(RDO_SPR_AVS_CURR, ((ma) / RDO_SPR_AVS_CURR_MA_STEP)) + +#define RDO_AVS(idx, out_mv, op_ma, flags) \ + (RDO_OBJ(idx) | (flags) | \ + RDO_SPR_AVS_OUT_VOLT(out_mv) | RDO_SPR_AVS_OP_CURR(op_ma)) static inline enum pd_pdo_type pdo_type(u32 pdo) { @@ -660,6 +681,11 @@ static inline unsigned int rdo_max_power(u32 rdo) #define PD_P_SNK_STDBY_MW 2500 /* 2500 mW */ +#define PD_I_SNK_STBY_MA 500 /* 500 mA */ + +#define PD_T_AVS_SRC_TRANS_SMALL 50 /* 50 ms */ +#define PD_T_AVS_SRC_TRANS_LARGE 700 /* 700 ms */ + #if IS_ENABLED(CONFIG_TYPEC) struct usb_power_delivery; diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index b22e659f81ba..93079450bba0 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -31,7 +31,7 @@ enum typec_cc_polarity { /* Time to wait for TCPC to complete transmit */ #define PD_T_TCPC_TX_TIMEOUT 100 /* in ms */ #define PD_ROLE_SWAP_TIMEOUT (MSEC_PER_SEC * 10) -#define PD_PPS_CTRL_TIMEOUT (MSEC_PER_SEC * 10) +#define PD_AUG_PSY_CTRL_TIMEOUT (MSEC_PER_SEC * 10) enum tcpm_transmit_status { TCPC_TX_SUCCESS = 0, From 84db3719d27337b952fe382413d341fb95351130 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Tue, 17 Mar 2026 11:05:46 +0800 Subject: [PATCH 078/176] usb: dwc3: imx: avoid calling imx suspend/resume callbacks twice If a runtime suspend is executed followed by a system suspend, the driver may invoke dwc3_imx_suspend() twice, which causes enable_irq() to be called twice as well. This leads to an unbalanced IRQ state and may trigger warnings or malfunction. Prevent this by checking the pm_suspended flag before running the imx suspend/resume path. Fixes: 76fc9452a6bf ("usb: dwc3: introduce flatten model driver of i.MX Soc") Signed-off-by: Xu Yang Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260317030546.1665206-1-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-imx.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/dwc3/dwc3-imx.c b/drivers/usb/dwc3/dwc3-imx.c index 303708f7d79a..973a486b544d 100644 --- a/drivers/usb/dwc3/dwc3-imx.c +++ b/drivers/usb/dwc3/dwc3-imx.c @@ -288,6 +288,9 @@ static void dwc3_imx_remove(struct platform_device *pdev) static void dwc3_imx_suspend(struct dwc3_imx *dwc_imx, pm_message_t msg) { + if (dwc_imx->pm_suspended) + return; + if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc_imx->dev)) dwc3_imx_wakeup_enable(dwc_imx, msg); @@ -299,6 +302,9 @@ static void dwc3_imx_resume(struct dwc3_imx *dwc_imx, pm_message_t msg) { struct dwc3 *dwc = &dwc_imx->dwc; + if (!dwc_imx->pm_suspended) + return; + dwc_imx->pm_suspended = false; if (!dwc_imx->wakeup_pending) disable_irq_nosync(dwc_imx->irq); From c3e7cc8bc5ca08b2fae3d43c7c86f140daa873ef Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 18 Mar 2026 11:52:37 -0700 Subject: [PATCH 079/176] thunderbolt: Use kzalloc_flex() for struct tb_path allocation Simplifies allocation of struct tb_path by using a flexible array member. Also added __counted_by for extra runtime analysis. Signed-off-by: Rosen Penev Reviewed-by: Kees Cook Signed-off-by: Mika Westerberg --- drivers/thunderbolt/path.c | 28 +++++++--------------------- drivers/thunderbolt/tb.h | 5 +++-- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c index 22fb4a1e1acd..8713ea0f47c1 100644 --- a/drivers/thunderbolt/path.c +++ b/drivers/thunderbolt/path.c @@ -150,22 +150,17 @@ struct tb_path *tb_path_discover(struct tb_port *src, int src_hopid, num_hops++; } - path = kzalloc_obj(*path); + path = kzalloc_flex(*path, hops, num_hops); if (!path) return NULL; + path->path_length = num_hops; + path->name = name; path->tb = src->sw->tb; - path->path_length = num_hops; path->activated = true; path->alloc_hopid = alloc_hopid; - path->hops = kzalloc_objs(*path->hops, num_hops); - if (!path->hops) { - kfree(path); - return NULL; - } - tb_dbg(path->tb, "discovering %s path starting from %llx:%u\n", path->name, tb_route(src->sw), src->port); @@ -245,10 +240,6 @@ struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid, size_t num_hops; int i, ret; - path = kzalloc_obj(*path); - if (!path) - return NULL; - first_port = last_port = NULL; i = 0; tb_for_each_port_on_path(src, dst, in_port) { @@ -259,20 +250,17 @@ struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid, } /* Check that src and dst are reachable */ - if (first_port != src || last_port != dst) { - kfree(path); + if (first_port != src || last_port != dst) return NULL; - } /* Each hop takes two ports */ num_hops = i / 2; - path->hops = kzalloc_objs(*path->hops, num_hops); - if (!path->hops) { - kfree(path); + path = kzalloc_flex(*path, hops, num_hops); + if (!path) return NULL; - } + path->path_length = num_hops; path->alloc_hopid = true; in_hopid = src_hopid; @@ -339,7 +327,6 @@ struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid, } path->tb = tb; - path->path_length = num_hops; path->name = name; return path; @@ -372,7 +359,6 @@ void tb_path_free(struct tb_path *path) } } - kfree(path->hops); kfree(path); } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index e96474f17067..217c3114bec8 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -419,9 +419,9 @@ enum tb_path_port { * @activated: Is the path active * @clear_fc: Clear all flow control from the path config space entries * when deactivating this path - * @hops: Path hops * @path_length: How many hops the path uses * @alloc_hopid: Does this path consume port HopID + * @hops: Path hops * * A path consists of a number of hops (see &struct tb_path_hop). To * establish a PCIe tunnel two paths have to be created between the two @@ -440,9 +440,10 @@ struct tb_path { bool drop_packages; bool activated; bool clear_fc; - struct tb_path_hop *hops; int path_length; bool alloc_hopid; + + struct tb_path_hop hops[] __counted_by(path_length); }; /* HopIDs 0-7 are reserved by the Thunderbolt protocol */ From 0f93a753f9fbd6d9674393714f8663cff2168b93 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 25 Mar 2026 18:12:56 +0100 Subject: [PATCH 080/176] usb: misc: appledisplay: use HID includes The driver uses its own definitions for HID requests. This leads to duplication and obfuscation. Use HID's definitions. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260325171311.384010-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/appledisplay.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/usb/misc/appledisplay.c b/drivers/usb/misc/appledisplay.c index 4beebde59842..16883592f7fc 100644 --- a/drivers/usb/misc/appledisplay.c +++ b/drivers/usb/misc/appledisplay.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -20,9 +21,6 @@ #define APPLE_VENDOR_ID 0x05AC -#define USB_REQ_GET_REPORT 0x01 -#define USB_REQ_SET_REPORT 0x09 - #define ACD_USB_TIMEOUT 250 #define ACD_USB_EDID 0x0302 @@ -140,7 +138,7 @@ static int appledisplay_bl_update_status(struct backlight_device *bd) retval = usb_control_msg( pdata->udev, usb_sndctrlpipe(pdata->udev, 0), - USB_REQ_SET_REPORT, + HID_REQ_SET_REPORT, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, ACD_USB_BRIGHTNESS, 0, @@ -163,7 +161,7 @@ static int appledisplay_bl_get_brightness(struct backlight_device *bd) retval = usb_control_msg( pdata->udev, usb_rcvctrlpipe(pdata->udev, 0), - USB_REQ_GET_REPORT, + HID_REQ_GET_REPORT, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, ACD_USB_BRIGHTNESS, 0, From d4cdecadbd400a017c3d2a0150fd16d973950e5d Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 25 Mar 2026 18:12:57 +0100 Subject: [PATCH 081/176] usb: misc: iowarrior: use HID includes The driver uses its own definitions for HID requests. This leads to duplication and obfuscation. Use HID's definitions. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260325171311.384010-2-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/iowarrior.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 54f1bb0f7123..22504c0a2841 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #define DRIVER_AUTHOR "Christian Lucht " @@ -98,14 +99,13 @@ struct iowarrior { /* globals */ /*--------------*/ -#define USB_REQ_GET_REPORT 0x01 //#if 0 static int usb_get_report(struct usb_device *dev, struct usb_host_interface *inter, unsigned char type, unsigned char id, void *buf, int size) { return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - USB_REQ_GET_REPORT, + HID_REQ_GET_REPORT, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, (type << 8) + id, inter->desc.bInterfaceNumber, buf, size, @@ -113,14 +113,12 @@ static int usb_get_report(struct usb_device *dev, } //#endif -#define USB_REQ_SET_REPORT 0x09 - static int usb_set_report(struct usb_interface *intf, unsigned char type, unsigned char id, void *buf, int size) { return usb_control_msg(interface_to_usbdev(intf), usb_sndctrlpipe(interface_to_usbdev(intf), 0), - USB_REQ_SET_REPORT, + HID_REQ_SET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE, (type << 8) + id, intf->cur_altsetting->desc.bInterfaceNumber, buf, From a402532ab855620e02a16950aea86fc621c6f87c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 23 Mar 2026 20:17:30 +0800 Subject: [PATCH 082/176] usb: gadget: bdc: validate status-report endpoint indices bdc_sr_xsf() decodes a 5-bit endpoint number from the hardware status report and uses it to index bdc->bdc_ep_array[] directly. The array is only allocated to bdc->num_eps for the current controller instance, so a status report can carry an endpoint number that still fits the 5-bit field but does not fit the runtime-sized endpoint table. Reject status reports whose endpoint number is outside bdc->num_eps before indexing the endpoint array. Signed-off-by: Pengpeng Hou Reviewed-by: Florian Fainelli Tested-by: Justin Chen Link: https://patch.msgid.link/20260323121730.75245-1-pengpeng@iscas.ac.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/bdc/bdc_ep.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index c0ab3347059a..a7a22e5ec47b 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -1647,6 +1647,10 @@ void bdc_sr_xsf(struct bdc *bdc, struct bdc_sr *sreport) u8 ep_num; ep_num = (le32_to_cpu(sreport->offset[3])>>4) & 0x1f; + if (ep_num >= bdc->num_eps) { + dev_err(bdc->dev, "xsf for invalid ep %u\n", ep_num); + return; + } ep = bdc->bdc_ep_array[ep_num]; if (!ep || !(ep->flags & BDC_EP_ENABLED)) { dev_err(bdc->dev, "xsf for ep not enabled\n"); From 448f428a4b54bb589a6da1f23edcb941098e2953 Mon Sep 17 00:00:00 2001 From: Nicolas Chauvet Date: Mon, 23 Mar 2026 15:02:48 +0100 Subject: [PATCH 083/176] usb: tegra: use MODULE_FIRMWARE if SOC is ENABLED This allows to reduce the size of the initramfs by only selecting the related firmware when a given SOC is enabled. Signed-off-by: Nicolas Chauvet Link: https://patch.msgid.link/20260323140249.173603-1-kwizart@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-tegra.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index ed4b11f8d298..d2214d309e96 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -2562,7 +2562,9 @@ static const struct tegra_xusb_soc tegra124_soc = { .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, }; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_124_SOC) || IS_ENABLED(CONFIG_ARCH_TEGRA_132_SOC) MODULE_FIRMWARE("nvidia/tegra124/xusb.bin"); +#endif static const char * const tegra210_supply_names[] = { "dvddio-pex", @@ -2600,11 +2602,15 @@ static const struct tegra_xusb_soc tegra210_soc = { .smi_intr = XUSB_CFG_ARU_SMI_INTR, }, }; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) MODULE_FIRMWARE("nvidia/tegra210/xusb.bin"); +#endif static const char * const tegra186_supply_names[] = { }; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) MODULE_FIRMWARE("nvidia/tegra186/xusb.bin"); +#endif static const struct tegra_xusb_phy_type tegra186_phy_types[] = { { .name = "usb3", .num = 3, }, @@ -2677,7 +2683,9 @@ static const struct tegra_xusb_soc tegra194_soc = { }, .lpm_support = true, }; +#if IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) MODULE_FIRMWARE("nvidia/tegra194/xusb.bin"); +#endif static const struct tegra_xusb_soc_ops tegra234_ops = { .mbox_reg_readl = &bar2_readl, From edcef7bf2cef8ef0c6bdc0ca1ad7e3b2405cd6fd Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Wed, 18 Mar 2026 10:13:22 +0200 Subject: [PATCH 084/176] dt-bindings: usb: qcom,snps-dwc3: Document the Eliza compatible Document the compatible for the Qualcomm Synopsys DWC3 glue controller found on Eliza SoC. It follows the same binding requirements as other recent Qualcomm SoCs, so add it to the existing schema conditionals covering the required properties. Signed-off-by: Abel Vesa Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260318-eliza-bindings-dwc3-v1-1-92bdf233cb87@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index f879e2e104c4..e67a967c677f 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -24,6 +24,7 @@ properties: compatible: items: - enum: + - qcom,eliza-dwc3 - qcom,glymur-dwc3 - qcom,glymur-dwc3-mp - qcom,ipq4019-dwc3 @@ -346,6 +347,7 @@ allOf: compatible: contains: enum: + - qcom,eliza-dwc3 - qcom,milos-dwc3 - qcom,qcm2290-dwc3 - qcom,qcs615-dwc3 @@ -507,6 +509,7 @@ allOf: compatible: contains: enum: + - qcom,eliza-dwc3 - qcom,ipq4019-dwc3 - qcom,ipq8064-dwc3 - qcom,kaanapali-dwc3 From e44297dd2b75e4929a44cc0242d05f6f83d7c177 Mon Sep 17 00:00:00 2001 From: Alexander Stein Date: Fri, 13 Mar 2026 15:12:19 +0100 Subject: [PATCH 085/176] dt-bindings: usb: ti,usb8041: Support nested USB hubs Onboard USB hubs might be nested. Add the reference for the generic usb-hub.yaml binding and lift the restriction on peer-hub. A (downstream) hub might only be connected on USB High-Speed lines. Signed-off-by: Alexander Stein Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260313141220.1843488-1-alexander.stein@ew.tq-group.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/ti,usb8041.yaml | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/ti,usb8041.yaml b/Documentation/devicetree/bindings/usb/ti,usb8041.yaml index 5e3eae9c2961..07e13fae640b 100644 --- a/Documentation/devicetree/bindings/usb/ti,usb8041.yaml +++ b/Documentation/devicetree/bindings/usb/ti,usb8041.yaml @@ -11,6 +11,7 @@ maintainers: allOf: - $ref: usb-device.yaml# + - $ref: usb-hub.yaml# properties: compatible: @@ -30,17 +31,20 @@ properties: description: VDD power supply to the hub - peer-hub: - $ref: /schemas/types.yaml#/definitions/phandle - description: - phandle to the peer hub on the controller. + peer-hub: true + +patternProperties: + '^.*@[1-9a-f][0-9a-f]*$': + description: The hard wired USB devices + type: object + $ref: /schemas/usb/usb-device.yaml + additionalProperties: true required: - compatible - reg - - peer-hub -additionalProperties: false +unevaluatedProperties: false examples: - | @@ -56,7 +60,14 @@ examples: compatible = "usb451,8142"; reg = <1>; peer-hub = <&hub_3_0>; + #address-cells = <1>; + #size-cells = <0>; reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; + + hub@1 { + compatible = "usb123,4567"; + reg = <1>; + }; }; /* 3.0 hub on port 2 */ From 4f95526e65fadb06595e4e57c68fce97a361d13f Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Tue, 24 Mar 2026 10:23:22 +0100 Subject: [PATCH 086/176] dt-bindings: usb: document the Renesas UPD720201/UPD720202 USB 3.0 xHCI Host Controller Document the Renesas UPD720201/UPD720202 USB 3.0 xHCI Host Controller, which connects over PCIe and requires specific power supplies to start up. Reviewed-by: Rob Herring (Arm) Signed-off-by: Neil Armstrong Link: https://patch.msgid.link/20260324-topic-sm8650-ayaneo-pocket-s2-upd-bindings-v2-1-b86a1543b76b@linaro.org Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/renesas,upd720201-pci.yaml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/renesas,upd720201-pci.yaml diff --git a/Documentation/devicetree/bindings/usb/renesas,upd720201-pci.yaml b/Documentation/devicetree/bindings/usb/renesas,upd720201-pci.yaml new file mode 100644 index 000000000000..4e890d0d2070 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/renesas,upd720201-pci.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/renesas,upd720201-pci.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: UPD720201/UPD720202 USB 3.0 xHCI Host Controller (PCIe) + +maintainers: + - Neil Armstrong + +description: + UPD720201 USB 3.0 xHCI Host Controller via PCIe x1 Gen2 interface. + The UPD720202 supports up to two downstream ports, while UPD720201 + supports up to four downstream USB 3.0 rev1.0 ports. + +properties: + compatible: + enum: + - pci1912,0014 # UPD720201 + - pci1912,0015 # UPD720202 + + reg: + maxItems: 1 + + avdd33-supply: + description: +3.3 V power supply for analog circuit + + vdd10-supply: + description: +1.05 V power supply + + vdd33-supply: + description: +3.3 V power supply + +required: + - compatible + - reg + - avdd33-supply + - vdd10-supply + - vdd33-supply + +allOf: + - $ref: usb-xhci.yaml + +additionalProperties: true + +examples: + - | + pcie@0 { + reg = <0x0 0x1000>; + ranges = <0x02000000 0x0 0x100000 0x10000000 0x0 0x0>; + #address-cells = <3>; + #size-cells = <2>; + device_type = "pci"; + + usb-controller@0 { + compatible = "pci1912,0014"; + reg = <0x0 0x0 0x0 0x0 0x0>; + avdd33-supply = <&avdd33_reg>; + vdd10-supply = <&vdd10_reg>; + vdd33-supply = <&vdd33_reg>; + }; + }; From 7ef80d637e8da8ae6a0a277068ebc6a56451a9b3 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 19 Mar 2026 17:48:48 +0800 Subject: [PATCH 087/176] dt-bindings: usb: nxp,ptn5110: add optional orientation-gpios property The Type-C chip know the cable orientation and then normally will set the switch channel to correctly configure the data path. Some chips itself support to output the control signal by indicating the capability in bit[0] of STANDARD_OUTPUT_CAPABILITIES register and do it in CONFIG_STANDARD_OUTPUT register. For PTN5110 which doesn't present this capability currently there is no way to achieve the orientation setting. Add an optional "orientation-gpios" property to achieve the same purpose. Acked-by: Rob Herring (Arm) Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260319-support-setting-orientation-use-gpio-v4-1-ab6dfa8610c2@nxp.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/nxp,ptn5110.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/nxp,ptn5110.yaml b/Documentation/devicetree/bindings/usb/nxp,ptn5110.yaml index 65a8632b4d9e..581e5916eadd 100644 --- a/Documentation/devicetree/bindings/usb/nxp,ptn5110.yaml +++ b/Documentation/devicetree/bindings/usb/nxp,ptn5110.yaml @@ -26,6 +26,10 @@ properties: $ref: /schemas/connector/usb-connector.yaml# unevaluatedProperties: false + orientation-gpios: + maxItems: 1 + description: Optional orientation select control + required: - compatible - reg From 35a99b690c9e0cf7e2ff70934e7a985839193948 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 19 Mar 2026 17:48:49 +0800 Subject: [PATCH 088/176] usb: typec: tcpci: support setting orientation via GPIO If the chip indicates its "Connection Orientation" standard output control in STANDARD_OUTPUT_CAPABILITIES register, it can do the thing by programming CONFIG_STANDARD_OUTPUT register. Due to the optional feature, the chip which not present this capability currently doesn't have a way to correctly set the data path. This add the support to set orientation via a simple GPIO. Signed-off-by: Xu Yang Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260319-support-setting-orientation-use-gpio-v4-2-ab6dfa8610c2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 8b7e6eb92ca2..0148b8f50412 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -42,6 +43,7 @@ struct tcpci { struct tcpc_dev tcpc; struct tcpci_data *data; + struct gpio_desc *orientation_gpio; }; struct tcpci_chip { @@ -316,6 +318,10 @@ static int tcpci_set_orientation(struct tcpc_dev *tcpc, struct tcpci *tcpci = tcpc_to_tcpci(tcpc); unsigned int reg; + if (tcpci->orientation_gpio) + return gpiod_set_value_cansleep(tcpci->orientation_gpio, + orientation != TYPEC_ORIENTATION_NORMAL); + switch (orientation) { case TYPEC_ORIENTATION_NONE: /* We can't put a single output into high impedance */ @@ -903,6 +909,7 @@ EXPORT_SYMBOL_GPL(tcpci_unregister_port); static int tcpci_probe(struct i2c_client *client) { struct tcpci_chip *chip; + struct gpio_desc *orient_gpio = NULL; int err; u16 val = 0; @@ -931,12 +938,23 @@ static int tcpci_probe(struct i2c_client *client) if (err < 0) return err; + if (err == 0) { + orient_gpio = devm_gpiod_get_optional(&client->dev, "orientation", + GPIOD_OUT_LOW); + if (IS_ERR(orient_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(orient_gpio), + "unable to acquire orientation gpio\n"); + err = !!orient_gpio; + } + chip->data.set_orientation = err; chip->tcpci = tcpci_register_port(&client->dev, &chip->data); if (IS_ERR(chip->tcpci)) return PTR_ERR(chip->tcpci); + chip->tcpci->orientation_gpio = orient_gpio; + err = devm_request_threaded_irq(&client->dev, client->irq, NULL, _tcpci_irq, IRQF_SHARED | IRQF_ONESHOT, From 091ef6f503f49cdb3b19542b23f3cfd3e80a5f11 Mon Sep 17 00:00:00 2001 From: Kexin Sun Date: Sat, 21 Mar 2026 19:00:06 +0800 Subject: [PATCH 089/176] usb: gadget: udc: update outdated comment for renamed usb_gadget_udc_start() The function usb_gadget_udc_start() was renamed to usb_gadget_udc_start_locked() by commit 286d9975a838 ("usb: gadget: udc: core: Prevent soft_connect_store() race"). Update the comment in usb_gadget_udc_set_speed() accordingly. Assisted-by: unnamed:deepseek-v3.2 coccinelle Signed-off-by: Kexin Sun Link: https://patch.msgid.link/20260321110006.8484-1-kexinsun@smail.nju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 08d2a93b3bba..e8861eaad907 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1266,8 +1266,9 @@ static inline void usb_gadget_udc_stop_locked(struct usb_udc *udc) * @speed: The maximum speed to allowed to run * * This call is issued by the UDC Class driver before calling - * usb_gadget_udc_start() in order to make sure that we don't try to - * connect on speeds the gadget driver doesn't support. + * usb_gadget_udc_start_locked() in order to make sure that + * we don't try to connect on speeds the gadget driver + * doesn't support. */ static inline void usb_gadget_udc_set_speed(struct usb_udc *udc, enum usb_device_speed speed) From e7e86965a69d0f6797116e54dda01b56deca71c0 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Fri, 20 Mar 2026 07:15:37 +0000 Subject: [PATCH 090/176] dt-bindings: usb: dwc3: spacemit: add support for K3 SoC Add compatible string for DWC3 USB controller found in SpacemiT K3 SoC. The USB2.0 host controller in K3 SoC actually use DWC3 IP but only support USB2.0 functionality, thus in the hardware layer, it has only one USB2 PHY. While in K1 SoC, the USB controller has both USB2 and USB3 Combo PHY connected, but able to work in a reduced USB2.0 mode which requres only one USB2 PHY, leaves the USB3 Combo PHY to PCIe controller. So both K1 and K3 SoC are able to work in the USB2.0 mode which requires one PHY. Explicitly reduce number of phy property to minimal one. Signed-off-by: Yixun Lan Acked-by: Conor Dooley Link: https://patch.msgid.link/20260320-02-k3-usb20-support-v2-1-308ea0e44038@kernel.org Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/spacemit,k1-dwc3.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/spacemit,k1-dwc3.yaml b/Documentation/devicetree/bindings/usb/spacemit,k1-dwc3.yaml index 0f0b5e061ca1..cc27b363ca79 100644 --- a/Documentation/devicetree/bindings/usb/spacemit,k1-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/spacemit,k1-dwc3.yaml @@ -27,7 +27,9 @@ allOf: properties: compatible: - const: spacemit,k1-dwc3 + enum: + - spacemit,k1-dwc3 + - spacemit,k3-dwc3 reg: maxItems: 1 @@ -42,11 +44,13 @@ properties: maxItems: 1 phys: + minItems: 1 items: - description: phandle to USB2/HS PHY - description: phandle to USB3/SS PHY phy-names: + minItems: 1 items: - const: usb2-phy - const: usb3-phy From c05cf9d274daf72dc7e433480cf2e0e888f6bd89 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Fri, 20 Mar 2026 07:15:38 +0000 Subject: [PATCH 091/176] usb: dwc3: dwc3-generic-plat: spacemit: add support for K3 SoC Add support for the DWC3 USB controller which found in SpacemiT K3 SoC. Acked-by: Thinh Nguyen Signed-off-by: Yixun Lan Link: https://patch.msgid.link/20260320-02-k3-usb20-support-v2-2-308ea0e44038@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-generic-plat.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c index e846844e0023..28219968b8b0 100644 --- a/drivers/usb/dwc3/dwc3-generic-plat.c +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -212,6 +212,7 @@ static const struct dwc3_generic_config eic7700_dwc3 = { static const struct of_device_id dwc3_generic_of_match[] = { { .compatible = "spacemit,k1-dwc3", }, + { .compatible = "spacemit,k3-dwc3", }, { .compatible = "fsl,ls1028a-dwc3", &fsl_ls1028_dwc3}, { .compatible = "eswin,eic7700-dwc3", &eic7700_dwc3}, { /* sentinel */ } From 764c2e6e60bf17910d84e7179fee14129e053b96 Mon Sep 17 00:00:00 2001 From: Chukun Pan Date: Thu, 26 Mar 2026 18:00:10 +0800 Subject: [PATCH 092/176] usb: dwc3: Add optional VBUS regulator support to SpacemiT K1 Some SpacemiT K1 boards (like OrangePi R2S) provide USB VBUS through a controllable regulator. Add support for the optional vbus-supply property so the regulator can be properly managed in host mode instead of left always-on. Note that this doesn't apply to USB Hub downstream ports with different VBUS supplies. The enabled and disabled actions of the regulator are handled automatically by devm_regulator_get_enable_optional(). Signed-off-by: Chukun Pan Acked-by: Thinh Nguyen Reviewed-by: Anand Moon Link: https://patch.msgid.link/20260326100010.3588454-2-amadeus@jmu.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-generic-plat.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c index 28219968b8b0..69b7e6227b3b 100644 --- a/drivers/usb/dwc3/dwc3-generic-plat.c +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include "glue.h" #define EIC7700_HSP_BUS_FILTER_EN BIT(0) @@ -69,6 +71,20 @@ static int dwc3_eic7700_init(struct dwc3_generic *dwc3g) return 0; } +static int dwc3_spacemit_k1_init(struct dwc3_generic *dwc3g) +{ + struct device *dev = dwc3g->dev; + + if (usb_get_dr_mode(dev) == USB_DR_MODE_HOST) { + int ret = devm_regulator_get_enable_optional(dev, "vbus"); + + if (ret && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable VBUS\n"); + } + + return 0; +} + static int dwc3_generic_probe(struct platform_device *pdev) { const struct dwc3_generic_config *plat_config; @@ -201,6 +217,11 @@ static const struct dev_pm_ops dwc3_generic_dev_pm_ops = { dwc3_generic_runtime_idle) }; +static const struct dwc3_generic_config spacemit_k1_dwc3 = { + .init = dwc3_spacemit_k1_init, + .properties = DWC3_DEFAULT_PROPERTIES, +}; + static const struct dwc3_generic_config fsl_ls1028_dwc3 = { .properties.gsbuscfg0_reqinfo = 0x2222, }; @@ -211,7 +232,7 @@ static const struct dwc3_generic_config eic7700_dwc3 = { }; static const struct of_device_id dwc3_generic_of_match[] = { - { .compatible = "spacemit,k1-dwc3", }, + { .compatible = "spacemit,k1-dwc3", &spacemit_k1_dwc3}, { .compatible = "spacemit,k3-dwc3", }, { .compatible = "fsl,ls1028a-dwc3", &fsl_ls1028_dwc3}, { .compatible = "eswin,eic7700-dwc3", &eic7700_dwc3}, From 5bda9c0261bce6a70bd7cb9afa0551c2fdf4ebc1 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Mar 2026 18:32:53 +0400 Subject: [PATCH 093/176] dt-bindings: vendor-prefixes: Add Hynetek Semiconductor Co., Ltd. Hynetek Semiconductor Co., Ltd. focuses on intelligent energy control technology, mainly for the intelligent fast charging and digital energy fields. Link: https://en.hynetek.com/ Acked-by: Conor Dooley Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260318-husb311-v4-1-69e029255430@flipper.net Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index db654fd97b1d..20a39586e28c 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -747,6 +747,8 @@ patternProperties: description: Hycon Technology Corp. "^hydis,.*": description: Hydis Technologies + "^hynetek,.*": + description: Hynetek Semiconductor Co., Ltd. "^hynitron,.*": description: Shanghai Hynitron Microelectronics Co. Ltd. "^hynix,.*": From 8d6efc4a46b6b839cdcdd37313d60335ba5d8309 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Mar 2026 18:32:54 +0400 Subject: [PATCH 094/176] dt-bindings: usb: richtek,rt1711h: Switch ETEK ET7304 to use a fallback compatible As stated in [1], ETEK ET7304 is identical to Richtek RT1715, except for the VID value in its registers, so reflect it in the bindings via a fallback compatible. As there are various TCPCI chips by different vendors reimplementing the registers and behavior of the RT1711H/RT1715, fallback compatibles will scale better. Link: https://lore.kernel.org/all/20260220-et7304-v3-2-ede2d9634957@gmail.com/ [1] Signed-off-by: Alexey Charkov Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260318-husb311-v4-2-69e029255430@flipper.net Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/richtek,rt1711h.yaml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml index 1eb611f35998..210090308e7c 100644 --- a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml +++ b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml @@ -18,10 +18,14 @@ description: | properties: compatible: - enum: - - etekmicro,et7304 - - richtek,rt1711h - - richtek,rt1715 + oneOf: + - enum: + - richtek,rt1711h + - richtek,rt1715 + - items: + - enum: + - etekmicro,et7304 + - const: richtek,rt1715 description: RT1711H support PD20, ET7304 and RT1715 support PD30 except Fast Role Swap. From aa9de45cdba50540412d619de50c666137d946e7 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Mar 2026 18:32:55 +0400 Subject: [PATCH 095/176] dt-bindings: usb: richtek,rt1711h: Add Hynetek HUSB311 HUSB311 is a pin-compatible and register-compatible drop-in replacement for RT1711H, so add its compatible string to the existing binding. Link: https://www.hynetek.com/uploadfiles/site/219/news/0863c0c7-f535-4f09-bacd-0440d2c21088.pdf Link: https://dl.xkwy2018.com/downloads/RK3588S/03_Product%20Line%20Branch_Tablet/02_Key%20Device%20Specifications/HUSB311%20introduction%2020210526.pdf Link: https://www.richtek.com/assets/product_file/RT1711H/DS1711H-04.pdf Signed-off-by: Alexey Charkov Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260318-husb311-v4-3-69e029255430@flipper.net Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml index 210090308e7c..7ded36384518 100644 --- a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml +++ b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml @@ -22,12 +22,17 @@ properties: - enum: - richtek,rt1711h - richtek,rt1715 + - items: + - enum: + - hynetek,husb311 + - const: richtek,rt1711h - items: - enum: - etekmicro,et7304 - const: richtek,rt1715 description: RT1711H support PD20, ET7304 and RT1715 support PD30 except Fast Role Swap. + HUSB311 is a rebrand of RT1711H which is pin and register compatible. reg: maxItems: 1 From b41470c6b4d4177adf8968015514cf240e37d226 Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Wed, 18 Mar 2026 18:32:56 +0400 Subject: [PATCH 096/176] usb: typec: tcpci_rt1711h: Drop unnecessary VID/PID/DID checks Existing checks for VID/PID/DID in the driver are redundant since the driver is already matched to the device via I2C device ID and OF compatible strings, and they preclude the use of fallback compatibles. Remove them to make the driver slimmer and adding new clones easier. Reviewed-by: Heikki Krogerus Signed-off-by: Alexey Charkov Link: https://patch.msgid.link/20260318-husb311-v4-4-69e029255430@flipper.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_rt1711h.c | 59 +------------------------- 1 file changed, 2 insertions(+), 57 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c index 37cf55ad74f8..4b3e4e22a82e 100644 --- a/drivers/usb/typec/tcpm/tcpci_rt1711h.c +++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c @@ -18,13 +18,6 @@ #include #include -#define RT1711H_VID 0x29CF -#define ET7304_VID 0x6DCF -#define RT1711H_PID 0x1711 -#define RT1711H_DID 0x2171 -#define RT1715_DID 0x2173 -#define ET7304_DID 0x2173 - #define RT1711H_PHYCTRL1 0x80 #define RT1711H_PHYCTRL2 0x81 @@ -57,8 +50,6 @@ struct rt1711h_chip_info { u32 rxdz_sel; - u16 vid; - u16 did; bool enable_pd30_extended_message; }; @@ -304,35 +295,6 @@ static int rt1711h_sw_reset(struct rt1711h_chip *chip) return 0; } -static int rt1711h_check_revision(struct i2c_client *i2c, struct rt1711h_chip *chip) -{ - int ret; - - ret = i2c_smbus_read_word_data(i2c, TCPC_VENDOR_ID); - if (ret < 0) - return ret; - if (ret != chip->info->vid) { - dev_err(&i2c->dev, "vid is not correct, 0x%04x\n", ret); - return -ENODEV; - } - ret = i2c_smbus_read_word_data(i2c, TCPC_PRODUCT_ID); - if (ret < 0) - return ret; - if (ret != RT1711H_PID) { - dev_err(&i2c->dev, "pid is not correct, 0x%04x\n", ret); - return -ENODEV; - } - ret = i2c_smbus_read_word_data(i2c, TCPC_BCD_DEV); - if (ret < 0) - return ret; - if (ret != chip->info->did) { - dev_err(&i2c->dev, "did is not correct, 0x%04x\n", ret); - return -ENODEV; - } - dev_dbg(&i2c->dev, "did is 0x%04x\n", ret); - return ret; -} - static int rt1711h_probe(struct i2c_client *client) { int ret; @@ -349,12 +311,6 @@ static int rt1711h_probe(struct i2c_client *client) chip->info = i2c_get_match_data(client); - ret = rt1711h_check_revision(client, chip); - if (ret < 0) { - dev_err(&client->dev, "check vid/pid fail\n"); - return ret; - } - chip->data.regmap = devm_regmap_init_i2c(client, &rt1711h_regmap_config); if (IS_ERR(chip->data.regmap)) @@ -408,27 +364,16 @@ static void rt1711h_remove(struct i2c_client *client) tcpci_unregister_port(chip->tcpci); } -static const struct rt1711h_chip_info et7304 = { - .rxdz_sel = RT1711H_BMCIO_RXDZSEL, - .vid = ET7304_VID, - .did = ET7304_DID, - .enable_pd30_extended_message = true, -}; - static const struct rt1711h_chip_info rt1711h = { - .vid = RT1711H_VID, - .did = RT1711H_DID, }; static const struct rt1711h_chip_info rt1715 = { .rxdz_sel = RT1711H_BMCIO_RXDZSEL, - .vid = RT1711H_VID, - .did = RT1715_DID, .enable_pd30_extended_message = true, }; static const struct i2c_device_id rt1711h_id[] = { - { "et7304", (kernel_ulong_t)&et7304 }, + { "et7304", (kernel_ulong_t)&rt1715 }, { "rt1711h", (kernel_ulong_t)&rt1711h }, { "rt1715", (kernel_ulong_t)&rt1715 }, {} @@ -436,7 +381,7 @@ static const struct i2c_device_id rt1711h_id[] = { MODULE_DEVICE_TABLE(i2c, rt1711h_id); static const struct of_device_id rt1711h_of_match[] = { - { .compatible = "etekmicro,et7304", .data = &et7304 }, + { .compatible = "etekmicro,et7304", .data = &rt1715 }, { .compatible = "richtek,rt1711h", .data = &rt1711h }, { .compatible = "richtek,rt1715", .data = &rt1715 }, {} From 6b9db53197094f38a18797495df2e3c758ec51dc Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 17 Mar 2026 20:30:15 +0400 Subject: [PATCH 097/176] usb: typec: fusb302: Switch to threaded IRQ handler FUSB302 fails to probe with -EINVAL if its interrupt line is connected via an I2C GPIO expander, such as TI TCA6416. Switch the interrupt handler to a threaded one, which also works behind such GPIO expanders. Cc: stable Fixes: 309b6341d557 ("usb: typec: fusb302: Revert incorrect threaded irq fix") Signed-off-by: Alexey Charkov Reviewed-by: Hans de Goede Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260317-fusb302-irq-v2-1-dbabd5c5c961@flipper.net Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/fusb302.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index ce7069fb4be6..889c4c29c1b8 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -1764,8 +1764,9 @@ static int fusb302_probe(struct i2c_client *client) goto destroy_workqueue; } - ret = request_irq(chip->gpio_int_n_irq, fusb302_irq_intn, - IRQF_TRIGGER_LOW, "fsc_interrupt_int_n", chip); + ret = request_threaded_irq(chip->gpio_int_n_irq, NULL, fusb302_irq_intn, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "fsc_interrupt_int_n", chip); if (ret < 0) { dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret); goto tcpm_unregister_port; From fb14e7f7cbb4abbcde5576282d91352deaff2887 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 16 Mar 2026 14:48:30 +0800 Subject: [PATCH 098/176] dt-bindings: usb: cdns,usb3: document USBSSP controller support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the Cadence USBSS DRD binding to document that it also covers the USBSSP (SuperSpeed Plus, USB 3.1 gen2x1) controller. Both USBSS and USBSSP share the same DRD/OTG register interface, so the driver auto-detects the controller version at runtime — no additional compatible string is needed. Changes to the binding: - Update title and add description - maximum-speed: add super-speed-plus This patch is Assisted-by: Cursor:claude-4.6-opus Signed-off-by: Peter Chen Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260316064831.274865-2-peter.chen@cixtech.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/cdns,usb3.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml index a199e5ba6416..2d95fb7321af 100644 --- a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml +++ b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml @@ -4,11 +4,17 @@ $id: http://devicetree.org/schemas/usb/cdns,usb3.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Cadence USBSS-DRD controller +title: Cadence USBSS and USBSSP DRD controller maintainers: - Pawel Laszczak +description: + Cadence USB dual-role controller. Covers USBSS (SuperSpeed, USB 3.0) and + USBSSP (SuperSpeed Plus, USB 3.1 gen2x1). Both variants share the same + DRD/OTG register interface, so the driver auto-detects the controller + version at runtime. + properties: compatible: const: cdns,usb3 @@ -49,7 +55,7 @@ properties: cdns3 to type C connector. maximum-speed: - enum: [super-speed, high-speed, full-speed] + enum: [super-speed-plus, super-speed, high-speed, full-speed] phys: minItems: 1 From 6076388ca1eda808b95f9479f3b04839d348a2f7 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 16 Mar 2026 14:48:31 +0800 Subject: [PATCH 099/176] usb: cdns3: Add USBSSP platform driver support The Cadence USBSSP (CDNSP) controller was previously only accessible through PCI, coupling the gadget driver with the PCI glue layer into a single monolithic module (cdnsp-udc-pci). This prevented using the CDNSP IP on SoC/platform designs that expose the controller through device tree. It restructures the driver to decouple the CDNSP gadget from PCI. - Introduce CONFIG_USB_CDNSP as a standalone tristate (analogous to CONFIG_USB_CDNS3), with USB_CDNSP_GADGET and USB_CDNSP_HOST as bool sub-options. The gadget code builds as a separate cdnsp.ko module. - Regroup USBSSP and CDNS3 Kconfig options under the USB_CDNS_SUPPORT menu so they appear properly grouped in menuconfig. - Refactor cdnsp-pci.c into a thin PCI-to-platform wrapper (similar to cdns3-pci-wrap.c) that registers a platform device and passes PCI resources and platform data to the common platform driver. - Auto-detect the controller version (USBSS vs USBSSP) at runtime by reading the DRD/OTG Device ID register in cdns_drd_init(), and select the appropriate gadget init function (cdns3_gadget_init or cdnsp_gadget_init) based on cdns->version. This follows the same pattern already used for host initialization. - Fix gadget-export.h to use IS_REACHABLE() keyed on the tristate module config (CONFIG_USB_CDNS3/CONFIG_USB_CDNSP) instead of IS_ENABLED() on the bool gadget config. The bool configs are always 'y' when enabled, causing IS_ENABLED/IS_REACHABLE to always return true and resulting in link errors when cdns-usb-common is built-in but the gadget module is loadable. - Add missing MODULE_LICENSE()/MODULE_DESCRIPTION() and EXPORT_SYMBOL_GPL() to the cdns3 and cdnsp gadget modules, required by modpost. - Pass override_apb_timeout through cdns3_platform_data so the PCI wrapper can communicate PCI-specific APB timeout values to the common driver. This patch is Assisted-by: Cursor:claude-4.6-opus Signed-off-by: Peter Chen Acked-by: Pawel Laszczak Link: https://patch.msgid.link/20260316064831.274865-3-peter.chen@cixtech.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/Kconfig | 50 +++---- drivers/usb/cdns3/Makefile | 30 ++--- drivers/usb/cdns3/cdns3-gadget.c | 4 + drivers/usb/cdns3/cdns3-plat.c | 17 ++- drivers/usb/cdns3/cdnsp-gadget.c | 4 + drivers/usb/cdns3/cdnsp-pci.c | 209 +++++++++++++----------------- drivers/usb/cdns3/core.c | 11 +- drivers/usb/cdns3/core.h | 5 +- drivers/usb/cdns3/gadget-export.h | 4 +- 9 files changed, 160 insertions(+), 174 deletions(-) diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig index 0a514b591527..97fa84dddbca 100644 --- a/drivers/usb/cdns3/Kconfig +++ b/drivers/usb/cdns3/Kconfig @@ -20,10 +20,6 @@ config USB_CDNS3 Say Y here if your system has a Cadence USB3 dual-role controller. It supports: dual-role switch, Host-only, and Peripheral-only. - If you choose to build this driver is a dynamically linked - as module, the module will be called cdns3.ko. -endif - if USB_CDNS3 config USB_CDNS3_GADGET @@ -89,29 +85,27 @@ config USB_CDNS3_STARFIVE If you choose to build this driver as module it will be dynamically linked and module will be called cdns3-starfive.ko -endif -if USB_CDNS_SUPPORT +endif # USB_CDNS3 -config USB_CDNSP_PCI - tristate "Cadence CDNSP Dual-Role Controller" - depends on USB_CDNS_SUPPORT && USB_PCI && ACPI +config USB_CDNSP + tristate "Cadence USBSSP Dual-Role Controller" + depends on USB_CDNS_SUPPORT help - Say Y here if your system has a Cadence CDNSP dual-role controller. - It supports: dual-role switch Host-only, and Peripheral-only. + Say Y here if your system has a Cadence USBSSP dual-role controller. + It supports: dual-role switch, Host-only, and Peripheral-only. + Cadence CDNSP Controller device mode is very similar to XHCI controller. + Therefore some algorithms used has been taken from xHCI driver. + Host controller is compliant with XHCI so it uses standard XHCI driver. - If you choose to build this driver is a dynamically linked - module, the module will be called cdnsp.ko. -endif - -if USB_CDNSP_PCI +if USB_CDNSP config USB_CDNSP_GADGET - bool "Cadence CDNSP device controller" - depends on USB_GADGET=y || USB_GADGET=USB_CDNSP_PCI + bool "Cadence USBSSP device controller" + depends on USB_GADGET=y || USB_GADGET=USB_CDNSP help Say Y here to enable device controller functionality of the - Cadence CDNSP-DEV driver. + Cadence USBSSP-DEV driver. Cadence CDNSP Device Controller in device mode is very similar to XHCI controller. Therefore some algorithms @@ -120,8 +114,8 @@ config USB_CDNSP_GADGET It doesn't support LS. config USB_CDNSP_HOST - bool "Cadence CDNSP host controller" - depends on USB=y || USB=USB_CDNSP_PCI + bool "Cadence USBSSP host controller" + depends on USB=y || USB=USB_CDNSP select USB_CDNS_HOST help Say Y here to enable host controller functionality of the @@ -130,4 +124,16 @@ config USB_CDNSP_HOST Host controller is compliant with XHCI so it uses standard XHCI driver. -endif +config USB_CDNSP_PCI + tristate "Cadence USBSSP support on PCIe-based platforms" + depends on USB_PCI && ACPI + help + If you're using the USBSSP Core IP with a PCIe, please say + 'Y' or 'M' here. + + If you choose to build this driver as module it will + be dynamically linked and module will be called cdnsp-pci.ko + +endif # USB_CDNSP + +endif # USB_CDNS_SUPPORT diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index 48dfae75b5aa..63484f145bb9 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -4,41 +4,33 @@ CFLAGS_cdns3-trace.o := -I$(src) CFLAGS_cdnsp-trace.o := -I$(src) cdns-usb-common-y := core.o drd.o -cdns3-y := cdns3-plat.o ifeq ($(CONFIG_USB),m) obj-m += cdns-usb-common.o -obj-m += cdns3.o +obj-m += cdns3-plat.o else obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns-usb-common.o -obj-$(CONFIG_USB_CDNS3) += cdns3.o +obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns3-plat.o endif cdns-usb-common-$(CONFIG_USB_CDNS_HOST) += host.o -cdns3-$(CONFIG_USB_CDNS3_GADGET) += cdns3-gadget.o cdns3-ep0.o +# For CDNS3 gadget ifneq ($(CONFIG_USB_CDNS3_GADGET),) +cdns3-y := cdns3-gadget.o cdns3-ep0.o cdns3-$(CONFIG_TRACING) += cdns3-trace.o +obj-$(CONFIG_USB_CDNS3) += cdns3.o endif - obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o obj-$(CONFIG_USB_CDNS3_STARFIVE) += cdns3-starfive.o -cdnsp-udc-pci-y := cdnsp-pci.o - -ifdef CONFIG_USB_CDNSP_PCI -ifeq ($(CONFIG_USB),m) -obj-m += cdnsp-udc-pci.o -else -obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-udc-pci.o -endif -endif - -cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET) += cdnsp-ring.o cdnsp-gadget.o \ - cdnsp-mem.o cdnsp-ep0.o - +# For CDNSP gadget ifneq ($(CONFIG_USB_CDNSP_GADGET),) -cdnsp-udc-pci-$(CONFIG_TRACING) += cdnsp-trace.o +cdnsp-y := cdnsp-ring.o cdnsp-gadget.o \ + cdnsp-mem.o cdnsp-ep0.o +cdnsp-$(CONFIG_TRACING) += cdnsp-trace.o +obj-$(CONFIG_USB_CDNSP) += cdnsp.o endif +obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-pci.o diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index d59a60a16ec7..b800bd1bedd4 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -3508,3 +3508,7 @@ int cdns3_gadget_init(struct cdns *cdns) return 0; } +EXPORT_SYMBOL_GPL(cdns3_gadget_init); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cadence USBSS DRD Driver - gadget"); diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index 735df88774e4..71c612e27b73 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -44,6 +44,14 @@ static void set_phy_power_off(struct cdns *cdns) phy_power_off(cdns->usb2_phy); } +static int cdns3_plat_gadget_init(struct cdns *cdns) +{ + if (cdns->version < CDNSP_CONTROLLER_V2) + return cdns3_gadget_init(cdns); + else + return cdnsp_gadget_init(cdns); +} + /** * cdns3_plat_probe - probe for cdns3 core device * @pdev: Pointer to cdns3 core platform device @@ -64,6 +72,8 @@ static int cdns3_plat_probe(struct platform_device *pdev) cdns->dev = dev; cdns->pdata = dev_get_platdata(dev); + if (cdns->pdata && cdns->pdata->override_apb_timeout) + cdns->override_apb_timeout = cdns->pdata->override_apb_timeout; platform_set_drvdata(pdev, cdns); @@ -143,12 +153,15 @@ static int cdns3_plat_probe(struct platform_device *pdev) if (ret) goto err_phy_power_on; - cdns->gadget_init = cdns3_gadget_init; - ret = cdns_init(cdns); if (ret) goto err_cdns_init; + cdns->gadget_init = cdns3_plat_gadget_init; + ret = cdns_core_init_role(cdns); + if (ret) + goto err_cdns_init; + device_set_wakeup_capable(dev, true); pm_runtime_set_active(dev); pm_runtime_enable(dev); diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 6b3815f8a6e5..8db7eee528a1 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -2075,3 +2075,7 @@ int cdnsp_gadget_init(struct cdns *cdns) return 0; } +EXPORT_SYMBOL_GPL(cdnsp_gadget_init); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cadence CDNSP DRD Driver - gadget"); diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c index 566d94e49102..432007cfe695 100644 --- a/drivers/usb/cdns3/cdnsp-pci.c +++ b/drivers/usb/cdns3/cdnsp-pci.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Cadence PCI Glue driver. + * Cadence USBSSP PCI Glue driver. * * Copyright (C) 2019 Cadence. * @@ -16,7 +16,19 @@ #include #include "core.h" -#include "gadget-export.h" + +struct cdnsp_wrap { + struct platform_device *plat_dev; + struct resource dev_res[6]; + int devfn; +}; + +#define RES_IRQ_HOST_ID 0 +#define RES_IRQ_PERIPHERAL_ID 1 +#define RES_IRQ_OTG_ID 2 +#define RES_HOST_ID 3 +#define RES_DEV_ID 4 +#define RES_DRD_ID 5 #define PCI_BAR_HOST 0 #define PCI_BAR_OTG 0 @@ -26,16 +38,16 @@ #define PCI_DEV_FN_OTG 1 #define PCI_DRIVER_NAME "cdns-pci-usbssp" -#define PLAT_DRIVER_NAME "cdns-usbssp" +#define PLAT_DRIVER_NAME "cdns-usb3" -#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20 +#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20 static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev) { /* * Gets the second function. - * Platform has two function. The fist keeps resources for - * Host/Device while the secon keeps resources for DRD/OTG. + * Platform has two function. The first keeps resources for + * Host/Device while the second keeps resources for DRD/OTG. */ if (pdev->device == PCI_DEVICE_ID_CDNS_USBSSP) return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSS, NULL); @@ -48,11 +60,12 @@ static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev) static int cdnsp_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - struct device *dev = &pdev->dev; - struct pci_dev *func; + struct platform_device_info plat_info; + static struct cdns3_platform_data pdata; + struct cdnsp_wrap *wrap; struct resource *res; - struct cdns *cdnsp; - int ret; + struct pci_dev *func; + int ret = 0; /* * For GADGET/HOST PCI (devfn) function number is 0, @@ -79,146 +92,105 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, } pci_set_master(pdev); + if (pci_is_enabled(func)) { - cdnsp = pci_get_drvdata(func); + wrap = pci_get_drvdata(func); } else { - cdnsp = kzalloc_obj(*cdnsp); - if (!cdnsp) { + wrap = kzalloc_obj(*wrap); + if (!wrap) { ret = -ENOMEM; goto put_pci; } } - /* For GADGET device function number is 0. */ - if (pdev->devfn == 0) { - resource_size_t rsrc_start, rsrc_len; + res = wrap->dev_res; - /* Function 0: host(BAR_0) + device(BAR_1).*/ - dev_dbg(dev, "Initialize resources\n"); - rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV); - rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV); - res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev"); - if (!res) { - dev_dbg(dev, "controller already in use\n"); - ret = -EBUSY; - goto free_cdnsp; - } + if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) { + /* Function 0: host(BAR_0) + device(BAR_2). */ + dev_dbg(&pdev->dev, "Initialize Device resources\n"); + res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV); + res[RES_DEV_ID].end = pci_resource_end(pdev, PCI_BAR_DEV); + res[RES_DEV_ID].name = "dev"; + res[RES_DEV_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "USBSSP-DEV physical base addr: %pa\n", + &res[RES_DEV_ID].start); - cdnsp->dev_regs = devm_ioremap(dev, rsrc_start, rsrc_len); - if (!cdnsp->dev_regs) { - dev_dbg(dev, "error mapping memory\n"); - ret = -EFAULT; - goto free_cdnsp; - } + res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST); + res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST); + res[RES_HOST_ID].name = "xhci"; + res[RES_HOST_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "USBSSP-XHCI physical base addr: %pa\n", + &res[RES_HOST_ID].start); - cdnsp->dev_irq = pdev->irq; - dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n", - &rsrc_start); + /* Interrupt for XHCI */ + wrap->dev_res[RES_IRQ_HOST_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_HOST_ID].name = "host"; + wrap->dev_res[RES_IRQ_HOST_ID].flags = IORESOURCE_IRQ; - res = &cdnsp->xhci_res[0]; - res->start = pci_resource_start(pdev, PCI_BAR_HOST); - res->end = pci_resource_end(pdev, PCI_BAR_HOST); - res->name = "xhci"; - res->flags = IORESOURCE_MEM; - dev_dbg(dev, "USBSS-XHCI physical base addr: %pa\n", - &res->start); - - /* Interrupt for XHCI, */ - res = &cdnsp->xhci_res[1]; - res->start = pdev->irq; - res->name = "host"; - res->flags = IORESOURCE_IRQ; + /* Interrupt for device. It's the same as for HOST. */ + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].name = "peripheral"; + wrap->dev_res[RES_IRQ_PERIPHERAL_ID].flags = IORESOURCE_IRQ; } else { - res = &cdnsp->otg_res; - res->start = pci_resource_start(pdev, PCI_BAR_OTG); - res->end = pci_resource_end(pdev, PCI_BAR_OTG); - res->name = "otg"; - res->flags = IORESOURCE_MEM; - dev_dbg(dev, "CDNSP-DRD physical base addr: %pa\n", - &res->start); + res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG); + res[RES_DRD_ID].end = pci_resource_end(pdev, PCI_BAR_OTG); + res[RES_DRD_ID].name = "otg"; + res[RES_DRD_ID].flags = IORESOURCE_MEM; + dev_dbg(&pdev->dev, "CDNSP-DRD physical base addr: %pa\n", + &res[RES_DRD_ID].start); /* Interrupt for OTG/DRD. */ - cdnsp->otg_irq = pdev->irq; + wrap->dev_res[RES_IRQ_OTG_ID].start = pdev->irq; + wrap->dev_res[RES_IRQ_OTG_ID].name = "otg"; + wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ; } - /* - * Cadence PCI based platform require some longer timeout for APB - * to fixes domain clock synchronization issue after resuming - * controller from L1 state. - */ - cdnsp->override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE; - pci_set_drvdata(pdev, cdnsp); - if (pci_is_enabled(func)) { - cdnsp->dev = dev; - cdnsp->gadget_init = cdnsp_gadget_init; - - ret = cdns_init(cdnsp); - if (ret) - goto free_cdnsp; + /* set up platform device info */ + pdata.override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE; + memset(&plat_info, 0, sizeof(plat_info)); + plat_info.parent = &pdev->dev; + plat_info.fwnode = pdev->dev.fwnode; + plat_info.name = PLAT_DRIVER_NAME; + plat_info.id = pdev->devfn; + plat_info.res = wrap->dev_res; + plat_info.num_res = ARRAY_SIZE(wrap->dev_res); + plat_info.dma_mask = pdev->dma_mask; + plat_info.data = &pdata; + plat_info.size_data = sizeof(pdata); + wrap->devfn = pdev->devfn; + /* register platform device */ + wrap->plat_dev = platform_device_register_full(&plat_info); + if (IS_ERR(wrap->plat_dev)) { + ret = PTR_ERR(wrap->plat_dev); + kfree(wrap); + goto put_pci; + } } - device_wakeup_enable(&pdev->dev); - if (pci_dev_run_wake(pdev)) - pm_runtime_put_noidle(&pdev->dev); - - return 0; - -free_cdnsp: - if (!pci_is_enabled(func)) - kfree(cdnsp); - + pci_set_drvdata(pdev, wrap); put_pci: pci_dev_put(func); - return ret; } static void cdnsp_pci_remove(struct pci_dev *pdev) { - struct cdns *cdnsp; + struct cdnsp_wrap *wrap; struct pci_dev *func; func = cdnsp_get_second_fun(pdev); - cdnsp = (struct cdns *)pci_get_drvdata(pdev); + wrap = pci_get_drvdata(pdev); - if (pci_dev_run_wake(pdev)) - pm_runtime_get_noresume(&pdev->dev); + if (wrap->devfn == pdev->devfn) + platform_device_unregister(wrap->plat_dev); - if (pci_is_enabled(func)) { - cdns_remove(cdnsp); - } else { - kfree(cdnsp); - } + if (!pci_is_enabled(func)) + kfree(wrap); pci_dev_put(func); } -static int __maybe_unused cdnsp_pci_suspend(struct device *dev) -{ - struct cdns *cdns = dev_get_drvdata(dev); - - return cdns_suspend(cdns); -} - -static int __maybe_unused cdnsp_pci_resume(struct device *dev) -{ - struct cdns *cdns = dev_get_drvdata(dev); - unsigned long flags; - int ret; - - spin_lock_irqsave(&cdns->lock, flags); - ret = cdns_resume(cdns); - spin_unlock_irqrestore(&cdns->lock, flags); - cdns_set_active(cdns, 1); - - return ret; -} - -static const struct dev_pm_ops cdnsp_pci_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(cdnsp_pci_suspend, cdnsp_pci_resume) -}; - static const struct pci_device_id cdnsp_pci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), .class = PCI_CLASS_SERIAL_USB_DEVICE }, @@ -230,13 +202,10 @@ static const struct pci_device_id cdnsp_pci_ids[] = { }; static struct pci_driver cdnsp_pci_driver = { - .name = "cdnsp-pci", + .name = PCI_DRIVER_NAME, .id_table = cdnsp_pci_ids, .probe = cdnsp_pci_probe, .remove = cdnsp_pci_remove, - .driver = { - .pm = &cdnsp_pci_pm_ops, - } }; module_pci_driver(cdnsp_pci_driver); @@ -245,4 +214,4 @@ MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids); MODULE_ALIAS("pci:cdnsp"); MODULE_AUTHOR("Pawel Laszczak "); MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Cadence CDNSP PCI driver"); +MODULE_DESCRIPTION("Cadence CDNSP PCI wrapper"); diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index f0e32227c0b7..10f00b6c3c83 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -80,7 +80,7 @@ static void cdns_exit_roles(struct cdns *cdns) * * Returns 0 on success otherwise negative errno */ -static int cdns_core_init_role(struct cdns *cdns) +int cdns_core_init_role(struct cdns *cdns) { struct device *dev = cdns->dev; enum usb_dr_mode best_dr_mode; @@ -197,11 +197,14 @@ static int cdns_core_init_role(struct cdns *cdns) goto err; } + dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); + return 0; err: cdns_exit_roles(cdns); return ret; } +EXPORT_SYMBOL_GPL(cdns_core_init_role); /** * cdns_hw_role_state_machine - role switch state machine based on hw events. @@ -469,14 +472,8 @@ int cdns_init(struct cdns *cdns) if (ret) goto init_failed; - ret = cdns_core_init_role(cdns); - if (ret) - goto init_failed; - spin_lock_init(&cdns->lock); - dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); - return 0; init_failed: cdns_drd_exit(cdns); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 801be9e61340..dc8c4137de15 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -45,6 +45,7 @@ struct cdns3_platform_data { unsigned long quirks; #define CDNS3_DEFAULT_PM_RUNTIME_ALLOW BIT(0) #define CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE BIT(1) + u32 override_apb_timeout; /* 0 = use default (e.g. for PCI) */ }; /** @@ -119,14 +120,14 @@ struct cdns { struct cdns3_platform_data *pdata; spinlock_t lock; struct xhci_plat_priv *xhci_plat_data; - u32 override_apb_timeout; - int (*gadget_init)(struct cdns *cdns); + u32 override_apb_timeout; }; int cdns_hw_role_switch(struct cdns *cdns); int cdns_init(struct cdns *cdns); int cdns_remove(struct cdns *cdns); +int cdns_core_init_role(struct cdns *cdns); #ifdef CONFIG_PM_SLEEP int cdns_resume(struct cdns *cdns); diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h index c37b6269b001..0cb600e2b5d2 100644 --- a/drivers/usb/cdns3/gadget-export.h +++ b/drivers/usb/cdns3/gadget-export.h @@ -10,7 +10,7 @@ #ifndef __LINUX_CDNS3_GADGET_EXPORT #define __LINUX_CDNS3_GADGET_EXPORT -#if IS_ENABLED(CONFIG_USB_CDNSP_GADGET) +#if defined(CONFIG_USB_CDNSP_GADGET) && IS_REACHABLE(CONFIG_USB_CDNSP) int cdnsp_gadget_init(struct cdns *cdns); #else @@ -22,7 +22,7 @@ static inline int cdnsp_gadget_init(struct cdns *cdns) #endif /* CONFIG_USB_CDNSP_GADGET */ -#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET) +#if defined(CONFIG_USB_CDNS3_GADGET) && IS_REACHABLE(CONFIG_USB_CDNS3) int cdns3_gadget_init(struct cdns *cdns); #else From 2a6bfe9e46eebc17f7bf734c0232e173aae4f86b Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 23 Mar 2026 09:54:12 +0100 Subject: [PATCH 100/176] dt-bindings: usb: qcom,snps-dwc3: Drop stale child node comment After moving the binding to style with combined wrapper+device (so one node) there is no child node required. Drop the stale comment about it. Signed-off-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260323-dt-bindings-snps-qcom-dwc3-cleanup-v2-1-3bcd37c0a5b5@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index e67a967c677f..14d721857ffc 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -154,8 +154,6 @@ properties: wakeup-source: true -# Required child node: - required: - compatible - reg From 2c0471192910263d4e6dc964f307c915f56d7a3a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 23 Mar 2026 09:54:13 +0100 Subject: [PATCH 101/176] dt-bindings: usb: qcom,snps-dwc3: Add missing clocks and interrupts constraints The top-level part defines variable number of clocks and interrupts, and each "if:then:" block narrows them. It however narrows only the maxItems leaving minItems undefined, which then takes different values depending on dtschema being used. Recommended style is to avoid ambiguity in such case, thus if top-level part has broad constraints, then each "if:then:" must specify both upper and lower limits. Add missing constraints, mostly minItems but also maxItems for one variant. Signed-off-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260323-dt-bindings-snps-qcom-dwc3-cleanup-v2-2-3bcd37c0a5b5@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index 14d721857ffc..7dca8be66ef1 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -174,6 +174,7 @@ allOf: then: properties: clocks: + minItems: 3 maxItems: 3 clock-names: items: @@ -221,6 +222,7 @@ allOf: then: properties: clocks: + minItems: 5 maxItems: 5 clock-names: items: @@ -263,6 +265,7 @@ allOf: then: properties: clocks: + minItems: 4 maxItems: 4 clock-names: items: @@ -282,6 +285,7 @@ allOf: then: properties: clocks: + minItems: 4 maxItems: 4 clock-names: items: @@ -302,6 +306,7 @@ allOf: then: properties: clocks: + minItems: 9 maxItems: 9 clock-names: items: @@ -363,6 +368,7 @@ allOf: properties: clocks: minItems: 6 + maxItems: 6 clock-names: items: - const: cfg_noc @@ -404,6 +410,7 @@ allOf: then: properties: clocks: + minItems: 7 maxItems: 7 clock-names: items: @@ -472,6 +479,7 @@ allOf: then: properties: interrupts: + minItems: 4 maxItems: 4 interrupt-names: items: From eb06a0a15771c4b4a1ce80396e9bdbee0374855c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 23 Mar 2026 09:54:14 +0100 Subject: [PATCH 102/176] dt-bindings: usb: qcom,snps-dwc3: Add constraints for SM6375 The qcom,sm6375-dwc3 is already documented in top level part, but it misses specific constraints for clocks and interrupts. Closes: https://sashiko.dev/#/patchset/20260319092348.35237-2-krzysztof.kozlowski%40oss.qualcomm.com Signed-off-by: Krzysztof Kozlowski Reviewed-by: Konrad Dybcio Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260323-dt-bindings-snps-qcom-dwc3-cleanup-v2-3-3bcd37c0a5b5@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index 7dca8be66ef1..d4746c3edb99 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -359,6 +359,7 @@ allOf: - qcom,sc8180x-dwc3-mp - qcom,sm6115-dwc3 - qcom,sm6125-dwc3 + - qcom,sm6375-dwc3 - qcom,sm8150-dwc3 - qcom,sm8250-dwc3 - qcom,sm8450-dwc3 @@ -534,6 +535,7 @@ allOf: - qcom,sdx75-dwc3 - qcom,sm4250-dwc3 - qcom,sm6350-dwc3 + - qcom,sm6375-dwc3 - qcom,sm8150-dwc3 - qcom,sm8250-dwc3 - qcom,sm8350-dwc3 From 2bd012e02be7b136b0198a0c7be6c49585d6ca13 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 23 Mar 2026 09:54:15 +0100 Subject: [PATCH 103/176] dt-bindings: usb: qcom,snps-dwc3: Add constraints for SM4250 The qcom,sm4250-dwc3 is already documented in top level part, but it misses specific constraints for clocks. The SoC is derivative of SM6115 (or vice versa), so the interrupts part is incorrectly placed and should be same as for SM6115. Closes: https://sashiko.dev/#/patchset/20260319092348.35237-2-krzysztof.kozlowski%40oss.qualcomm.com Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260323-dt-bindings-snps-qcom-dwc3-cleanup-v2-4-3bcd37c0a5b5@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index d4746c3edb99..e81363600735 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -357,6 +357,7 @@ allOf: - qcom,sar2130p-dwc3 - qcom,sc8180x-dwc3 - qcom,sc8180x-dwc3-mp + - qcom,sm4250-dwc3 - qcom,sm6115-dwc3 - qcom,sm6125-dwc3 - qcom,sm6375-dwc3 @@ -454,6 +455,7 @@ allOf: - qcom,msm8996-dwc3 - qcom,qcs404-dwc3 - qcom,sdm660-dwc3 + - qcom,sm4250-dwc3 - qcom,sm6115-dwc3 - qcom,sm6125-dwc3 then: @@ -533,7 +535,6 @@ allOf: - qcom,sdx55-dwc3 - qcom,sdx65-dwc3 - qcom,sdx75-dwc3 - - qcom,sm4250-dwc3 - qcom,sm6350-dwc3 - qcom,sm6375-dwc3 - qcom,sm8150-dwc3 From 5b99bcbae2d3bea8dc785593ebf7246854f7b90f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 23 Mar 2026 09:54:16 +0100 Subject: [PATCH 104/176] dt-bindings: usb: qcom,snps-dwc3: Add constraints for IPQ5424 and IPQ9574 The qcom,ipq5424-dwc3 and qcom,ipq9574-dwc3 are already documented in top level part, but they miss specific constraints for clocks (IPQ5424) and interrupts (both). Closes: https://sashiko.dev/#/patchset/20260319092348.35237-2-krzysztof.kozlowski%40oss.qualcomm.com Signed-off-by: Krzysztof Kozlowski Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260323-dt-bindings-snps-qcom-dwc3-cleanup-v2-5-3bcd37c0a5b5@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/qcom,snps-dwc3.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index e81363600735..8201656b41ed 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -203,6 +203,7 @@ allOf: compatible: contains: enum: + - qcom,ipq5424-dwc3 - qcom,ipq9574-dwc3 - qcom,kaanapali-dwc3 - qcom,msm8953-dwc3 @@ -491,6 +492,26 @@ allOf: - const: dp_hs_phy_irq - const: dm_hs_phy_irq + - if: + properties: + compatible: + contains: + enum: + - qcom,ipq5424-dwc3 + - qcom,ipq9574-dwc3 + then: + properties: + interrupts: + minItems: 5 + maxItems: 5 + interrupt-names: + items: + - const: dwc_usb3 + - const: pwr_event + - const: qusb2_phy + - const: dp_hs_phy_irq + - const: dm_hs_phy_irq + - if: properties: compatible: From e972256f256c5ae908e15e2c6880f9144fbcae93 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Thu, 19 Mar 2026 07:51:03 +0000 Subject: [PATCH 105/176] dt-bindings: usb: Add support for Terminus FE1.1s USB2.0 Hub controller Terminus FE1.1s is USB2.0 protocol compliant 4-port USB HUB, It support MTT (Multiple Transaction Translator) mode, the upstream port supports high-speed 480MHz and full-speed 12MHz modes, also has integrated 5V to 3.3V, 1.8V regulator and Power-On-Reset circuit. Introduce the DT binding for it. Link: https://terminus-usa.com/wp-content/uploads/2024/06/FE1.1s-Product-Brief-Rev.-2.0-2023.pdf [1] Signed-off-by: Yixun Lan Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260319-03-usb-hub-fe1-v2-1-e4e26809dd7d@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/terminus,fe11.yaml | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/terminus,fe11.yaml diff --git a/Documentation/devicetree/bindings/usb/terminus,fe11.yaml b/Documentation/devicetree/bindings/usb/terminus,fe11.yaml new file mode 100644 index 000000000000..645f97d73807 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/terminus,fe11.yaml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/terminus,fe11.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Terminus FE1.1/1.1S USB 2.0 Hub Controller + +maintainers: + - Yixun Lan + +allOf: + - $ref: usb-hub.yaml# + +properties: + compatible: + enum: + - usb1a40,0101 + + reg: true + + reset-gpios: + description: + GPIO controlling the RESET#. + + vdd-supply: + description: + Regulator supply to the hub, one of 3.3V or 5V can be chosen. + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + patternProperties: + '^port@': + $ref: /schemas/graph.yaml#/properties/port + + properties: + reg: + minimum: 1 + maximum: 4 + +required: + - compatible + - reg + - vdd-supply + +unevaluatedProperties: false + +examples: + - | + #include + usb { + #address-cells = <1>; + #size-cells = <0>; + + hub@1 { + compatible = "usb1a40,0101"; + reg = <1>; + reset-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>; + vdd-supply = <&vcc_5v>; + }; + }; From 00b4fe5be06aecd6426930de86b7cffc2330f4b8 Mon Sep 17 00:00:00 2001 From: Yixun Lan Date: Thu, 19 Mar 2026 07:51:04 +0000 Subject: [PATCH 106/176] usb: misc: onboard_usb_dev: Add Terminus FE1.1s USB2.0 Hub (1a40:0101) Terminus FE1.1s is USB2.0 protocol compliant 4-port USB HUB, It support MTT (Multiple Transaction Translator) mode, the upstream port supports high-speed 480MHz and full-speed 12MHz modes, also it has integrated 5V to 3.3V/1.8V regulator and Power-On-Reset circuit. Link: https://terminus-usa.com/wp-content/uploads/2024/06/FE1.1s-Product-Brief-Rev.-2.0-2023.pdf [1] Signed-off-by: Yixun Lan Link: https://patch.msgid.link/20260319-03-usb-hub-fe1-v2-2-e4e26809dd7d@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/onboard_usb_dev.c | 2 ++ drivers/usb/misc/onboard_usb_dev.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index 6dd73f23e9be..7cdbdfe07a76 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -571,6 +571,7 @@ static struct platform_driver onboard_dev_driver = { #define VENDOR_ID_MICROCHIP 0x0424 #define VENDOR_ID_PARADE 0x1da0 #define VENDOR_ID_REALTEK 0x0bda +#define VENDOR_ID_TERMINUS 0x1a40 #define VENDOR_ID_TI 0x0451 #define VENDOR_ID_VIA 0x2109 #define VENDOR_ID_XMOS 0x20B1 @@ -676,6 +677,7 @@ static const struct usb_device_id onboard_dev_id_table[] = { { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 HUB */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 HUB */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0179) }, /* RTL8188ETV 2.4GHz WiFi */ + { USB_DEVICE(VENDOR_ID_TERMINUS, 0x0101) }, /* Terminus FE1.1s 2.0 HUB */ { USB_DEVICE(VENDOR_ID_TI, 0x8025) }, /* TI USB8020B 3.0 HUB */ { USB_DEVICE(VENDOR_ID_TI, 0x8027) }, /* TI USB8020B 2.0 HUB */ { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 HUB */ diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h index 56cb5b162141..ac1aa3e122ad 100644 --- a/drivers/usb/misc/onboard_usb_dev.h +++ b/drivers/usb/misc/onboard_usb_dev.h @@ -152,6 +152,7 @@ static const struct of_device_id onboard_dev_match[] = { { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, }, + { .compatible = "usb1a40,0101", .data = &vialab_vl817_data, }, { .compatible = "usb1a86,8091", .data = &wch_ch334_data, }, { .compatible = "usb1da0,5511", .data = ¶de_ps5511_data, }, { .compatible = "usb1da0,55a1", .data = ¶de_ps5511_data, }, From ee90b493e0a04605b8976bf085dacc19d38e1e8f Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 19 Mar 2026 15:46:23 +0100 Subject: [PATCH 107/176] usb: uapi: add usb 3.0 authentication declarations This adds the USB authentication extensions to the uapi chapter 9 declarations, so that user space tools correctly operate on the descriptor and commands. This is necessary for sniffing and debugging in gadget mode to correctly work, even though the kernel does not use these requests in host mode. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260319144715.2957358-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/usb/ch9.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h index 8003243a4937..62771e38a83d 100644 --- a/include/uapi/linux/usb/ch9.h +++ b/include/uapi/linux/usb/ch9.h @@ -102,6 +102,8 @@ #define USB_REQ_LOOPBACK_DATA_WRITE 0x15 #define USB_REQ_LOOPBACK_DATA_READ 0x16 #define USB_REQ_SET_INTERFACE_DS 0x17 +#define USB_REQ_AUTH_IN 0x18 +#define USB_REQ_AUTH_OUT 0x19 /* specific requests for USB Power Delivery */ #define USB_REQ_GET_PARTNER_PDO 20 @@ -1147,6 +1149,17 @@ struct usb_ptm_cap_descriptor { /*-------------------------------------------------------------------------*/ +struct usb_authentication_capability_descriptor { + __u8 bLength; + __u8 bDescriptorType; /* set to USB_DT_DEVICE_CAPABILITY */ + __u8 bmAttributes; + + __u8 bcdProtocolVersion; + __u8 bcdCapability; +} __attribute__((packed)); + +/*-------------------------------------------------------------------------*/ + /* USB_DT_WIRELESS_ENDPOINT_COMP: companion descriptor associated with * each endpoint descriptor for a wireless device */ From dd13bda7338608c981da5ca79506b6fb902c8815 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 19 Mar 2026 15:46:24 +0100 Subject: [PATCH 108/176] USB: uapi: add BULK_MAX_PACKET_UPDATE The spec for Embedded USB2 Version 2.0 adds a new feature request. This needs to be added to uapi for monitoring. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260319144715.2957358-2-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/usb/ch9.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h index 62771e38a83d..c3e593378377 100644 --- a/include/uapi/linux/usb/ch9.h +++ b/include/uapi/linux/usb/ch9.h @@ -123,15 +123,17 @@ * are at most sixteen features of each type.) Hubs may also support a * new USB_REQ_TEST_AND_SET_FEATURE to put ports into L1 suspend. */ -#define USB_DEVICE_SELF_POWERED 0 /* (read only) */ -#define USB_DEVICE_REMOTE_WAKEUP 1 /* dev may initiate wakeup */ -#define USB_DEVICE_TEST_MODE 2 /* (wired high speed only) */ -#define USB_DEVICE_BATTERY 2 /* (wireless) */ -#define USB_DEVICE_B_HNP_ENABLE 3 /* (otg) dev may initiate HNP */ -#define USB_DEVICE_WUSB_DEVICE 3 /* (wireless)*/ -#define USB_DEVICE_A_HNP_SUPPORT 4 /* (otg) RH port supports HNP */ -#define USB_DEVICE_A_ALT_HNP_SUPPORT 5 /* (otg) other RH port does */ -#define USB_DEVICE_DEBUG_MODE 6 /* (special devices only) */ +#define USB_DEVICE_SELF_POWERED 0 /* (read only) */ +#define USB_DEVICE_REMOTE_WAKEUP 1 /* dev may initiate wakeup */ +#define USB_DEVICE_TEST_MODE 2 /* (wired high speed only) */ +#define USB_DEVICE_BATTERY 2 /* (wireless) */ +#define USB_DEVICE_B_HNP_ENABLE 3 /* (otg) dev may initiate HNP */ +#define USB_DEVICE_WUSB_DEVICE 3 /* (wireless)*/ +#define USB_DEVICE_A_HNP_SUPPORT 4 /* (otg) RH port supports HNP */ +#define USB_DEVICE_A_ALT_HNP_SUPPORT 5 /* (otg) other RH port does */ +#define USB_DEVICE_DEBUG_MODE 6 /* (special devices only) */ + +#define USB_DEVICE_BULK_MAX_PACKET_UPDATE 8 /* (eUSB2v2) bump maxpacket to 1024 */ /* * Test Mode Selectors From 698f54d4eb9034bb4366985659bd77ae471b6c4e Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 25 Mar 2026 15:55:20 +0100 Subject: [PATCH 109/176] usb: translate ENOSPC for user space In case of insufficient bandwidth usb_submit_urb() returns -ENOSPC. Translating this to -EIO is not optimal. There are insufficient resources not an error. EBUSY is a better fit. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20260325145537.372993-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- include/linux/usb.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/linux/usb.h b/include/linux/usb.h index 04277af4bb9d..815f2212936e 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -2075,6 +2075,8 @@ static inline int usb_translate_errors(int error_code) case -ENODEV: case -EOPNOTSUPP: return error_code; + case -ENOSPC: + return -EBUSY; default: return -EIO; } From 1afaec82e12168fe5ec18c145b1499fc61979833 Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Wed, 25 Mar 2026 22:22:22 +0000 Subject: [PATCH 110/176] dt-bindings: mfd: maxim,max77759: reference power-supply schema and add regulator property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the max77759 binding to reference power-supply schema, so that PMIC node can reference its supplier. Also, add regulator property to control CHGIN (OTG) voltage. Signed-off-by: Amit Sunil Dhamne Reviewed-by: Krzysztof Kozlowski Reviewed-by: André Draszik Link: https://patch.msgid.link/20260325-max77759-charger-v9-1-4486dd297adc@google.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/mfd/maxim,max77759.yaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml b/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml index 525de9ab3c2b..42e4a84d5204 100644 --- a/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml +++ b/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml @@ -16,6 +16,9 @@ description: | The MAX77759 includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. +allOf: + - $ref: /schemas/power/supply/power-supply.yaml# + properties: compatible: const: maxim,max77759 @@ -37,12 +40,18 @@ properties: nvmem-0: $ref: /schemas/nvmem/maxim,max77759-nvmem.yaml + chgin-otg-regulator: + type: object + description: Provides Boost for sourcing VBUS. + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + required: - compatible - interrupts - reg -additionalProperties: false +unevaluatedProperties: false examples: - | @@ -59,6 +68,11 @@ examples: interrupt-controller; #interrupt-cells = <2>; + power-supplies = <&maxtcpci>; + + chgin-otg-regulator { + regulator-name = "chgin-otg"; + }; gpio { compatible = "maxim,max77759-gpio"; From 47f8ba232a17977f41c01e4f50934440f951395a Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Wed, 25 Mar 2026 22:22:23 +0000 Subject: [PATCH 111/176] dt-bindings: usb: maxim,max33359: Add supply property for vbus Add a regulator supply property for vbus. This notifies the regulator provider to source vbus when Type-C operates in Source power mode, while turn off sourcing vbus when operating in Sink mode or disconnected. Signed-off-by: Amit Sunil Dhamne Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260325-max77759-charger-v9-2-4486dd297adc@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/maxim,max33359.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml index 46a3748c8be4..003c0b713068 100644 --- a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml +++ b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml @@ -32,6 +32,9 @@ properties: description: Properties for usb c connector. + vbus-supply: + description: Regulator to control sourcing Vbus. + required: - compatible - reg @@ -53,6 +56,7 @@ examples: reg = <0x25>; interrupt-parent = <&gpa8>; interrupts = <2 IRQ_TYPE_LEVEL_LOW>; + vbus-supply = <&chgin_otg_reg>; connector { compatible = "usb-c-connector"; From b422f7c072ac8d9b83c3d22e03709b92626ca88a Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Wed, 25 Mar 2026 22:22:24 +0000 Subject: [PATCH 112/176] mfd: max77759: add register bitmasks and modify irq configs for charger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add register bitmasks for charger function. In addition split the charger IRQs further such that each bit represents an IRQ downstream of charger regmap irq chip. In addition populate the ack_base to offload irq ack to the regmap irq chip framework. Signed-off-by: Amit Sunil Dhamne Reviewed-by: André Draszik Link: https://patch.msgid.link/20260325-max77759-charger-v9-3-4486dd297adc@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/mfd/max77759.c | 95 +++++++++++++++++--- include/linux/mfd/max77759.h | 166 +++++++++++++++++++++++++++++------ 2 files changed, 222 insertions(+), 39 deletions(-) diff --git a/drivers/mfd/max77759.c b/drivers/mfd/max77759.c index a7efe233ec8c..9fa6027a92c4 100644 --- a/drivers/mfd/max77759.c +++ b/drivers/mfd/max77759.c @@ -201,8 +201,24 @@ static const struct regmap_config max77759_regmap_config_charger = { * - SYSUVLO_INT * - FSHIP_NOT_RD * - CHGR_INT: charger - * - CHG_INT - * - CHG_INT2 + * - INT1 + * - AICL + * - CHGIN + * - WCIN + * - CHG + * - BAT + * - INLIM + * - THM2 + * - BYP + * - INT2 + * - INSEL + * - SYS_UVLO1 + * - SYS_UVLO2 + * - BAT_OILO + * - CHG_STA_CC + * - CHG_STA_CV + * - CHG_STA_TO + * - CHG_STA_DONE */ enum { MAX77759_INT_MAXQ, @@ -228,8 +244,22 @@ enum { }; enum { - MAX77759_CHARGER_INT_1, - MAX77759_CHARGER_INT_2, + MAX77759_CHGR_INT1_AICL, + MAX77759_CHGR_INT1_CHGIN, + MAX77759_CHGR_INT1_WCIN, + MAX77759_CHGR_INT1_CHG, + MAX77759_CHGR_INT1_BAT, + MAX77759_CHGR_INT1_INLIM, + MAX77759_CHGR_INT1_THM2, + MAX77759_CHGR_INT1_BYP, + MAX77759_CHGR_INT2_INSEL, + MAX77759_CHGR_INT2_SYS_UVLO1, + MAX77759_CHGR_INT2_SYS_UVLO2, + MAX77759_CHGR_INT2_BAT_OILO, + MAX77759_CHGR_INT2_CHG_STA_CC, + MAX77759_CHGR_INT2_CHG_STA_CV, + MAX77759_CHGR_INT2_CHG_STA_TO, + MAX77759_CHGR_INT2_CHG_STA_DONE, }; static const struct regmap_irq max77759_pmic_irqs[] = { @@ -256,8 +286,38 @@ static const struct regmap_irq max77759_topsys_irqs[] = { }; static const struct regmap_irq max77759_chgr_irqs[] = { - REGMAP_IRQ_REG(MAX77759_CHARGER_INT_1, 0, GENMASK(7, 0)), - REGMAP_IRQ_REG(MAX77759_CHARGER_INT_2, 1, GENMASK(7, 0)), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_AICL, 0, + MAX77759_CHGR_REG_CHG_INT_AICL), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_CHGIN, 0, + MAX77759_CHGR_REG_CHG_INT_CHGIN), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_WCIN, 0, + MAX77759_CHGR_REG_CHG_INT_WCIN), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_CHG, 0, + MAX77759_CHGR_REG_CHG_INT_CHG), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_BAT, 0, + MAX77759_CHGR_REG_CHG_INT_BAT), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_INLIM, 0, + MAX77759_CHGR_REG_CHG_INT_INLIM), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_THM2, 0, + MAX77759_CHGR_REG_CHG_INT_THM2), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_BYP, 0, + MAX77759_CHGR_REG_CHG_INT_BYP), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_INSEL, 1, + MAX77759_CHGR_REG_CHG_INT2_INSEL), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_SYS_UVLO1, 1, + MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO1), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_SYS_UVLO2, 1, + MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO2), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_BAT_OILO, 1, + MAX77759_CHGR_REG_CHG_INT2_BAT_OILO), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_CC, 1, + MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CC), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_CV, 1, + MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CV), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_TO, 1, + MAX77759_CHGR_REG_CHG_INT2_CHG_STA_TO), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_DONE, 1, + MAX77759_CHGR_REG_CHG_INT2_CHG_STA_DONE), }; static const struct regmap_irq_chip max77759_pmic_irq_chip = { @@ -297,11 +357,12 @@ static const struct regmap_irq_chip max77759_topsys_irq_chip = { .num_irqs = ARRAY_SIZE(max77759_topsys_irqs), }; -static const struct regmap_irq_chip max77759_chrg_irq_chip = { +static const struct regmap_irq_chip max77759_chgr_irq_chip = { .name = "max77759-chgr", .domain_suffix = "CHGR", .status_base = MAX77759_CHGR_REG_CHG_INT, .mask_base = MAX77759_CHGR_REG_CHG_INT_MASK, + .ack_base = MAX77759_CHGR_REG_CHG_INT, .num_regs = 2, .irqs = max77759_chgr_irqs, .num_irqs = ARRAY_SIZE(max77759_chgr_irqs), @@ -325,8 +386,22 @@ static const struct resource max77759_gpio_resources[] = { }; static const struct resource max77759_charger_resources[] = { - DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_1, "INT1"), - DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_2, "INT2"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_AICL, "AICL"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_CHGIN, "CHGIN"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_WCIN, "WCIN"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_CHG, "CHG"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_BAT, "BAT"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_INLIM, "INLIM"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_THM2, "THM2"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_BYP, "BYP"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_INSEL, "INSEL"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_SYS_UVLO1, "SYS_UVLO1"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_SYS_UVLO2, "SYS_UVLO2"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_BAT_OILO, "BAT_OILO"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_CC, "CHG_STA_CC"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_CV, "CHG_STA_CV"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_TO, "CHG_STA_TO"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_DONE, "CHG_STA_DONE"), }; static const struct mfd_cell max77759_cells[] = { @@ -567,7 +642,7 @@ static int max77759_add_chained_charger(struct i2c_client *client, max77759->regmap_charger, MAX77759_INT_CHGR, parent, - &max77759_chrg_irq_chip, + &max77759_chgr_irq_chip, &irq_chip_data); if (ret) return ret; diff --git a/include/linux/mfd/max77759.h b/include/linux/mfd/max77759.h index c6face34e385..ad1aa4c2b779 100644 --- a/include/linux/mfd/max77759.h +++ b/include/linux/mfd/max77759.h @@ -59,35 +59,65 @@ #define MAX77759_MAXQ_REG_AP_DATAIN0 0xb1 #define MAX77759_MAXQ_REG_UIC_SWRST 0xe0 -#define MAX77759_CHGR_REG_CHG_INT 0xb0 -#define MAX77759_CHGR_REG_CHG_INT2 0xb1 -#define MAX77759_CHGR_REG_CHG_INT_MASK 0xb2 -#define MAX77759_CHGR_REG_CHG_INT2_MASK 0xb3 -#define MAX77759_CHGR_REG_CHG_INT_OK 0xb4 -#define MAX77759_CHGR_REG_CHG_DETAILS_00 0xb5 -#define MAX77759_CHGR_REG_CHG_DETAILS_01 0xb6 -#define MAX77759_CHGR_REG_CHG_DETAILS_02 0xb7 -#define MAX77759_CHGR_REG_CHG_DETAILS_03 0xb8 -#define MAX77759_CHGR_REG_CHG_CNFG_00 0xb9 -#define MAX77759_CHGR_REG_CHG_CNFG_01 0xba -#define MAX77759_CHGR_REG_CHG_CNFG_02 0xbb -#define MAX77759_CHGR_REG_CHG_CNFG_03 0xbc -#define MAX77759_CHGR_REG_CHG_CNFG_04 0xbd -#define MAX77759_CHGR_REG_CHG_CNFG_05 0xbe -#define MAX77759_CHGR_REG_CHG_CNFG_06 0xbf -#define MAX77759_CHGR_REG_CHG_CNFG_07 0xc0 -#define MAX77759_CHGR_REG_CHG_CNFG_08 0xc1 -#define MAX77759_CHGR_REG_CHG_CNFG_09 0xc2 -#define MAX77759_CHGR_REG_CHG_CNFG_10 0xc3 -#define MAX77759_CHGR_REG_CHG_CNFG_11 0xc4 -#define MAX77759_CHGR_REG_CHG_CNFG_12 0xc5 -#define MAX77759_CHGR_REG_CHG_CNFG_13 0xc6 -#define MAX77759_CHGR_REG_CHG_CNFG_14 0xc7 -#define MAX77759_CHGR_REG_CHG_CNFG_15 0xc8 -#define MAX77759_CHGR_REG_CHG_CNFG_16 0xc9 -#define MAX77759_CHGR_REG_CHG_CNFG_17 0xca -#define MAX77759_CHGR_REG_CHG_CNFG_18 0xcb -#define MAX77759_CHGR_REG_CHG_CNFG_19 0xcc +#define MAX77759_CHGR_REG_CHG_INT 0xb0 +#define MAX77759_CHGR_REG_CHG_INT_AICL BIT(7) +#define MAX77759_CHGR_REG_CHG_INT_CHGIN BIT(6) +#define MAX77759_CHGR_REG_CHG_INT_WCIN BIT(5) +#define MAX77759_CHGR_REG_CHG_INT_CHG BIT(4) +#define MAX77759_CHGR_REG_CHG_INT_BAT BIT(3) +#define MAX77759_CHGR_REG_CHG_INT_INLIM BIT(2) +#define MAX77759_CHGR_REG_CHG_INT_THM2 BIT(1) +#define MAX77759_CHGR_REG_CHG_INT_BYP BIT(0) +#define MAX77759_CHGR_REG_CHG_INT2 0xb1 +#define MAX77759_CHGR_REG_CHG_INT2_INSEL BIT(7) +#define MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO1 BIT(6) +#define MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO2 BIT(5) +#define MAX77759_CHGR_REG_CHG_INT2_BAT_OILO BIT(4) +#define MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CC BIT(3) +#define MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CV BIT(2) +#define MAX77759_CHGR_REG_CHG_INT2_CHG_STA_TO BIT(1) +#define MAX77759_CHGR_REG_CHG_INT2_CHG_STA_DONE BIT(0) +#define MAX77759_CHGR_REG_CHG_INT_MASK 0xb2 +#define MAX77759_CHGR_REG_CHG_INT2_MASK 0xb3 +#define MAX77759_CHGR_REG_CHG_INT_OK 0xb4 +#define MAX77759_CHGR_REG_CHG_DETAILS_00 0xb5 +#define MAX77759_CHGR_REG_CHG_DETAILS_00_CHGIN_DTLS GENMASK(6, 5) +#define MAX77759_CHGR_REG_CHG_DETAILS_01 0xb6 +#define MAX77759_CHGR_REG_CHG_DETAILS_01_BAT_DTLS GENMASK(6, 4) +#define MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS GENMASK(3, 0) +#define MAX77759_CHGR_REG_CHG_DETAILS_02 0xb7 +#define MAX77759_CHGR_REG_CHG_DETAILS_02_CHGIN_STS BIT(5) +#define MAX77759_CHGR_REG_CHG_DETAILS_03 0xb8 +#define MAX77759_CHGR_REG_CHG_CNFG_00 0xb9 +#define MAX77759_CHGR_REG_CHG_CNFG_00_MODE GENMASK(3, 0) +#define MAX77759_CHGR_REG_CHG_CNFG_01 0xba +#define MAX77759_CHGR_REG_CHG_CNFG_02 0xbb +#define MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC GENMASK(5, 0) +#define MAX77759_CHGR_REG_CHG_CNFG_03 0xbc +#define MAX77759_CHGR_REG_CHG_CNFG_04 0xbd +#define MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM GENMASK(5, 0) +#define MAX77759_CHGR_REG_CHG_CNFG_05 0xbe +#define MAX77759_CHGR_REG_CHG_CNFG_06 0xbf +#define MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT GENMASK(3, 2) +#define MAX77759_CHGR_REG_CHG_CNFG_07 0xc0 +#define MAX77759_CHGR_REG_CHG_CNFG_08 0xc1 +#define MAX77759_CHGR_REG_CHG_CNFG_09 0xc2 +#define MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM GENMASK(6, 0) +#define MAX77759_CHGR_REG_CHG_CNFG_10 0xc3 +#define MAX77759_CHGR_REG_CHG_CNFG_11 0xc4 +#define MAX77759_CHGR_REG_CHG_CNFG_12 0xc5 +/* Wireless Charging input channel select */ +#define MAX77759_CHGR_REG_CHG_CNFG_12_WCINSEL BIT(6) +/* CHGIN/USB input channel select */ +#define MAX77759_CHGR_REG_CHG_CNFG_12_CHGINSEL BIT(5) +#define MAX77759_CHGR_REG_CHG_CNFG_13 0xc6 +#define MAX77759_CHGR_REG_CHG_CNFG_14 0xc7 +#define MAX77759_CHGR_REG_CHG_CNFG_15 0xc8 +#define MAX77759_CHGR_REG_CHG_CNFG_16 0xc9 +#define MAX77759_CHGR_REG_CHG_CNFG_17 0xca +#define MAX77759_CHGR_REG_CHG_CNFG_18 0xcb +#define MAX77759_CHGR_REG_CHG_CNFG_18_WDTEN BIT(0) +#define MAX77759_CHGR_REG_CHG_CNFG_19 0xcc /* MaxQ opcodes for max77759_maxq_command() */ #define MAX77759_MAXQ_OPCODE_MAXLENGTH (MAX77759_MAXQ_REG_AP_DATAOUT32 - \ @@ -101,6 +131,84 @@ #define MAX77759_MAXQ_OPCODE_USER_SPACE_READ 0x81 #define MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE 0x82 +/* + * enum max77759_chgr_chgin_dtls_status - Charger Input Status + * @MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE: + * Charger input voltage (Vchgin) < Under Voltage Threshold (Vuvlo) + * @MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: Vchgin > Vuvlo and + * Vchgin < (Battery Voltage (Vbatt) + system voltage (Vsys)) + * @MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE: + * Vchgin > Over Voltage threshold (Vovlo) + * @MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID: + * Vchgin > Vuvlo, Vchgin < Vovlo and Vchgin > (Vsys + Vbatt) + */ +enum max77759_chgr_chgin_dtls_status { + MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE, + MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE, + MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE, + MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID, +}; + +/* + * enum max77759_chgr_bat_dtls_states - Battery Details + * @MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP: No battery and the charger suspended + * @MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY: Vbatt < Vtrickle + * @MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT: Charging suspended due to timer fault + * @MAX77759_CHGR_BAT_DTLS_BAT_OKAY: Battery okay and Vbatt > Min Sys Voltage (Vsysmin) + * @MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE: Battery is okay. Vtrickle < Vbatt < Vsysmin + * @MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE: Battery voltage > Overvoltage threshold + * @MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT: Battery current exceeds overcurrent threshold + * @MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE: Battery only mode and battery level not available + */ +enum max77759_chgr_bat_dtls_states { + MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP, + MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY, + MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT, + MAX77759_CHGR_BAT_DTLS_BAT_OKAY, + MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE, + MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE, + MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT, + MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE, +}; + +/* + * enum max77759_chgr_chg_dtls_states - Charger Details + * @MAX77759_CHGR_CHG_DTLS_PREQUAL: Charger in prequalification mode + * @MAX77759_CHGR_CHG_DTLS_CC: Charger in fast charge const curr mode + * @MAX77759_CHGR_CHG_DTLS_CV: Charger in fast charge const voltage mode + * @MAX77759_CHGR_CHG_DTLS_TO: Charger is in top off mode + * @MAX77759_CHGR_CHG_DTLS_DONE: Charger is done + * @MAX77759_CHGR_CHG_DTLS_RSVD_1: Reserved + * @MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: Charger is in timer fault mode + * @MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: Charger is suspended as battery removal detected + * @MAX77759_CHGR_CHG_DTLS_OFF: Charger is off. Input invalid or charger disabled + * @MAX77759_CHGR_CHG_DTLS_RSVD_2: Reserved + * @MAX77759_CHGR_CHG_DTLS_RSVD_3: Reserved + * @MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: Charger is off as watchdog timer expired + * @MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: Charger is in JEITA control mode + */ +enum max77759_chgr_chg_dtls_states { + MAX77759_CHGR_CHG_DTLS_PREQUAL, + MAX77759_CHGR_CHG_DTLS_CC, + MAX77759_CHGR_CHG_DTLS_CV, + MAX77759_CHGR_CHG_DTLS_TO, + MAX77759_CHGR_CHG_DTLS_DONE, + MAX77759_CHGR_CHG_DTLS_RSVD_1, + MAX77759_CHGR_CHG_DTLS_TIMER_FAULT, + MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM, + MAX77759_CHGR_CHG_DTLS_OFF, + MAX77759_CHGR_CHG_DTLS_RSVD_2, + MAX77759_CHGR_CHG_DTLS_RSVD_3, + MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER, + MAX77759_CHGR_CHG_DTLS_SUSP_JEITA, +}; + +enum max77759_chgr_mode { + MAX77759_CHGR_MODE_OFF, + MAX77759_CHGR_MODE_CHG_BUCK_ON = 0x5, + MAX77759_CHGR_MODE_OTG_BOOST_ON = 0xA, +}; + /** * struct max77759 - core max77759 internal data structure * From f23388d0f6523cc3a72edf6e78cb11931a07da10 Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Wed, 25 Mar 2026 22:22:25 +0000 Subject: [PATCH 113/176] lib/linear_ranges: Add linear_range_get_selector_high_array Add a helper function to find the selector for a given value in a linear range array. The selector should be such that the value it represents should be higher or equal to the given value. Signed-off-by: Amit Sunil Dhamne Reviewed-by: Matti Vaittinen Acked-by: Mark Brown Link: https://patch.msgid.link/20260325-max77759-charger-v9-4-4486dd297adc@google.com Signed-off-by: Greg Kroah-Hartman --- include/linux/linear_range.h | 3 +++ lib/linear_ranges.c | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/include/linux/linear_range.h b/include/linux/linear_range.h index 2e4f4c3539c0..0f3037f1a94f 100644 --- a/include/linux/linear_range.h +++ b/include/linux/linear_range.h @@ -57,5 +57,8 @@ void linear_range_get_selector_within(const struct linear_range *r, int linear_range_get_selector_low_array(const struct linear_range *r, int ranges, unsigned int val, unsigned int *selector, bool *found); +int linear_range_get_selector_high_array(const struct linear_range *r, + int ranges, unsigned int val, + unsigned int *selector, bool *found); #endif diff --git a/lib/linear_ranges.c b/lib/linear_ranges.c index a1a7dfa881de..c85583678f6b 100644 --- a/lib/linear_ranges.c +++ b/lib/linear_ranges.c @@ -241,6 +241,42 @@ int linear_range_get_selector_high(const struct linear_range *r, } EXPORT_SYMBOL_GPL(linear_range_get_selector_high); +/** + * linear_range_get_selector_high_array - return linear range selector for value + * @r: pointer to array of linear ranges where selector is looked from + * @ranges: amount of ranges to scan from array + * @val: value for which the selector is searched + * @selector: address where found selector value is updated + * @found: flag to indicate that given value was in the range + * + * Scan array of ranges for selector for which range value matches given + * input value. Value is matching if it is equal or higher than given value + * If given value is found to be in a range scanning is stopped and @found is + * set true. If a range with values greater than given value is found + * but the range min is being greater than given value, then the range's + * lowest selector is updated to @selector and scanning is stopped. + * + * Return: 0 on success, -EINVAL if range array is invalid or does not contain + * range with a value greater or equal to given value + */ +int linear_range_get_selector_high_array(const struct linear_range *r, + int ranges, unsigned int val, + unsigned int *selector, bool *found) +{ + int i; + int ret; + + for (i = 0; i < ranges; i++) { + ret = linear_range_get_selector_high(&r[i], val, selector, + found); + if (!ret) + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(linear_range_get_selector_high_array); + /** * linear_range_get_selector_within - return linear range selector for value * @r: pointer to linear range where selector is looked from From 70d7dd27f6dc9bada652c3a84ba7e6688a96f8db Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Wed, 25 Mar 2026 22:22:26 +0000 Subject: [PATCH 114/176] power: supply: max77759: add charger driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for MAX77759 battery charger driver. This is a 4A 1-Cell Li+/LiPoly dual input switch mode charger. While the device can support USB & wireless charger inputs, this implementation only supports USB input. This implementation supports both buck and boost modes. Signed-off-by: Amit Sunil Dhamne Reviewed-by: André Draszik Link: https://patch.msgid.link/20260325-max77759-charger-v9-5-4486dd297adc@google.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 6 + drivers/power/supply/Kconfig | 11 + drivers/power/supply/Makefile | 1 + drivers/power/supply/max77759_charger.c | 774 ++++++++++++++++++++++++ 4 files changed, 792 insertions(+) create mode 100644 drivers/power/supply/max77759_charger.c diff --git a/MAINTAINERS b/MAINTAINERS index 96ea84948d76..92745606ec12 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15708,6 +15708,12 @@ F: drivers/mfd/max77759.c F: drivers/nvmem/max77759-nvmem.c F: include/linux/mfd/max77759.h +MAXIM MAX77759 BATTERY CHARGER DRIVER +M: Amit Sunil Dhamne +L: linux-kernel@vger.kernel.org +S: Maintained +F: drivers/power/supply/max77759_charger.c + MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER M: Javier Martinez Canillas L: linux-kernel@vger.kernel.org diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 92f9f7aae92f..3a2cdb95c98e 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -631,6 +631,17 @@ config CHARGER_MAX77705 help Say Y to enable support for the Maxim MAX77705 battery charger. +config CHARGER_MAX77759 + tristate "Maxim MAX77759 battery charger driver" + depends on MFD_MAX77759 && REGULATOR + default MFD_MAX77759 + help + Say M or Y here to enable the MAX77759 battery charger. MAX77759 + charger is a function of the MAX77759 PMIC. This is a dual input + switch-mode charger. This driver supports buck and OTG boost modes. + + If built as a module, it will be called max77759_charger. + config CHARGER_MAX77976 tristate "Maxim MAX77976 battery charger driver" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 4b79d5abc49a..6af905875ad5 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -128,3 +128,4 @@ obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o +obj-$(CONFIG_CHARGER_MAX77759) += max77759_charger.o diff --git a/drivers/power/supply/max77759_charger.c b/drivers/power/supply/max77759_charger.c new file mode 100644 index 000000000000..9bb414599f16 --- /dev/null +++ b/drivers/power/supply/max77759_charger.c @@ -0,0 +1,774 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * max77759_charger.c - Battery charger driver for MAX77759 charger device. + * + * Copyright 2025 Google LLC. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Default values for Fast Charge Current & Float Voltage */ +#define CHG_CC_DEFAULT_UA 2266770 +#define CHG_FV_DEFAULT_MV 4300 + +#define MAX_NUM_RETRIES 3 +#define PSY_WORK_RETRY_DELAY_MS 10 + +#define FOREACH_IRQ(S) \ + S(AICL), \ + S(CHGIN), \ + S(CHG), \ + S(INLIM), \ + S(BAT_OILO), \ + S(CHG_STA_CC), \ + S(CHG_STA_CV), \ + S(CHG_STA_TO), \ + S(CHG_STA_DONE) + +#define GENERATE_ENUM(e) e +#define GENERATE_STRING(s) #s + +enum { + FOREACH_IRQ(GENERATE_ENUM) +}; + +static const char *const chgr_irqs_str[] = { + FOREACH_IRQ(GENERATE_STRING) +}; + +#define NUM_IRQS ARRAY_SIZE(chgr_irqs_str) + +/* Fast charge current limits (in uA) */ +static const struct linear_range chgcc_limit_ranges[] = { + LINEAR_RANGE(133330, 0x0, 0x2, 0), + LINEAR_RANGE(200000, 0x3, 0x3C, 66670), +}; + +/* Charge Termination Voltage Limits (in mV) */ +static const struct linear_range chg_cv_prm_ranges[] = { + LINEAR_RANGE(3800, 0x38, 0x39, 100), + LINEAR_RANGE(4000, 0x0, 0x32, 10), +}; + +/* USB input current limits (in uA) */ +static const struct linear_range chgin_ilim_ranges[] = { + LINEAR_RANGE(100000, 0x3, 0x7F, 25000), +}; + +struct max77759_charger { + struct device *dev; + struct regmap *regmap; + struct power_supply *psy; + struct regulator_dev *chgin_otg_rdev; + struct notifier_block nb; + struct power_supply *tcpm_psy; + struct delayed_work psy_work; + struct mutex retry_lock; /* Protects psy_work_retry_cnt */ + u32 psy_work_retry_cnt; + int irqs[NUM_IRQS]; + struct mutex lock; /* protects the state below */ + enum max77759_chgr_mode mode; +}; + +static inline int unlock_prot_regs(struct max77759_charger *chg, bool unlock) +{ + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_06, + MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT, unlock + ? MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT : 0); +} + +static int charger_input_valid(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_INT_OK, &val); + if (ret) + return ret; + + return (val & MAX77759_CHGR_REG_CHG_INT_CHG) && + (val & MAX77759_CHGR_REG_CHG_INT_CHGIN); +} + +static int get_online(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = charger_input_valid(chg); + if (ret <= 0) + return ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_02, &val); + if (ret) + return ret; + + guard(mutex)(&chg->lock); + + return (val & MAX77759_CHGR_REG_CHG_DETAILS_02_CHGIN_STS) && + (chg->mode == MAX77759_CHGR_MODE_CHG_BUCK_ON); +} + +static int get_status(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { + case MAX77759_CHGR_CHG_DTLS_PREQUAL: + case MAX77759_CHGR_CHG_DTLS_CC: + case MAX77759_CHGR_CHG_DTLS_CV: + case MAX77759_CHGR_CHG_DTLS_TO: + return POWER_SUPPLY_STATUS_CHARGING; + case MAX77759_CHGR_CHG_DTLS_DONE: + return POWER_SUPPLY_STATUS_FULL; + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: + return POWER_SUPPLY_STATUS_NOT_CHARGING; + case MAX77759_CHGR_CHG_DTLS_OFF: + return POWER_SUPPLY_STATUS_DISCHARGING; + default: + break; + } + + return POWER_SUPPLY_STATUS_UNKNOWN; +} + +static int get_charge_type(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { + case MAX77759_CHGR_CHG_DTLS_PREQUAL: + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + case MAX77759_CHGR_CHG_DTLS_CC: + case MAX77759_CHGR_CHG_DTLS_CV: + return POWER_SUPPLY_CHARGE_TYPE_FAST; + case MAX77759_CHGR_CHG_DTLS_TO: + return POWER_SUPPLY_CHARGE_TYPE_STANDARD; + case MAX77759_CHGR_CHG_DTLS_DONE: + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: + case MAX77759_CHGR_CHG_DTLS_OFF: + return POWER_SUPPLY_CHARGE_TYPE_NONE; + default: + break; + } + + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; +} + +static int get_chg_health(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_00, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_00_CHGIN_DTLS, val)) { + case MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE: + case MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + case MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE: + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + case MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID: + return POWER_SUPPLY_HEALTH_GOOD; + default: + break; + } + + return POWER_SUPPLY_HEALTH_UNKNOWN; +} + +static int get_batt_health(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_BAT_DTLS, val)) { + case MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP: + return POWER_SUPPLY_HEALTH_NO_BATTERY; + case MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY: + return POWER_SUPPLY_HEALTH_DEAD; + case MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT: + return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + case MAX77759_CHGR_BAT_DTLS_BAT_OKAY: + case MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE: + return POWER_SUPPLY_HEALTH_GOOD; + case MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE: + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + case MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE: + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + case MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT: + return POWER_SUPPLY_HEALTH_OVERCURRENT; + default: + break; + } + + return POWER_SUPPLY_HEALTH_UNKNOWN; +} + +static int get_health(struct max77759_charger *chg) +{ + int ret; + + ret = get_online(chg); + if (ret < 0) + return ret; + + if (ret) { + ret = get_chg_health(chg); + if (ret < 0 || ret != POWER_SUPPLY_HEALTH_GOOD) + return ret; + } + + return get_batt_health(chg); +} + +static int get_fast_charge_current(struct max77759_charger *chg) +{ + u32 regval, val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, ®val); + if (ret) + return ret; + + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); + ret = linear_range_get_value_array(chgcc_limit_ranges, + ARRAY_SIZE(chgcc_limit_ranges), + regval, &val); + return ret ? ret : val; +} + +static int set_fast_charge_current_limit(struct max77759_charger *chg, + u32 cc_max_ua) +{ + bool found; + u32 regval; + + linear_range_get_selector_high_array(chgcc_limit_ranges, + ARRAY_SIZE(chgcc_limit_ranges), + cc_max_ua, ®val, &found); + if (!found) + return -EINVAL; + + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, + MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); +} + +static int get_float_voltage(struct max77759_charger *chg) +{ + u32 regval, val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, ®val); + if (ret) + return ret; + + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, regval); + ret = linear_range_get_value_array(chg_cv_prm_ranges, + ARRAY_SIZE(chg_cv_prm_ranges), + regval, &val); + + return ret ? ret : val; +} + +static int set_float_voltage_limit(struct max77759_charger *chg, u32 fv_mv) +{ + u32 regval; + bool found; + + linear_range_get_selector_high_array(chg_cv_prm_ranges, + ARRAY_SIZE(chg_cv_prm_ranges), + fv_mv, ®val, &found); + if (!found) + return -EINVAL; + + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, + MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, + regval); +} + +static int get_input_current_limit(struct max77759_charger *chg) +{ + u32 regval, val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, ®val); + if (ret) + return ret; + + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, regval); + regval = umax(regval, chgin_ilim_ranges[0].min_sel); + + ret = linear_range_get_value_array(chgin_ilim_ranges, + ARRAY_SIZE(chgin_ilim_ranges), + regval, &val); + + return ret ? ret : val; +} + +static int set_input_current_limit(struct max77759_charger *chg, int ilim_ua) +{ + u32 regval; + + if (ilim_ua < 0) + return -EINVAL; + + linear_range_get_selector_within(chgin_ilim_ranges, ilim_ua, ®val); + + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, + MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, + regval); +} + +static const enum power_supply_property max77759_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, +}; + +static int max77759_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *pval) +{ + struct max77759_charger *chg = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = get_online(chg); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = charger_input_valid(chg); + break; + case POWER_SUPPLY_PROP_STATUS: + ret = get_status(chg); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = get_charge_type(chg); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = get_health(chg); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = get_fast_charge_current(chg); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + ret = get_float_voltage(chg); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = get_input_current_limit(chg); + break; + default: + ret = -EINVAL; + } + + pval->intval = ret; + return ret < 0 ? ret : 0; +} + +static const struct power_supply_desc max77759_charger_desc = { + .name = "max77759-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = max77759_charger_props, + .num_properties = ARRAY_SIZE(max77759_charger_props), + .get_property = max77759_charger_get_property, +}; + +static int charger_set_mode(struct max77759_charger *chg, + enum max77759_chgr_mode mode) +{ + int ret; + + guard(mutex)(&chg->lock); + + if (chg->mode == mode) + return 0; + + if ((mode == MAX77759_CHGR_MODE_CHG_BUCK_ON || + mode == MAX77759_CHGR_MODE_OTG_BOOST_ON) && + chg->mode != MAX77759_CHGR_MODE_OFF) { + dev_err(chg->dev, "Invalid mode transition from %d to %d\n", + chg->mode, mode); + return -EINVAL; + } + + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, + MAX77759_CHGR_REG_CHG_CNFG_00_MODE, mode); + if (ret) + return ret; + + chg->mode = mode; + return 0; +} + +static int enable_chgin_otg(struct regulator_dev *rdev) +{ + struct max77759_charger *chg = rdev_get_drvdata(rdev); + + return charger_set_mode(chg, MAX77759_CHGR_MODE_OTG_BOOST_ON); +} + +static int disable_chgin_otg(struct regulator_dev *rdev) +{ + struct max77759_charger *chg = rdev_get_drvdata(rdev); + + return charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); +} + +static int chgin_otg_status(struct regulator_dev *rdev) +{ + struct max77759_charger *chg = rdev_get_drvdata(rdev); + + guard(mutex)(&chg->lock); + + return chg->mode == MAX77759_CHGR_MODE_OTG_BOOST_ON; +} + +static const struct regulator_ops chgin_otg_reg_ops = { + .enable = enable_chgin_otg, + .disable = disable_chgin_otg, + .is_enabled = chgin_otg_status, +}; + +static const struct regulator_desc chgin_otg_reg_desc = { + .name = "chgin-otg", + .of_match = of_match_ptr("chgin-otg-regulator"), + .owner = THIS_MODULE, + .ops = &chgin_otg_reg_ops, + .fixed_uV = 5000000, + .n_voltages = 1, +}; + +static irqreturn_t irq_handler(int irq, void *data) +{ + struct max77759_charger *chg = data; + + power_supply_changed(chg->psy); + + return IRQ_HANDLED; +} + +static irqreturn_t bat_oilo_irq_handler(int irq, void *data) +{ + struct max77759_charger *chg = data; + + dev_warn_ratelimited(chg->dev, + "Battery over-current threshold crossed\n"); + + return irq_handler(irq, data); +} + +static int max77759_init_irqhandler(struct max77759_charger *chg) +{ + struct device *dev = chg->dev; + irq_handler_t thread_fn; + char *name; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(chgr_irqs_str); i++) { + ret = platform_get_irq_byname(to_platform_device(dev), + chgr_irqs_str[i]); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get irq resource for %s\n", + chgr_irqs_str[i]); + + chg->irqs[i] = ret; + name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", dev_name(dev), + chgr_irqs_str[i]); + if (!name) + return dev_err_probe(dev, -ENOMEM, + "Failed to allocate space for irqname: %s\n", + chgr_irqs_str[i]); + + if (i == BAT_OILO) + thread_fn = bat_oilo_irq_handler; + else + thread_fn = irq_handler; + + ret = devm_request_threaded_irq(dev, chg->irqs[i], NULL, + thread_fn, 0, name, chg); + if (ret) + return dev_err_probe(dev, ret, + "Unable to register irq handler for %s\n", + chgr_irqs_str[i]); + } + + return 0; +} + +static int max77759_charger_init(struct max77759_charger *chg) +{ + struct power_supply_battery_info *info; + u32 regval, fast_chg_curr, fv; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, ®val); + if (ret) + return ret; + + chg->mode = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_00_MODE, regval); + ret = charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + if (ret) + return ret; + + if (power_supply_get_battery_info(chg->psy, &info)) { + fv = CHG_FV_DEFAULT_MV; + fast_chg_curr = CHG_CC_DEFAULT_UA; + } else { + fv = info->constant_charge_voltage_max_uv / 1000; + fast_chg_curr = info->constant_charge_current_max_ua; + } + + ret = set_fast_charge_current_limit(chg, fast_chg_curr); + if (ret) + return ret; + + ret = set_float_voltage_limit(chg, fv); + if (ret) + return ret; + + ret = unlock_prot_regs(chg, true); + if (ret) + return ret; + + /* Disable wireless charging input */ + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_12, + MAX77759_CHGR_REG_CHG_CNFG_12_WCINSEL, 0); + if (ret) + goto relock; + + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_18, + MAX77759_CHGR_REG_CHG_CNFG_18_WDTEN, 0); + if (ret) + goto relock; + + return unlock_prot_regs(chg, false); + +relock: + (void)unlock_prot_regs(chg, false); + return ret; +} + +static void psy_work_item(struct work_struct *work) +{ + struct max77759_charger *chg = + container_of(work, struct max77759_charger, psy_work.work); + union power_supply_propval current_limit, online; + int ret; + + ret = power_supply_get_property(chg->tcpm_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, + ¤t_limit); + if (ret) { + dev_err(chg->dev, + "Failed to get CURRENT_MAX psy property, ret=%d\n", + ret); + goto err; + } + + ret = power_supply_get_property(chg->tcpm_psy, POWER_SUPPLY_PROP_ONLINE, + &online); + if (ret) { + dev_err(chg->dev, + "Failed to get ONLINE psy property, ret=%d\n", + ret); + goto err; + } + + if (online.intval && current_limit.intval) { + ret = set_input_current_limit(chg, current_limit.intval); + if (ret) { + dev_err(chg->dev, + "Unable to set current limit, ret=%d\n", ret); + goto err; + } + + charger_set_mode(chg, MAX77759_CHGR_MODE_CHG_BUCK_ON); + } else { + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + } + + scoped_guard(mutex, &chg->retry_lock) { + if (chg->psy_work_retry_cnt) + dev_dbg(chg->dev, + "chg psy_work succeeded after %u tries\n", + chg->psy_work_retry_cnt); + chg->psy_work_retry_cnt = 0; + } + + return; + +err: + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + scoped_guard(mutex, &chg->retry_lock) { + if (chg->psy_work_retry_cnt >= MAX_NUM_RETRIES) { + dev_err(chg->dev, "chg psy work failed, giving up\n"); + return; + } + + ++chg->psy_work_retry_cnt; + dev_dbg(chg->dev, "Retrying %u/%u chg psy_work\n", + chg->psy_work_retry_cnt, MAX_NUM_RETRIES); + schedule_delayed_work(&chg->psy_work, + msecs_to_jiffies(PSY_WORK_RETRY_DELAY_MS)); + } +} + +static int psy_changed(struct notifier_block *nb, unsigned long evt, void *data) +{ + struct max77759_charger *chg = container_of(nb, struct max77759_charger, + nb); + static const char *psy_name = "tcpm-source"; + struct power_supply *psy = data; + + if (!strnstr(psy->desc->name, psy_name, strlen(psy_name)) || + evt != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + chg->tcpm_psy = psy; + scoped_guard(mutex, &chg->retry_lock) + chg->psy_work_retry_cnt = 0; + + schedule_delayed_work(&chg->psy_work, 0); + + return NOTIFY_OK; +} + +static void max_tcpci_unregister_psy_notifier(void *nb) +{ + power_supply_unreg_notifier(nb); +} + +static int max77759_charger_probe(struct platform_device *pdev) +{ + struct regulator_config chgin_otg_reg_cfg; + struct power_supply_config psy_cfg; + struct device *dev = &pdev->dev; + struct max77759_charger *chg; + int ret; + + device_set_of_node_from_dev(dev, dev->parent); + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + platform_set_drvdata(pdev, chg); + chg->dev = dev; + chg->regmap = dev_get_regmap(dev->parent, "charger"); + if (!chg->regmap) + return dev_err_probe(dev, -ENODEV, "Missing regmap\n"); + + ret = devm_mutex_init(dev, &chg->lock); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize lock\n"); + + ret = devm_mutex_init(dev, &chg->retry_lock); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize retry_lock\n"); + + psy_cfg.fwnode = dev_fwnode(dev); + psy_cfg.drv_data = chg; + chg->psy = devm_power_supply_register(dev, &max77759_charger_desc, + &psy_cfg); + if (IS_ERR(chg->psy)) + return dev_err_probe(dev, PTR_ERR(chg->psy), + "Failed to register psy\n"); + + ret = max77759_charger_init(chg); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize max77759 charger\n"); + + chgin_otg_reg_cfg.dev = dev; + chgin_otg_reg_cfg.driver_data = chg; + chgin_otg_reg_cfg.of_node = dev_of_node(dev); + chg->chgin_otg_rdev = devm_regulator_register(dev, &chgin_otg_reg_desc, + &chgin_otg_reg_cfg); + if (IS_ERR(chg->chgin_otg_rdev)) + return dev_err_probe(dev, PTR_ERR(chg->chgin_otg_rdev), + "Failed to register chgin otg regulator\n"); + + ret = devm_delayed_work_autocancel(dev, &chg->psy_work, psy_work_item); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize psy work\n"); + + chg->nb.notifier_call = psy_changed; + ret = power_supply_reg_notifier(&chg->nb); + if (ret) + return dev_err_probe(dev, ret, + "Unable to register psy notifier\n"); + + ret = devm_add_action_or_reset(dev, max_tcpci_unregister_psy_notifier, + &chg->nb); + if (ret) + return dev_err_probe(dev, ret, + "Failed to add devm action to unregister psy notifier\n"); + + return max77759_init_irqhandler(chg); +} + +static const struct platform_device_id max77759_charger_id[] = { + { .name = "max77759-charger", }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77759_charger_id); + +static struct platform_driver max77759_charger_driver = { + .driver = { + .name = "max77759-charger", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = max77759_charger_probe, + .id_table = max77759_charger_id, +}; +module_platform_driver(max77759_charger_driver); + +MODULE_AUTHOR("Amit Sunil Dhamne "); +MODULE_DESCRIPTION("Maxim MAX77759 charger driver"); +MODULE_LICENSE("GPL"); From 0c8935642cb08c2b64cc4264ac751298994edd8a Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Wed, 25 Mar 2026 22:22:27 +0000 Subject: [PATCH 115/176] usb: typec: tcpm/tcpci_maxim: deprecate WAR for setting charger mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TCPCI maxim driver directly writes to the charger's register space to set charger mode depending on the power role. As MAX77759 chg driver exists, this WAR is not required. Instead, use a regulator interface to source vbus when typec is in source power mode. In other power modes, this regulator will be turned off if active. Signed-off-by: Amit Sunil Dhamne Reviewed-by: Heikki Krogerus Reviewed-by: André Draszik Link: https://patch.msgid.link/20260325-max77759-charger-v9-6-4486dd297adc@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpci_maxim.h | 1 + drivers/usb/typec/tcpm/tcpci_maxim_core.c | 54 ++++++++++++++--------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.h b/drivers/usb/typec/tcpm/tcpci_maxim.h index b33540a42a95..b314606eb0f6 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.h +++ b/drivers/usb/typec/tcpm/tcpci_maxim.h @@ -60,6 +60,7 @@ struct max_tcpci_chip { struct tcpm_port *port; enum contamiant_state contaminant_state; bool veto_vconn_swap; + struct regulator *vbus_reg; }; static inline int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index 19f638650796..c0ee7e6959ed 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -35,12 +36,6 @@ */ #define TCPC_RECEIVE_BUFFER_LEN 32 -#define MAX_BUCK_BOOST_SID 0x69 -#define MAX_BUCK_BOOST_OP 0xb9 -#define MAX_BUCK_BOOST_OFF 0 -#define MAX_BUCK_BOOST_SOURCE 0xa -#define MAX_BUCK_BOOST_SINK 0x5 - static const struct regmap_range max_tcpci_tcpci_range[] = { regmap_reg_range(0x00, 0x95) }; @@ -202,32 +197,49 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) tcpm_pd_receive(chip->port, &msg, rx_type); } +static int get_vbus_regulator_handle(struct max_tcpci_chip *chip) +{ + if (IS_ERR_OR_NULL(chip->vbus_reg)) { + chip->vbus_reg = devm_regulator_get_exclusive(chip->dev, + "vbus"); + if (IS_ERR_OR_NULL(chip->vbus_reg)) { + dev_err(chip->dev, + "Failed to get vbus regulator handle\n"); + return -ENODEV; + } + } + + return 0; +} + static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink) { struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); - u8 buffer_source[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SOURCE}; - u8 buffer_sink[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SINK}; - u8 buffer_none[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_OFF}; - struct i2c_client *i2c = chip->client; int ret; - struct i2c_msg msgs[] = { - { - .addr = MAX_BUCK_BOOST_SID, - .flags = i2c->flags & I2C_M_TEN, - .len = 2, - .buf = source ? buffer_source : sink ? buffer_sink : buffer_none, - }, - }; - if (source && sink) { dev_err(chip->dev, "Both source and sink set\n"); return -EINVAL; } - ret = i2c_transfer(i2c->adapter, msgs, 1); + ret = get_vbus_regulator_handle(chip); + if (ret) { + /* + * Regulator is not necessary for sink only applications. Return + * success in cases where sink mode is being modified. + */ + return source ? ret : 1; + } - return ret < 0 ? ret : 1; + if (source) { + if (!regulator_is_enabled(chip->vbus_reg)) + ret = regulator_enable(chip->vbus_reg); + } else { + if (regulator_is_enabled(chip->vbus_reg)) + ret = regulator_disable(chip->vbus_reg); + } + + return ret < 0 ? ret : 1; } static void process_power_status(struct max_tcpci_chip *chip) From 81ebd43cc0d6d106ce7b6ccbf7b5e40ca7f5503d Mon Sep 17 00:00:00 2001 From: Michael Zimmermann Date: Fri, 27 Mar 2026 20:22:09 +0100 Subject: [PATCH 116/176] usb: gadget: f_hid: don't call cdev_init while cdev in use When calling unbind, then bind again, cdev_init reinitialized the cdev, even though there may still be references to it. That's the case when the /dev/hidg* device is still opened. This obviously unsafe behavior like oopes. This fixes this by using cdev_alloc to put the cdev on the heap. That way, we can simply allocate a new one in hidg_bind. Closes: https://lore.kernel.org/linux-usb/CAN9vWDKZn0Ts5JyV2_xcAmbnBEi0znMLg_USMFrShRryXrgWGQ@mail.gmail.com/T/#m2cb0dba3633b67b2a679c98499508267d1508881 Cc: stable Signed-off-by: Michael Zimmermann Link: https://patch.msgid.link/20260327192209.59945-1-sigmaepsilon92@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_hid.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 8812ebf33d14..66be2e1282c1 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -106,7 +106,7 @@ struct f_hidg { struct list_head report_list; struct device dev; - struct cdev cdev; + struct cdev *cdev; struct usb_function func; struct usb_ep *in_ep; @@ -749,8 +749,9 @@ static int f_hidg_release(struct inode *inode, struct file *fd) static int f_hidg_open(struct inode *inode, struct file *fd) { + struct kobject *parent = inode->i_cdev->kobj.parent; struct f_hidg *hidg = - container_of(inode->i_cdev, struct f_hidg, cdev); + container_of(parent, struct f_hidg, dev.kobj); fd->private_data = hidg; @@ -1285,8 +1286,12 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) } /* create char device */ - cdev_init(&hidg->cdev, &f_hidg_fops); - status = cdev_device_add(&hidg->cdev, &hidg->dev); + hidg->cdev = cdev_alloc(); + if (!hidg->cdev) + goto fail_free_all; + hidg->cdev->ops = &f_hidg_fops; + + status = cdev_device_add(hidg->cdev, &hidg->dev); if (status) goto fail_free_all; @@ -1588,7 +1593,7 @@ static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) { struct f_hidg *hidg = func_to_hidg(f); - cdev_device_del(&hidg->cdev, &hidg->dev); + cdev_device_del(hidg->cdev, &hidg->dev); destroy_workqueue(hidg->workqueue); usb_free_all_descriptors(f); } From 3c7df5079cfc6133d01ae144ae76a980276cc726 Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Wed, 1 Apr 2026 21:02:08 +0000 Subject: [PATCH 117/176] mfd: max77759: fix comment style for enums Fix comment style for enums so they're kernel-doc compliant. Signed-off-by: Amit Sunil Dhamne Link: https://patch.msgid.link/20260401-fix-mfd-max77759-usb-next-v1-1-174ec23ad824@google.com Signed-off-by: Greg Kroah-Hartman --- include/linux/mfd/max77759.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/linux/mfd/max77759.h b/include/linux/mfd/max77759.h index ad1aa4c2b779..ec19be952877 100644 --- a/include/linux/mfd/max77759.h +++ b/include/linux/mfd/max77759.h @@ -131,12 +131,12 @@ #define MAX77759_MAXQ_OPCODE_USER_SPACE_READ 0x81 #define MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE 0x82 -/* +/** * enum max77759_chgr_chgin_dtls_status - Charger Input Status * @MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE: * Charger input voltage (Vchgin) < Under Voltage Threshold (Vuvlo) - * @MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: Vchgin > Vuvlo and - * Vchgin < (Battery Voltage (Vbatt) + system voltage (Vsys)) + * @MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: + * Vchgin > Vuvlo and Vchgin < (Battery Voltage (Vbatt) + system voltage (Vsys)) * @MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE: * Vchgin > Over Voltage threshold (Vovlo) * @MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID: @@ -149,7 +149,7 @@ enum max77759_chgr_chgin_dtls_status { MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID, }; -/* +/** * enum max77759_chgr_bat_dtls_states - Battery Details * @MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP: No battery and the charger suspended * @MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY: Vbatt < Vtrickle @@ -171,7 +171,7 @@ enum max77759_chgr_bat_dtls_states { MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE, }; -/* +/** * enum max77759_chgr_chg_dtls_states - Charger Details * @MAX77759_CHGR_CHG_DTLS_PREQUAL: Charger in prequalification mode * @MAX77759_CHGR_CHG_DTLS_CC: Charger in fast charge const curr mode From 7b7f2dd913829e06705035dfc41ca25fa6ec68d3 Mon Sep 17 00:00:00 2001 From: Pawel Laszczak Date: Tue, 31 Mar 2026 10:19:11 +0200 Subject: [PATCH 118/176] usb: cdnsp: Add support for device-only configuration This patch introduces support for operating the Cadence USBSSP (cdnsp) controller in a peripheral-only mode, bypassing the Dual-Role Device (DRD) logic. The change in BAR indexing (from BAR 2 to BAR 1) is a direct consequence of switching from 64-bit to 32-bit addressing in the Peripheral-only configuration. Tested on PCI platform with Device-only configuration. Platform-side changes are included to support the PCI glue layer's property injection. Signed-off-by: Pawel Laszczak Acked-by: Bjorn Helgaas # pci_ids.h Link: https://patch.msgid.link/20260331-device_only-v1-1-00378b80365c@cadence.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-plat.c | 24 +++++++++-------- drivers/usb/cdns3/cdnsp-pci.c | 47 +++++++++++++++++++++++++++------- drivers/usb/cdns3/core.c | 3 ++- drivers/usb/cdns3/core.h | 5 +++- drivers/usb/cdns3/drd.c | 16 ++++++++++-- include/linux/pci_ids.h | 1 + 6 files changed, 73 insertions(+), 23 deletions(-) diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index 71c612e27b73..33746e672cda 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -75,6 +75,7 @@ static int cdns3_plat_probe(struct platform_device *pdev) if (cdns->pdata && cdns->pdata->override_apb_timeout) cdns->override_apb_timeout = cdns->pdata->override_apb_timeout; + cdns->no_drd = device_property_read_bool(dev, "no_drd"); platform_set_drvdata(pdev, cdns); ret = platform_get_irq_byname(pdev, "host"); @@ -107,21 +108,23 @@ static int cdns3_plat_probe(struct platform_device *pdev) cdns->dev_regs = regs; - cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); - if (cdns->otg_irq < 0) - return dev_err_probe(dev, cdns->otg_irq, - "Failed to get otg IRQ\n"); + if (!cdns->no_drd) { + cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); + if (cdns->otg_irq < 0) + return dev_err_probe(dev, cdns->otg_irq, + "Failed to get otg IRQ\n"); - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); - if (!res) { - dev_err(dev, "couldn't get otg resource\n"); - return -ENXIO; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); + if (!res) { + dev_err(dev, "couldn't get otg resource\n"); + return -ENXIO; + } + + cdns->otg_res = *res; } cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable"); - cdns->otg_res = *res; - cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); if (cdns->wakeup_irq == -EPROBE_DEFER) return cdns->wakeup_irq; @@ -158,6 +161,7 @@ static int cdns3_plat_probe(struct platform_device *pdev) goto err_cdns_init; cdns->gadget_init = cdns3_plat_gadget_init; + ret = cdns_core_init_role(cdns); if (ret) goto err_cdns_init; diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c index 432007cfe695..e20c59ceb8a4 100644 --- a/drivers/usb/cdns3/cdnsp-pci.c +++ b/drivers/usb/cdns3/cdnsp-pci.c @@ -19,6 +19,7 @@ struct cdnsp_wrap { struct platform_device *plat_dev; + struct property_entry prop[3]; struct resource dev_res[6]; int devfn; }; @@ -29,10 +30,15 @@ struct cdnsp_wrap { #define RES_HOST_ID 3 #define RES_DEV_ID 4 #define RES_DRD_ID 5 - +/* DRD PCI configuration - 64-bit addressing */ +/* First PCI function */ #define PCI_BAR_HOST 0 -#define PCI_BAR_OTG 0 #define PCI_BAR_DEV 2 +/* Second PCI function */ +#define PCI_BAR_OTG 0 +/* Device only PCI configuration - 32-bit addressing */ +/* First PCI function */ +#define PCI_BAR_ONLY_DEV 1 #define PCI_DEV_FN_HOST_DEVICE 0 #define PCI_DEV_FN_OTG 1 @@ -65,6 +71,7 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, struct cdnsp_wrap *wrap; struct resource *res; struct pci_dev *func; + bool no_drd = false; int ret = 0; /* @@ -75,11 +82,14 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, pdev->devfn != PCI_DEV_FN_OTG)) return -EINVAL; + if (pdev->device == PCI_DEVICE_ID_CDNS_UDC_USBSSP) + no_drd = true; + func = cdnsp_get_second_fun(pdev); - if (!func) + if (!func && !no_drd) return -EINVAL; - if (func->class == PCI_CLASS_SERIAL_USB_XHCI || + if ((func && func->class == PCI_CLASS_SERIAL_USB_XHCI) || pdev->class == PCI_CLASS_SERIAL_USB_XHCI) { ret = -EINVAL; goto put_pci; @@ -93,7 +103,7 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, pci_set_master(pdev); - if (pci_is_enabled(func)) { + if (func && pci_is_enabled(func)) { wrap = pci_get_drvdata(func); } else { wrap = kzalloc_obj(*wrap); @@ -106,10 +116,13 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, res = wrap->dev_res; if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) { + int bar_dev = no_drd ? PCI_BAR_ONLY_DEV : PCI_BAR_DEV; + /* Function 0: host(BAR_0) + device(BAR_2). */ dev_dbg(&pdev->dev, "Initialize Device resources\n"); - res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV); - res[RES_DEV_ID].end = pci_resource_end(pdev, PCI_BAR_DEV); + + res[RES_DEV_ID].start = pci_resource_start(pdev, bar_dev); + res[RES_DEV_ID].end = pci_resource_end(pdev, bar_dev); res[RES_DEV_ID].name = "dev"; res[RES_DEV_ID].flags = IORESOURCE_MEM; dev_dbg(&pdev->dev, "USBSSP-DEV physical base addr: %pa\n", @@ -145,9 +158,20 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ; } - if (pci_is_enabled(func)) { + if (no_drd || pci_is_enabled(func)) { + u8 idx = 0; + /* set up platform device info */ pdata.override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE; + if (no_drd) { + wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "peripheral"); + wrap->prop[idx++] = PROPERTY_ENTRY_BOOL("no_drd"); + } else { + wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "otg"); + wrap->prop[idx++] = PROPERTY_ENTRY_BOOL("usb-role-switch"); + } + + wrap->prop[idx] = (struct property_entry){ }; memset(&plat_info, 0, sizeof(plat_info)); plat_info.parent = &pdev->dev; plat_info.fwnode = pdev->dev.fwnode; @@ -158,6 +182,7 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, plat_info.dma_mask = pdev->dma_mask; plat_info.data = &pdata; plat_info.size_data = sizeof(pdata); + plat_info.properties = wrap->prop; wrap->devfn = pdev->devfn; /* register platform device */ wrap->plat_dev = platform_device_register_full(&plat_info); @@ -185,13 +210,17 @@ static void cdnsp_pci_remove(struct pci_dev *pdev) if (wrap->devfn == pdev->devfn) platform_device_unregister(wrap->plat_dev); - if (!pci_is_enabled(func)) + if (!func || !pci_is_enabled(func)) kfree(wrap); pci_dev_put(func); } static const struct pci_device_id cdnsp_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP), + .class = PCI_CLASS_SERIAL_USB_DEVICE }, + { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP), + .class = PCI_CLASS_SERIAL_USB_CDNS }, { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), .class = PCI_CLASS_SERIAL_USB_DEVICE }, { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 10f00b6c3c83..72f7acba6258 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -71,7 +71,8 @@ static void cdns_role_stop(struct cdns *cdns) static void cdns_exit_roles(struct cdns *cdns) { cdns_role_stop(cdns); - cdns_drd_exit(cdns); + if (!cdns->no_drd) + cdns_drd_exit(cdns); } /** diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index dc8c4137de15..6abe231f4559 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -80,9 +80,11 @@ struct cdns3_platform_data { * @pdata: platform data from glue layer * @lock: spinlock structure * @xhci_plat_data: xhci private data structure pointer + * @gadget_init: pointer to gadget initialization function * @override_apb_timeout: hold value of APB timeout. For value 0 the default * value in CHICKEN_BITS_3 will be preserved. - * @gadget_init: pointer to gadget initialization function + * @no_drd: DRD register block is inaccessible - driver handles only + * device mode. */ struct cdns { struct device *dev; @@ -122,6 +124,7 @@ struct cdns { struct xhci_plat_priv *xhci_plat_data; int (*gadget_init)(struct cdns *cdns); u32 override_apb_timeout; + bool no_drd; }; int cdns_hw_role_switch(struct cdns *cdns); diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index 84fb38a5723a..38f3051c2188 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -107,7 +107,7 @@ void cdns_clear_vbus(struct cdns *cdns) { u32 reg; - if (cdns->version != CDNSP_CONTROLLER_V2) + if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd) return; reg = readl(&cdns->otg_cdnsp_regs->override); @@ -120,7 +120,7 @@ void cdns_set_vbus(struct cdns *cdns) { u32 reg; - if (cdns->version != CDNSP_CONTROLLER_V2) + if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd) return; reg = readl(&cdns->otg_cdnsp_regs->override); @@ -234,6 +234,9 @@ int cdns_drd_gadget_on(struct cdns *cdns) u32 ready_bit; int ret, val; + if (cdns->no_drd) + return 0; + /* switch OTG core */ writel(OTGCMD_DEV_BUS_REQ | reg, &cdns->otg_regs->cmd); @@ -265,6 +268,9 @@ void cdns_drd_gadget_off(struct cdns *cdns) { u32 val; + if (cdns->no_drd) + return; + /* * Driver should wait at least 10us after disabling Device * before turning-off Device (DEV_BUS_DROP). @@ -392,6 +398,12 @@ int cdns_drd_init(struct cdns *cdns) u32 state, reg; int ret; + if (cdns->no_drd) { + cdns->version = CDNSP_CONTROLLER_V2; + cdns->dr_mode = USB_DR_MODE_PERIPHERAL; + return 0; + } + regs = devm_ioremap_resource(cdns->dev, &cdns->otg_res); if (IS_ERR(regs)) return PTR_ERR(regs); diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 406abf629be2..a931fb201402 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2424,6 +2424,7 @@ #define PCI_DEVICE_ID_CDNS_USBSS 0x0100 #define PCI_DEVICE_ID_CDNS_USB 0x0120 #define PCI_DEVICE_ID_CDNS_USBSSP 0x0200 +#define PCI_DEVICE_ID_CDNS_UDC_USBSSP 0x0400 #define PCI_VENDOR_ID_ARECA 0x17d3 #define PCI_DEVICE_ID_ARECA_1110 0x1110 From 1897852293faca4c2be51e0a19f739622f771623 Mon Sep 17 00:00:00 2001 From: Kelvin Mbogo Date: Wed, 25 Mar 2026 13:36:38 +0300 Subject: [PATCH 119/176] usb: usbip: fix integer overflow in usbip_recv_iso() usbip_recv_iso() computes the iso descriptor buffer size as: int size = np * sizeof(*iso); where np comes straight from the wire (urb->number_of_packets, set by usbip_pack_ret_submit() before we get here). With np = 0x10000001 and sizeof(*iso) == 16 the product is 0x100000010 which truncates to 16 on a 32-bit int. kzalloc(16) succeeds but the following receive loop writes np * 16 bytes into it - game over. USBIP_MAX_ISO_PACKETS (1024) already exists in usbip_common.h for the submit path but was never enforced on the receive side. Clamp np to [1, USBIP_MAX_ISO_PACKETS] and switch to kcalloc() so the allocator itself can catch overflows in the future. Fold the existing np == 0 early return into the new bounds check. usbip_pack_ret_submit() already copied the bogus np into urb->number_of_packets before we run, so just returning -EPROTO is not enough - processcompl() in the HCD will still iterate that many iso_frame_desc entries when it completes the failed URB. Zero out urb->number_of_packets before bailing to prevent that secondary crash (confirmed on 6.12.0, processcompl+0x63 with CR2 in unmapped slab). Signed-off-by: Kelvin Mbogo Link: https://patch.msgid.link/20260325103640.8090-1-addcontent08@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/usbip_common.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c index a2b2da1255dd..8b6eb7476747 100644 --- a/drivers/usb/usbip/usbip_common.c +++ b/drivers/usb/usbip/usbip_common.c @@ -662,7 +662,7 @@ int usbip_recv_iso(struct usbip_device *ud, struct urb *urb) void *buff; struct usbip_iso_packet_descriptor *iso; int np = urb->number_of_packets; - int size = np * sizeof(*iso); + int size; int i; int ret; int total_length = 0; @@ -670,11 +670,21 @@ int usbip_recv_iso(struct usbip_device *ud, struct urb *urb) if (!usb_pipeisoc(urb->pipe)) return 0; - /* my Bluetooth dongle gets ISO URBs which are np = 0 */ - if (np == 0) - return 0; + if (np <= 0 || np > USBIP_MAX_ISO_PACKETS) { + dev_err(&urb->dev->dev, + "recv iso: invalid number_of_packets %d\n", np); + /* + * usbip_pack_ret_submit() already set urb->number_of_packets + * from the wire. Zero it so processcompl() does not iterate + * OOB descriptors on the way out. + */ + urb->number_of_packets = 0; + return -EPROTO; + } - buff = kzalloc(size, GFP_KERNEL); + size = np * sizeof(*iso); + + buff = kcalloc(np, sizeof(*iso), GFP_KERNEL); if (!buff) return -ENOMEM; From 591c1d972d8f19862ecd7279c7ef4df48b0a9b33 Mon Sep 17 00:00:00 2001 From: Kelvin Mbogo Date: Wed, 25 Mar 2026 13:36:39 +0300 Subject: [PATCH 120/176] usb: usbip: validate iso frame actual_length in usbip_recv_iso() usbip_recv_iso() sums each frame's actual_length into an int accumulator without checking the individual values first: total_length += urb->iso_frame_desc[i].actual_length; A malicious server can send actual_length = 0xFFFFFFFC for one frame and a small value for the other, making the signed sum wrap around to match urb->actual_length. The sanity check passes, and usbip_pad_iso() later computes a negative actualoffset, feeding it to memmove() as a source pointer - reads before the allocation, leaked to userspace via USBDEVFS_REAPURB. Reject any frame whose actual_length exceeds transfer_buffer_length (one frame can't carry more data than the whole buffer), and widen the accumulator to u32 so that many moderately-large frames can't wrap it either. Signed-off-by: Kelvin Mbogo Link: https://patch.msgid.link/20260325103640.8090-2-addcontent08@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/usbip_common.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c index 8b6eb7476747..fd620e960039 100644 --- a/drivers/usb/usbip/usbip_common.c +++ b/drivers/usb/usbip/usbip_common.c @@ -665,7 +665,7 @@ int usbip_recv_iso(struct usbip_device *ud, struct urb *urb) int size; int i; int ret; - int total_length = 0; + u32 total_length = 0; if (!usb_pipeisoc(urb->pipe)) return 0; @@ -706,14 +706,23 @@ int usbip_recv_iso(struct usbip_device *ud, struct urb *urb) for (i = 0; i < np; i++) { usbip_iso_packet_correct_endian(&iso[i], 0); usbip_pack_iso(&iso[i], &urb->iso_frame_desc[i], 0); + if (urb->iso_frame_desc[i].actual_length > + (unsigned int)urb->transfer_buffer_length) { + dev_err(&urb->dev->dev, + "recv iso: frame actual_length %u exceeds buffer %d\n", + urb->iso_frame_desc[i].actual_length, + urb->transfer_buffer_length); + kfree(buff); + return -EPROTO; + } total_length += urb->iso_frame_desc[i].actual_length; } kfree(buff); - if (total_length != urb->actual_length) { + if (total_length != (u32)urb->actual_length) { dev_err(&urb->dev->dev, - "total length of iso packets %d not equal to actual length of buffer %d\n", + "total length of iso packets %u not equal to actual length of buffer %d\n", total_length, urb->actual_length); if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) From 74a2287209a858470d15e2996ead2337bd293ff4 Mon Sep 17 00:00:00 2001 From: Kelvin Mbogo Date: Wed, 25 Mar 2026 13:36:40 +0300 Subject: [PATCH 121/176] usb: usbip: fix OOB read/write in usbip_pad_iso() usbip_pad_iso() repositions ISO frame data within the transfer buffer via memmove(). Neither the source offset (actualoffset, derived by subtracting wire-supplied actual_length values) nor the destination offset (iso_frame_desc[i].offset, taken directly from the wire) is bounds-checked. If a crafted actual_length wraps actualoffset negative through the subtraction (see patch 2/3 for the root cause), the memmove source points before the allocation - slab OOB read, data returned to userspace. Independently, iso_frame_desc[i].offset is never validated against transfer_buffer_length. Setting offset past the end of the buffer gives a fully controlled OOB write into whatever sits next in the slab - confirmed with offset=400 on a 392-byte buffer, 64-byte write. Add bounds checks for both the source and destination ranges before each memmove call. Use unsigned comparisons after the sign check on actualoffset to avoid signed/unsigned conversion surprises. Signed-off-by: Kelvin Mbogo Link: https://patch.msgid.link/20260325103640.8090-3-addcontent08@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/usbip_common.c | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c index fd620e960039..8ebaaeaf848e 100644 --- a/drivers/usb/usbip/usbip_common.c +++ b/drivers/usb/usbip/usbip_common.c @@ -770,6 +770,42 @@ void usbip_pad_iso(struct usbip_device *ud, struct urb *urb) */ for (i = np-1; i > 0; i--) { actualoffset -= urb->iso_frame_desc[i].actual_length; + + /* + * Validate source range: actualoffset can go negative + * via crafted actual_length values from the wire. + */ + if (actualoffset < 0 || + (unsigned int)actualoffset > + (unsigned int)urb->transfer_buffer_length || + urb->iso_frame_desc[i].actual_length > + (unsigned int)urb->transfer_buffer_length - + (unsigned int)actualoffset) { + dev_err(&urb->dev->dev, + "pad_iso: bad src off=%d len=%u bufsz=%d\n", + actualoffset, + urb->iso_frame_desc[i].actual_length, + urb->transfer_buffer_length); + return; + } + + /* + * Validate destination range: iso_frame_desc[i].offset + * is wire-supplied and must not exceed the buffer. + */ + if (urb->iso_frame_desc[i].offset > + (unsigned int)urb->transfer_buffer_length || + urb->iso_frame_desc[i].actual_length > + (unsigned int)urb->transfer_buffer_length - + urb->iso_frame_desc[i].offset) { + dev_err(&urb->dev->dev, + "pad_iso: bad dst off=%u len=%u bufsz=%d\n", + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].actual_length, + urb->transfer_buffer_length); + return; + } + memmove(urb->transfer_buffer + urb->iso_frame_desc[i].offset, urb->transfer_buffer + actualoffset, urb->iso_frame_desc[i].actual_length); From abe93f27cdd7838007703ff02d0c5f7fc0e5d7f6 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Thu, 2 Apr 2026 16:13:18 +0300 Subject: [PATCH 122/176] xhci: use BIT macro We have the macro. Use it. Signed-off-by: Oliver Neukum Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-2-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.h | 121 ++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 2b0796f6d00e..1bef4301e2b4 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -12,6 +12,7 @@ #ifndef __LINUX_XHCI_HCD_H #define __LINUX_XHCI_HCD_H +#include #include #include #include @@ -125,17 +126,17 @@ struct xhci_op_regs { * PCI config regs). HC does NOT drive a USB reset on the downstream ports. * The xHCI driver must reinitialize the xHC after setting this bit. */ -#define CMD_RESET (1 << 1) +#define CMD_RESET BIT(1) /* Event Interrupt Enable - a '1' allows interrupts from the host controller */ #define CMD_EIE XHCI_CMD_EIE /* Host System Error Interrupt Enable - get out-of-band signal for HC errors */ #define CMD_HSEIE XHCI_CMD_HSEIE /* bits 4:6 are reserved (and should be preserved on writes). */ /* light reset (port status stays unchanged) - reset completed when this is 0 */ -#define CMD_LRESET (1 << 7) +#define CMD_LRESET BIT(7) /* host controller save/restore state. */ -#define CMD_CSS (1 << 8) -#define CMD_CRS (1 << 9) +#define CMD_CSS BIT(8) +#define CMD_CRS BIT(9) /* Enable Wrap Event - '1' means xHC generates an event when MFINDEX wraps. */ #define CMD_EWE XHCI_CMD_EWE /* MFINDEX power management - '1' means xHC can stop MFINDEX counter if all root @@ -143,9 +144,9 @@ struct xhci_op_regs { * '0' means the xHC can power it off if all ports are in the disconnect, * disabled, or powered-off state. */ -#define CMD_PM_INDEX (1 << 11) +#define CMD_PM_INDEX BIT(11) /* bit 14 Extended TBC Enable, changes Isoc TRB fields to support larger TBC */ -#define CMD_ETE (1 << 14) +#define CMD_ETE BIT(14) /* bits 15:31 are reserved (and should be preserved on writes). */ #define XHCI_RESET_LONG_USEC (10 * 1000 * 1000) @@ -155,22 +156,22 @@ struct xhci_op_regs { /* HC not running - set to 1 when run/stop bit is cleared. */ #define STS_HALT XHCI_STS_HALT /* serious error, e.g. PCI parity error. The HC will clear the run/stop bit. */ -#define STS_FATAL (1 << 2) +#define STS_FATAL BIT(2) /* event interrupt - clear this prior to clearing any IP flags in IR set*/ -#define STS_EINT (1 << 3) +#define STS_EINT BIT(3) /* port change detect */ -#define STS_PORT (1 << 4) +#define STS_PORT BIT(4) /* bits 5:7 reserved and zeroed */ /* save state status - '1' means xHC is saving state */ -#define STS_SAVE (1 << 8) +#define STS_SAVE BIT(8) /* restore state status - '1' means xHC is restoring state */ -#define STS_RESTORE (1 << 9) +#define STS_RESTORE BIT(9) /* true: save or restore error */ -#define STS_SRE (1 << 10) +#define STS_SRE BIT(10) /* true: Controller Not Ready to accept doorbell or op reg writes after reset */ #define STS_CNR XHCI_STS_CNR /* true: internal Host Controller Error - SW needs to reset and reinitialize */ -#define STS_HCE (1 << 12) +#define STS_HCE BIT(12) /* bits 13:31 reserved and should be preserved */ /* @@ -182,17 +183,17 @@ struct xhci_op_regs { /* Most of the device notification types should only be used for debug. * SW does need to pay attention to function wake notifications. */ -#define DEV_NOTE_FWAKE (1 << 1) +#define DEV_NOTE_FWAKE BIT(1) /* CRCR - Command Ring Control Register - cmd_ring bitmasks */ /* bit 0 - Cycle bit indicates the ownership of the command ring */ -#define CMD_RING_CYCLE (1 << 0) +#define CMD_RING_CYCLE BIT(0) /* stop ring operation after completion of the currently executing command */ -#define CMD_RING_PAUSE (1 << 1) +#define CMD_RING_PAUSE BIT(1) /* stop ring immediately - abort the currently executing command */ -#define CMD_RING_ABORT (1 << 2) +#define CMD_RING_ABORT BIT(2) /* true: command ring is running */ -#define CMD_RING_RUNNING (1 << 3) +#define CMD_RING_RUNNING BIT(3) /* bits 63:6 - Command Ring pointer */ #define CMD_RING_PTR_MASK GENMASK_ULL(63, 6) @@ -200,9 +201,9 @@ struct xhci_op_regs { /* bits 0:7 - maximum number of device slots enabled (NumSlotsEn) */ #define MAX_DEVS(p) ((p) & 0xff) /* bit 8: U3 Entry Enabled, assert PLC when root port enters U3, xhci 1.1 */ -#define CONFIG_U3E (1 << 8) +#define CONFIG_U3E BIT(8) /* bit 9: Configuration Information Enable, xhci 1.1 */ -#define CONFIG_CIE (1 << 9) +#define CONFIG_CIE BIT(9) /* bits 10:31 - reserved and should be preserved */ /* bits 15:0 - HCD page shift bit */ @@ -235,9 +236,9 @@ struct xhci_intr_reg { /* iman bitmasks */ /* bit 0 - Interrupt Pending (IP), whether there is an interrupt pending. Write-1-to-clear. */ -#define IMAN_IP (1 << 0) +#define IMAN_IP BIT(0) /* bit 1 - Interrupt Enable (IE), whether the interrupter is capable of generating an interrupt */ -#define IMAN_IE (1 << 1) +#define IMAN_IE BIT(1) /* imod bitmasks */ /* @@ -267,7 +268,7 @@ struct xhci_intr_reg { * bit 3 - Event Handler Busy (EHB), whether the event ring is scheduled to be serviced by * a work queue (or delayed service routine)? */ -#define ERST_EHB (1 << 3) +#define ERST_EHB BIT(3) /* bits 63:4 - Event Ring Dequeue Pointer */ #define ERST_PTR_MASK GENMASK_ULL(63, 4) @@ -356,15 +357,15 @@ struct xhci_slot_ctx { #define GET_DEV_SPEED(n) (((n) & DEV_SPEED) >> 20) /* bit 24 reserved */ /* Is this LS/FS device connected through a HS hub? - bit 25 */ -#define DEV_MTT (0x1 << 25) +#define DEV_MTT BIT(25) /* Set if the device is a hub - bit 26 */ -#define DEV_HUB (0x1 << 26) +#define DEV_HUB BIT(26) /* Index of the last valid endpoint context in this device context - 27:31 */ #define LAST_CTX_MASK (0x1f << 27) #define LAST_CTX(p) ((p) << 27) #define LAST_CTX_TO_EP_NUM(p) (((p) >> 27) - 1) -#define SLOT_FLAG (1 << 0) -#define EP0_FLAG (1 << 1) +#define SLOT_FLAG BIT(0) +#define EP0_FLAG BIT(1) /* dev_info2 bitmasks */ /* Max Exit Latency (ms) - worst case time to wake up all links in dev path */ @@ -463,7 +464,7 @@ struct xhci_ep_ctx { #define EP_MAXPSTREAMS(p) (((p) << 10) & EP_MAXPSTREAMS_MASK) #define CTX_TO_EP_MAXPSTREAMS(p) (((p) & EP_MAXPSTREAMS_MASK) >> 10) /* Endpoint is set up with a Linear Stream Array (vs. Secondary Stream Array) */ -#define EP_HAS_LSA (1 << 15) +#define EP_HAS_LSA BIT(15) /* hosts with LEC=1 use bits 31:24 as ESIT high bits. */ #define CTX_TO_MAX_ESIT_PAYLOAD_HI(p) (((p) >> 24) & 0xff) @@ -498,7 +499,7 @@ struct xhci_ep_ctx { #define CTX_TO_MAX_ESIT_PAYLOAD(p) (((p) >> 16) & 0xffff) /* deq bitmasks */ -#define EP_CTX_CYCLE_MASK (1 << 0) +#define EP_CTX_CYCLE_MASK BIT(0) /* bits 63:4 - TR Dequeue Pointer */ #define TR_DEQ_PTR_MASK GENMASK_ULL(63, 4) @@ -661,18 +662,18 @@ struct xhci_virt_ep { struct xhci_ring *new_ring; unsigned int err_count; unsigned int ep_state; -#define SET_DEQ_PENDING (1 << 0) -#define EP_HALTED (1 << 1) /* For stall handling */ -#define EP_STOP_CMD_PENDING (1 << 2) /* For URB cancellation */ +#define SET_DEQ_PENDING BIT(0) +#define EP_HALTED BIT(1) /* For stall handling */ +#define EP_STOP_CMD_PENDING BIT(2) /* For URB cancellation */ /* Transitioning the endpoint to using streams, don't enqueue URBs */ -#define EP_GETTING_STREAMS (1 << 3) -#define EP_HAS_STREAMS (1 << 4) +#define EP_GETTING_STREAMS BIT(3) +#define EP_HAS_STREAMS BIT(4) /* Transitioning the endpoint to not using streams, don't enqueue URBs */ -#define EP_GETTING_NO_STREAMS (1 << 5) -#define EP_HARD_CLEAR_TOGGLE (1 << 6) -#define EP_SOFT_CLEAR_TOGGLE (1 << 7) +#define EP_GETTING_NO_STREAMS BIT(5) +#define EP_HARD_CLEAR_TOGGLE BIT(6) +#define EP_SOFT_CLEAR_TOGGLE BIT(7) /* usb_hub_clear_tt_buffer is in progress */ -#define EP_CLEARING_TT (1 << 8) +#define EP_CLEARING_TT BIT(8) /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; struct xhci_hcd *xhci; @@ -954,7 +955,7 @@ struct xhci_link_trb { }; /* control bitfields */ -#define LINK_TOGGLE (0x1<<1) +#define LINK_TOGGLE BIT(1) /* Command completion event TRB */ struct xhci_event_cmd { @@ -968,13 +969,13 @@ struct xhci_event_cmd { #define COMP_PARAM(p) ((p) & 0xffffff) /* Command Completion Parameter */ /* Address device - disable SetAddress */ -#define TRB_BSR (1<<9) +#define TRB_BSR BIT(9) /* Configure Endpoint - Deconfigure */ -#define TRB_DC (1<<9) +#define TRB_DC BIT(9) /* Stop Ring - Transfer State Preserve */ -#define TRB_TSP (1<<9) +#define TRB_TSP BIT(9) enum xhci_ep_reset_type { EP_HARD_RESET, @@ -1017,13 +1018,13 @@ enum xhci_setup_dev { #define SCT_FOR_TRB(p) (((p) & 0x7) << 1) /* Link TRB specific fields */ -#define TRB_TC (1<<1) +#define TRB_TC BIT(1) /* Port Status Change Event TRB fields */ /* Port ID - bits 31:24 */ #define GET_PORT_ID(p) (((p) & (0xff << 24)) >> 24) -#define EVENT_DATA (1 << 2) +#define EVENT_DATA BIT(2) /* Normal TRB fields */ /* transfer_len bitmasks - bits 0:16 */ @@ -1038,36 +1039,36 @@ enum xhci_setup_dev { #define GET_INTR_TARGET(p) (((p) >> 22) & 0x3ff) /* Cycle bit - indicates TRB ownership by HC or HCD */ -#define TRB_CYCLE (1<<0) +#define TRB_CYCLE BIT(0) /* * Force next event data TRB to be evaluated before task switch. * Used to pass OS data back after a TD completes. */ -#define TRB_ENT (1<<1) +#define TRB_ENT BIT(1) /* Interrupt on short packet */ -#define TRB_ISP (1<<2) +#define TRB_ISP BIT(2) /* Set PCIe no snoop attribute */ -#define TRB_NO_SNOOP (1<<3) +#define TRB_NO_SNOOP BIT(3) /* Chain multiple TRBs into a TD */ -#define TRB_CHAIN (1<<4) +#define TRB_CHAIN BIT(4) /* Interrupt on completion */ -#define TRB_IOC (1<<5) +#define TRB_IOC BIT(5) /* The buffer pointer contains immediate data */ -#define TRB_IDT (1<<6) +#define TRB_IDT BIT(6) /* TDs smaller than this might use IDT */ #define TRB_IDT_MAX_SIZE 8 /* Block Event Interrupt */ -#define TRB_BEI (1<<9) +#define TRB_BEI BIT(9) /* Control transfer TRB specific fields */ -#define TRB_DIR_IN (1<<16) +#define TRB_DIR_IN BIT(16) #define TRB_TX_TYPE(p) ((p) << 16) #define TRB_DATA_OUT 2 #define TRB_DATA_IN 3 /* Isochronous TRB specific fields */ -#define TRB_SIA (1<<31) +#define TRB_SIA BIT(31) #define TRB_FRAME_ID(p) (((p) & 0x7ff) << 20) #define GET_FRAME_ID(p) (((p) >> 20) & 0x7ff) /* Total burst count field, Rsvdz on xhci 1.1 with Extended TBC enabled (ETE) */ @@ -1535,9 +1536,9 @@ struct xhci_hcd { struct xhci_interrupter **interrupters; struct xhci_ring *cmd_ring; unsigned int cmd_ring_state; -#define CMD_RING_STATE_RUNNING (1 << 0) -#define CMD_RING_STATE_ABORTED (1 << 1) -#define CMD_RING_STATE_STOPPED (1 << 2) +#define CMD_RING_STATE_RUNNING BIT(0) +#define CMD_RING_STATE_ABORTED BIT(1) +#define CMD_RING_STATE_STOPPED BIT(2) struct list_head cmd_list; unsigned int cmd_ring_reserved_trbs; struct delayed_work cmd_timer; @@ -1578,9 +1579,9 @@ struct xhci_hcd { * * There are no reports of xHCI host controllers that display this issue. */ -#define XHCI_STATE_DYING (1 << 0) -#define XHCI_STATE_HALTED (1 << 1) -#define XHCI_STATE_REMOVING (1 << 2) +#define XHCI_STATE_DYING BIT(0) +#define XHCI_STATE_HALTED BIT(1) +#define XHCI_STATE_REMOVING BIT(2) unsigned long long quirks; #define XHCI_LINK_TRB_QUIRK BIT_ULL(0) #define XHCI_RESET_EP_QUIRK BIT_ULL(1) /* Deprecated */ From 88a985cf9533c6585ee3a784cb8320df409be389 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Thu, 2 Apr 2026 16:13:19 +0300 Subject: [PATCH 123/176] usb: xhci: Simplify clearing the Event Interrupt bit USBSTS is mostly RW1C, so to clear EINT we should write just this one bit. Remove pointless code which ORs the bit with current value of the register, even though the bit is already known to be set, and writes the result back, which clears all active RW1C flags. We used to inadvertently clear PCD and SRE in this way. PCD isn't used by the driver and SRE is only used at resume, so clearing them should make no difference. Don't clear them anymore. Tested by connecting and mounting a storage device on a few HCs. Before: xhci_irq USBSTS 0x00000018 EINT PCD -> 0x00000000 xhci_irq USBSTS 0x00000008 EINT -> 0x00000000 After: xhci_irq USBSTS 0x00000018 EINT PCD -> 0x00000010 PCD xhci_irq USBSTS 0x00000018 EINT PCD -> 0x00000010 PCD Some flags are RsvdZ - should be written as zero regardless of the value read, so technically it was a bug. But no problems are known. Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-3-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1cbefee3c4ca..3589af0e2768 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3208,10 +3208,9 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) /* * Clear the op reg interrupt status first, * so we can receive interrupts from other MSI-X interrupters. - * Write 1 to clear the interrupt status. + * USBSTS bits are write 1 to clear. */ - status |= STS_EINT; - writel(status, &xhci->op_regs->status); + writel(STS_EINT, &xhci->op_regs->status); /* This is the handler of the primary interrupter */ xhci_handle_events(xhci, xhci->interrupters[0], false); From 452af0f9ffe1b25cb63698aac24e2fc782c928a8 Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Thu, 2 Apr 2026 16:13:20 +0300 Subject: [PATCH 124/176] usb: xhci: Fix debugfs bandwidth reporting Replace kernel USB speed numbers with xHCI protocol IDs expected by HW. They are numerically equal up to high speed, but instead of SuperSpeed we were querying SuperSpeed+. Gen1 hardware rejects such commands with TRB Error, which resulted in zero available bandwidth being shown. While at that, report failures properly. No attempt made at "tunneling" all possible comp codes through errno, debugfs users may inspect the result through event-ring/trbs. Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-4-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-debugfs.c | 10 +++++++--- drivers/usb/host/xhci.c | 9 ++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c index ade178ab34a7..d07276192256 100644 --- a/drivers/usb/host/xhci-debugfs.c +++ b/drivers/usb/host/xhci-debugfs.c @@ -700,6 +700,10 @@ static int xhci_port_bw_show(struct xhci_hcd *xhci, u8 dev_speed, seq_printf(s, "port[%d] available bw: %d%%.\n", i, ctx->bytes[i]); err_out: + if (ret == -EIO) { + seq_puts(s, "Get Port Bandwidth failed\n"); + ret = 0; + } pm_runtime_put_sync(dev); xhci_free_port_bw_ctx(xhci, ctx); return ret; @@ -710,7 +714,7 @@ static int xhci_ss_bw_show(struct seq_file *s, void *unused) int ret; struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; - ret = xhci_port_bw_show(xhci, USB_SPEED_SUPER, s); + ret = xhci_port_bw_show(xhci, DEV_PORT_SPEED(XDEV_SS), s); return ret; } @@ -719,7 +723,7 @@ static int xhci_hs_bw_show(struct seq_file *s, void *unused) int ret; struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; - ret = xhci_port_bw_show(xhci, USB_SPEED_HIGH, s); + ret = xhci_port_bw_show(xhci, DEV_PORT_SPEED(XDEV_HS), s); return ret; } @@ -728,7 +732,7 @@ static int xhci_fs_bw_show(struct seq_file *s, void *unused) int ret; struct xhci_hcd *xhci = (struct xhci_hcd *)s->private; - ret = xhci_port_bw_show(xhci, USB_SPEED_FULL, s); + ret = xhci_port_bw_show(xhci, DEV_PORT_SPEED(XDEV_FS), s); return ret; } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index ef6d8662adec..eb6927779b1e 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -3201,7 +3201,12 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) } EXPORT_SYMBOL_GPL(xhci_reset_bandwidth); -/* Get the available bandwidth of the ports under the xhci roothub */ +/* + * Get the available bandwidth of the ports under the xhci roothub. + * EIO means the command failed: command not implemented or unsupported + * speed (TRB Error), some ASMedia complete with Parameter Error when + * querying the root hub (slot_id = 0), or other error or timeout. + */ int xhci_get_port_bandwidth(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, u8 dev_speed) { @@ -3230,6 +3235,8 @@ int xhci_get_port_bandwidth(struct xhci_hcd *xhci, struct xhci_container_ctx *ct spin_unlock_irqrestore(&xhci->lock, flags); wait_for_completion(cmd->completion); + if (cmd->status != COMP_SUCCESS) + ret = -EIO; err_out: kfree(cmd->completion); kfree(cmd); From 5ef760cf1c83f456b699196e5ad5c3a33b2d76f6 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:21 +0300 Subject: [PATCH 125/176] usb: xhci: simplify CMRT initialization logic The function compliance_mode_recovery_timer_init() is called from xhci_init() because the Compliance Mode Recovery Timer (CMRT) must be set up before xhci_run() when the xhci driver is re-initialized. To handle this case, the boolean flag 'comp_timer_running' was introduced to track whether xhci_run() had already been called, ensuring that xhci_resume() would not invoke compliance_mode_recovery_timer_init() a second time. This can be simplified by moving the 'done' label in xhci_resume() to after the compliance_mode_recovery_timer_init() call. With this change, the timer initialization runs only when the xhci driver has not been re-initialized, making the 'comp_timer_running' flag unnecessary and allowing it to be removed. Reviewed-by: Andy Shevchenko Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-5-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index eb6927779b1e..8e0b6a673868 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1084,7 +1084,6 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) u32 command, temp = 0; struct usb_hcd *hcd = xhci_to_hcd(xhci); int retval = 0; - bool comp_timer_running = false; bool pending_portevent = false; bool suspended_usb3_devs = false; @@ -1196,7 +1195,6 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) retval = xhci_init(hcd); if (retval) return retval; - comp_timer_running = true; xhci_dbg(xhci, "Start the primary HCD\n"); retval = xhci_run(hcd); @@ -1265,16 +1263,16 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) usb_hcd_resume_root_hub(hcd); } } -done: + /* * If system is subject to the Quirk, Compliance Mode Timer needs to * be re-initialized Always after a system resume. Ports are subject * to suffer the Compliance Mode issue again. It doesn't matter if * ports have entered previously to U0 before system's suspension. */ - if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && !comp_timer_running) + if (xhci->quirks & XHCI_COMP_MODE_QUIRK) compliance_mode_recovery_timer_init(xhci); - +done: if (xhci->quirks & XHCI_ASMEDIA_MODIFY_FLOWCONTROL) usb_asmedia_modifyflowcontrol(to_pci_dev(hcd->self.controller)); From ab915ec99c06f04edfac40976613a809e0edbfa6 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:22 +0300 Subject: [PATCH 126/176] usb: xhci: relocate Restore/Controller error check A Restore Error or Host Controller Error indicates that the host controller failed to resume after suspend. In such cases, the xhci driver is fully re-initialized, similar to a post-hibernation scenario. The existing error check is only relevant when 'power_lost' is false. If 'power_lost' is true, a Restore or Controller error has no effect: no warning is printed and the 'power_lost' state remains unchanged. Move the entire error check into the if '!power_lost' condition to make this dependency explicit and simplify the resume logic. Reviewed-by: Andy Shevchenko Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-6-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 8e0b6a673868..fdd3a19b7c9c 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1140,16 +1140,13 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) spin_unlock_irq(&xhci->lock); return -ETIMEDOUT; } - } - temp = readl(&xhci->op_regs->status); - - /* re-initialize the HC on Restore Error, or Host Controller Error */ - if ((temp & (STS_SRE | STS_HCE)) && - !(xhci->xhc_state & XHCI_STATE_REMOVING)) { - if (!power_lost) + /* re-initialize the HC on Restore Error, or Host Controller Error */ + temp = readl(&xhci->op_regs->status); + if ((temp & (STS_SRE | STS_HCE)) && !(xhci->xhc_state & XHCI_STATE_REMOVING)) { xhci_warn(xhci, "xHC error in resume, USBSTS 0x%x, Reinit\n", temp); - power_lost = true; + power_lost = true; + } } if (power_lost) { From 0837c87f95295798bcb16981271fbb9adf766eb1 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:23 +0300 Subject: [PATCH 127/176] usb: xhci: factor out roothub bandwidth cleanup Introduce xhci_rh_bw_cleanup() to release all bandwidth tracking structures associated with xHCI roothub ports. The new helper clears: * TT bandwidth entries * Per-interval endpoint lists This refactors and consolidates the existing per-port cleanup logic previously embedded in xhci_mem_cleanup(), reducing duplication and making the teardown sequence easier to follow. The helper will also be reused for upcoming S4 resume handling. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-7-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 50 +++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 75bc1eb98b76..d4a9dbed8f16 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1895,10 +1895,36 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup } EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter); +/* Cleanup roothub bandwidth data */ +static void xhci_rh_bw_cleanup(struct xhci_hcd *xhci) +{ + struct xhci_root_port_bw_info *rh_bw; + struct xhci_tt_bw_info *tt_info, *tt_next; + struct list_head *eps, *ep, *ep_next; + + for (int i = 0; i < xhci->max_ports; i++) { + rh_bw = &xhci->rh_bw[i]; + + /* Clear and free all TT bandwidth entries */ + list_for_each_entry_safe(tt_info, tt_next, &rh_bw->tts, tt_list) { + list_del(&tt_info->tt_list); + kfree(tt_info); + } + + /* Clear per-interval endpoint lists */ + for (int j = 0; j < XHCI_MAX_INTERVAL; j++) { + eps = &rh_bw->bw_table.interval_bw[j].endpoints; + + list_for_each_safe(ep, ep_next, eps) + list_del_init(ep); + } + } +} + void xhci_mem_cleanup(struct xhci_hcd *xhci) { struct device *dev = xhci_to_hcd(xhci)->self.sysdev; - int i, j; + int i; cancel_delayed_work_sync(&xhci->cmd_timer); @@ -1917,15 +1943,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed command ring"); xhci_cleanup_command_queue(xhci); - for (i = 0; i < xhci->max_ports && xhci->rh_bw; i++) { - struct xhci_interval_bw_table *bwt = &xhci->rh_bw[i].bw_table; - for (j = 0; j < XHCI_MAX_INTERVAL; j++) { - struct list_head *ep = &bwt->interval_bw[j].endpoints; - while (!list_empty(ep)) - list_del_init(ep->next); - } - } - for (i = xhci->max_slots; i > 0; i--) xhci_free_virt_devices_depth_first(xhci, i); @@ -1959,18 +1976,9 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) scratchpad_free(xhci); - if (!xhci->rh_bw) - goto no_bw; + if (xhci->rh_bw) + xhci_rh_bw_cleanup(xhci); - for (i = 0; i < xhci->max_ports; i++) { - struct xhci_tt_bw_info *tt, *n; - list_for_each_entry_safe(tt, n, &xhci->rh_bw[i].tts, tt_list) { - list_del(&tt->tt_list); - kfree(tt); - } - } - -no_bw: xhci->cmd_ring_reserved_trbs = 0; xhci->usb2_rhub.num_ports = 0; xhci->usb3_rhub.num_ports = 0; From b1ebf19216c84f13b4f56f271548a62101d75d9d Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:24 +0300 Subject: [PATCH 128/176] usb: xhci: move reserving command ring trb Move the command ring TRB reservation from xhci_mem_init() to xhci_init(). Function xhci_mem_init() is intended for memory allocation, while xhci_init() is for initialization. This split allows xhci_init() to be reused when resuming from S4 suspend-to-disk. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-8-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 7 ------- drivers/usb/host/xhci.c | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index d4a9dbed8f16..45638ab13635 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2485,13 +2485,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "First segment DMA is 0x%pad", &xhci->cmd_ring->first_seg->dma); - /* - * Reserve one command ring TRB for disabling LPM. - * Since the USB core grabs the shared usb_bus bandwidth mutex before - * disabling LPM, we only need to reserve one TRB for all devices. - */ - xhci->cmd_ring_reserved_trbs++; - /* Allocate and set up primary interrupter 0 with an event ring. */ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating primary event ring"); xhci->interrupters = kcalloc_node(xhci->max_interrupters, sizeof(*xhci->interrupters), diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index fdd3a19b7c9c..b9fa941425c5 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -564,6 +564,12 @@ static int xhci_init(struct usb_hcd *hcd) /* Set the Number of Device Slots Enabled to the maximum supported value */ xhci_enable_max_dev_slots(xhci); + /* + * Reserve one command ring TRB for disabling LPM. + * Since the USB core grabs the shared usb_bus bandwidth mutex before + * disabling LPM, we only need to reserve one TRB for all devices. + */ + xhci->cmd_ring_reserved_trbs = 1; /* Set the address in the Command Ring Control register */ xhci_set_cmd_ring_deq(xhci); From e4573f49378f610ee4805ebc60896470ca54747a Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:25 +0300 Subject: [PATCH 129/176] usb: xhci: move ring initialization Move ring initialization from xhci_ring_alloc() to xhci_ring_init(). Call xhci_ring_init() after xhci_ring_alloc(); in the future, it can also be used to re-initialize the ring during resume. Additionally, remove xhci_dbg_trace() from xhci_mem_init(). The command ring's first segment DMA address is now printed during the trace call in xhci_ring_init(). This refactoring lays also the groundwork for eventually replacing: * xhci_dbc_ring_init() * xhci_clear_command_ring() Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-9-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 19 ++++++++++++++----- drivers/usb/host/xhci.c | 3 +++ drivers/usb/host/xhci.h | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 45638ab13635..ca4463eebc49 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -129,6 +129,13 @@ static void xhci_initialize_ring_segments(struct xhci_hcd *xhci, struct xhci_rin ring->last_seg->trbs[TRBS_PER_SEGMENT - 1].link.control |= cpu_to_le32(LINK_TOGGLE); } +void xhci_ring_init(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + xhci_initialize_ring_segments(xhci, ring); + xhci_initialize_ring_info(ring); + trace_xhci_ring_alloc(ring); +} + /* * Link the src ring segments to the dst ring. * Set Toggle Cycle for the new ring if needed. @@ -389,9 +396,6 @@ struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci, unsigned int num_segs, if (ret) goto fail; - xhci_initialize_ring_segments(xhci, ring); - xhci_initialize_ring_info(ring); - trace_xhci_ring_alloc(ring); return ring; fail: @@ -668,6 +672,8 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, cur_ring = stream_info->stream_rings[cur_stream]; if (!cur_ring) goto cleanup_rings; + + xhci_ring_init(xhci, cur_ring); cur_ring->stream_id = cur_stream; cur_ring->trb_address_map = &stream_info->trb_address_map; /* Set deq ptr, cycle bit, and stream context type */ @@ -1011,6 +1017,8 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, if (!dev->eps[0].ring) goto fail; + xhci_ring_init(xhci, dev->eps[0].ring); + dev->udev = udev; /* Point to output device context in dcbaa. */ @@ -1492,6 +1500,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, virt_dev->eps[ep_index].skip = false; ep_ring = virt_dev->eps[ep_index].new_ring; + xhci_ring_init(xhci, ep_ring); /* Fill the endpoint context */ ep_ctx->ep_info = cpu_to_le32(EP_MAX_ESIT_PAYLOAD_HI(max_esit_payload) | @@ -2370,6 +2379,8 @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, if (!ir) return NULL; + xhci_ring_init(xhci, ir->event_ring); + spin_lock_irq(&xhci->lock); if (!intr_num) { /* Find available secondary interrupter, interrupter 0 is reserved for primary */ @@ -2482,8 +2493,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) goto fail; xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocated command ring at %p", xhci->cmd_ring); - xhci_dbg_trace(xhci, trace_xhci_dbg_init, "First segment DMA is 0x%pad", - &xhci->cmd_ring->first_seg->dma); /* Allocate and set up primary interrupter 0 with an event ring. */ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Allocating primary event ring"); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index b9fa941425c5..dd495dc740c3 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -564,6 +564,8 @@ static int xhci_init(struct usb_hcd *hcd) /* Set the Number of Device Slots Enabled to the maximum supported value */ xhci_enable_max_dev_slots(xhci); + /* Initialize the Command ring */ + xhci_ring_init(xhci, xhci->cmd_ring); /* * Reserve one command ring TRB for disabling LPM. * Since the USB core grabs the shared usb_bus bandwidth mutex before @@ -583,6 +585,7 @@ static int xhci_init(struct usb_hcd *hcd) xhci_set_dev_notifications(xhci); /* Initialize the Primary interrupter */ + xhci_ring_init(xhci, xhci->interrupters[0]->event_ring); xhci_add_interrupter(xhci, 0); xhci->interrupters[0]->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 1bef4301e2b4..06f6da4d982f 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1824,6 +1824,7 @@ void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring); int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, unsigned int num_trbs, gfp_t flags); void xhci_initialize_ring_info(struct xhci_ring *ring); +void xhci_ring_init(struct xhci_hcd *xhci, struct xhci_ring *ring); void xhci_free_endpoint_ring(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, unsigned int ep_index); From 45484754a0ddfec798c8ddf0de489e68f4be4bcf Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:26 +0300 Subject: [PATCH 130/176] usb: xhci: move initialization for lifetime objects Initialize objects that exist for the lifetime of the driver only once, rather than repeatedly. These objects do not require re-initialization after events such as S4 (suspend-to-disk). Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-10-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 1 - drivers/usb/host/xhci.c | 15 ++++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index ca4463eebc49..2cd6111c9707 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2009,7 +2009,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci->port_caps = NULL; xhci->interrupters = NULL; - xhci->page_size = 0; xhci->usb2_rhub.bus_state.bus_suspended = 0; xhci->usb3_rhub.bus_state.bus_suspended = 0; } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index dd495dc740c3..674bd40e4e2d 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -549,13 +549,6 @@ static int xhci_init(struct usb_hcd *hcd) int retval; xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__); - spin_lock_init(&xhci->lock); - - INIT_LIST_HEAD(&xhci->cmd_list); - INIT_DELAYED_WORK(&xhci->cmd_timer, xhci_handle_command_timeout); - init_completion(&xhci->cmd_ring_stop_completion); - xhci_hcd_page_size(xhci); - memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs)); retval = xhci_mem_init(xhci, GFP_KERNEL); if (retval) @@ -5532,6 +5525,14 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); } + spin_lock_init(&xhci->lock); + INIT_LIST_HEAD(&xhci->cmd_list); + INIT_DELAYED_WORK(&xhci->cmd_timer, xhci_handle_command_timeout); + init_completion(&xhci->cmd_ring_stop_completion); + xhci_hcd_page_size(xhci); + + memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs)); + xhci_dbg(xhci, "Calling HCD init\n"); /* Initialize HCD and host controller data structures. */ retval = xhci_init(hcd); From b4dd01eb9bd162193b250dc6d6b7e6b5eb688564 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:27 +0300 Subject: [PATCH 131/176] usb: xhci: split core allocation and initialization Separate allocation and initialization in the xHCI core: * xhci_mem_init() now only handles memory allocation. * xhci_init() now only handles initialization. This split allows xhci_init() to be reused when resuming from S4 suspend-to-disk. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-11-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 3 +++ drivers/usb/host/xhci.c | 30 ++++++++++-------------------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 2cd6111c9707..f1b4f06d4b8b 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2421,6 +2421,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) struct device *dev = xhci_to_hcd(xhci)->self.sysdev; dma_addr_t dma; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__); + /* * xHCI section 5.4.6 - Device Context array must be * "physically contiguous and 64-byte (cache line) aligned". @@ -2510,6 +2512,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) if (xhci_setup_port_arrays(xhci, flags)) goto fail; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__); return 0; fail: diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 674bd40e4e2d..9e2e2c2ed0e0 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -536,24 +536,13 @@ static void xhci_set_dev_notifications(struct xhci_hcd *xhci) writel(dev_notf, &xhci->op_regs->dev_notification); } -/* - * Initialize memory for HCD and xHC (one-time init). - * - * Program the PAGESIZE register, initialize the device context array, create - * device contexts (?), set up a command ring segment (or two?), create event - * ring (one for now). - */ -static int xhci_init(struct usb_hcd *hcd) +/* Setup basic xHCI registers */ +static void xhci_init(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); - int retval; xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Starting %s", __func__); - retval = xhci_mem_init(xhci, GFP_KERNEL); - if (retval) - return retval; - /* Set the Number of Device Slots Enabled to the maximum supported value */ xhci_enable_max_dev_slots(xhci); @@ -589,7 +578,6 @@ static int xhci_init(struct usb_hcd *hcd) } xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__); - return 0; } /*-------------------------------------------------------------------------*/ @@ -1190,11 +1178,12 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) * first with the primary HCD, and then with the secondary HCD. * If we don't do the same, the host will never be started. */ - xhci_dbg(xhci, "Initialize the xhci_hcd\n"); - retval = xhci_init(hcd); + retval = xhci_mem_init(xhci, GFP_KERNEL); if (retval) return retval; + xhci_init(hcd); + xhci_dbg(xhci, "Start the primary HCD\n"); retval = xhci_run(hcd); if (!retval && xhci->shared_hcd) { @@ -5533,12 +5522,13 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) memset(xhci->devs, 0, MAX_HC_SLOTS * sizeof(*xhci->devs)); - xhci_dbg(xhci, "Calling HCD init\n"); - /* Initialize HCD and host controller data structures. */ - retval = xhci_init(hcd); + /* Allocate xHCI data structures */ + retval = xhci_mem_init(xhci, GFP_KERNEL); if (retval) return retval; - xhci_dbg(xhci, "Called HCD init\n"); + + /* Initialize HCD and host controller data structures */ + xhci_init(hcd); if (xhci_hcd_is_usb3(hcd)) xhci_hcd_init_usb3_data(xhci, hcd); From 951564d2cabd36fa3ee09d89c61bd33e6b73791b Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:28 +0300 Subject: [PATCH 132/176] usb: xhci: improve debug messages during suspend Improve debug output for suspend failures, particularly when the controller handshake does not complete. This will become important as upcoming patches significantly rework the resume path, making more detailed suspend-side messages valuable for debugging. Add an explicit check of the Save/Restore Error (SRE) flag after a successful Save State (CSS) operation. The xHCI specification (note in section 4.23.2) states: "After a Save or Restore State operation completes, the Save/Restore Error (SRE) flag in USBSTS should be checked to ensure the operation completed successfully." Currently, the SRE error is only observed and warning is printed. This patch does not introduce deeper error handling, as the correct response is unclear and changes to suspend behavior may risk regressions once the resume path is updated. Additionally, simplify and clean up the suspend USBSTS CSS/SSS handling code, improving readability and quirk handling for AMD SNPS xHC controllers that occasionally do not clear the SSS bit. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-12-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 65 +++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 9e2e2c2ed0e0..2c573aad4464 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -957,11 +957,11 @@ static bool xhci_pending_portevent(struct xhci_hcd *xhci) */ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) { - int rc = 0; + int err; unsigned int delay = XHCI_MAX_HALT_USEC * 2; struct usb_hcd *hcd = xhci_to_hcd(xhci); u32 command; - u32 res; + u32 usbsts; if (!hcd->state) return 0; @@ -1007,11 +1007,10 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) /* Some chips from Fresco Logic need an extraordinary delay */ delay *= (xhci->quirks & XHCI_SLOW_SUSPEND) ? 10 : 1; - if (xhci_handshake(&xhci->op_regs->status, - STS_HALT, STS_HALT, delay)) { - xhci_warn(xhci, "WARN: xHC CMD_RUN timeout\n"); - spin_unlock_irq(&xhci->lock); - return -ETIMEDOUT; + err = xhci_handshake(&xhci->op_regs->status, STS_HALT, STS_HALT, delay); + if (err) { + xhci_warn(xhci, "Clearing Run/Stop bit failed %d\n", err); + goto handshake_error; } xhci_clear_command_ring(xhci); @@ -1022,28 +1021,34 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) command = readl(&xhci->op_regs->command); command |= CMD_CSS; writel(command, &xhci->op_regs->command); + + err = xhci_handshake(&xhci->op_regs->status, STS_SAVE, 0, 20 * USEC_PER_MSEC); + usbsts = readl(&xhci->op_regs->status); xhci->broken_suspend = 0; - if (xhci_handshake(&xhci->op_regs->status, - STS_SAVE, 0, 20 * 1000)) { - /* - * AMD SNPS xHC 3.0 occasionally does not clear the - * SSS bit of USBSTS and when driver tries to poll - * to see if the xHC clears BIT(8) which never happens - * and driver assumes that controller is not responding - * and times out. To workaround this, its good to check - * if SRE and HCE bits are not set (as per xhci - * Section 5.4.2) and bypass the timeout. - */ - res = readl(&xhci->op_regs->status); - if ((xhci->quirks & XHCI_SNPS_BROKEN_SUSPEND) && - (((res & STS_SRE) == 0) && - ((res & STS_HCE) == 0))) { - xhci->broken_suspend = 1; - } else { - xhci_warn(xhci, "WARN: xHC save state timeout\n"); - spin_unlock_irq(&xhci->lock); - return -ETIMEDOUT; + if (err) { + /* + * AMD SNPS xHC 3.0 occasionally does not clear the + * SSS bit of USBSTS and when driver tries to poll + * to see if the xHC clears BIT(8) which never happens + * and driver assumes that controller is not responding + * and times out. To workaround this, its good to check + * if SRE and HCE bits are not set (as per xhci + * Section 5.4.2) and bypass the timeout. + */ + if (!(xhci->quirks & XHCI_SNPS_BROKEN_SUSPEND)) { + xhci_warn(xhci, "Controller Save State failed %d\n", err); + goto handshake_error; } + + if (usbsts & (STS_SRE | STS_HCE)) { + xhci_warn(xhci, "Controller Save State failed, USBSTS 0x%08x\n", usbsts); + goto handshake_error; + } + + xhci_dbg(xhci, "SNPS broken suspend, save state unreliable\n"); + xhci->broken_suspend = 1; + } else if (usbsts & STS_SRE) { + xhci_warn(xhci, "Suspend Save Error (SRE), USBSTS 0x%08x\n", usbsts); } spin_unlock_irq(&xhci->lock); @@ -1059,7 +1064,11 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup) __func__); } - return rc; + return 0; + +handshake_error: + spin_unlock_irq(&xhci->lock); + return -ETIMEDOUT; } EXPORT_SYMBOL_GPL(xhci_suspend); From 2a70e5dc0301ad961b198f086a39e2479f6b8933 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:29 +0300 Subject: [PATCH 133/176] usb: xhci: optimize resuming from S4 (suspend-to-disk) On resume from S4 (power loss after suspend/hibernation), the xHCI driver previously freed, reallocated, and fully reinitialized all data structures. Most of this is unnecessary because the data is restored from a saved image; only the xHCI registers lose their values. This patch optimizes S4 resume by performing only a host controller reset, which includes: * Freeing or clearing runtime-created data. * Rewriting xHCI registers. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-13-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 4 +-- drivers/usb/host/xhci.c | 53 ++++++++++++++++++++++--------------- drivers/usb/host/xhci.h | 2 ++ 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index f1b4f06d4b8b..4156822eb000 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -936,7 +936,7 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, struct xhci_virt_device *dev, * that tt_info, then free the child first. Recursive. * We can't rely on udev at this point to find child-parent relationships. */ -static void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id) +void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id) { struct xhci_virt_device *vdev; struct list_head *tt_list_head; @@ -1905,7 +1905,7 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter); /* Cleanup roothub bandwidth data */ -static void xhci_rh_bw_cleanup(struct xhci_hcd *xhci) +void xhci_rh_bw_cleanup(struct xhci_hcd *xhci) { struct xhci_root_port_bw_info *rh_bw; struct xhci_tt_bw_info *tt_info, *tt_next; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 2c573aad4464..ece3ff7916ff 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1082,9 +1082,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) { u32 command, temp = 0; struct usb_hcd *hcd = xhci_to_hcd(xhci); + struct xhci_segment *seg; int retval = 0; bool pending_portevent = false; bool suspended_usb3_devs = false; + bool reset_registers = false; if (!hcd->state) return 0; @@ -1103,10 +1105,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) spin_lock_irq(&xhci->lock); - if (xhci->quirks & XHCI_RESET_ON_RESUME || xhci->broken_suspend) - power_lost = true; - - if (!power_lost) { + if (power_lost || xhci->broken_suspend || xhci->quirks & XHCI_RESET_ON_RESUME) { + xhci_dbg(xhci, "HC state lost, performing host controller reset\n"); + reset_registers = true; + } else { + xhci_dbg(xhci, "HC state intact, continuing without reset\n"); /* * Some controllers might lose power during suspend, so wait * for controller not ready bit to clear, just as in xHC init. @@ -1144,11 +1147,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) temp = readl(&xhci->op_regs->status); if ((temp & (STS_SRE | STS_HCE)) && !(xhci->xhc_state & XHCI_STATE_REMOVING)) { xhci_warn(xhci, "xHC error in resume, USBSTS 0x%x, Reinit\n", temp); - power_lost = true; + reset_registers = true; } } - if (power_lost) { + if (reset_registers) { if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && !(xhci_all_ports_seen_u0(xhci))) { timer_delete_sync(&xhci->comp_mode_recovery_timer); @@ -1172,27 +1175,33 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) if (retval) return retval; - xhci_dbg(xhci, "// Disabling event ring interrupts\n"); - temp = readl(&xhci->op_regs->status); - writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); - xhci_disable_interrupter(xhci, xhci->interrupters[0]); + cancel_delayed_work_sync(&xhci->cmd_timer); + + /* Delete all remaining commands */ + xhci_cleanup_command_queue(xhci); + + /* Clear data which is re-initilized during runtime */ + xhci_for_each_ring_seg(xhci->interrupters[0]->event_ring->first_seg, seg) + memset(seg->trbs, 0, sizeof(union xhci_trb) * TRBS_PER_SEGMENT); + + for (int i = xhci->max_slots; i > 0; i--) + xhci_free_virt_devices_depth_first(xhci, i); + + xhci_rh_bw_cleanup(xhci); + + xhci->cmd_ring_reserved_trbs = 0; + xhci_for_each_ring_seg(xhci->cmd_ring->first_seg, seg) + memset(seg->trbs, 0, sizeof(union xhci_trb) * TRBS_PER_SEGMENT); - xhci_dbg(xhci, "cleaning up memory\n"); - xhci_mem_cleanup(xhci); xhci_debugfs_exit(xhci); - xhci_dbg(xhci, "xhci_stop completed - status = %x\n", - readl(&xhci->op_regs->status)); - - /* USB core calls the PCI reinit and start functions twice: - * first with the primary HCD, and then with the secondary HCD. - * If we don't do the same, the host will never be started. - */ - retval = xhci_mem_init(xhci, GFP_KERNEL); - if (retval) - return retval; xhci_init(hcd); + /* + * USB core calls the PCI reinit and start functions twice: + * first with the primary HCD, and then with the secondary HCD. + * If we don't do the same, the host will never be started. + */ xhci_dbg(xhci, "Start the primary HCD\n"); retval = xhci_run(hcd); if (!retval && xhci->shared_hcd) { diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 06f6da4d982f..aeecd301f207 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1793,6 +1793,7 @@ void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *), void xhci_mem_cleanup(struct xhci_hcd *xhci); int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags); void xhci_free_virt_device(struct xhci_hcd *xhci, struct xhci_virt_device *dev, int slot_id); +void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_id); int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, struct usb_device *udev, gfp_t flags); int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev); void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci, @@ -1804,6 +1805,7 @@ void xhci_update_tt_active_eps(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, int old_active_eps); void xhci_clear_endpoint_bw_info(struct xhci_bw_info *bw_info); +void xhci_rh_bw_cleanup(struct xhci_hcd *xhci); void xhci_update_bw_info(struct xhci_hcd *xhci, struct xhci_container_ctx *in_ctx, struct xhci_input_control_ctx *ctrl_ctx, From 1e6138c0654334d42b3984f1154ee35234f35302 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:30 +0300 Subject: [PATCH 134/176] usb: xhci: stop treating 'wIndex' as a mutable port number The USB request parameter 'wIndex' is a 16-bit field whose meaning depends on the request type. For hub port operations, only bits 7:0 encode the port number (1..MaxPorts). Despite this, the current code extracts the port number into 'portnum1' while also modifying and using 'wIndex' directly as a 0-based port index. This dual use is both confusing and error-prone, since 'wIndex' is not always a pure port number. Clean this up by deriving a single 0-based 'portnum' from 'wIndex' and using it throughout the function. The original 'wIndex' value is no longer modified or treated as a port number. This also matches existing xhci code. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-14-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 65 +++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 04cc3d681495..4730beae2478 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1218,13 +1218,12 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, struct xhci_hub *rhub; struct xhci_port **ports; struct xhci_port *port; - int portnum1; + int portnum; rhub = xhci_get_rhub(hcd); ports = rhub->ports; max_ports = rhub->num_ports; bus_state = &rhub->bus_state; - portnum1 = wIndex & 0xff; spin_lock_irqsave(&xhci->lock, flags); switch (typeReq) { @@ -1258,11 +1257,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, spin_unlock_irqrestore(&xhci->lock, flags); return retval; case GetPortStatus: - if (!portnum1 || portnum1 > max_ports) + portnum = (wIndex & 0xff) - 1; + if (!in_range(portnum, 0, max_ports)) goto error; - wIndex--; - port = ports[portnum1 - 1]; + port = ports[portnum]; temp = xhci_portsc_readl(port); if (temp == ~(u32)0) { xhci_hc_died(xhci); @@ -1270,13 +1269,12 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; } trace_xhci_get_port_status(port, temp); - status = xhci_get_port_status(hcd, bus_state, wIndex, temp, - &flags); + status = xhci_get_port_status(hcd, bus_state, portnum, temp, &flags); if (status == 0xffffffff) goto error; xhci_dbg(xhci, "Get port status %d-%d read: 0x%x, return 0x%x", - hcd->self.busnum, portnum1, temp, status); + hcd->self.busnum, portnum + 1, temp, status); put_unaligned(cpu_to_le32(status), (__le32 *) buf); /* if USB 3.1 extended port status return additional 4 bytes */ @@ -1303,12 +1301,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* The MSB of wIndex is the U1/U2 timeout */ timeout = (wIndex & 0xff00) >> 8; - wIndex &= 0xff; - if (!portnum1 || portnum1 > max_ports) + portnum = (wIndex & 0xff) - 1; + if (!in_range(portnum, 0, max_ports)) goto error; - port = ports[portnum1 - 1]; - wIndex--; + port = ports[portnum]; temp = xhci_portsc_readl(port); if (temp == ~(u32)0) { xhci_hc_died(xhci); @@ -1335,7 +1332,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) || (temp & PORT_PLS_MASK) >= XDEV_U3) { xhci_warn(xhci, "USB core suspending port %d-%d not in U0/U1/U2\n", - hcd->self.busnum, portnum1); + hcd->self.busnum, portnum + 1); goto error; } @@ -1355,14 +1352,14 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, spin_lock_irqsave(&xhci->lock, flags); temp = xhci_portsc_readl(port); - bus_state->suspended_ports |= 1 << wIndex; + bus_state->suspended_ports |= 1 << portnum; break; case USB_PORT_FEAT_LINK_STATE: temp = xhci_portsc_readl(port); /* Disable port */ if (link_state == USB_SS_PORT_LS_SS_DISABLED) { xhci_dbg(xhci, "Disable port %d-%d\n", - hcd->self.busnum, portnum1); + hcd->self.busnum, portnum + 1); temp = xhci_port_state_to_neutral(temp); /* * Clear all change bits, so that we get a new @@ -1379,7 +1376,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* Put link in RxDetect (enable port) */ if (link_state == USB_SS_PORT_LS_RX_DETECT) { xhci_dbg(xhci, "Enable port %d-%d\n", - hcd->self.busnum, portnum1); + hcd->self.busnum, portnum + 1); xhci_set_link_state(xhci, port, link_state); temp = xhci_portsc_readl(port); break; @@ -1411,7 +1408,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } xhci_dbg(xhci, "Enable compliance mode transition for port %d-%d\n", - hcd->self.busnum, portnum1); + hcd->self.busnum, portnum + 1); xhci_set_link_state(xhci, port, link_state); temp = xhci_portsc_readl(port); @@ -1425,7 +1422,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* Can't set port link state above '3' (U3) */ if (link_state > USB_SS_PORT_LS_U3) { xhci_warn(xhci, "Cannot set port %d-%d link state %d\n", - hcd->self.busnum, portnum1, link_state); + hcd->self.busnum, portnum + 1, link_state); goto error; } @@ -1460,7 +1457,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if (!wait_for_completion_timeout(&port->u3exit_done, msecs_to_jiffies(500))) xhci_dbg(xhci, "missing U0 port change event for port %d-%d\n", - hcd->self.busnum, portnum1); + hcd->self.busnum, portnum + 1); spin_lock_irqsave(&xhci->lock, flags); temp = xhci_portsc_readl(port); break; @@ -1486,7 +1483,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } spin_lock_irqsave(&xhci->lock, flags); temp = xhci_portsc_readl(port); - bus_state->suspended_ports |= 1 << wIndex; + bus_state->suspended_ports |= 1 << portnum; } break; case USB_PORT_FEAT_POWER: @@ -1504,13 +1501,13 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, temp = xhci_portsc_readl(port); xhci_dbg(xhci, "set port reset, actual port %d-%d status = 0x%x\n", - hcd->self.busnum, portnum1, temp); + hcd->self.busnum, portnum + 1, temp); break; case USB_PORT_FEAT_REMOTE_WAKE_MASK: xhci_set_remote_wake_mask(xhci, port, wake_mask); temp = xhci_portsc_readl(port); xhci_dbg(xhci, "set port remote wake mask, actual port %d-%d status = 0x%x\n", - hcd->self.busnum, portnum1, temp); + hcd->self.busnum, portnum + 1, temp); break; case USB_PORT_FEAT_BH_PORT_RESET: temp |= PORT_WR; @@ -1540,8 +1537,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if (test_mode > USB_TEST_FORCE_ENABLE || test_mode < USB_TEST_J) goto error; - retval = xhci_enter_test_mode(xhci, test_mode, wIndex, - &flags); + retval = xhci_enter_test_mode(xhci, test_mode, portnum, &flags); break; default: goto error; @@ -1550,12 +1546,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, temp = xhci_portsc_readl(port); break; case ClearPortFeature: - if (!portnum1 || portnum1 > max_ports) + portnum = (wIndex & 0xff) - 1; + if (!in_range(portnum, 0, max_ports)) goto error; - port = ports[portnum1 - 1]; - - wIndex--; + port = ports[portnum]; temp = xhci_portsc_readl(port); if (temp == ~(u32)0) { xhci_hc_died(xhci); @@ -1575,17 +1570,17 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if ((temp & PORT_PE) == 0) goto error; - set_bit(wIndex, &bus_state->resuming_ports); - usb_hcd_start_port_resume(&hcd->self, wIndex); + set_bit(portnum, &bus_state->resuming_ports); + usb_hcd_start_port_resume(&hcd->self, portnum); xhci_set_link_state(xhci, port, XDEV_RESUME); spin_unlock_irqrestore(&xhci->lock, flags); msleep(USB_RESUME_TIMEOUT); spin_lock_irqsave(&xhci->lock, flags); xhci_set_link_state(xhci, port, XDEV_U0); - clear_bit(wIndex, &bus_state->resuming_ports); - usb_hcd_end_port_resume(&hcd->self, wIndex); + clear_bit(portnum, &bus_state->resuming_ports); + usb_hcd_end_port_resume(&hcd->self, portnum); } - bus_state->port_c_suspend |= 1 << wIndex; + bus_state->port_c_suspend |= 1 << portnum; if (!port->slot_id) { xhci_dbg(xhci, "slot_id is zero\n"); @@ -1594,7 +1589,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_ring_device(xhci, port->slot_id); break; case USB_PORT_FEAT_C_SUSPEND: - bus_state->port_c_suspend &= ~(1 << wIndex); + bus_state->port_c_suspend &= ~(1 << portnum); fallthrough; case USB_PORT_FEAT_C_RESET: case USB_PORT_FEAT_C_BH_PORT_RESET: @@ -1603,7 +1598,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_C_ENABLE: case USB_PORT_FEAT_C_PORT_LINK_STATE: case USB_PORT_FEAT_C_PORT_CONFIG_ERROR: - xhci_clear_port_change_bit(xhci, wValue, wIndex, port, temp); + xhci_clear_port_change_bit(xhci, wValue, portnum, port, temp); break; case USB_PORT_FEAT_ENABLE: xhci_disable_port(xhci, port); From ec5521a77167cc258bd4a587756f11d66b05a4c2 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:31 +0300 Subject: [PATCH 135/176] usb: xhci: rename 'wIndex' parameters to 'portnum' Several helper functions take a parameter named 'wIndex', but the value they receive is not the raw USB request wIndex field. The only function that actually processes the USB hub request parameter is xhci_hub_control(), which extracts the relevant port number (and other upper-byte fields) before passing them down. To avoid confusion between the USB request parameter and the derived 0-based port index, rename all such function parameters from 'wIndex' to 'portnum'. This improves readability and makes the call intentions clearer. When a function accept struct 'xhci_port' pointer, use its port number instead. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-15-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 60 +++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 4730beae2478..de843ecc7d63 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -577,8 +577,8 @@ static void xhci_disable_port(struct xhci_hcd *xhci, struct xhci_port *port) hcd->self.busnum, port->hcd_portnum + 1, portsc); } -static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, - u16 wIndex, struct xhci_port *port, u32 port_status) +static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, struct xhci_port *port, + u32 port_status) { char *port_change_bit; u32 status; @@ -625,7 +625,7 @@ static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, port_status = xhci_portsc_readl(port); xhci_dbg(xhci, "clear port%d %s change, portsc: 0x%x\n", - wIndex + 1, port_change_bit, port_status); + port->hcd_portnum + 1, port_change_bit, port_status); } struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd) @@ -675,14 +675,13 @@ static void xhci_set_port_power(struct xhci_hcd *xhci, struct xhci_port *port, spin_lock_irqsave(&xhci->lock, *flags); } -static void xhci_port_set_test_mode(struct xhci_hcd *xhci, - u16 test_mode, u16 wIndex) +static void xhci_port_set_test_mode(struct xhci_hcd *xhci, u16 test_mode, int portnum) { u32 temp; struct xhci_port *port; /* xhci only supports test mode for usb2 ports */ - port = xhci->usb2_rhub.ports[wIndex]; + port = xhci->usb2_rhub.ports[portnum]; temp = readl(&port->port_reg->portpmsc); temp |= test_mode << PORT_TEST_MODE_SHIFT; writel(temp, &port->port_reg->portpmsc); @@ -691,8 +690,8 @@ static void xhci_port_set_test_mode(struct xhci_hcd *xhci, xhci_start(xhci); } -static int xhci_enter_test_mode(struct xhci_hcd *xhci, - u16 test_mode, u16 wIndex, unsigned long *flags) +static int xhci_enter_test_mode(struct xhci_hcd *xhci, u16 test_mode, int portnum, + unsigned long *flags) __must_hold(&xhci->lock) { int i, retval; @@ -726,10 +725,8 @@ static int xhci_enter_test_mode(struct xhci_hcd *xhci, /* Disable runtime PM for test mode */ pm_runtime_forbid(xhci_to_hcd(xhci)->self.controller); /* Set PORTPMSC.PTC field to enter selected test mode */ - /* Port is selected by wIndex. port_id = wIndex + 1 */ - xhci_dbg(xhci, "Enter Test Mode: %d, Port_id=%d\n", - test_mode, wIndex + 1); - xhci_port_set_test_mode(xhci, test_mode, wIndex); + xhci_dbg(xhci, "Enter Test Mode: %u, Port_id=%d\n", test_mode, portnum + 1); + xhci_port_set_test_mode(xhci, test_mode, portnum); return retval; } @@ -913,8 +910,7 @@ static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, * the compliance mode timer is deleted. A port won't enter * compliance mode if it has previously entered U0. */ -static void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, - u16 wIndex) +static void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, int portnum) { u32 all_ports_seen_u0 = ((1 << xhci->usb3_rhub.num_ports) - 1); bool port_in_u0 = ((status & PORT_PLS_MASK) == XDEV_U0); @@ -923,7 +919,7 @@ static void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, return; if ((xhci->port_status_u0 != all_ports_seen_u0) && port_in_u0) { - xhci->port_status_u0 |= 1 << wIndex; + xhci->port_status_u0 |= 1 << portnum; if (xhci->port_status_u0 == all_ports_seen_u0) { timer_delete_sync(&xhci->comp_mode_recovery_timer); xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, @@ -941,12 +937,12 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, struct xhci_bus_state *bus_state; struct xhci_hcd *xhci; struct usb_hcd *hcd; - u32 wIndex; + int portnum; hcd = port->rhub->hcd; bus_state = &port->rhub->bus_state; xhci = hcd_to_xhci(hcd); - wIndex = port->hcd_portnum; + portnum = port->hcd_portnum; if ((portsc & PORT_RESET) || !(portsc & PORT_PE)) { return -EINVAL; @@ -954,7 +950,7 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, /* did port event handler already start resume timing? */ if (!port->resume_timestamp) { /* If not, maybe we are in a host initiated resume? */ - if (test_bit(wIndex, &bus_state->resuming_ports)) { + if (test_bit(portnum, &bus_state->resuming_ports)) { /* Host initiated resume doesn't time the resume * signalling using resume_done[]. * It manually sets RESUME state, sleeps 20ms @@ -968,20 +964,20 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, unsigned long timeout = jiffies + msecs_to_jiffies(USB_RESUME_TIMEOUT); - set_bit(wIndex, &bus_state->resuming_ports); + set_bit(portnum, &bus_state->resuming_ports); port->resume_timestamp = timeout; mod_timer(&hcd->rh_timer, timeout); - usb_hcd_start_port_resume(&hcd->self, wIndex); + usb_hcd_start_port_resume(&hcd->self, portnum); } /* Has resume been signalled for USB_RESUME_TIME yet? */ } else if (time_after_eq(jiffies, port->resume_timestamp)) { int time_left; xhci_dbg(xhci, "resume USB2 port %d-%d\n", - hcd->self.busnum, wIndex + 1); + hcd->self.busnum, portnum + 1); port->resume_timestamp = 0; - clear_bit(wIndex, &bus_state->resuming_ports); + clear_bit(portnum, &bus_state->resuming_ports); reinit_completion(&port->rexit_done); port->rexit_active = true; @@ -1005,7 +1001,7 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, int port_status = xhci_portsc_readl(port); xhci_warn(xhci, "Port resume timed out, port %d-%d: 0x%x\n", - hcd->self.busnum, wIndex + 1, port_status); + hcd->self.busnum, portnum + 1, port_status); /* * keep rexit_active set if U0 transition failed so we * know to report PORT_STAT_SUSPEND status back to @@ -1014,9 +1010,9 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, */ } - usb_hcd_end_port_resume(&hcd->self, wIndex); - bus_state->port_c_suspend |= 1 << wIndex; - bus_state->suspended_ports &= ~(1 << wIndex); + usb_hcd_end_port_resume(&hcd->self, portnum); + bus_state->port_c_suspend |= 1 << portnum; + bus_state->suspended_ports &= ~(1 << portnum); } return 0; @@ -1153,10 +1149,8 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, * - Stop the Synopsys redriver Compliance Mode polling. * - Drop and reacquire the xHCI lock, in order to wait for port resume. */ -static u32 xhci_get_port_status(struct usb_hcd *hcd, - struct xhci_bus_state *bus_state, - u16 wIndex, u32 raw_port_status, - unsigned long *flags) +static u32 xhci_get_port_status(struct usb_hcd *hcd, struct xhci_bus_state *bus_state, + int portnum, u32 raw_port_status, unsigned long *flags) __releases(&xhci->lock) __acquires(&xhci->lock) { @@ -1165,7 +1159,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, struct xhci_port *port; rhub = xhci_get_rhub(hcd); - port = rhub->ports[wIndex]; + port = rhub->ports[portnum]; /* common wPortChange bits */ if (raw_port_status & PORT_CSC) @@ -1196,7 +1190,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, xhci_get_usb2_port_status(port, &status, raw_port_status, flags); - if (bus_state->port_c_suspend & (1 << wIndex)) + if (bus_state->port_c_suspend & (1 << portnum)) status |= USB_PORT_STAT_C_SUSPEND << 16; return status; @@ -1598,7 +1592,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_C_ENABLE: case USB_PORT_FEAT_C_PORT_LINK_STATE: case USB_PORT_FEAT_C_PORT_CONFIG_ERROR: - xhci_clear_port_change_bit(xhci, wValue, portnum, port, temp); + xhci_clear_port_change_bit(xhci, wValue, port, temp); break; case USB_PORT_FEAT_ENABLE: xhci_disable_port(xhci, port); From b7a56f8652d00a8c4d94c2214301f6475989339e Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:32 +0300 Subject: [PATCH 136/176] usb: xhci: clean up handling of upper bits in SetPortFeature wIndex In Set Port Feature requests, the upper byte of 'wIndex' encodes feature-specific parameters. The current code reads these upper bits in an early pre-processing block, and then the same feature is handled again later in the main switch statement. This results in duplicated condition checks and makes the control flow harder to follow. Move all feature-specific extraction of 'wIndex' upper bits into the main SetPortFeature logic so that each feature is handled in exactly one place. This reduces duplication, makes the handling clearer, and keeps 'wIndex' parsing local to the code that actually uses the values. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-16-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index de843ecc7d63..daa04ac811fc 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1205,10 +1205,10 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u32 temp, status; int retval = 0; struct xhci_bus_state *bus_state; - u16 link_state = 0; - u16 wake_mask = 0; - u16 timeout = 0; - u16 test_mode = 0; + u16 link_state; + u16 wake_mask; + u8 timeout; + u8 test_mode; struct xhci_hub *rhub; struct xhci_port **ports; struct xhci_port *port; @@ -1286,15 +1286,6 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } break; case SetPortFeature: - if (wValue == USB_PORT_FEAT_LINK_STATE) - link_state = (wIndex & 0xff00) >> 3; - if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK) - wake_mask = wIndex & 0xff00; - if (wValue == USB_PORT_FEAT_TEST) - test_mode = (wIndex & 0xff00) >> 8; - /* The MSB of wIndex is the U1/U2 timeout */ - timeout = (wIndex & 0xff00) >> 8; - portnum = (wIndex & 0xff) - 1; if (!in_range(portnum, 0, max_ports)) goto error; @@ -1349,6 +1340,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, bus_state->suspended_ports |= 1 << portnum; break; case USB_PORT_FEAT_LINK_STATE: + link_state = (wIndex & 0xff00) >> 3; temp = xhci_portsc_readl(port); /* Disable port */ if (link_state == USB_SS_PORT_LS_SS_DISABLED) { @@ -1498,6 +1490,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, hcd->self.busnum, portnum + 1, temp); break; case USB_PORT_FEAT_REMOTE_WAKE_MASK: + wake_mask = wIndex & 0xff00; xhci_set_remote_wake_mask(xhci, port, wake_mask); temp = xhci_portsc_readl(port); xhci_dbg(xhci, "set port remote wake mask, actual port %d-%d status = 0x%x\n", @@ -1511,6 +1504,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_U1_TIMEOUT: if (hcd->speed < HCD_USB3) goto error; + + timeout = (wIndex & 0xff00) >> 8; temp = readl(&port->port_reg->portpmsc); temp &= ~PORT_U1_TIMEOUT_MASK; temp |= PORT_U1_TIMEOUT(timeout); @@ -1519,6 +1514,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_U2_TIMEOUT: if (hcd->speed < HCD_USB3) goto error; + + timeout = (wIndex & 0xff00) >> 8; temp = readl(&port->port_reg->portpmsc); temp &= ~PORT_U2_TIMEOUT_MASK; temp |= PORT_U2_TIMEOUT(timeout); @@ -1528,6 +1525,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* 4.19.6 Port Test Modes (USB2 Test Mode) */ if (hcd->speed != HCD_USB2) goto error; + + test_mode = (wIndex & 0xff00) >> 8; if (test_mode > USB_TEST_FORCE_ENABLE || test_mode < USB_TEST_J) goto error; From 995e2af1656882294cbf86396f74c308806f33dc Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:33 +0300 Subject: [PATCH 137/176] usb: xhci: clean up 'wValue' handling in xhci_hub_control() Several hub control requests encode a descriptor type in the upper byte of 'wValue'. Clean this up by extracting the descriptor type into a local variable and using it for all relevant requests. Replace magic value (0x02) with the appropriate macro (HUB_EXT_PORT_STATUS) This improves readability and makes the handling of 'wValue' consistent. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-17-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index daa04ac811fc..b57fe0967e10 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1209,6 +1209,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wake_mask; u8 timeout; u8 test_mode; + u8 desc_type; struct xhci_hub *rhub; struct xhci_port **ports; struct xhci_port *port; @@ -1226,13 +1227,13 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, memset(buf, 0, 4); break; case GetHubDescriptor: + desc_type = (wValue & 0xff00) >> 8; /* Check to make sure userspace is asking for the USB 3.0 hub * descriptor for the USB 3.0 roothub. If not, we stall the * endpoint, like external hubs do. */ if (hcd->speed >= HCD_USB3 && - (wLength < USB_DT_SS_HUB_SIZE || - wValue != (USB_DT_SS_HUB << 8))) { + (wLength < USB_DT_SS_HUB_SIZE || desc_type != USB_DT_SS_HUB)) { xhci_dbg(xhci, "Wrong hub descriptor type for " "USB 3.0 roothub.\n"); goto error; @@ -1241,7 +1242,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, (struct usb_hub_descriptor *) buf); break; case DeviceRequest | USB_REQ_GET_DESCRIPTOR: - if ((wValue & 0xff00) != (USB_DT_BOS << 8)) + desc_type = (wValue & 0xff00) >> 8; + if (desc_type != USB_DT_BOS) goto error; if (hcd->speed < HCD_USB3) @@ -1272,7 +1274,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, put_unaligned(cpu_to_le32(status), (__le32 *) buf); /* if USB 3.1 extended port status return additional 4 bytes */ - if (wValue == 0x02) { + if (wValue == HUB_EXT_PORT_STATUS) { u32 port_li; if (hcd->speed < HCD_USB31 || wLength != 8) { From 4515039a8a1bea2055e8f10e88560684d0ea0257 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:34 +0300 Subject: [PATCH 138/176] usb: xhci: separate use of USB Chapter 11 PLS macros from xHCI-specific PLS macros The xhci driver uses two different sources for Port Link State (PLS): 1. The PLS field in the PORTSC register (bits 8:5). 2. The PLS value encoded in bits 15:8 of the USB request wIndex, received by xhci_hub_control(). While both represent similar link states, they differ in a few details, for example, xHCI's Resume State. Because of these differences, the xhci driver defines its own set of PLS macros in xhci-port.h, which are intended to be used when reading and writing PORTSC. The generic USB Chapter 11 macros in ch11.h should only be used when parsing or replying to hub-class USB requests. To avoid mixing these two representations and prevent incorrect state reporting, replace all uses of Chapter 11 PLS macros with the xHCI versions when interacting with the PORTSC register. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-18-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index b57fe0967e10..7fb17799cfdc 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -866,8 +866,8 @@ static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, * unless we're already in compliance * or the inactive state. */ - if (pls != USB_SS_PORT_LS_COMP_MOD && - pls != USB_SS_PORT_LS_SS_INACTIVE) { + if (pls != XDEV_COMP_MODE && + pls != XDEV_INACTIVE) { pls = USB_SS_PORT_LS_COMP_MOD; } /* Return also connection bit - @@ -895,7 +895,7 @@ static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, * caused by a delay on the host-device negotiation. */ if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && - (pls == USB_SS_PORT_LS_COMP_MOD)) + (pls == XDEV_COMP_MODE)) pls |= USB_PORT_STAT_CONNECTION; } @@ -1365,7 +1365,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if (link_state == USB_SS_PORT_LS_RX_DETECT) { xhci_dbg(xhci, "Enable port %d-%d\n", hcd->self.busnum, portnum + 1); - xhci_set_link_state(xhci, port, link_state); + xhci_set_link_state(xhci, port, XDEV_RXDETECT); temp = xhci_portsc_readl(port); break; } @@ -1397,7 +1397,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_dbg(xhci, "Enable compliance mode transition for port %d-%d\n", hcd->self.busnum, portnum + 1); - xhci_set_link_state(xhci, port, link_state); + xhci_set_link_state(xhci, port, XDEV_COMP_MODE); temp = xhci_portsc_readl(port); break; @@ -1435,7 +1435,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, reinit_completion(&port->u3exit_done); } if (pls <= XDEV_U3) /* U1, U2, U3 */ - xhci_set_link_state(xhci, port, USB_SS_PORT_LS_U0); + xhci_set_link_state(xhci, port, XDEV_U0); if (!wait_u0) { if (pls > XDEV_U3) goto error; @@ -1461,7 +1461,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_stop_device(xhci, port->slot_id, 1); spin_lock_irqsave(&xhci->lock, flags); } - xhci_set_link_state(xhci, port, USB_SS_PORT_LS_U3); + xhci_set_link_state(xhci, port, XDEV_U3); spin_unlock_irqrestore(&xhci->lock, flags); while (retries--) { usleep_range(4000, 8000); From f84965acad118b19ddaa02fbea0a4c2d079c3fb7 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:35 +0300 Subject: [PATCH 139/176] usb: xhci: add PORTPMSC variable to xhci_hub_control() The code handling U1/U2 timeout updates reads and modifies the PORTPMSC register using the generic 'temp' variable, which is also used for PORTSC. This makes the code hard to read and increases the risk of mixing up register contents. Introduce a dedicated 'portpmsc' variable for PORTPMSC accesses and use it in both U1 and U2 timeout handlers. This makes the intent clearer and keeps register operations logically separated. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-19-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 7fb17799cfdc..4da3b48dfce0 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1202,7 +1202,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, struct xhci_hcd *xhci = hcd_to_xhci(hcd); int max_ports; unsigned long flags; - u32 temp, status; + u32 temp, portpmsc, status; int retval = 0; struct xhci_bus_state *bus_state; u16 link_state; @@ -1508,20 +1508,20 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; timeout = (wIndex & 0xff00) >> 8; - temp = readl(&port->port_reg->portpmsc); - temp &= ~PORT_U1_TIMEOUT_MASK; - temp |= PORT_U1_TIMEOUT(timeout); - writel(temp, &port->port_reg->portpmsc); + portpmsc = readl(&port->port_reg->portpmsc); + portpmsc &= ~PORT_U1_TIMEOUT_MASK; + portpmsc |= PORT_U1_TIMEOUT(timeout); + writel(portpmsc, &port->port_reg->portpmsc); break; case USB_PORT_FEAT_U2_TIMEOUT: if (hcd->speed < HCD_USB3) goto error; timeout = (wIndex & 0xff00) >> 8; - temp = readl(&port->port_reg->portpmsc); - temp &= ~PORT_U2_TIMEOUT_MASK; - temp |= PORT_U2_TIMEOUT(timeout); - writel(temp, &port->port_reg->portpmsc); + portpmsc = readl(&port->port_reg->portpmsc); + portpmsc &= ~PORT_U2_TIMEOUT_MASK; + portpmsc |= PORT_U2_TIMEOUT(timeout); + writel(portpmsc, &port->port_reg->portpmsc); break; case USB_PORT_FEAT_TEST: /* 4.19.6 Port Test Modes (USB2 Test Mode) */ From 58a5bfc4aaac4a232e21a8155dec7c6070379193 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:36 +0300 Subject: [PATCH 140/176] usb: xhci: add PORTSC variable to xhci_hub_control() The variable 'temp' is used multiple times throughout xhci_hub_control() for holding only PORTSC register values. As a follow-up to introducing a dedicated variable for PORTPMSC, rename all remaining 'temp' to 'portsc'. This improves readability and clarifies what is being modified. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-20-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 102 ++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 4da3b48dfce0..9cd64d6989c9 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1202,7 +1202,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, struct xhci_hcd *xhci = hcd_to_xhci(hcd); int max_ports; unsigned long flags; - u32 temp, portpmsc, status; + u32 portsc, portpmsc, status; int retval = 0; struct xhci_bus_state *bus_state; u16 link_state; @@ -1258,19 +1258,19 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; port = ports[portnum]; - temp = xhci_portsc_readl(port); - if (temp == ~(u32)0) { + portsc = xhci_portsc_readl(port); + if (portsc == ~(u32)0) { xhci_hc_died(xhci); retval = -ENODEV; break; } - trace_xhci_get_port_status(port, temp); - status = xhci_get_port_status(hcd, bus_state, portnum, temp, &flags); + trace_xhci_get_port_status(port, portsc); + status = xhci_get_port_status(hcd, bus_state, portnum, portsc, &flags); if (status == 0xffffffff) goto error; xhci_dbg(xhci, "Get port status %d-%d read: 0x%x, return 0x%x", - hcd->self.busnum, portnum + 1, temp, status); + hcd->self.busnum, portnum + 1, portsc, status); put_unaligned(cpu_to_le32(status), (__le32 *) buf); /* if USB 3.1 extended port status return additional 4 bytes */ @@ -1283,7 +1283,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; } port_li = readl(&port->port_reg->portli); - status = xhci_get_ext_port_status(temp, port_li); + status = xhci_get_ext_port_status(portsc, port_li); put_unaligned_le32(status, &buf[4]); } break; @@ -1293,18 +1293,18 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; port = ports[portnum]; - temp = xhci_portsc_readl(port); - if (temp == ~(u32)0) { + portsc = xhci_portsc_readl(port); + if (portsc == ~(u32)0) { xhci_hc_died(xhci); retval = -ENODEV; break; } - temp = xhci_port_state_to_neutral(temp); + portsc = xhci_port_state_to_neutral(portsc); /* FIXME: What new port features do we need to support? */ switch (wValue) { case USB_PORT_FEAT_SUSPEND: - temp = xhci_portsc_readl(port); - if ((temp & PORT_PLS_MASK) != XDEV_U0) { + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PLS_MASK) != XDEV_U0) { /* Resume the port to U0 first */ xhci_set_link_state(xhci, port, XDEV_U0); spin_unlock_irqrestore(&xhci->lock, flags); @@ -1315,9 +1315,9 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, * a port unless the port reports that it is in the * enabled (PED = ‘1’,PLS < ‘3’) state. */ - temp = xhci_portsc_readl(port); - if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) - || (temp & PORT_PLS_MASK) >= XDEV_U3) { + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PE) == 0 || (portsc & PORT_RESET) || + (portsc & PORT_PLS_MASK) >= XDEV_U3) { xhci_warn(xhci, "USB core suspending port %d-%d not in U0/U1/U2\n", hcd->self.busnum, portnum + 1); goto error; @@ -1338,26 +1338,26 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, msleep(10); /* wait device to enter */ spin_lock_irqsave(&xhci->lock, flags); - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); bus_state->suspended_ports |= 1 << portnum; break; case USB_PORT_FEAT_LINK_STATE: link_state = (wIndex & 0xff00) >> 3; - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); /* Disable port */ if (link_state == USB_SS_PORT_LS_SS_DISABLED) { xhci_dbg(xhci, "Disable port %d-%d\n", hcd->self.busnum, portnum + 1); - temp = xhci_port_state_to_neutral(temp); + portsc = xhci_port_state_to_neutral(portsc); /* * Clear all change bits, so that we get a new * connection event. */ - temp |= PORT_CSC | PORT_PEC | PORT_WRC | - PORT_OCC | PORT_RC | PORT_PLC | - PORT_CEC; - xhci_portsc_writel(port, temp | PORT_PE); - temp = xhci_portsc_readl(port); + portsc |= PORT_CSC | PORT_PEC | PORT_WRC | + PORT_OCC | PORT_RC | PORT_PLC | + PORT_CEC; + xhci_portsc_writel(port, portsc | PORT_PE); + portsc = xhci_portsc_readl(port); break; } @@ -1366,7 +1366,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_dbg(xhci, "Enable port %d-%d\n", hcd->self.busnum, portnum + 1); xhci_set_link_state(xhci, port, XDEV_RXDETECT); - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); break; } @@ -1390,7 +1390,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; } - if ((temp & PORT_CONNECT)) { + if ((portsc & PORT_CONNECT)) { xhci_warn(xhci, "Can't set compliance mode when port is connected\n"); goto error; } @@ -1399,11 +1399,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, hcd->self.busnum, portnum + 1); xhci_set_link_state(xhci, port, XDEV_COMP_MODE); - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); break; } /* Port must be enabled */ - if (!(temp & PORT_PE)) { + if (!(portsc & PORT_PE)) { retval = -ENODEV; break; } @@ -1422,7 +1422,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, * completion */ if (link_state == USB_SS_PORT_LS_U0) { - u32 pls = temp & PORT_PLS_MASK; + u32 pls = portsc & PORT_PLS_MASK; bool wait_u0 = false; /* already in U0 */ @@ -1447,7 +1447,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_dbg(xhci, "missing U0 port change event for port %d-%d\n", hcd->self.busnum, portnum + 1); spin_lock_irqsave(&xhci->lock, flags); - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); break; } @@ -1465,12 +1465,12 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, spin_unlock_irqrestore(&xhci->lock, flags); while (retries--) { usleep_range(4000, 8000); - temp = xhci_portsc_readl(port); - if ((temp & PORT_PLS_MASK) == XDEV_U3) + portsc = xhci_portsc_readl(port); + if ((portsc & PORT_PLS_MASK) == XDEV_U3) break; } spin_lock_irqsave(&xhci->lock, flags); - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); bus_state->suspended_ports |= 1 << portnum; } break; @@ -1484,24 +1484,24 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_set_port_power(xhci, port, true, &flags); break; case USB_PORT_FEAT_RESET: - temp = (temp | PORT_RESET); - xhci_portsc_writel(port, temp); + portsc |= PORT_RESET; + xhci_portsc_writel(port, portsc); - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); xhci_dbg(xhci, "set port reset, actual port %d-%d status = 0x%x\n", - hcd->self.busnum, portnum + 1, temp); + hcd->self.busnum, portnum + 1, portsc); break; case USB_PORT_FEAT_REMOTE_WAKE_MASK: wake_mask = wIndex & 0xff00; xhci_set_remote_wake_mask(xhci, port, wake_mask); - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); xhci_dbg(xhci, "set port remote wake mask, actual port %d-%d status = 0x%x\n", - hcd->self.busnum, portnum + 1, temp); + hcd->self.busnum, portnum + 1, portsc); break; case USB_PORT_FEAT_BH_PORT_RESET: - temp |= PORT_WR; - xhci_portsc_writel(port, temp); - temp = xhci_portsc_readl(port); + portsc |= PORT_WR; + xhci_portsc_writel(port, portsc); + portsc = xhci_portsc_readl(port); break; case USB_PORT_FEAT_U1_TIMEOUT: if (hcd->speed < HCD_USB3) @@ -1538,7 +1538,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; } /* unblock any posted writes */ - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); break; case ClearPortFeature: portnum = (wIndex & 0xff) - 1; @@ -1546,23 +1546,23 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; port = ports[portnum]; - temp = xhci_portsc_readl(port); - if (temp == ~(u32)0) { + portsc = xhci_portsc_readl(port); + if (portsc == ~(u32)0) { xhci_hc_died(xhci); retval = -ENODEV; break; } /* FIXME: What new port features do we need to support? */ - temp = xhci_port_state_to_neutral(temp); + portsc = xhci_port_state_to_neutral(portsc); switch (wValue) { case USB_PORT_FEAT_SUSPEND: - temp = xhci_portsc_readl(port); + portsc = xhci_portsc_readl(port); xhci_dbg(xhci, "clear USB_PORT_FEAT_SUSPEND\n"); - xhci_dbg(xhci, "PORTSC %04x\n", temp); - if (temp & PORT_RESET) + xhci_dbg(xhci, "PORTSC %04x\n", portsc); + if (portsc & PORT_RESET) goto error; - if ((temp & PORT_PLS_MASK) == XDEV_U3) { - if ((temp & PORT_PE) == 0) + if ((portsc & PORT_PLS_MASK) == XDEV_U3) { + if ((portsc & PORT_PE) == 0) goto error; set_bit(portnum, &bus_state->resuming_ports); @@ -1593,7 +1593,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_C_ENABLE: case USB_PORT_FEAT_C_PORT_LINK_STATE: case USB_PORT_FEAT_C_PORT_CONFIG_ERROR: - xhci_clear_port_change_bit(xhci, wValue, port, temp); + xhci_clear_port_change_bit(xhci, wValue, port, portsc); break; case USB_PORT_FEAT_ENABLE: xhci_disable_port(xhci, port); From aef5305da27e79818e752a97d3d08516aa892d0a Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:37 +0300 Subject: [PATCH 141/176] usb: xhci: rename parameter to match argument 'portsc' A previous patch renamed the temporary variable holding the value read from the PORTSC register from 'temp' to 'portsc'. This patch follows up by updating the parameter names of all helper functions called from xhci_hub_control() that receive a PORTSC value, as well as the functions they call. Function changed: xhci_get_port_status() L xhci_get_usb3_port_status() L xhci_hub_report_usb3_link_state() L xhci_del_comp_mod_timer() xhci_get_ext_port_status() xhci_port_state_to_neutral() xhci_clear_port_change_bit() xhci_port_speed() The reason for the rename is to differentiate between port status/change bit to be written to PORTSC and replying to hub-class USB requests. Each of them use their specific macros. Use "portsc" name for PORTSC values and "status" for values intended for replying to hub-class USB request. A dedicated structure for USB hub port status responses ('struct usb_port_status' from ch11.h) exists and will be integrated in a later patch. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-21-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 61 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 9cd64d6989c9..8e134f6b4e37 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -375,11 +375,11 @@ static void xhci_hub_descriptor(struct usb_hcd *hcd, struct xhci_hcd *xhci, } -static unsigned int xhci_port_speed(unsigned int port_status) +static unsigned int xhci_port_speed(int portsc) { - if (DEV_LOWSPEED(port_status)) + if (DEV_LOWSPEED(portsc)) return USB_PORT_STAT_LOW_SPEED; - if (DEV_HIGHSPEED(port_status)) + if (DEV_HIGHSPEED(portsc)) return USB_PORT_STAT_HIGH_SPEED; /* * FIXME: Yes, we should check for full speed, but the core uses that as @@ -429,9 +429,9 @@ static unsigned int xhci_port_speed(unsigned int port_status) /** * xhci_port_state_to_neutral() - Clean up read portsc value back into writeable - * @state: u32 port value read from portsc register to be cleanup up + * @portsc: u32 port value read from portsc register to be cleanup up * - * Given a port state, this function returns a value that would result in the + * Given a portsc, this function returns a value that would result in the * port being in the same state, if the value was written to the port status * control register. * Save Read Only (RO) bits and save read/write bits where @@ -442,10 +442,10 @@ static unsigned int xhci_port_speed(unsigned int port_status) * changing port state. */ -u32 xhci_port_state_to_neutral(u32 state) +u32 xhci_port_state_to_neutral(u32 portsc) { /* Save read-only status and port state */ - return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); + return (portsc & XHCI_PORT_RO) | (portsc & XHCI_PORT_RWS); } EXPORT_SYMBOL_GPL(xhci_port_state_to_neutral); @@ -578,7 +578,7 @@ static void xhci_disable_port(struct xhci_hcd *xhci, struct xhci_port *port) } static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, struct xhci_port *port, - u32 port_status) + u32 portsc) { char *port_change_bit; u32 status; @@ -621,11 +621,11 @@ static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, struct return; } /* Change bits are all write 1 to clear */ - xhci_portsc_writel(port, port_status | status); - port_status = xhci_portsc_readl(port); + xhci_portsc_writel(port, portsc | status); + portsc = xhci_portsc_readl(port); xhci_dbg(xhci, "clear port%d %s change, portsc: 0x%x\n", - port->hcd_portnum + 1, port_change_bit, port_status); + port->hcd_portnum + 1, port_change_bit, portsc); } struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd) @@ -851,14 +851,14 @@ void xhci_test_and_clear_bit(struct xhci_hcd *xhci, struct xhci_port *port, /* Updates Link Status for super Speed port */ static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, - u32 *status, u32 status_reg) + u32 *status, u32 portsc) { - u32 pls = status_reg & PORT_PLS_MASK; + u32 pls = portsc & PORT_PLS_MASK; /* When the CAS bit is set then warm reset * should be performed on port */ - if (status_reg & PORT_CAS) { + if (portsc & PORT_CAS) { /* The CAS bit can be set while the port is * in any link state. * Only roothubs have CAS bit, so we @@ -910,10 +910,10 @@ static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, * the compliance mode timer is deleted. A port won't enter * compliance mode if it has previously entered U0. */ -static void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, int portnum) +static void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 portsc, int portnum) { u32 all_ports_seen_u0 = ((1 << xhci->usb3_rhub.num_ports) - 1); - bool port_in_u0 = ((status & PORT_PLS_MASK) == XDEV_U0); + bool port_in_u0 = ((portsc & PORT_PLS_MASK) == XDEV_U0); if (!(xhci->quirks & XHCI_COMP_MODE_QUIRK)) return; @@ -1018,13 +1018,13 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, return 0; } -static u32 xhci_get_ext_port_status(u32 raw_port_status, u32 port_li) +static u32 xhci_get_ext_port_status(u32 portsc, u32 port_li) { u32 ext_stat = 0; int speed_id; /* only support rx and tx lane counts of 1 in usb3.1 spec */ - speed_id = DEV_PORT_SPEED(raw_port_status); + speed_id = DEV_PORT_SPEED(portsc); ext_stat |= speed_id; /* bits 3:0, RX speed id */ ext_stat |= speed_id << 4; /* bits 7:4, TX speed id */ @@ -1150,7 +1150,7 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, * - Drop and reacquire the xHCI lock, in order to wait for port resume. */ static u32 xhci_get_port_status(struct usb_hcd *hcd, struct xhci_bus_state *bus_state, - int portnum, u32 raw_port_status, unsigned long *flags) + int portnum, u32 portsc, unsigned long *flags) __releases(&xhci->lock) __acquires(&xhci->lock) { @@ -1162,33 +1162,32 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, struct xhci_bus_state *bus_ port = rhub->ports[portnum]; /* common wPortChange bits */ - if (raw_port_status & PORT_CSC) + if (portsc & PORT_CSC) status |= USB_PORT_STAT_C_CONNECTION << 16; - if (raw_port_status & PORT_PEC) + if (portsc & PORT_PEC) status |= USB_PORT_STAT_C_ENABLE << 16; - if ((raw_port_status & PORT_OCC)) + if (portsc & PORT_OCC) status |= USB_PORT_STAT_C_OVERCURRENT << 16; - if ((raw_port_status & PORT_RC)) + if (portsc & PORT_RC) status |= USB_PORT_STAT_C_RESET << 16; /* common wPortStatus bits */ - if (raw_port_status & PORT_CONNECT) { + if (portsc & PORT_CONNECT) { status |= USB_PORT_STAT_CONNECTION; - status |= xhci_port_speed(raw_port_status); + status |= xhci_port_speed(portsc); } - if (raw_port_status & PORT_PE) + if (portsc & PORT_PE) status |= USB_PORT_STAT_ENABLE; - if (raw_port_status & PORT_OC) + if (portsc & PORT_OC) status |= USB_PORT_STAT_OVERCURRENT; - if (raw_port_status & PORT_RESET) + if (portsc & PORT_RESET) status |= USB_PORT_STAT_RESET; /* USB2 and USB3 specific bits, including Port Link State */ if (hcd->speed >= HCD_USB3) - xhci_get_usb3_port_status(port, &status, raw_port_status); + xhci_get_usb3_port_status(port, &status, portsc); else - xhci_get_usb2_port_status(port, &status, raw_port_status, - flags); + xhci_get_usb2_port_status(port, &status, portsc, flags); if (bus_state->port_c_suspend & (1 << portnum)) status |= USB_PORT_STAT_C_SUSPEND << 16; From 5dfc7f985f09f998fa3a384d79ea6c64fbc7afd8 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:38 +0300 Subject: [PATCH 142/176] usb: xhci: cleanup xhci_hub_report_usb3_link_state() Improve readability of xhci_hub_report_usb3_link_state(). Comments are shortened and clarified, and the code now makes it explicit when the Port Link State (PLS) value is modified versus when other status bits are updated. No functional changes. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-22-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-hub.c | 58 ++++++++++++++----------------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 8e134f6b4e37..bacd0ddd0d09 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -850,53 +850,37 @@ void xhci_test_and_clear_bit(struct xhci_hcd *xhci, struct xhci_port *port, } /* Updates Link Status for super Speed port */ -static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, - u32 *status, u32 portsc) +static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, u32 *status, u32 portsc) { u32 pls = portsc & PORT_PLS_MASK; - /* When the CAS bit is set then warm reset - * should be performed on port + /* + * CAS indicates that a warm reset is required, it may be set in any + * link state and is only present on roothubs. */ if (portsc & PORT_CAS) { - /* The CAS bit can be set while the port is - * in any link state. - * Only roothubs have CAS bit, so we - * pretend to be in compliance mode - * unless we're already in compliance - * or the inactive state. + /* + * If not already in Compliance or Inactive state, + * report Compliance Mode so the hub logic triggers a warm reset. */ - if (pls != XDEV_COMP_MODE && - pls != XDEV_INACTIVE) { + if (pls != XDEV_COMP_MODE && pls != XDEV_INACTIVE) pls = USB_SS_PORT_LS_COMP_MOD; - } - /* Return also connection bit - - * hub state machine resets port - * when this bit is set. - */ - pls |= USB_PORT_STAT_CONNECTION; - } else { - /* - * Resume state is an xHCI internal state. Do not report it to - * usb core, instead, pretend to be U3, thus usb core knows - * it's not ready for transfer. - */ - if (pls == XDEV_RESUME) { - *status |= USB_SS_PORT_LS_U3; - return; - } + /* Signal a connection change to force a reset */ + *status |= USB_PORT_STAT_CONNECTION; + } else if (pls == XDEV_RESUME) { /* - * If CAS bit isn't set but the Port is already at - * Compliance Mode, fake a connection so the USB core - * notices the Compliance state and resets the port. - * This resolves an issue generated by the SN65LVPE502CP - * in which sometimes the port enters compliance mode - * caused by a delay on the host-device negotiation. + * Resume is an internal xHCI-only state and must not be exposed + * to usbcore. Report it as U3 so transfers are blocked. */ - if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && - (pls == XDEV_COMP_MODE)) - pls |= USB_PORT_STAT_CONNECTION; + pls = USB_SS_PORT_LS_U3; + } else if (pls == XDEV_COMP_MODE) { + /* + * Some hardware may enter Compliance Mode without CAS. + * Fake a connection event so usbcore notices and resets the port. + */ + if (xhci->quirks & XHCI_COMP_MODE_QUIRK) + *status |= USB_PORT_STAT_CONNECTION; } /* update status field */ From d81a5580845875de1f3e7156b2317f89bf81a936 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:39 +0300 Subject: [PATCH 143/176] usb: xhci: simpilfy resume root hub code Resume roothubs without checking 'retval' value, as it is always '0'. Due to changes made in commit 79989bd4ab86 ("xhci: always resume roothubs if xHC was reset during resume") the check is redundant. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-23-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index ece3ff7916ff..6d27c471d4da 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1245,29 +1245,25 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume) xhci_dbc_resume(xhci); - if (retval == 0) { - /* - * Resume roothubs only if there are pending events. - * USB 3 devices resend U3 LFPS wake after a 100ms delay if - * the first wake signalling failed, give it that chance if - * there are suspended USB 3 devices. - */ - if (xhci->usb3_rhub.bus_state.suspended_ports || - xhci->usb3_rhub.bus_state.bus_suspended) - suspended_usb3_devs = true; + /* + * Resume roothubs only if there are pending events. + * USB 3 devices resend U3 LFPS wake after a 100ms delay if + * the first wake signalling failed, give it that chance if + * there are suspended USB 3 devices. + */ + if (xhci->usb3_rhub.bus_state.suspended_ports || xhci->usb3_rhub.bus_state.bus_suspended) + suspended_usb3_devs = true; + pending_portevent = xhci_pending_portevent(xhci); + if (suspended_usb3_devs && !pending_portevent && is_auto_resume) { + msleep(120); pending_portevent = xhci_pending_portevent(xhci); + } - if (suspended_usb3_devs && !pending_portevent && is_auto_resume) { - msleep(120); - pending_portevent = xhci_pending_portevent(xhci); - } - - if (pending_portevent) { - if (xhci->shared_hcd) - usb_hcd_resume_root_hub(xhci->shared_hcd); - usb_hcd_resume_root_hub(hcd); - } + if (pending_portevent) { + if (xhci->shared_hcd) + usb_hcd_resume_root_hub(xhci->shared_hcd); + usb_hcd_resume_root_hub(hcd); } /* From 9a7ad750a8fbd274e27c1f045271bb2f876e0569 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:40 +0300 Subject: [PATCH 144/176] usb: xhci: move roothub port limit validation Function xhci_setup_port_arrays() limits the number of roothub ports for both USB 2 and 3, this causes code repetition. Solve this by moving roothub port limits validation to xhci_create_rhub_port_array(). Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-24-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 45 +++++++++++++++---------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 4156822eb000..a9fd26559e50 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -2165,15 +2165,28 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, /* FIXME: Should we disable ports not in the Extended Capabilities? */ } -static void xhci_create_rhub_port_array(struct xhci_hcd *xhci, - struct xhci_hub *rhub, gfp_t flags) +static void xhci_create_rhub_port_array(struct xhci_hcd *xhci, struct xhci_hub *rhub, + unsigned int max_ports, gfp_t flags) { int port_index = 0; int i; struct device *dev = xhci_to_hcd(xhci)->self.sysdev; - if (!rhub->num_ports) + if (!rhub->num_ports) { + xhci_info(xhci, "USB%u root hub has no ports\n", rhub->maj_rev); return; + } + + /* + * Place limits on the number of roothub ports so that the hub + * descriptors aren't longer than the USB core will allocate. + */ + if (rhub->num_ports > max_ports) { + xhci->usb3_rhub.num_ports = max_ports; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Limiting USB%u root hub ports to %u", + rhub->maj_rev, max_ports); + } + rhub->ports = kcalloc_node(rhub->num_ports, sizeof(*rhub->ports), flags, dev_to_node(dev)); if (!rhub->ports) @@ -2269,30 +2282,8 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) "Found %u USB 2.0 ports and %u USB 3.0 ports.", xhci->usb2_rhub.num_ports, xhci->usb3_rhub.num_ports); - /* Place limits on the number of roothub ports so that the hub - * descriptors aren't longer than the USB core will allocate. - */ - if (xhci->usb3_rhub.num_ports > USB_SS_MAXPORTS) { - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "Limiting USB 3.0 roothub ports to %u.", - USB_SS_MAXPORTS); - xhci->usb3_rhub.num_ports = USB_SS_MAXPORTS; - } - if (xhci->usb2_rhub.num_ports > USB_MAXCHILDREN) { - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "Limiting USB 2.0 roothub ports to %u.", - USB_MAXCHILDREN); - xhci->usb2_rhub.num_ports = USB_MAXCHILDREN; - } - - if (!xhci->usb2_rhub.num_ports) - xhci_info(xhci, "USB2 root hub has no ports\n"); - - if (!xhci->usb3_rhub.num_ports) - xhci_info(xhci, "USB3 root hub has no ports\n"); - - xhci_create_rhub_port_array(xhci, &xhci->usb2_rhub, flags); - xhci_create_rhub_port_array(xhci, &xhci->usb3_rhub, flags); + xhci_create_rhub_port_array(xhci, &xhci->usb2_rhub, USB_MAXCHILDREN, flags); + xhci_create_rhub_port_array(xhci, &xhci->usb3_rhub, USB_SS_MAXPORTS, flags); return 0; } From dad6711b9eac38ffe6fc4bebc7c6b7a721692497 Mon Sep 17 00:00:00 2001 From: Niklas Neronin Date: Thu, 2 Apr 2026 16:13:41 +0300 Subject: [PATCH 145/176] usb: xhci: remove duplicate '0x' prefix Prefix "0x" is automatically added by '%pad'. Signed-off-by: Niklas Neronin Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-25-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 6 +++--- drivers/usb/host/xhci-ring.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index a9fd26559e50..997fe90f54e5 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -994,14 +994,14 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, if (!dev->out_ctx) goto fail; - xhci_dbg(xhci, "Slot %d output ctx = 0x%pad (dma)\n", slot_id, &dev->out_ctx->dma); + xhci_dbg(xhci, "Slot %d output ctx = %pad (dma)\n", slot_id, &dev->out_ctx->dma); /* Allocate the (input) device context for address device command */ dev->in_ctx = xhci_alloc_container_ctx(xhci, XHCI_CTX_TYPE_INPUT, flags); if (!dev->in_ctx) goto fail; - xhci_dbg(xhci, "Slot %d input ctx = 0x%pad (dma)\n", slot_id, &dev->in_ctx->dma); + xhci_dbg(xhci, "Slot %d input ctx = %pad (dma)\n", slot_id, &dev->in_ctx->dma); /* Initialize the cancellation and bandwidth list for each ep */ for (i = 0; i < 31; i++) { @@ -2424,7 +2424,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci->dcbaa->dma = dma; xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "Device context base array address = 0x%pad (DMA), %p (virt)", + "Device context base array address = %pad (DMA), %p (virt)", &xhci->dcbaa->dma, xhci->dcbaa); /* diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 3589af0e2768..e47e644b296e 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -755,7 +755,7 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, } if ((ep->ep_state & SET_DEQ_PENDING)) { - xhci_warn(xhci, "Set TR Deq already pending, don't submit for 0x%pad\n", + xhci_warn(xhci, "Set TR Deq already pending, don't submit for %pad\n", &addr); return -EBUSY; } @@ -763,7 +763,7 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci, /* This function gets called from contexts where it cannot sleep */ cmd = xhci_alloc_command(xhci, false, GFP_ATOMIC); if (!cmd) { - xhci_warn(xhci, "Can't alloc Set TR Deq cmd 0x%pad\n", &addr); + xhci_warn(xhci, "Can't alloc Set TR Deq cmd %pad\n", &addr); return -ENOMEM; } From 25e531b422dc2ac90cdae3b6e74b5cdeb081440d Mon Sep 17 00:00:00 2001 From: Michal Pecio Date: Thu, 2 Apr 2026 16:13:42 +0300 Subject: [PATCH 146/176] usb: xhci: Make usb_host_endpoint.hcpriv survive endpoint_disable() xHCI hardware maintains its endpoint state between add_endpoint() and drop_endpoint() calls followed by successful check_bandwidth(). So does the driver. Core may call endpoint_disable() during xHCI endpoint life, so don't clear host_ep->hcpriv then, because this breaks endpoint_reset(). If a driver calls usb_set_interface(), submits URBs which make host sequence state non-zero and calls usb_clear_halt(), the device clears its sequence state but xhci_endpoint_reset() bails out. The next URB malfunctions: USB2 loses one packet, USB3 gets Transaction Error or may not complete at all on some (buggy?) HCs from ASMedia and AMD. This is triggered by uvcvideo on bulk video devices. The code was copied from ehci_endpoint_disable() but it isn't needed here - hcpriv should only be NULL on emulated root hub endpoints. It might prevent resetting and inadvertently enabling a disabled and dropped endpoint, but core shouldn't try to reset dropped endpoints. Document xhci requirements regarding hcpriv. They are currently met. Fixes: 18b74067ac78 ("xhci: Fix use-after-free regression in xhci clear hub TT implementation") Cc: stable@vger.kernel.org Signed-off-by: Michal Pecio Signed-off-by: Mathias Nyman Link: https://patch.msgid.link/20260402131342.2628648-26-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci.c | 1 - include/linux/usb.h | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 6d27c471d4da..a54f5b57f205 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -3292,7 +3292,6 @@ static void xhci_endpoint_disable(struct usb_hcd *hcd, xhci_dbg(xhci, "endpoint disable with ep_state 0x%x\n", ep->ep_state); done: - host_ep->hcpriv = NULL; spin_unlock_irqrestore(&xhci->lock, flags); } diff --git a/include/linux/usb.h b/include/linux/usb.h index 815f2212936e..779bbfdfa0c7 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -54,7 +54,8 @@ struct ep_device; * @eusb2_isoc_ep_comp: eUSB2 isoc companion descriptor for this endpoint * @urb_list: urbs queued to this endpoint; maintained by usbcore * @hcpriv: for use by HCD; typically holds hardware dma queue head (QH) - * with one or more transfer descriptors (TDs) per urb + * with one or more transfer descriptors (TDs) per urb; must be preserved + * by core while BW is allocated for the endpoint * @ep_dev: ep_device for sysfs info * @extra: descriptors following this endpoint in the configuration * @extralen: how many bytes of "extra" are valid From 49eac82653e13243cec5f8c25e35161b922a9c80 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 3 Apr 2026 14:03:37 +0200 Subject: [PATCH 147/176] Revert "usb: cdnsp: Add support for device-only configuration" This reverts commit 7b7f2dd913829e06705035dfc41ca25fa6ec68d3. There was some problems with an earlier cdns3 change, so this one needs to be backed out as well. Cc: Pawel Laszczak Cc: Bjorn Helgaas Reported-by: Peter Chen Link: https://lore.kernel.org/r/ac+LEWMCQpLSnfoD@nchen-desktop Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-plat.c | 24 ++++++++--------- drivers/usb/cdns3/cdnsp-pci.c | 47 +++++++--------------------------- drivers/usb/cdns3/core.c | 3 +-- drivers/usb/cdns3/core.h | 5 +--- drivers/usb/cdns3/drd.c | 16 ++---------- include/linux/pci_ids.h | 1 - 6 files changed, 23 insertions(+), 73 deletions(-) diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index 33746e672cda..71c612e27b73 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -75,7 +75,6 @@ static int cdns3_plat_probe(struct platform_device *pdev) if (cdns->pdata && cdns->pdata->override_apb_timeout) cdns->override_apb_timeout = cdns->pdata->override_apb_timeout; - cdns->no_drd = device_property_read_bool(dev, "no_drd"); platform_set_drvdata(pdev, cdns); ret = platform_get_irq_byname(pdev, "host"); @@ -108,23 +107,21 @@ static int cdns3_plat_probe(struct platform_device *pdev) cdns->dev_regs = regs; - if (!cdns->no_drd) { - cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); - if (cdns->otg_irq < 0) - return dev_err_probe(dev, cdns->otg_irq, - "Failed to get otg IRQ\n"); + cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); + if (cdns->otg_irq < 0) + return dev_err_probe(dev, cdns->otg_irq, + "Failed to get otg IRQ\n"); - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); - if (!res) { - dev_err(dev, "couldn't get otg resource\n"); - return -ENXIO; - } - - cdns->otg_res = *res; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); + if (!res) { + dev_err(dev, "couldn't get otg resource\n"); + return -ENXIO; } cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable"); + cdns->otg_res = *res; + cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup"); if (cdns->wakeup_irq == -EPROBE_DEFER) return cdns->wakeup_irq; @@ -161,7 +158,6 @@ static int cdns3_plat_probe(struct platform_device *pdev) goto err_cdns_init; cdns->gadget_init = cdns3_plat_gadget_init; - ret = cdns_core_init_role(cdns); if (ret) goto err_cdns_init; diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c index e20c59ceb8a4..432007cfe695 100644 --- a/drivers/usb/cdns3/cdnsp-pci.c +++ b/drivers/usb/cdns3/cdnsp-pci.c @@ -19,7 +19,6 @@ struct cdnsp_wrap { struct platform_device *plat_dev; - struct property_entry prop[3]; struct resource dev_res[6]; int devfn; }; @@ -30,15 +29,10 @@ struct cdnsp_wrap { #define RES_HOST_ID 3 #define RES_DEV_ID 4 #define RES_DRD_ID 5 -/* DRD PCI configuration - 64-bit addressing */ -/* First PCI function */ + #define PCI_BAR_HOST 0 -#define PCI_BAR_DEV 2 -/* Second PCI function */ #define PCI_BAR_OTG 0 -/* Device only PCI configuration - 32-bit addressing */ -/* First PCI function */ -#define PCI_BAR_ONLY_DEV 1 +#define PCI_BAR_DEV 2 #define PCI_DEV_FN_HOST_DEVICE 0 #define PCI_DEV_FN_OTG 1 @@ -71,7 +65,6 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, struct cdnsp_wrap *wrap; struct resource *res; struct pci_dev *func; - bool no_drd = false; int ret = 0; /* @@ -82,14 +75,11 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, pdev->devfn != PCI_DEV_FN_OTG)) return -EINVAL; - if (pdev->device == PCI_DEVICE_ID_CDNS_UDC_USBSSP) - no_drd = true; - func = cdnsp_get_second_fun(pdev); - if (!func && !no_drd) + if (!func) return -EINVAL; - if ((func && func->class == PCI_CLASS_SERIAL_USB_XHCI) || + if (func->class == PCI_CLASS_SERIAL_USB_XHCI || pdev->class == PCI_CLASS_SERIAL_USB_XHCI) { ret = -EINVAL; goto put_pci; @@ -103,7 +93,7 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, pci_set_master(pdev); - if (func && pci_is_enabled(func)) { + if (pci_is_enabled(func)) { wrap = pci_get_drvdata(func); } else { wrap = kzalloc_obj(*wrap); @@ -116,13 +106,10 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, res = wrap->dev_res; if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) { - int bar_dev = no_drd ? PCI_BAR_ONLY_DEV : PCI_BAR_DEV; - /* Function 0: host(BAR_0) + device(BAR_2). */ dev_dbg(&pdev->dev, "Initialize Device resources\n"); - - res[RES_DEV_ID].start = pci_resource_start(pdev, bar_dev); - res[RES_DEV_ID].end = pci_resource_end(pdev, bar_dev); + res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV); + res[RES_DEV_ID].end = pci_resource_end(pdev, PCI_BAR_DEV); res[RES_DEV_ID].name = "dev"; res[RES_DEV_ID].flags = IORESOURCE_MEM; dev_dbg(&pdev->dev, "USBSSP-DEV physical base addr: %pa\n", @@ -158,20 +145,9 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ; } - if (no_drd || pci_is_enabled(func)) { - u8 idx = 0; - + if (pci_is_enabled(func)) { /* set up platform device info */ pdata.override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE; - if (no_drd) { - wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "peripheral"); - wrap->prop[idx++] = PROPERTY_ENTRY_BOOL("no_drd"); - } else { - wrap->prop[idx++] = PROPERTY_ENTRY_STRING("dr_mode", "otg"); - wrap->prop[idx++] = PROPERTY_ENTRY_BOOL("usb-role-switch"); - } - - wrap->prop[idx] = (struct property_entry){ }; memset(&plat_info, 0, sizeof(plat_info)); plat_info.parent = &pdev->dev; plat_info.fwnode = pdev->dev.fwnode; @@ -182,7 +158,6 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, plat_info.dma_mask = pdev->dma_mask; plat_info.data = &pdata; plat_info.size_data = sizeof(pdata); - plat_info.properties = wrap->prop; wrap->devfn = pdev->devfn; /* register platform device */ wrap->plat_dev = platform_device_register_full(&plat_info); @@ -210,17 +185,13 @@ static void cdnsp_pci_remove(struct pci_dev *pdev) if (wrap->devfn == pdev->devfn) platform_device_unregister(wrap->plat_dev); - if (!func || !pci_is_enabled(func)) + if (!pci_is_enabled(func)) kfree(wrap); pci_dev_put(func); } static const struct pci_device_id cdnsp_pci_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP), - .class = PCI_CLASS_SERIAL_USB_DEVICE }, - { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC_USBSSP), - .class = PCI_CLASS_SERIAL_USB_CDNS }, { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), .class = PCI_CLASS_SERIAL_USB_DEVICE }, { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 72f7acba6258..10f00b6c3c83 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -71,8 +71,7 @@ static void cdns_role_stop(struct cdns *cdns) static void cdns_exit_roles(struct cdns *cdns) { cdns_role_stop(cdns); - if (!cdns->no_drd) - cdns_drd_exit(cdns); + cdns_drd_exit(cdns); } /** diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 6abe231f4559..dc8c4137de15 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -80,11 +80,9 @@ struct cdns3_platform_data { * @pdata: platform data from glue layer * @lock: spinlock structure * @xhci_plat_data: xhci private data structure pointer - * @gadget_init: pointer to gadget initialization function * @override_apb_timeout: hold value of APB timeout. For value 0 the default * value in CHICKEN_BITS_3 will be preserved. - * @no_drd: DRD register block is inaccessible - driver handles only - * device mode. + * @gadget_init: pointer to gadget initialization function */ struct cdns { struct device *dev; @@ -124,7 +122,6 @@ struct cdns { struct xhci_plat_priv *xhci_plat_data; int (*gadget_init)(struct cdns *cdns); u32 override_apb_timeout; - bool no_drd; }; int cdns_hw_role_switch(struct cdns *cdns); diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index 38f3051c2188..84fb38a5723a 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -107,7 +107,7 @@ void cdns_clear_vbus(struct cdns *cdns) { u32 reg; - if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd) + if (cdns->version != CDNSP_CONTROLLER_V2) return; reg = readl(&cdns->otg_cdnsp_regs->override); @@ -120,7 +120,7 @@ void cdns_set_vbus(struct cdns *cdns) { u32 reg; - if (cdns->version != CDNSP_CONTROLLER_V2 || cdns->no_drd) + if (cdns->version != CDNSP_CONTROLLER_V2) return; reg = readl(&cdns->otg_cdnsp_regs->override); @@ -234,9 +234,6 @@ int cdns_drd_gadget_on(struct cdns *cdns) u32 ready_bit; int ret, val; - if (cdns->no_drd) - return 0; - /* switch OTG core */ writel(OTGCMD_DEV_BUS_REQ | reg, &cdns->otg_regs->cmd); @@ -268,9 +265,6 @@ void cdns_drd_gadget_off(struct cdns *cdns) { u32 val; - if (cdns->no_drd) - return; - /* * Driver should wait at least 10us after disabling Device * before turning-off Device (DEV_BUS_DROP). @@ -398,12 +392,6 @@ int cdns_drd_init(struct cdns *cdns) u32 state, reg; int ret; - if (cdns->no_drd) { - cdns->version = CDNSP_CONTROLLER_V2; - cdns->dr_mode = USB_DR_MODE_PERIPHERAL; - return 0; - } - regs = devm_ioremap_resource(cdns->dev, &cdns->otg_res); if (IS_ERR(regs)) return PTR_ERR(regs); diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index a931fb201402..406abf629be2 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2424,7 +2424,6 @@ #define PCI_DEVICE_ID_CDNS_USBSS 0x0100 #define PCI_DEVICE_ID_CDNS_USB 0x0120 #define PCI_DEVICE_ID_CDNS_USBSSP 0x0200 -#define PCI_DEVICE_ID_CDNS_UDC_USBSSP 0x0400 #define PCI_VENDOR_ID_ARECA 0x17d3 #define PCI_DEVICE_ID_ARECA_1110 0x1110 From 29fea9f4ece6d071a906187d768a87ac41e6e041 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 3 Apr 2026 14:05:13 +0200 Subject: [PATCH 148/176] Revert "usb: cdns3: Add USBSSP platform driver support" This reverts commit 6076388ca1eda808b95f9479f3b04839d348a2f7. There were some build issues as reported by Arnd, so revert this for now. Cc: Peter Chen Cc: Pawel Laszczak Reported-by: Arnd Bergmann Link: https://lore.kernel.org/r/ac+LEWMCQpLSnfoD@nchen-desktop Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/Kconfig | 50 ++++--- drivers/usb/cdns3/Makefile | 32 +++-- drivers/usb/cdns3/cdns3-gadget.c | 4 - drivers/usb/cdns3/cdns3-plat.c | 17 +-- drivers/usb/cdns3/cdnsp-gadget.c | 4 - drivers/usb/cdns3/cdnsp-pci.c | 209 +++++++++++++++++------------- drivers/usb/cdns3/core.c | 11 +- drivers/usb/cdns3/core.h | 5 +- drivers/usb/cdns3/gadget-export.h | 4 +- 9 files changed, 175 insertions(+), 161 deletions(-) diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig index 97fa84dddbca..0a514b591527 100644 --- a/drivers/usb/cdns3/Kconfig +++ b/drivers/usb/cdns3/Kconfig @@ -20,6 +20,10 @@ config USB_CDNS3 Say Y here if your system has a Cadence USB3 dual-role controller. It supports: dual-role switch, Host-only, and Peripheral-only. + If you choose to build this driver is a dynamically linked + as module, the module will be called cdns3.ko. +endif + if USB_CDNS3 config USB_CDNS3_GADGET @@ -85,27 +89,29 @@ config USB_CDNS3_STARFIVE If you choose to build this driver as module it will be dynamically linked and module will be called cdns3-starfive.ko +endif -endif # USB_CDNS3 +if USB_CDNS_SUPPORT -config USB_CDNSP - tristate "Cadence USBSSP Dual-Role Controller" - depends on USB_CDNS_SUPPORT +config USB_CDNSP_PCI + tristate "Cadence CDNSP Dual-Role Controller" + depends on USB_CDNS_SUPPORT && USB_PCI && ACPI help - Say Y here if your system has a Cadence USBSSP dual-role controller. - It supports: dual-role switch, Host-only, and Peripheral-only. - Cadence CDNSP Controller device mode is very similar to XHCI controller. - Therefore some algorithms used has been taken from xHCI driver. - Host controller is compliant with XHCI so it uses standard XHCI driver. + Say Y here if your system has a Cadence CDNSP dual-role controller. + It supports: dual-role switch Host-only, and Peripheral-only. -if USB_CDNSP + If you choose to build this driver is a dynamically linked + module, the module will be called cdnsp.ko. +endif + +if USB_CDNSP_PCI config USB_CDNSP_GADGET - bool "Cadence USBSSP device controller" - depends on USB_GADGET=y || USB_GADGET=USB_CDNSP + bool "Cadence CDNSP device controller" + depends on USB_GADGET=y || USB_GADGET=USB_CDNSP_PCI help Say Y here to enable device controller functionality of the - Cadence USBSSP-DEV driver. + Cadence CDNSP-DEV driver. Cadence CDNSP Device Controller in device mode is very similar to XHCI controller. Therefore some algorithms @@ -114,8 +120,8 @@ config USB_CDNSP_GADGET It doesn't support LS. config USB_CDNSP_HOST - bool "Cadence USBSSP host controller" - depends on USB=y || USB=USB_CDNSP + bool "Cadence CDNSP host controller" + depends on USB=y || USB=USB_CDNSP_PCI select USB_CDNS_HOST help Say Y here to enable host controller functionality of the @@ -124,16 +130,4 @@ config USB_CDNSP_HOST Host controller is compliant with XHCI so it uses standard XHCI driver. -config USB_CDNSP_PCI - tristate "Cadence USBSSP support on PCIe-based platforms" - depends on USB_PCI && ACPI - help - If you're using the USBSSP Core IP with a PCIe, please say - 'Y' or 'M' here. - - If you choose to build this driver as module it will - be dynamically linked and module will be called cdnsp-pci.ko - -endif # USB_CDNSP - -endif # USB_CDNS_SUPPORT +endif diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index 63484f145bb9..48dfae75b5aa 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -4,33 +4,41 @@ CFLAGS_cdns3-trace.o := -I$(src) CFLAGS_cdnsp-trace.o := -I$(src) cdns-usb-common-y := core.o drd.o +cdns3-y := cdns3-plat.o ifeq ($(CONFIG_USB),m) obj-m += cdns-usb-common.o -obj-m += cdns3-plat.o +obj-m += cdns3.o else obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns-usb-common.o -obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns3-plat.o +obj-$(CONFIG_USB_CDNS3) += cdns3.o endif cdns-usb-common-$(CONFIG_USB_CDNS_HOST) += host.o +cdns3-$(CONFIG_USB_CDNS3_GADGET) += cdns3-gadget.o cdns3-ep0.o -# For CDNS3 gadget ifneq ($(CONFIG_USB_CDNS3_GADGET),) -cdns3-y := cdns3-gadget.o cdns3-ep0.o cdns3-$(CONFIG_TRACING) += cdns3-trace.o -obj-$(CONFIG_USB_CDNS3) += cdns3.o endif + obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o obj-$(CONFIG_USB_CDNS3_STARFIVE) += cdns3-starfive.o -# For CDNSP gadget -ifneq ($(CONFIG_USB_CDNSP_GADGET),) -cdnsp-y := cdnsp-ring.o cdnsp-gadget.o \ - cdnsp-mem.o cdnsp-ep0.o -cdnsp-$(CONFIG_TRACING) += cdnsp-trace.o -obj-$(CONFIG_USB_CDNSP) += cdnsp.o +cdnsp-udc-pci-y := cdnsp-pci.o + +ifdef CONFIG_USB_CDNSP_PCI +ifeq ($(CONFIG_USB),m) +obj-m += cdnsp-udc-pci.o +else +obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-udc-pci.o +endif +endif + +cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET) += cdnsp-ring.o cdnsp-gadget.o \ + cdnsp-mem.o cdnsp-ep0.o + +ifneq ($(CONFIG_USB_CDNSP_GADGET),) +cdnsp-udc-pci-$(CONFIG_TRACING) += cdnsp-trace.o endif -obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-pci.o diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index b800bd1bedd4..d59a60a16ec7 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -3508,7 +3508,3 @@ int cdns3_gadget_init(struct cdns *cdns) return 0; } -EXPORT_SYMBOL_GPL(cdns3_gadget_init); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Cadence USBSS DRD Driver - gadget"); diff --git a/drivers/usb/cdns3/cdns3-plat.c b/drivers/usb/cdns3/cdns3-plat.c index 71c612e27b73..735df88774e4 100644 --- a/drivers/usb/cdns3/cdns3-plat.c +++ b/drivers/usb/cdns3/cdns3-plat.c @@ -44,14 +44,6 @@ static void set_phy_power_off(struct cdns *cdns) phy_power_off(cdns->usb2_phy); } -static int cdns3_plat_gadget_init(struct cdns *cdns) -{ - if (cdns->version < CDNSP_CONTROLLER_V2) - return cdns3_gadget_init(cdns); - else - return cdnsp_gadget_init(cdns); -} - /** * cdns3_plat_probe - probe for cdns3 core device * @pdev: Pointer to cdns3 core platform device @@ -72,8 +64,6 @@ static int cdns3_plat_probe(struct platform_device *pdev) cdns->dev = dev; cdns->pdata = dev_get_platdata(dev); - if (cdns->pdata && cdns->pdata->override_apb_timeout) - cdns->override_apb_timeout = cdns->pdata->override_apb_timeout; platform_set_drvdata(pdev, cdns); @@ -153,12 +143,9 @@ static int cdns3_plat_probe(struct platform_device *pdev) if (ret) goto err_phy_power_on; - ret = cdns_init(cdns); - if (ret) - goto err_cdns_init; + cdns->gadget_init = cdns3_gadget_init; - cdns->gadget_init = cdns3_plat_gadget_init; - ret = cdns_core_init_role(cdns); + ret = cdns_init(cdns); if (ret) goto err_cdns_init; diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 8db7eee528a1..6b3815f8a6e5 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -2075,7 +2075,3 @@ int cdnsp_gadget_init(struct cdns *cdns) return 0; } -EXPORT_SYMBOL_GPL(cdnsp_gadget_init); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Cadence CDNSP DRD Driver - gadget"); diff --git a/drivers/usb/cdns3/cdnsp-pci.c b/drivers/usb/cdns3/cdnsp-pci.c index 432007cfe695..566d94e49102 100644 --- a/drivers/usb/cdns3/cdnsp-pci.c +++ b/drivers/usb/cdns3/cdnsp-pci.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Cadence USBSSP PCI Glue driver. + * Cadence PCI Glue driver. * * Copyright (C) 2019 Cadence. * @@ -16,19 +16,7 @@ #include #include "core.h" - -struct cdnsp_wrap { - struct platform_device *plat_dev; - struct resource dev_res[6]; - int devfn; -}; - -#define RES_IRQ_HOST_ID 0 -#define RES_IRQ_PERIPHERAL_ID 1 -#define RES_IRQ_OTG_ID 2 -#define RES_HOST_ID 3 -#define RES_DEV_ID 4 -#define RES_DRD_ID 5 +#include "gadget-export.h" #define PCI_BAR_HOST 0 #define PCI_BAR_OTG 0 @@ -38,16 +26,16 @@ struct cdnsp_wrap { #define PCI_DEV_FN_OTG 1 #define PCI_DRIVER_NAME "cdns-pci-usbssp" -#define PLAT_DRIVER_NAME "cdns-usb3" +#define PLAT_DRIVER_NAME "cdns-usbssp" -#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20 +#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20 static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev) { /* * Gets the second function. - * Platform has two function. The first keeps resources for - * Host/Device while the second keeps resources for DRD/OTG. + * Platform has two function. The fist keeps resources for + * Host/Device while the secon keeps resources for DRD/OTG. */ if (pdev->device == PCI_DEVICE_ID_CDNS_USBSSP) return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSS, NULL); @@ -60,12 +48,11 @@ static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev) static int cdnsp_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - struct platform_device_info plat_info; - static struct cdns3_platform_data pdata; - struct cdnsp_wrap *wrap; - struct resource *res; + struct device *dev = &pdev->dev; struct pci_dev *func; - int ret = 0; + struct resource *res; + struct cdns *cdnsp; + int ret; /* * For GADGET/HOST PCI (devfn) function number is 0, @@ -92,105 +79,146 @@ static int cdnsp_pci_probe(struct pci_dev *pdev, } pci_set_master(pdev); - if (pci_is_enabled(func)) { - wrap = pci_get_drvdata(func); + cdnsp = pci_get_drvdata(func); } else { - wrap = kzalloc_obj(*wrap); - if (!wrap) { + cdnsp = kzalloc_obj(*cdnsp); + if (!cdnsp) { ret = -ENOMEM; goto put_pci; } } - res = wrap->dev_res; + /* For GADGET device function number is 0. */ + if (pdev->devfn == 0) { + resource_size_t rsrc_start, rsrc_len; - if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) { - /* Function 0: host(BAR_0) + device(BAR_2). */ - dev_dbg(&pdev->dev, "Initialize Device resources\n"); - res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV); - res[RES_DEV_ID].end = pci_resource_end(pdev, PCI_BAR_DEV); - res[RES_DEV_ID].name = "dev"; - res[RES_DEV_ID].flags = IORESOURCE_MEM; - dev_dbg(&pdev->dev, "USBSSP-DEV physical base addr: %pa\n", - &res[RES_DEV_ID].start); + /* Function 0: host(BAR_0) + device(BAR_1).*/ + dev_dbg(dev, "Initialize resources\n"); + rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV); + rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV); + res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev"); + if (!res) { + dev_dbg(dev, "controller already in use\n"); + ret = -EBUSY; + goto free_cdnsp; + } - res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST); - res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST); - res[RES_HOST_ID].name = "xhci"; - res[RES_HOST_ID].flags = IORESOURCE_MEM; - dev_dbg(&pdev->dev, "USBSSP-XHCI physical base addr: %pa\n", - &res[RES_HOST_ID].start); + cdnsp->dev_regs = devm_ioremap(dev, rsrc_start, rsrc_len); + if (!cdnsp->dev_regs) { + dev_dbg(dev, "error mapping memory\n"); + ret = -EFAULT; + goto free_cdnsp; + } - /* Interrupt for XHCI */ - wrap->dev_res[RES_IRQ_HOST_ID].start = pdev->irq; - wrap->dev_res[RES_IRQ_HOST_ID].name = "host"; - wrap->dev_res[RES_IRQ_HOST_ID].flags = IORESOURCE_IRQ; + cdnsp->dev_irq = pdev->irq; + dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n", + &rsrc_start); - /* Interrupt for device. It's the same as for HOST. */ - wrap->dev_res[RES_IRQ_PERIPHERAL_ID].start = pdev->irq; - wrap->dev_res[RES_IRQ_PERIPHERAL_ID].name = "peripheral"; - wrap->dev_res[RES_IRQ_PERIPHERAL_ID].flags = IORESOURCE_IRQ; + res = &cdnsp->xhci_res[0]; + res->start = pci_resource_start(pdev, PCI_BAR_HOST); + res->end = pci_resource_end(pdev, PCI_BAR_HOST); + res->name = "xhci"; + res->flags = IORESOURCE_MEM; + dev_dbg(dev, "USBSS-XHCI physical base addr: %pa\n", + &res->start); + + /* Interrupt for XHCI, */ + res = &cdnsp->xhci_res[1]; + res->start = pdev->irq; + res->name = "host"; + res->flags = IORESOURCE_IRQ; } else { - res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG); - res[RES_DRD_ID].end = pci_resource_end(pdev, PCI_BAR_OTG); - res[RES_DRD_ID].name = "otg"; - res[RES_DRD_ID].flags = IORESOURCE_MEM; - dev_dbg(&pdev->dev, "CDNSP-DRD physical base addr: %pa\n", - &res[RES_DRD_ID].start); + res = &cdnsp->otg_res; + res->start = pci_resource_start(pdev, PCI_BAR_OTG); + res->end = pci_resource_end(pdev, PCI_BAR_OTG); + res->name = "otg"; + res->flags = IORESOURCE_MEM; + dev_dbg(dev, "CDNSP-DRD physical base addr: %pa\n", + &res->start); /* Interrupt for OTG/DRD. */ - wrap->dev_res[RES_IRQ_OTG_ID].start = pdev->irq; - wrap->dev_res[RES_IRQ_OTG_ID].name = "otg"; - wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ; + cdnsp->otg_irq = pdev->irq; } + /* + * Cadence PCI based platform require some longer timeout for APB + * to fixes domain clock synchronization issue after resuming + * controller from L1 state. + */ + cdnsp->override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE; + pci_set_drvdata(pdev, cdnsp); + if (pci_is_enabled(func)) { - /* set up platform device info */ - pdata.override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE; - memset(&plat_info, 0, sizeof(plat_info)); - plat_info.parent = &pdev->dev; - plat_info.fwnode = pdev->dev.fwnode; - plat_info.name = PLAT_DRIVER_NAME; - plat_info.id = pdev->devfn; - plat_info.res = wrap->dev_res; - plat_info.num_res = ARRAY_SIZE(wrap->dev_res); - plat_info.dma_mask = pdev->dma_mask; - plat_info.data = &pdata; - plat_info.size_data = sizeof(pdata); - wrap->devfn = pdev->devfn; - /* register platform device */ - wrap->plat_dev = platform_device_register_full(&plat_info); - if (IS_ERR(wrap->plat_dev)) { - ret = PTR_ERR(wrap->plat_dev); - kfree(wrap); - goto put_pci; - } + cdnsp->dev = dev; + cdnsp->gadget_init = cdnsp_gadget_init; + + ret = cdns_init(cdnsp); + if (ret) + goto free_cdnsp; } - pci_set_drvdata(pdev, wrap); + device_wakeup_enable(&pdev->dev); + if (pci_dev_run_wake(pdev)) + pm_runtime_put_noidle(&pdev->dev); + + return 0; + +free_cdnsp: + if (!pci_is_enabled(func)) + kfree(cdnsp); + put_pci: pci_dev_put(func); + return ret; } static void cdnsp_pci_remove(struct pci_dev *pdev) { - struct cdnsp_wrap *wrap; + struct cdns *cdnsp; struct pci_dev *func; func = cdnsp_get_second_fun(pdev); - wrap = pci_get_drvdata(pdev); + cdnsp = (struct cdns *)pci_get_drvdata(pdev); - if (wrap->devfn == pdev->devfn) - platform_device_unregister(wrap->plat_dev); + if (pci_dev_run_wake(pdev)) + pm_runtime_get_noresume(&pdev->dev); - if (!pci_is_enabled(func)) - kfree(wrap); + if (pci_is_enabled(func)) { + cdns_remove(cdnsp); + } else { + kfree(cdnsp); + } pci_dev_put(func); } +static int __maybe_unused cdnsp_pci_suspend(struct device *dev) +{ + struct cdns *cdns = dev_get_drvdata(dev); + + return cdns_suspend(cdns); +} + +static int __maybe_unused cdnsp_pci_resume(struct device *dev) +{ + struct cdns *cdns = dev_get_drvdata(dev); + unsigned long flags; + int ret; + + spin_lock_irqsave(&cdns->lock, flags); + ret = cdns_resume(cdns); + spin_unlock_irqrestore(&cdns->lock, flags); + cdns_set_active(cdns, 1); + + return ret; +} + +static const struct dev_pm_ops cdnsp_pci_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cdnsp_pci_suspend, cdnsp_pci_resume) +}; + static const struct pci_device_id cdnsp_pci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP), .class = PCI_CLASS_SERIAL_USB_DEVICE }, @@ -202,10 +230,13 @@ static const struct pci_device_id cdnsp_pci_ids[] = { }; static struct pci_driver cdnsp_pci_driver = { - .name = PCI_DRIVER_NAME, + .name = "cdnsp-pci", .id_table = cdnsp_pci_ids, .probe = cdnsp_pci_probe, .remove = cdnsp_pci_remove, + .driver = { + .pm = &cdnsp_pci_pm_ops, + } }; module_pci_driver(cdnsp_pci_driver); @@ -214,4 +245,4 @@ MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids); MODULE_ALIAS("pci:cdnsp"); MODULE_AUTHOR("Pawel Laszczak "); MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Cadence CDNSP PCI wrapper"); +MODULE_DESCRIPTION("Cadence CDNSP PCI driver"); diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 10f00b6c3c83..f0e32227c0b7 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -80,7 +80,7 @@ static void cdns_exit_roles(struct cdns *cdns) * * Returns 0 on success otherwise negative errno */ -int cdns_core_init_role(struct cdns *cdns) +static int cdns_core_init_role(struct cdns *cdns) { struct device *dev = cdns->dev; enum usb_dr_mode best_dr_mode; @@ -197,14 +197,11 @@ int cdns_core_init_role(struct cdns *cdns) goto err; } - dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); - return 0; err: cdns_exit_roles(cdns); return ret; } -EXPORT_SYMBOL_GPL(cdns_core_init_role); /** * cdns_hw_role_state_machine - role switch state machine based on hw events. @@ -472,8 +469,14 @@ int cdns_init(struct cdns *cdns) if (ret) goto init_failed; + ret = cdns_core_init_role(cdns); + if (ret) + goto init_failed; + spin_lock_init(&cdns->lock); + dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); + return 0; init_failed: cdns_drd_exit(cdns); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index dc8c4137de15..801be9e61340 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -45,7 +45,6 @@ struct cdns3_platform_data { unsigned long quirks; #define CDNS3_DEFAULT_PM_RUNTIME_ALLOW BIT(0) #define CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE BIT(1) - u32 override_apb_timeout; /* 0 = use default (e.g. for PCI) */ }; /** @@ -120,14 +119,14 @@ struct cdns { struct cdns3_platform_data *pdata; spinlock_t lock; struct xhci_plat_priv *xhci_plat_data; - int (*gadget_init)(struct cdns *cdns); u32 override_apb_timeout; + + int (*gadget_init)(struct cdns *cdns); }; int cdns_hw_role_switch(struct cdns *cdns); int cdns_init(struct cdns *cdns); int cdns_remove(struct cdns *cdns); -int cdns_core_init_role(struct cdns *cdns); #ifdef CONFIG_PM_SLEEP int cdns_resume(struct cdns *cdns); diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h index 0cb600e2b5d2..c37b6269b001 100644 --- a/drivers/usb/cdns3/gadget-export.h +++ b/drivers/usb/cdns3/gadget-export.h @@ -10,7 +10,7 @@ #ifndef __LINUX_CDNS3_GADGET_EXPORT #define __LINUX_CDNS3_GADGET_EXPORT -#if defined(CONFIG_USB_CDNSP_GADGET) && IS_REACHABLE(CONFIG_USB_CDNSP) +#if IS_ENABLED(CONFIG_USB_CDNSP_GADGET) int cdnsp_gadget_init(struct cdns *cdns); #else @@ -22,7 +22,7 @@ static inline int cdnsp_gadget_init(struct cdns *cdns) #endif /* CONFIG_USB_CDNSP_GADGET */ -#if defined(CONFIG_USB_CDNS3_GADGET) && IS_REACHABLE(CONFIG_USB_CDNS3) +#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET) int cdns3_gadget_init(struct cdns *cdns); #else From 1df7a7652f032cf1abe1c102a031c2128e24c31d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 3 Apr 2026 14:06:20 +0200 Subject: [PATCH 149/176] Revert "dt-bindings: usb: cdns,usb3: document USBSSP controller support" This reverts commit fb14e7f7cbb4abbcde5576282d91352deaff2887. There were some build issues as reported by Arnd, so revert this for now. Cc: Peter Chen Cc: Pawel Laszczak Reported-by: Arnd Bergmann Link: https://lore.kernel.org/r/ac+LEWMCQpLSnfoD@nchen-desktop Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/cdns,usb3.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml index 2d95fb7321af..a199e5ba6416 100644 --- a/Documentation/devicetree/bindings/usb/cdns,usb3.yaml +++ b/Documentation/devicetree/bindings/usb/cdns,usb3.yaml @@ -4,17 +4,11 @@ $id: http://devicetree.org/schemas/usb/cdns,usb3.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Cadence USBSS and USBSSP DRD controller +title: Cadence USBSS-DRD controller maintainers: - Pawel Laszczak -description: - Cadence USB dual-role controller. Covers USBSS (SuperSpeed, USB 3.0) and - USBSSP (SuperSpeed Plus, USB 3.1 gen2x1). Both variants share the same - DRD/OTG register interface, so the driver auto-detects the controller - version at runtime. - properties: compatible: const: cdns,usb3 @@ -55,7 +49,7 @@ properties: cdns3 to type C connector. maximum-speed: - enum: [super-speed-plus, super-speed, high-speed, full-speed] + enum: [super-speed, high-speed, full-speed] phys: minItems: 1 From 498c05821bb42f70e9bf6512c3dec4aa821815d0 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 1 Apr 2026 14:47:26 -0700 Subject: [PATCH 150/176] thunderbolt: tunnel: Simplify allocation Use a flexible array member and kzalloc_flex to combine allocations. Add __counted_by for extra runtime analysis. Move counting variable assignment after allocation. kzalloc_flex with GCC >= 15 does this automatically. Signed-off-by: Rosen Penev Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 10 ++-------- drivers/thunderbolt/tunnel.h | 5 +++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 89676acf1290..f38f7753b6e4 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -180,19 +180,14 @@ static struct tb_tunnel *tb_tunnel_alloc(struct tb *tb, size_t npaths, { struct tb_tunnel *tunnel; - tunnel = kzalloc_obj(*tunnel); + tunnel = kzalloc_flex(*tunnel, paths, npaths); if (!tunnel) return NULL; - tunnel->paths = kzalloc_objs(tunnel->paths[0], npaths); - if (!tunnel->paths) { - kfree(tunnel); - return NULL; - } + tunnel->npaths = npaths; INIT_LIST_HEAD(&tunnel->list); tunnel->tb = tb; - tunnel->npaths = npaths; tunnel->type = type; kref_init(&tunnel->kref); @@ -219,7 +214,6 @@ static void tb_tunnel_destroy(struct kref *kref) tb_path_free(tunnel->paths[i]); } - kfree(tunnel->paths); kfree(tunnel); } diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 2c44fc8a10bc..4878763a82b3 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -37,7 +37,6 @@ enum tb_tunnel_state { * @src_port: Source port of the tunnel * @dst_port: Destination port of the tunnel. For discovered incomplete * tunnels may be %NULL or null adapter port instead. - * @paths: All paths required by the tunnel * @npaths: Number of paths in @paths * @pre_activate: Optional tunnel specific initialization called before * activation. Can touch hardware. @@ -69,13 +68,13 @@ enum tb_tunnel_state { * @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read * @callback: Optional callback called when DP tunnel is fully activated * @callback_data: Optional data for @callback + * @paths: All paths required by the tunnel */ struct tb_tunnel { struct kref kref; struct tb *tb; struct tb_port *src_port; struct tb_port *dst_port; - struct tb_path **paths; size_t npaths; int (*pre_activate)(struct tb_tunnel *tunnel); int (*activate)(struct tb_tunnel *tunnel, bool activate); @@ -107,6 +106,8 @@ struct tb_tunnel { struct delayed_work dprx_work; void (*callback)(struct tb_tunnel *tunnel, void *data); void *callback_data; + + struct tb_path *paths[] __counted_by(npaths); }; struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, From f8cc59ecc22841be5deb07b549c0c6a2657cd5f9 Mon Sep 17 00:00:00 2001 From: Fabio Porcedda Date: Thu, 2 Apr 2026 11:57:27 +0200 Subject: [PATCH 151/176] USB: serial: option: add Telit Cinterion FN990A MBIM composition Add the following Telit Cinterion FN990A MBIM composition: 0x1074: MBIM + tty (AT/NMEA) + tty (AT) + tty (AT) + tty (diag) + DPL (Data Packet Logging) + adb T: Bus=01 Lev=01 Prnt=04 Port=06 Cnt=01 Dev#= 7 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=1bc7 ProdID=1074 Rev=05.04 S: Manufacturer=Telit Wireless Solutions S: Product=FN990 S: SerialNumber=70628d0c C: #Ifs= 8 Cfg#= 1 Atr=e0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 1 Cls=02(commc) Sub=0e Prot=00 Driver=cdc_mbim E: Ad=81(I) Atr=03(Int.) MxPS= 64 Ivl=32ms I: If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=02 Driver=cdc_mbim E: Ad=0f(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=8e(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=83(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=85(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=87(I) Atr=03(Int.) MxPS= 10 Ivl=32ms I: If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=88(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 6 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=80 Driver=(none) E: Ad=8f(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms I: If#= 7 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) E: Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=89(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms Cc: stable@vger.kernel.org Signed-off-by: Fabio Porcedda Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index e349ed66d2ac..0cd65b782488 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -1383,6 +1383,8 @@ static const struct usb_device_id option_ids[] = { .driver_info = NCTRL(2) | RSVD(3) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1073, 0xff), /* Telit FN990A (ECM) */ .driver_info = NCTRL(0) | RSVD(1) }, + { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1074, 0xff), /* Telit FN990A (MBIM) */ + .driver_info = NCTRL(5) | RSVD(6) | RSVD(7) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1075, 0xff), /* Telit FN990A (PCIe) */ .driver_info = RSVD(0) }, { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1077, 0xff), /* Telit FN990A (rmnet + audio) */ From abad793a41edbf05b87935f6b83017512ed0003a Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 2 Apr 2026 15:14:55 +0800 Subject: [PATCH 152/176] usb: chipidea: core: refactor ci_usb_role_switch_set() Current code is redundant, refactor the code, no function change. Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260402071457.2516021-1-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index fac11f20cf0a..87be716dff3e 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -618,28 +618,13 @@ static int ci_usb_role_switch_set(struct usb_role_switch *sw, struct ci_hdrc *ci = usb_role_switch_get_drvdata(sw); struct ci_hdrc_cable *cable; - if (role == USB_ROLE_HOST) { - cable = &ci->platdata->id_extcon; - cable->changed = true; - cable->connected = true; - cable = &ci->platdata->vbus_extcon; - cable->changed = true; - cable->connected = false; - } else if (role == USB_ROLE_DEVICE) { - cable = &ci->platdata->id_extcon; - cable->changed = true; - cable->connected = false; - cable = &ci->platdata->vbus_extcon; - cable->changed = true; - cable->connected = true; - } else { - cable = &ci->platdata->id_extcon; - cable->changed = true; - cable->connected = false; - cable = &ci->platdata->vbus_extcon; - cable->changed = true; - cable->connected = false; - } + cable = &ci->platdata->id_extcon; + cable->changed = true; + cable->connected = (role == USB_ROLE_HOST); + + cable = &ci->platdata->vbus_extcon; + cable->changed = true; + cable->connected = (role == USB_ROLE_DEVICE); ci_irq(ci); return 0; From b94b631d9f78e653855f7fb58dbcb86c2a856f6f Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 2 Apr 2026 15:14:56 +0800 Subject: [PATCH 153/176] usb: chipidea: core: allow ci_irq_handler() handle both ID and VBUS change For USB role switch-triggered IRQ, ID and VBUS change come together, for example when switching from host to device mode. ID indicate a role switch and VBUS is required to determine whether the device controller can start operating. Currently, ci_irq_handler() handles only a single event per invocation. This can cause an issue where switching to device mode results in the device controller not working at all. Allowing ci_irq_handler() to handle both ID and VBUS change in one call resolves this issue. Meanwhile, this change also affects the VBUS event handling logic. Previously, if an ID event indicated host mode the VBUS IRQ will be ignored as the device disable BSE when stop() is called. With the new behavior, if ID and VBUS IRQ occur together and the target mode is host, the VBUS event is queued and ci_handle_vbus_change() will call usb_gadget_vbus_connect(), after which USBMODE is switched to device mode, causing host mode to stop working. To prevent this, an additional check is added to skip handling VBUS event when current role is not device mode. Suggested-by: Peter Chen Fixes: e1b5d2bed67c ("usb: chipidea: core: handle usb role switch in a common way") Cc: stable@vger.kernel.org Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260402071457.2516021-2-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 45 +++++++++++++++++++------------------ drivers/usb/chipidea/otg.c | 3 +++ 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 87be716dff3e..7cfabb04a4fb 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -544,30 +544,31 @@ static irqreturn_t ci_irq_handler(int irq, void *data) if (ret == IRQ_HANDLED) return ret; } - } - /* - * Handle id change interrupt, it indicates device/host function - * switch. - */ - if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) { - ci->id_event = true; - /* Clear ID change irq status */ - hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); - ci_otg_queue_work(ci); - return IRQ_HANDLED; - } + /* + * Handle id change interrupt, it indicates device/host function + * switch. + */ + if ((otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) { + ci->id_event = true; + /* Clear ID change irq status */ + hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); + } - /* - * Handle vbus change interrupt, it indicates device connection - * and disconnection events. - */ - if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { - ci->b_sess_valid_event = true; - /* Clear BSV irq */ - hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); - ci_otg_queue_work(ci); - return IRQ_HANDLED; + /* + * Handle vbus change interrupt, it indicates device connection + * and disconnection events. + */ + if ((otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { + ci->b_sess_valid_event = true; + /* Clear BSV irq */ + hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); + } + + if (ci->id_event || ci->b_sess_valid_event) { + ci_otg_queue_work(ci); + return IRQ_HANDLED; + } } /* Handle device/host interrupt */ diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index 647e98f4e351..def933b73a90 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -130,6 +130,9 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci) void ci_handle_vbus_change(struct ci_hdrc *ci) { + if (ci->role != CI_ROLE_GADGET) + return; + if (!ci->is_otg) { if (ci->platdata->flags & CI_HDRC_FORCE_VBUS_ACTIVE_ALWAYS) usb_gadget_vbus_connect(&ci->gadget); From a4e99587102a83ee911c670752fbca694c7e557f Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 2 Apr 2026 15:14:57 +0800 Subject: [PATCH 154/176] usb: chipidea: otg: not wait vbus drop if use role_switch The usb role switch will update ID and VBUS states at the same time, and vbus will not drop when execute data role swap in Type-C usecase. So lets not wait vbus drop in usb role switch case too. Fixes: e1b5d2bed67c ("usb: chipidea: core: handle usb role switch in a common way") Cc: stable@vger.kernel.org Acked-by: Peter Chen Reviewed-by: Jun Li Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260402071457.2516021-3-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/otg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index def933b73a90..fecc7d7e2f0d 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -190,8 +190,8 @@ void ci_handle_id_switch(struct ci_hdrc *ci) ci_role_stop(ci); - if (role == CI_ROLE_GADGET && - IS_ERR(ci->platdata->vbus_extcon.edev)) + if (role == CI_ROLE_GADGET && !ci->role_switch && + IS_ERR(ci->platdata->vbus_extcon.edev)) /* * Wait vbus lower than OTGSC_BSV before connecting * to host. If connecting status is from an external From 609865ab3d5d803556f628e221ecd3d06aed9f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Br=C3=A1t?= Date: Thu, 2 Apr 2026 19:24:33 +0200 Subject: [PATCH 155/176] usb: storage: Expand range of matched versions for VL817 quirks entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expands range of matched bcdDevice values for the VL817 quirk entry. This is based on experience with Axagon EE35-GTR rev1 3.5" HDD enclosure, which reports its bcdDevice as 0x0843, but presumably other vendors using this IC in their products may set it to any other value. Signed-off-by: Daniel Brát Cc: stable Link: https://patch.msgid.link/20260402172433.5227-1-danek.brat@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/unusual_devs.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 47f50d7a385c..255968f9ca42 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -2350,10 +2350,11 @@ UNUSUAL_DEV( 0x2027, 0xa001, 0x0000, 0x9999, US_FL_SCM_MULT_TARG ), /* - * Reported by DocMAX - * and Thomas Weißschuh + * Reported by DocMAX , + * Thomas Weißschuh + * and Daniel Brát */ -UNUSUAL_DEV( 0x2109, 0x0715, 0x9999, 0x9999, +UNUSUAL_DEV( 0x2109, 0x0715, 0x0000, 0x9999, "VIA Labs, Inc.", "VL817 SATA Bridge", USB_SC_DEVICE, USB_PR_DEVICE, NULL, From f58752ebcb35e156c85cd1a82d6579c7af3b9023 Mon Sep 17 00:00:00 2001 From: Dave Carey Date: Thu, 2 Apr 2026 14:29:50 -0400 Subject: [PATCH 156/176] USB: cdc-acm: Add quirks for Yoga Book 9 14IAH10 INGENIC touchscreen The Lenovo Yoga Book 9 14IAH10 (83KJ) has a composite USB device (17EF:6161) that controls both touchscreens via a CDC ACM interface. Interface 0 is a standard CDC ACM control interface, but interface 1 (the data interface) incorrectly declares vendor-specific class (0xFF) instead of USB_CLASS_CDC_DATA. cdc-acm rejects the device at probe with -EINVAL, leaving interface 0 unbound and EP 0x82 never polled. With no consumer polling EP 0x82, the firmware's watchdog fires every ~20 seconds and resets the USB bus, producing a continuous disconnect/ reconnect loop that prevents the touchscreens from ever initialising. Add two new quirk flags: VENDOR_CLASS_DATA_IFACE: Bypasses the bInterfaceClass check in acm_probe() that would otherwise reject the vendor-class data interface with -EINVAL. ALWAYS_POLL_CTRL: Submits the notification URB at probe() rather than waiting for a TTY open. This keeps EP 0x82 polled at all times, permanently suppressing the firmware watchdog. The URB is resubmitted after port_shutdown() and on system resume. SET_CONTROL_LINE_STATE (DTR|RTS) is sent at probe and after port_shutdown() to complete firmware handshake. Note: the firmware performs exactly 4 USB connect/disconnect cycles (~19 s each) on every cold boot before stabilising. This is a fixed firmware property; touch is available ~75-80 s after power-on. Signed-off-by: Dave Carey Cc: stable Tested-by: Dave Carey Acked-by: Oliver Neukum Link: https://patch.msgid.link/20260402182950.389016-1-carvsdriver@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 53 ++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index cf3c3eede1a5..54059e4fc6ed 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -114,6 +114,8 @@ static int acm_ctrl_msg(struct acm *acm, int request, int value, int retval; retval = usb_autopm_get_interface(acm->control); +#define VENDOR_CLASS_DATA_IFACE BIT(9) /* data interface uses vendor-specific class */ +#define ALWAYS_POLL_CTRL BIT(10) /* keep ctrl URB active even without an open TTY */ if (retval) return retval; @@ -710,12 +712,14 @@ static int acm_port_activate(struct tty_port *port, struct tty_struct *tty) set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); acm->control->needs_remote_wakeup = 1; - acm->ctrlurb->dev = acm->dev; - retval = usb_submit_urb(acm->ctrlurb, GFP_KERNEL); - if (retval) { - dev_err(&acm->control->dev, - "%s - usb_submit_urb(ctrl irq) failed\n", __func__); - goto error_submit_urb; + if (!(acm->quirks & ALWAYS_POLL_CTRL)) { + acm->ctrlurb->dev = acm->dev; + retval = usb_submit_urb(acm->ctrlurb, GFP_KERNEL); + if (retval) { + dev_err(&acm->control->dev, + "%s - usb_submit_urb(ctrl irq) failed\n", __func__); + goto error_submit_urb; + } } acm_tty_set_termios(tty, NULL); @@ -788,6 +792,14 @@ static void acm_port_shutdown(struct tty_port *port) acm_unpoison_urbs(acm); + if (acm->quirks & ALWAYS_POLL_CTRL) { + acm->ctrlurb->dev = acm->dev; + if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) + dev_dbg(&acm->control->dev, + "ctrl polling restart failed after port close\n"); + /* port_shutdown() cleared DTR/RTS; restore them */ + acm_set_control(acm, USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS); + } } static void acm_tty_cleanup(struct tty_struct *tty) @@ -1328,6 +1340,9 @@ static int acm_probe(struct usb_interface *intf, dev_dbg(&intf->dev, "Your device has switched interfaces.\n"); swap(control_interface, data_interface); + } else if (quirks & VENDOR_CLASS_DATA_IFACE) { + dev_dbg(&intf->dev, + "Vendor-specific data interface class, continuing.\n"); } else { return -EINVAL; } @@ -1522,6 +1537,9 @@ static int acm_probe(struct usb_interface *intf, acm->line.bDataBits = 8; acm_set_line(acm, &acm->line); + if (quirks & ALWAYS_POLL_CTRL) + acm_set_control(acm, USB_CDC_CTRL_DTR | USB_CDC_CTRL_RTS); + if (!acm->combined_interfaces) { rv = usb_driver_claim_interface(&acm_driver, data_interface, acm); if (rv) @@ -1543,6 +1561,13 @@ static int acm_probe(struct usb_interface *intf, dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); + if (acm->quirks & ALWAYS_POLL_CTRL) { + acm->ctrlurb->dev = acm->dev; + if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) + dev_warn(&intf->dev, + "failed to start persistent ctrl polling\n"); + } + return 0; err_release_data_interface: @@ -1669,7 +1694,7 @@ static int acm_resume(struct usb_interface *intf) acm_unpoison_urbs(acm); - if (tty_port_initialized(&acm->port)) { + if (tty_port_initialized(&acm->port) || (acm->quirks & ALWAYS_POLL_CTRL)) { rv = usb_submit_urb(acm->ctrlurb, GFP_ATOMIC); for (;;) { @@ -2016,6 +2041,20 @@ static const struct usb_device_id acm_ids[] = { /* CH343 supports CAP_BRK, but doesn't advertise it */ { USB_DEVICE(0x1a86, 0x55d3), .driver_info = MISSING_CAP_BRK, }, + /* + * Lenovo Yoga Book 9 14IAH10 (83KJ) — INGENIC 17EF:6161 touchscreen + * composite device. The CDC ACM control interface (0) uses a standard + * Union descriptor, but the data interface (1) is declared as vendor- + * specific class (0xff) with no CDC data descriptors, so cdc-acm would + * normally reject it. The firmware also requires continuous polling of + * the notification endpoint (EP 0x82) to suppress a 20-second watchdog + * reset; ALWAYS_POLL_CTRL keeps the ctrlurb active even when no TTY is + * open. Match only the control interface by class to avoid probing the + * vendor-specific data interface. + */ + { USB_DEVICE_INTERFACE_CLASS(0x17ef, 0x6161, USB_CLASS_COMM), + .driver_info = VENDOR_CLASS_DATA_IFACE | ALWAYS_POLL_CTRL }, + /* control interfaces without any protocol set */ { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_PROTO_NONE) }, From 1ace770f5de3e28a4d61c2b5252cb823dcdb6049 Mon Sep 17 00:00:00 2001 From: Jameson Thies Date: Thu, 2 Apr 2026 18:24:38 +0000 Subject: [PATCH 157/176] usb: typec: ucsi: Set usb mode on partner change Currently the partner usb_mode is only set in ucsi_register_partner(). If the partner enters USB4 operation after it is registered, this is not reported to the typec class. The UCSI spec states that the Connector Partner Changed bit can represent a Connector Partner Flags change. When handling a UCSI partner change, check the partner flags for USB4 operation. Signed-off-by: Jameson Thies Reviewed-by: Benson Leung Link: https://patch.msgid.link/20260402182438.867396-1-jthies@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index f6bb88b1ccee..301c36529566 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1187,6 +1187,12 @@ static void ucsi_partner_change(struct ucsi_connector *con) if (UCSI_CONSTAT(con, PARTNER_FLAG_USB)) typec_set_mode(con->port, TYPEC_STATE_USB); } + + if (((con->ucsi->version >= UCSI_VERSION_3_0 && + UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN4)) || + (con->ucsi->version >= UCSI_VERSION_2_0 && + UCSI_CONSTAT(con, PARTNER_FLAG_USB4_GEN3))) && con->partner) + typec_partner_set_usb_mode(con->partner, USB_MODE_USB4); } if ((!UCSI_CONSTAT(con, PARTNER_FLAG_USB)) && From 274875f72f6c188cdc9d4424e02341472bc2b3c1 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 6 Apr 2026 16:18:31 +0200 Subject: [PATCH 158/176] usb: core: config: reverse the size check of the SSP isoc endpoint descriptor Reverse the check of the size of the usb_ssp_isoc_ep_comp_descriptor structure to be done before accessing the structure itself. Functionally, this doesn't really do anything as the buffer is all internal to the kernel, and reading off the end is just fine, but static checking tools get picky when noticing that a potential read could be made "outside" of an allocated buffer. Not a bugfix, but a cleanup to keep tools from tripping over this constantly and annoying me with their pointless reports. Link: https://patch.msgid.link/2026040630-graded-postwar-760f@gregkh Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c index 6a1fd967e0a6..417140b012bb 100644 --- a/drivers/usb/core/config.c +++ b/drivers/usb/core/config.c @@ -54,8 +54,8 @@ static void usb_parse_ssp_isoc_endpoint_companion(struct device *ddev, * follows the SuperSpeed Endpoint Companion descriptor */ desc = (struct usb_ssp_isoc_ep_comp_descriptor *) buffer; - if (desc->bDescriptorType != USB_DT_SSP_ISOC_ENDPOINT_COMP || - size < USB_DT_SSP_ISOC_EP_COMP_SIZE) { + if (size < USB_DT_SSP_ISOC_EP_COMP_SIZE || + desc->bDescriptorType != USB_DT_SSP_ISOC_ENDPOINT_COMP) { dev_notice(ddev, "Invalid SuperSpeedPlus isoc endpoint companion" "for config %d interface %d altsetting %d ep %d.\n", cfgno, inum, asnum, ep->desc.bEndpointAddress); From f880aac8a57ebd92abfa685d45424b2998ac1059 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 6 Apr 2026 17:09:48 +0200 Subject: [PATCH 159/176] usb: gadget: renesas_usb3: validate endpoint index in standard request handlers The GET_STATUS and SET/CLEAR_FEATURE handlers extract the endpoint number from the host-supplied wIndex without any sort of validation. Fix this up by validating the number of endpoints actually match up with the number the device has before attempting to dereference a pointer based on this math. This is just like what was done in commit ee0d382feb44 ("usb: gadget: aspeed_udc: validate endpoint index for ast udc") for the aspeed driver. Fixes: 746bfe63bba3 ("usb: gadget: renesas_usb3: add support for Renesas USB3.0 peripheral controller") Cc: stable Assisted-by: gregkh_clanker_t1000 Link: https://patch.msgid.link/2026040647-sincerity-untidy-b104@gregkh Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/renesas_usb3.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index b0b264d34919..2c9c3e935a5e 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -1669,6 +1669,10 @@ static bool usb3_std_req_get_status(struct renesas_usb3 *usb3, break; case USB_RECIP_ENDPOINT: num = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + if (num >= usb3->num_usb3_eps) { + stall = true; + break; + } usb3_ep = usb3_get_ep(usb3, num); if (usb3_ep->halt) status |= 1 << USB_ENDPOINT_HALT; @@ -1781,7 +1785,8 @@ static bool usb3_std_req_feature_endpoint(struct renesas_usb3 *usb3, struct renesas_usb3_ep *usb3_ep; struct renesas_usb3_request *usb3_req; - if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT) + if ((le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT) || + (num >= usb3->num_usb3_eps)) return true; /* stall */ usb3_ep = usb3_get_ep(usb3, num); From 2ab833a16a825373aad2ba7d54b572b277e95b71 Mon Sep 17 00:00:00 2001 From: Nathan Rebello Date: Thu, 2 Apr 2026 04:52:59 -0400 Subject: [PATCH 160/176] usbip: validate number_of_packets in usbip_pack_ret_submit() When a USB/IP client receives a RET_SUBMIT response, usbip_pack_ret_submit() unconditionally overwrites urb->number_of_packets from the network PDU. This value is subsequently used as the loop bound in usbip_recv_iso() and usbip_pad_iso() to iterate over urb->iso_frame_desc[], a flexible array whose size was fixed at URB allocation time based on the *original* number_of_packets from the CMD_SUBMIT. A malicious USB/IP server can set number_of_packets in the response to a value larger than what was originally submitted, causing a heap out-of-bounds write when usbip_recv_iso() writes to urb->iso_frame_desc[i] beyond the allocated region. KASAN confirmed this with kernel 7.0.0-rc5: BUG: KASAN: slab-out-of-bounds in usbip_recv_iso+0x46a/0x640 Write of size 4 at addr ffff888106351d40 by task vhci_rx/69 The buggy address is located 0 bytes to the right of allocated 320-byte region [ffff888106351c00, ffff888106351d40) The server side (stub_rx.c) and gadget side (vudc_rx.c) already validate number_of_packets in the CMD_SUBMIT path since commits c6688ef9f297 ("usbip: fix stub_rx: harden CMD_SUBMIT path to handle malicious input") and b78d830f0049 ("usbip: fix vudc_rx: harden CMD_SUBMIT path to handle malicious input"). The server side validates against USBIP_MAX_ISO_PACKETS because no URB exists yet at that point. On the client side we have the original URB, so we can use the tighter bound: the response must not exceed the original number_of_packets. This mirrors the existing validation of actual_length against transfer_buffer_length in usbip_recv_xbuff(), which checks the response value against the original allocation size. Kelvin Mbogo's series ("usb: usbip: fix integer overflow in usbip_recv_iso()", v2) hardens the receive-side functions themselves; this patch complements that work by catching the bad value at its source -- in usbip_pack_ret_submit() before the overwrite -- and using the tighter per-URB allocation bound rather than the global USBIP_MAX_ISO_PACKETS limit. Fix this by checking rpdu->number_of_packets against urb->number_of_packets in usbip_pack_ret_submit() before the overwrite. On violation, clamp to zero so that usbip_recv_iso() and usbip_pad_iso() safely return early. Fixes: 1325f85fa49f ("staging: usbip: bugfix add number of packets for isochronous frames") Cc: stable Acked-by: Shuah Khan Signed-off-by: Nathan Rebello Link: https://patch.msgid.link/20260402085259.234-1-nathan.c.rebello@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/usbip_common.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c index 8ebaaeaf848e..a5837c0feb05 100644 --- a/drivers/usb/usbip/usbip_common.c +++ b/drivers/usb/usbip/usbip_common.c @@ -470,6 +470,18 @@ static void usbip_pack_ret_submit(struct usbip_header *pdu, struct urb *urb, urb->status = rpdu->status; urb->actual_length = rpdu->actual_length; urb->start_frame = rpdu->start_frame; + /* + * The number_of_packets field determines the length of + * iso_frame_desc[], which is a flexible array allocated + * at URB creation time. A response must never claim more + * packets than originally submitted; doing so would cause + * an out-of-bounds write in usbip_recv_iso() and + * usbip_pad_iso(). Clamp to zero on violation so both + * functions safely return early. + */ + if (rpdu->number_of_packets < 0 || + rpdu->number_of_packets > urb->number_of_packets) + rpdu->number_of_packets = 0; urb->number_of_packets = rpdu->number_of_packets; urb->error_count = rpdu->error_count; } From 9a8881aab5d3e69dd72d1b18bbde39be6f4664bf Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Tue, 7 Apr 2026 09:31:22 +0800 Subject: [PATCH 161/176] USB: of: Simplify with scoped for each OF child loop Use scoped for-each loop when iterating over device nodes to make code a bit simpler. Signed-off-by: Hans Zhang <18255117159@163.com> Link: https://patch.msgid.link/20260407013122.1296818-1-18255117159@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/of.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/usb/core/of.c b/drivers/usb/core/of.c index 763e4122ed5b..a6ee20d8774e 100644 --- a/drivers/usb/core/of.c +++ b/drivers/usb/core/of.c @@ -79,17 +79,13 @@ EXPORT_SYMBOL_GPL(usb_of_has_combined_node); static bool usb_of_has_devices_or_graph(const struct usb_device *hub) { const struct device_node *np = hub->dev.of_node; - struct device_node *child; if (of_graph_is_present(np)) return true; - for_each_child_of_node(np, child) { - if (of_property_present(child, "reg")) { - of_node_put(child); + for_each_child_of_node_scoped(np, child) + if (of_property_present(child, "reg")) return true; - } - } return false; } From 250892b5d64d6a54faf4a0829ff3a99af27a4bc1 Mon Sep 17 00:00:00 2001 From: Jameson Thies Date: Fri, 3 Apr 2026 22:33:26 +0000 Subject: [PATCH 162/176] dt-bindings: chrome: Add cros-ec-ucsi compatibility to typec binding Chrome OS devices with discrete power delivery controllers (PDCs) allow the host to read port status and control port behavior through a USB Type-C Connector System Software (UCSI) interface with the embedded controller (EC). This uses a separate interface driver than other Chrome OS devices with a Type-C port manager in the EC FW. Those use a host command interface supported by cros-ec-typec. Add a cros-ec-ucsi compatibility string to the existing cros-ec-typec binding. Additionally, update maintainer list to reflect cros-ec-ucsi and cros-ec-typec driver maintainers. Signed-off-by: Jameson Thies Reviewed-by: Benson Leung Reviewed-by: Rob Herring (Arm) Reviewed-by: Abhishek Pandit-Subedi Link: https://patch.msgid.link/20260403223357.1896403-2-jthies@google.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/chrome/google,cros-ec-typec.yaml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/chrome/google,cros-ec-typec.yaml b/Documentation/devicetree/bindings/chrome/google,cros-ec-typec.yaml index 9f9816fbecbc..fd1a459879bd 100644 --- a/Documentation/devicetree/bindings/chrome/google,cros-ec-typec.yaml +++ b/Documentation/devicetree/bindings/chrome/google,cros-ec-typec.yaml @@ -8,17 +8,28 @@ title: Google Chrome OS EC(Embedded Controller) Type C port driver. maintainers: - Benson Leung - - Prashant Malani + - Abhishek Pandit-Subedi + - Andrei Kuchynski + - Łukasz Bartosik + - Jameson Thies description: Chrome OS devices have an Embedded Controller(EC) which has access to Type C port state. This node is intended to allow the host to read and - control the Type C ports. The node for this device should be under a - cros-ec node like google,cros-ec-spi. + control the Type C ports. This binding is compatible with both the + cros-ec-typec and cros-ec-ucsi drivers. The cros-ec-typec driver + supports the host command interface used by the Chrome OS EC with a + built-in Type-C port manager and external Type-C Port Controller + (TCPC). The cros-ec-ucsi driver supports the USB Type-C Connector + System Software (UCSI) interface used by the Chrome OS EC when the + platform has a separate power delivery controller (PDC). The node for + this device should be under a cros-ec node like google,cros-ec-spi. properties: compatible: - const: google,cros-ec-typec + enum: + - google,cros-ec-typec + - google,cros-ec-ucsi '#address-cells': const: 1 From 40b17a345d3fe88b98acfe2637452baa32785ee0 Mon Sep 17 00:00:00 2001 From: Jameson Thies Date: Fri, 3 Apr 2026 22:33:27 +0000 Subject: [PATCH 163/176] usb: typec: cros_ec_ucsi: Load driver from OF and ACPI definitions Add support for cros_ec_ucsi to load based on "google,cros-ec-ucsi" compatible devices and "GOOG0021" ACPI nodes. Signed-off-by: Jameson Thies Reviewed-by: Benson Leung Reviewed-by: Abhishek Pandit-Subedi Link: https://patch.msgid.link/20260403223357.1896403-3-jthies@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/cros_ec_ucsi.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/ucsi/cros_ec_ucsi.c b/drivers/usb/typec/ucsi/cros_ec_ucsi.c index 6bca2dce211c..251aa7251ce6 100644 --- a/drivers/usb/typec/ucsi/cros_ec_ucsi.c +++ b/drivers/usb/typec/ucsi/cros_ec_ucsi.c @@ -5,11 +5,13 @@ * Copyright 2024 Google LLC. */ +#include #include #include #include #include #include +#include #include #include #include @@ -257,7 +259,6 @@ static void cros_ucsi_destroy(struct cros_ucsi_data *udata) static int cros_ucsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct cros_ec_dev *ec_data = dev_get_drvdata(dev->parent); struct cros_ucsi_data *udata; int ret; @@ -265,9 +266,16 @@ static int cros_ucsi_probe(struct platform_device *pdev) if (!udata) return -ENOMEM; + /* ACPI and OF FW nodes for cros_ec_ucsi are children of the ChromeOS EC. If the + * cros_ec_ucsi device has an ACPI or OF FW node, its parent is the ChromeOS EC device. + * Platforms without a FW node for cros_ec_ucsi may add it as a subdevice of cros_ec_dev. + */ udata->dev = dev; + if (is_acpi_device_node(dev->fwnode) || is_of_node(dev->fwnode)) + udata->ec = dev_get_drvdata(dev->parent); + else + udata->ec = ((struct cros_ec_dev *)dev_get_drvdata(dev->parent))->ec_dev; - udata->ec = ec_data->ec_dev; if (!udata->ec) return dev_err_probe(dev, -ENODEV, "couldn't find parent EC device\n"); @@ -348,10 +356,24 @@ static const struct platform_device_id cros_ucsi_id[] = { }; MODULE_DEVICE_TABLE(platform, cros_ucsi_id); +static const struct acpi_device_id cros_ec_ucsi_acpi_device_ids[] = { + { "GOOG0021", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cros_ec_ucsi_acpi_device_ids); + +static const struct of_device_id cros_ucsi_of_match[] = { + { .compatible = "google,cros-ec-ucsi", }, + {} +}; +MODULE_DEVICE_TABLE(of, cros_ucsi_of_match); + static struct platform_driver cros_ucsi_driver = { .driver = { .name = KBUILD_MODNAME, .pm = &cros_ucsi_pm_ops, + .acpi_match_table = cros_ec_ucsi_acpi_device_ids, + .of_match_table = cros_ucsi_of_match, }, .id_table = cros_ucsi_id, .probe = cros_ucsi_probe, From 2c863dbbeac7b919d4634ad886978a6731916de3 Mon Sep 17 00:00:00 2001 From: Ethan Tidmore Date: Thu, 2 Apr 2026 13:00:08 -0500 Subject: [PATCH 164/176] usb: gadget: f_hid: Add missing error code Currently in cdev_alloc() error path no error code is assigned. Assign error code '-ENOMEM'. Detected by Smatch: drivers/usb/gadget/function/f_hid.c:1291 hidg_bind() warn: missing error code 'status' Fixes: 81ebd43cc0d6d ("usb: gadget: f_hid: don't call cdev_init while cdev in use") Signed-off-by: Ethan Tidmore Acked-by: Peter Korsgaard Reviewed-by: Michael Zimmermann Link: https://patch.msgid.link/20260402180008.64233-1-ethantidmore06@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_hid.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index e0c3f39ee95e..c5a12a6760ea 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -1278,8 +1278,10 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) /* create char device */ hidg->cdev = cdev_alloc(); - if (!hidg->cdev) + if (!hidg->cdev) { + status = -ENOMEM; goto fail_free_all; + } hidg->cdev->ops = &f_hidg_fops; status = cdev_device_add(hidg->cdev, &hidg->dev); From c088d5dd2fffb4de1fb8e7f57751c8b82942180a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 7 Apr 2026 10:55:05 +0200 Subject: [PATCH 165/176] usb: gadget: f_phonet: fix skb frags[] overflow in pn_rx_complete() A broken/bored/mean USB host can overflow the skb_shared_info->frags[] array on a Linux gadget exposing a Phonet function by sending an unbounded sequence of full-page OUT transfers. pn_rx_complete() finalizes the skb only when req->actual < req->length, where req->length is set to PAGE_SIZE by the gadget. If the host always sends exactly PAGE_SIZE bytes per transfer, fp->rx.skb will never be reset and each completion will add another fragment via skb_add_rx_frag(). Once nr_frags exceeds MAX_SKB_FRAGS (default 17), subsequent frag stores overwrite memory adjacent to the shinfo on the heap. Drop the skb and account a length error when the frag limit is reached, matching the fix applied in t7xx by commit f0813bcd2d9d ("net: wwan: t7xx: fix potential skb->frags overflow in RX path"). Cc: stable Assisted-by: gregkh_clanker_t1000 Link: https://patch.msgid.link/2026040705-fruit-unloved-0701@gregkh Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_phonet.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/usb/gadget/function/f_phonet.c b/drivers/usb/gadget/function/f_phonet.c index 2c007790ead2..b1ee9a7c2e94 100644 --- a/drivers/usb/gadget/function/f_phonet.c +++ b/drivers/usb/gadget/function/f_phonet.c @@ -333,6 +333,15 @@ static void pn_rx_complete(struct usb_ep *ep, struct usb_request *req) if (unlikely(!skb)) break; + if (unlikely(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS)) { + /* Frame count from host exceeds frags[] capacity */ + dev_kfree_skb_any(skb); + if (fp->rx.skb == skb) + fp->rx.skb = NULL; + dev->stats.rx_length_errors++; + break; + } + if (skb->len == 0) { /* First fragment */ skb->protocol = htons(ETH_P_PHONET); skb_reset_mac_header(skb); From 8f993d30b95dc9557a8a96ceca11abed674c8acb Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 7 Apr 2026 11:02:54 +0200 Subject: [PATCH 166/176] usb: gadget: f_ncm: validate minimum block_len in ncm_unwrap_ntb() The block_len read from the host-supplied NTB header is checked against ntb_max but has no lower bound. When block_len is smaller than opts->ndp_size, the bounds check of: ndp_index > (block_len - opts->ndp_size) will underflow producing a huge unsigned value that ndp_index can never exceed, defeating the check entirely. The same underflow occurs in the datagram index checks against block_len - opts->dpe_size. With those checks neutered, a malicious USB host can choose ndp_index and datagram offsets that point past the actual transfer, and the skb_put_data() copies adjacent kernel memory into the network skb. Fix this by rejecting block lengths that cannot hold at least the NTB header plus one NDP. This will make block_len - opts->ndp_size and block_len - opts->dpe_size both well-defined. Commit 8d2b1a1ec9f5 ("CDC-NCM: avoid overflow in sanity checking") fixed a related class of issues on the host side of NCM. Fixes: 2b74b0a04d3e ("USB: gadget: f_ncm: add bounds checks to ncm_unwrap_ntb()") Cc: stable Assisted-by: gregkh_clanker_t1000 Link: https://patch.msgid.link/2026040753-baffle-handheld-624d@gregkh Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index a6fa5ed3d6cb..c5bf8a448d64 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -1210,8 +1210,8 @@ static int ncm_unwrap_ntb(struct gether *port, block_len = get_ncm(&tmp, opts->block_length); /* (d)wBlockLength */ - if (block_len > ntb_max) { - INFO(port->func.config->cdev, "OUT size exceeded\n"); + if ((block_len < opts->nth_size + opts->ndp_size) || (block_len > ntb_max)) { + INFO(port->func.config->cdev, "Bad block length: %#X\n", block_len); goto err; } From 37d9c4c055c3b3357c61dba2335ab21340e33553 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 7 Apr 2026 21:23:43 +0200 Subject: [PATCH 167/176] USB: serial: iuu_phoenix: fix iuutool author name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original iuutool author is Juan Carlos Borrás - fix the spelling. Signed-off-by: Thorsten Blum Signed-off-by: Johan Hovold --- drivers/usb/serial/iuu_phoenix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c index f8d6aa30a3e1..0ca111b111c7 100644 --- a/drivers/usb/serial/iuu_phoenix.c +++ b/drivers/usb/serial/iuu_phoenix.c @@ -6,7 +6,7 @@ * Copyright (C) 2007 Alain Degreffe (eczema@ecze.com) * - * Original code taken from iuutool (Copyright (C) 2006 Juan Carlos Borrás) + * Original code taken from iuutool (Copyright (C) 2006 Juan Carlos Borrás) * * And tested with help of WB Electronics */ From cfcd7b29e5191f5ff097f7c278188face0c79ab7 Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Thu, 2 Apr 2026 16:32:04 +0800 Subject: [PATCH 168/176] usbip: tools: add hint when no exported devices are found When refresh_exported_devices() finds no devices, it's helpful to inform users about potential causes. This could be due to: 1. The usbip driver module is not loaded. 2. No devices have been exported yet. Add an informational message to guide users when ndevs == 0. Also update the condition in usbip_host_driver_open() and usbip_device_driver_open() to check both ret and ndevs == 0, and change err() to info(). Message visibility by scenario: - usbipd (console mode): Show on console/serial, this allows instant visibility for debugging. - usbipd -D (daemon mode): Message logged to syslog, can keep logs for later traceability in production. Also can use "journalctl -f" to trace on console. Suggested-by: Shuah Khan Signed-off-by: Zongmin Zhou Reviewed-by: Shuah Khan Link: https://patch.msgid.link/20260402083204.53179-1-min_halo@163.com Signed-off-by: Greg Kroah-Hartman --- tools/usb/usbip/libsrc/usbip_device_driver.c | 6 +++--- tools/usb/usbip/libsrc/usbip_host_common.c | 3 +++ tools/usb/usbip/libsrc/usbip_host_driver.c | 7 ++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/usb/usbip/libsrc/usbip_device_driver.c b/tools/usb/usbip/libsrc/usbip_device_driver.c index 927a151fa9aa..1dfbb76ab26c 100644 --- a/tools/usb/usbip/libsrc/usbip_device_driver.c +++ b/tools/usb/usbip/libsrc/usbip_device_driver.c @@ -137,9 +137,9 @@ static int usbip_device_driver_open(struct usbip_host_driver *hdriver) INIT_LIST_HEAD(&hdriver->edev_list); ret = usbip_generic_driver_open(hdriver); - if (ret) - err("please load " USBIP_CORE_MOD_NAME ".ko and " - USBIP_DEVICE_DRV_NAME ".ko!"); + if (ret || hdriver->ndevs == 0) + info("please load " USBIP_CORE_MOD_NAME ".ko and " + USBIP_DEVICE_DRV_NAME ".ko"); return ret; } diff --git a/tools/usb/usbip/libsrc/usbip_host_common.c b/tools/usb/usbip/libsrc/usbip_host_common.c index ca78aa368476..01599cb2fa7b 100644 --- a/tools/usb/usbip/libsrc/usbip_host_common.c +++ b/tools/usb/usbip/libsrc/usbip_host_common.c @@ -149,6 +149,9 @@ static int refresh_exported_devices(struct usbip_host_driver *hdriver) } } + if (hdriver->ndevs == 0) + info("Please load appropriate modules or export devices."); + return 0; } diff --git a/tools/usb/usbip/libsrc/usbip_host_driver.c b/tools/usb/usbip/libsrc/usbip_host_driver.c index 573e73ec36bd..bd8a6b84de0e 100644 --- a/tools/usb/usbip/libsrc/usbip_host_driver.c +++ b/tools/usb/usbip/libsrc/usbip_host_driver.c @@ -32,9 +32,10 @@ static int usbip_host_driver_open(struct usbip_host_driver *hdriver) INIT_LIST_HEAD(&hdriver->edev_list); ret = usbip_generic_driver_open(hdriver); - if (ret) - err("please load " USBIP_CORE_MOD_NAME ".ko and " - USBIP_HOST_DRV_NAME ".ko!"); + if (ret || hdriver->ndevs == 0) + info("please load " USBIP_CORE_MOD_NAME ".ko and " + USBIP_HOST_DRV_NAME ".ko"); + return ret; } From e7d219f4302182f956bc25e1b76bfe14009847e7 Mon Sep 17 00:00:00 2001 From: Qinghua Zhao Date: Thu, 9 Apr 2026 22:54:28 +0800 Subject: [PATCH 169/176] drivers/usb/host: Fix spelling error 'seperate' -> 'separate' Fix typo in comment where 'seperate' should be 'separate'. Signed-off-by: Qinghua Zhao Link: https://patch.msgid.link/20260409145428.18130-1-zqh1630@126.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mvebu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-mvebu.c b/drivers/usb/host/xhci-mvebu.c index 257e4d79971f..f91c5004fade 100644 --- a/drivers/usb/host/xhci-mvebu.c +++ b/drivers/usb/host/xhci-mvebu.c @@ -30,7 +30,7 @@ static void xhci_mvebu_mbus_config(void __iomem *base, writel(0, base + USB3_WIN_BASE(win)); } - /* Program each DRAM CS in a seperate window */ + /* Program each DRAM CS in a separate window */ for (win = 0; win < dram->num_cs; win++) { const struct mbus_dram_window *cs = &dram->cs[win]; From 2b7f1a4f19f87f44f95a812b0f40f79a23950f99 Mon Sep 17 00:00:00 2001 From: Charan Pedumuru Date: Fri, 27 Mar 2026 16:47:42 +0000 Subject: [PATCH 170/176] arm: dts: at91: remove unused #address-cells/#size-cells from sam9x60 udc node The UDC node does not define any child nodes, so the "#address-cells" and "#size-cells" properties are unnecessary. Remove these unused properties to simplify the devicetree node and keep it consistent with DT conventions. Reviewed-by: Claudiu Beznea Signed-off-by: Charan Pedumuru Link: https://patch.msgid.link/20260327-atmel-usb-v4-1-eb8b6e49b29d@gmail.com Signed-off-by: Greg Kroah-Hartman --- arch/arm/boot/dts/microchip/sam9x60.dtsi | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm/boot/dts/microchip/sam9x60.dtsi b/arch/arm/boot/dts/microchip/sam9x60.dtsi index b075865e6a76..e708b3df4ccd 100644 --- a/arch/arm/boot/dts/microchip/sam9x60.dtsi +++ b/arch/arm/boot/dts/microchip/sam9x60.dtsi @@ -75,8 +75,6 @@ ahb { ranges; usb0: gadget@500000 { - #address-cells = <1>; - #size-cells = <0>; compatible = "microchip,sam9x60-udc"; reg = <0x00500000 0x100000 0xf803c000 0x400>; From 73d4839a3a86f3329fda8cc2cda723f10b22eb92 Mon Sep 17 00:00:00 2001 From: Charan Pedumuru Date: Fri, 27 Mar 2026 16:47:43 +0000 Subject: [PATCH 171/176] dt-bindings: usb: generic-ohci: add AT91RM9200 OHCI binding support Convert the Atmel AT91RM9200 OHCI USB host controller binding to DT schema by defining it in the existing generic OHCI schema. Signed-off-by: Charan Pedumuru Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260327-atmel-usb-v4-2-eb8b6e49b29d@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/atmel-usb.txt | 27 ------------ .../devicetree/bindings/usb/generic-ohci.yaml | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/atmel-usb.txt b/Documentation/devicetree/bindings/usb/atmel-usb.txt index 12183ef47ee4..c09685283109 100644 --- a/Documentation/devicetree/bindings/usb/atmel-usb.txt +++ b/Documentation/devicetree/bindings/usb/atmel-usb.txt @@ -1,32 +1,5 @@ Atmel SOC USB controllers -OHCI - -Required properties: - - compatible: Should be "atmel,at91rm9200-ohci" for USB controllers - used in host mode. - - reg: Address and length of the register set for the device - - interrupts: Should contain ohci interrupt - - clocks: Should reference the peripheral, host and system clocks - - clock-names: Should contain three strings - "ohci_clk" for the peripheral clock - "hclk" for the host clock - "uhpck" for the system clock - - num-ports: Number of ports. - - atmel,vbus-gpio: If present, specifies a gpio that needs to be - activated for the bus to be powered. - - atmel,oc-gpio: If present, specifies a gpio that needs to be - activated for the overcurrent detection. - -usb0: ohci@500000 { - compatible = "atmel,at91rm9200-ohci", "usb-ohci"; - reg = <0x00500000 0x100000>; - clocks = <&uhphs_clk>, <&uhphs_clk>, <&uhpck>; - clock-names = "ohci_clk", "hclk", "uhpck"; - interrupts = <20 4>; - num-ports = <2>; -}; - EHCI Required properties: diff --git a/Documentation/devicetree/bindings/usb/generic-ohci.yaml b/Documentation/devicetree/bindings/usb/generic-ohci.yaml index 961cbf85eeb5..d42f448fa204 100644 --- a/Documentation/devicetree/bindings/usb/generic-ohci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ohci.yaml @@ -55,6 +55,7 @@ properties: - ti,ohci-omap3 - items: - enum: + - atmel,at91rm9200-ohci - cavium,octeon-6335-ohci - nintendo,hollywood-usb-ohci - nxp,ohci-nxp @@ -137,6 +138,24 @@ properties: The associated ISP1301 device. Necessary for the UDC controller for connecting to the USB physical layer. + atmel,vbus-gpio: + description: + GPIO used to control or sense the USB VBUS power. Each entry + represents a VBUS-related GPIO; count and order may vary by hardware. + Entries follow standard GPIO specifier format. A value of 0 indicates + an unused or unavailable VBUS signal. + minItems: 1 + maxItems: 3 + + atmel,oc-gpio: + description: + GPIO used to signal USB overcurrent condition. Each entry represents + an OC detection GPIO; count and order may vary by hardware. Entries + follow standard GPIO specifier format. A value of 0 indicates an + unused or unavailable OC signal. + minItems: 1 + maxItems: 3 + required: - compatible - reg @@ -144,6 +163,28 @@ required: allOf: - $ref: usb-hcd.yaml + - if: + properties: + compatible: + contains: + const: atmel,at91rm9200-ohci + then: + properties: + clock-names: + items: + - const: ohci_clk + - const: hclk + - const: uhpck + + required: + - clocks + - clock-names + + else: + properties: + atmel,vbus-gpio: false + atmel,oc-gpio: false + - if: not: properties: From 02d58df0a5c12e5dcff0a690973537fc1b27db3e Mon Sep 17 00:00:00 2001 From: Charan Pedumuru Date: Fri, 27 Mar 2026 16:47:44 +0000 Subject: [PATCH 172/176] dt-bindings: usb: generic-ehci: fix schema structure and add at91sam9g45 constraints Add clock and phy constraints for atmel,at91sam9g45-ehci and reorganize the allOf section to fix dtbs_check warnings. Reviewed-by: Rob Herring (Arm) Signed-off-by: Charan Pedumuru Link: https://patch.msgid.link/20260327-atmel-usb-v4-3-eb8b6e49b29d@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/atmel-usb.txt | 24 ---------- .../devicetree/bindings/usb/generic-ehci.yaml | 46 +++++++++++++------ 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/atmel-usb.txt b/Documentation/devicetree/bindings/usb/atmel-usb.txt index c09685283109..bf2149e5f0b3 100644 --- a/Documentation/devicetree/bindings/usb/atmel-usb.txt +++ b/Documentation/devicetree/bindings/usb/atmel-usb.txt @@ -1,29 +1,5 @@ Atmel SOC USB controllers -EHCI - -Required properties: - - compatible: Should be "atmel,at91sam9g45-ehci" for USB controllers - used in host mode. - - reg: Address and length of the register set for the device - - interrupts: Should contain ehci interrupt - - clocks: Should reference the peripheral and the UTMI clocks - - clock-names: Should contain two strings - "ehci_clk" for the peripheral clock - "usb_clk" for the UTMI clock - -Optional properties: - - phy_type : For multi port host USB controllers, should be one of - "utmi", or "hsic". - -usb1: ehci@800000 { - compatible = "atmel,at91sam9g45-ehci", "usb-ehci"; - reg = <0x00800000 0x100000>; - interrupts = <22 4>; - clocks = <&utmi>, <&uhphs_clk>; - clock-names = "usb_clk", "ehci_clk"; -}; - AT91 USB device controller Required properties: diff --git a/Documentation/devicetree/bindings/usb/generic-ehci.yaml b/Documentation/devicetree/bindings/usb/generic-ehci.yaml index 601f097c09a6..55a5aa7d7a54 100644 --- a/Documentation/devicetree/bindings/usb/generic-ehci.yaml +++ b/Documentation/devicetree/bindings/usb/generic-ehci.yaml @@ -9,19 +9,6 @@ title: USB EHCI Controller maintainers: - Greg Kroah-Hartman -allOf: - - $ref: usb-hcd.yaml - - if: - properties: - compatible: - not: - contains: - const: ibm,usb-ehci-440epx - then: - properties: - reg: - maxItems: 1 - properties: compatible: oneOf: @@ -167,6 +154,39 @@ required: - reg - interrupts +allOf: + - $ref: usb-hcd.yaml + - if: + properties: + compatible: + not: + contains: + const: ibm,usb-ehci-440epx + then: + properties: + reg: + maxItems: 1 + - if: + properties: + compatible: + contains: + const: atmel,at91sam9g45-ehci + then: + properties: + clock-names: + items: + - const: usb_clk + - const: ehci_clk + + phy_type: + enum: + - utmi + - hsic + + required: + - clocks + - clock-names + unevaluatedProperties: false examples: From abfffb4b365ec18c4d3c6465b47fb36936fd4c2b Mon Sep 17 00:00:00 2001 From: Charan Pedumuru Date: Fri, 27 Mar 2026 16:47:45 +0000 Subject: [PATCH 173/176] dt-bindings: usb: atmel,at91rm9200-udc: convert to DT schema Convert Atmel AT91 USB Device Controller (UDC) binding to DT schema. Changes during conversion: - Include "atmel,pullup-gpio" and "atmel,matrix" in the properties since they are required by existing in-tree DTS definitions. Reviewed-by: Rob Herring (Arm) Signed-off-by: Charan Pedumuru Link: https://patch.msgid.link/20260327-atmel-usb-v4-4-eb8b6e49b29d@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/atmel,at91rm9200-udc.yaml | 76 +++++++++++++++++++ .../devicetree/bindings/usb/atmel-usb.txt | 28 ------- 2 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/atmel,at91rm9200-udc.yaml diff --git a/Documentation/devicetree/bindings/usb/atmel,at91rm9200-udc.yaml b/Documentation/devicetree/bindings/usb/atmel,at91rm9200-udc.yaml new file mode 100644 index 000000000000..a4eabb935e6e --- /dev/null +++ b/Documentation/devicetree/bindings/usb/atmel,at91rm9200-udc.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/atmel,at91rm9200-udc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Atmel AT91 USB Device Controller (UDC) + +maintainers: + - Nicolas Ferre + - Alexandre Belloni + +description: + The Atmel AT91 USB Device Controller provides USB gadget (device-mode) + functionality on AT91 SoCs. It requires a peripheral clock and an AHB + clock for operation and may optionally control VBUS power through a GPIO. + +properties: + compatible: + enum: + - atmel,at91rm9200-udc + - atmel,at91sam9260-udc + - atmel,at91sam9261-udc + - atmel,at91sam9263-udc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + items: + - const: pclk + - const: hclk + + atmel,vbus-gpio: + description: GPIO used to enable or control VBUS power for the USB bus. + maxItems: 1 + + atmel,matrix: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to the Atmel bus matrix controller. + + atmel,pullup-gpio: + description: + GPIO controlling the USB D+ pull-up resistor used to signal device + connection to the host. + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + #include + #include + #include + gadget@fffa4000 { + compatible = "atmel,at91rm9200-udc"; + reg = <0xfffa4000 0x4000>; + interrupts = <11 IRQ_TYPE_LEVEL_HIGH 2>; + clocks = <&udc_clk>, <&udpck>; + clock-names = "pclk", "hclk"; + atmel,vbus-gpio = <&pioC 5 GPIO_ACTIVE_HIGH>; + }; +... diff --git a/Documentation/devicetree/bindings/usb/atmel-usb.txt b/Documentation/devicetree/bindings/usb/atmel-usb.txt index bf2149e5f0b3..ab353576d1de 100644 --- a/Documentation/devicetree/bindings/usb/atmel-usb.txt +++ b/Documentation/devicetree/bindings/usb/atmel-usb.txt @@ -1,33 +1,5 @@ Atmel SOC USB controllers -AT91 USB device controller - -Required properties: - - compatible: Should be one of the following - "atmel,at91rm9200-udc" - "atmel,at91sam9260-udc" - "atmel,at91sam9261-udc" - "atmel,at91sam9263-udc" - - reg: Address and length of the register set for the device - - interrupts: Should contain macb interrupt - - clocks: Should reference the peripheral and the AHB clocks - - clock-names: Should contain two strings - "pclk" for the peripheral clock - "hclk" for the AHB clock - -Optional properties: - - atmel,vbus-gpio: If present, specifies a gpio that needs to be - activated for the bus to be powered. - -usb1: gadget@fffa4000 { - compatible = "atmel,at91rm9200-udc"; - reg = <0xfffa4000 0x4000>; - interrupts = <10 4>; - clocks = <&udc_clk>, <&udpck>; - clock-names = "pclk", "hclk"; - atmel,vbus-gpio = <&pioC 5 0>; -}; - Atmel High-Speed USB device controller Required properties: From 35c8b7148c8d387efde7b54019035e8c4a9ea99e Mon Sep 17 00:00:00 2001 From: Charan Pedumuru Date: Fri, 27 Mar 2026 16:47:46 +0000 Subject: [PATCH 174/176] dt-bindings: usb: atmel,at91sam9rl-udc: convert to DT schema Convert Atmel High-Speed USB Device Controller (USBA) binding to DT schema. Changes during conversion: - Make the "clock-names" property flexible enough to accept the items in any order as the existing in tree DTS nodes doesn't follow an order. Signed-off-by: Charan Pedumuru Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260327-atmel-usb-v4-5-eb8b6e49b29d@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/atmel,at91sam9rl-udc.yaml | 74 +++++++++++++++++++ .../devicetree/bindings/usb/atmel-usb.txt | 46 ------------ 2 files changed, 74 insertions(+), 46 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/atmel,at91sam9rl-udc.yaml delete mode 100644 Documentation/devicetree/bindings/usb/atmel-usb.txt diff --git a/Documentation/devicetree/bindings/usb/atmel,at91sam9rl-udc.yaml b/Documentation/devicetree/bindings/usb/atmel,at91sam9rl-udc.yaml new file mode 100644 index 000000000000..cdbbd17f8036 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/atmel,at91sam9rl-udc.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/atmel,at91sam9rl-udc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Atmel High-Speed USB Device Controller (USBA) + +maintainers: + - Nicolas Ferre + - Alexandre Belloni + +description: + The Atmel High-Speed USB Device Controller (USBA) provides USB 2.0 + high-speed gadget functionality on several Atmel and Microchip SoCs. + The controller requires a peripheral clock and a host clock for operation + and may optionally use a GPIO to detect VBUS presence. + +properties: + compatible: + oneOf: + - enum: + - atmel,at91sam9rl-udc + - atmel,at91sam9g45-udc + - atmel,sama5d3-udc + - items: + - const: microchip,lan9662-udc + - const: atmel,sama5d3-udc + - const: microchip,sam9x60-udc + + reg: + maxItems: 2 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 2 + + clock-names: + minItems: 2 + maxItems: 2 + items: + enum: [pclk, hclk] + + atmel,vbus-gpio: + description: GPIO used to detect the presence of VBUS, indicating that + the USB cable is connected. + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + gadget@fff78000 { + compatible = "atmel,at91sam9g45-udc"; + reg = <0x00600000 0x80000 + 0xfff78000 0x400>; + interrupts = <27 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&pmc PMC_TYPE_PERIPHERAL 27>, <&pmc PMC_TYPE_CORE PMC_UTMI>; + clock-names = "pclk", "hclk"; + atmel,vbus-gpio = <&pioC 15 GPIO_ACTIVE_HIGH>; + }; +... diff --git a/Documentation/devicetree/bindings/usb/atmel-usb.txt b/Documentation/devicetree/bindings/usb/atmel-usb.txt deleted file mode 100644 index ab353576d1de..000000000000 --- a/Documentation/devicetree/bindings/usb/atmel-usb.txt +++ /dev/null @@ -1,46 +0,0 @@ -Atmel SOC USB controllers - -Atmel High-Speed USB device controller - -Required properties: - - compatible: Should be one of the following - "atmel,at91sam9rl-udc" - "atmel,at91sam9g45-udc" - "atmel,sama5d3-udc" - "microchip,sam9x60-udc" - "microchip,lan9662-udc" - For "microchip,lan9662-udc" the fallback "atmel,sama5d3-udc" - is required. - - reg: Address and length of the register set for the device - - interrupts: Should contain usba interrupt - - clocks: Should reference the peripheral and host clocks - - clock-names: Should contain two strings - "pclk" for the peripheral clock - "hclk" for the host clock - -Deprecated property: - - ep childnode: To specify the number of endpoints and their properties. - -Optional properties: - - atmel,vbus-gpio: If present, specifies a gpio that allows to detect whether - vbus is present (USB is connected). - -Deprecated child node properties: - - name: Name of the endpoint. - - reg: Num of the endpoint. - - atmel,fifo-size: Size of the fifo. - - atmel,nb-banks: Number of banks. - - atmel,can-dma: Boolean to specify if the endpoint support DMA. - - atmel,can-isoc: Boolean to specify if the endpoint support ISOC. - -usb2: gadget@fff78000 { - #address-cells = <1>; - #size-cells = <0>; - compatible = "atmel,at91sam9rl-udc"; - reg = <0x00600000 0x80000 - 0xfff78000 0x400>; - interrupts = <27 4 0>; - clocks = <&utmi>, <&udphs_clk>; - clock-names = "hclk", "pclk"; - atmel,vbus-gpio = <&pioB 19 0>; -}; From d7a8d8b40800f51a6281ea330233a0ef22982513 Mon Sep 17 00:00:00 2001 From: Minda Chen Date: Fri, 10 Apr 2026 19:24:59 +0800 Subject: [PATCH 175/176] dt-bindings: usb: dwc3: add support for StarFive JHB100 Add support for the USB 2.0 Dual-Role Device (DRD) controller embedded in the StarFive JHB100 SoC. The controller is based on the Synopsys DesignWare Core USB 3 (DWC3) IP. Signed-off-by: Minda Chen Acked-by: Conor Dooley Link: https://patch.msgid.link/20260410112500.90432-2-minda.chen@starfivetech.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/usb/starfive,jhb100-dwc3.yaml | 64 +++++++++++++++++++ MAINTAINERS | 3 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/usb/starfive,jhb100-dwc3.yaml diff --git a/Documentation/devicetree/bindings/usb/starfive,jhb100-dwc3.yaml b/Documentation/devicetree/bindings/usb/starfive,jhb100-dwc3.yaml new file mode 100644 index 000000000000..fbabe99e9d5c --- /dev/null +++ b/Documentation/devicetree/bindings/usb/starfive,jhb100-dwc3.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/starfive,jhb100-dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: StarFive JHB100 DWC3 USB SoC Controller + +maintainers: + - Minda Chen + +description: + The USB DRD controller on JHB100 BMC SoC. + +allOf: + - $ref: snps,dwc3-common.yaml# + +properties: + compatible: + const: starfive,jhb100-dwc3 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: USB main enable clk + - description: DWC3 bus early clock + - description: DWC3 ref clock + + clock-names: + items: + - const: main + - const: bus_early + - const: ref + + resets: + maxItems: 1 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + +unevaluatedProperties: false + +examples: + - | + usb@11800000 { + compatible = "starfive,jhb100-dwc3"; + reg = <0x11800000 0x10000>; + clocks = <&usbcrg 9>, + <&usbcrg 5>, + <&usbcrg 6>; + clock-names = "main", "bus_early", "ref"; + resets = <&usbcrg 4>; + interrupts = <105>; + dr_mode = "host"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index f81be0167ba9..63cbb8642f4d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25267,10 +25267,11 @@ F: Documentation/devicetree/bindings/reset/starfive,jh7100-reset.yaml F: drivers/reset/starfive/reset-starfive-jh71* F: include/dt-bindings/reset/starfive?jh71*.h -STARFIVE JH71X0 USB DRIVERS +STARFIVE USB DRIVERS M: Minda Chen S: Maintained F: Documentation/devicetree/bindings/usb/starfive,jh7110-usb.yaml +F: Documentation/devicetree/bindings/usb/starfive,jhb100-dwc3.yaml F: drivers/usb/cdns3/cdns3-starfive.c STARFIVE JH71XX PMU CONTROLLER DRIVER From 87117347a0e77f528f357faa2230d5caffcd1b4e Mon Sep 17 00:00:00 2001 From: Minda Chen Date: Fri, 10 Apr 2026 19:25:00 +0800 Subject: [PATCH 176/176] usb: dwc3: starfive: Add JHB100 USB 2.0 DRD controller JHB100 contains 2 dwc3 USB controllers and PHYs and working as USB 2.0 speed. It can working in generic platform and setting default properties. Signed-off-by: Minda Chen Link: https://patch.msgid.link/20260410112500.90432-3-minda.chen@starfivetech.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-generic-plat.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c index 69b7e6227b3b..ca69ac0eb07c 100644 --- a/drivers/usb/dwc3/dwc3-generic-plat.c +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -236,6 +236,7 @@ static const struct of_device_id dwc3_generic_of_match[] = { { .compatible = "spacemit,k3-dwc3", }, { .compatible = "fsl,ls1028a-dwc3", &fsl_ls1028_dwc3}, { .compatible = "eswin,eic7700-dwc3", &eic7700_dwc3}, + { .compatible = "starfive,jhb100-dwc3", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, dwc3_generic_of_match);