diff options
134 files changed, 4763 insertions, 1381 deletions
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/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 <bleung@chromium.org> - - Prashant Malani <pmalani@chromium.org> + - Abhishek Pandit-Subedi <abhishekpandit@chromium.org> + - Andrei Kuchynski <akuchynski@chromium.org> + - Łukasz Bartosik <ukaszb@chromium.org> + - Jameson Thies <jthies@google.com> 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 diff --git a/Documentation/devicetree/bindings/connector/usb-connector.yaml b/Documentation/devicetree/bindings/connector/usb-connector.yaml index d97b29e49bf5..8ca0292490a2 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: pd-disable: [typec-power-opmode] sink-vdos-v1: [ sink-vdos ] @@ -331,8 +365,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/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"; 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 <nicolas.ferre@microchip.com> + - Alexandre Belloni <alexandre.belloni@bootlin.com> + +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 <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/clock/at91.h> + #include <dt-bindings/gpio/gpio.h> + 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,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 <nicolas.ferre@microchip.com> + - Alexandre Belloni <alexandre.belloni@bootlin.com> + +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 <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/clock/at91.h> + #include <dt-bindings/gpio/gpio.h> + 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 12183ef47ee4..000000000000 --- a/Documentation/devicetree/bindings/usb/atmel-usb.txt +++ /dev/null @@ -1,125 +0,0 @@ -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: - - 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: - - 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: - - 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>; -}; 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 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 <alchark@flipper.net> + +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 <dt-bindings/gpio/gpio.h> + 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>; + }; + }; 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 <jun.li@nxp.com> +deprecated: true + properties: compatible: oneOf: 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 <gregkh@linuxfoundation.org> -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: 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 @@ -145,6 +164,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: compatible: diff --git a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml index 3de4dc40b791..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"; @@ -75,6 +79,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_LOAD_CHAR(0, 1, 1, 2)>; + sink-compliance = /bits/ 8 <(COMPLIANCE_LPS | COMPLIANCE_PS1)>; + charging-adapter-pdp-milliwatt = <18000>; }; }; }; 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 ACTIVE_LEVEL> - 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 <davidm@egauge.net> + +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 <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + 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>; + }; + }; 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 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 <xu.yang_2@nxp.com> + +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 <dt-bindings/interrupt-controller/arm-gic.h> + + 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 = <GIC_SPI 180 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 386 IRQ_TYPE_LEVEL_HIGH>; + 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; + }; 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 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 = <GIC_SPI 149 IRQ_TYPE_NONE>; - 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/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/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index 7d784a648b7d..8201656b41ed 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 @@ -153,8 +154,6 @@ properties: wakeup-source: true -# Required child node: - required: - compatible - reg @@ -175,6 +174,7 @@ allOf: then: properties: clocks: + minItems: 3 maxItems: 3 clock-names: items: @@ -203,6 +203,7 @@ allOf: compatible: contains: enum: + - qcom,ipq5424-dwc3 - qcom,ipq9574-dwc3 - qcom,kaanapali-dwc3 - qcom,msm8953-dwc3 @@ -222,6 +223,7 @@ allOf: then: properties: clocks: + minItems: 5 maxItems: 5 clock-names: items: @@ -264,6 +266,7 @@ allOf: then: properties: clocks: + minItems: 4 maxItems: 4 clock-names: items: @@ -283,6 +286,7 @@ allOf: then: properties: clocks: + minItems: 4 maxItems: 4 clock-names: items: @@ -303,6 +307,7 @@ allOf: then: properties: clocks: + minItems: 9 maxItems: 9 clock-names: items: @@ -346,14 +351,17 @@ allOf: compatible: contains: enum: + - qcom,eliza-dwc3 - qcom,milos-dwc3 - qcom,qcm2290-dwc3 - qcom,qcs615-dwc3 - qcom,sar2130p-dwc3 - qcom,sc8180x-dwc3 - qcom,sc8180x-dwc3-mp + - qcom,sm4250-dwc3 - qcom,sm6115-dwc3 - qcom,sm6125-dwc3 + - qcom,sm6375-dwc3 - qcom,sm8150-dwc3 - qcom,sm8250-dwc3 - qcom,sm8450-dwc3 @@ -363,6 +371,7 @@ allOf: properties: clocks: minItems: 6 + maxItems: 6 clock-names: items: - const: cfg_noc @@ -404,6 +413,7 @@ allOf: then: properties: clocks: + minItems: 7 maxItems: 7 clock-names: items: @@ -446,6 +456,7 @@ allOf: - qcom,msm8996-dwc3 - qcom,qcs404-dwc3 - qcom,sdm660-dwc3 + - qcom,sm4250-dwc3 - qcom,sm6115-dwc3 - qcom,sm6125-dwc3 then: @@ -472,6 +483,7 @@ allOf: then: properties: interrupts: + minItems: 4 maxItems: 4 interrupt-names: items: @@ -485,6 +497,26 @@ allOf: 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: + contains: + enum: - qcom,glymur-dwc3 - qcom,milos-dwc3 - qcom,x1e80100-dwc3 @@ -500,13 +532,14 @@ 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: compatible: contains: enum: + - qcom,eliza-dwc3 - qcom,ipq4019-dwc3 - qcom,ipq8064-dwc3 - qcom,kaanapali-dwc3 @@ -523,8 +556,8 @@ allOf: - qcom,sdx55-dwc3 - qcom,sdx65-dwc3 - qcom,sdx75-dwc3 - - qcom,sm4250-dwc3 - qcom,sm6350-dwc3 + - qcom,sm6375-dwc3 - qcom,sm8150-dwc3 - qcom,sm8250-dwc3 - qcom,sm8350-dwc3 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 <neil.armstrong@linaro.org> + +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>; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml index ae611f7e57ca..7ded36384518 100644 --- a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml +++ b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml @@ -18,11 +18,21 @@ description: | properties: compatible: - enum: - - richtek,rt1711h - - richtek,rt1715 + oneOf: + - enum: + - richtek,rt1711h + - richtek,rt1715 + - items: + - enum: + - hynetek,husb311 + - const: richtek,rt1711h + - items: + - enum: + - etekmicro,et7304 + - const: 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. + HUSB311 is a rebrand of RT1711H which is pin and register compatible. reg: maxItems: 1 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 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 <peter.griffin@linaro.org> + +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 <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/reset/stih407-resets.h> + usb@fe1ffc00 { + compatible = "st,st-ohci-300x"; + reg = <0xfe1ffc00 0x100>; + interrupts = <GIC_SPI 149 IRQ_TYPE_NONE>; + 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"; + }; +... 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 <minda.chen@starfivetech.com> + +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/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 <dlan@kernel.org> + +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 <dt-bindings/gpio/gpio.h> + 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>; + }; + }; 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 <balbi@ti.com> + +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 <dt-bindings/interrupt-controller/arm-gic.h> + omap_dwc3_1@0 { + compatible = "ti,dwc3"; + reg = <0x0 0x10000>; + interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>; + #address-cells = <1>; + #size-cells = <1>; + utmi-mode = <2>; + ranges = <0 0 0x20000>; + + usb@10000 { + compatible = "snps,dwc3"; + reg = <0x10000 0x17000>; + interrupts = <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>; + 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; + }; + }; +... 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 <balbi@ti.com> + +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 <dt-bindings/interrupt-controller/arm-gic.h> + usb@4a0ab000 { + compatible = "ti,omap4-musb"; + reg = <0x4a0ab000 0x1000>; + interrupts = <GIC_SPI 92 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>; + 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>; + }; +... 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 */ diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 6339988e3805..28784d66ae7b 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -365,6 +365,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,.*": @@ -547,6 +549,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,.*": @@ -751,6 +755,8 @@ patternProperties: description: Hycon Technology Corp. "^hydis,.*": description: Hydis Technologies + "^hynetek,.*": + description: Hynetek Semiconductor Co., Ltd. "^hynitron,.*": description: Shanghai Hynitron Microelectronics Co. Ltd. "^hynix,.*": diff --git a/MAINTAINERS b/MAINTAINERS index c00df84252aa..76d8291237be 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15816,6 +15816,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 <amitsd@google.com> +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 <javier@dowhile0.org> L: linux-kernel@vger.kernel.org @@ -25486,10 +25492,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 <minda.chen@starfivetech.com> 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 @@ -26536,7 +26543,7 @@ F: drivers/media/i2c/thp7312.c F: include/uapi/linux/thp7312.h THUNDERBOLT DMA TRAFFIC TEST DRIVER -M: Isaac Hazan <isaac.hazan@intel.com> +M: Mika Westerberg <westeri@kernel.org> L: linux-usb@vger.kernel.org S: Maintained F: drivers/thunderbolt/dma_test.c 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/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index e8a1bdb246c4..83392ed6a8da 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -642,6 +642,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 f2efbb82707c..7ee839dca7f3 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -130,3 +130,4 @@ obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o obj-$(CONFIG_MACSMC_POWER) += macsmc-power.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 <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/devm-helpers.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/linear_range.h> +#include <linux/mfd/max77759.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/string_choices.h> +#include <linux/workqueue.h> + +/* 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 <amitsd@google.com>"); +MODULE_DESCRIPTION("Maxim MAX77759 charger driver"); +MODULE_LICENSE("GPL"); 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/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, 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/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. 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); 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 */ 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, diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index fac11f20cf0a..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 */ @@ -618,28 +619,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; diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index 647e98f4e351..fecc7d7e2f0d 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); @@ -187,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 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 @@ skip_normal_probe: 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 @@ skip_countries: 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 @@ skip_countries: 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) }, 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); 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, \ 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); 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; } diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 44e38f922bc5..b1364f0c384c 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) { @@ -141,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); @@ -805,10 +820,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. */ 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/core.c b/drivers/usb/dwc3/core.c index 161a4d58b2ce..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) @@ -1675,6 +1695,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 +2502,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 +2566,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..e0dee9d28740 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) @@ -1119,6 +1120,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 @@ -1149,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 @@ -1161,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. @@ -1373,6 +1378,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; @@ -1403,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/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c index e846844e0023..ca69ac0eb07c 100644 --- a/drivers/usb/dwc3/dwc3-generic-plat.c +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -12,6 +12,8 @@ #include <linux/reset.h> #include <linux/regmap.h> #include <linux/mfd/syscon.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/otg.h> #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,9 +232,11 @@ 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}, + { .compatible = "starfive,jhb100-dwc3", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, dwc3_generic_of_match); diff --git a/drivers/usb/dwc3/dwc3-imx.c b/drivers/usb/dwc3/dwc3-imx.c new file mode 100644 index 000000000000..973a486b544d --- /dev/null +++ b/drivers/usb/dwc3/dwc3-imx.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dwc3-imx.c - NXP i.MX Soc USB3 Specific Glue layer + * + * Copyright 2026 NXP + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#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 (dwc_imx->pm_suspended) + return; + + 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; + + if (!dwc_imx->pm_suspended) + return; + + 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"); 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; } 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) { 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){ \ 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 <linux/delay.h> #include <linux/time64.h> #include <linux/ulpi/regs.h> +#include <linux/ulpi/driver.h> #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; } diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index e5ccaec7750c..c5a12a6760ea 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; @@ -1276,8 +1277,14 @@ 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) { + status = -ENOMEM; + goto fail_free_all; + } + hidg->cdev->ops = &f_hidg_fops; + + status = cdev_device_add(hidg->cdev, &hidg->dev); if (status) goto fail_free_all; @@ -1579,7 +1586,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); } 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/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 @@ parse_ntb: 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; } 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); 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; 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"); 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) 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) { + int ret; + if (udc->enabled) - return; + return 0; - clk_enable(udc->clk); + 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. * 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); 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; 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 <linux/clk.h> #include <linux/platform_data/usb-ehci-orion.h> #include <linux/of.h> -#include <linux/phy/phy.h> #include <linux/usb.h> #include <linux/usb/hcd.h> #include <linux/io.h> @@ -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. */ 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 { 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-hub.c b/drivers/usb/host/xhci-hub.c index 04cc3d681495..bacd0ddd0d09 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); @@ -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 portsc) { char *port_change_bit; u32 status; @@ -621,11 +621,11 @@ static void xhci_clear_port_change_bit(struct xhci_hcd *xhci, u16 wValue, 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", - wIndex + 1, port_change_bit, port_status); + port->hcd_portnum + 1, port_change_bit, portsc); } 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; } @@ -853,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 status_reg) +static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, 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 + /* + * CAS indicates that a warm reset is required, it may be set in any + * link state and is only present on roothubs. */ - if (status_reg & 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 (portsc & PORT_CAS) { + /* + * If not already in Compliance or Inactive state, + * report Compliance Mode so the hub logic triggers a warm reset. */ - 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 - - * hub state machine resets port - * when this bit is set. - */ - pls |= USB_PORT_STAT_CONNECTION; - } else { + + /* Signal a connection change to force a reset */ + *status |= USB_PORT_STAT_CONNECTION; + } else if (pls == XDEV_RESUME) { /* - * 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. + * Resume is an internal xHCI-only state and must not be exposed + * to usbcore. Report it as U3 so transfers are blocked. */ - if (pls == XDEV_RESUME) { - *status |= USB_SS_PORT_LS_U3; - return; - } - + pls = USB_SS_PORT_LS_U3; + } else if (pls == XDEV_COMP_MODE) { /* - * 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. + * 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) && - (pls == USB_SS_PORT_LS_COMP_MOD)) - pls |= USB_PORT_STAT_CONNECTION; + if (xhci->quirks & XHCI_COMP_MODE_QUIRK) + *status |= USB_PORT_STAT_CONNECTION; } /* update status field */ @@ -913,17 +894,16 @@ 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 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; 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 +921,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 +934,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 +948,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 +985,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,21 +994,21 @@ 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; } -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 */ @@ -1153,10 +1133,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 portsc, unsigned long *flags) __releases(&xhci->lock) __acquires(&xhci->lock) { @@ -1165,38 +1143,37 @@ 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) + 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 << wIndex)) + if (bus_state->port_c_suspend & (1 << portnum)) status |= USB_PORT_STAT_C_SUSPEND << 16; return status; @@ -1208,23 +1185,23 @@ 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 portsc, portpmsc, 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; + u8 desc_type; 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) { @@ -1233,13 +1210,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; @@ -1248,7 +1225,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) @@ -1258,29 +1236,28 @@ 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]; - temp = xhci_portsc_readl(port); - if (temp == ~(u32)0) { + port = ports[portnum]; + 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, wIndex, 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, portnum1, 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 */ - if (wValue == 0x02) { + if (wValue == HUB_EXT_PORT_STATUS) { u32 port_li; if (hcd->speed < HCD_USB31 || wLength != 8) { @@ -1289,38 +1266,28 @@ 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; 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; - - 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--; - temp = xhci_portsc_readl(port); - if (temp == ~(u32)0) { + port = ports[portnum]; + 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); @@ -1331,11 +1298,11 @@ 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, portnum1); + hcd->self.busnum, portnum + 1); goto error; } @@ -1354,34 +1321,35 @@ 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); - bus_state->suspended_ports |= 1 << wIndex; + portsc = xhci_portsc_readl(port); + bus_state->suspended_ports |= 1 << portnum; break; case USB_PORT_FEAT_LINK_STATE: - temp = xhci_portsc_readl(port); + link_state = (wIndex & 0xff00) >> 3; + 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, portnum1); - temp = xhci_port_state_to_neutral(temp); + hcd->self.busnum, portnum + 1); + 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; } /* 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); - xhci_set_link_state(xhci, port, link_state); - temp = xhci_portsc_readl(port); + hcd->self.busnum, portnum + 1); + xhci_set_link_state(xhci, port, XDEV_RXDETECT); + portsc = xhci_portsc_readl(port); break; } @@ -1405,27 +1373,27 @@ 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; } xhci_dbg(xhci, "Enable compliance mode transition for port %d-%d\n", - hcd->self.busnum, portnum1); - xhci_set_link_state(xhci, port, link_state); + 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; } /* 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; } @@ -1437,7 +1405,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 */ @@ -1450,7 +1418,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; @@ -1460,9 +1428,9 @@ 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); + portsc = xhci_portsc_readl(port); break; } @@ -1476,17 +1444,17 @@ 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); - 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); - bus_state->suspended_ports |= 1 << wIndex; + portsc = xhci_portsc_readl(port); + bus_state->suspended_ports |= 1 << portnum; } break; case USB_PORT_FEAT_POWER: @@ -1499,93 +1467,98 @@ 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, portnum1, 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, portnum1, 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) goto error; - temp = readl(&port->port_reg->portpmsc); - temp &= ~PORT_U1_TIMEOUT_MASK; - temp |= PORT_U1_TIMEOUT(timeout); - writel(temp, &port->port_reg->portpmsc); + + timeout = (wIndex & 0xff00) >> 8; + 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; - temp = readl(&port->port_reg->portpmsc); - temp &= ~PORT_U2_TIMEOUT_MASK; - temp |= PORT_U2_TIMEOUT(timeout); - writel(temp, &port->port_reg->portpmsc); + + timeout = (wIndex & 0xff00) >> 8; + 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) */ 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; - retval = xhci_enter_test_mode(xhci, test_mode, wIndex, - &flags); + retval = xhci_enter_test_mode(xhci, test_mode, portnum, &flags); break; default: goto error; } /* unblock any posted writes */ - temp = xhci_portsc_readl(port); + portsc = 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--; - temp = xhci_portsc_readl(port); - if (temp == ~(u32)0) { + port = ports[portnum]; + 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(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 +1567,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 +1576,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, port, portsc); break; case USB_PORT_FEAT_ENABLE: xhci_disable_port(xhci, port); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 75bc1eb98b76..997fe90f54e5 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 */ @@ -930,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; @@ -988,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++) { @@ -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) | @@ -1895,10 +1904,36 @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup } EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter); +/* Cleanup roothub bandwidth data */ +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 +1952,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 +1985,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; @@ -1992,7 +2009,6 @@ no_bw: 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; } @@ -2149,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) @@ -2253,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; } @@ -2362,6 +2369,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 */ @@ -2403,6 +2412,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". @@ -2413,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); /* @@ -2474,15 +2485,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); - - /* - * 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"); @@ -2501,6 +2503,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-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]; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1cbefee3c4ca..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; } @@ -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); diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 3f6aa2440b05..d2214d309e96 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); @@ -2566,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", @@ -2604,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, }, @@ -2681,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, diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index ef6d8662adec..a54f5b57f205 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -536,34 +536,24 @@ 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__); - 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) - return retval; /* 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 + * 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); @@ -577,6 +567,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; @@ -587,7 +578,6 @@ static int xhci_init(struct usb_hcd *hcd) } xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished %s", __func__); - return 0; } /*-------------------------------------------------------------------------*/ @@ -967,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; @@ -1017,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); @@ -1032,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); @@ -1069,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); @@ -1083,10 +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 comp_timer_running = false; bool pending_portevent = false; bool suspended_usb3_devs = false; + bool reset_registers = false; if (!hcd->state) return 0; @@ -1105,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. @@ -1141,19 +1142,16 @@ 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; + 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); @@ -1177,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: + 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, "Initialize the xhci_hcd\n"); - retval = xhci_init(hcd); - if (retval) - return retval; - comp_timer_running = true; - xhci_dbg(xhci, "Start the primary HCD\n"); retval = xhci_run(hcd); if (!retval && xhci->shared_hcd) { @@ -1241,40 +1245,36 @@ 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); } -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)); @@ -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); @@ -3285,7 +3292,6 @@ rescan: 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); } @@ -5521,12 +5527,21 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); } - xhci_dbg(xhci, "Calling HCD init\n"); - /* Initialize HCD and host controller data structures. */ - retval = xhci_init(hcd); + 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)); + + /* 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); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 2b0796f6d00e..aeecd301f207 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 <linux/bits.h> #include <linux/usb.h> #include <linux/timer.h> #include <linux/kernel.h> @@ -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 */ @@ -1792,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, @@ -1803,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, @@ -1823,6 +1826,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); diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 45dc209f5fe5..1f0b3c44d388 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: @@ -170,25 +169,13 @@ 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) -#define MTS_MESSAGE(x...) \ - printk( KERN_INFO MTS_NAME x ) - #if defined MTS_DO_DEBUG #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 +184,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 +302,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 +317,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 +327,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); @@ -386,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); } @@ -452,12 +435,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 +503,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 +543,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); @@ -601,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)) @@ -666,59 +643,47 @@ 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 ) { - 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; } 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 { + 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 { + 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; } @@ -744,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, 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); } 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 <linux/init.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/hid.h> #include <linux/usb.h> #include <linux/backlight.h> #include <linux/timer.h> @@ -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, 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"); 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"); diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 18670dfed2e7..22504c0a2841 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -21,6 +21,7 @@ #include <linux/sched.h> #include <linux/mutex.h> #include <linux/poll.h> +#include <linux/hid.h> #include <linux/usb/iowarrior.h> #define DRIVER_AUTHOR "Christian Lucht <lucht@codemercs.com>" @@ -74,7 +75,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 */ @@ -99,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, @@ -114,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, @@ -234,8 +231,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); @@ -246,7 +242,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 +292,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))) { @@ -367,20 +359,21 @@ 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; 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; @@ -443,8 +436,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, @@ -457,7 +449,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; @@ -483,8 +474,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: @@ -523,9 +513,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 +658,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 +760,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 +876,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: diff --git a/drivers/usb/misc/onboard_usb_dev.c b/drivers/usb/misc/onboard_usb_dev.c index ba37eb99efba..7cdbdfe07a76 100644 --- a/drivers/usb/misc/onboard_usb_dev.c +++ b/drivers/usb/misc/onboard_usb_dev.c @@ -565,11 +565,13 @@ 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 #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 @@ -649,6 +651,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 */ @@ -673,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 1a1e86e60e04..ac1aa3e122ad 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, @@ -80,17 +73,17 @@ static const struct onboard_dev_pdata bison_intcamera_data = { .is_hub = false, }; -static const struct onboard_dev_pdata cypress_hx3_data = { +static const struct onboard_dev_pdata corechips_sl6341_data = { .reset_us = 10000, .num_supplies = 2, - .supply_names = { "vdd", "vdd2" }, + .supply_names = { "vdd1v1", "vdd3v3" }, .is_hub = true, }; -static const struct onboard_dev_pdata cypress_hx2vl_data = { - .reset_us = 1, - .num_supplies = 1, - .supply_names = { "vdd" }, +static const struct onboard_dev_pdata cypress_hx3_data = { + .reset_us = 10000, + .num_supplies = 2, + .supply_names = { "vdd", "vdd2" }, .is_hub = true, }; @@ -108,6 +101,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 +128,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, }, @@ -137,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, }, @@ -153,12 +152,15 @@ 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, }, { .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, }, {} }; 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); } 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); } 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"); } diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c index a8af7615b1bf..b7d3c44b970e 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 @@ probe_abort: 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) 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; 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); } 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 */ diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 313612114db9..c71461893d20 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) */ 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); } 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, /* 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 <mail@vacharakis.de> - * and Thomas Weißschuh <linux@weissschuh.net> + * Reported by DocMAX <mail@vacharakis.de>, + * Thomas Weißschuh <linux@weissschuh.net> + * and Daniel Brát <danek.brat@gmail.com> */ -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, diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 58fb97ea6877..db5e4a4c0a99 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)); @@ -265,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 @@ -281,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); } @@ -306,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) { 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: 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..889c4c29c1b8 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 <drm/bridge/aux-bridge.h> #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/errno.h> @@ -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); @@ -1755,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; @@ -1764,6 +1774,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: diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 2a951c585e92..0148b8f50412 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -7,6 +7,7 @@ #include <linux/bitfield.h> #include <linux/delay.h> +#include <linux/gpio/consumer.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/i2c.h> @@ -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, @@ -999,7 +1017,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" }, 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 <linux/kernel.h> #include <linux/module.h> #include <linux/regmap.h> +#include <linux/regulator/consumer.h> #include <linux/usb/pd.h> #include <linux/usb/tcpci.h> #include <linux/usb/tcpm.h> @@ -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; + } + + 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; + return ret < 0 ? ret : 1; } static void process_power_status(struct max_tcpci_chip *chip) diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c index 88c50b984e8a..4b3e4e22a82e 100644 --- a/drivers/usb/typec/tcpm/tcpci_rt1711h.c +++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c @@ -18,11 +18,6 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> -#define RT1711H_VID 0x29CF -#define RT1711H_PID 0x1711 -#define RT1711H_DID 0x2171 -#define RT1715_DID 0x2173 - #define RT1711H_PHYCTRL1 0x80 #define RT1711H_PHYCTRL2 0x81 @@ -55,7 +50,6 @@ struct rt1711h_chip_info { u32 rxdz_sel; - u16 did; bool enable_pd30_extended_message; }; @@ -301,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 != RT1711H_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; @@ -346,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)) @@ -406,16 +365,15 @@ static void rt1711h_remove(struct i2c_client *client) } static const struct rt1711h_chip_info rt1711h = { - .did = RT1711H_DID, }; static const struct rt1711h_chip_info rt1715 = { .rxdz_sel = RT1711H_BMCIO_RXDZSEL, - .did = RT1715_DID, .enable_pd30_extended_message = true, }; static const struct i2c_device_id rt1711h_id[] = { + { "et7304", (kernel_ulong_t)&rt1715 }, { "rt1711h", (kernel_ulong_t)&rt1711h }, { "rt1715", (kernel_ulong_t)&rt1715 }, {} @@ -423,6 +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 = &rt1715 }, { .compatible = "richtek,rt1711h", .data = &rt1711h }, { .compatible = "richtek,rt1715", .data = &rt1715 }, {} diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 8e0e14a2704e..dfbb94ddc98a 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -12,6 +12,7 @@ #include <linux/jiffies.h> #include <linux/kernel.h> #include <linux/kthread.h> +#include <linux/minmax.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/power_supply.h> @@ -61,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), \ @@ -188,7 +190,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 +232,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 { @@ -305,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; @@ -337,6 +386,47 @@ 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; +}; + +enum aug_req_type { + PD_PPS, + PD_SPR_AVS, +}; + struct tcpm_port { struct device *dev; @@ -499,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; @@ -585,6 +680,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 */ @@ -605,9 +703,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[] = { @@ -725,7 +823,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 +939,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", @@ -1367,6 +1465,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) { @@ -1653,7 +1809,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 +1817,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: @@ -3194,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]); @@ -3365,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); } } @@ -3468,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. */ @@ -3478,6 +3626,20 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, 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: port->swap_status = (type == PD_CTRL_WAIT ? -EAGAIN : -EOPNOTSUPP); @@ -3530,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: @@ -3542,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); @@ -3655,6 +3825,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 ? @@ -3907,6 +4090,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; } @@ -4016,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: @@ -4056,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"); @@ -4077,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; } @@ -4127,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; @@ -4294,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; @@ -4523,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); @@ -4562,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; } @@ -5055,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); @@ -5286,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 @@ -5308,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); @@ -5344,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); @@ -5531,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) { @@ -5565,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 @@ -5703,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. @@ -5852,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: @@ -6826,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; @@ -6834,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; } @@ -6844,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; - } - - target_mw = (req_op_curr * port->supply_voltage) / 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 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; @@ -6887,7 +7291,7 @@ swap_unlock: 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; @@ -6895,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; } @@ -6905,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; @@ -6974,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) { @@ -6985,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; @@ -7145,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); @@ -7291,6 +7776,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; @@ -7464,6 +8072,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); @@ -7506,6 +8117,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; } @@ -7536,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[] = { @@ -7554,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 { @@ -7569,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; @@ -7580,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; @@ -7599,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; @@ -7674,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_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_PROG_ONLINE: - ret = tcpm_pps_activate(port, true); + 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; @@ -7713,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; @@ -7764,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; @@ -7859,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/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); 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 <linux/acpi.h> #include <linux/container_of.h> #include <linux/dev_printk.h> #include <linux/jiffies.h> #include <linux/mod_devicetable.h> #include <linux/module.h> +#include <linux/of.h> #include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_usbpd_notify.h> #include <linux/platform_data/cros_ec_proto.h> @@ -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, diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 46262ee0d192..5b7ad9e99cb9 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -241,6 +241,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; } @@ -1186,10 +1188,18 @@ 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); } - /* 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 11b3e24e34e2..12e07b9fe622 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -371,14 +371,18 @@ 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, }, + { .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, }, { .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, }, {} }; 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, 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 @@ call_put_busid_priv: 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 */ diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c index a2b2da1255dd..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; } @@ -662,19 +674,29 @@ 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; + u32 total_length = 0; 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; + } + + size = np * sizeof(*iso); - buff = kzalloc(size, GFP_KERNEL); + buff = kcalloc(np, sizeof(*iso), GFP_KERNEL); if (!buff) return -ENOMEM; @@ -696,14 +718,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) @@ -751,6 +782,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); 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; diff --git a/include/dt-bindings/usb/pd.h b/include/dt-bindings/usb/pd.h index e6526b138174..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" @@ -465,4 +483,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 */ 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/include/linux/mfd/max77759.h b/include/linux/mfd/max77759.h index c6face34e385..ec19be952877 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 - \ @@ -102,6 +132,84 @@ #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 * * @regmap_top: Regmap for accessing TOP registers 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 */ }; diff --git a/include/linux/usb.h b/include/linux/usb.h index 4aab20015851..25a203ac7a7e 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -55,7 +55,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 @@ -2081,6 +2082,8 @@ static inline int usb_translate_errors(int error_code) case -ENODEV: case -EOPNOTSUPP: return error_code; + case -ENOSPC: + return -EBUSY; default: return -EIO; } 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; diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index 6ccd1b2af993..337a5485af7c 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,32 @@ 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 + +#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) { return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK; @@ -339,6 +433,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; @@ -582,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, 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. diff --git a/include/uapi/linux/usb/ch9.h b/include/uapi/linux/usb/ch9.h index 8003243a4937..c3e593378377 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 @@ -121,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 @@ -1147,6 +1151,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 */ 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 @@ -242,6 +242,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 * @val: value for which the selector is searched 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; } |
