diff options
80 files changed, 2724 insertions, 558 deletions
diff --git a/Documentation/devicetree/bindings/connector/usb-connector.yaml b/Documentation/devicetree/bindings/connector/usb-connector.yaml index 67700440e23b..11e40d225b9f 100644 --- a/Documentation/devicetree/bindings/connector/usb-connector.yaml +++ b/Documentation/devicetree/bindings/connector/usb-connector.yaml @@ -293,6 +293,13 @@ properties: PD negotiation till BC1.2 detection completes. default: 0 + pd-revision: + description: Specifies the maximum USB PD revision and version supported by + the connector. This property is specified in the following order; + <revision_major, revision_minor, version_major, version_minor>. + $ref: /schemas/types.yaml#/definitions/uint8-array + maxItems: 4 + dependencies: sink-vdos-v1: [ sink-vdos ] sink-vdos: [ sink-vdos-v1 ] diff --git a/Documentation/devicetree/bindings/usb/aspeed,usb-vhub.yaml b/Documentation/devicetree/bindings/usb/aspeed,usb-vhub.yaml index a86bcd95100e..7f22f9c031b2 100644 --- a/Documentation/devicetree/bindings/usb/aspeed,usb-vhub.yaml +++ b/Documentation/devicetree/bindings/usb/aspeed,usb-vhub.yaml @@ -113,27 +113,27 @@ examples: - | #include <dt-bindings/clock/aspeed-clock.h> vhub: usb-vhub@1e6a0000 { - compatible = "aspeed,ast2500-usb-vhub"; - reg = <0x1e6a0000 0x300>; - interrupts = <5>; - clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; - aspeed,vhub-downstream-ports = <5>; - aspeed,vhub-generic-endpoints = <15>; - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_usb2ad_default>; - - vhub-vendor-id = <0x1d6b>; - vhub-product-id = <0x0107>; - vhub-device-revision = <0x0100>; - vhub-strings { - #address-cells = <1>; - #size-cells = <0>; - - string@409 { - reg = <0x409>; - manufacturer = "ASPEED"; - product = "USB Virtual Hub"; - serial-number = "0000"; - }; + compatible = "aspeed,ast2500-usb-vhub"; + reg = <0x1e6a0000 0x300>; + interrupts = <5>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + aspeed,vhub-downstream-ports = <5>; + aspeed,vhub-generic-endpoints = <15>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2ad_default>; + + vhub-vendor-id = <0x1d6b>; + vhub-product-id = <0x0107>; + vhub-device-revision = <0x0100>; + vhub-strings { + #address-cells = <1>; + #size-cells = <0>; + + string@409 { + reg = <0x409>; + manufacturer = "ASPEED"; + product = "USB Virtual Hub"; + serial-number = "0000"; }; + }; }; diff --git a/Documentation/devicetree/bindings/usb/brcm,bdc.yaml b/Documentation/devicetree/bindings/usb/brcm,bdc.yaml index 9e561fee98f1..f9375c69e86b 100644 --- a/Documentation/devicetree/bindings/usb/brcm,bdc.yaml +++ b/Documentation/devicetree/bindings/usb/brcm,bdc.yaml @@ -41,10 +41,10 @@ additionalProperties: false examples: - | - usb@f0b02000 { - compatible = "brcm,bdc-udc-v2"; - reg = <0xf0b02000 0xfc4>; - interrupts = <0x0 0x60 0x0>; - phys = <&usbphy_0 0x0>; - clocks = <&sw_usbd>; - }; + usb@f0b02000 { + compatible = "brcm,bdc-udc-v2"; + reg = <0xf0b02000 0xfc4>; + interrupts = <0x0 0x60 0x0>; + phys = <&usbphy_0 0x0>; + clocks = <&sw_usbd>; + }; diff --git a/Documentation/devicetree/bindings/usb/cypress,hx3.yaml b/Documentation/devicetree/bindings/usb/cypress,hx3.yaml index e44e88d993d0..1033b7a4b8f9 100644 --- a/Documentation/devicetree/bindings/usb/cypress,hx3.yaml +++ b/Documentation/devicetree/bindings/usb/cypress,hx3.yaml @@ -56,21 +56,21 @@ examples: /* 2.0 hub on port 1 */ hub_2_0: hub@1 { - compatible = "usb4b4,6504"; - reg = <1>; - peer-hub = <&hub_3_0>; - reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; - vdd-supply = <®_1v2_usb>; - vdd2-supply = <®_3v3_usb>; + compatible = "usb4b4,6504"; + reg = <1>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; + vdd-supply = <®_1v2_usb>; + vdd2-supply = <®_3v3_usb>; }; /* 3.0 hub on port 2 */ hub_3_0: hub@2 { - compatible = "usb4b4,6506"; - reg = <2>; - peer-hub = <&hub_2_0>; - reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; - vdd-supply = <®_1v2_usb>; - vdd2-supply = <®_3v3_usb>; + compatible = "usb4b4,6506"; + reg = <2>; + peer-hub = <&hub_2_0>; + reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; + vdd-supply = <®_1v2_usb>; + vdd2-supply = <®_3v3_usb>; }; }; diff --git a/Documentation/devicetree/bindings/usb/dwc2.yaml b/Documentation/devicetree/bindings/usb/dwc2.yaml index a5f2e3442a0e..e83d30a91b88 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.yaml +++ b/Documentation/devicetree/bindings/usb/dwc2.yaml @@ -192,7 +192,7 @@ unevaluatedProperties: false examples: - | - usb@101c0000 { + usb@101c0000 { compatible = "rockchip,rk3066-usb", "snps,dwc2"; reg = <0x10180000 0x40000>; interrupts = <18>; @@ -200,6 +200,6 @@ examples: clock-names = "otg"; phys = <&usbphy>; phy-names = "usb2-phy"; - }; + }; ... diff --git a/Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml b/Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml index 8b25b9a01ced..e3a7df91f7f1 100644 --- a/Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml +++ b/Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml @@ -87,21 +87,21 @@ examples: #size-cells = <0>; typec-mux@42 { - compatible = "fcs,fsa4480"; - reg = <0x42>; + compatible = "fcs,fsa4480"; + reg = <0x42>; - interrupts-extended = <&tlmm 2 IRQ_TYPE_LEVEL_LOW>; + interrupts-extended = <&tlmm 2 IRQ_TYPE_LEVEL_LOW>; - vcc-supply = <&vreg_bob>; + vcc-supply = <&vreg_bob>; - mode-switch; - orientation-switch; + mode-switch; + orientation-switch; - port { - fsa4480_ept: endpoint { - remote-endpoint = <&typec_controller>; + port { + fsa4480_ept: endpoint { + remote-endpoint = <&typec_controller>; + }; }; - }; }; }; ... diff --git a/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml b/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml index 8a5f837eff94..e588514fab2d 100644 --- a/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml +++ b/Documentation/devicetree/bindings/usb/gpio-sbu-mux.yaml @@ -20,6 +20,7 @@ properties: items: - enum: - nxp,cbdtu02043 + - onnn,fsusb42 - onnn,fsusb43l10x - pericom,pi3usb102 - ti,tmuxhs4212 diff --git a/Documentation/devicetree/bindings/usb/intel,keembay-dwc3.yaml b/Documentation/devicetree/bindings/usb/intel,keembay-dwc3.yaml index d3511f48cd55..1a75544a8c31 100644 --- a/Documentation/devicetree/bindings/usb/intel,keembay-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/intel,keembay-dwc3.yaml @@ -58,20 +58,20 @@ examples: #define KEEM_BAY_A53_AUX_USB_SUSPEND usb { - compatible = "intel,keembay-dwc3"; - clocks = <&scmi_clk KEEM_BAY_A53_AUX_USB>, - <&scmi_clk KEEM_BAY_A53_AUX_USB_REF>, - <&scmi_clk KEEM_BAY_A53_AUX_USB_ALT_REF>, - <&scmi_clk KEEM_BAY_A53_AUX_USB_SUSPEND>; - clock-names = "async_master", "ref", "alt_ref", "suspend"; - ranges; - #address-cells = <1>; - #size-cells = <1>; - - usb@34000000 { - compatible = "snps,dwc3"; - reg = <0x34000000 0x10000>; - interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>; - dr_mode = "peripheral"; - }; + compatible = "intel,keembay-dwc3"; + clocks = <&scmi_clk KEEM_BAY_A53_AUX_USB>, + <&scmi_clk KEEM_BAY_A53_AUX_USB_REF>, + <&scmi_clk KEEM_BAY_A53_AUX_USB_ALT_REF>, + <&scmi_clk KEEM_BAY_A53_AUX_USB_SUSPEND>; + clock-names = "async_master", "ref", "alt_ref", "suspend"; + ranges; + #address-cells = <1>; + #size-cells = <1>; + + usb@34000000 { + compatible = "snps,dwc3"; + reg = <0x34000000 0x10000>; + interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>; + dr_mode = "peripheral"; + }; }; diff --git a/Documentation/devicetree/bindings/usb/ite,it5205.yaml b/Documentation/devicetree/bindings/usb/ite,it5205.yaml index 36ec4251b5f2..889710733de5 100644 --- a/Documentation/devicetree/bindings/usb/ite,it5205.yaml +++ b/Documentation/devicetree/bindings/usb/ite,it5205.yaml @@ -54,19 +54,19 @@ examples: #size-cells = <0>; typec-mux@48 { - compatible = "ite,it5205"; - reg = <0x48>; + compatible = "ite,it5205"; + reg = <0x48>; - mode-switch; - orientation-switch; + mode-switch; + orientation-switch; - vcc-supply = <&mt6359_vibr_ldo_reg>; + vcc-supply = <&mt6359_vibr_ldo_reg>; - port { - it5205_usbss_sbu: endpoint { - remote-endpoint = <&typec_controller>; + port { + it5205_usbss_sbu: endpoint { + remote-endpoint = <&typec_controller>; + }; }; - }; }; }; ... diff --git a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml index 20b62228371b..3de4dc40b791 100644 --- a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml +++ b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml @@ -13,8 +13,12 @@ description: Maxim TCPCI Type-C PD controller properties: compatible: - enum: - - maxim,max33359 + oneOf: + - enum: + - maxim,max33359 + - items: + - const: maxim,max77759-tcpci + - const: maxim,max33359 reg: maxItems: 1 @@ -70,6 +74,7 @@ examples: PDO_FIXED_DUAL_ROLE) PDO_FIXED(9000, 2000, 0)>; sink-bc12-completion-time-ms = <500>; + pd-revision = /bits/ 8 <0x03 0x01 0x01 0x08>; }; }; }; diff --git a/Documentation/devicetree/bindings/usb/maxim,max3420-udc.yaml b/Documentation/devicetree/bindings/usb/maxim,max3420-udc.yaml index 8e0f4ecc010d..6edb1fc5044e 100644 --- a/Documentation/devicetree/bindings/usb/maxim,max3420-udc.yaml +++ b/Documentation/devicetree/bindings/usb/maxim,max3420-udc.yaml @@ -50,18 +50,18 @@ additionalProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> - #include <dt-bindings/interrupt-controller/irq.h> - spi { - #address-cells = <1>; - #size-cells = <0>; + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + spi { + #address-cells = <1>; + #size-cells = <0>; - udc@0 { - compatible = "maxim,max3420-udc"; - reg = <0>; - interrupt-parent = <&gpio>; - interrupts = <0 IRQ_TYPE_EDGE_FALLING>, <10 IRQ_TYPE_EDGE_BOTH>; - interrupt-names = "udc", "vbus"; - spi-max-frequency = <12500000>; - }; - }; + udc@0 { + compatible = "maxim,max3420-udc"; + reg = <0>; + interrupt-parent = <&gpio>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>, <10 IRQ_TYPE_EDGE_BOTH>; + interrupt-names = "udc", "vbus"; + spi-max-frequency = <12500000>; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra210-xusb.yaml b/Documentation/devicetree/bindings/usb/nvidia,tegra210-xusb.yaml index 90296613b3a5..c0e313c70bba 100644 --- a/Documentation/devicetree/bindings/usb/nvidia,tegra210-xusb.yaml +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra210-xusb.yaml @@ -189,7 +189,7 @@ examples: #size-cells = <0>; ethernet@1 { - compatible = "usb955,9ff"; - reg = <1>; + compatible = "usb955,9ff"; + reg = <1>; }; }; diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index 935e204b607b..8fd02e8aaaa5 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -26,6 +26,7 @@ properties: - qcom,msm8998-dwc3 - qcom,qcm2290-dwc3 - qcom,qcs404-dwc3 + - qcom,qcs615-dwc3 - qcom,qcs8300-dwc3 - qcom,qdu1000-dwc3 - qcom,sa8775p-dwc3 @@ -341,6 +342,7 @@ allOf: contains: enum: - qcom,qcm2290-dwc3 + - qcom,qcs615-dwc3 - qcom,sar2130p-dwc3 - qcom,sc8180x-dwc3 - qcom,sc8180x-dwc3-mp @@ -453,8 +455,10 @@ allOf: then: properties: interrupts: + minItems: 3 maxItems: 4 interrupt-names: + minItems: 3 items: - const: pwr_event - const: dp_hs_phy_irq @@ -469,6 +473,7 @@ allOf: - qcom,ipq4019-dwc3 - qcom,ipq8064-dwc3 - qcom,msm8994-dwc3 + - qcom,qcs615-dwc3 - qcom,qcs8300-dwc3 - qcom,qdu1000-dwc3 - qcom,sa8775p-dwc3 diff --git a/Documentation/devicetree/bindings/usb/renesas,rzv2m-usb3drd.yaml b/Documentation/devicetree/bindings/usb/renesas,rzv2m-usb3drd.yaml index ff625600d9af..b87e139c29e5 100644 --- a/Documentation/devicetree/bindings/usb/renesas,rzv2m-usb3drd.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,rzv2m-usb3drd.yaml @@ -104,26 +104,26 @@ examples: #size-cells = <1>; usb3host: usb@85060000 { - compatible = "renesas,r9a09g011-xhci", - "renesas,rzv2m-xhci"; - reg = <0x85060000 0x2000>; - interrupts = <GIC_SPI 245 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_H>, - <&cpg CPG_MOD R9A09G011_USB_PCLK>; - clock-names = "axi", "reg"; - power-domains = <&cpg>; - resets = <&cpg R9A09G011_USB_ARESETN_H>; + compatible = "renesas,r9a09g011-xhci", + "renesas,rzv2m-xhci"; + reg = <0x85060000 0x2000>; + interrupts = <GIC_SPI 245 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_H>, + <&cpg CPG_MOD R9A09G011_USB_PCLK>; + clock-names = "axi", "reg"; + power-domains = <&cpg>; + resets = <&cpg R9A09G011_USB_ARESETN_H>; }; usb3peri: usb3peri@85070000 { - compatible = "renesas,r9a09g011-usb3-peri", - "renesas,rzv2m-usb3-peri"; - reg = <0x85070000 0x400>; - interrupts = <GIC_SPI 246 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_P>, - <&cpg CPG_MOD R9A09G011_USB_PCLK>; - clock-names = "axi", "reg"; - power-domains = <&cpg>; - resets = <&cpg R9A09G011_USB_ARESETN_P>; + compatible = "renesas,r9a09g011-usb3-peri", + "renesas,rzv2m-usb3-peri"; + reg = <0x85070000 0x400>; + interrupts = <GIC_SPI 246 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&cpg CPG_MOD R9A09G011_USB_ACLK_P>, + <&cpg CPG_MOD R9A09G011_USB_PCLK>; + clock-names = "axi", "reg"; + power-domains = <&cpg>; + resets = <&cpg R9A09G011_USB_ARESETN_P>; }; }; diff --git a/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml b/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml index b2b811a0ade8..4e56e4ffeaf2 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usb3-peri.yaml @@ -132,19 +132,19 @@ examples: usb-role-switch; ports { - #address-cells = <1>; - #size-cells = <0>; - port@0 { - reg = <0>; - usb3_hs_ep: endpoint { - remote-endpoint = <&hs_ep>; - }; + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + usb3_hs_ep: endpoint { + remote-endpoint = <&hs_ep>; }; - port@1 { - reg = <1>; - usb3_role_switch: endpoint { - remote-endpoint = <&hd3ss3220_out_ep>; - }; + }; + port@1 { + reg = <1>; + usb3_role_switch: endpoint { + remote-endpoint = <&hd3ss3220_out_ep>; }; + }; }; }; diff --git a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml index b23ef29bf794..980f325341d4 100644 --- a/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml +++ b/Documentation/devicetree/bindings/usb/renesas,usbhs.yaml @@ -26,6 +26,7 @@ properties: - renesas,usbhs-r9a07g043 # RZ/G2UL and RZ/Five - renesas,usbhs-r9a07g044 # RZ/G2{L,LC} - renesas,usbhs-r9a07g054 # RZ/V2L + - renesas,usbhs-r9a08g045 # RZ/G3S - const: renesas,rzg2l-usbhs - items: @@ -130,6 +131,7 @@ allOf: - renesas,usbhs-r9a07g043 - renesas,usbhs-r9a07g044 - renesas,usbhs-r9a07g054 + - renesas,usbhs-r9a08g045 then: properties: interrupts: diff --git a/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml b/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml index 54c6586cb56d..bec1c8047bc0 100644 --- a/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml +++ b/Documentation/devicetree/bindings/usb/ti,hd3ss3220.yaml @@ -56,26 +56,26 @@ examples: #size-cells = <0>; hd3ss3220@47 { - compatible = "ti,hd3ss3220"; - reg = <0x47>; - interrupt-parent = <&gpio6>; - interrupts = <3>; + compatible = "ti,hd3ss3220"; + reg = <0x47>; + interrupt-parent = <&gpio6>; + interrupts = <3>; - ports { - #address-cells = <1>; - #size-cells = <0>; - port@0 { - reg = <0>; - hd3ss3220_in_ep: endpoint { - remote-endpoint = <&ss_ep>; - }; - }; - port@1 { - reg = <1>; - hd3ss3220_out_ep: endpoint { - remote-endpoint = <&usb3_role_switch>; - }; - }; + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + hd3ss3220_in_ep: endpoint { + remote-endpoint = <&ss_ep>; + }; }; + port@1 { + reg = <1>; + hd3ss3220_out_ep: endpoint { + remote-endpoint = <&usb3_role_switch>; + }; + }; + }; }; }; diff --git a/Documentation/devicetree/bindings/usb/ti,tusb73x0-pci.yaml b/Documentation/devicetree/bindings/usb/ti,tusb73x0-pci.yaml index ddda734f36fb..c4a91b3d6612 100644 --- a/Documentation/devicetree/bindings/usb/ti,tusb73x0-pci.yaml +++ b/Documentation/devicetree/bindings/usb/ti,tusb73x0-pci.yaml @@ -48,8 +48,8 @@ examples: device_type = "pci"; usb@0 { - compatible = "pci104c,8241"; - reg = <0x0 0x0 0x0 0x0 0x0>; - ti,pwron-active-high; + compatible = "pci104c,8241"; + reg = <0x0 0x0 0x0 0x0 0x0>; + ti,pwron-active-high; }; }; diff --git a/Documentation/devicetree/bindings/usb/ti,usb8020b.yaml b/Documentation/devicetree/bindings/usb/ti,usb8020b.yaml index 8ef117793e11..61217da8b2f3 100644 --- a/Documentation/devicetree/bindings/usb/ti,usb8020b.yaml +++ b/Documentation/devicetree/bindings/usb/ti,usb8020b.yaml @@ -51,19 +51,19 @@ examples: /* 2.0 hub on port 1 */ hub_2_0: hub@1 { - compatible = "usb451,8027"; - reg = <1>; - peer-hub = <&hub_3_0>; - reset-gpios = <&pio 7 GPIO_ACTIVE_HIGH>; - vdd-supply = <&usb_hub_fixed_3v3>; + compatible = "usb451,8027"; + reg = <1>; + peer-hub = <&hub_3_0>; + reset-gpios = <&pio 7 GPIO_ACTIVE_HIGH>; + vdd-supply = <&usb_hub_fixed_3v3>; }; /* 3.0 hub on port 2 */ hub_3_0: hub@2 { - compatible = "usb451,8025"; - reg = <2>; - peer-hub = <&hub_2_0>; - reset-gpios = <&pio 7 GPIO_ACTIVE_HIGH>; - vdd-supply = <&usb_hub_fixed_3v3>; + compatible = "usb451,8025"; + reg = <2>; + peer-hub = <&hub_2_0>; + reset-gpios = <&pio 7 GPIO_ACTIVE_HIGH>; + vdd-supply = <&usb_hub_fixed_3v3>; }; }; diff --git a/Documentation/devicetree/bindings/usb/ti,usb8041.yaml b/Documentation/devicetree/bindings/usb/ti,usb8041.yaml index c2e29bd61e11..bce730a5e237 100644 --- a/Documentation/devicetree/bindings/usb/ti,usb8041.yaml +++ b/Documentation/devicetree/bindings/usb/ti,usb8041.yaml @@ -51,17 +51,17 @@ examples: /* 2.0 hub on port 1 */ hub_2_0: hub@1 { - compatible = "usb451,8142"; - reg = <1>; - peer-hub = <&hub_3_0>; - reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; + compatible = "usb451,8142"; + reg = <1>; + peer-hub = <&hub_3_0>; + reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; }; /* 3.0 hub on port 2 */ hub_3_0: hub@2 { - compatible = "usb451,8140"; - reg = <2>; - peer-hub = <&hub_2_0>; - reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; + compatible = "usb451,8140"; + reg = <2>; + peer-hub = <&hub_2_0>; + reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; }; }; diff --git a/Documentation/usb/usbip_protocol.rst b/Documentation/usb/usbip_protocol.rst index adc158967cc6..3da1df3d94f5 100644 --- a/Documentation/usb/usbip_protocol.rst +++ b/Documentation/usb/usbip_protocol.rst @@ -285,17 +285,17 @@ OP_REP_IMPORT: +-----------+--------+------------+---------------------------------------------------+ | 0x138 | 2 | | bcdDevice | +-----------+--------+------------+---------------------------------------------------+ -| 0x139 | 1 | | bDeviceClass | +| 0x13A | 1 | | bDeviceClass | +-----------+--------+------------+---------------------------------------------------+ -| 0x13A | 1 | | bDeviceSubClass | +| 0x13B | 1 | | bDeviceSubClass | +-----------+--------+------------+---------------------------------------------------+ -| 0x13B | 1 | | bDeviceProtocol | +| 0x13C | 1 | | bDeviceProtocol | +-----------+--------+------------+---------------------------------------------------+ -| 0x13C | 1 | | bConfigurationValue | +| 0x13D | 1 | | bConfigurationValue | +-----------+--------+------------+---------------------------------------------------+ -| 0x13D | 1 | | bNumConfigurations | +| 0x13E | 1 | | bNumConfigurations | +-----------+--------+------------+---------------------------------------------------+ -| 0x13E | 1 | | bNumInterfaces | +| 0x13F | 1 | | bNumInterfaces | +-----------+--------+------------+---------------------------------------------------+ The following four commands have a common basic header called diff --git a/MAINTAINERS b/MAINTAINERS index a87ddad78e26..94cb3791f3f3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5432,9 +5432,12 @@ F: include/linux/platform_data/cros_usbpd_notify.h CHROMEOS EC USB TYPE-C DRIVER M: Prashant Malani <pmalani@chromium.org> +M: Benson Leung <bleung@chromium.org> +M: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> L: chrome-platform@lists.linux.dev S: Maintained F: drivers/platform/chrome/cros_ec_typec.* +F: drivers/platform/chrome/cros_typec_altmode.* F: drivers/platform/chrome/cros_typec_switch.c F: drivers/platform/chrome/cros_typec_vdm.* @@ -5450,6 +5453,13 @@ L: chrome-platform@lists.linux.dev S: Maintained F: drivers/watchdog/cros_ec_wdt.c +CHROMEOS UCSI DRIVER +M: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> +M: Łukasz Bartosik <ukaszb@chromium.org> +L: chrome-platform@lists.linux.dev +S: Maintained +F: drivers/usb/typec/ucsi/cros_ec_ucsi.c + CHRONTEL CH7322 CEC DRIVER M: Joe Tessler <jrt@google.com> L: linux-media@vger.kernel.org diff --git a/arch/arm64/boot/dts/qcom/x1e80100-qcp.dts b/arch/arm64/boot/dts/qcom/x1e80100-qcp.dts index 5ef030c60abe..f45df1396eae 100644 --- a/arch/arm64/boot/dts/qcom/x1e80100-qcp.dts +++ b/arch/arm64/boot/dts/qcom/x1e80100-qcp.dts @@ -84,6 +84,14 @@ remote-endpoint = <&usb_1_ss0_qmpphy_out>; }; }; + + port@2 { + reg = <2>; + + pmic_glink_ss0_sbu: endpoint { + remote-endpoint = <&usb_1_ss0_sbu_mux>; + }; + }; }; }; @@ -112,6 +120,14 @@ remote-endpoint = <&usb_1_ss1_qmpphy_out>; }; }; + + port@2 { + reg = <2>; + + pmic_glink_ss1_sbu: endpoint { + remote-endpoint = <&usb_1_ss1_sbu_mux>; + }; + }; }; }; @@ -140,6 +156,14 @@ remote-endpoint = <&usb_1_ss2_qmpphy_out>; }; }; + + port@2 { + reg = <2>; + + pmic_glink_ss2_sbu: endpoint { + remote-endpoint = <&usb_1_ss2_sbu_mux>; + }; + }; }; }; }; @@ -256,6 +280,63 @@ regulator-boot-on; }; + + usb-1-ss0-sbu-mux { + compatible = "onnn,fsusb42", "gpio-sbu-mux"; + + enable-gpios = <&tlmm 168 GPIO_ACTIVE_LOW>; + select-gpios = <&tlmm 167 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&usb_1_ss0_sbu_default>; + pinctrl-names = "default"; + + mode-switch; + orientation-switch; + + port { + usb_1_ss0_sbu_mux: endpoint { + remote-endpoint = <&pmic_glink_ss0_sbu>; + }; + }; + }; + + usb-1-ss1-sbu-mux { + compatible = "onnn,fsusb42", "gpio-sbu-mux"; + + enable-gpios = <&tlmm 179 GPIO_ACTIVE_LOW>; + select-gpios = <&tlmm 178 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&usb_1_ss1_sbu_default>; + pinctrl-names = "default"; + + mode-switch; + orientation-switch; + + port { + usb_1_ss1_sbu_mux: endpoint { + remote-endpoint = <&pmic_glink_ss1_sbu>; + }; + }; + }; + + usb-1-ss2-sbu-mux { + compatible = "onnn,fsusb42", "gpio-sbu-mux"; + + enable-gpios = <&tlmm 171 GPIO_ACTIVE_LOW>; + select-gpios = <&tlmm 170 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&usb_1_ss2_sbu_default>; + pinctrl-names = "default"; + + mode-switch; + orientation-switch; + + port { + usb_1_ss2_sbu_mux: endpoint { + remote-endpoint = <&pmic_glink_ss2_sbu>; + }; + }; + }; }; &apps_rsc { @@ -630,6 +711,30 @@ status = "okay"; }; +&mdss_dp0 { + status = "okay"; +}; + +&mdss_dp0_out { + data-lanes = <0 1>; +}; + +&mdss_dp1 { + status = "okay"; +}; + +&mdss_dp1_out { + data-lanes = <0 1>; +}; + +&mdss_dp2 { + status = "okay"; +}; + +&mdss_dp2_out { + data-lanes = <0 1>; +}; + &mdss_dp3 { compatible = "qcom,x1e80100-dp"; /delete-property/ #sound-dai-cells; @@ -872,6 +977,79 @@ }; }; + usb_1_ss0_sbu_default: usb-1-ss0-sbu-state { + mode-pins { + pins = "gpio166"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + output-high; + }; + + oe-n-pins { + pins = "gpio168"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + sel-pins { + pins = "gpio167"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + }; + + usb_1_ss1_sbu_default: usb-1-ss1-sbu-state { + mode-pins { + pins = "gpio177"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + output-high; + }; + + oe-n-pins { + pins = "gpio179"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + sel-pins { + pins = "gpio178"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + }; + + usb_1_ss2_sbu_default: usb-1-ss2-sbu-state { + mode-pins { + pins = "gpio169"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + output-high; + }; + + oe-n-pins { + pins = "gpio171"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + + sel-pins { + pins = "gpio170"; + function = "gpio"; + bias-disable; + drive-strength = <2>; + }; + }; + wcd_default: wcd-reset-n-active-state { pins = "gpio191"; function = "gpio"; diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index b7dbaf77b6db..1b2f2bd09662 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -237,12 +237,19 @@ config CROS_EC_SYSFS To compile this driver as a module, choose M here: the module will be called cros_ec_sysfs. +config CROS_EC_TYPEC_ALTMODES + bool + help + Selectable symbol to enable altmodes. + config CROS_EC_TYPEC tristate "ChromeOS EC Type-C Connector Control" depends on MFD_CROS_EC_DEV && TYPEC depends on CROS_USBPD_NOTIFY depends on USB_ROLE_SWITCH default MFD_CROS_EC_DEV + select CROS_EC_TYPEC_ALTMODES if TYPEC_DP_ALTMODE + select CROS_EC_TYPEC_ALTMODES if TYPEC_TBT_ALTMODE help If you say Y here, you get support for accessing Type C connector information from the Chrome OS EC. diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index fb8335458a22..1a5a484563cc 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -19,7 +19,11 @@ obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o obj-$(CONFIG_CROS_EC_UART) += cros_ec_uart.o cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o cros-ec-typec-objs := cros_ec_typec.o cros_typec_vdm.o +ifneq ($(CONFIG_CROS_EC_TYPEC_ALTMODES),) + cros-ec-typec-objs += cros_typec_altmode.o +endif obj-$(CONFIG_CROS_EC_TYPEC) += cros-ec-typec.o + obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index ae2f86296954..6ee182101bc9 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -18,6 +18,7 @@ #include "cros_ec_typec.h" #include "cros_typec_vdm.h" +#include "cros_typec_altmode.h" #define DRV_NAME "cros-ec-typec" @@ -290,30 +291,32 @@ static int cros_typec_register_port_altmodes(struct cros_typec_data *typec, struct typec_altmode *amode; /* All PD capable CrOS devices are assumed to support DP altmode. */ + memset(&desc, 0, sizeof(desc)); desc.svid = USB_TYPEC_DP_SID; desc.mode = USB_TYPEC_DP_MODE; desc.vdo = DP_PORT_VDO; - amode = typec_port_register_altmode(port->port, &desc); + amode = cros_typec_register_displayport(port, &desc, + typec->ap_driven_altmode); if (IS_ERR(amode)) return PTR_ERR(amode); port->port_altmode[CROS_EC_ALTMODE_DP] = amode; - typec_altmode_set_drvdata(amode, port); - amode->ops = &port_amode_ops; /* * Register TBT compatibility alt mode. The EC will not enter the mode - * if it doesn't support it, so it's safe to register it unconditionally - * here for now. + * if it doesn't support it and it will not enter automatically by + * design so we can use the |ap_driven_altmode| feature to check if we + * should register it. */ - memset(&desc, 0, sizeof(desc)); - desc.svid = USB_TYPEC_TBT_SID; - desc.mode = TYPEC_ANY_MODE; - amode = typec_port_register_altmode(port->port, &desc); - if (IS_ERR(amode)) - return PTR_ERR(amode); - port->port_altmode[CROS_EC_ALTMODE_TBT] = amode; - typec_altmode_set_drvdata(amode, port); - amode->ops = &port_amode_ops; + if (typec->ap_driven_altmode) { + memset(&desc, 0, sizeof(desc)); + desc.svid = USB_TYPEC_TBT_SID; + desc.mode = TBT_MODE; + desc.inactive = true; + amode = cros_typec_register_thunderbolt(port, &desc); + if (IS_ERR(amode)) + return PTR_ERR(amode); + port->port_altmode[CROS_EC_ALTMODE_TBT] = amode; + } port->state.alt = NULL; port->state.mode = TYPEC_STATE_USB; @@ -576,6 +579,10 @@ static int cros_typec_enable_dp(struct cros_typec_data *typec, if (!ret) ret = typec_mux_set(port->mux, &port->state); + if (!ret) + ret = cros_typec_displayport_status_update(port->state.alt, + port->state.data); + return ret; } @@ -619,6 +626,7 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, }; struct ec_params_usb_pd_mux_ack mux_ack; enum typec_orientation orientation; + struct cros_typec_altmode_node *node; int ret; ret = cros_ec_cmd(typec->ec, 0, EC_CMD_USB_PD_MUX_INFO, @@ -677,6 +685,14 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, port->mux_flags); } + /* Iterate all partner alt-modes and set the active alternate mode. */ + list_for_each_entry(node, &port->partner_mode_list, list) { + typec_altmode_update_active( + node->amode, + port->state.alt && + node->amode->svid == port->state.alt->svid); + } + mux_ack: if (!typec->needs_mux_ack) return ret; @@ -1244,6 +1260,8 @@ static int cros_typec_probe(struct platform_device *pdev) typec->typec_cmd_supported = cros_ec_check_features(ec_dev, EC_FEATURE_TYPEC_CMD); typec->needs_mux_ack = cros_ec_check_features(ec_dev, EC_FEATURE_TYPEC_MUX_REQUIRE_AP_ACK); + typec->ap_driven_altmode = cros_ec_check_features( + ec_dev, EC_FEATURE_TYPEC_REQUIRE_AP_MODE_ENTRY); ret = cros_ec_cmd(typec->ec, 0, EC_CMD_USB_PD_PORTS, NULL, 0, &resp, sizeof(resp)); diff --git a/drivers/platform/chrome/cros_ec_typec.h b/drivers/platform/chrome/cros_ec_typec.h index deda180a646f..9fd5342bb0ad 100644 --- a/drivers/platform/chrome/cros_ec_typec.h +++ b/drivers/platform/chrome/cros_ec_typec.h @@ -39,6 +39,7 @@ struct cros_typec_data { struct work_struct port_work; bool typec_cmd_supported; bool needs_mux_ack; + bool ap_driven_altmode; }; /* Per port data. */ diff --git a/drivers/platform/chrome/cros_typec_altmode.c b/drivers/platform/chrome/cros_typec_altmode.c new file mode 100644 index 000000000000..557340b53af0 --- /dev/null +++ b/drivers/platform/chrome/cros_typec_altmode.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Alt-mode implementation on ChromeOS EC. + * + * Copyright 2024 Google LLC + * Author: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> + */ +#include "cros_ec_typec.h" + +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_tbt.h> +#include <linux/usb/pd_vdo.h> + +#include "cros_typec_altmode.h" + +struct cros_typec_altmode_data { + struct work_struct work; + struct cros_typec_port *port; + struct typec_altmode *alt; + bool ap_mode_entry; + + struct mutex lock; + u32 header; + u32 *vdo_data; + u8 vdo_size; + + u16 sid; + u8 mode; +}; + +struct cros_typec_dp_data { + struct cros_typec_altmode_data adata; + struct typec_displayport_data data; + bool configured; + bool pending_status_update; +}; + +static void cros_typec_altmode_work(struct work_struct *work) +{ + struct cros_typec_altmode_data *data = + container_of(work, struct cros_typec_altmode_data, work); + + mutex_lock(&data->lock); + + if (typec_altmode_vdm(data->alt, data->header, data->vdo_data, + data->vdo_size)) + dev_err(&data->alt->dev, "VDM 0x%x failed\n", data->header); + + data->header = 0; + data->vdo_data = NULL; + data->vdo_size = 0; + + mutex_unlock(&data->lock); +} + +static int cros_typec_altmode_enter(struct typec_altmode *alt, u32 *vdo) +{ + struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt); + struct ec_params_typec_control req = { + .port = adata->port->port_num, + .command = TYPEC_CONTROL_COMMAND_ENTER_MODE, + }; + int svdm_version; + int ret; + + if (!adata->ap_mode_entry) { + dev_warn(&alt->dev, + "EC does not support AP driven mode entry\n"); + return -EOPNOTSUPP; + } + + if (adata->sid == USB_TYPEC_DP_SID) + req.mode_to_enter = CROS_EC_ALTMODE_DP; + else if (adata->sid == USB_TYPEC_TBT_SID) + req.mode_to_enter = CROS_EC_ALTMODE_TBT; + else + return -EOPNOTSUPP; + + ret = cros_ec_cmd(adata->port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL, + &req, sizeof(req), NULL, 0); + if (ret < 0) + return ret; + + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) + return svdm_version; + + mutex_lock(&adata->lock); + + adata->header = VDO(adata->sid, 1, svdm_version, CMD_ENTER_MODE); + adata->header |= VDO_OPOS(adata->mode); + adata->header |= VDO_CMDT(CMDT_RSP_ACK); + adata->vdo_data = NULL; + adata->vdo_size = 1; + schedule_work(&adata->work); + + mutex_unlock(&adata->lock); + return ret; +} + +static int cros_typec_altmode_exit(struct typec_altmode *alt) +{ + struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt); + struct ec_params_typec_control req = { + .port = adata->port->port_num, + .command = TYPEC_CONTROL_COMMAND_EXIT_MODES, + }; + int svdm_version; + int ret; + + if (!adata->ap_mode_entry) { + dev_warn(&alt->dev, + "EC does not support AP driven mode exit\n"); + return -EOPNOTSUPP; + } + + ret = cros_ec_cmd(adata->port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL, + &req, sizeof(req), NULL, 0); + + if (ret < 0) + return ret; + + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) + return svdm_version; + + mutex_lock(&adata->lock); + + adata->header = VDO(adata->sid, 1, svdm_version, CMD_EXIT_MODE); + adata->header |= VDO_OPOS(adata->mode); + adata->header |= VDO_CMDT(CMDT_RSP_ACK); + adata->vdo_data = NULL; + adata->vdo_size = 1; + schedule_work(&adata->work); + + mutex_unlock(&adata->lock); + return ret; +} + +static int cros_typec_displayport_vdm(struct typec_altmode *alt, u32 header, + const u32 *data, int count) +{ + struct cros_typec_dp_data *dp_data = typec_altmode_get_drvdata(alt); + struct cros_typec_altmode_data *adata = &dp_data->adata; + + + int cmd_type = PD_VDO_CMDT(header); + int cmd = PD_VDO_CMD(header); + int svdm_version; + + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) + return svdm_version; + + mutex_lock(&adata->lock); + + switch (cmd_type) { + case CMDT_INIT: + if (PD_VDO_SVDM_VER(header) < svdm_version) { + typec_partner_set_svdm_version(adata->port->partner, + PD_VDO_SVDM_VER(header)); + svdm_version = PD_VDO_SVDM_VER(header); + } + + adata->header = VDO(adata->sid, 1, svdm_version, cmd); + adata->header |= VDO_OPOS(adata->mode); + + /* + * DP_CMD_CONFIGURE: We can't actually do anything with the + * provided VDO yet so just send back an ACK. + * + * DP_CMD_STATUS_UPDATE: We wait for Mux changes to send + * DPStatus Acks. + */ + switch (cmd) { + case DP_CMD_CONFIGURE: + dp_data->data.conf = *data; + adata->header |= VDO_CMDT(CMDT_RSP_ACK); + dp_data->configured = true; + schedule_work(&adata->work); + break; + case DP_CMD_STATUS_UPDATE: + dp_data->pending_status_update = true; + break; + default: + adata->header |= VDO_CMDT(CMDT_RSP_ACK); + schedule_work(&adata->work); + break; + } + + break; + default: + break; + } + + mutex_unlock(&adata->lock); + return 0; +} + +static int cros_typec_thunderbolt_vdm(struct typec_altmode *alt, u32 header, + const u32 *data, int count) +{ + struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt); + + int cmd_type = PD_VDO_CMDT(header); + int cmd = PD_VDO_CMD(header); + int svdm_version; + + svdm_version = typec_altmode_get_svdm_version(alt); + if (svdm_version < 0) + return svdm_version; + + mutex_lock(&adata->lock); + + switch (cmd_type) { + case CMDT_INIT: + if (PD_VDO_SVDM_VER(header) < svdm_version) { + typec_partner_set_svdm_version(adata->port->partner, + PD_VDO_SVDM_VER(header)); + svdm_version = PD_VDO_SVDM_VER(header); + } + + adata->header = VDO(adata->sid, 1, svdm_version, cmd); + adata->header |= VDO_OPOS(adata->mode); + + switch (cmd) { + case CMD_ENTER_MODE: + /* Don't respond to the enter mode vdm because it + * triggers mux configuration. This is handled directly + * by the cros_ec_typec driver so the Thunderbolt driver + * doesn't need to be involved. + */ + break; + default: + adata->header |= VDO_CMDT(CMDT_RSP_ACK); + schedule_work(&adata->work); + break; + } + + break; + default: + break; + } + + mutex_unlock(&adata->lock); + return 0; +} + + +static int cros_typec_altmode_vdm(struct typec_altmode *alt, u32 header, + const u32 *data, int count) +{ + struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt); + + if (!adata->ap_mode_entry) + return -EOPNOTSUPP; + + if (adata->sid == USB_TYPEC_DP_SID) + return cros_typec_displayport_vdm(alt, header, data, count); + + if (adata->sid == USB_TYPEC_TBT_SID) + return cros_typec_thunderbolt_vdm(alt, header, data, count); + + return -EINVAL; +} + +static const struct typec_altmode_ops cros_typec_altmode_ops = { + .enter = cros_typec_altmode_enter, + .exit = cros_typec_altmode_exit, + .vdm = cros_typec_altmode_vdm, +}; + +#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE) +int cros_typec_displayport_status_update(struct typec_altmode *altmode, + struct typec_displayport_data *data) +{ + struct cros_typec_dp_data *dp_data = + typec_altmode_get_drvdata(altmode); + struct cros_typec_altmode_data *adata = &dp_data->adata; + + if (!dp_data->pending_status_update) { + dev_dbg(&altmode->dev, + "Got DPStatus without a pending request\n"); + return 0; + } + + if (dp_data->configured && dp_data->data.conf != data->conf) + dev_dbg(&altmode->dev, + "DP Conf doesn't match. Requested 0x%04x, Actual 0x%04x\n", + dp_data->data.conf, data->conf); + + mutex_lock(&adata->lock); + + dp_data->data = *data; + dp_data->pending_status_update = false; + adata->header |= VDO_CMDT(CMDT_RSP_ACK); + adata->vdo_data = &dp_data->data.status; + adata->vdo_size = 2; + schedule_work(&adata->work); + + mutex_unlock(&adata->lock); + + return 0; +} + +struct typec_altmode * +cros_typec_register_displayport(struct cros_typec_port *port, + struct typec_altmode_desc *desc, + bool ap_mode_entry) +{ + struct typec_altmode *alt; + struct cros_typec_dp_data *dp_data; + struct cros_typec_altmode_data *adata; + + alt = typec_port_register_altmode(port->port, desc); + if (IS_ERR(alt)) + return alt; + + dp_data = devm_kzalloc(&alt->dev, sizeof(*dp_data), GFP_KERNEL); + if (!dp_data) { + typec_unregister_altmode(alt); + return ERR_PTR(-ENOMEM); + } + + adata = &dp_data->adata; + INIT_WORK(&adata->work, cros_typec_altmode_work); + mutex_init(&adata->lock); + adata->alt = alt; + adata->port = port; + adata->ap_mode_entry = ap_mode_entry; + adata->sid = desc->svid; + adata->mode = desc->mode; + + typec_altmode_set_ops(alt, &cros_typec_altmode_ops); + typec_altmode_set_drvdata(alt, adata); + + return alt; +} +#endif + +#if IS_ENABLED(CONFIG_TYPEC_TBT_ALTMODE) +struct typec_altmode * +cros_typec_register_thunderbolt(struct cros_typec_port *port, + struct typec_altmode_desc *desc) +{ + struct typec_altmode *alt; + struct cros_typec_altmode_data *adata; + + alt = typec_port_register_altmode(port->port, desc); + if (IS_ERR(alt)) + return alt; + + adata = devm_kzalloc(&alt->dev, sizeof(*adata), GFP_KERNEL); + if (!adata) { + typec_unregister_altmode(alt); + return ERR_PTR(-ENOMEM); + } + + INIT_WORK(&adata->work, cros_typec_altmode_work); + adata->alt = alt; + adata->port = port; + adata->ap_mode_entry = true; + adata->sid = desc->svid; + adata->mode = desc->mode; + + typec_altmode_set_ops(alt, &cros_typec_altmode_ops); + typec_altmode_set_drvdata(alt, adata); + + return alt; +} +#endif diff --git a/drivers/platform/chrome/cros_typec_altmode.h b/drivers/platform/chrome/cros_typec_altmode.h new file mode 100644 index 000000000000..3f2aa95d065a --- /dev/null +++ b/drivers/platform/chrome/cros_typec_altmode.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __CROS_TYPEC_ALTMODE_H__ +#define __CROS_TYPEC_ALTMODE_H__ + +#include <linux/kconfig.h> +#include <linux/usb/typec.h> + +struct cros_typec_port; +struct typec_altmode; +struct typec_altmode_desc; +struct typec_displayport_data; + +#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE) +struct typec_altmode * +cros_typec_register_displayport(struct cros_typec_port *port, + struct typec_altmode_desc *desc, + bool ap_mode_entry); + +int cros_typec_displayport_status_update(struct typec_altmode *altmode, + struct typec_displayport_data *data); +#else +static inline struct typec_altmode * +cros_typec_register_displayport(struct cros_typec_port *port, + struct typec_altmode_desc *desc, + bool ap_mode_entry) +{ + return typec_port_register_altmode(port->port, desc); +} + +static inline int cros_typec_displayport_status_update(struct typec_altmode *altmode, + struct typec_displayport_data *data) +{ + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_TYPEC_TBT_ALTMODE) +struct typec_altmode * +cros_typec_register_thunderbolt(struct cros_typec_port *port, + struct typec_altmode_desc *desc); +#else +static inline struct typec_altmode * +cros_typec_register_thunderbolt(struct cros_typec_port *port, + struct typec_altmode_desc *desc) +{ + return typec_port_register_altmode(port->port, desc); +} +#endif + +#endif /* __CROS_TYPEC_ALTMODE_H__ */ diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index 465e9267b49c..98980a23e1c2 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -529,9 +529,7 @@ int cdns_resume(struct cdns *cdns) int ret = 0; if (cdns_power_is_lost(cdns)) { - if (cdns->role_sw) { - cdns->role = cdns_role_get(cdns->role_sw); - } else { + if (!cdns->role_sw) { real_role = cdns_hw_role_state_machine(cdns); if (real_role != cdns->role) { ret = cdns_hw_role_switch(cdns); diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 0cce19208370..442d79e32a65 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -256,8 +256,14 @@ static int ci_ehci_hub_control( struct device *dev = hcd->self.controller; struct ci_hdrc *ci = dev_get_drvdata(dev); - port_index = wIndex & 0xff; - port_index -= (port_index > 0); + /* + * Avoid out-of-bounds values while calculating the port index + * from wIndex. The compiler doesn't like pointers to invalid + * addresses, even if they are never used. + */ + port_index = (wIndex - 1) & 0xff; + if (port_index >= HCS_N_PORTS_MAX) + port_index = 0; status_reg = &ehci->regs->port_status[port_index]; spin_lock_irqsave(&ehci->lock, flags); diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c index ff1a941fd2ed..e2527faa6592 100644 --- a/drivers/usb/class/usblp.c +++ b/drivers/usb/class/usblp.c @@ -87,7 +87,7 @@ /* Get two-int array: [0]=vendor ID, [1]=product ID: */ #define LPIOC_GET_VID_PID(len) _IOC(_IOC_READ, 'P', IOCNR_GET_VID_PID, len) /* Perform class specific soft reset */ -#define LPIOC_SOFT_RESET _IOC(_IOC_NONE, 'P', IOCNR_SOFT_RESET, 0); +#define LPIOC_SOFT_RESET _IOC(_IOC_NONE, 'P', IOCNR_SOFT_RESET, 0) /* * A DEVICE_ID string may include the printer's serial number. diff --git a/drivers/usb/common/common.c b/drivers/usb/common/common.c index 871cf199b6bf..fc0845f681be 100644 --- a/drivers/usb/common/common.c +++ b/drivers/usb/common/common.c @@ -41,6 +41,12 @@ const char *usb_ep_type_string(int ep_type) } EXPORT_SYMBOL_GPL(usb_ep_type_string); +/** + * usb_otg_state_string() - returns human readable name of OTG state. + * @state: the OTG state to return the human readable name of. If it's not + * any of the states defined in usb_otg_state enum, 'UNDEFINED' will be + * returned. + */ const char *usb_otg_state_string(enum usb_otg_state state) { static const char *const names[] = { @@ -179,6 +185,14 @@ static const char *const usb_dr_modes[] = { [USB_DR_MODE_OTG] = "otg", }; +/** + * usb_get_dr_mode_from_string() - Get dual role mode for given string + * @str: String to find the corresponding dual role mode for + * + * This function performs a lookup for the given string and returns the + * corresponding enum usb_dr_mode. If no match for the string could be found, + * 'USB_DR_MODE_UNKNOWN' is returned. + */ static enum usb_dr_mode usb_get_dr_mode_from_string(const char *str) { int ret; diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index f203fdbfb6f6..460d4dde5994 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1086,15 +1086,14 @@ int usb_register_driver(struct usb_driver *new_driver, struct module *owner, pr_info("%s: registered new interface driver %s\n", usbcore_name, new_driver->name); -out: - return retval; + return 0; out_newid: driver_unregister(&new_driver->driver); - +out: pr_err("%s: error %d registering interface driver %s\n", usbcore_name, retval, new_driver->name); - goto out; + return retval; } EXPORT_SYMBOL_GPL(usb_register_driver); diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index a08f3f228e6d..56b534f59907 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -422,7 +422,12 @@ static int suspend_common(struct device *dev, pm_message_t msg) bool do_wakeup; int retval; - do_wakeup = PMSG_IS_AUTO(msg) ? true : device_may_wakeup(dev); + if (PMSG_IS_AUTO(msg)) + do_wakeup = true; + else if (PMSG_NO_WAKEUP(msg)) + do_wakeup = false; + else + do_wakeup = device_may_wakeup(dev); /* Root hub suspend should have stopped all downstream traffic, * and all bus master traffic. And done so for both the interface @@ -521,6 +526,11 @@ static int hcd_pci_suspend(struct device *dev) return suspend_common(dev, PMSG_SUSPEND); } +static int hcd_pci_freeze(struct device *dev) +{ + return suspend_common(dev, PMSG_FREEZE); +} + static int hcd_pci_suspend_noirq(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); @@ -590,6 +600,7 @@ static int hcd_pci_restore(struct device *dev) #else #define hcd_pci_suspend NULL +#define hcd_pci_freeze NULL #define hcd_pci_suspend_noirq NULL #define hcd_pci_poweroff_late NULL #define hcd_pci_resume_noirq NULL @@ -624,7 +635,7 @@ const struct dev_pm_ops usb_hcd_pci_pm_ops = { .suspend_noirq = hcd_pci_suspend_noirq, .resume_noirq = hcd_pci_resume_noirq, .resume = hcd_pci_resume, - .freeze = hcd_pci_suspend, + .freeze = hcd_pci_freeze, .freeze_noirq = check_root_hub_suspended, .thaw_noirq = NULL, .thaw = hcd_pci_resume, diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index b4cba23831ac..23f3cb1989f4 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -854,7 +854,7 @@ static const struct attribute_group dev_string_attr_grp = { static ssize_t descriptors_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, + const struct bin_attribute *attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -890,11 +890,11 @@ descriptors_read(struct file *filp, struct kobject *kobj, } return count - nleft; } -static BIN_ATTR_RO(descriptors, 18 + 65535); /* dev descr + max-size raw descriptor */ +static const BIN_ATTR_RO(descriptors, 18 + 65535); /* dev descr + max-size raw descriptor */ static ssize_t bos_descriptors_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, + const struct bin_attribute *attr, char *buf, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -913,12 +913,12 @@ bos_descriptors_read(struct file *filp, struct kobject *kobj, } return n; } -static BIN_ATTR_RO(bos_descriptors, 65535); /* max-size BOS */ +static const BIN_ATTR_RO(bos_descriptors, 65535); /* max-size BOS */ /* When modifying this list, be sure to modify dev_bin_attrs_are_visible() * accordingly. */ -static struct bin_attribute *dev_bin_attrs[] = { +static const struct bin_attribute *const dev_bin_attrs[] = { &bin_attr_descriptors, &bin_attr_bos_descriptors, NULL @@ -944,7 +944,7 @@ static umode_t dev_bin_attrs_are_visible(struct kobject *kobj, } static const struct attribute_group dev_bin_attr_grp = { - .bin_attrs = dev_bin_attrs, + .bin_attrs_new = dev_bin_attrs, .is_bin_visible = dev_bin_attrs_are_visible, }; diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index f219c82e9619..7578c5133568 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1479,6 +1479,26 @@ static int dwc3_core_init(struct dwc3 *dwc) } } + /* + * STAR 9001346572: This issue affects DWC_usb31 versions 1.80a and + * prior. When an active endpoint not currently cached in the host + * controller is chosen to be cached to the same index as an endpoint + * receiving NAKs, the endpoint receiving NAKs enters continuous + * retry mode. This prevents it from being evicted from the host + * controller cache, blocking the new endpoint from being cached and + * serviced. + * + * To resolve this, for controller versions 1.70a and 1.80a, set the + * GUCTL3 bit[16] (USB2.0 Internal Retry Disable) to 1. This bit + * disables the USB2.0 internal retry feature. The GUCTL3[16] register + * function is available only from version 1.70a. + */ + if (DWC3_VER_IS_WITHIN(DWC31, 170A, 180A)) { + reg = dwc3_readl(dwc->regs, DWC3_GUCTL3); + reg |= DWC3_GUCTL3_USB20_RETRY_DISABLE; + dwc3_writel(dwc->regs, DWC3_GUCTL3, reg); + } + return 0; err_power_off_phy: @@ -2589,12 +2609,15 @@ static int dwc3_resume(struct device *dev) pinctrl_pm_select_default_state(dev); pm_runtime_disable(dev); - pm_runtime_set_active(dev); + ret = pm_runtime_set_active(dev); + if (ret) + goto out; ret = dwc3_resume_common(dwc, PMSG_RESUME); if (ret) pm_runtime_set_suspended(dev); +out: pm_runtime_enable(dev); return ret; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index f11570c8ffd0..914fe79d08e8 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -425,6 +425,7 @@ /* Global User Control Register 3 */ #define DWC3_GUCTL3_SPLITDISABLE BIT(14) +#define DWC3_GUCTL3_USB20_RETRY_DISABLE BIT(16) /* Device Configuration Register */ #define DWC3_DCFG_NUMLANES(n) (((n) & 0x3) << 30) /* DWC_usb32 only */ @@ -957,7 +958,6 @@ struct dwc3_request { struct usb_request request; struct list_head list; struct dwc3_ep *dep; - struct scatterlist *sg; struct scatterlist *start_sg; unsigned int num_pending_sgs; diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c index 7d43da5f2897..c158364bc03e 100644 --- a/drivers/usb/dwc3/dwc3-am62.c +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -108,6 +108,9 @@ #define DWC3_AM62_AUTOSUSPEND_DELAY 100 +#define USBSS_DEBUG_CFG_OFF 0x0 +#define USBSS_DEBUG_CFG_DISABLED 0x7 + struct dwc3_am62 { struct device *dev; void __iomem *usbss; @@ -117,6 +120,7 @@ struct dwc3_am62 { unsigned int offset; unsigned int vbus_divider; u32 wakeup_stat; + void __iomem *phy_regs; }; static const int dwc3_ti_rate_table[] = { /* in KHZ */ @@ -166,6 +170,7 @@ static int phy_syscon_pll_refclk(struct dwc3_am62 *am62) if (ret) return ret; + of_node_put(args.np); am62->offset = args.args[0]; /* Core voltage. PHY_CORE_VOLTAGE bit Recommended to be 0 always */ @@ -184,15 +189,47 @@ static int phy_syscon_pll_refclk(struct dwc3_am62 *am62) return 0; } +static int dwc3_ti_init(struct dwc3_am62 *am62) +{ + int ret; + u32 reg; + + /* Read the syscon property and set the rate code */ + ret = phy_syscon_pll_refclk(am62); + if (ret) + return ret; + + /* Workaround Errata i2409 */ + if (am62->phy_regs) { + reg = readl(am62->phy_regs + USB_PHY_PLL_REG12); + reg |= USB_PHY_PLL_LDO_REF_EN | USB_PHY_PLL_LDO_REF_EN_EN; + writel(reg, am62->phy_regs + USB_PHY_PLL_REG12); + } + + /* VBUS divider select */ + reg = dwc3_ti_readl(am62, USBSS_PHY_CONFIG); + if (am62->vbus_divider) + reg |= 1 << USBSS_PHY_VBUS_SEL_SHIFT; + + dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg); + + clk_prepare_enable(am62->usb2_refclk); + + /* Set mode valid bit to indicate role is valid */ + reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); + reg |= USBSS_MODE_VALID; + dwc3_ti_writel(am62, USBSS_MODE_CONTROL, reg); + + return 0; +} + static int dwc3_ti_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = pdev->dev.of_node; struct dwc3_am62 *am62; unsigned long rate; - void __iomem *phy; int i, ret; - u32 reg; am62 = devm_kzalloc(dev, sizeof(*am62), GFP_KERNEL); if (!am62) @@ -228,29 +265,17 @@ static int dwc3_ti_probe(struct platform_device *pdev) am62->rate_code = i; - /* Read the syscon property and set the rate code */ - ret = phy_syscon_pll_refclk(am62); - if (ret) - return ret; - - /* Workaround Errata i2409 */ - phy = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(phy)) { + am62->phy_regs = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(am62->phy_regs)) { dev_err(dev, "can't map PHY IOMEM resource. Won't apply i2409 fix.\n"); - phy = NULL; - } else { - reg = readl(phy + USB_PHY_PLL_REG12); - reg |= USB_PHY_PLL_LDO_REF_EN | USB_PHY_PLL_LDO_REF_EN_EN; - writel(reg, phy + USB_PHY_PLL_REG12); + am62->phy_regs = NULL; } - /* VBUS divider select */ am62->vbus_divider = device_property_read_bool(dev, "ti,vbus-divider"); - reg = dwc3_ti_readl(am62, USBSS_PHY_CONFIG); - if (am62->vbus_divider) - reg |= 1 << USBSS_PHY_VBUS_SEL_SHIFT; - dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg); + ret = dwc3_ti_init(am62); + if (ret) + return ret; pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -258,7 +283,6 @@ static int dwc3_ti_probe(struct platform_device *pdev) * Don't ignore its dependencies with its children */ pm_suspend_ignore_children(dev, false); - clk_prepare_enable(am62->usb2_refclk); pm_runtime_get_noresume(dev); ret = of_platform_populate(node, NULL, NULL, dev); @@ -267,11 +291,6 @@ static int dwc3_ti_probe(struct platform_device *pdev) goto err_pm_disable; } - /* Set mode valid bit to indicate role is valid */ - reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL); - reg |= USBSS_MODE_VALID; - dwc3_ti_writel(am62, USBSS_MODE_CONTROL, reg); - /* Device has capability to wakeup system from sleep */ device_set_wakeup_capable(dev, true); ret = device_wakeup_enable(dev); @@ -339,6 +358,9 @@ static int dwc3_ti_suspend_common(struct device *dev) dwc3_ti_writel(am62, USBSS_WAKEUP_STAT, USBSS_WAKEUP_STAT_CLR); } + /* just to track if module resets on suspend */ + dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_DISABLED); + clk_disable_unprepare(am62->usb2_refclk); return 0; @@ -349,7 +371,14 @@ static int dwc3_ti_resume_common(struct device *dev) struct dwc3_am62 *am62 = dev_get_drvdata(dev); u32 reg; - clk_prepare_enable(am62->usb2_refclk); + reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG); + if (reg != USBSS_DEBUG_CFG_DISABLED) { + /* lost power/context */ + dwc3_ti_init(am62); + } else { + dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF); + clk_prepare_enable(am62->usb2_refclk); + } if (device_may_wakeup(dev)) { /* Clear wakeup config enable bits */ diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c index b261c46124c6..fe74d11bb629 100644 --- a/drivers/usb/dwc3/dwc3-omap.c +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -457,7 +457,7 @@ static int dwc3_omap_probe(struct platform_device *pdev) struct dwc3_omap *omap; struct device *dev = &pdev->dev; - struct regulator *vbus_reg = NULL; + struct regulator *vbus_reg; int ret; int irq; @@ -483,12 +483,11 @@ static int dwc3_omap_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - if (of_property_read_bool(node, "vbus-supply")) { - vbus_reg = devm_regulator_get(dev, "vbus"); - if (IS_ERR(vbus_reg)) { - dev_err(dev, "vbus init failed\n"); - return PTR_ERR(vbus_reg); - } + vbus_reg = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(vbus_reg)) { + if (PTR_ERR(vbus_reg) != -ENODEV) + return dev_err_probe(dev, PTR_ERR(vbus_reg), "vbus init failed\n"); + vbus_reg = NULL; } omap->dev = dev; diff --git a/drivers/usb/dwc3/dwc3-st.c b/drivers/usb/dwc3/dwc3-st.c index e16c3237180e..ef7c43008946 100644 --- a/drivers/usb/dwc3/dwc3-st.c +++ b/drivers/usb/dwc3/dwc3-st.c @@ -309,7 +309,6 @@ static void st_dwc3_remove(struct platform_device *pdev) reset_control_assert(dwc3_data->rstc_rst); } -#ifdef CONFIG_PM_SLEEP static int st_dwc3_suspend(struct device *dev) { struct st_dwc3 *dwc3_data = dev_get_drvdata(dev); @@ -343,9 +342,8 @@ static int st_dwc3_resume(struct device *dev) return 0; } -#endif /* CONFIG_PM_SLEEP */ -static SIMPLE_DEV_PM_OPS(st_dwc3_dev_pm_ops, st_dwc3_suspend, st_dwc3_resume); +static DEFINE_SIMPLE_DEV_PM_OPS(st_dwc3_dev_pm_ops, st_dwc3_suspend, st_dwc3_resume); static const struct of_device_id st_dwc3_match[] = { { .compatible = "st,stih407-dwc3" }, @@ -360,7 +358,7 @@ static struct platform_driver st_dwc3_driver = { .driver = { .name = "usb-st-dwc3", .of_match_table = st_dwc3_match, - .pm = &st_dwc3_dev_pm_ops, + .pm = pm_sleep_ptr(&st_dwc3_dev_pm_ops), }, }; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 31a654c6f15b..7270f092329a 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2740,6 +2740,8 @@ static int dwc3_gadget_soft_disconnect(struct dwc3 *dwc) __dwc3_gadget_stop(dwc); spin_unlock_irqrestore(&dwc->lock, flags); + usb_gadget_set_state(dwc->gadget, USB_STATE_NOTATTACHED); + return ret; } diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c index 15bb3aa12aa8..5a2e1237f85c 100644 --- a/drivers/usb/gadget/function/f_tcm.c +++ b/drivers/usb/gadget/function/f_tcm.c @@ -12,6 +12,7 @@ #include <linux/string.h> #include <linux/configfs.h> #include <linux/ctype.h> +#include <linux/delay.h> #include <linux/usb/ch9.h> #include <linux/usb/composite.h> #include <linux/usb/gadget.h> @@ -50,7 +51,7 @@ static int bot_enqueue_cmd_cbw(struct f_uas *fu) if (fu->flags & USBG_BOT_CMD_PEND) return 0; - ret = usb_ep_queue(fu->ep_out, fu->cmd.req, GFP_ATOMIC); + ret = usb_ep_queue(fu->ep_out, fu->cmd[0].req, GFP_ATOMIC); if (!ret) fu->flags |= USBG_BOT_CMD_PEND; return ret; @@ -62,10 +63,11 @@ static void bot_status_complete(struct usb_ep *ep, struct usb_request *req) struct f_uas *fu = cmd->fu; transport_generic_free_cmd(&cmd->se_cmd, 0); - if (req->status < 0) { - pr_err("ERR %s(%d)\n", __func__, __LINE__); + if (req->status == -ESHUTDOWN) return; - } + + if (req->status < 0) + pr_err("ERR %s(%d)\n", __func__, __LINE__); /* CSW completed, wait for next CBW */ bot_enqueue_cmd_cbw(fu); @@ -136,7 +138,7 @@ static void bot_send_bad_status(struct usbg_cmd *cmd) } req->complete = bot_err_compl; req->context = cmd; - req->buf = fu->cmd.buf; + req->buf = fu->cmd[0].buf; usb_ep_queue(ep, req, GFP_KERNEL); } else { bot_enqueue_sense_code(fu, cmd); @@ -196,6 +198,11 @@ static void bot_read_compl(struct usb_ep *ep, struct usb_request *req) if (req->status < 0) pr_err("ERR %s(%d)\n", __func__, __LINE__); + if (req->status == -ESHUTDOWN) { + transport_generic_free_cmd(&cmd->se_cmd, 0); + return; + } + bot_send_status(cmd, true); } @@ -244,11 +251,8 @@ static int usbg_prepare_w_request(struct usbg_cmd *, struct usb_request *); static int bot_send_write_request(struct usbg_cmd *cmd) { struct f_uas *fu = cmd->fu; - struct se_cmd *se_cmd = &cmd->se_cmd; - struct usb_gadget *gadget = fuas_to_gadget(fu); int ret; - init_completion(&cmd->write_complete); cmd->fu = fu; if (!cmd->data_len) { @@ -256,22 +260,6 @@ static int bot_send_write_request(struct usbg_cmd *cmd) return -EINVAL; } - if (!gadget->sg_supported) { - cmd->data_buf = kmalloc(se_cmd->data_length, GFP_KERNEL); - if (!cmd->data_buf) - return -ENOMEM; - - fu->bot_req_out->buf = cmd->data_buf; - } else { - fu->bot_req_out->buf = NULL; - fu->bot_req_out->num_sgs = se_cmd->t_data_nents; - fu->bot_req_out->sg = se_cmd->t_data_sg; - } - - fu->bot_req_out->complete = usbg_data_write_cmpl; - fu->bot_req_out->length = se_cmd->data_length; - fu->bot_req_out->context = cmd; - ret = usbg_prepare_w_request(cmd, fu->bot_req_out); if (ret) goto cleanup; @@ -279,8 +267,6 @@ static int bot_send_write_request(struct usbg_cmd *cmd) if (ret) pr_err("%s(%d)\n", __func__, __LINE__); - wait_for_completion(&cmd->write_complete); - target_execute_cmd(se_cmd); cleanup: return ret; } @@ -292,14 +278,31 @@ static void bot_cmd_complete(struct usb_ep *ep, struct usb_request *req) struct f_uas *fu = req->context; int ret; + if (req->status == -ESHUTDOWN) + return; + fu->flags &= ~USBG_BOT_CMD_PEND; - if (req->status < 0) + if (req->status < 0) { + struct usb_gadget *gadget = fuas_to_gadget(fu); + + dev_err(&gadget->dev, "BOT command req err (%d)\n", req->status); + bot_enqueue_cmd_cbw(fu); return; + } ret = bot_submit_command(fu, req->buf, req->actual); - if (ret) + if (ret) { pr_err("%s(%d): %d\n", __func__, __LINE__, ret); + if (!(fu->flags & USBG_BOT_WEDGED)) + usb_ep_set_wedge(fu->ep_in); + + fu->flags |= USBG_BOT_WEDGED; + bot_enqueue_cmd_cbw(fu); + } else if (fu->flags & USBG_BOT_WEDGED) { + fu->flags &= ~USBG_BOT_WEDGED; + usb_ep_clear_halt(fu->ep_in); + } } static int bot_prepare_reqs(struct f_uas *fu) @@ -314,8 +317,8 @@ static int bot_prepare_reqs(struct f_uas *fu) if (!fu->bot_req_out) goto err_out; - fu->cmd.req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); - if (!fu->cmd.req) + fu->cmd[0].req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); + if (!fu->cmd[0].req) goto err_cmd; fu->bot_status.req = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); @@ -327,27 +330,27 @@ static int bot_prepare_reqs(struct f_uas *fu) fu->bot_status.req->complete = bot_status_complete; fu->bot_status.csw.Signature = cpu_to_le32(US_BULK_CS_SIGN); - fu->cmd.buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL); - if (!fu->cmd.buf) + fu->cmd[0].buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL); + if (!fu->cmd[0].buf) goto err_buf; - fu->cmd.req->complete = bot_cmd_complete; - fu->cmd.req->buf = fu->cmd.buf; - fu->cmd.req->length = fu->ep_out->maxpacket; - fu->cmd.req->context = fu; + fu->cmd[0].req->complete = bot_cmd_complete; + fu->cmd[0].req->buf = fu->cmd[0].buf; + fu->cmd[0].req->length = fu->ep_out->maxpacket; + fu->cmd[0].req->context = fu; ret = bot_enqueue_cmd_cbw(fu); if (ret) goto err_queue; return 0; err_queue: - kfree(fu->cmd.buf); - fu->cmd.buf = NULL; + kfree(fu->cmd[0].buf); + fu->cmd[0].buf = NULL; err_buf: usb_ep_free_request(fu->ep_in, fu->bot_status.req); err_sts: - usb_ep_free_request(fu->ep_out, fu->cmd.req); - fu->cmd.req = NULL; + usb_ep_free_request(fu->ep_out, fu->cmd[0].req); + fu->cmd[0].req = NULL; err_cmd: usb_ep_free_request(fu->ep_out, fu->bot_req_out); fu->bot_req_out = NULL; @@ -372,16 +375,16 @@ static void bot_cleanup_old_alt(struct f_uas *fu) usb_ep_free_request(fu->ep_in, fu->bot_req_in); usb_ep_free_request(fu->ep_out, fu->bot_req_out); - usb_ep_free_request(fu->ep_out, fu->cmd.req); + usb_ep_free_request(fu->ep_out, fu->cmd[0].req); usb_ep_free_request(fu->ep_in, fu->bot_status.req); - kfree(fu->cmd.buf); + kfree(fu->cmd[0].buf); fu->bot_req_in = NULL; fu->bot_req_out = NULL; - fu->cmd.req = NULL; + fu->cmd[0].req = NULL; fu->bot_status.req = NULL; - fu->cmd.buf = NULL; + fu->cmd[0].buf = NULL; } static void bot_set_alt(struct f_uas *fu) @@ -441,14 +444,10 @@ static int usbg_bot_setup(struct usb_function *f, pr_err("No LUNs configured?\n"); return -EINVAL; } - /* - * If 4 LUNs are present we return 3 i.e. LUN 0..3 can be - * accessed. The upper limit is 0xf - */ luns--; - if (luns > 0xf) { + if (luns > US_BULK_MAX_LUN_LIMIT) { pr_info_once("Limiting the number of luns to 16\n"); - luns = 0xf; + luns = US_BULK_MAX_LUN_LIMIT; } ret_lun = cdev->req->buf; *ret_lun = luns; @@ -457,6 +456,11 @@ static int usbg_bot_setup(struct usb_function *f, case US_BULK_RESET_REQUEST: /* XXX maybe we should remove previous requests for IN + OUT */ + if (fu->flags & USBG_BOT_WEDGED) { + fu->flags &= ~USBG_BOT_WEDGED; + usb_ep_clear_halt(fu->ep_in); + } + bot_enqueue_cmd_cbw(fu); return 0; } @@ -465,6 +469,45 @@ static int usbg_bot_setup(struct usb_function *f, /* Start uas.c code */ +static int tcm_to_uasp_response(enum tcm_tmrsp_table code) +{ + switch (code) { + case TMR_FUNCTION_FAILED: + return RC_TMF_FAILED; + case TMR_FUNCTION_COMPLETE: + case TMR_TASK_DOES_NOT_EXIST: + return RC_TMF_COMPLETE; + case TMR_LUN_DOES_NOT_EXIST: + return RC_INCORRECT_LUN; + case TMR_FUNCTION_REJECTED: + case TMR_TASK_MGMT_FUNCTION_NOT_SUPPORTED: + default: + return RC_TMF_NOT_SUPPORTED; + } +} + +static unsigned char uasp_to_tcm_func(int code) +{ + switch (code) { + case TMF_ABORT_TASK: + return TMR_ABORT_TASK; + case TMF_ABORT_TASK_SET: + return TMR_ABORT_TASK_SET; + case TMF_CLEAR_TASK_SET: + return TMR_CLEAR_TASK_SET; + case TMF_LOGICAL_UNIT_RESET: + return TMR_LUN_RESET; + case TMF_CLEAR_ACA: + return TMR_CLEAR_ACA; + case TMF_I_T_NEXUS_RESET: + case TMF_QUERY_TASK: + case TMF_QUERY_TASK_SET: + case TMF_QUERY_ASYNC_EVENT: + default: + return TMR_UNKNOWN; + } +} + static void uasp_cleanup_one_stream(struct f_uas *fu, struct uas_stream *stream) { /* We have either all three allocated or none */ @@ -482,10 +525,14 @@ static void uasp_cleanup_one_stream(struct f_uas *fu, struct uas_stream *stream) static void uasp_free_cmdreq(struct f_uas *fu) { - usb_ep_free_request(fu->ep_cmd, fu->cmd.req); - kfree(fu->cmd.buf); - fu->cmd.req = NULL; - fu->cmd.buf = NULL; + int i; + + for (i = 0; i < USBG_NUM_CMDS; i++) { + usb_ep_free_request(fu->ep_cmd, fu->cmd[i].req); + kfree(fu->cmd[i].buf); + fu->cmd[i].req = NULL; + fu->cmd[i].buf = NULL; + } } static void uasp_cleanup_old_alt(struct f_uas *fu) @@ -500,7 +547,7 @@ static void uasp_cleanup_old_alt(struct f_uas *fu) usb_ep_disable(fu->ep_status); usb_ep_disable(fu->ep_cmd); - for (i = 0; i < UASP_SS_EP_COMP_NUM_STREAMS; i++) + for (i = 0; i < USBG_NUM_CMDS; i++) uasp_cleanup_one_stream(fu, &fu->stream[i]); uasp_free_cmdreq(fu); } @@ -512,7 +559,7 @@ static int uasp_prepare_r_request(struct usbg_cmd *cmd) struct se_cmd *se_cmd = &cmd->se_cmd; struct f_uas *fu = cmd->fu; struct usb_gadget *gadget = fuas_to_gadget(fu); - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &fu->stream[se_cmd->map_tag]; if (!gadget->sg_supported) { cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); @@ -532,6 +579,7 @@ static int uasp_prepare_r_request(struct usbg_cmd *cmd) } stream->req_in->is_last = 1; + stream->req_in->stream_id = cmd->tag; stream->req_in->complete = uasp_status_data_cmpl; stream->req_in->length = se_cmd->data_length; stream->req_in->context = cmd; @@ -544,7 +592,7 @@ static void uasp_prepare_status(struct usbg_cmd *cmd) { struct se_cmd *se_cmd = &cmd->se_cmd; struct sense_iu *iu = &cmd->sense_iu; - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &cmd->fu->stream[se_cmd->map_tag]; cmd->state = UASP_QUEUE_COMMAND; iu->iu_id = IU_ID_STATUS; @@ -556,20 +604,76 @@ static void uasp_prepare_status(struct usbg_cmd *cmd) iu->len = cpu_to_be16(se_cmd->scsi_sense_length); iu->status = se_cmd->scsi_status; stream->req_status->is_last = 1; + stream->req_status->stream_id = cmd->tag; stream->req_status->context = cmd; stream->req_status->length = se_cmd->scsi_sense_length + 16; stream->req_status->buf = iu; stream->req_status->complete = uasp_status_data_cmpl; } +static void uasp_prepare_response(struct usbg_cmd *cmd) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct response_iu *rsp_iu = &cmd->response_iu; + struct uas_stream *stream = &cmd->fu->stream[se_cmd->map_tag]; + + cmd->state = UASP_QUEUE_COMMAND; + rsp_iu->iu_id = IU_ID_RESPONSE; + rsp_iu->tag = cpu_to_be16(cmd->tag); + + if (cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) + rsp_iu->response_code = cmd->tmr_rsp; + else + rsp_iu->response_code = + tcm_to_uasp_response(se_cmd->se_tmr_req->response); + + /* + * The UASP driver must support all the task management functions listed + * in Table 20 of UAS-r04. To remain compliant while indicate that the + * TMR did not go through, report RC_TMF_FAILED instead of + * RC_TMF_NOT_SUPPORTED and print a warning to the user. + */ + switch (cmd->tmr_func) { + case TMF_ABORT_TASK: + case TMF_ABORT_TASK_SET: + case TMF_CLEAR_TASK_SET: + case TMF_LOGICAL_UNIT_RESET: + case TMF_CLEAR_ACA: + case TMF_I_T_NEXUS_RESET: + case TMF_QUERY_TASK: + case TMF_QUERY_TASK_SET: + case TMF_QUERY_ASYNC_EVENT: + if (rsp_iu->response_code == RC_TMF_NOT_SUPPORTED) { + struct usb_gadget *gadget = fuas_to_gadget(cmd->fu); + + dev_warn(&gadget->dev, "TMF function %d not supported\n", + cmd->tmr_func); + rsp_iu->response_code = RC_TMF_FAILED; + } + break; + default: + break; + } + + stream->req_status->is_last = 1; + stream->req_status->stream_id = cmd->tag; + stream->req_status->context = cmd; + stream->req_status->length = sizeof(struct response_iu); + stream->req_status->buf = rsp_iu; + stream->req_status->complete = uasp_status_data_cmpl; +} + +static void usbg_release_cmd(struct se_cmd *se_cmd); +static int uasp_send_tm_response(struct usbg_cmd *cmd); + static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) { struct usbg_cmd *cmd = req->context; - struct uas_stream *stream = cmd->stream; struct f_uas *fu = cmd->fu; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; int ret; - if (req->status < 0) + if (req->status == -ESHUTDOWN) goto cleanup; switch (cmd->state) { @@ -600,8 +704,37 @@ static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) break; case UASP_QUEUE_COMMAND: - transport_generic_free_cmd(&cmd->se_cmd, 0); - usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); + /* + * Overlapped command detected and cancelled. + * So send overlapped attempted status. + */ + if (cmd->tmr_rsp == RC_OVERLAPPED_TAG && + req->status == -ECONNRESET) { + uasp_send_tm_response(cmd); + return; + } + + hash_del(&stream->node); + + /* + * If no command submitted to target core here, just free the + * bitmap index. This is for the cases where f_tcm handles + * status response instead of the target core. + */ + if (cmd->tmr_rsp != RC_OVERLAPPED_TAG && + cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) { + struct se_session *se_sess; + + se_sess = fu->tpg->tpg_nexus->tvn_se_sess; + sbitmap_queue_clear(&se_sess->sess_tag_pool, + cmd->se_cmd.map_tag, + cmd->se_cmd.map_cpu); + } else { + transport_generic_free_cmd(&cmd->se_cmd, 0); + } + + usb_ep_queue(fu->ep_cmd, cmd->req, GFP_ATOMIC); + complete(&stream->cmd_completion); break; default: @@ -610,27 +743,38 @@ static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) return; cleanup: + hash_del(&stream->node); transport_generic_free_cmd(&cmd->se_cmd, 0); } static int uasp_send_status_response(struct usbg_cmd *cmd) { struct f_uas *fu = cmd->fu; - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; struct sense_iu *iu = &cmd->sense_iu; iu->tag = cpu_to_be16(cmd->tag); - stream->req_status->complete = uasp_status_data_cmpl; - stream->req_status->context = cmd; cmd->fu = fu; uasp_prepare_status(cmd); return usb_ep_queue(fu->ep_status, stream->req_status, GFP_ATOMIC); } +static int uasp_send_tm_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; + struct response_iu *iu = &cmd->response_iu; + + iu->tag = cpu_to_be16(cmd->tag); + cmd->fu = fu; + uasp_prepare_response(cmd); + return usb_ep_queue(fu->ep_status, stream->req_status, GFP_ATOMIC); +} + static int uasp_send_read_response(struct usbg_cmd *cmd) { struct f_uas *fu = cmd->fu; - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &fu->stream[cmd->se_cmd.map_tag]; struct sense_iu *iu = &cmd->sense_iu; int ret; @@ -674,11 +818,10 @@ static int uasp_send_write_request(struct usbg_cmd *cmd) { struct f_uas *fu = cmd->fu; struct se_cmd *se_cmd = &cmd->se_cmd; - struct uas_stream *stream = cmd->stream; + struct uas_stream *stream = &fu->stream[se_cmd->map_tag]; struct sense_iu *iu = &cmd->sense_iu; int ret; - init_completion(&cmd->write_complete); cmd->fu = fu; iu->tag = cpu_to_be16(cmd->tag); @@ -710,36 +853,31 @@ static int uasp_send_write_request(struct usbg_cmd *cmd) pr_err("%s(%d)\n", __func__, __LINE__); } - wait_for_completion(&cmd->write_complete); - target_execute_cmd(se_cmd); cleanup: return ret; } -static int usbg_submit_command(struct f_uas *, void *, unsigned int); +static int usbg_submit_command(struct f_uas *, struct usb_request *); static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req) { struct f_uas *fu = req->context; - int ret; - if (req->status < 0) + if (req->status == -ESHUTDOWN) return; - ret = usbg_submit_command(fu, req->buf, req->actual); - /* - * Once we tune for performance enqueue the command req here again so - * we can receive a second command while we processing this one. Pay - * attention to properly sync STAUS endpoint with DATA IN + OUT so you - * don't break HS. - */ - if (!ret) + if (req->status < 0) { + usb_ep_queue(fu->ep_cmd, req, GFP_ATOMIC); return; - usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); + } + + usbg_submit_command(fu, req); } static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream) { + init_completion(&stream->cmd_completion); + stream->req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); if (!stream->req_in) goto out; @@ -764,66 +902,48 @@ out: return -ENOMEM; } -static int uasp_alloc_cmd(struct f_uas *fu) +static int uasp_alloc_cmd(struct f_uas *fu, int i) { - fu->cmd.req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL); - if (!fu->cmd.req) + fu->cmd[i].req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL); + if (!fu->cmd[i].req) goto err; - fu->cmd.buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL); - if (!fu->cmd.buf) + fu->cmd[i].buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL); + if (!fu->cmd[i].buf) goto err_buf; - fu->cmd.req->complete = uasp_cmd_complete; - fu->cmd.req->buf = fu->cmd.buf; - fu->cmd.req->length = fu->ep_cmd->maxpacket; - fu->cmd.req->context = fu; + fu->cmd[i].req->complete = uasp_cmd_complete; + fu->cmd[i].req->buf = fu->cmd[i].buf; + fu->cmd[i].req->length = fu->ep_cmd->maxpacket; + fu->cmd[i].req->context = fu; return 0; err_buf: - usb_ep_free_request(fu->ep_cmd, fu->cmd.req); + usb_ep_free_request(fu->ep_cmd, fu->cmd[i].req); err: return -ENOMEM; } -static void uasp_setup_stream_res(struct f_uas *fu, int max_streams) -{ - int i; - - for (i = 0; i < max_streams; i++) { - struct uas_stream *s = &fu->stream[i]; - - s->req_in->stream_id = i + 1; - s->req_out->stream_id = i + 1; - s->req_status->stream_id = i + 1; - } -} - static int uasp_prepare_reqs(struct f_uas *fu) { int ret; int i; - int max_streams; - if (fu->flags & USBG_USE_STREAMS) - max_streams = UASP_SS_EP_COMP_NUM_STREAMS; - else - max_streams = 1; - - for (i = 0; i < max_streams; i++) { + for (i = 0; i < USBG_NUM_CMDS; i++) { ret = uasp_alloc_stream_res(fu, &fu->stream[i]); if (ret) goto err_cleanup; } - ret = uasp_alloc_cmd(fu); - if (ret) - goto err_free_stream; - uasp_setup_stream_res(fu, max_streams); + for (i = 0; i < USBG_NUM_CMDS; i++) { + ret = uasp_alloc_cmd(fu, i); + if (ret) + goto err_free_stream; - ret = usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); - if (ret) - goto err_free_stream; + ret = usb_ep_queue(fu->ep_cmd, fu->cmd[i].req, GFP_ATOMIC); + if (ret) + goto err_free_stream; + } return 0; @@ -914,6 +1034,8 @@ static int get_cmd_dir(const unsigned char *cdb) case READ_TOC: case READ_FORMAT_CAPACITIES: case REQUEST_SENSE: + case ATA_12: + case ATA_16: ret = DMA_FROM_DEVICE; break; @@ -957,7 +1079,18 @@ static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req) struct usbg_cmd *cmd = req->context; struct se_cmd *se_cmd = &cmd->se_cmd; - if (req->status < 0) { + cmd->state = UASP_QUEUE_COMMAND; + + if (req->status == -ESHUTDOWN) { + struct uas_stream *stream = &cmd->fu->stream[se_cmd->map_tag]; + + hash_del(&stream->node); + target_put_sess_cmd(se_cmd); + transport_generic_free_cmd(&cmd->se_cmd, 0); + return; + } + + if (req->status) { pr_err("%s() state %d transfer failed\n", __func__, cmd->state); goto cleanup; } @@ -969,11 +1102,22 @@ static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req) se_cmd->data_length); } - complete(&cmd->write_complete); + cmd->flags |= USBG_CMD_PENDING_DATA_WRITE; + queue_work(cmd->fu->tpg->workqueue, &cmd->work); return; cleanup: - transport_generic_free_cmd(&cmd->se_cmd, 0); + target_put_sess_cmd(se_cmd); + + /* Command was aborted due to overlapped tag */ + if (cmd->state == UASP_QUEUE_COMMAND && + cmd->tmr_rsp == RC_OVERLAPPED_TAG) { + uasp_send_tm_response(cmd); + return; + } + + transport_send_check_condition_and_sense(se_cmd, + TCM_CHECK_CONDITION_ABORT_CMD, 0); } static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req) @@ -995,9 +1139,12 @@ static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req) } req->is_last = 1; + req->stream_id = cmd->tag; req->complete = usbg_data_write_cmpl; req->length = se_cmd->data_length; req->context = cmd; + + cmd->state = UASP_SEND_STATUS; return 0; } @@ -1037,36 +1184,153 @@ static int usbg_send_read_response(struct se_cmd *se_cmd) return uasp_send_read_response(cmd); } -static void usbg_cmd_work(struct work_struct *work) +static void usbg_aborted_task(struct se_cmd *se_cmd); + +static void usbg_submit_tmr(struct usbg_cmd *cmd) +{ + struct se_session *se_sess; + struct se_cmd *se_cmd; + int flags = TARGET_SCF_ACK_KREF; + + se_cmd = &cmd->se_cmd; + se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess; + + target_submit_tmr(se_cmd, se_sess, + cmd->response_iu.add_response_info, + cmd->unpacked_lun, NULL, uasp_to_tcm_func(cmd->tmr_func), + GFP_ATOMIC, cmd->tag, flags); +} + +static void usbg_submit_cmd(struct usbg_cmd *cmd) { - struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); struct se_cmd *se_cmd; struct tcm_usbg_nexus *tv_nexus; struct usbg_tpg *tpg; int dir, flags = (TARGET_SCF_UNKNOWN_SIZE | TARGET_SCF_ACK_KREF); + /* + * Note: each command will spawn its own process, and each stage of the + * command is processed sequentially. Should this no longer be the case, + * locking is needed. + */ + if (cmd->flags & USBG_CMD_PENDING_DATA_WRITE) { + target_execute_cmd(&cmd->se_cmd); + cmd->flags &= ~USBG_CMD_PENDING_DATA_WRITE; + return; + } + se_cmd = &cmd->se_cmd; tpg = cmd->fu->tpg; tv_nexus = tpg->tpg_nexus; dir = get_cmd_dir(cmd->cmd_buf); - if (dir < 0) { - __target_init_cmd(se_cmd, - tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, - tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, - cmd->prio_attr, cmd->sense_iu.sense, - cmd->unpacked_lun, NULL); + if (dir < 0) goto out; - } target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, 0, cmd->prio_attr, dir, flags); + return; out: + __target_init_cmd(se_cmd, + tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, + tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, + cmd->prio_attr, cmd->sense_iu.sense, + cmd->unpacked_lun, NULL); transport_send_check_condition_and_sense(se_cmd, - TCM_UNSUPPORTED_SCSI_OPCODE, 1); - transport_generic_free_cmd(&cmd->se_cmd, 0); + TCM_UNSUPPORTED_SCSI_OPCODE, 0); +} + +static void usbg_cmd_work(struct work_struct *work) +{ + struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); + + /* + * Failure is detected by f_tcm here. Skip submitting the command to the + * target core if we already know the failing response and send the usb + * response to the host directly. + */ + if (cmd->tmr_rsp != RC_RESPONSE_UNKNOWN) + goto skip; + + if (cmd->tmr_func) + usbg_submit_tmr(cmd); + else + usbg_submit_cmd(cmd); + + return; + +skip: + if (cmd->tmr_rsp == RC_OVERLAPPED_TAG) { + struct f_uas *fu = cmd->fu; + struct se_session *se_sess; + struct uas_stream *stream = NULL; + struct hlist_node *tmp; + struct usbg_cmd *active_cmd = NULL; + + se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess; + + hash_for_each_possible_safe(fu->stream_hash, stream, tmp, node, cmd->tag) { + int i = stream - &fu->stream[0]; + + active_cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[i]; + if (active_cmd->tag == cmd->tag) + break; + } + + /* Sanity check */ + if (!stream || (active_cmd && active_cmd->tag != cmd->tag)) { + usbg_submit_command(cmd->fu, cmd->req); + return; + } + + reinit_completion(&stream->cmd_completion); + + /* + * A UASP command consists of the command, data, and status + * stages, each operating sequentially from different endpoints. + * + * Each USB endpoint operates independently, and depending on + * hardware implementation, a completion callback for a transfer + * from one endpoint may not reflect the order of completion on + * the wire. This is particularly true for devices with + * endpoints that have independent interrupts and event buffers. + * + * The driver must still detect misbehaving hosts and respond + * with an overlap status. To reduce false overlap failures, + * allow the active and matching stream ID a brief 1ms to + * complete before responding with an overlap command failure. + * Overlap failure should be rare. + */ + wait_for_completion_timeout(&stream->cmd_completion, msecs_to_jiffies(1)); + + /* If the previous stream is completed, retry the command. */ + if (!hash_hashed(&stream->node)) { + usbg_submit_command(cmd->fu, cmd->req); + return; + } + + /* + * The command isn't submitted to the target core, so we're safe + * to remove the bitmap index from the session tag pool. + */ + sbitmap_queue_clear(&se_sess->sess_tag_pool, + cmd->se_cmd.map_tag, + cmd->se_cmd.map_cpu); + + /* + * Overlap command tag detected. Cancel any pending transfer of + * the command submitted to target core. + */ + active_cmd->tmr_rsp = RC_OVERLAPPED_TAG; + usbg_aborted_task(&active_cmd->se_cmd); + + /* Send the response after the transfer is aborted. */ + return; + } + + uasp_send_tm_response(cmd); } static struct usbg_cmd *usbg_get_cmd(struct f_uas *fu, @@ -1084,6 +1348,7 @@ static struct usbg_cmd *usbg_get_cmd(struct f_uas *fu, memset(cmd, 0, sizeof(*cmd)); cmd->se_cmd.map_tag = tag; cmd->se_cmd.map_cpu = cpu; + cmd->se_cmd.cpuid = cpu; cmd->se_cmd.tag = cmd->tag = scsi_tag; cmd->fu = fu; @@ -1092,50 +1357,82 @@ static struct usbg_cmd *usbg_get_cmd(struct f_uas *fu, static void usbg_release_cmd(struct se_cmd *); -static int usbg_submit_command(struct f_uas *fu, - void *cmdbuf, unsigned int len) +static int usbg_submit_command(struct f_uas *fu, struct usb_request *req) { - struct command_iu *cmd_iu = cmdbuf; + struct iu *iu = req->buf; struct usbg_cmd *cmd; struct usbg_tpg *tpg = fu->tpg; struct tcm_usbg_nexus *tv_nexus; + struct uas_stream *stream; + struct hlist_node *tmp; + struct command_iu *cmd_iu; u32 cmd_len; u16 scsi_tag; - if (cmd_iu->iu_id != IU_ID_COMMAND) { - pr_err("Unsupported type %d\n", cmd_iu->iu_id); - return -EINVAL; - } - tv_nexus = tpg->tpg_nexus; if (!tv_nexus) { pr_err("Missing nexus, ignoring command\n"); return -EINVAL; } - cmd_len = (cmd_iu->len & ~0x3) + 16; - if (cmd_len > USBG_MAX_CMD) - return -EINVAL; - - scsi_tag = be16_to_cpup(&cmd_iu->tag); + scsi_tag = be16_to_cpup(&iu->tag); cmd = usbg_get_cmd(fu, tv_nexus, scsi_tag); if (IS_ERR(cmd)) { pr_err("usbg_get_cmd failed\n"); return -ENOMEM; } - memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len); - if (fu->flags & USBG_USE_STREAMS) { - if (cmd->tag > UASP_SS_EP_COMP_NUM_STREAMS) - goto err; - if (!cmd->tag) - cmd->stream = &fu->stream[0]; - else - cmd->stream = &fu->stream[cmd->tag - 1]; - } else { - cmd->stream = &fu->stream[0]; + cmd->req = req; + cmd->fu = fu; + cmd->tag = scsi_tag; + cmd->se_cmd.tag = scsi_tag; + cmd->tmr_func = 0; + cmd->tmr_rsp = RC_RESPONSE_UNKNOWN; + cmd->flags = 0; + + cmd_iu = (struct command_iu *)iu; + + /* Command and Task Management IUs share the same LUN offset */ + cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun); + + if (iu->iu_id != IU_ID_COMMAND && iu->iu_id != IU_ID_TASK_MGMT) { + cmd->tmr_rsp = RC_INVALID_INFO_UNIT; + goto skip; + } + + hash_for_each_possible_safe(fu->stream_hash, stream, tmp, node, scsi_tag) { + struct usbg_cmd *active_cmd; + struct se_session *se_sess; + int i = stream - &fu->stream[0]; + + se_sess = cmd->fu->tpg->tpg_nexus->tvn_se_sess; + active_cmd = &((struct usbg_cmd *)se_sess->sess_cmd_map)[i]; + + if (active_cmd->tag == scsi_tag) { + cmd->tmr_rsp = RC_OVERLAPPED_TAG; + goto skip; + } } + stream = &fu->stream[cmd->se_cmd.map_tag]; + hash_add(fu->stream_hash, &stream->node, scsi_tag); + + if (iu->iu_id == IU_ID_TASK_MGMT) { + struct task_mgmt_iu *tm_iu; + + tm_iu = (struct task_mgmt_iu *)iu; + cmd->tmr_func = tm_iu->function; + goto skip; + } + + cmd_len = (cmd_iu->len & ~0x3) + 16; + if (cmd_len > USBG_MAX_CMD) { + target_free_tag(tv_nexus->tvn_se_sess, &cmd->se_cmd); + hash_del(&stream->node); + return -EINVAL; + } + memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len); + switch (cmd_iu->prio_attr & 0x7) { case UAS_HEAD_TAG: cmd->prio_attr = TCM_HEAD_TAG; @@ -1155,15 +1452,11 @@ static int usbg_submit_command(struct f_uas *fu, break; } - cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun); - +skip: INIT_WORK(&cmd->work, usbg_cmd_work); queue_work(tpg->workqueue, &cmd->work); return 0; -err: - usbg_release_cmd(&cmd->se_cmd); - return -EINVAL; } static void bot_cmd_work(struct work_struct *work) @@ -1172,30 +1465,40 @@ static void bot_cmd_work(struct work_struct *work) struct se_cmd *se_cmd; struct tcm_usbg_nexus *tv_nexus; struct usbg_tpg *tpg; + int flags = TARGET_SCF_ACK_KREF; int dir; + /* + * Note: each command will spawn its own process, and each stage of the + * command is processed sequentially. Should this no longer be the case, + * locking is needed. + */ + if (cmd->flags & USBG_CMD_PENDING_DATA_WRITE) { + target_execute_cmd(&cmd->se_cmd); + cmd->flags &= ~USBG_CMD_PENDING_DATA_WRITE; + return; + } + se_cmd = &cmd->se_cmd; tpg = cmd->fu->tpg; tv_nexus = tpg->tpg_nexus; dir = get_cmd_dir(cmd->cmd_buf); - if (dir < 0) { - __target_init_cmd(se_cmd, - tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, - tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, - cmd->prio_attr, cmd->sense_iu.sense, - cmd->unpacked_lun, NULL); + if (dir < 0) goto out; - } target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, - cmd->data_len, cmd->prio_attr, dir, 0); + cmd->data_len, cmd->prio_attr, dir, flags); return; out: + __target_init_cmd(se_cmd, + tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, + tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, + cmd->prio_attr, cmd->sense_iu.sense, + cmd->unpacked_lun, NULL); transport_send_check_condition_and_sense(se_cmd, - TCM_UNSUPPORTED_SCSI_OPCODE, 1); - transport_generic_free_cmd(&cmd->se_cmd, 0); + TCM_UNSUPPORTED_SCSI_OPCODE, 0); } static int bot_submit_command(struct f_uas *fu, @@ -1239,6 +1542,7 @@ static int bot_submit_command(struct f_uas *fu, cmd->is_read = cbw->Flags & US_BULK_FLAG_IN ? 1 : 0; cmd->data_len = le32_to_cpu(cbw->DataTransferLength); cmd->se_cmd.tag = le32_to_cpu(cmd->bot_tag); + cmd->flags = 0; INIT_WORK(&cmd->work, bot_cmd_work); queue_work(tpg->workqueue, &cmd->work); @@ -1275,16 +1579,38 @@ static void usbg_release_cmd(struct se_cmd *se_cmd) se_cmd); struct se_session *se_sess = se_cmd->se_sess; + cmd->tag = 0; kfree(cmd->data_buf); target_free_tag(se_sess, se_cmd); } static void usbg_queue_tm_rsp(struct se_cmd *se_cmd) { + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, se_cmd); + + uasp_send_tm_response(cmd); } static void usbg_aborted_task(struct se_cmd *se_cmd) { + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, se_cmd); + struct f_uas *fu = cmd->fu; + struct usb_gadget *gadget = fuas_to_gadget(fu); + struct uas_stream *stream = &fu->stream[se_cmd->map_tag]; + int ret = 0; + + if (stream->req_out->status == -EINPROGRESS) + ret = usb_ep_dequeue(fu->ep_out, stream->req_out); + else if (stream->req_in->status == -EINPROGRESS) + ret = usb_ep_dequeue(fu->ep_in, stream->req_in); + else if (stream->req_status->status == -EINPROGRESS) + ret = usb_ep_dequeue(fu->ep_status, stream->req_status); + + if (ret) + dev_err(&gadget->dev, "Failed to abort cmd tag %d, (%d)\n", + cmd->tag, ret); + + cmd->state = UASP_QUEUE_COMMAND; } static const char *usbg_check_wwn(const char *name) @@ -1355,7 +1681,8 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn, goto unref_dep; mutex_init(&tpg->tpg_mutex); atomic_set(&tpg->tpg_port_count, 0); - tpg->workqueue = alloc_workqueue("tcm_usb_gadget", 0, 1); + tpg->workqueue = alloc_workqueue("tcm_usb_gadget", + WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE); if (!tpg->workqueue) goto free_tpg; @@ -1746,7 +2073,7 @@ static struct usb_endpoint_descriptor uasp_ss_bi_desc = { static struct usb_ss_ep_comp_descriptor uasp_bi_ep_comp_desc = { .bLength = sizeof(uasp_bi_ep_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - .bMaxBurst = 0, + .bMaxBurst = 15, .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, .wBytesPerInterval = 0, }; @@ -1754,7 +2081,7 @@ static struct usb_ss_ep_comp_descriptor uasp_bi_ep_comp_desc = { static struct usb_ss_ep_comp_descriptor bot_bi_ep_comp_desc = { .bLength = sizeof(bot_bi_ep_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - .bMaxBurst = 0, + .bMaxBurst = 15, }; static struct usb_endpoint_descriptor uasp_bo_desc = { @@ -1789,12 +2116,14 @@ static struct usb_endpoint_descriptor uasp_ss_bo_desc = { static struct usb_ss_ep_comp_descriptor uasp_bo_ep_comp_desc = { .bLength = sizeof(uasp_bo_ep_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 15, .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, }; static struct usb_ss_ep_comp_descriptor bot_bo_ep_comp_desc = { .bLength = sizeof(bot_bo_ep_comp_desc), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 15, }; static struct usb_endpoint_descriptor uasp_status_desc = { @@ -1971,43 +2300,39 @@ static int tcm_bind(struct usb_configuration *c, struct usb_function *f) bot_intf_desc.bInterfaceNumber = iface; uasp_intf_desc.bInterfaceNumber = iface; fu->iface = iface; - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bi_desc, - &uasp_bi_ep_comp_desc); + ep = usb_ep_autoconfig(gadget, &uasp_fs_bi_desc); if (!ep) goto ep_fail; fu->ep_in = ep; - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bo_desc, - &uasp_bo_ep_comp_desc); + ep = usb_ep_autoconfig(gadget, &uasp_fs_bo_desc); if (!ep) goto ep_fail; fu->ep_out = ep; - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_status_desc, - &uasp_status_in_ep_comp_desc); + ep = usb_ep_autoconfig(gadget, &uasp_fs_status_desc); if (!ep) goto ep_fail; fu->ep_status = ep; - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_cmd_desc, - &uasp_cmd_comp_desc); + ep = usb_ep_autoconfig(gadget, &uasp_fs_cmd_desc); if (!ep) goto ep_fail; fu->ep_cmd = ep; /* Assume endpoint addresses are the same for both speeds */ - uasp_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; - uasp_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; + uasp_bi_desc.bEndpointAddress = uasp_fs_bi_desc.bEndpointAddress; + uasp_bo_desc.bEndpointAddress = uasp_fs_bo_desc.bEndpointAddress; uasp_status_desc.bEndpointAddress = - uasp_ss_status_desc.bEndpointAddress; - uasp_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; + uasp_fs_status_desc.bEndpointAddress; + uasp_cmd_desc.bEndpointAddress = uasp_fs_cmd_desc.bEndpointAddress; - uasp_fs_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; - uasp_fs_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; - uasp_fs_status_desc.bEndpointAddress = - uasp_ss_status_desc.bEndpointAddress; - uasp_fs_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; + uasp_ss_bi_desc.bEndpointAddress = uasp_fs_bi_desc.bEndpointAddress; + uasp_ss_bo_desc.bEndpointAddress = uasp_fs_bo_desc.bEndpointAddress; + uasp_ss_status_desc.bEndpointAddress = + uasp_fs_status_desc.bEndpointAddress; + uasp_ss_cmd_desc.bEndpointAddress = uasp_fs_cmd_desc.bEndpointAddress; ret = usb_assign_descriptors(f, uasp_fs_function_desc, uasp_hs_function_desc, uasp_ss_function_desc, @@ -2051,9 +2376,14 @@ static void tcm_delayed_set_alt(struct work_struct *wq) static int tcm_get_alt(struct usb_function *f, unsigned intf) { - if (intf == bot_intf_desc.bInterfaceNumber) + struct f_uas *fu = to_f_uas(f); + + if (fu->iface != intf) + return -EOPNOTSUPP; + + if (fu->flags & USBG_IS_BOT) return USB_G_ALT_INT_BBB; - if (intf == uasp_intf_desc.bInterfaceNumber) + else if (fu->flags & USBG_IS_UAS) return USB_G_ALT_INT_UAS; return -EOPNOTSUPP; @@ -2063,6 +2393,9 @@ static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_uas *fu = to_f_uas(f); + if (fu->iface != intf) + return -EOPNOTSUPP; + if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) { struct guas_setup_wq *work; @@ -2271,6 +2604,8 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi) fu->function.disable = tcm_disable; fu->function.free_func = tcm_free; fu->tpg = tpg_instances[i].tpg; + + hash_init(fu->stream_hash); mutex_unlock(&tpg_instances_lock); return &fu->function; diff --git a/drivers/usb/gadget/function/storage_common.h b/drivers/usb/gadget/function/storage_common.h index ced5d2b09234..11ac785d5eee 100644 --- a/drivers/usb/gadget/function/storage_common.h +++ b/drivers/usb/gadget/function/storage_common.h @@ -131,7 +131,7 @@ static inline bool fsg_lun_is_open(struct fsg_lun *curlun) #define FSG_BUFLEN ((u32)16384) /* Maximal number of LUNs supported in mass storage function */ -#define FSG_MAX_LUNS 16 +#define FSG_MAX_LUNS (US_BULK_MAX_LUN_LIMIT + 1) enum fsg_buffer_state { BUF_STATE_SENDING = -2, diff --git a/drivers/usb/gadget/function/tcm.h b/drivers/usb/gadget/function/tcm.h index 3cd565794ad7..009974d81d66 100644 --- a/drivers/usb/gadget/function/tcm.h +++ b/drivers/usb/gadget/function/tcm.h @@ -4,6 +4,7 @@ #include <linux/kref.h> /* #include <linux/usb/uas.h> */ +#include <linux/hashtable.h> #include <linux/usb/composite.h> #include <linux/usb/uas.h> #include <linux/usb/storage.h> @@ -13,9 +14,11 @@ #define USBG_NAMELEN 32 #define fuas_to_gadget(f) (f->function.config->cdev->gadget) -#define UASP_SS_EP_COMP_LOG_STREAMS 4 +#define UASP_SS_EP_COMP_LOG_STREAMS 5 #define UASP_SS_EP_COMP_NUM_STREAMS (1 << UASP_SS_EP_COMP_LOG_STREAMS) +#define USBG_NUM_CMDS (UASP_SS_EP_COMP_NUM_STREAMS + 1) + enum { USB_G_STR_INT_UAS = 0, USB_G_STR_INT_BBB, @@ -24,7 +27,7 @@ enum { #define USB_G_ALT_INT_BBB 0 #define USB_G_ALT_INT_UAS 1 -#define USB_G_DEFAULT_SESSION_TAGS 128 +#define USB_G_DEFAULT_SESSION_TAGS USBG_NUM_CMDS struct tcm_usbg_nexus { struct se_session *tvn_se_sess; @@ -72,15 +75,23 @@ struct usbg_cmd { struct se_cmd se_cmd; void *data_buf; /* used if no sg support available */ struct f_uas *fu; - struct completion write_complete; struct kref ref; + struct usb_request *req; + + u32 flags; +#define USBG_CMD_PENDING_DATA_WRITE BIT(0) + /* UAS only */ u16 tag; u16 prio_attr; struct sense_iu sense_iu; + struct response_iu response_iu; enum uas_state state; - struct uas_stream *stream; + + int tmr_func; + int tmr_rsp; +#define RC_RESPONSE_UNKNOWN 0xff /* BOT only */ __le32 bot_tag; @@ -93,6 +104,9 @@ struct uas_stream { struct usb_request *req_in; struct usb_request *req_out; struct usb_request *req_status; + + struct completion cmd_completion; + struct hlist_node node; }; struct usbg_cdb { @@ -116,15 +130,17 @@ struct f_uas { #define USBG_USE_STREAMS (1 << 2) #define USBG_IS_BOT (1 << 3) #define USBG_BOT_CMD_PEND (1 << 4) +#define USBG_BOT_WEDGED (1 << 5) - struct usbg_cdb cmd; + struct usbg_cdb cmd[USBG_NUM_CMDS]; struct usb_ep *ep_in; struct usb_ep *ep_out; /* UAS */ struct usb_ep *ep_status; struct usb_ep *ep_cmd; - struct uas_stream stream[UASP_SS_EP_COMP_NUM_STREAMS]; + struct uas_stream stream[USBG_NUM_CMDS]; + DECLARE_HASHTABLE(stream_hash, UASP_SS_EP_COMP_LOG_STREAMS); /* BOT */ struct bot_status bot_status; diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h index 9e94cebf4a56..f6b9a00a0ab9 100644 --- a/drivers/usb/host/xhci-caps.h +++ b/drivers/usb/host/xhci-caps.h @@ -83,3 +83,9 @@ #define HCC2_CIC(p) ((p) & (1 << 5)) /* true: HC support Extended TBC Capability, Isoc burst count > 65535 */ #define HCC2_ETC(p) ((p) & (1 << 6)) +/* true: HC support Extended TBC TRB Status Capability */ +#define HCC2_ETC_TSC(p) ((p) & (1 << 7)) +/* true: HC support Get/Set Extended Property Capability */ +#define HCC2_GSC(p) ((p) & (1 << 8)) +/* true: HC support Virtualization Based Trusted I/O Capability */ +#define HCC2_VTC(p) ((p) & (1 << 9)) diff --git a/drivers/usb/host/xhci-dbgcap.c b/drivers/usb/host/xhci-dbgcap.c index 227e513867dd..fd7895b24367 100644 --- a/drivers/usb/host/xhci-dbgcap.c +++ b/drivers/usb/host/xhci-dbgcap.c @@ -957,7 +957,7 @@ static void xhci_dbc_handle_events(struct work_struct *work) /* set fast poll rate if there are pending data transfers */ if (!list_empty(&dbc->eps[BULK_OUT].list_pending) || !list_empty(&dbc->eps[BULK_IN].list_pending)) - poll_interval = 1; + poll_interval = 0; break; default: dev_info(dbc->dev, "stop handling dbc events\n"); diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index d719c16ea30b..60ed753c85bb 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -110,15 +110,74 @@ static void dbc_start_rx(struct dbc_port *port) } } +/* + * Queue received data to tty buffer and push it. + * + * Returns nr of remaining bytes that didn't fit tty buffer, i.e. 0 if all + * bytes sucessfullt moved. In case of error returns negative errno. + * Call with lock held + */ +static int dbc_rx_push_buffer(struct dbc_port *port, struct dbc_request *req) +{ + char *packet = req->buf; + unsigned int n, size = req->actual; + int count; + + if (!req->actual) + return 0; + + /* if n_read is set then request was partially moved to tty buffer */ + n = port->n_read; + if (n) { + packet += n; + size -= n; + } + + count = tty_insert_flip_string(&port->port, packet, size); + if (count) + tty_flip_buffer_push(&port->port); + if (count != size) { + port->n_read += count; + return size - count; + } + + port->n_read = 0; + return 0; +} + static void dbc_read_complete(struct xhci_dbc *dbc, struct dbc_request *req) { unsigned long flags; struct dbc_port *port = dbc_to_port(dbc); + struct tty_struct *tty; + int untransferred; + + tty = port->port.tty; spin_lock_irqsave(&port->port_lock, flags); + + /* + * Only defer copyig data to tty buffer in case: + * - !list_empty(&port->read_queue), there are older pending data + * - tty is throttled + * - failed to copy all data to buffer, defer remaining part + */ + + if (list_empty(&port->read_queue) && tty && !tty_throttled(tty)) { + untransferred = dbc_rx_push_buffer(port, req); + if (untransferred == 0) { + list_add_tail(&req->list_pool, &port->read_pool); + if (req->status != -ESHUTDOWN) + dbc_start_rx(port); + goto out; + } + } + + /* defer moving data from req to tty buffer to a tasklet */ list_add_tail(&req->list_pool, &port->read_queue); tasklet_schedule(&port->push); +out: spin_unlock_irqrestore(&port->port_lock, flags); } @@ -331,10 +390,10 @@ static void dbc_rx_push(struct tasklet_struct *t) struct dbc_request *req; struct tty_struct *tty; unsigned long flags; - bool do_push = false; bool disconnect = false; struct dbc_port *port = from_tasklet(port, t, push); struct list_head *queue = &port->read_queue; + int untransferred; spin_lock_irqsave(&port->port_lock, flags); tty = port->port.tty; @@ -356,42 +415,15 @@ static void dbc_rx_push(struct tasklet_struct *t) break; } - if (req->actual) { - char *packet = req->buf; - unsigned int n, size = req->actual; - int count; - - n = port->n_read; - if (n) { - packet += n; - size -= n; - } - - count = tty_insert_flip_string(&port->port, packet, - size); - if (count) - do_push = true; - if (count != size) { - port->n_read += count; - break; - } - port->n_read = 0; - } + untransferred = dbc_rx_push_buffer(port, req); + if (untransferred > 0) + break; list_move_tail(&req->list_pool, &port->read_pool); } - if (do_push) - tty_flip_buffer_push(&port->port); - - if (!list_empty(queue) && tty) { - if (!tty_throttled(tty)) { - if (do_push) - tasklet_schedule(&port->push); - else - pr_warn("ttyDBC0: RX not scheduled?\n"); - } - } + if (!list_empty(queue)) + tasklet_schedule(&port->push); if (!disconnect) dbc_start_rx(port); diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index db109b570c5c..e88814c5743b 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -330,6 +330,8 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s usb3_hcd->can_do_streams = 1; if (xhci->shared_hcd) { + xhci->shared_hcd->rsrc_start = hcd->rsrc_start; + xhci->shared_hcd->rsrc_len = hcd->rsrc_len; ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); if (ret) goto put_usb3_hcd; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 09b05a62375e..3d66ed240dc3 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -422,7 +422,8 @@ static void xhci_handle_stopped_cmd_ring(struct xhci_hcd *xhci, if ((xhci->cmd_ring->dequeue != xhci->cmd_ring->enqueue) && !(xhci->xhc_state & XHCI_STATE_DYING)) { xhci->current_cmd = cur_cmd; - xhci_mod_cmd_timer(xhci); + if (cur_cmd) + xhci_mod_cmd_timer(xhci); xhci_ring_cmd_db(xhci); } } @@ -1649,12 +1650,13 @@ static void xhci_handle_cmd_nec_get_fw(struct xhci_hcd *xhci, NEC_FW_MINOR(le32_to_cpu(event->status))); } -static void xhci_complete_del_and_free_cmd(struct xhci_command *cmd, u32 status) +static void xhci_complete_del_and_free_cmd(struct xhci_command *cmd, u32 comp_code, u32 comp_param) { list_del(&cmd->cmd_list); if (cmd->completion) { - cmd->status = status; + cmd->status = comp_code; + cmd->comp_param = comp_param; complete(cmd->completion); } else { kfree(cmd); @@ -1666,7 +1668,7 @@ void xhci_cleanup_command_queue(struct xhci_hcd *xhci) struct xhci_command *cur_cmd, *tmp_cmd; xhci->current_cmd = NULL; list_for_each_entry_safe(cur_cmd, tmp_cmd, &xhci->cmd_list, cmd_list) - xhci_complete_del_and_free_cmd(cur_cmd, COMP_COMMAND_ABORTED); + xhci_complete_del_and_free_cmd(cur_cmd, COMP_COMMAND_ABORTED, 0); } void xhci_handle_command_timeout(struct work_struct *work) @@ -1751,6 +1753,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, struct xhci_event_cmd *event) { unsigned int slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + u32 status = le32_to_cpu(event->status); u64 cmd_dma; dma_addr_t cmd_dequeue_dma; u32 cmd_comp_code; @@ -1879,7 +1882,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, } event_handled: - xhci_complete_del_and_free_cmd(cmd, cmd_comp_code); + xhci_complete_del_and_free_cmd(cmd, cmd_comp_code, COMP_PARAM(status)); inc_deq(xhci, xhci->cmd_ring); } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 4914f0a10cff..8c164340a2c3 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -529,6 +529,7 @@ struct xhci_command { /* Input context for changing device state */ struct xhci_container_ctx *in_ctx; u32 status; + u32 comp_param; int slot_id; /* If completion is null, no one is waiting on this command * and the structure can be freed after the command completes. @@ -959,6 +960,9 @@ struct xhci_event_cmd { __le32 flags; }; +/* status bitmasks */ +#define COMP_PARAM(p) ((p) & 0xffffff) /* Command Completion Parameter */ + /* Address device - disable SetAddress */ #define TRB_BSR (1<<9) diff --git a/drivers/usb/storage/Kconfig b/drivers/usb/storage/Kconfig index d17b60a644ef..4be1d617d63d 100644 --- a/drivers/usb/storage/Kconfig +++ b/drivers/usb/storage/Kconfig @@ -3,8 +3,7 @@ # USB Storage driver configuration # -comment "NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may" -comment "also be needed; see USB_STORAGE Help for more info" +comment "NOTE: USB_STORAGE depends on SCSI but BLK_DEV_SD may also be needed; see USB_STORAGE Help for more info" config USB_STORAGE tristate "USB Mass Storage support" diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c index 9d767f6bf722..e6bc8ecaecbb 100644 --- a/drivers/usb/storage/transport.c +++ b/drivers/usb/storage/transport.c @@ -1087,13 +1087,9 @@ int usb_stor_Bulk_max_lun(struct us_data *us) usb_stor_dbg(us, "GetMaxLUN command result is %d, data is %d\n", result, us->iobuf[0]); - /* - * If we have a successful request, return the result if valid. The - * CBW LUN field is 4 bits wide, so the value reported by the device - * should fit into that. - */ + /* If we have a successful request, return the result if valid. */ if (result > 0) { - if (us->iobuf[0] < 16) { + if (us->iobuf[0] <= US_BULK_MAX_LUN_LIMIT) { return us->iobuf[0]; } else { dev_info(&us->pusb_intf->dev, diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig index 1a6b5e872b0d..7867fa7c405d 100644 --- a/drivers/usb/typec/altmodes/Kconfig +++ b/drivers/usb/typec/altmodes/Kconfig @@ -23,4 +23,13 @@ config TYPEC_NVIDIA_ALTMODE To compile this driver as a module, choose M here: the module will be called typec_nvidia. +config TYPEC_TBT_ALTMODE + tristate "Thunderbolt3 Alternate Mode driver" + help + Select this option if you have Thunderbolt3 hardware on your + system. + + To compile this driver as a module, choose M here: the + module will be called typec_thunderbolt. + endmenu diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile index 45717548b396..508a68351bd2 100644 --- a/drivers/usb/typec/altmodes/Makefile +++ b/drivers/usb/typec/altmodes/Makefile @@ -4,3 +4,5 @@ obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o typec_displayport-y := displayport.o obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE) += typec_nvidia.o typec_nvidia-y := nvidia.o +obj-$(CONFIG_TYPEC_TBT_ALTMODE) += typec_thunderbolt.o +typec_thunderbolt-y := thunderbolt.o diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 2f03190a9873..ac84a6d64c2f 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -252,7 +252,7 @@ static void dp_altmode_work(struct work_struct *work) case DP_STATE_ENTER: ret = typec_altmode_enter(dp->alt, NULL); if (ret && ret != -EBUSY) - dev_err(&dp->alt->dev, "failed to enter mode\n"); + dev_err(&dp->alt->dev, "failed to enter mode: %d\n", ret); break; case DP_STATE_ENTER_PRIME: ret = typec_cable_altmode_enter(dp->alt, TYPEC_PLUG_SOP_P, NULL); @@ -791,7 +791,7 @@ void dp_altmode_remove(struct typec_altmode *alt) EXPORT_SYMBOL_GPL(dp_altmode_remove); static const struct typec_device_id dp_typec_id[] = { - { USB_TYPEC_DP_SID, USB_TYPEC_DP_MODE }, + { USB_TYPEC_DP_SID }, { }, }; MODULE_DEVICE_TABLE(typec, dp_typec_id); diff --git a/drivers/usb/typec/altmodes/nvidia.c b/drivers/usb/typec/altmodes/nvidia.c index fe70b36f078f..2b77d931e494 100644 --- a/drivers/usb/typec/altmodes/nvidia.c +++ b/drivers/usb/typec/altmodes/nvidia.c @@ -24,7 +24,7 @@ static void nvidia_altmode_remove(struct typec_altmode *alt) } static const struct typec_device_id nvidia_typec_id[] = { - { USB_TYPEC_NVIDIA_VLINK_SID, TYPEC_ANY_MODE }, + { USB_TYPEC_NVIDIA_VLINK_SID }, { }, }; MODULE_DEVICE_TABLE(typec, nvidia_typec_id); diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c new file mode 100644 index 000000000000..1b475b1d98e7 --- /dev/null +++ b/drivers/usb/typec/altmodes/thunderbolt.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB Typec-C Thunderbolt3 Alternate Mode driver + * + * Copyright (C) 2019 Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + */ + +#include <linux/lockdep.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/usb/pd_vdo.h> +#include <linux/usb/typec_altmode.h> +#include <linux/usb/typec_tbt.h> + +enum tbt_state { + TBT_STATE_IDLE, + TBT_STATE_SOP_P_ENTER, + TBT_STATE_SOP_PP_ENTER, + TBT_STATE_ENTER, + TBT_STATE_EXIT, + TBT_STATE_SOP_PP_EXIT, + TBT_STATE_SOP_P_EXIT +}; + +struct tbt_altmode { + enum tbt_state state; + struct typec_cable *cable; + struct typec_altmode *alt; + struct typec_altmode *plug[2]; + u32 enter_vdo; + + struct work_struct work; + struct mutex lock; /* device lock */ +}; + +static bool tbt_ready(struct typec_altmode *alt); + +static int tbt_enter_mode(struct tbt_altmode *tbt) +{ + struct typec_altmode *plug = tbt->plug[TYPEC_PLUG_SOP_P]; + u32 vdo; + + vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1); + vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0; + vdo |= TBT_MODE; + + if (plug) { + if (typec_cable_is_active(tbt->cable)) + vdo |= TBT_ENTER_MODE_ACTIVE_CABLE; + + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo)); + vdo |= plug->vdo & TBT_CABLE_ROUNDED; + vdo |= plug->vdo & TBT_CABLE_OPTICAL; + vdo |= plug->vdo & TBT_CABLE_RETIMER; + vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING; + } else { + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE); + } + + tbt->enter_vdo = vdo; + return typec_altmode_enter(tbt->alt, &vdo); +} + +static void tbt_altmode_work(struct work_struct *work) +{ + struct tbt_altmode *tbt = container_of(work, struct tbt_altmode, work); + int ret; + + mutex_lock(&tbt->lock); + + switch (tbt->state) { + case TBT_STATE_SOP_P_ENTER: + ret = typec_cable_altmode_enter(tbt->alt, TYPEC_PLUG_SOP_P, NULL); + if (ret) { + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_P]->dev, + "failed to enter mode (%d)\n", ret); + goto disable_plugs; + } + break; + case TBT_STATE_SOP_PP_ENTER: + ret = typec_cable_altmode_enter(tbt->alt, TYPEC_PLUG_SOP_PP, NULL); + if (ret) { + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_PP]->dev, + "failed to enter mode (%d)\n", ret); + goto disable_plugs; + } + break; + case TBT_STATE_ENTER: + ret = tbt_enter_mode(tbt); + if (ret) + dev_dbg(&tbt->alt->dev, "failed to enter mode (%d)\n", + ret); + break; + case TBT_STATE_EXIT: + typec_altmode_exit(tbt->alt); + break; + case TBT_STATE_SOP_PP_EXIT: + typec_cable_altmode_exit(tbt->alt, TYPEC_PLUG_SOP_PP); + break; + case TBT_STATE_SOP_P_EXIT: + typec_cable_altmode_exit(tbt->alt, TYPEC_PLUG_SOP_P); + break; + default: + break; + } + + tbt->state = TBT_STATE_IDLE; + + mutex_unlock(&tbt->lock); + return; + +disable_plugs: + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) { + if (tbt->plug[i]) + typec_altmode_put_plug(tbt->plug[i]); + + tbt->plug[i] = NULL; + } + + tbt->state = TBT_STATE_ENTER; + schedule_work(&tbt->work); + mutex_unlock(&tbt->lock); +} + +/* + * If SOP' is available, enter that first (which will trigger a VDM response + * that will enter SOP" if available and then the port). If entering SOP' fails, + * stop attempting to enter either cable altmode (probably not supported) and + * directly enter the port altmode. + */ +static int tbt_enter_modes_ordered(struct typec_altmode *alt) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + int ret = 0; + + lockdep_assert_held(&tbt->lock); + + if (!tbt_ready(tbt->alt)) + return -ENODEV; + + if (tbt->plug[TYPEC_PLUG_SOP_P]) { + ret = typec_cable_altmode_enter(alt, TYPEC_PLUG_SOP_P, NULL); + if (ret < 0) { + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) { + if (tbt->plug[i]) + typec_altmode_put_plug(tbt->plug[i]); + + tbt->plug[i] = NULL; + } + } else { + return ret; + } + } + + return tbt_enter_mode(tbt); +} + +static int tbt_cable_altmode_vdm(struct typec_altmode *alt, + enum typec_plug_index sop, const u32 hdr, + const u32 *vdo, int count) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + int cmd_type = PD_VDO_CMDT(hdr); + int cmd = PD_VDO_CMD(hdr); + + mutex_lock(&tbt->lock); + + if (tbt->state != TBT_STATE_IDLE) { + mutex_unlock(&tbt->lock); + return -EBUSY; + } + + switch (cmd_type) { + case CMDT_RSP_ACK: + switch (cmd) { + case CMD_ENTER_MODE: + /* + * Following the order described in USB Type-C Spec + * R2.0 Section 6.7.3: SOP', SOP", then port. + */ + if (sop == TYPEC_PLUG_SOP_P) { + if (tbt->plug[TYPEC_PLUG_SOP_PP]) + tbt->state = TBT_STATE_SOP_PP_ENTER; + else + tbt->state = TBT_STATE_ENTER; + } else if (sop == TYPEC_PLUG_SOP_PP) + tbt->state = TBT_STATE_ENTER; + + break; + case CMD_EXIT_MODE: + /* Exit in opposite order: Port, SOP", then SOP'. */ + if (sop == TYPEC_PLUG_SOP_PP) + tbt->state = TBT_STATE_SOP_P_EXIT; + break; + } + break; + default: + break; + } + + if (tbt->state != TBT_STATE_IDLE) + schedule_work(&tbt->work); + + mutex_unlock(&tbt->lock); + return 0; +} + +static int tbt_altmode_vdm(struct typec_altmode *alt, + const u32 hdr, const u32 *vdo, int count) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + struct typec_thunderbolt_data data; + int cmd_type = PD_VDO_CMDT(hdr); + int cmd = PD_VDO_CMD(hdr); + + mutex_lock(&tbt->lock); + + if (tbt->state != TBT_STATE_IDLE) { + mutex_unlock(&tbt->lock); + return -EBUSY; + } + + switch (cmd_type) { + case CMDT_RSP_ACK: + /* Port altmode is last to enter and first to exit. */ + switch (cmd) { + case CMD_ENTER_MODE: + memset(&data, 0, sizeof(data)); + + data.device_mode = tbt->alt->vdo; + data.enter_vdo = tbt->enter_vdo; + if (tbt->plug[TYPEC_PLUG_SOP_P]) + data.cable_mode = tbt->plug[TYPEC_PLUG_SOP_P]->vdo; + + typec_altmode_notify(alt, TYPEC_STATE_MODAL, &data); + break; + case CMD_EXIT_MODE: + if (tbt->plug[TYPEC_PLUG_SOP_PP]) + tbt->state = TBT_STATE_SOP_PP_EXIT; + else if (tbt->plug[TYPEC_PLUG_SOP_P]) + tbt->state = TBT_STATE_SOP_P_EXIT; + break; + } + break; + case CMDT_RSP_NAK: + switch (cmd) { + case CMD_ENTER_MODE: + dev_warn(&alt->dev, "Enter Mode refused\n"); + break; + default: + break; + } + break; + default: + break; + } + + if (tbt->state != TBT_STATE_IDLE) + schedule_work(&tbt->work); + + mutex_unlock(&tbt->lock); + + return 0; +} + +static int tbt_altmode_activate(struct typec_altmode *alt, int activate) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + int ret; + + mutex_lock(&tbt->lock); + + if (activate) + ret = tbt_enter_modes_ordered(alt); + else + ret = typec_altmode_exit(alt); + + mutex_unlock(&tbt->lock); + + return ret; +} + +static const struct typec_altmode_ops tbt_altmode_ops = { + .vdm = tbt_altmode_vdm, + .activate = tbt_altmode_activate +}; + +static const struct typec_cable_ops tbt_cable_ops = { + .vdm = tbt_cable_altmode_vdm, +}; + +static int tbt_altmode_probe(struct typec_altmode *alt) +{ + struct tbt_altmode *tbt; + + tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL); + if (!tbt) + return -ENOMEM; + + INIT_WORK(&tbt->work, tbt_altmode_work); + mutex_init(&tbt->lock); + tbt->alt = alt; + + alt->desc = "Thunderbolt3"; + typec_altmode_set_drvdata(alt, tbt); + typec_altmode_set_ops(alt, &tbt_altmode_ops); + + if (tbt_ready(alt)) { + if (tbt->plug[TYPEC_PLUG_SOP_P]) + tbt->state = TBT_STATE_SOP_P_ENTER; + else if (tbt->plug[TYPEC_PLUG_SOP_PP]) + tbt->state = TBT_STATE_SOP_PP_ENTER; + else + tbt->state = TBT_STATE_ENTER; + schedule_work(&tbt->work); + } + + return 0; +} + +static void tbt_altmode_remove(struct typec_altmode *alt) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) { + if (tbt->plug[i]) + typec_altmode_put_plug(tbt->plug[i]); + } + + if (tbt->cable) + typec_cable_put(tbt->cable); +} + +static bool tbt_ready(struct typec_altmode *alt) +{ + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); + struct typec_altmode *plug; + + if (tbt->cable) + return true; + + /* Thunderbolt 3 requires a cable with eMarker */ + tbt->cable = typec_cable_get(typec_altmode2port(tbt->alt)); + if (!tbt->cable) + return false; + + /* We accept systems without SOP' or SOP''. This means the port altmode + * driver will be responsible for properly ordering entry/exit. + */ + for (int i = 0; i < TYPEC_PLUG_SOP_PP + 1; i++) { + plug = typec_altmode_get_plug(tbt->alt, i); + if (IS_ERR(plug)) + continue; + + if (!plug || plug->svid != USB_TYPEC_TBT_SID) + break; + + plug->desc = "Thunderbolt3"; + plug->cable_ops = &tbt_cable_ops; + typec_altmode_set_drvdata(plug, tbt); + + tbt->plug[i] = plug; + } + + return true; +} + +static const struct typec_device_id tbt_typec_id[] = { + { USB_TYPEC_TBT_SID }, + { } +}; +MODULE_DEVICE_TABLE(typec, tbt_typec_id); + +static struct typec_altmode_driver tbt_altmode_driver = { + .id_table = tbt_typec_id, + .probe = tbt_altmode_probe, + .remove = tbt_altmode_remove, + .driver = { + .name = "typec-thunderbolt", + } +}; +module_typec_altmode_driver(tbt_altmode_driver); + +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode"); diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c index aa879253d3b8..ae90688d23e4 100644 --- a/drivers/usb/typec/bus.c +++ b/drivers/usb/typec/bus.c @@ -454,8 +454,7 @@ static int typec_match(struct device *dev, const struct device_driver *driver) const struct typec_device_id *id; for (id = drv->id_table; id->svid; id++) - if (id->svid == altmode->svid && - (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode)) + if (id->svid == altmode->svid) return 1; return 0; } @@ -470,8 +469,7 @@ static int typec_uevent(const struct device *dev, struct kobj_uevent_env *env) if (add_uevent_var(env, "MODE=%u", altmode->mode)) return -ENOMEM; - return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X", - altmode->svid, altmode->mode); + return add_uevent_var(env, "MODALIAS=typec:id%04X", altmode->svid); } static int typec_altmode_create_links(struct altmode *alt) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 4b3047e055a3..b5e67a57762c 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -237,13 +237,13 @@ static int altmode_match(struct device *dev, void *data) if (!is_typec_altmode(dev)) return 0; - return ((adev->svid == id->svid) && (adev->mode == id->mode)); + return (adev->svid == id->svid); } static void typec_altmode_set_partner(struct altmode *altmode) { struct typec_altmode *adev = &altmode->adev; - struct typec_device_id id = { adev->svid, adev->mode, }; + struct typec_device_id id = { adev->svid }; struct typec_port *port = typec_altmode2port(adev); struct altmode *partner; struct device *dev; @@ -458,7 +458,8 @@ static umode_t typec_altmode_attr_is_visible(struct kobject *kobj, struct typec_altmode *adev = to_typec_altmode(kobj_to_dev(kobj)); if (attr == &dev_attr_active.attr) - if (!adev->ops || !adev->ops->activate) + if (!is_typec_port(adev->dev.parent) && + (!adev->ops || !adev->ops->activate)) return 0444; return attr->mode; @@ -563,7 +564,7 @@ typec_register_altmode(struct device *parent, if (is_port) { alt->attrs[3] = &dev_attr_supported_roles.attr; - alt->adev.active = true; /* Enabled by default */ + alt->adev.active = !desc->inactive; /* Enabled by default */ } sprintf(alt->group_name, "mode%d", desc->mode); diff --git a/drivers/usb/typec/hd3ss3220.c b/drivers/usb/typec/hd3ss3220.c index fb1242e82ffd..3ecc688dda82 100644 --- a/drivers/usb/typec/hd3ss3220.c +++ b/drivers/usb/typec/hd3ss3220.c @@ -16,10 +16,17 @@ #include <linux/delay.h> #include <linux/workqueue.h> +#define HD3SS3220_REG_CN_STAT 0x08 #define HD3SS3220_REG_CN_STAT_CTRL 0x09 #define HD3SS3220_REG_GEN_CTRL 0x0A #define HD3SS3220_REG_DEV_REV 0xA0 +/* Register HD3SS3220_REG_CN_STAT */ +#define HD3SS3220_REG_CN_STAT_CURRENT_MODE_MASK (BIT(7) | BIT(6)) +#define HD3SS3220_REG_CN_STAT_CURRENT_MODE_DEFAULT 0x00 +#define HD3SS3220_REG_CN_STAT_CURRENT_MODE_MID BIT(6) +#define HD3SS3220_REG_CN_STAT_CURRENT_MODE_HIGH BIT(7) + /* Register HD3SS3220_REG_CN_STAT_CTRL*/ #define HD3SS3220_REG_CN_STAT_CTRL_ATTACHED_STATE_MASK (BIT(7) | BIT(6)) #define HD3SS3220_REG_CN_STAT_CTRL_AS_DFP BIT(6) @@ -28,10 +35,16 @@ #define HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS BIT(4) /* Register HD3SS3220_REG_GEN_CTRL*/ +#define HD3SS3220_REG_GEN_CTRL_DISABLE_TERM BIT(0) #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_MASK (BIT(2) | BIT(1)) #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT 0x00 #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SNK BIT(1) #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SRC (BIT(2) | BIT(1)) +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_MASK (BIT(5) | BIT(4)) +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DEFAULT 0x00 +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DFP BIT(5) +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_UFP BIT(4) +#define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DRP (BIT(5) | BIT(4)) struct hd3ss3220 { struct device *dev; @@ -43,8 +56,96 @@ struct hd3ss3220 { bool poll; }; -static int hd3ss3220_set_source_pref(struct hd3ss3220 *hd3ss3220, int src_pref) +static int hd3ss3220_set_power_opmode(struct hd3ss3220 *hd3ss3220, int power_opmode) +{ + int current_mode; + + switch (power_opmode) { + case TYPEC_PWR_MODE_USB: + current_mode = HD3SS3220_REG_CN_STAT_CURRENT_MODE_DEFAULT; + break; + case TYPEC_PWR_MODE_1_5A: + current_mode = HD3SS3220_REG_CN_STAT_CURRENT_MODE_MID; + break; + case TYPEC_PWR_MODE_3_0A: + current_mode = HD3SS3220_REG_CN_STAT_CURRENT_MODE_HIGH; + break; + case TYPEC_PWR_MODE_PD: /* Power delivery not supported */ + default: + dev_err(hd3ss3220->dev, "bad power operation mode: %d\n", power_opmode); + return -EINVAL; + } + + return regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_CN_STAT, + HD3SS3220_REG_CN_STAT_CURRENT_MODE_MASK, + current_mode); +} + +static int hd3ss3220_set_port_type(struct hd3ss3220 *hd3ss3220, int type) +{ + int mode_select, err; + + switch (type) { + case TYPEC_PORT_SRC: + mode_select = HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DFP; + break; + case TYPEC_PORT_SNK: + mode_select = HD3SS3220_REG_GEN_CTRL_MODE_SELECT_UFP; + break; + case TYPEC_PORT_DRP: + mode_select = HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DRP; + break; + default: + dev_err(hd3ss3220->dev, "bad port type: %d\n", type); + return -EINVAL; + } + + /* Disable termination before changing MODE_SELECT as required by datasheet */ + err = regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, + HD3SS3220_REG_GEN_CTRL_DISABLE_TERM, + HD3SS3220_REG_GEN_CTRL_DISABLE_TERM); + if (err < 0) { + dev_err(hd3ss3220->dev, "Failed to disable port for mode change: %d\n", err); + return err; + } + + err = regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, + HD3SS3220_REG_GEN_CTRL_MODE_SELECT_MASK, + mode_select); + if (err < 0) { + dev_err(hd3ss3220->dev, "Failed to change mode: %d\n", err); + regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, + HD3SS3220_REG_GEN_CTRL_DISABLE_TERM, 0); + return err; + } + + err = regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, + HD3SS3220_REG_GEN_CTRL_DISABLE_TERM, 0); + if (err < 0) + dev_err(hd3ss3220->dev, "Failed to re-enable port after mode change: %d\n", err); + + return err; +} + +static int hd3ss3220_set_source_pref(struct hd3ss3220 *hd3ss3220, int prefer_role) { + int src_pref; + + switch (prefer_role) { + case TYPEC_NO_PREFERRED_ROLE: + src_pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT; + break; + case TYPEC_SINK: + src_pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SNK; + break; + case TYPEC_SOURCE: + src_pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SRC; + break; + default: + dev_err(hd3ss3220->dev, "bad role preference: %d\n", prefer_role); + return -EINVAL; + } + return regmap_update_bits(hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, HD3SS3220_REG_GEN_CTRL_SRC_PREF_MASK, src_pref); @@ -76,31 +177,23 @@ static enum usb_role hd3ss3220_get_attached_state(struct hd3ss3220 *hd3ss3220) return attached_state; } -static int hd3ss3220_dr_set(struct typec_port *port, enum typec_data_role role) +static int hd3ss3220_try_role(struct typec_port *port, int role) { struct hd3ss3220 *hd3ss3220 = typec_get_drvdata(port); - enum usb_role role_val; - int pref, ret = 0; - if (role == TYPEC_HOST) { - role_val = USB_ROLE_HOST; - pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SRC; - } else { - role_val = USB_ROLE_DEVICE; - pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SNK; - } - - ret = hd3ss3220_set_source_pref(hd3ss3220, pref); - usleep_range(10, 100); + return hd3ss3220_set_source_pref(hd3ss3220, role); +} - usb_role_switch_set_role(hd3ss3220->role_sw, role_val); - typec_set_data_role(hd3ss3220->port, role); +static int hd3ss3220_port_type_set(struct typec_port *port, enum typec_port_type type) +{ + struct hd3ss3220 *hd3ss3220 = typec_get_drvdata(port); - return ret; + return hd3ss3220_set_port_type(hd3ss3220, type); } static const struct typec_operations hd3ss3220_ops = { - .dr_set = hd3ss3220_dr_set + .try_role = hd3ss3220_try_role, + .port_type_set = hd3ss3220_port_type_set, }; static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) @@ -108,9 +201,6 @@ static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) enum usb_role role_state = hd3ss3220_get_attached_state(hd3ss3220); usb_role_switch_set_role(hd3ss3220->role_sw, role_state); - if (role_state == USB_ROLE_NONE) - hd3ss3220_set_source_pref(hd3ss3220, - HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT); switch (role_state) { case USB_ROLE_HOST: @@ -162,6 +252,67 @@ static irqreturn_t hd3ss3220_irq_handler(int irq, void *data) return hd3ss3220_irq(hd3ss3220); } +static int hd3ss3220_configure_power_opmode(struct hd3ss3220 *hd3ss3220, + struct fwnode_handle *connector) +{ + /* + * Supported power operation mode can be configured through device tree + */ + const char *cap_str; + int ret, power_opmode; + + ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str); + if (ret) + return 0; + + power_opmode = typec_find_pwr_opmode(cap_str); + return hd3ss3220_set_power_opmode(hd3ss3220, power_opmode); +} + +static int hd3ss3220_configure_port_type(struct hd3ss3220 *hd3ss3220, + struct fwnode_handle *connector, + struct typec_capability *cap) +{ + /* + * Port type can be configured through device tree + */ + const char *cap_str; + int ret; + + ret = fwnode_property_read_string(connector, "power-role", &cap_str); + if (ret) + return 0; + + ret = typec_find_port_power_role(cap_str); + if (ret < 0) + return ret; + + cap->type = ret; + return hd3ss3220_set_port_type(hd3ss3220, cap->type); +} + +static int hd3ss3220_configure_source_pref(struct hd3ss3220 *hd3ss3220, + struct fwnode_handle *connector, + struct typec_capability *cap) +{ + /* + * Preferred role can be configured through device tree + */ + const char *cap_str; + int ret; + + ret = fwnode_property_read_string(connector, "try-power-role", &cap_str); + if (ret) + return 0; + + ret = typec_find_power_role(cap_str); + if (ret < 0) + return ret; + + cap->prefer_role = ret; + return hd3ss3220_set_source_pref(hd3ss3220, cap->prefer_role); +} + static const struct regmap_config config = { .reg_bits = 8, .val_bits = 8, @@ -188,8 +339,6 @@ static int hd3ss3220_probe(struct i2c_client *client) if (IS_ERR(hd3ss3220->regmap)) return PTR_ERR(hd3ss3220->regmap); - hd3ss3220_set_source_pref(hd3ss3220, - HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT); /* For backward compatibility check the connector child node first */ connector = device_get_named_child_node(hd3ss3220->dev, "connector"); if (connector) { @@ -217,12 +366,24 @@ static int hd3ss3220_probe(struct i2c_client *client) typec_cap.ops = &hd3ss3220_ops; typec_cap.fwnode = connector; + ret = hd3ss3220_configure_source_pref(hd3ss3220, connector, &typec_cap); + if (ret < 0) + goto err_put_role; + + ret = hd3ss3220_configure_port_type(hd3ss3220, connector, &typec_cap); + if (ret < 0) + goto err_put_role; + hd3ss3220->port = typec_register_port(&client->dev, &typec_cap); if (IS_ERR(hd3ss3220->port)) { ret = PTR_ERR(hd3ss3220->port); goto err_put_role; } + ret = hd3ss3220_configure_power_opmode(hd3ss3220, connector); + if (ret < 0) + goto err_unreg_port; + hd3ss3220_set_role(hd3ss3220); ret = regmap_read(hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, &data); if (ret < 0) diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 5dfe95754394..65dda9183e6f 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -718,7 +718,7 @@ DEFINE_SHOW_ATTRIBUTE(port_iom_status); static void pmc_mux_port_debugfs_init(struct pmc_usb_port *port) { struct dentry *debugfs_dir; - char name[6]; + char name[8]; snprintf(name, sizeof(name), "port%d", port->usb3_port - 1); diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 24a6a4354df8..48762508cc86 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -282,7 +282,7 @@ static int tcpci_set_polarity(struct tcpc_dev *tcpc, if (cc2 == TYPEC_CC_RD) /* Role control would have the Rp setting when DRP was enabled */ reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP); - else + else if (cc2 >= TYPEC_CC_RP_DEF) reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RD); } else { reg &= ~TCPC_ROLE_CTRL_CC1; @@ -290,7 +290,7 @@ static int tcpci_set_polarity(struct tcpc_dev *tcpc, if (cc1 == TYPEC_CC_RD) /* Role control would have the Rp setting when DRP was enabled */ reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP); - else + else if (cc1 >= TYPEC_CC_RP_DEF) reg |= FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RD); } } diff --git a/drivers/usb/typec/tcpm/tcpci_mt6370.c b/drivers/usb/typec/tcpm/tcpci_mt6370.c index 1479f961772d..ed822f438a09 100644 --- a/drivers/usb/typec/tcpm/tcpci_mt6370.c +++ b/drivers/usb/typec/tcpm/tcpci_mt6370.c @@ -11,7 +11,6 @@ #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/pm_wakeup.h> #include <linux/pm_wakeirq.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 6021eeb903fe..460dbde9fe22 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -185,7 +185,8 @@ S(UNSTRUCTURED_VDMS), \ S(STRUCTURED_VDMS), \ S(COUNTRY_INFO), \ - S(COUNTRY_CODES) + S(COUNTRY_CODES), \ + S(REVISION_INFORMATION) #define GENERATE_ENUM(e) e #define GENERATE_STRING(s) #s @@ -225,6 +226,7 @@ enum pd_msg_request { PD_MSG_CTRL_NOT_SUPP, PD_MSG_DATA_SINK_CAP, PD_MSG_DATA_SOURCE_CAP, + PD_MSG_DATA_REV, }; enum adev_actions { @@ -310,6 +312,13 @@ struct pd_data { unsigned int operating_snk_mw; }; +struct pd_revision_info { + u8 rev_major; + u8 rev_minor; + u8 ver_major; + u8 ver_minor; +}; + /* * @sink_wait_cap_time: Deadline (in ms) for tTypeCSinkWaitCap timer * @ps_src_wait_off_time: Deadline (in ms) for tPSSourceOff timer @@ -567,6 +576,9 @@ struct tcpm_port { /* Timer deadline values configured at runtime */ struct pd_timings timings; + + /* Indicates maximum (revision, version) supported */ + struct pd_revision_info pd_rev; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -1234,6 +1246,24 @@ static u32 tcpm_forge_legacy_pdo(struct tcpm_port *port, u32 pdo, enum typec_rol } } +static int tcpm_pd_send_revision(struct tcpm_port *port) +{ + struct pd_message msg; + u32 rmdo; + + memset(&msg, 0, sizeof(msg)); + rmdo = RMDO(port->pd_rev.rev_major, port->pd_rev.rev_minor, + port->pd_rev.ver_major, port->pd_rev.ver_minor); + msg.payload[0] = cpu_to_le32(rmdo); + msg.header = PD_HEADER_LE(PD_DATA_REVISION, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 1); + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + static int tcpm_pd_send_source_caps(struct tcpm_port *port) { struct pd_message msg; @@ -3537,6 +3567,17 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); break; + case PD_CTRL_GET_REVISION: + if (port->negotiated_rev >= PD_REV30 && port->pd_rev.rev_major) + tcpm_pd_handle_msg(port, PD_MSG_DATA_REV, + REVISION_INFORMATION); + 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 ? @@ -3781,6 +3822,14 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) tcpm_ams_finish(port); } break; + case PD_MSG_DATA_REV: + ret = tcpm_pd_send_revision(port); + if (ret) + tcpm_log(port, + "Unable to send revision msg, ret=%d", + ret); + tcpm_ams_finish(port); + break; default: break; } @@ -7036,7 +7085,9 @@ static void tcpm_port_unregister_pd(struct tcpm_port *port) static int tcpm_port_register_pd(struct tcpm_port *port) { - struct usb_power_delivery_desc desc = { port->typec_caps.pd_revision }; + u16 pd_revision = port->typec_caps.pd_revision; + u16 pd_version = port->pd_rev.ver_major << 8 | port->pd_rev.ver_minor; + struct usb_power_delivery_desc desc = { pd_revision, pd_version }; struct usb_power_delivery_capabilities *cap; int ret, i; @@ -7331,6 +7382,29 @@ static int tcpm_fw_get_snk_vdos(struct tcpm_port *port, struct fwnode_handle *fw return 0; } +static void tcpm_fw_get_pd_revision(struct tcpm_port *port, struct fwnode_handle *fwnode) +{ + int ret; + u8 val[4]; + + ret = fwnode_property_count_u8(fwnode, "pd-revision"); + if (!ret || ret != 4) { + tcpm_log(port, "Unable to find pd-revision property or incorrect array size"); + return; + } + + ret = fwnode_property_read_u8_array(fwnode, "pd-revision", val, 4); + if (ret) { + tcpm_log(port, "Failed to parse pd-revision, ret:(%d)", ret); + return; + } + + port->pd_rev.rev_major = val[0]; + port->pd_rev.rev_minor = val[1]; + port->pd_rev.ver_major = val[2]; + port->pd_rev.ver_minor = val[3]; +} + /* Power Supply access to expose source power information */ enum tcpm_psy_online_states { TCPM_PSY_OFFLINE = 0, @@ -7669,11 +7743,18 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) goto out_destroy_wq; tcpm_fw_get_timings(port, tcpc->fwnode); + tcpm_fw_get_pd_revision(port, tcpc->fwnode); port->try_role = port->typec_caps.prefer_role; port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ - port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ + + if (port->pd_rev.rev_major) + port->typec_caps.pd_revision = port->pd_rev.rev_major << 8 | + port->pd_rev.rev_minor; + else + port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ + port->typec_caps.svdm_version = SVDM_VER_2_0; port->typec_caps.driver_data = port; port->typec_caps.ops = &tcpm_ops; diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index 680e1b87b152..75559601fe8f 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -69,6 +69,19 @@ config UCSI_PMIC_GLINK To compile the driver as a module, choose M here: the module will be called ucsi_glink. +config CROS_EC_UCSI + tristate "UCSI Driver for ChromeOS EC" + depends on MFD_CROS_EC_DEV + depends on CROS_USBPD_NOTIFY + depends on !EXTCON_TCSS_CROS_EC + default MFD_CROS_EC_DEV + help + This driver enables UCSI support for a ChromeOS EC. The EC is + expected to implement a PPM. + + To compile the driver as a module, choose M here: the module + will be called cros_ec_ucsi. + config UCSI_LENOVO_YOGA_C630 tristate "UCSI Interface Driver for Lenovo Yoga C630" depends on EC_LENOVO_YOGA_C630 diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile index aed41d23887b..be98a879104d 100644 --- a/drivers/usb/typec/ucsi/Makefile +++ b/drivers/usb/typec/ucsi/Makefile @@ -21,4 +21,5 @@ obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o +obj-$(CONFIG_CROS_EC_UCSI) += cros_ec_ucsi.o obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o diff --git a/drivers/usb/typec/ucsi/cros_ec_ucsi.c b/drivers/usb/typec/ucsi/cros_ec_ucsi.c new file mode 100644 index 000000000000..c605c8616726 --- /dev/null +++ b/drivers/usb/typec/ucsi/cros_ec_ucsi.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UCSI driver for ChromeOS EC + * + * Copyright 2024 Google LLC. + */ + +#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/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_usbpd_notify.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include "ucsi.h" + +/* + * Maximum size in bytes of a UCSI message between AP and EC + */ +#define MAX_EC_DATA_SIZE 256 + +/* + * Maximum time in milliseconds the cros_ec_ucsi driver + * will wait for a response to a command or and ack. + */ +#define WRITE_TMO_MS 5000 + +/* Number of times to attempt recovery from a write timeout before giving up. */ +#define WRITE_TMO_CTR_MAX 5 + +struct cros_ucsi_data { + struct device *dev; + struct ucsi *ucsi; + + struct cros_ec_device *ec; + struct notifier_block nb; + struct work_struct work; + struct delayed_work write_tmo; + int tmo_counter; + + struct completion complete; + unsigned long flags; +}; + +static int cros_ucsi_read(struct ucsi *ucsi, unsigned int offset, void *val, + size_t val_len) +{ + struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); + struct ec_params_ucsi_ppm_get req = { + .offset = offset, + .size = val_len, + }; + int ret; + + if (val_len > MAX_EC_DATA_SIZE) { + dev_err(udata->dev, "Can't read %zu bytes. Too big.\n", val_len); + return -EINVAL; + } + + ret = cros_ec_cmd(udata->ec, 0, EC_CMD_UCSI_PPM_GET, + &req, sizeof(req), val, val_len); + if (ret < 0) { + dev_warn(udata->dev, "Failed to send EC message UCSI_PPM_GET: error=%d\n", ret); + return ret; + } + return 0; +} + +static int cros_ucsi_read_version(struct ucsi *ucsi, u16 *version) +{ + return cros_ucsi_read(ucsi, UCSI_VERSION, version, sizeof(*version)); +} + +static int cros_ucsi_read_cci(struct ucsi *ucsi, u32 *cci) +{ + return cros_ucsi_read(ucsi, UCSI_CCI, cci, sizeof(*cci)); +} + +static int cros_ucsi_read_message_in(struct ucsi *ucsi, void *val, + size_t val_len) +{ + return cros_ucsi_read(ucsi, UCSI_MESSAGE_IN, val, val_len); +} + +static int cros_ucsi_async_control(struct ucsi *ucsi, u64 cmd) +{ + struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); + u8 ec_buf[sizeof(struct ec_params_ucsi_ppm_set) + sizeof(cmd)]; + struct ec_params_ucsi_ppm_set *req = (struct ec_params_ucsi_ppm_set *) ec_buf; + int ret; + + req->offset = UCSI_CONTROL; + memcpy(req->data, &cmd, sizeof(cmd)); + ret = cros_ec_cmd(udata->ec, 0, EC_CMD_UCSI_PPM_SET, + req, sizeof(ec_buf), NULL, 0); + if (ret < 0) { + dev_warn(udata->dev, "Failed to send EC message UCSI_PPM_SET: error=%d\n", ret); + return ret; + } + return 0; +} + +static int cros_ucsi_sync_control(struct ucsi *ucsi, u64 cmd) +{ + struct cros_ucsi_data *udata = ucsi_get_drvdata(ucsi); + int ret; + + ret = ucsi_sync_control_common(ucsi, cmd); + switch (ret) { + case -EBUSY: + /* EC may return -EBUSY if CCI.busy is set. + * Convert this to a timeout. + */ + case -ETIMEDOUT: + /* Schedule recovery attempt when we timeout + * or tried to send a command while still busy. + */ + cancel_delayed_work_sync(&udata->write_tmo); + schedule_delayed_work(&udata->write_tmo, + msecs_to_jiffies(WRITE_TMO_MS)); + break; + case 0: + /* Successful write. Cancel any pending recovery work. */ + cancel_delayed_work_sync(&udata->write_tmo); + break; + } + + return ret; +} + +static const struct ucsi_operations cros_ucsi_ops = { + .read_version = cros_ucsi_read_version, + .read_cci = cros_ucsi_read_cci, + .read_message_in = cros_ucsi_read_message_in, + .async_control = cros_ucsi_async_control, + .sync_control = cros_ucsi_sync_control, +}; + +static void cros_ucsi_work(struct work_struct *work) +{ + struct cros_ucsi_data *udata = container_of(work, struct cros_ucsi_data, work); + u32 cci; + + if (cros_ucsi_read_cci(udata->ucsi, &cci)) + return; + + ucsi_notify_common(udata->ucsi, cci); +} + +static void cros_ucsi_write_timeout(struct work_struct *work) +{ + struct cros_ucsi_data *udata = + container_of(work, struct cros_ucsi_data, write_tmo.work); + u32 cci; + u64 cmd; + + if (cros_ucsi_read(udata->ucsi, UCSI_CCI, &cci, sizeof(cci))) { + dev_err(udata->dev, + "Reading CCI failed; no write timeout recovery possible.\n"); + return; + } + + if (cci & UCSI_CCI_BUSY) { + udata->tmo_counter++; + + if (udata->tmo_counter <= WRITE_TMO_CTR_MAX) + schedule_delayed_work(&udata->write_tmo, + msecs_to_jiffies(WRITE_TMO_MS)); + else + dev_err(udata->dev, + "PPM unresponsive - too many write timeouts.\n"); + + return; + } + + /* No longer busy means we can reset our timeout counter. */ + udata->tmo_counter = 0; + + /* Need to ack previous command which may have timed out. */ + if (cci & UCSI_CCI_COMMAND_COMPLETE) { + cmd = UCSI_ACK_CC_CI | UCSI_ACK_COMMAND_COMPLETE; + cros_ucsi_async_control(udata->ucsi, cmd); + + /* Check again after a few seconds that the system has + * recovered to make sure our async write above was successful. + */ + schedule_delayed_work(&udata->write_tmo, + msecs_to_jiffies(WRITE_TMO_MS)); + return; + } + + /* We recovered from a previous timeout. Treat this as a recovery from + * suspend and call resume. + */ + ucsi_resume(udata->ucsi); +} + +static int cros_ucsi_event(struct notifier_block *nb, + unsigned long host_event, void *_notify) +{ + struct cros_ucsi_data *udata = container_of(nb, struct cros_ucsi_data, nb); + + if (!(host_event & PD_EVENT_PPM)) + return NOTIFY_OK; + + dev_dbg(udata->dev, "UCSI notification received\n"); + flush_work(&udata->work); + schedule_work(&udata->work); + + return NOTIFY_OK; +} + +static void cros_ucsi_destroy(struct cros_ucsi_data *udata) +{ + cros_usbpd_unregister_notify(&udata->nb); + cancel_delayed_work_sync(&udata->write_tmo); + cancel_work_sync(&udata->work); + ucsi_destroy(udata->ucsi); +} + +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; + + udata = devm_kzalloc(dev, sizeof(*udata), GFP_KERNEL); + if (!udata) + return -ENOMEM; + + udata->dev = dev; + + udata->ec = ec_data->ec_dev; + if (!udata->ec) + return dev_err_probe(dev, -ENODEV, "couldn't find parent EC device\n"); + + platform_set_drvdata(pdev, udata); + + INIT_WORK(&udata->work, cros_ucsi_work); + INIT_DELAYED_WORK(&udata->write_tmo, cros_ucsi_write_timeout); + init_completion(&udata->complete); + + udata->ucsi = ucsi_create(dev, &cros_ucsi_ops); + if (IS_ERR(udata->ucsi)) + return dev_err_probe(dev, PTR_ERR(udata->ucsi), "failed to allocate UCSI instance\n"); + + ucsi_set_drvdata(udata->ucsi, udata); + + udata->nb.notifier_call = cros_ucsi_event; + ret = cros_usbpd_register_notify(&udata->nb); + if (ret) { + dev_err_probe(dev, ret, "failed to register notifier\n"); + ucsi_destroy(udata->ucsi); + return ret; + } + + ret = ucsi_register(udata->ucsi); + if (ret) { + dev_err_probe(dev, ret, "failed to register UCSI\n"); + cros_ucsi_destroy(udata); + return ret; + } + + return 0; +} + +static void cros_ucsi_remove(struct platform_device *dev) +{ + struct cros_ucsi_data *udata = platform_get_drvdata(dev); + + ucsi_unregister(udata->ucsi); + cros_ucsi_destroy(udata); +} + +static int __maybe_unused cros_ucsi_suspend(struct device *dev) +{ + struct cros_ucsi_data *udata = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&udata->write_tmo); + cancel_work_sync(&udata->work); + + return 0; +} + +static void __maybe_unused cros_ucsi_complete(struct device *dev) +{ + struct cros_ucsi_data *udata = dev_get_drvdata(dev); + + ucsi_resume(udata->ucsi); +} + +/* + * UCSI protocol is also used on ChromeOS platforms which reply on + * cros_ec_lpc.c driver for communication with embedded controller (EC). + * On such platforms communication with the EC is not available until + * the .complete() callback of the cros_ec_lpc driver is executed. + * For this reason we delay ucsi_resume() until the .complete() stage + * otherwise UCSI SET_NOTIFICATION_ENABLE command will fail and we won't + * receive any UCSI notifications from the EC where PPM is implemented. + */ +static const struct dev_pm_ops cros_ucsi_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend = cros_ucsi_suspend, + .complete = cros_ucsi_complete, +#endif +}; + +static const struct platform_device_id cros_ucsi_id[] = { + { KBUILD_MODNAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(platform, cros_ucsi_id); + +static struct platform_driver cros_ucsi_driver = { + .driver = { + .name = KBUILD_MODNAME, + .pm = &cros_ucsi_pm_ops, + }, + .id_table = cros_ucsi_id, + .probe = cros_ucsi_probe, + .remove = cros_ucsi_remove, +}; + +module_platform_driver(cros_ucsi_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("UCSI driver for ChromeOS EC"); diff --git a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c index f3a5e24ea84d..4cae85c0dc12 100644 --- a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c +++ b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c @@ -71,7 +71,7 @@ static int yoga_c630_ucsi_async_control(struct ucsi *ucsi, u64 command) return yoga_c630_ec_ucsi_write(uec->ec, (u8*)&command); } -const struct ucsi_operations yoga_c630_ucsi_ops = { +static const struct ucsi_operations yoga_c630_ucsi_ops = { .read_version = yoga_c630_ucsi_read_version, .read_cci = yoga_c630_ucsi_read_cci, .read_message_in = yoga_c630_ucsi_read_message_in, diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index b03e5021c25b..2f722849dfc9 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -1161,12 +1161,8 @@ static int vhci_setup(struct usb_hcd *hcd) hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; } - /* - * Support SG. - * sg_tablesize is an arbitrary value to alleviate memory pressure - * on the host. - */ - hcd->self.sg_tablesize = 32; + /* accept arbitrarily long scatter-gather lists */ + hcd->self.sg_tablesize = ~0; hcd->self.no_sg_constraint = 1; return 0; diff --git a/drivers/usb/usbip/vudc_sysfs.c b/drivers/usb/usbip/vudc_sysfs.c index 907a43a00896..2aae3edfc813 100644 --- a/drivers/usb/usbip/vudc_sysfs.c +++ b/drivers/usb/usbip/vudc_sysfs.c @@ -67,7 +67,7 @@ out: * Exposes device descriptor from the gadget driver. */ static ssize_t dev_desc_read(struct file *file, struct kobject *kobj, - struct bin_attribute *attr, char *out, + const struct bin_attribute *attr, char *out, loff_t off, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -88,7 +88,7 @@ unlock: spin_unlock_irqrestore(&udc->lock, flags); return ret; } -static BIN_ATTR_RO(dev_desc, sizeof(struct usb_device_descriptor)); +static const BIN_ATTR_RO(dev_desc, sizeof(struct usb_device_descriptor)); static ssize_t usbip_sockfd_store(struct device *dev, struct device_attribute *attr, @@ -252,14 +252,14 @@ static struct attribute *dev_attrs[] = { NULL, }; -static struct bin_attribute *dev_bin_attrs[] = { +static const struct bin_attribute *const dev_bin_attrs[] = { &bin_attr_dev_desc, NULL, }; static const struct attribute_group vudc_attr_group = { .attrs = dev_attrs, - .bin_attrs = dev_bin_attrs, + .bin_attrs_new = dev_bin_attrs, }; const struct attribute_group *vudc_groups[] = { diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index b3c4993e656e..ecf290a0c98f 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -5044,8 +5044,11 @@ struct ec_response_pd_status { #define PD_EVENT_POWER_CHANGE BIT(1) #define PD_EVENT_IDENTITY_RECEIVED BIT(2) #define PD_EVENT_DATA_SWAP BIT(3) +#define PD_EVENT_TYPEC BIT(4) +#define PD_EVENT_PPM BIT(5) + struct ec_response_host_event_status { - uint32_t status; /* PD MCU host event status */ + uint32_t status; /* PD MCU host event status */ } __ec_align4; /* Set USB type-C port role and muxes */ @@ -6105,6 +6108,29 @@ struct ec_response_typec_vdm_response { #undef VDO_MAX_SIZE +/* + * UCSI OPM-PPM commands + * + * These commands are used for communication between OPM and PPM. + * Only UCSI3.0 is tested. + */ + +#define EC_CMD_UCSI_PPM_SET 0x0140 + +/* The data size is stored in the host command protocol header. */ +struct ec_params_ucsi_ppm_set { + uint16_t offset; + uint8_t data[]; +} __ec_align2; + +#define EC_CMD_UCSI_PPM_GET 0x0141 + +/* For 'GET' sub-commands, data will be returned as a raw payload. */ +struct ec_params_ucsi_ppm_get { + uint16_t offset; + uint8_t size; +} __ec_align2; + /*****************************************************************************/ /* The command range 0x200-0x2FF is reserved for Rotor. */ diff --git a/include/linux/pm.h b/include/linux/pm.h index e7f0260f15ad..08c37b83fea8 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -570,7 +570,8 @@ const struct dev_pm_ops name = { \ { .event = PM_EVENT_AUTO_RESUME, }) #define PMSG_IS_AUTO(msg) (((msg).event & PM_EVENT_AUTO) != 0) - +#define PMSG_NO_WAKEUP(msg) (((msg).event & \ + (PM_EVENT_FREEZE | PM_EVENT_QUIESCE)) != 0) /* * Device run-time power management status. * diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index d50098fb16b5..3068c3084eb6 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -33,7 +33,9 @@ enum pd_ctrl_msg_type { PD_CTRL_FR_SWAP = 19, PD_CTRL_GET_PPS_STATUS = 20, PD_CTRL_GET_COUNTRY_CODES = 21, - /* 22-31 Reserved */ + /* 22-23 Reserved */ + PD_CTRL_GET_REVISION = 24, + /* 25-31 Reserved */ }; enum pd_data_msg_type { @@ -46,7 +48,9 @@ enum pd_data_msg_type { PD_DATA_ALERT = 6, PD_DATA_GET_COUNTRY_INFO = 7, PD_DATA_ENTER_USB = 8, - /* 9-14 Reserved */ + /* 9-11 Reserved */ + PD_DATA_REVISION = 12, + /* 13-14 Reserved */ PD_DATA_VENDOR_DEF = 15, /* 16-31 Reserved */ }; @@ -453,6 +457,20 @@ static inline unsigned int rdo_max_power(u32 rdo) #define EUDO_TBT_SUPPORT BIT(14) #define EUDO_HOST_PRESENT BIT(13) +/* + * Request Message Data Object (PD Revision 3.1+ only) + * -------- + * <31:28> :: Revision Major + * <27:24> :: Revision Minor + * <23:20> :: Version Major + * <19:16> :: Version Minor + * <15:0> :: Reserved, Shall be set to zero + */ + +#define RMDO(rev_maj, rev_min, ver_maj, ver_min) \ + (((rev_maj) & 0xf) << 28 | ((rev_min) & 0xf) << 24 | \ + ((ver_maj) & 0xf) << 20 | ((ver_min) & 0xf) << 16) + /* USB PD timers and counters */ #define PD_T_NO_RESPONSE 5000 /* 4.5 - 5.5 seconds */ #define PD_T_DB_DETECT 10000 /* 10 - 15 seconds */ diff --git a/include/linux/usb/storage.h b/include/linux/usb/storage.h index 8539956bc2be..51be3bb8fccb 100644 --- a/include/linux/usb/storage.h +++ b/include/linux/usb/storage.h @@ -82,4 +82,12 @@ struct bulk_cs_wrap { #define US_BULK_RESET_REQUEST 0xff #define US_BULK_GET_MAX_LUN 0xfe +/* + * If 4 LUNs are supported then the LUNs would be + * numbered from 0 to 3, and the return value for + * US_BULK_GET_MAX_LUN request would be 3. The valid + * LUN field is 4 bits wide, the upper limit is 0x0f. + */ +#define US_BULK_MAX_LUN_LIMIT 0x0f + #endif diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index d616b8807000..252af3f77039 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -140,6 +140,7 @@ int typec_cable_set_identity(struct typec_cable *cable); * @mode: Index of the Mode * @vdo: VDO returned by Discover Modes USB PD command * @roles: Only for ports. DRP if the mode is available in both roles + * @inactive: Only for ports. Make this port inactive (default is active). * * Description of an Alternate Mode which a connector, cable plug or partner * supports. @@ -150,6 +151,7 @@ struct typec_altmode_desc { u32 vdo; /* Only used with ports */ enum typec_port_data roles; + bool inactive; }; void typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision); diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h index fa97d7e00f5c..55dcea12082c 100644 --- a/include/linux/usb/typec_tbt.h +++ b/include/linux/usb/typec_tbt.h @@ -44,6 +44,7 @@ struct typec_thunderbolt_data { #define TBT_GEN3_NON_ROUNDED 0 #define TBT_GEN3_GEN4_ROUNDED_NON_ROUNDED 1 +#define TBT_CABLE_ROUNDED BIT(19) #define TBT_CABLE_OPTICAL BIT(21) #define TBT_CABLE_RETIMER BIT(22) #define TBT_CABLE_LINK_TRAINING BIT(23) diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index 2ebdba111a8f..beef1752e36e 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -206,7 +206,7 @@ struct usb_ffs_dmabuf_transfer_req { * +-----+-----------------+------+--------------------------+ * | off | name | type | description | * +-----+-----------------+------+--------------------------+ - * | 0 | inteface | U8 | related interface number | + * | 0 | interface | U8 | related interface number | * +-----+-----------------+------+--------------------------+ * | 1 | dwLength | U32 | length of the descriptor | * +-----+-----------------+------+--------------------------+ @@ -224,7 +224,7 @@ struct usb_ffs_dmabuf_transfer_req { * +-----+-----------------+------+--------------------------+ * | off | name | type | description | * +-----+-----------------+------+--------------------------+ - * | 0 | inteface | U8 | related interface number | + * | 0 | interface | U8 | related interface number | * +-----+-----------------+------+--------------------------+ * | 1 | dwLength | U32 | length of the descriptor | * +-----+-----------------+------+--------------------------+ @@ -237,7 +237,7 @@ struct usb_ffs_dmabuf_transfer_req { * | 11 | ExtProp[] | | list of ext. prop. d. | * +-----+-----------------+------+--------------------------+ * - * ExtCompat[] is an array of valid Extended Compatiblity descriptors + * ExtCompat[] is an array of valid Extended Compatibility descriptors * which have the following format: * * +-----+-----------------------+------+-------------------------------------+ @@ -295,7 +295,7 @@ struct usb_functionfs_strings_head { * | 16 | stringtab | StringTab[lang_count] | table of strings per lang | * * For each language there is one stringtab entry (ie. there are lang_count - * stringtab entires). Each StringTab has following format: + * stringtab entries). Each StringTab has following format: * * | off | name | type | description | * |-----+---------+-------------------+------------------------------------| diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index 9c7b404defbd..d3d00e85edf7 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -237,7 +237,6 @@ int main(void) DEVID(typec_device_id); DEVID_FIELD(typec_device_id, svid); - DEVID_FIELD(typec_device_id, mode); DEVID(tee_client_device_id); DEVID_FIELD(tee_client_device_id, uuid); diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 19ec72a69e90..00586119a25b 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1219,17 +1219,12 @@ static void do_tbsvc_entry(struct module *mod, void *symval) module_alias_printf(mod, true, "tbsvc:%s", alias); } -/* Looks like: typec:idNmN */ +/* Looks like: typec:idN */ static void do_typec_entry(struct module *mod, void *symval) { - char alias[256] = {}; - DEF_FIELD(symval, typec_device_id, svid); - DEF_FIELD(symval, typec_device_id, mode); - - ADD(alias, "m", mode != TYPEC_ANY_MODE, mode); - module_alias_printf(mod, false, "typec:id%04X%s", svid, alias); + module_alias_printf(mod, false, "typec:id%04X", svid); } /* Looks like: tee:uuid */ |