diff options
83 files changed, 4046 insertions, 830 deletions
diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1 index d4b8cf40a9e4..c4ba92f004c3 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac1 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1 @@ -6,7 +6,7 @@ Description: ===================== ======================================= c_chmask capture channel mask - c_srate capture sampling rate + c_srate list of capture sampling rates (comma-separated) c_ssize capture sample size (bytes) c_mute_present capture mute control enable c_volume_present capture volume control enable @@ -17,7 +17,7 @@ Description: c_volume_res capture volume control resolution (in 1/256 dB) p_chmask playback channel mask - p_srate playback sampling rate + p_srate list of playback sampling rates (comma-separated) p_ssize playback sample size (bytes) p_mute_present playback mute control enable p_volume_present playback volume control enable @@ -29,4 +29,5 @@ Description: (in 1/256 dB) req_number the number of pre-allocated requests for both capture and playback + function_name name of the interface ===================== ======================================= diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac2 b/Documentation/ABI/testing/configfs-usb-gadget-uac2 index 7fb3dbe26857..3371c39f651d 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac2 +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac2 @@ -6,8 +6,9 @@ Description: ===================== ======================================= c_chmask capture channel mask - c_srate capture sampling rate + c_srate list of capture sampling rates (comma-separated) c_ssize capture sample size (bytes) + c_hs_bint capture bInterval for HS/SS (1-4: fixed, 0: auto) c_sync capture synchronization type (async/adaptive) c_mute_present capture mute control enable @@ -20,8 +21,9 @@ Description: (in 1/256 dB) fb_max maximum extra bandwidth in async mode p_chmask playback channel mask - p_srate playback sampling rate + p_srate list of playback sampling rates (comma-separated) p_ssize playback sample size (bytes) + p_hs_bint playback bInterval for HS/SS (1-4: fixed, 0: auto) p_mute_present playback mute control enable p_volume_present playback volume control enable p_volume_min playback volume control min value @@ -32,4 +34,5 @@ Description: (in 1/256 dB) req_number the number of pre-allocated requests for both capture and playback + function_name name of the interface ===================== ======================================= diff --git a/Documentation/ABI/testing/sysfs-driver-eud b/Documentation/ABI/testing/sysfs-driver-eud new file mode 100644 index 000000000000..83f3872182a4 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-eud @@ -0,0 +1,9 @@ +What: /sys/bus/platform/drivers/eud/.../enable +Date: February 2022 +Contact: Souradeep Chowdhury <[email protected]> +Description: + The Enable/Disable sysfs interface for Embedded + USB Debugger(EUD). This enables and disables the + EUD based on a 1 or a 0 value. By enabling EUD, + the user is able to activate the mini-usb hub of + EUD for debug and trace capabilities. diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml new file mode 100644 index 000000000000..c98aab209bc5 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/soc/qcom/qcom,eud.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Qualcomm Embedded USB Debugger + +maintainers: + - Souradeep Chowdhury <[email protected]> + +description: + This binding is used to describe the Qualcomm Embedded USB Debugger, which is + mini USB-hub implemented on chip to support USB-based debug capabilities. + +properties: + compatible: + items: + - enum: + - qcom,sc7280-eud + - const: qcom,eud + + reg: + items: + - description: EUD Base Register Region + - description: EUD Mode Manager Register + + interrupts: + description: EUD interrupt + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: + These ports is to be attached to the endpoint of the DWC3 controller node + and type C connector node. The controller has the "usb-role-switch" + property. + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: This port is to be attached to the DWC3 controller. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: This port is to be attached to the type C connector. + +required: + - compatible + - reg + - ports + +additionalProperties: false + +examples: + - | + eud@88e0000 { + compatible = "qcom,sc7280-eud","qcom,eud"; + reg = <0x88e0000 0x2000>, + <0x88e2000 0x1000>; + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + eud_ep: endpoint { + remote-endpoint = <&usb2_role_switch>; + }; + }; + port@1 { + reg = <1>; + eud_con: endpoint { + remote-endpoint = <&con_eud>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/dwc2.yaml b/Documentation/devicetree/bindings/usb/dwc2.yaml index f00867ebc147..481aaa09f3f2 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.yaml +++ b/Documentation/devicetree/bindings/usb/dwc2.yaml @@ -53,6 +53,7 @@ properties: - const: st,stm32mp15-hsotg - const: snps,dwc2 - const: samsung,s3c6400-hsotg + - const: intel,socfpga-agilex-hsotg reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/usb/exynos-usb.txt b/Documentation/devicetree/bindings/usb/exynos-usb.txt deleted file mode 100644 index f7ae79825d7d..000000000000 --- a/Documentation/devicetree/bindings/usb/exynos-usb.txt +++ /dev/null @@ -1,115 +0,0 @@ -Samsung Exynos SoC USB controller - -The USB devices interface with USB controllers on Exynos SOCs. -The device node has following properties. - -EHCI -Required properties: - - compatible: should be "samsung,exynos4210-ehci" for USB 2.0 - EHCI controller in host mode. - - reg: physical base address of the controller and length of memory mapped - region. - - interrupts: interrupt number to the cpu. - - clocks: from common clock binding: handle to usb clock. - - clock-names: from common clock binding: Shall be "usbhost". - - phys: from the *Generic PHY* bindings; array specifying phy(s) used - by the root port. - - phy-names: from the *Generic PHY* bindings; array of the names for - each phy for the root ports, must be a subset of the following: - "host", "hsic0", "hsic1". - -Optional properties: - - samsung,vbus-gpio: if present, specifies the GPIO that - needs to be pulled up for the bus to be powered. - -Example: - - usb@12110000 { - compatible = "samsung,exynos4210-ehci"; - reg = <0x12110000 0x100>; - interrupts = <0 71 0>; - samsung,vbus-gpio = <&gpx2 6 1 3 3>; - - clocks = <&clock 285>; - clock-names = "usbhost"; - - phys = <&usb2phy 1>; - phy-names = "host"; - }; - -OHCI -Required properties: - - compatible: should be "samsung,exynos4210-ohci" for USB 2.0 - OHCI companion controller in host mode. - - reg: physical base address of the controller and length of memory mapped - region. - - interrupts: interrupt number to the cpu. - - clocks: from common clock binding: handle to usb clock. - - clock-names: from common clock binding: Shall be "usbhost". - - phys: from the *Generic PHY* bindings; array specifying phy(s) used - by the root port. - - phy-names: from the *Generic PHY* bindings; array of the names for - each phy for the root ports, must be a subset of the following: - "host", "hsic0", "hsic1". - -Example: - usb@12120000 { - compatible = "samsung,exynos4210-ohci"; - reg = <0x12120000 0x100>; - interrupts = <0 71 0>; - - clocks = <&clock 285>; - clock-names = "usbhost"; - - phys = <&usb2phy 1>; - phy-names = "host"; - }; - -DWC3 -Required properties: - - compatible: should be one of the following - - "samsung,exynos5250-dwusb3": for USB 3.0 DWC3 controller on - Exynos5250/5420. - "samsung,exynos5433-dwusb3": for USB 3.0 DWC3 controller on - Exynos5433. - "samsung,exynos7-dwusb3": for USB 3.0 DWC3 controller on Exynos7. - - #address-cells, #size-cells : should be '1' if the device has sub-nodes - with 'reg' property. - - ranges: allows valid 1:1 translation between child's address space and - parent's address space - - clocks: Clock IDs array as required by the controller. - - clock-names: Names of clocks corresponding to IDs in the clock property. - Following clock names shall be provided for different - compatibles: - - samsung,exynos5250-dwusb3: "usbdrd30", - - samsung,exynos5433-dwusb3: "aclk", "susp_clk", "pipe_pclk", - "phyclk", - - samsung,exynos7-dwusb3: "usbdrd30", "usbdrd30_susp_clk", - "usbdrd30_axius_clk" - - vdd10-supply: 1.0V powr supply - - vdd33-supply: 3.0V/3.3V power supply - -Sub-nodes: -The dwc3 core should be added as subnode to Exynos dwc3 glue. -- dwc3 : - The binding details of dwc3 can be found in: - Documentation/devicetree/bindings/usb/snps,dwc3.yaml - -Example: - usb@12000000 { - compatible = "samsung,exynos5250-dwusb3"; - clocks = <&clock 286>; - clock-names = "usbdrd30"; - #address-cells = <1>; - #size-cells = <1>; - ranges; - vdd10-supply = <&ldo11_reg>; - vdd33-supply = <&ldo9_reg>; - - dwc3 { - compatible = "synopsys,dwc3"; - reg = <0x12000000 0x10000>; - interrupts = <0 72 0>; - usb-phy = <&usb2_phy &usb3_phy>; - }; - }; diff --git a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml index 11f7bacd4e2b..41efb51638d1 100644 --- a/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml +++ b/Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml @@ -146,7 +146,11 @@ properties: 2 - used by mt2712 etc, revision 2 following IPM rule; 101 - used by mt8183, specific 1.01; 102 - used by mt8192, specific 1.02; - enum: [1, 2, 101, 102] + 103 - used by mt8195, IP0, specific 1.03; + 104 - used by mt8195, IP1, specific 1.04; + 105 - used by mt8195, IP2, specific 1.05; + 106 - used by mt8195, IP3, specific 1.06; + enum: [1, 2, 101, 102, 103, 104, 105, 106] mediatek,u3p-dis-msk: $ref: /schemas/types.yaml#/definitions/uint32 diff --git a/Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml b/Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml new file mode 100644 index 000000000000..48c458c65848 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/microchip,mpfs-musb.yaml @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/microchip,mpfs-musb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip MPFS USB Controller Device Tree Bindings + +allOf: + - $ref: usb-drd.yaml# + +maintainers: + - Conor Dooley <[email protected]> + +properties: + compatible: + enum: + - microchip,mpfs-musb + + dr_mode: true + + reg: + maxItems: 1 + + interrupts: + minItems: 2 + maxItems: 2 + + interrupt-names: + items: + - const: dma + - const: mc + + clocks: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + +additionalProperties: false + +examples: + - | + #include "dt-bindings/clock/microchip,mpfs-clock.h" + usb@20201000 { + compatible = "microchip,mpfs-musb"; + reg = <0x20201000 0x1000>; + clocks = <&clkcfg CLK_USB>; + interrupt-parent = <&plic>; + interrupts = <86>, <87>; + interrupt-names = "dma", "mc"; + dr_mode = "host"; + }; + +... diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index 2d23a4ff702f..ce252db2aab3 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -16,6 +16,7 @@ properties: - qcom,ipq4019-dwc3 - qcom,ipq6018-dwc3 - qcom,ipq8064-dwc3 + - qcom,msm8953-dwc3 - qcom,msm8996-dwc3 - qcom,msm8998-dwc3 - qcom,sc7180-dwc3 diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1719.yaml b/Documentation/devicetree/bindings/usb/richtek,rt1719.yaml new file mode 100644 index 000000000000..65a93f7738d5 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/richtek,rt1719.yaml @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: "http://devicetree.org/schemas/usb/richtek,rt1719.yaml#" +$schema: "http://devicetree.org/meta-schemas/core.yaml#" + +title: Richtek RT1719 sink-only Type-C PD controller bindings + +maintainers: + - ChiYuan Huang <[email protected]> + +description: | + The RT1719 is a sink-only USB Type-C contoller that complies with the latest + USB Type-C and PD standards. It does the USB Type-C detection including attach + and orientation. It integrates the physical layer of the USB BMC power + delivery protocol to allow up to 100W of power. The BMC PD block enables full + support for alternative interfaces of the Type-C specification. + +properties: + compatible: + enum: + - richtek,rt1719 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + wakeup-source: + description: enable IRQ remote wakeup, see power/wakeup-source.txt + type: boolean + + connector: + type: object + $ref: ../connector/usb-connector.yaml# + description: + Properties for usb c connector. + +additionalProperties: false + +required: + - compatible + - reg + - connector + - interrupts + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + rt1719@43 { + compatible = "richtek,rt1719"; + reg = <0x43>; + interrupts-extended = <&gpio26 2 IRQ_TYPE_LEVEL_LOW>; + wakeup-source; + + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + endpoint { + remote-endpoint = <&usb_hs>; + }; + }; + port@1 { + reg = <1>; + endpoint { + remote-endpoint = <&usb_ss>; + }; + }; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml b/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml new file mode 100644 index 000000000000..22b91a27d776 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/samsung,exynos-dwc3.yaml @@ -0,0 +1,129 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/samsung,exynos-dwc3.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung Exynos SoC USB 3.0 DWC3 Controller + +maintainers: + - Krzysztof Kozlowski <[email protected]> + +properties: + compatible: + enum: + - samsung,exynos5250-dwusb3 + - samsung,exynos5433-dwusb3 + - samsung,exynos7-dwusb3 + + '#address-cells': + const: 1 + + clocks: + minItems: 1 + maxItems: 4 + + clock-names: + minItems: 1 + maxItems: 4 + + ranges: true + + '#size-cells': + const: 1 + + vdd10-supply: + description: 1.0V power supply + + vdd33-supply: + description: 3.0V/3.3V power supply + +patternProperties: + "^usb@[0-9a-f]+$": + $ref: snps,dwc3.yaml# + description: Required child node + +required: + - compatible + - '#address-cells' + - clocks + - clock-names + - ranges + - '#size-cells' + - vdd10-supply + - vdd33-supply + +allOf: + - if: + properties: + compatible: + contains: + const: samsung,exynos5250-dwusb3 + then: + properties: + clocks: + minItems: 1 + maxItems: 1 + clock-names: + items: + - const: usbdrd30 + + - if: + properties: + compatible: + contains: + const: samsung,exynos54333-dwusb3 + then: + properties: + clocks: + minItems: 4 + maxItems: 4 + clock-names: + items: + - const: aclk + - const: susp_clk + - const: pipe_pclk + - const: phyclk + + - if: + properties: + compatible: + contains: + const: samsung,exynos7-dwusb3 + then: + properties: + clocks: + minItems: 3 + maxItems: 3 + clock-names: + items: + - const: usbdrd30 + - const: usbdrd30_susp_clk + - const: usbdrd30_axius_clk + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/exynos5420.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + + usb { + compatible = "samsung,exynos5250-dwusb3"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + clocks = <&clock CLK_USBD300>; + clock-names = "usbdrd30"; + vdd33-supply = <&ldo9_reg>; + vdd10-supply = <&ldo11_reg>; + + usb@12000000 { + compatible = "snps,dwc3"; + reg = <0x12000000 0x10000>; + interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>; + phys = <&usbdrd_phy0 0>, <&usbdrd_phy0 1>; + phy-names = "usb2-phy", "usb3-phy"; + snps,dis_u3_susphy_quirk; + }; + }; diff --git a/Documentation/devicetree/bindings/usb/samsung,exynos-usb2.yaml b/Documentation/devicetree/bindings/usb/samsung,exynos-usb2.yaml new file mode 100644 index 000000000000..fbf07d6e707a --- /dev/null +++ b/Documentation/devicetree/bindings/usb/samsung,exynos-usb2.yaml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/samsung,exynos-usb2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung Exynos SoC USB 2.0 EHCI/OHCI Controller + +maintainers: + - Krzysztof Kozlowski <[email protected]> + +properties: + compatible: + enum: + - samsung,exynos4210-ehci + - samsung,exynos4210-ohci + + '#address-cells': + const: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: usbhost + + interrupts: + maxItems: 1 + + phys: + minItems: 1 + maxItems: 3 + + phy-names: + items: + enum: [host, hsic0, hsic1] + minItems: 1 + maxItems: 3 + + reg: + maxItems: 1 + + samsung,vbus-gpio: + description: + Only for controller in EHCI mode, if present, specifies the GPIO that + needs to be pulled up for the bus to be powered. + + '#size-cells': + const: 0 + +patternProperties: + "^.*@[0-9a-f]{1,2}$": + description: The hard wired USB devices + type: object + $ref: /usb/usb-device.yaml + +required: + - compatible + - clocks + - clock-names + - interrupts + - phys + - phy-names + +allOf: + - if: + properties: + compatible: + contains: + const: samsung,exynos4210-ohci + then: + properties: + samsung,vbus-gpio: false + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/exynos5420.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + + usb@12110000 { + compatible = "samsung,exynos4210-ehci"; + reg = <0x12110000 0x100>; + interrupts = <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clock CLK_USBH20>; + clock-names = "usbhost"; + phys = <&usb2_phy 0>; + phy-names = "host"; + + #address-cells = <1>; + #size-cells = <0>; + + hub@1 { + compatible = "usb0424,9514"; + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + + usbether@1 { + compatible = "usb0424,ec00"; + reg = <1>; + local-mac-address = [00 00 00 00 00 00]; + }; + }; + }; + + usb@12120000 { + compatible = "samsung,exynos4210-ohci"; + reg = <0x12120000 0x100>; + interrupts = <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clock CLK_USBH20>; + clock-names = "usbhost"; + phys = <&usb2_phy 0>; + phy-names = "host"; + }; diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml index d29ffcd27472..f4471f8bdbef 100644 --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml @@ -263,8 +263,11 @@ properties: Value for REFCLKPER field of GUCTL register for reference clock period in nanoseconds, when the hardware set default does not match the actual clock. - minimum: 1 - maximum: 0x3ff + + This binding is deprecated. Instead, provide an appropriate reference clock. + minimum: 8 + maximum: 62 + deprecated: true snps,rx-thr-num-pkt-prd: description: @@ -332,6 +335,12 @@ properties: items: enum: [1, 4, 8, 16, 32, 64, 128, 256] + port: + $ref: /schemas/graph.yaml#/properties/port + description: + This port is used with the 'usb-role-switch' property to connect the + dwc3 to type C connector. + unevaluatedProperties: false required: diff --git a/Documentation/devicetree/bindings/usb/willsemi,wusb3801.yaml b/Documentation/devicetree/bindings/usb/willsemi,wusb3801.yaml new file mode 100644 index 000000000000..c2b2243c7892 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/willsemi,wusb3801.yaml @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/willsemi,wusb3801.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: WUSB3801 Type-C port controller DT bindings + +description: + The Will Semiconductor WUSB3801 is a USB Type-C port controller which + supports role and plug orientation detection using the CC pins. It is + compatible with the USB Type-C Cable and Connector Specification v1.2. + +maintainers: + - Samuel Holland <[email protected]> + +properties: + compatible: + enum: + - willsemi,wusb3801 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + connector: + type: object + $ref: ../connector/usb-connector.yaml# + description: + The managed USB Type-C connector. Since WUSB3801 does not support + Power Delivery, the node should have the "pd-disable" property. + + properties: + compatible: + const: usb-c-connector + + required: + - pd-disable + +required: + - compatible + - reg + - interrupts + - connector + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + tcpc@60 { + compatible = "willsemi,wusb3801"; + reg = <0x60>; + interrupt-parent = <&gpio0>; + interrupts = <4 IRQ_TYPE_LEVEL_LOW>; + + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + vbus-supply = <&otg_switch>; + power-role = "dual"; + try-power-role = "sink"; + data-role = "dual"; + typec-power-opmode = "default"; + pd-disable; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 294093d45a23..a8ab97717a46 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1340,6 +1340,8 @@ patternProperties: description: Wi2Wi, Inc. "^wiligear,.*": description: Wiligear, Ltd. + "^willsemi,.*": + description: Will Semiconductor Ltd. "^winbond,.*": description: Winbond Electronics corp. "^wingtech,.*": diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index cbbd948c626f..c6d034abce3a 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -726,7 +726,7 @@ The uac2 function provides these attributes in its function directory: ================ ==================================================== c_chmask capture channel mask - c_srate capture sampling rate + c_srate list of capture sampling rates (comma-separated) c_ssize capture sample size (bytes) c_sync capture synchronization type (async/adaptive) c_mute_present capture mute control enable @@ -734,17 +734,20 @@ The uac2 function provides these attributes in its function directory: c_volume_min capture volume control min value (in 1/256 dB) c_volume_max capture volume control max value (in 1/256 dB) c_volume_res capture volume control resolution (in 1/256 dB) + c_hs_bint capture bInterval for HS/SS (1-4: fixed, 0: auto) fb_max maximum extra bandwidth in async mode p_chmask playback channel mask - p_srate playback sampling rate + p_srate list of playback sampling rates (comma-separated) p_ssize playback sample size (bytes) p_mute_present playback mute control enable p_volume_present playback volume control enable p_volume_min playback volume control min value (in 1/256 dB) p_volume_max playback volume control max value (in 1/256 dB) p_volume_res playback volume control resolution (in 1/256 dB) + p_hs_bint playback bInterval for HS/SS (1-4: fixed, 0: auto) req_number the number of pre-allocated request for both capture and playback + function_name name of the interface ================ ==================================================== The attributes have sane default values. @@ -916,7 +919,7 @@ The uac1 function provides these attributes in its function directory: ================ ==================================================== c_chmask capture channel mask - c_srate capture sampling rate + c_srate list of capture sampling rates (comma-separated) c_ssize capture sample size (bytes) c_mute_present capture mute control enable c_volume_present capture volume control enable @@ -924,7 +927,7 @@ The uac1 function provides these attributes in its function directory: c_volume_max capture volume control max value (in 1/256 dB) c_volume_res capture volume control resolution (in 1/256 dB) p_chmask playback channel mask - p_srate playback sampling rate + p_srate list of playback sampling rates (comma-separated) p_ssize playback sample size (bytes) p_mute_present playback mute control enable p_volume_present playback volume control enable @@ -933,6 +936,7 @@ The uac1 function provides these attributes in its function directory: p_volume_res playback volume control resolution (in 1/256 dB) req_number the number of pre-allocated requests for both capture and playback + function_name name of the interface ================ ==================================================== The attributes have sane default values. diff --git a/MAINTAINERS b/MAINTAINERS index 1ba1e4af2cbc..63db2398047e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15770,6 +15770,14 @@ F: sound/soc/codecs/wcd-clsh-v2.* F: sound/soc/codecs/wsa881x.c F: sound/soc/qcom/ +QCOM EMBEDDED USB DEBUGGER (EUD) +M: Souradeep Chowdhury <[email protected]> +S: Maintained +F: Documentation/ABI/testing/sysfs-driver-eud +F: Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml +F: drivers/usb/misc/qcom_eud.c + QCOM IPA DRIVER M: Alex Elder <[email protected]> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex.dtsi index 0dd2d2ee765a..f4270cf18996 100644 --- a/arch/arm64/boot/dts/intel/socfpga_agilex.dtsi +++ b/arch/arm64/boot/dts/intel/socfpga_agilex.dtsi @@ -502,7 +502,7 @@ }; usb0: usb@ffb00000 { - compatible = "snps,dwc2"; + compatible = "intel,socfpga-agilex-hsotg", "snps,dwc2"; reg = <0xffb00000 0x40000>; interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>; phys = <&usbphy0>; @@ -515,7 +515,7 @@ }; usb1: usb@ffb40000 { - compatible = "snps,dwc2"; + compatible = "intel,socfpga-agilex-hsotg", "snps,dwc2"; reg = <0xffb40000 0x40000>; interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>; phys = <&usbphy0>; diff --git a/arch/arm64/boot/dts/qcom/ipq6018.dtsi b/arch/arm64/boot/dts/qcom/ipq6018.dtsi index 66ec5615651d..a614b9f73e2c 100644 --- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi +++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi @@ -743,12 +743,13 @@ interrupts = <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>; phys = <&qusb_phy_0>, <&usb0_ssphy>; phy-names = "usb2-phy", "usb3-phy"; + clocks = <&xo>; + clock-names = "ref"; tx-fifo-resize; snps,is-utmi-l1-suspend; snps,hird-threshold = /bits/ 8 <0x0>; snps,dis_u2_susphy_quirk; snps,dis_u3_susphy_quirk; - snps,ref-clock-period-ns = <0x32>; dr_mode = "host"; }; }; diff --git a/arch/arm64/boot/dts/qcom/sc7280-idp.dts b/arch/arm64/boot/dts/qcom/sc7280-idp.dts index 9b991ba5daaf..f40eaa51b5bc 100644 --- a/arch/arm64/boot/dts/qcom/sc7280-idp.dts +++ b/arch/arm64/boot/dts/qcom/sc7280-idp.dts @@ -61,6 +61,10 @@ modem-init; }; +&usb_2_dwc3 { + dr_mode = "otg"; +}; + &pmk8350_rtc { status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/sc7280.dtsi b/arch/arm64/boot/dts/qcom/sc7280.dtsi index 937c2e0e93eb..96917fe81d43 100644 --- a/arch/arm64/boot/dts/qcom/sc7280.dtsi +++ b/arch/arm64/boot/dts/qcom/sc7280.dtsi @@ -2583,6 +2583,12 @@ phys = <&usb_2_hsphy>; phy-names = "usb2-phy"; maximum-speed = "high-speed"; + usb-role-switch; + port { + usb2_role_switch: endpoint { + remote-endpoint = <&eud_ep>; + }; + }; }; }; @@ -2624,6 +2630,36 @@ interrupts = <GIC_SPI 582 IRQ_TYPE_LEVEL_HIGH>; }; + eud: eud@88e0000 { + compatible = "qcom,sc7280-eud","qcom,eud"; + reg = <0 0x88e0000 0 0x2000>, + <0 0x88e2000 0 0x1000>; + interrupts-extended = <&pdc 11 IRQ_TYPE_LEVEL_HIGH>; + ports { + port@0 { + eud_ep: endpoint { + remote-endpoint = <&usb2_role_switch>; + }; + }; + port@1 { + eud_con: endpoint { + remote-endpoint = <&con_eud>; + }; + }; + }; + }; + + eud_typec: connector { + compatible = "usb-c-connector"; + ports { + port@0 { + con_eud: endpoint { + remote-endpoint = <&eud_con>; + }; + }; + }; + }; + nsp_noc: interconnect@a0c0000 { reg = <0 0x0a0c0000 0 0x10000>; compatible = "qcom,sc7280-nsp-noc"; diff --git a/arch/arm64/boot/dts/xilinx/zynqmp-clk-ccf.dtsi b/arch/arm64/boot/dts/xilinx/zynqmp-clk-ccf.dtsi index 1e0b1bca7c94..8493dd7d5f1f 100644 --- a/arch/arm64/boot/dts/xilinx/zynqmp-clk-ccf.dtsi +++ b/arch/arm64/boot/dts/xilinx/zynqmp-clk-ccf.dtsi @@ -223,11 +223,11 @@ clocks = <&zynqmp_clk UART1_REF>, <&zynqmp_clk LPD_LSBUS>; }; -&usb0 { +&dwc3_0 { clocks = <&zynqmp_clk USB0_BUS_REF>, <&zynqmp_clk USB3_DUAL_REF>; }; -&usb1 { +&dwc3_1 { clocks = <&zynqmp_clk USB1_BUS_REF>, <&zynqmp_clk USB3_DUAL_REF>; }; diff --git a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi index 74e66443e4ce..ba68fb8529ee 100644 --- a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi +++ b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi @@ -811,7 +811,6 @@ status = "disabled"; compatible = "xlnx,zynqmp-dwc3"; reg = <0x0 0xff9d0000 0x0 0x100>; - clock-names = "bus_clk", "ref_clk"; power-domains = <&zynqmp_firmware PD_USB_0>; resets = <&zynqmp_reset ZYNQMP_RESET_USB0_CORERESET>, <&zynqmp_reset ZYNQMP_RESET_USB0_HIBERRESET>, @@ -825,6 +824,7 @@ interrupt-parent = <&gic>; interrupt-names = "dwc_usb3", "otg"; interrupts = <0 65 4>, <0 69 4>; + clock-names = "bus_early", "ref"; #stream-id-cells = <1>; iommus = <&smmu 0x860>; snps,quirk-frame-length-adjustment = <0x20>; @@ -838,7 +838,6 @@ status = "disabled"; compatible = "xlnx,zynqmp-dwc3"; reg = <0x0 0xff9e0000 0x0 0x100>; - clock-names = "bus_clk", "ref_clk"; power-domains = <&zynqmp_firmware PD_USB_1>; resets = <&zynqmp_reset ZYNQMP_RESET_USB1_CORERESET>, <&zynqmp_reset ZYNQMP_RESET_USB1_HIBERRESET>, @@ -852,6 +851,7 @@ interrupt-parent = <&gic>; interrupt-names = "dwc_usb3", "otg"; interrupts = <0 70 4>, <0 74 4>; + clock-names = "bus_early", "ref"; #stream-id-cells = <1>; iommus = <&smmu 0x861>; snps,quirk-frame-length-adjustment = <0x20>; diff --git a/drivers/usb/cdns3/cdnsp-debug.h b/drivers/usb/cdns3/cdnsp-debug.h index a8776df2d4e0..f0ca865cce2a 100644 --- a/drivers/usb/cdns3/cdnsp-debug.h +++ b/drivers/usb/cdns3/cdnsp-debug.h @@ -182,208 +182,211 @@ static inline const char *cdnsp_decode_trb(char *str, size_t size, u32 field0, int ep_id = TRB_TO_EP_INDEX(field3) - 1; int type = TRB_FIELD_TO_TYPE(field3); unsigned int ep_num; - int ret = 0; + int ret; u32 temp; ep_num = DIV_ROUND_UP(ep_id, 2); switch (type) { case TRB_LINK: - ret += snprintf(str, size, - "LINK %08x%08x intr %ld type '%s' flags %c:%c:%c:%c", - field1, field0, GET_INTR_TARGET(field2), - cdnsp_trb_type_string(type), - field3 & TRB_IOC ? 'I' : 'i', - field3 & TRB_CHAIN ? 'C' : 'c', - field3 & TRB_TC ? 'T' : 't', - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "LINK %08x%08x intr %ld type '%s' flags %c:%c:%c:%c", + field1, field0, GET_INTR_TARGET(field2), + cdnsp_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_TC ? 'T' : 't', + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_TRANSFER: case TRB_COMPLETION: case TRB_PORT_STATUS: case TRB_HC_EVENT: - ret += snprintf(str, size, - "ep%d%s(%d) type '%s' TRB %08x%08x status '%s'" - " len %ld slot %ld flags %c:%c", - ep_num, ep_id % 2 ? "out" : "in", - TRB_TO_EP_INDEX(field3), - cdnsp_trb_type_string(type), field1, field0, - cdnsp_trb_comp_code_string(GET_COMP_CODE(field2)), - EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3), - field3 & EVENT_DATA ? 'E' : 'e', - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "ep%d%s(%d) type '%s' TRB %08x%08x status '%s'" + " len %ld slot %ld flags %c:%c", + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), + cdnsp_trb_type_string(type), field1, field0, + cdnsp_trb_comp_code_string(GET_COMP_CODE(field2)), + EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3), + field3 & EVENT_DATA ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_MFINDEX_WRAP: - ret += snprintf(str, size, "%s: flags %c", - cdnsp_trb_type_string(type), - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, "%s: flags %c", + cdnsp_trb_type_string(type), + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_SETUP: - ret += snprintf(str, size, - "type '%s' bRequestType %02x bRequest %02x " - "wValue %02x%02x wIndex %02x%02x wLength %d " - "length %ld TD size %ld intr %ld Setup ID %ld " - "flags %c:%c:%c", - cdnsp_trb_type_string(type), - field0 & 0xff, - (field0 & 0xff00) >> 8, - (field0 & 0xff000000) >> 24, - (field0 & 0xff0000) >> 16, - (field1 & 0xff00) >> 8, - field1 & 0xff, - (field1 & 0xff000000) >> 16 | - (field1 & 0xff0000) >> 16, - TRB_LEN(field2), GET_TD_SIZE(field2), - GET_INTR_TARGET(field2), - TRB_SETUPID_TO_TYPE(field3), - field3 & TRB_IDT ? 'D' : 'd', - field3 & TRB_IOC ? 'I' : 'i', - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "type '%s' bRequestType %02x bRequest %02x " + "wValue %02x%02x wIndex %02x%02x wLength %d " + "length %ld TD size %ld intr %ld Setup ID %ld " + "flags %c:%c:%c", + cdnsp_trb_type_string(type), + field0 & 0xff, + (field0 & 0xff00) >> 8, + (field0 & 0xff000000) >> 24, + (field0 & 0xff0000) >> 16, + (field1 & 0xff00) >> 8, + field1 & 0xff, + (field1 & 0xff000000) >> 16 | + (field1 & 0xff0000) >> 16, + TRB_LEN(field2), GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + TRB_SETUPID_TO_TYPE(field3), + field3 & TRB_IDT ? 'D' : 'd', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_DATA: - ret += snprintf(str, size, - "type '%s' Buffer %08x%08x length %ld TD size %ld " - "intr %ld flags %c:%c:%c:%c:%c:%c:%c", - cdnsp_trb_type_string(type), - field1, field0, TRB_LEN(field2), - GET_TD_SIZE(field2), - GET_INTR_TARGET(field2), - field3 & TRB_IDT ? 'D' : 'i', - field3 & TRB_IOC ? 'I' : 'i', - field3 & TRB_CHAIN ? 'C' : 'c', - field3 & TRB_NO_SNOOP ? 'S' : 's', - field3 & TRB_ISP ? 'I' : 'i', - field3 & TRB_ENT ? 'E' : 'e', - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "type '%s' Buffer %08x%08x length %ld TD size %ld " + "intr %ld flags %c:%c:%c:%c:%c:%c:%c", + cdnsp_trb_type_string(type), + field1, field0, TRB_LEN(field2), + GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + field3 & TRB_IDT ? 'D' : 'i', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_STATUS: - ret += snprintf(str, size, - "Buffer %08x%08x length %ld TD size %ld intr" - "%ld type '%s' flags %c:%c:%c:%c", - field1, field0, TRB_LEN(field2), - GET_TD_SIZE(field2), - GET_INTR_TARGET(field2), - cdnsp_trb_type_string(type), - field3 & TRB_IOC ? 'I' : 'i', - field3 & TRB_CHAIN ? 'C' : 'c', - field3 & TRB_ENT ? 'E' : 'e', - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "Buffer %08x%08x length %ld TD size %ld intr" + "%ld type '%s' flags %c:%c:%c:%c", + field1, field0, TRB_LEN(field2), + GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + cdnsp_trb_type_string(type), + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_NORMAL: case TRB_ISOC: case TRB_EVENT_DATA: case TRB_TR_NOOP: - ret += snprintf(str, size, - "type '%s' Buffer %08x%08x length %ld " - "TD size %ld intr %ld " - "flags %c:%c:%c:%c:%c:%c:%c:%c:%c", - cdnsp_trb_type_string(type), - field1, field0, TRB_LEN(field2), - GET_TD_SIZE(field2), - GET_INTR_TARGET(field2), - field3 & TRB_BEI ? 'B' : 'b', - field3 & TRB_IDT ? 'T' : 't', - field3 & TRB_IOC ? 'I' : 'i', - field3 & TRB_CHAIN ? 'C' : 'c', - field3 & TRB_NO_SNOOP ? 'S' : 's', - field3 & TRB_ISP ? 'I' : 'i', - field3 & TRB_ENT ? 'E' : 'e', - field3 & TRB_CYCLE ? 'C' : 'c', - !(field3 & TRB_EVENT_INVALIDATE) ? 'V' : 'v'); + ret = snprintf(str, size, + "type '%s' Buffer %08x%08x length %ld " + "TD size %ld intr %ld " + "flags %c:%c:%c:%c:%c:%c:%c:%c:%c", + cdnsp_trb_type_string(type), + field1, field0, TRB_LEN(field2), + GET_TD_SIZE(field2), + GET_INTR_TARGET(field2), + field3 & TRB_BEI ? 'B' : 'b', + field3 & TRB_IDT ? 'T' : 't', + field3 & TRB_IOC ? 'I' : 'i', + field3 & TRB_CHAIN ? 'C' : 'c', + field3 & TRB_NO_SNOOP ? 'S' : 's', + field3 & TRB_ISP ? 'I' : 'i', + field3 & TRB_ENT ? 'E' : 'e', + field3 & TRB_CYCLE ? 'C' : 'c', + !(field3 & TRB_EVENT_INVALIDATE) ? 'V' : 'v'); break; case TRB_CMD_NOOP: case TRB_ENABLE_SLOT: - ret += snprintf(str, size, "%s: flags %c", - cdnsp_trb_type_string(type), - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, "%s: flags %c", + cdnsp_trb_type_string(type), + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_DISABLE_SLOT: - ret += snprintf(str, size, "%s: slot %ld flags %c", - cdnsp_trb_type_string(type), - TRB_TO_SLOT_ID(field3), - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, "%s: slot %ld flags %c", + cdnsp_trb_type_string(type), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_ADDR_DEV: - ret += snprintf(str, size, - "%s: ctx %08x%08x slot %ld flags %c:%c", - cdnsp_trb_type_string(type), field1, field0, - TRB_TO_SLOT_ID(field3), - field3 & TRB_BSR ? 'B' : 'b', - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "%s: ctx %08x%08x slot %ld flags %c:%c", + cdnsp_trb_type_string(type), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_BSR ? 'B' : 'b', + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_CONFIG_EP: - ret += snprintf(str, size, - "%s: ctx %08x%08x slot %ld flags %c:%c", - cdnsp_trb_type_string(type), field1, field0, - TRB_TO_SLOT_ID(field3), - field3 & TRB_DC ? 'D' : 'd', - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "%s: ctx %08x%08x slot %ld flags %c:%c", + cdnsp_trb_type_string(type), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_DC ? 'D' : 'd', + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_EVAL_CONTEXT: - ret += snprintf(str, size, - "%s: ctx %08x%08x slot %ld flags %c", - cdnsp_trb_type_string(type), field1, field0, - TRB_TO_SLOT_ID(field3), - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "%s: ctx %08x%08x slot %ld flags %c", + cdnsp_trb_type_string(type), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_RESET_EP: case TRB_HALT_ENDPOINT: case TRB_FLUSH_ENDPOINT: - ret += snprintf(str, size, - "%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c", - cdnsp_trb_type_string(type), - ep_num, ep_id % 2 ? "out" : "in", - TRB_TO_EP_INDEX(field3), field1, field0, - TRB_TO_SLOT_ID(field3), - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), field1, field0, + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_STOP_RING: - ret += snprintf(str, size, - "%s: ep%d%s(%d) slot %ld sp %d flags %c", - cdnsp_trb_type_string(type), - ep_num, ep_id % 2 ? "out" : "in", - TRB_TO_EP_INDEX(field3), - TRB_TO_SLOT_ID(field3), - TRB_TO_SUSPEND_PORT(field3), - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "%s: ep%d%s(%d) slot %ld sp %d flags %c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), + TRB_TO_SLOT_ID(field3), + TRB_TO_SUSPEND_PORT(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_SET_DEQ: - ret += snprintf(str, size, - "%s: ep%d%s(%d) deq %08x%08x stream %ld slot %ld flags %c", - cdnsp_trb_type_string(type), - ep_num, ep_id % 2 ? "out" : "in", - TRB_TO_EP_INDEX(field3), field1, field0, - TRB_TO_STREAM_ID(field2), - TRB_TO_SLOT_ID(field3), - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, + "%s: ep%d%s(%d) deq %08x%08x stream %ld slot %ld flags %c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), field1, field0, + TRB_TO_STREAM_ID(field2), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_RESET_DEV: - ret += snprintf(str, size, "%s: slot %ld flags %c", - cdnsp_trb_type_string(type), - TRB_TO_SLOT_ID(field3), - field3 & TRB_CYCLE ? 'C' : 'c'); + ret = snprintf(str, size, "%s: slot %ld flags %c", + cdnsp_trb_type_string(type), + TRB_TO_SLOT_ID(field3), + field3 & TRB_CYCLE ? 'C' : 'c'); break; case TRB_ENDPOINT_NRDY: - temp = TRB_TO_HOST_STREAM(field2); - - ret += snprintf(str, size, - "%s: ep%d%s(%d) H_SID %x%s%s D_SID %lx flags %c:%c", - cdnsp_trb_type_string(type), - ep_num, ep_id % 2 ? "out" : "in", - TRB_TO_EP_INDEX(field3), temp, - temp == STREAM_PRIME_ACK ? "(PRIME)" : "", - temp == STREAM_REJECTED ? "(REJECTED)" : "", - TRB_TO_DEV_STREAM(field0), - field3 & TRB_STAT ? 'S' : 's', - field3 & TRB_CYCLE ? 'C' : 'c'); + temp = TRB_TO_HOST_STREAM(field2); + + ret = snprintf(str, size, + "%s: ep%d%s(%d) H_SID %x%s%s D_SID %lx flags %c:%c", + cdnsp_trb_type_string(type), + ep_num, ep_id % 2 ? "out" : "in", + TRB_TO_EP_INDEX(field3), temp, + temp == STREAM_PRIME_ACK ? "(PRIME)" : "", + temp == STREAM_REJECTED ? "(REJECTED)" : "", + TRB_TO_DEV_STREAM(field0), + field3 & TRB_STAT ? 'S' : 's', + field3 & TRB_CYCLE ? 'C' : 'c'); break; default: - ret += snprintf(str, size, - "type '%s' -> raw %08x %08x %08x %08x", - cdnsp_trb_type_string(type), - field0, field1, field2, field3); + ret = snprintf(str, size, + "type '%s' -> raw %08x %08x %08x %08x", + cdnsp_trb_type_string(type), + field0, field1, field2, field3); } + if (ret >= size) + pr_info("CDNSP: buffer overflowed.\n"); + return str; } diff --git a/drivers/usb/cdns3/cdnsp-gadget.c b/drivers/usb/cdns3/cdnsp-gadget.c index 5c9d07cc5410..c67715f6f756 100644 --- a/drivers/usb/cdns3/cdnsp-gadget.c +++ b/drivers/usb/cdns3/cdnsp-gadget.c @@ -1243,12 +1243,9 @@ static int cdnsp_run(struct cdnsp_device *pdev, enum usb_device_speed speed) { u32 fs_speed = 0; - u64 temp_64; u32 temp; int ret; - temp_64 = cdnsp_read_64(&pdev->ir_set->erst_dequeue); - temp_64 &= ~ERST_PTR_MASK; temp = readl(&pdev->ir_set->irq_control); temp &= ~IMOD_INTERVAL_MASK; temp |= ((IMOD_DEFAULT_INTERVAL / 250) & IMOD_INTERVAL_MASK); diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index f9ca5010f65b..dc6c96e04bcf 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -2152,7 +2152,7 @@ static void udc_id_switch_for_host(struct ci_hdrc *ci) { /* * host doesn't care B_SESSION_VALID event - * so clear and disbale BSV irq + * so clear and disable BSV irq */ if (ci->is_otg) hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS); diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c index 5509d3847af4..0a4f441aff8f 100644 --- a/drivers/usb/common/ulpi.c +++ b/drivers/usb/common/ulpi.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/acpi.h> +#include <linux/debugfs.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/clk/clk-conf.h> @@ -232,9 +233,64 @@ err: return 0; } +static int ulpi_regs_read(struct seq_file *seq, void *data) +{ + struct ulpi *ulpi = seq->private; + +#define ulpi_print(name, reg) do { \ + int ret = ulpi_read(ulpi, reg); \ + if (ret < 0) \ + return ret; \ + seq_printf(seq, name " %.02x\n", ret); \ +} while (0) + + ulpi_print("Vendor ID Low ", ULPI_VENDOR_ID_LOW); + ulpi_print("Vendor ID High ", ULPI_VENDOR_ID_HIGH); + ulpi_print("Product ID Low ", ULPI_PRODUCT_ID_LOW); + ulpi_print("Product ID High ", ULPI_PRODUCT_ID_HIGH); + ulpi_print("Function Control ", ULPI_FUNC_CTRL); + ulpi_print("Interface Control ", ULPI_IFC_CTRL); + ulpi_print("OTG Control ", ULPI_OTG_CTRL); + ulpi_print("USB Interrupt Enable Rising ", ULPI_USB_INT_EN_RISE); + ulpi_print("USB Interrupt Enable Falling", ULPI_USB_INT_EN_FALL); + ulpi_print("USB Interrupt Status ", ULPI_USB_INT_STS); + ulpi_print("USB Interrupt Latch ", ULPI_USB_INT_LATCH); + ulpi_print("Debug ", ULPI_DEBUG); + ulpi_print("Scratch Register ", ULPI_SCRATCH); + ulpi_print("Carkit Control ", ULPI_CARKIT_CTRL); + ulpi_print("Carkit Interrupt Delay ", ULPI_CARKIT_INT_DELAY); + ulpi_print("Carkit Interrupt Enable ", ULPI_CARKIT_INT_EN); + ulpi_print("Carkit Interrupt Status ", ULPI_CARKIT_INT_STS); + ulpi_print("Carkit Interrupt Latch ", ULPI_CARKIT_INT_LATCH); + ulpi_print("Carkit Pulse Control ", ULPI_CARKIT_PLS_CTRL); + ulpi_print("Transmit Positive Width ", ULPI_TX_POS_WIDTH); + ulpi_print("Transmit Negative Width ", ULPI_TX_NEG_WIDTH); + ulpi_print("Receive Polarity Recovery ", ULPI_POLARITY_RECOVERY); + + return 0; +} + +static int ulpi_regs_open(struct inode *inode, struct file *f) +{ + struct ulpi *ulpi = inode->i_private; + + return single_open(f, ulpi_regs_read, ulpi); +} + +static const struct file_operations ulpi_regs_ops = { + .owner = THIS_MODULE, + .open = ulpi_regs_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek +}; + +#define ULPI_ROOT debugfs_lookup(KBUILD_MODNAME, NULL) + static int ulpi_register(struct device *dev, struct ulpi *ulpi) { int ret; + struct dentry *root; ulpi->dev.parent = dev; /* needed early for ops */ ulpi->dev.bus = &ulpi_bus; @@ -259,6 +315,9 @@ static int ulpi_register(struct device *dev, struct ulpi *ulpi) return ret; } + root = debugfs_create_dir(dev_name(dev), ULPI_ROOT); + debugfs_create_file("regs", 0444, root, ulpi, &ulpi_regs_ops); + dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n", ulpi->id.vendor, ulpi->id.product); @@ -304,6 +363,8 @@ EXPORT_SYMBOL_GPL(ulpi_register_interface); */ void ulpi_unregister_interface(struct ulpi *ulpi) { + debugfs_remove_recursive(debugfs_lookup(dev_name(&ulpi->dev), + ULPI_ROOT)); device_unregister(&ulpi->dev); } EXPORT_SYMBOL_GPL(ulpi_unregister_interface); @@ -312,13 +373,21 @@ EXPORT_SYMBOL_GPL(ulpi_unregister_interface); static int __init ulpi_init(void) { - return bus_register(&ulpi_bus); + int ret; + struct dentry *root; + + root = debugfs_create_dir(KBUILD_MODNAME, NULL); + ret = bus_register(&ulpi_bus); + if (ret) + debugfs_remove(root); + return ret; } subsys_initcall(ulpi_init); static void __exit ulpi_exit(void) { bus_unregister(&ulpi_bus); + debugfs_remove_recursive(ULPI_ROOT); } module_exit(ulpi_exit); diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index fa66e6e58792..6abb7294e919 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -139,30 +139,42 @@ MODULE_PARM_DESC(usbfs_memory_mb, /* Hard limit, necessary to avoid arithmetic overflow */ #define USBFS_XFER_MAX (UINT_MAX / 2 - 1000000) -static atomic64_t usbfs_memory_usage; /* Total memory currently allocated */ +static DEFINE_SPINLOCK(usbfs_memory_usage_lock); +static u64 usbfs_memory_usage; /* Total memory currently allocated */ /* Check whether it's okay to allocate more memory for a transfer */ static int usbfs_increase_memory_usage(u64 amount) { - u64 lim; + u64 lim, total_mem; + unsigned long flags; + int ret; lim = READ_ONCE(usbfs_memory_mb); lim <<= 20; - atomic64_add(amount, &usbfs_memory_usage); - - if (lim > 0 && atomic64_read(&usbfs_memory_usage) > lim) { - atomic64_sub(amount, &usbfs_memory_usage); - return -ENOMEM; - } + ret = 0; + spin_lock_irqsave(&usbfs_memory_usage_lock, flags); + total_mem = usbfs_memory_usage + amount; + if (lim > 0 && total_mem > lim) + ret = -ENOMEM; + else + usbfs_memory_usage = total_mem; + spin_unlock_irqrestore(&usbfs_memory_usage_lock, flags); - return 0; + return ret; } /* Memory for a transfer is being deallocated */ static void usbfs_decrease_memory_usage(u64 amount) { - atomic64_sub(amount, &usbfs_memory_usage); + unsigned long flags; + + spin_lock_irqsave(&usbfs_memory_usage_lock, flags); + if (amount > usbfs_memory_usage) + usbfs_memory_usage = 0; + else + usbfs_memory_usage -= amount; + spin_unlock_irqrestore(&usbfs_memory_usage_lock, flags); } static int connected(struct usb_dev_state *ps) diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index d630cccd2e6e..784466117c92 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -248,7 +248,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id, hcd->rsrc_len, driver->description)) break; } - if (region == PCI_ROM_RESOURCE) { + if (region == PCI_STD_NUM_BARS) { dev_dbg(&dev->dev, "no i/o regions available\n"); retval = -EBUSY; goto put_hcd; diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 47a1c8bddf86..1460857026e0 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2983,8 +2983,12 @@ static int hub_port_reset(struct usb_hub *hub, int port1, status); } - /* Check for disconnect or reset */ - if (status == 0 || status == -ENOTCONN || status == -ENODEV) { + /* + * Check for disconnect or reset, and bail out after several + * reset attempts to avoid warm reset loop. + */ + if (status == 0 || status == -ENOTCONN || status == -ENODEV || + (status == -EBUSY && i == PORT_RESET_TRIES - 1)) { usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_RESET); @@ -5005,6 +5009,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, retval = usb_get_bos_descriptor(udev); if (!retval) { udev->lpm_capable = usb_device_supports_lpm(udev); + udev->lpm_disable_count = 1; usb_set_lpm_parameters(udev); } } @@ -5928,16 +5933,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev) */ usb_disable_usb2_hardware_lpm(udev); - /* Disable LPM while we reset the device and reinstall the alt settings. - * Device-initiated LPM, and system exit latency settings are cleared - * when the device is reset, so we have to set them up again. - */ - ret = usb_unlocked_disable_lpm(udev); - if (ret) { - dev_err(&udev->dev, "%s Failed to disable LPM\n", __func__); - goto re_enumerate_no_bos; - } - bos = udev->bos; udev->bos = NULL; @@ -6042,8 +6037,6 @@ done: re_enumerate: usb_release_bos_descriptor(udev); udev->bos = bos; -re_enumerate_no_bos: - /* LPM state doesn't matter when we're about to destroy the device. */ hub_port_logical_disconnect(parent_hub, port1); return -ENODEV; } diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index 50b2fc7fcc0e..bb1da35eb891 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -166,7 +166,7 @@ usb_acpi_get_companion_for_port(struct usb_port *port_dev) if (!parent_handle) return NULL; - acpi_bus_get_device(parent_handle, &adev); + adev = acpi_fetch_acpi_dev(parent_handle); port1 = port_dev->portnum; } diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 2ce3667ec6fa..2f71636af6e1 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -688,6 +688,10 @@ EXPORT_SYMBOL_GPL(usb_alloc_dev); * Drivers for USB interfaces should normally record such references in * their probe() methods, when they bind to an interface, and release * them by calling usb_put_dev(), in their disconnect() methods. + * However, if a driver does not access the usb_device structure after + * its disconnect() method returns then refcounting is not necessary, + * because the USB core guarantees that a usb_device will not be + * deallocated until after all of its interface drivers have been unbound. * * Return: A pointer to the device with the incremented reference counter. */ @@ -722,6 +726,10 @@ EXPORT_SYMBOL_GPL(usb_put_dev); * Drivers for USB interfaces should normally record such references in * their probe() methods, when they bind to an interface, and release * them by calling usb_put_intf(), in their disconnect() methods. + * However, if a driver does not access the usb_interface structure after + * its disconnect() method returns then refcounting is not necessary, + * because the USB core guarantees that a usb_interface will not be + * deallocated until after its driver has been unbound. * * Return: A pointer to the interface with the incremented reference counter. */ diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index d300ae3d9274..1306f4ec788d 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -82,6 +82,14 @@ static void dwc2_set_s3c6400_params(struct dwc2_hsotg *hsotg) p->phy_utmi_width = 8; } +static void dwc2_set_socfpga_agilex_params(struct dwc2_hsotg *hsotg) +{ + struct dwc2_core_params *p = &hsotg->params; + + p->power_down = DWC2_POWER_DOWN_PARAM_NONE; + p->no_clock_gating = true; +} + static void dwc2_set_rk_params(struct dwc2_hsotg *hsotg) { struct dwc2_core_params *p = &hsotg->params; @@ -239,6 +247,8 @@ const struct of_device_id dwc2_of_match_table[] = { .data = dwc2_set_stm32mp15_fsotg_params }, { .compatible = "st,stm32mp15-hsotg", .data = dwc2_set_stm32mp15_hsotg_params }, + { .compatible = "intel,socfpga-agilex-hsotg", + .data = dwc2_set_socfpga_agilex_params }, {}, }; MODULE_DEVICE_TABLE(of, dwc2_of_match_table); diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index f4c09951b517..18adddfba3da 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -347,17 +347,64 @@ static void dwc3_frame_length_adjustment(struct dwc3 *dwc) */ static void dwc3_ref_clk_period(struct dwc3 *dwc) { + unsigned long period; + unsigned long fladj; + unsigned long decr; + unsigned long rate; u32 reg; - if (dwc->ref_clk_per == 0) + if (dwc->ref_clk) { + rate = clk_get_rate(dwc->ref_clk); + if (!rate) + return; + period = NSEC_PER_SEC / rate; + } else if (dwc->ref_clk_per) { + period = dwc->ref_clk_per; + rate = NSEC_PER_SEC / period; + } else { return; + } reg = dwc3_readl(dwc->regs, DWC3_GUCTL); reg &= ~DWC3_GUCTL_REFCLKPER_MASK; - reg |= FIELD_PREP(DWC3_GUCTL_REFCLKPER_MASK, dwc->ref_clk_per); + reg |= FIELD_PREP(DWC3_GUCTL_REFCLKPER_MASK, period); dwc3_writel(dwc->regs, DWC3_GUCTL, reg); -} + if (DWC3_VER_IS_PRIOR(DWC3, 250A)) + return; + + /* + * The calculation below is + * + * 125000 * (NSEC_PER_SEC / (rate * period) - 1) + * + * but rearranged for fixed-point arithmetic. The division must be + * 64-bit because 125000 * NSEC_PER_SEC doesn't fit in 32 bits (and + * neither does rate * period). + * + * Note that rate * period ~= NSEC_PER_SECOND, minus the number of + * nanoseconds of error caused by the truncation which happened during + * the division when calculating rate or period (whichever one was + * derived from the other). We first calculate the relative error, then + * scale it to units of 8 ppm. + */ + fladj = div64_u64(125000ULL * NSEC_PER_SEC, (u64)rate * period); + fladj -= 125000; + + /* + * The documented 240MHz constant is scaled by 2 to get PLS1 as well. + */ + decr = 480000000 / rate; + + reg = dwc3_readl(dwc->regs, DWC3_GFLADJ); + reg &= ~DWC3_GFLADJ_REFCLK_FLADJ_MASK + & ~DWC3_GFLADJ_240MHZDECR + & ~DWC3_GFLADJ_240MHZDECR_PLS1; + reg |= FIELD_PREP(DWC3_GFLADJ_REFCLK_FLADJ_MASK, fladj) + | FIELD_PREP(DWC3_GFLADJ_240MHZDECR, decr >> 1) + | FIELD_PREP(DWC3_GFLADJ_240MHZDECR_PLS1, decr & 1); + dwc3_writel(dwc->regs, DWC3_GFLADJ, reg); +} /** * dwc3_free_one_event_buffer - Frees one event buffer @@ -745,6 +792,38 @@ static int dwc3_phy_setup(struct dwc3 *dwc) return 0; } +static int dwc3_clk_enable(struct dwc3 *dwc) +{ + int ret; + + ret = clk_prepare_enable(dwc->bus_clk); + if (ret) + return ret; + + ret = clk_prepare_enable(dwc->ref_clk); + if (ret) + goto disable_bus_clk; + + ret = clk_prepare_enable(dwc->susp_clk); + if (ret) + goto disable_ref_clk; + + return 0; + +disable_ref_clk: + clk_disable_unprepare(dwc->ref_clk); +disable_bus_clk: + clk_disable_unprepare(dwc->bus_clk); + return ret; +} + +static void dwc3_clk_disable(struct dwc3 *dwc) +{ + clk_disable_unprepare(dwc->susp_clk); + clk_disable_unprepare(dwc->ref_clk); + clk_disable_unprepare(dwc->bus_clk); +} + static void dwc3_core_exit(struct dwc3 *dwc) { dwc3_event_buffers_cleanup(dwc); @@ -758,7 +837,7 @@ static void dwc3_core_exit(struct dwc3 *dwc) usb_phy_set_suspend(dwc->usb3_phy, 1); phy_power_off(dwc->usb2_generic_phy); phy_power_off(dwc->usb3_generic_phy); - clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks); + dwc3_clk_disable(dwc); reset_control_assert(dwc->reset); } @@ -1605,25 +1684,31 @@ static int dwc3_probe(struct platform_device *pdev) return PTR_ERR(dwc->reset); if (dev->of_node) { - ret = devm_clk_bulk_get_all(dev, &dwc->clks); - if (ret == -EPROBE_DEFER) - return ret; /* * Clocks are optional, but new DT platforms should support all * clocks as required by the DT-binding. */ - if (ret < 0) - dwc->num_clks = 0; - else - dwc->num_clks = ret; - + dwc->bus_clk = devm_clk_get_optional(dev, "bus_early"); + if (IS_ERR(dwc->bus_clk)) + return dev_err_probe(dev, PTR_ERR(dwc->bus_clk), + "could not get bus clock\n"); + + dwc->ref_clk = devm_clk_get_optional(dev, "ref"); + if (IS_ERR(dwc->ref_clk)) + return dev_err_probe(dev, PTR_ERR(dwc->ref_clk), + "could not get ref clock\n"); + + dwc->susp_clk = devm_clk_get_optional(dev, "suspend"); + if (IS_ERR(dwc->susp_clk)) + return dev_err_probe(dev, PTR_ERR(dwc->susp_clk), + "could not get suspend clock\n"); } ret = reset_control_deassert(dwc->reset); if (ret) return ret; - ret = clk_bulk_prepare_enable(dwc->num_clks, dwc->clks); + ret = dwc3_clk_enable(dwc); if (ret) goto assert_reset; @@ -1711,7 +1796,7 @@ err1: pm_runtime_disable(&pdev->dev); disable_clks: - clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks); + dwc3_clk_disable(dwc); assert_reset: reset_control_assert(dwc->reset); @@ -1755,7 +1840,7 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc) if (ret) return ret; - ret = clk_bulk_prepare_enable(dwc->num_clks, dwc->clks); + ret = dwc3_clk_enable(dwc); if (ret) goto assert_reset; @@ -1766,7 +1851,7 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc) return 0; disable_clks: - clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks); + dwc3_clk_disable(dwc); assert_reset: reset_control_assert(dwc->reset); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index e1cc3f7398fb..eb9c1efced05 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -388,6 +388,9 @@ /* Global Frame Length Adjustment Register */ #define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7) #define DWC3_GFLADJ_30MHZ_MASK 0x3f +#define DWC3_GFLADJ_REFCLK_FLADJ_MASK GENMASK(21, 8) +#define DWC3_GFLADJ_240MHZDECR GENMASK(30, 24) +#define DWC3_GFLADJ_240MHZDECR_PLS1 BIT(31) /* Global User Control Register*/ #define DWC3_GUCTL_REFCLKPER_MASK 0xffc00000 @@ -978,8 +981,9 @@ struct dwc3_scratchpad_array { * @eps: endpoint array * @gadget: device side representation of the peripheral controller * @gadget_driver: pointer to the gadget driver - * @clks: array of clocks - * @num_clks: number of clocks + * @bus_clk: clock for accessing the registers + * @ref_clk: reference clock + * @susp_clk: clock used when the SS phy is in low power (S3) state * @reset: reset control * @regs: base address for our registers * @regs_size: address space size @@ -1134,8 +1138,9 @@ struct dwc3 { struct usb_gadget *gadget; struct usb_gadget_driver *gadget_driver; - struct clk_bulk_data *clks; - int num_clks; + struct clk *bus_clk; + struct clk *ref_clk; + struct clk *susp_clk; struct reset_control *reset; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index d7f76835137f..b60b5f7b6dff 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -9,6 +9,7 @@ #include <linux/extcon.h> #include <linux/of_graph.h> +#include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/property.h> @@ -559,6 +560,18 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc) if (IS_ERR(dwc->role_sw)) return PTR_ERR(dwc->role_sw); + if (dwc->dev->of_node) { + /* populate connector entry */ + int ret = devm_of_platform_populate(dwc->dev); + + if (ret) { + usb_role_switch_unregister(dwc->role_sw); + dwc->role_sw = NULL; + dev_err(dwc->dev, "DWC3 platform devices creation failed: %i\n", ret); + return ret; + } + } + dwc3_set_mode(dwc, mode); return 0; } diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 06d0e88ec8af..2e0219901c57 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -120,6 +120,13 @@ static const struct property_entry dwc3_pci_intel_properties[] = { {} }; +static const struct property_entry dwc3_pci_intel_phy_charger_detect_properties[] = { + PROPERTY_ENTRY_STRING("dr_mode", "peripheral"), + PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"), + PROPERTY_ENTRY_BOOL("linux,phy_charger_detect"), + {} +}; + static const struct property_entry dwc3_pci_intel_byt_properties[] = { PROPERTY_ENTRY_STRING("dr_mode", "peripheral"), PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"), @@ -169,6 +176,10 @@ static const struct software_node dwc3_pci_intel_swnode = { .properties = dwc3_pci_intel_properties, }; +static const struct software_node dwc3_pci_intel_phy_charger_detect_swnode = { + .properties = dwc3_pci_intel_phy_charger_detect_properties, +}; + static const struct software_node dwc3_pci_intel_byt_swnode = { .properties = dwc3_pci_intel_byt_properties, }; @@ -185,7 +196,8 @@ static const struct software_node dwc3_pci_amd_mr_swnode = { .properties = dwc3_pci_mr_properties, }; -static int dwc3_pci_quirks(struct dwc3_pci *dwc) +static int dwc3_pci_quirks(struct dwc3_pci *dwc, + const struct software_node *swnode) { struct pci_dev *pdev = dwc->pci; @@ -239,10 +251,30 @@ static int dwc3_pci_quirks(struct dwc3_pci *dwc) gpiod_put(gpio); usleep_range(10000, 11000); } + + /* + * Make the pdev name predictable (only 1 DWC3 on BYT) + * and patch the phy dev-name into the lookup table so + * that the phy-driver can get the GPIOs. + */ + dwc->dwc3->id = PLATFORM_DEVID_NONE; + platform_bytcr_gpios.dev_id = "dwc3.ulpi"; + + /* + * Some Android tablets with a Crystal Cove PMIC + * (INT33FD), rely on the TUSB1211 phy for charger + * detection. These can be identified by them _not_ + * using the standard ACPI battery and ac drivers. + */ + if (acpi_dev_present("INT33FD", "1", 2) && + acpi_quirk_skip_acpi_ac_and_battery()) { + dev_info(&pdev->dev, "Using TUSB1211 phy for charger detection\n"); + swnode = &dwc3_pci_intel_phy_charger_detect_swnode; + } } } - return 0; + return device_add_software_node(&dwc->dwc3->dev, swnode); } #ifdef CONFIG_PM @@ -307,11 +339,7 @@ static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) dwc->dwc3->dev.parent = dev; ACPI_COMPANION_SET(&dwc->dwc3->dev, ACPI_COMPANION(dev)); - ret = device_add_software_node(&dwc->dwc3->dev, (void *)id->driver_data); - if (ret < 0) - goto err; - - ret = dwc3_pci_quirks(dwc); + ret = dwc3_pci_quirks(dwc, (void *)id->driver_data); if (ret) goto err; diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 1922fd02043c..4585ee3a444a 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -919,12 +919,12 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, data_len, ret); data_len -= ret; - buf = kmalloc(sizeof(*buf) + data_len, GFP_KERNEL); + buf = kmalloc(struct_size(buf, storage, data_len), GFP_KERNEL); if (!buf) return -ENOMEM; buf->length = data_len; buf->data = buf->storage; - memcpy(buf->storage, data + ret, data_len); + memcpy(buf->storage, data + ret, flex_array_size(buf, storage, data_len)); /* * At this point read_buffer is NULL or READ_BUFFER_DROP (if diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 46dd11dcb3a8..ba899ca8ee70 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -1188,6 +1188,8 @@ static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) int msf = common->cmnd[1] & 0x02; int start_track = common->cmnd[6]; u8 *buf = (u8 *)bh->buf; + u8 format; + int i, len; if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ start_track > 1) { @@ -1195,18 +1197,62 @@ static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) return -EINVAL; } - memset(buf, 0, 20); - buf[1] = (20-2); /* TOC data length */ - buf[2] = 1; /* First track number */ - buf[3] = 1; /* Last track number */ - buf[5] = 0x16; /* Data track, copying allowed */ - buf[6] = 0x01; /* Only track is number 1 */ - store_cdrom_address(&buf[8], msf, 0); + format = common->cmnd[2] & 0xf; + /* + * Check if CDB is old style SFF-8020i + * i.e. format is in 2 MSBs of byte 9 + * Mac OS-X host sends us this. + */ + if (format == 0) + format = (common->cmnd[9] >> 6) & 0x3; + + switch (format) { + case 0: + /* Formatted TOC */ + len = 4 + 2*8; /* 4 byte header + 2 descriptors */ + memset(buf, 0, len); + buf[1] = len - 2; /* TOC Length excludes length field */ + buf[2] = 1; /* First track number */ + buf[3] = 1; /* Last track number */ + buf[5] = 0x16; /* Data track, copying allowed */ + buf[6] = 0x01; /* Only track is number 1 */ + store_cdrom_address(&buf[8], msf, 0); + + buf[13] = 0x16; /* Lead-out track is data */ + buf[14] = 0xAA; /* Lead-out track number */ + store_cdrom_address(&buf[16], msf, curlun->num_sectors); + return len; + + case 2: + /* Raw TOC */ + len = 4 + 3*11; /* 4 byte header + 3 descriptors */ + memset(buf, 0, len); /* Header + A0, A1 & A2 descriptors */ + buf[1] = len - 2; /* TOC Length excludes length field */ + buf[2] = 1; /* First complete session */ + buf[3] = 1; /* Last complete session */ + + buf += 4; + /* fill in A0, A1 and A2 points */ + for (i = 0; i < 3; i++) { + buf[0] = 1; /* Session number */ + buf[1] = 0x16; /* Data track, copying allowed */ + /* 2 - Track number 0 -> TOC */ + buf[3] = 0xA0 + i; /* A0, A1, A2 point */ + /* 4, 5, 6 - Min, sec, frame is zero */ + buf[8] = 1; /* Pmin: last track number */ + buf += 11; /* go to next track descriptor */ + } + buf -= 11; /* go back to A2 descriptor */ - buf[13] = 0x16; /* Lead-out track is data */ - buf[14] = 0xAA; /* Lead-out track number */ - store_cdrom_address(&buf[16], msf, curlun->num_sectors); - return 20; + /* For A2, 7, 8, 9, 10 - zero, Pmin, Psec, Pframe of Lead out */ + store_cdrom_address(&buf[7], msf, curlun->num_sectors); + return len; + + default: + /* Multi-session, PMA, ATIP, CD-TEXT not supported/required */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } } static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh) @@ -1944,7 +1990,7 @@ static int do_scsi_command(struct fsg_common *common) common->data_size_from_cmnd = get_unaligned_be16(&common->cmnd[7]); reply = check_command(common, 10, DATA_DIR_TO_HOST, - (7<<6) | (1<<1), 1, + (0xf<<6) | (3<<1), 1, "READ TOC"); if (reply == 0) reply = do_read_toc(common, bh); diff --git a/drivers/usb/gadget/function/f_phonet.c b/drivers/usb/gadget/function/f_phonet.c index 068ed8417e5a..0bebbdf3f213 100644 --- a/drivers/usb/gadget/function/f_phonet.c +++ b/drivers/usb/gadget/function/f_phonet.c @@ -668,10 +668,8 @@ static struct usb_function *phonet_alloc(struct usb_function_instance *fi) { struct f_phonet *fp; struct f_phonet_opts *opts; - int size; - size = sizeof(*fp) + (phonet_rxq_size * sizeof(struct usb_request *)); - fp = kzalloc(size, GFP_KERNEL); + fp = kzalloc(struct_size(fp, out_reqv, phonet_rxq_size), GFP_KERNEL); if (!fp) return ERR_PTR(-ENOMEM); diff --git a/drivers/usb/gadget/function/f_serial.c b/drivers/usb/gadget/function/f_serial.c index 1ed8ff0ac2d3..a9480b9e312e 100644 --- a/drivers/usb/gadget/function/f_serial.c +++ b/drivers/usb/gadget/function/f_serial.c @@ -345,6 +345,10 @@ static void gser_free(struct usb_function *f) static void gser_unbind(struct usb_configuration *c, struct usb_function *f) { + struct f_gser *gser = func_to_gser(f); + + /* Ensure port is disconnected before unbinding */ + gserial_disconnect(&gser->port); usb_free_all_descriptors(f); } diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index 03f50643fbba..6f0e1d803dc2 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -3,6 +3,7 @@ * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API) * * Copyright (C) 2016 Ruslan Bilovol <[email protected]> + * Copyright (C) 2021 Julian Scheel <[email protected]> * * This driver doesn't expect any real Audio codec to be present * on the device - the audio streams are simply sinked to and @@ -42,6 +43,9 @@ struct f_uac1 { /* Interrupt IN endpoint of AC interface */ struct usb_ep *int_ep; atomic_t int_count; + int ctl_id; /* EP id */ + int c_srate; /* current capture srate */ + int p_srate; /* current playback prate */ }; static inline struct f_uac1 *func_to_uac1(struct usb_function *f) @@ -188,16 +192,18 @@ static struct uac1_as_header_descriptor as_in_header_desc = { .wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM), }; -DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(UAC_MAX_RATES); +#define uac_format_type_i_discrete_descriptor \ + uac_format_type_i_discrete_descriptor_##UAC_MAX_RATES -static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = { - .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), +static struct uac_format_type_i_discrete_descriptor as_out_type_i_desc = { + .bLength = 0, /* filled on rate setup */ .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_FORMAT_TYPE, .bFormatType = UAC_FORMAT_TYPE_I, .bSubframeSize = 2, .bBitResolution = 16, - .bSamFreqType = 1, + .bSamFreqType = 0, /* filled on rate setup */ }; /* Standard ISO OUT Endpoint Descriptor */ @@ -221,14 +227,14 @@ static struct uac_iso_endpoint_descriptor as_iso_out_desc = { .wLockDelay = cpu_to_le16(1), }; -static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = { - .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), +static struct uac_format_type_i_discrete_descriptor as_in_type_i_desc = { + .bLength = 0, /* filled on rate setup */ .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_FORMAT_TYPE, .bFormatType = UAC_FORMAT_TYPE_I, .bSubframeSize = 2, .bBitResolution = 16, - .bSamFreqType = 1, + .bSamFreqType = 0, /* filled on rate setup */ }; /* Standard ISO OUT Endpoint Descriptor */ @@ -303,7 +309,7 @@ enum { }; static struct usb_string strings_uac1[] = { - [STR_AC_IF].s = "AC Interface", + /* [STR_AC_IF].s = DYNAMIC, */ [STR_USB_OUT_IT].s = "Playback Input terminal", [STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels", [STR_IO_OUT_OT].s = "Playback Output terminal", @@ -333,6 +339,30 @@ static struct usb_gadget_strings *uac1_strings[] = { * This function is an ALSA sound card following USB Audio Class Spec 1.0. */ +static void uac_cs_attr_sample_rate(struct usb_ep *ep, struct usb_request *req) +{ + struct usb_function *fn = ep->driver_data; + struct usb_composite_dev *cdev = fn->config->cdev; + struct g_audio *agdev = func_to_g_audio(fn); + struct f_uac1 *uac1 = func_to_uac1(fn); + u8 *buf = (u8 *)req->buf; + u32 val = 0; + + if (req->actual != 3) { + WARN(cdev, "Invalid data size for UAC_EP_CS_ATTR_SAMPLE_RATE.\n"); + return; + } + + val = buf[0] | (buf[1] << 8) | (buf[2] << 16); + if (uac1->ctl_id == (USB_DIR_IN | 2)) { + uac1->p_srate = val; + u_audio_set_playback_srate(agdev, uac1->p_srate); + } else if (uac1->ctl_id == (USB_DIR_OUT | 1)) { + uac1->c_srate = val; + u_audio_set_capture_srate(agdev, uac1->c_srate); + } +} + static void audio_notify_complete(struct usb_ep *_ep, struct usb_request *req) { struct g_audio *audio = req->context; @@ -707,18 +737,27 @@ static int audio_set_endpoint_req(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = f->config->cdev->req; + struct f_uac1 *uac1 = func_to_uac1(f); int value = -EOPNOTSUPP; u16 ep = le16_to_cpu(ctrl->wIndex); u16 len = le16_to_cpu(ctrl->wLength); u16 w_value = le16_to_cpu(ctrl->wValue); + u8 cs = w_value >> 8; DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", ctrl->bRequest, w_value, len, ep); switch (ctrl->bRequest) { - case UAC_SET_CUR: + case UAC_SET_CUR: { + if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) { + cdev->gadget->ep0->driver_data = f; + uac1->ctl_id = ep; + req->complete = uac_cs_attr_sample_rate; + } value = len; break; + } case UAC_SET_MIN: break; @@ -743,16 +782,33 @@ static int audio_get_endpoint_req(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = f->config->cdev->req; + struct f_uac1 *uac1 = func_to_uac1(f); + u8 *buf = (u8 *)req->buf; int value = -EOPNOTSUPP; - u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u8 ep = le16_to_cpu(ctrl->wIndex); u16 len = le16_to_cpu(ctrl->wLength); u16 w_value = le16_to_cpu(ctrl->wValue); + u8 cs = w_value >> 8; + u32 val = 0; DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", ctrl->bRequest, w_value, len, ep); switch (ctrl->bRequest) { - case UAC_GET_CUR: + case UAC_GET_CUR: { + if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) { + if (ep == (USB_DIR_IN | 2)) + val = uac1->p_srate; + else if (ep == (USB_DIR_OUT | 1)) + val = uac1->c_srate; + buf[2] = (val >> 16) & 0xff; + buf[1] = (val >> 8) & 0xff; + buf[0] = val & 0xff; + } + value = len; + break; + } case UAC_GET_MIN: case UAC_GET_MAX: case UAC_GET_RES: @@ -905,6 +961,14 @@ static void f_audio_disable(struct usb_function *f) usb_ep_disable(uac1->int_ep); } +static void +f_audio_suspend(struct usb_function *f) +{ + struct f_uac1 *uac1 = func_to_uac1(f); + + u_audio_suspend(&uac1->g_audio); +} + /*-------------------------------------------------------------------------*/ static struct uac_feature_unit_descriptor *build_fu_desc(int chmask) { @@ -1074,10 +1138,10 @@ static int f_audio_validate_opts(struct g_audio *audio, struct device *dev) } else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) { dev_err(dev, "Error: incorrect capture sample size\n"); return -EINVAL; - } else if (!opts->p_srate) { + } else if (!opts->p_srates[0]) { dev_err(dev, "Error: incorrect playback sampling rate\n"); return -EINVAL; - } else if (!opts->c_srate) { + } else if (!opts->c_srates[0]) { dev_err(dev, "Error: incorrect capture sampling rate\n"); return -EINVAL; } @@ -1118,10 +1182,9 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) struct f_uac1_opts *audio_opts; struct usb_ep *ep = NULL; struct usb_string *us; - u8 *sam_freq; - int rate; int ba_iface_id; int status; + int idx, i; status = f_audio_validate_opts(audio, dev); if (status) @@ -1129,6 +1192,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst); + strings_uac1[STR_AC_IF].s = audio_opts->function_name; + us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1)); if (IS_ERR(us)) return PTR_ERR(us); @@ -1213,12 +1278,25 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) } /* Set sample rates */ - rate = audio_opts->c_srate; - sam_freq = as_out_type_i_desc.tSamFreq[0]; - memcpy(sam_freq, &rate, 3); - rate = audio_opts->p_srate; - sam_freq = as_in_type_i_desc.tSamFreq[0]; - memcpy(sam_freq, &rate, 3); + for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) { + if (audio_opts->c_srates[i] == 0) + break; + memcpy(as_out_type_i_desc.tSamFreq[idx++], + &audio_opts->c_srates[i], 3); + } + as_out_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx); + as_out_type_i_desc.bSamFreqType = idx; + + for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) { + if (audio_opts->p_srates[i] == 0) + break; + memcpy(as_in_type_i_desc.tSamFreq[idx++], + &audio_opts->p_srates[i], 3); + } + as_in_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx); + as_in_type_i_desc.bSamFreqType = idx; + uac1->p_srate = audio_opts->p_srates[0]; + uac1->c_srate = audio_opts->c_srates[0]; /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); @@ -1297,7 +1375,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize); audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize); audio->params.c_chmask = audio_opts->c_chmask; - audio->params.c_srate = audio_opts->c_srate; + memcpy(audio->params.c_srates, audio_opts->c_srates, + sizeof(audio->params.c_srates)); audio->params.c_ssize = audio_opts->c_ssize; if (FUIN_EN(audio_opts)) { audio->params.p_fu.id = USB_IN_FU_ID; @@ -1309,7 +1388,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) audio->params.p_fu.volume_res = audio_opts->p_volume_res; } audio->params.p_chmask = audio_opts->p_chmask; - audio->params.p_srate = audio_opts->p_srate; + memcpy(audio->params.p_srates, audio_opts->p_srates, + sizeof(audio->params.p_srates)); audio->params.p_ssize = audio_opts->p_ssize; if (FUOUT_EN(audio_opts)) { audio->params.c_fu.id = USB_OUT_FU_ID; @@ -1414,11 +1494,106 @@ end: \ \ CONFIGFS_ATTR(f_uac1_opts_, name) +#define UAC1_RATE_ATTRIBUTE(name) \ +static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int result = 0; \ + int i; \ + \ + mutex_lock(&opts->lock); \ + page[0] = '\0'; \ + for (i = 0; i < UAC_MAX_RATES; i++) { \ + if (opts->name##s[i] == 0) \ + break; \ + result += sprintf(page + strlen(page), "%u,", \ + opts->name##s[i]); \ + } \ + if (strlen(page) > 0) \ + page[strlen(page) - 1] = '\n'; \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + char *split_page = NULL; \ + int ret = -EINVAL; \ + char *token; \ + u32 num; \ + int i; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + i = 0; \ + memset(opts->name##s, 0x00, sizeof(opts->name##s)); \ + split_page = kstrdup(page, GFP_KERNEL); \ + while ((token = strsep(&split_page, ",")) != NULL) { \ + ret = kstrtou32(token, 0, &num); \ + if (ret) \ + goto end; \ + \ + opts->name##s[i++] = num; \ + ret = len; \ + }; \ + \ +end: \ + kfree(split_page); \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +#define UAC1_ATTRIBUTE_STRING(name) \ +static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = snprintf(page, sizeof(opts->name), "%s", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int ret = 0; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = snprintf(opts->name, min(sizeof(opts->name), len), \ + "%s", page); \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + UAC1_ATTRIBUTE(u32, c_chmask); -UAC1_ATTRIBUTE(u32, c_srate); +UAC1_RATE_ATTRIBUTE(c_srate); UAC1_ATTRIBUTE(u32, c_ssize); UAC1_ATTRIBUTE(u32, p_chmask); -UAC1_ATTRIBUTE(u32, p_srate); +UAC1_RATE_ATTRIBUTE(p_srate); UAC1_ATTRIBUTE(u32, p_ssize); UAC1_ATTRIBUTE(u32, req_number); @@ -1433,6 +1608,7 @@ UAC1_ATTRIBUTE(bool, c_volume_present); UAC1_ATTRIBUTE(s16, c_volume_min); UAC1_ATTRIBUTE(s16, c_volume_max); UAC1_ATTRIBUTE(s16, c_volume_res); +UAC1_ATTRIBUTE_STRING(function_name); static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_c_chmask, @@ -1455,6 +1631,8 @@ static struct configfs_attribute *f_uac1_attrs[] = { &f_uac1_opts_attr_c_volume_max, &f_uac1_opts_attr_c_volume_res, + &f_uac1_opts_attr_function_name, + NULL, }; @@ -1487,10 +1665,10 @@ static struct usb_function_instance *f_audio_alloc_inst(void) &f_uac1_func_type); opts->c_chmask = UAC1_DEF_CCHMASK; - opts->c_srate = UAC1_DEF_CSRATE; + opts->c_srates[0] = UAC1_DEF_CSRATE; opts->c_ssize = UAC1_DEF_CSSIZE; opts->p_chmask = UAC1_DEF_PCHMASK; - opts->p_srate = UAC1_DEF_PSRATE; + opts->p_srates[0] = UAC1_DEF_PSRATE; opts->p_ssize = UAC1_DEF_PSSIZE; opts->p_mute_present = UAC1_DEF_MUTE_PRESENT; @@ -1506,6 +1684,9 @@ static struct usb_function_instance *f_audio_alloc_inst(void) opts->c_volume_res = UAC1_DEF_RES_DB; opts->req_number = UAC1_DEF_REQ_NUM; + + snprintf(opts->function_name, sizeof(opts->function_name), "AC Interface"); + return &opts->func_inst; } @@ -1562,6 +1743,7 @@ static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) uac1->g_audio.func.get_alt = f_audio_get_alt; uac1->g_audio.func.setup = f_audio_setup; uac1->g_audio.func.disable = f_audio_disable; + uac1->g_audio.func.suspend = f_audio_suspend; uac1->g_audio.func.free_func = f_audio_free; return &uac1->g_audio.func; diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 097a709549d6..1905a8d8e0c9 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -70,6 +70,8 @@ struct f_uac2 { /* Interrupt IN endpoint of AC interface */ struct usb_ep *int_ep; atomic_t int_count; + /* transient state, only valid during handling of a single control request */ + int clock_id; }; static inline struct f_uac2 *func_to_uac2(struct usb_function *f) @@ -104,14 +106,11 @@ enum { STR_AS_IN_ALT1, }; -static char clksrc_in[8]; -static char clksrc_out[8]; - static struct usb_string strings_fn[] = { - [STR_ASSOC].s = "Source/Sink", + /* [STR_ASSOC].s = DYNAMIC, */ [STR_IF_CTRL].s = "Topology Control", - [STR_CLKSRC_IN].s = clksrc_in, - [STR_CLKSRC_OUT].s = clksrc_out, + [STR_CLKSRC_IN].s = "Input Clock", + [STR_CLKSRC_OUT].s = "Output Clock", [STR_USB_IT].s = "USBH Out", [STR_IO_IT].s = "USBD Out", [STR_USB_OT].s = "USBH In", @@ -125,6 +124,16 @@ static struct usb_string strings_fn[] = { { }, }; +static const char *const speed_names[] = { + [USB_SPEED_UNKNOWN] = "UNKNOWN", + [USB_SPEED_LOW] = "LS", + [USB_SPEED_FULL] = "FS", + [USB_SPEED_HIGH] = "HS", + [USB_SPEED_WIRELESS] = "W", + [USB_SPEED_SUPER] = "SS", + [USB_SPEED_SUPER_PLUS] = "SS+", +}; + static struct usb_gadget_strings str_fn = { .language = 0x0409, /* en-us */ .strings = strings_fn, @@ -166,7 +175,7 @@ static struct uac_clock_source_descriptor in_clk_src_desc = { .bDescriptorSubtype = UAC2_CLOCK_SOURCE, /* .bClockID = DYNAMIC */ .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED, - .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL), + .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL), .bAssocTerminal = 0, }; @@ -178,7 +187,7 @@ static struct uac_clock_source_descriptor out_clk_src_desc = { .bDescriptorSubtype = UAC2_CLOCK_SOURCE, /* .bClockID = DYNAMIC */ .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED, - .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL), + .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL), .bAssocTerminal = 0, }; @@ -344,7 +353,7 @@ static struct usb_endpoint_descriptor hs_epout_desc = { /* .bmAttributes = DYNAMIC */ /* .wMaxPacketSize = DYNAMIC */ - .bInterval = 4, + /* .bInterval = DYNAMIC */ }; static struct usb_endpoint_descriptor ss_epout_desc = { @@ -354,7 +363,7 @@ static struct usb_endpoint_descriptor ss_epout_desc = { .bEndpointAddress = USB_DIR_OUT, /* .bmAttributes = DYNAMIC */ /* .wMaxPacketSize = DYNAMIC */ - .bInterval = 4, + /* .bInterval = DYNAMIC */ }; static struct usb_ss_ep_comp_descriptor ss_epout_desc_comp = { @@ -478,7 +487,7 @@ static struct usb_endpoint_descriptor hs_epin_desc = { .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, /* .wMaxPacketSize = DYNAMIC */ - .bInterval = 4, + /* .bInterval = DYNAMIC */ }; static struct usb_endpoint_descriptor ss_epin_desc = { @@ -488,7 +497,7 @@ static struct usb_endpoint_descriptor ss_epin_desc = { .bEndpointAddress = USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC, /* .wMaxPacketSize = DYNAMIC */ - .bInterval = 4, + /* .bInterval = DYNAMIC */ }; static struct usb_ss_ep_comp_descriptor ss_epin_desc_comp = { @@ -634,44 +643,50 @@ struct cntrl_cur_lay3 { __le32 dCUR; }; -struct cntrl_range_lay3 { - __le16 wNumSubRanges; +struct cntrl_subrange_lay3 { __le32 dMIN; __le32 dMAX; __le32 dRES; } __packed; -static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, - struct usb_endpoint_descriptor *ep_desc, - enum usb_device_speed speed, bool is_playback) -{ - int chmask, srate, ssize; - u16 max_size_bw, max_size_ep; - unsigned int factor; +#define ranges_lay3_size(c) (sizeof(c.wNumSubRanges) \ + + le16_to_cpu(c.wNumSubRanges) \ + * sizeof(struct cntrl_subrange_lay3)) - switch (speed) { - case USB_SPEED_FULL: - max_size_ep = 1023; - factor = 1000; - break; +#define DECLARE_UAC2_CNTRL_RANGES_LAY3(k, n) \ + struct cntrl_ranges_lay3_##k { \ + __le16 wNumSubRanges; \ + struct cntrl_subrange_lay3 r[n]; \ +} __packed - case USB_SPEED_HIGH: - case USB_SPEED_SUPER: - max_size_ep = 1024; - factor = 8000; - break; +DECLARE_UAC2_CNTRL_RANGES_LAY3(srates, UAC_MAX_RATES); - default: - return -EINVAL; +static int get_max_srate(const int *srates) +{ + int i, max_srate = 0; + + for (i = 0; i < UAC_MAX_RATES; i++) { + if (srates[i] == 0) + break; + if (srates[i] > max_srate) + max_srate = srates[i]; } + return max_srate; +} + +static int get_max_bw_for_bint(const struct f_uac2_opts *uac2_opts, + u8 bint, unsigned int factor, bool is_playback) +{ + int chmask, srate, ssize; + u16 max_size_bw; if (is_playback) { chmask = uac2_opts->p_chmask; - srate = uac2_opts->p_srate; + srate = get_max_srate(uac2_opts->p_srates); ssize = uac2_opts->p_ssize; } else { chmask = uac2_opts->c_chmask; - srate = uac2_opts->c_srate; + srate = get_max_srate(uac2_opts->c_srates); ssize = uac2_opts->c_ssize; } @@ -681,14 +696,76 @@ static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, srate = srate * (1000 + uac2_opts->fb_max) / 1000; // updated srate is always bigger, therefore DIV_ROUND_UP always yields +1 max_size_bw = num_channels(chmask) * ssize * - (DIV_ROUND_UP(srate, factor / (1 << (ep_desc->bInterval - 1)))); + (DIV_ROUND_UP(srate, factor / (1 << (bint - 1)))); } else { // adding 1 frame provision for Win10 max_size_bw = num_channels(chmask) * ssize * - (DIV_ROUND_UP(srate, factor / (1 << (ep_desc->bInterval - 1))) + 1); + (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))) + 1); } - ep_desc->wMaxPacketSize = cpu_to_le16(min_t(u16, max_size_bw, - max_size_ep)); + return max_size_bw; +} + +static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_opts *uac2_opts, + struct usb_endpoint_descriptor *ep_desc, + enum usb_device_speed speed, bool is_playback) +{ + u16 max_size_bw, max_size_ep; + u8 bint, opts_bint; + char *dir; + + switch (speed) { + case USB_SPEED_FULL: + max_size_ep = 1023; + // fixed + bint = ep_desc->bInterval; + max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 1000, is_playback); + break; + + case USB_SPEED_HIGH: + case USB_SPEED_SUPER: + max_size_ep = 1024; + if (is_playback) + opts_bint = uac2_opts->p_hs_bint; + else + opts_bint = uac2_opts->c_hs_bint; + + if (opts_bint > 0) { + /* fixed bint */ + bint = opts_bint; + max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 8000, is_playback); + } else { + /* checking bInterval from 4 to 1 whether the required bandwidth fits */ + for (bint = 4; bint > 0; --bint) { + max_size_bw = get_max_bw_for_bint( + uac2_opts, bint, 8000, is_playback); + if (max_size_bw <= max_size_ep) + break; + } + } + break; + + default: + return -EINVAL; + } + + if (is_playback) + dir = "Playback"; + else + dir = "Capture"; + + if (max_size_bw <= max_size_ep) + dev_dbg(dev, + "%s %s: Would use wMaxPacketSize %d and bInterval %d\n", + speed_names[speed], dir, max_size_bw, bint); + else { + dev_warn(dev, + "%s %s: Req. wMaxPacketSize %d at bInterval %d > max ISOC %d, may drop data!\n", + speed_names[speed], dir, max_size_bw, bint, max_size_ep); + max_size_bw = max_size_ep; + } + + ep_desc->wMaxPacketSize = cpu_to_le16(max_size_bw); + ep_desc->bInterval = bint; return 0; } @@ -896,50 +973,45 @@ static void setup_descriptor(struct f_uac2_opts *opts) static int afunc_validate_opts(struct g_audio *agdev, struct device *dev) { struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); - - if (!opts->p_chmask && !opts->c_chmask) { - dev_err(dev, "Error: no playback and capture channels\n"); - return -EINVAL; - } else if (opts->p_chmask & ~UAC2_CHANNEL_MASK) { - dev_err(dev, "Error: unsupported playback channels mask\n"); - return -EINVAL; - } else if (opts->c_chmask & ~UAC2_CHANNEL_MASK) { - dev_err(dev, "Error: unsupported capture channels mask\n"); - return -EINVAL; - } else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) { - dev_err(dev, "Error: incorrect playback sample size\n"); - return -EINVAL; - } else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) { - dev_err(dev, "Error: incorrect capture sample size\n"); - return -EINVAL; - } else if (!opts->p_srate) { - dev_err(dev, "Error: incorrect playback sampling rate\n"); + const char *msg = NULL; + + if (!opts->p_chmask && !opts->c_chmask) + msg = "no playback and capture channels"; + else if (opts->p_chmask & ~UAC2_CHANNEL_MASK) + msg = "unsupported playback channels mask"; + else if (opts->c_chmask & ~UAC2_CHANNEL_MASK) + msg = "unsupported capture channels mask"; + else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) + msg = "incorrect playback sample size"; + else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) + msg = "incorrect capture sample size"; + else if (!opts->p_srates[0]) + msg = "incorrect playback sampling rate"; + else if (!opts->c_srates[0]) + msg = "incorrect capture sampling rate"; + + else if (opts->p_volume_max <= opts->p_volume_min) + msg = "incorrect playback volume max/min"; + else if (opts->c_volume_max <= opts->c_volume_min) + msg = "incorrect capture volume max/min"; + else if (opts->p_volume_res <= 0) + msg = "negative/zero playback volume resolution"; + else if (opts->c_volume_res <= 0) + msg = "negative/zero capture volume resolution"; + + else if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) + msg = "incorrect playback volume resolution"; + else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) + msg = "incorrect capture volume resolution"; + + else if ((opts->p_hs_bint < 0) || (opts->p_hs_bint > 4)) + msg = "incorrect playback HS/SS bInterval (1-4: fixed, 0: auto)"; + else if ((opts->c_hs_bint < 0) || (opts->c_hs_bint > 4)) + msg = "incorrect capture HS/SS bInterval (1-4: fixed, 0: auto)"; + + if (msg) { + dev_err(dev, "Error: %s\n", msg); return -EINVAL; - } else if (!opts->c_srate) { - dev_err(dev, "Error: incorrect capture sampling rate\n"); - return -EINVAL; - } - - if (opts->p_volume_max <= opts->p_volume_min) { - dev_err(dev, "Error: incorrect playback volume max/min\n"); - return -EINVAL; - } else if (opts->c_volume_max <= opts->c_volume_min) { - dev_err(dev, "Error: incorrect capture volume max/min\n"); - return -EINVAL; - } else if (opts->p_volume_res <= 0) { - dev_err(dev, "Error: negative/zero playback volume resolution\n"); - return -EINVAL; - } else if (opts->c_volume_res <= 0) { - dev_err(dev, "Error: negative/zero capture volume resolution\n"); - return -EINVAL; - } - - if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) { - dev_err(dev, "Error: incorrect playback volume resolution\n"); - return -EINVAL; - } else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) { - dev_err(dev, "Error: incorrect capture volume resolution\n"); - return -EINVAL; } return 0; @@ -961,6 +1033,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) if (ret) return ret; + strings_fn[STR_ASSOC].s = uac2_opts->function_name; + us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn)); if (IS_ERR(us)) return PTR_ERR(us); @@ -1037,9 +1111,6 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) *bma = cpu_to_le32(control); } - snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", uac2_opts->p_srate); - snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", uac2_opts->c_srate); - ret = usb_interface_id(cfg, fn); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); @@ -1103,44 +1174,49 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) std_ac_if_desc.bNumEndpoints = 1; } + hs_epin_desc.bInterval = uac2_opts->p_hs_bint; + ss_epin_desc.bInterval = uac2_opts->p_hs_bint; + hs_epout_desc.bInterval = uac2_opts->c_hs_bint; + ss_epout_desc.bInterval = uac2_opts->c_hs_bint; + /* Calculate wMaxPacketSize according to audio bandwidth */ - ret = set_ep_max_packet_size(uac2_opts, &fs_epin_desc, USB_SPEED_FULL, - true); + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epin_desc, + USB_SPEED_FULL, true); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size(uac2_opts, &fs_epout_desc, USB_SPEED_FULL, - false); + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epout_desc, + USB_SPEED_FULL, false); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size(uac2_opts, &hs_epin_desc, USB_SPEED_HIGH, - true); + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epin_desc, + USB_SPEED_HIGH, true); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size(uac2_opts, &hs_epout_desc, USB_SPEED_HIGH, - false); + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epout_desc, + USB_SPEED_HIGH, false); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size(uac2_opts, &ss_epin_desc, USB_SPEED_SUPER, - true); + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epin_desc, + USB_SPEED_SUPER, true); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size(uac2_opts, &ss_epout_desc, USB_SPEED_SUPER, - false); + ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epout_desc, + USB_SPEED_SUPER, false); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; @@ -1209,7 +1285,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->gadget = gadget; agdev->params.p_chmask = uac2_opts->p_chmask; - agdev->params.p_srate = uac2_opts->p_srate; + memcpy(agdev->params.p_srates, uac2_opts->p_srates, + sizeof(agdev->params.p_srates)); agdev->params.p_ssize = uac2_opts->p_ssize; if (FUIN_EN(uac2_opts)) { agdev->params.p_fu.id = USB_IN_FU_ID; @@ -1220,7 +1297,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->params.p_fu.volume_res = uac2_opts->p_volume_res; } agdev->params.c_chmask = uac2_opts->c_chmask; - agdev->params.c_srate = uac2_opts->c_srate; + memcpy(agdev->params.c_srates, uac2_opts->c_srates, + sizeof(agdev->params.c_srates)); agdev->params.c_ssize = uac2_opts->c_ssize; if (FUOUT_EN(uac2_opts)) { agdev->params.c_fu.id = USB_OUT_FU_ID; @@ -1411,6 +1489,14 @@ afunc_disable(struct usb_function *fn) usb_ep_disable(uac2->int_ep); } +static void +afunc_suspend(struct usb_function *fn) +{ + struct f_uac2 *uac2 = func_to_uac2(fn); + + u_audio_suspend(&uac2->g_audio); +} + static int in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { @@ -1423,10 +1509,10 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) u8 entity_id = (w_index >> 8) & 0xff; u8 control_selector = w_value >> 8; int value = -EOPNOTSUPP; - int p_srate, c_srate; + u32 p_srate, c_srate; - p_srate = opts->p_srate; - c_srate = opts->c_srate; + u_audio_get_playback_srate(agdev, &p_srate); + u_audio_get_capture_srate(agdev, &c_srate); if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { @@ -1500,28 +1586,39 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) u8 entity_id = (w_index >> 8) & 0xff; u8 control_selector = w_value >> 8; int value = -EOPNOTSUPP; - int p_srate, c_srate; - - p_srate = opts->p_srate; - c_srate = opts->c_srate; if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { - struct cntrl_range_lay3 r; + struct cntrl_ranges_lay3_srates rs; + int i; + int wNumSubRanges = 0; + int srate; + int *srates; if (entity_id == USB_IN_CLK_ID) - r.dMIN = cpu_to_le32(p_srate); + srates = opts->p_srates; else if (entity_id == USB_OUT_CLK_ID) - r.dMIN = cpu_to_le32(c_srate); + srates = opts->c_srates; else return -EOPNOTSUPP; - - r.dMAX = r.dMIN; - r.dRES = 0; - r.wNumSubRanges = cpu_to_le16(1); - - value = min_t(unsigned int, w_length, sizeof(r)); - memcpy(req->buf, &r, value); + for (i = 0; i < UAC_MAX_RATES; i++) { + srate = srates[i]; + if (srate == 0) + break; + + rs.r[wNumSubRanges].dMIN = cpu_to_le32(srate); + rs.r[wNumSubRanges].dMAX = cpu_to_le32(srate); + rs.r[wNumSubRanges].dRES = 0; + wNumSubRanges++; + dev_dbg(&agdev->gadget->dev, + "%s(): clk %d: rate ID %d: %d\n", + __func__, entity_id, wNumSubRanges, srate); + } + rs.wNumSubRanges = cpu_to_le16(wNumSubRanges); + value = min_t(unsigned int, w_length, ranges_lay3_size(rs)); + dev_dbg(&agdev->gadget->dev, "%s(): sending %d rates, size %d\n", + __func__, rs.wNumSubRanges, value); + memcpy(req->buf, &rs, value); } else { dev_err(&agdev->gadget->dev, "%s:%d control_selector=%d TODO!\n", @@ -1580,6 +1677,25 @@ ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr) return -EOPNOTSUPP; } +static void uac2_cs_control_sam_freq(struct usb_ep *ep, struct usb_request *req) +{ + struct usb_function *fn = ep->driver_data; + struct g_audio *agdev = func_to_g_audio(fn); + struct f_uac2 *uac2 = func_to_uac2(fn); + u32 val; + + if (req->actual != 4) + return; + + val = le32_to_cpu(*((__le32 *)req->buf)); + dev_dbg(&agdev->gadget->dev, "%s val: %d.\n", __func__, val); + if (uac2->clock_id == USB_IN_CLK_ID) { + u_audio_set_playback_srate(agdev, val); + } else if (uac2->clock_id == USB_OUT_CLK_ID) { + u_audio_set_capture_srate(agdev, val); + } +} + static void out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req) { @@ -1631,6 +1747,7 @@ out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req) static int out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { + struct usb_composite_dev *cdev = fn->config->cdev; struct usb_request *req = fn->config->cdev->req; struct g_audio *agdev = func_to_g_audio(fn); struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev); @@ -1640,10 +1757,17 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) u16 w_value = le16_to_cpu(cr->wValue); u8 entity_id = (w_index >> 8) & 0xff; u8 control_selector = w_value >> 8; + u8 clock_id = w_index >> 8; if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) { - if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) + if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) { + dev_dbg(&agdev->gadget->dev, + "control_selector UAC2_CS_CONTROL_SAM_FREQ, clock: %d\n", clock_id); + cdev->gadget->ep0->driver_data = fn; + uac2->clock_id = clock_id; + req->complete = uac2_cs_control_sam_freq; return w_length; + } } else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) || (FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) { memcpy(&uac2->setup_cr, cr, sizeof(*cr)); @@ -1731,10 +1855,12 @@ static struct configfs_item_operations f_uac2_item_ops = { .release = f_uac2_attr_release, }; +#define uac2_kstrtou8 kstrtou8 #define uac2_kstrtou32 kstrtou32 #define uac2_kstrtos16 kstrtos16 #define uac2_kstrtobool(s, base, res) kstrtobool((s), (res)) +static const char *u8_fmt = "%u\n"; static const char *u32_fmt = "%u\n"; static const char *s16_fmt = "%hd\n"; static const char *bool_fmt = "%u\n"; @@ -1836,13 +1962,110 @@ end: \ \ CONFIGFS_ATTR(f_uac2_opts_, name) +#define UAC2_RATE_ATTRIBUTE(name) \ +static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int result = 0; \ + int i; \ + \ + mutex_lock(&opts->lock); \ + page[0] = '\0'; \ + for (i = 0; i < UAC_MAX_RATES; i++) { \ + if (opts->name##s[i] == 0) \ + break; \ + result += sprintf(page + strlen(page), "%u,", \ + opts->name##s[i]); \ + } \ + if (strlen(page) > 0) \ + page[strlen(page) - 1] = '\n'; \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + char *split_page = NULL; \ + int ret = -EINVAL; \ + char *token; \ + u32 num; \ + int i; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + i = 0; \ + memset(opts->name##s, 0x00, sizeof(opts->name##s)); \ + split_page = kstrdup(page, GFP_KERNEL); \ + while ((token = strsep(&split_page, ",")) != NULL) { \ + ret = kstrtou32(token, 0, &num); \ + if (ret) \ + goto end; \ + \ + opts->name##s[i++] = num; \ + ret = len; \ + }; \ + \ +end: \ + kfree(split_page); \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac2_opts_, name) + +#define UAC2_ATTRIBUTE_STRING(name) \ +static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = snprintf(page, sizeof(opts->name), "%s", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac2_opts *opts = to_f_uac2_opts(item); \ + int ret = 0; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = snprintf(opts->name, min(sizeof(opts->name), len), \ + "%s", page); \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac2_opts_, name) + UAC2_ATTRIBUTE(u32, p_chmask); -UAC2_ATTRIBUTE(u32, p_srate); +UAC2_RATE_ATTRIBUTE(p_srate); UAC2_ATTRIBUTE(u32, p_ssize); +UAC2_ATTRIBUTE(u8, p_hs_bint); UAC2_ATTRIBUTE(u32, c_chmask); -UAC2_ATTRIBUTE(u32, c_srate); +UAC2_RATE_ATTRIBUTE(c_srate); UAC2_ATTRIBUTE_SYNC(c_sync); UAC2_ATTRIBUTE(u32, c_ssize); +UAC2_ATTRIBUTE(u8, c_hs_bint); UAC2_ATTRIBUTE(u32, req_number); UAC2_ATTRIBUTE(bool, p_mute_present); @@ -1857,14 +2080,17 @@ UAC2_ATTRIBUTE(s16, c_volume_min); UAC2_ATTRIBUTE(s16, c_volume_max); UAC2_ATTRIBUTE(s16, c_volume_res); UAC2_ATTRIBUTE(u32, fb_max); +UAC2_ATTRIBUTE_STRING(function_name); static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_p_chmask, &f_uac2_opts_attr_p_srate, &f_uac2_opts_attr_p_ssize, + &f_uac2_opts_attr_p_hs_bint, &f_uac2_opts_attr_c_chmask, &f_uac2_opts_attr_c_srate, &f_uac2_opts_attr_c_ssize, + &f_uac2_opts_attr_c_hs_bint, &f_uac2_opts_attr_c_sync, &f_uac2_opts_attr_req_number, &f_uac2_opts_attr_fb_max, @@ -1881,6 +2107,8 @@ static struct configfs_attribute *f_uac2_attrs[] = { &f_uac2_opts_attr_c_volume_max, &f_uac2_opts_attr_c_volume_res, + &f_uac2_opts_attr_function_name, + NULL, }; @@ -1913,11 +2141,13 @@ static struct usb_function_instance *afunc_alloc_inst(void) &f_uac2_func_type); opts->p_chmask = UAC2_DEF_PCHMASK; - opts->p_srate = UAC2_DEF_PSRATE; + opts->p_srates[0] = UAC2_DEF_PSRATE; opts->p_ssize = UAC2_DEF_PSSIZE; + opts->p_hs_bint = UAC2_DEF_PHSBINT; opts->c_chmask = UAC2_DEF_CCHMASK; - opts->c_srate = UAC2_DEF_CSRATE; + opts->c_srates[0] = UAC2_DEF_CSRATE; opts->c_ssize = UAC2_DEF_CSSIZE; + opts->c_hs_bint = UAC2_DEF_CHSBINT; opts->c_sync = UAC2_DEF_CSYNC; opts->p_mute_present = UAC2_DEF_MUTE_PRESENT; @@ -1934,6 +2164,9 @@ static struct usb_function_instance *afunc_alloc_inst(void) opts->req_number = UAC2_DEF_REQ_NUM; opts->fb_max = FBACK_FAST_MAX; + + snprintf(opts->function_name, sizeof(opts->function_name), "Source/Sink"); + return &opts->func_inst; } @@ -1985,6 +2218,7 @@ static struct usb_function *afunc_alloc(struct usb_function_instance *fi) uac2->g_audio.func.set_alt = afunc_set_alt; uac2->g_audio.func.get_alt = afunc_get_alt; uac2->g_audio.func.disable = afunc_disable; + uac2->g_audio.func.suspend = afunc_suspend; uac2->g_audio.func.setup = afunc_setup; uac2->g_audio.func.free_func = afunc_free; diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c index 4561d7a183ff..2bb569895a90 100644 --- a/drivers/usb/gadget/function/u_audio.c +++ b/drivers/usb/gadget/function/u_audio.c @@ -32,6 +32,7 @@ enum { UAC_P_PITCH_CTRL, UAC_MUTE_CTRL, UAC_VOLUME_CTRL, + UAC_RATE_CTRL, }; /* Runtime data params for one stream */ @@ -62,6 +63,10 @@ struct uac_rtd_params { s16 volume; int mute; + struct snd_kcontrol *snd_kctl_rate; /* read-only current rate */ + int srate; /* selected samplerate */ + int active; /* playback/capture running */ + spinlock_t lock; /* lock for control transfers */ }; @@ -150,8 +155,6 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) struct snd_pcm_runtime *runtime; struct uac_rtd_params *prm = req->context; struct snd_uac_chip *uac = prm->uac; - struct g_audio *audio_dev = uac->audio_dev; - struct uac_params *params = &audio_dev->params; unsigned int frames, p_pktsize; unsigned long long pitched_rate_mil, p_pktsize_residue_mil, residue_frames_mil, div_result; @@ -196,15 +199,14 @@ static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) */ unsigned long long p_interval_mil = uac->p_interval * 1000000ULL; - pitched_rate_mil = (unsigned long long) - params->p_srate * prm->pitch; + pitched_rate_mil = (unsigned long long) prm->srate * prm->pitch; div_result = pitched_rate_mil; do_div(div_result, uac->p_interval); do_div(div_result, 1000000); frames = (unsigned int) div_result; pr_debug("p_srate %d, pitch %d, interval_mil %llu, frames %d\n", - params->p_srate, prm->pitch, p_interval_mil, frames); + prm->srate, prm->pitch, p_interval_mil, frames); p_pktsize = min_t(unsigned int, uac->p_framesize * frames, @@ -281,7 +283,6 @@ static void u_audio_iso_fback_complete(struct usb_ep *ep, struct uac_rtd_params *prm = req->context; struct snd_uac_chip *uac = prm->uac; struct g_audio *audio_dev = uac->audio_dev; - struct uac_params *params = &audio_dev->params; int status = req->status; /* i/f shutting down */ @@ -303,7 +304,7 @@ static void u_audio_iso_fback_complete(struct usb_ep *ep, __func__, status, req->actual, req->length); u_audio_set_fback_frequency(audio_dev->gadget->speed, audio_dev->out_ep, - params->c_srate, prm->pitch, + prm->srate, prm->pitch, req->buf); if (usb_ep_queue(ep, req, GFP_ATOMIC)) @@ -387,16 +388,14 @@ static int uac_pcm_open(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct g_audio *audio_dev; struct uac_params *params; + struct uac_rtd_params *prm; int p_ssize, c_ssize; - int p_srate, c_srate; int p_chmask, c_chmask; audio_dev = uac->audio_dev; params = &audio_dev->params; p_ssize = params->p_ssize; c_ssize = params->c_ssize; - p_srate = params->p_srate; - c_srate = params->c_srate; p_chmask = params->p_chmask; c_chmask = params->c_chmask; uac->p_residue_mil = 0; @@ -404,19 +403,18 @@ static int uac_pcm_open(struct snd_pcm_substream *substream) runtime->hw = uac_pcm_hardware; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - runtime->hw.rate_min = p_srate; runtime->hw.formats = uac_ssize_to_fmt(p_ssize); runtime->hw.channels_min = num_channels(p_chmask); - runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize - / runtime->hw.periods_min; + prm = &uac->p_prm; } else { - runtime->hw.rate_min = c_srate; runtime->hw.formats = uac_ssize_to_fmt(c_ssize); runtime->hw.channels_min = num_channels(c_chmask); - runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize - / runtime->hw.periods_min; + prm = &uac->c_prm; } + runtime->hw.period_bytes_min = 2 * prm->max_psize + / runtime->hw.periods_min; + runtime->hw.rate_min = prm->srate; runtime->hw.rate_max = runtime->hw.rate_min; runtime->hw.channels_max = runtime->hw.channels_min; @@ -493,6 +491,99 @@ static inline void free_ep_fback(struct uac_rtd_params *prm, struct usb_ep *ep) dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__); } +static void set_active(struct uac_rtd_params *prm, bool active) +{ + // notifying through the Rate ctrl + struct snd_kcontrol *kctl = prm->snd_kctl_rate; + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + if (prm->active != active) { + prm->active = active; + snd_ctl_notify(prm->uac->card, SNDRV_CTL_EVENT_MASK_VALUE, + &kctl->id); + } + spin_unlock_irqrestore(&prm->lock, flags); +} + +int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate) +{ + struct uac_params *params = &audio_dev->params; + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + int i; + unsigned long flags; + + dev_dbg(&audio_dev->gadget->dev, "%s: srate %d\n", __func__, srate); + prm = &uac->c_prm; + for (i = 0; i < UAC_MAX_RATES; i++) { + if (params->c_srates[i] == srate) { + spin_lock_irqsave(&prm->lock, flags); + prm->srate = srate; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; + } + if (params->c_srates[i] == 0) + break; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(u_audio_set_capture_srate); + +int u_audio_get_capture_srate(struct g_audio *audio_dev, u32 *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + prm = &uac->c_prm; + spin_lock_irqsave(&prm->lock, flags); + *val = prm->srate; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_capture_srate); + +int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate) +{ + struct uac_params *params = &audio_dev->params; + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + int i; + unsigned long flags; + + dev_dbg(&audio_dev->gadget->dev, "%s: srate %d\n", __func__, srate); + prm = &uac->p_prm; + for (i = 0; i < UAC_MAX_RATES; i++) { + if (params->p_srates[i] == srate) { + spin_lock_irqsave(&prm->lock, flags); + prm->srate = srate; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; + } + if (params->p_srates[i] == 0) + break; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(u_audio_set_playback_srate); + +int u_audio_get_playback_srate(struct g_audio *audio_dev, u32 *val) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct uac_rtd_params *prm; + unsigned long flags; + + prm = &uac->p_prm; + spin_lock_irqsave(&prm->lock, flags); + *val = prm->srate; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_get_playback_srate); + int u_audio_start_capture(struct g_audio *audio_dev) { struct snd_uac_chip *uac = audio_dev->uac; @@ -504,8 +595,9 @@ int u_audio_start_capture(struct g_audio *audio_dev) struct uac_params *params = &audio_dev->params; int req_len, i; - ep = audio_dev->out_ep; prm = &uac->c_prm; + dev_dbg(dev, "start capture with rate %d\n", prm->srate); + ep = audio_dev->out_ep; config_ep_by_speed(gadget, &audio_dev->func, ep); req_len = ep->maxpacket; @@ -531,6 +623,8 @@ int u_audio_start_capture(struct g_audio *audio_dev) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); } + set_active(&uac->c_prm, true); + ep_fback = audio_dev->in_ep_fback; if (!ep_fback) return 0; @@ -562,7 +656,7 @@ int u_audio_start_capture(struct g_audio *audio_dev) */ prm->pitch = 1000000; u_audio_set_fback_frequency(audio_dev->gadget->speed, ep, - params->c_srate, prm->pitch, + prm->srate, prm->pitch, req_fback->buf); if (usb_ep_queue(ep_fback, req_fback, GFP_ATOMIC)) @@ -576,6 +670,7 @@ void u_audio_stop_capture(struct g_audio *audio_dev) { struct snd_uac_chip *uac = audio_dev->uac; + set_active(&uac->c_prm, false); if (audio_dev->in_ep_fback) free_ep_fback(&uac->c_prm, audio_dev->in_ep_fback); free_ep(&uac->c_prm, audio_dev->out_ep); @@ -596,8 +691,9 @@ int u_audio_start_playback(struct g_audio *audio_dev) int req_len, i; unsigned int p_pktsize; - ep = audio_dev->in_ep; prm = &uac->p_prm; + dev_dbg(dev, "start playback with rate %d\n", prm->srate); + ep = audio_dev->in_ep; config_ep_by_speed(gadget, &audio_dev->func, ep); ep_desc = ep->desc; @@ -618,7 +714,7 @@ int u_audio_start_playback(struct g_audio *audio_dev) uac->p_interval = factor / (1 << (ep_desc->bInterval - 1)); p_pktsize = min_t(unsigned int, uac->p_framesize * - (params->p_srate / uac->p_interval), + (prm->srate / uac->p_interval), ep->maxpacket); req_len = p_pktsize; @@ -646,6 +742,8 @@ int u_audio_start_playback(struct g_audio *audio_dev) dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); } + set_active(&uac->p_prm, true); + return 0; } EXPORT_SYMBOL_GPL(u_audio_start_playback); @@ -654,10 +752,20 @@ void u_audio_stop_playback(struct g_audio *audio_dev) { struct snd_uac_chip *uac = audio_dev->uac; + set_active(&uac->p_prm, false); free_ep(&uac->p_prm, audio_dev->in_ep); } EXPORT_SYMBOL_GPL(u_audio_stop_playback); +void u_audio_suspend(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + + set_active(&uac->p_prm, false); + set_active(&uac->c_prm, false); +} +EXPORT_SYMBOL_GPL(u_audio_suspend); + int u_audio_get_volume(struct g_audio *audio_dev, int playback, s16 *val) { struct snd_uac_chip *uac = audio_dev->uac; @@ -943,6 +1051,68 @@ static int u_audio_volume_put(struct snd_kcontrol *kcontrol, return change; } +static int get_max_srate(const int *srates) +{ + int i, max_srate = 0; + + for (i = 0; i < UAC_MAX_RATES; i++) { + if (srates[i] == 0) + break; + if (srates[i] > max_srate) + max_srate = srates[i]; + } + return max_srate; +} + +static int get_min_srate(const int *srates) +{ + int i, min_srate = INT_MAX; + + for (i = 0; i < UAC_MAX_RATES; i++) { + if (srates[i] == 0) + break; + if (srates[i] < min_srate) + min_srate = srates[i]; + } + return min_srate; +} + +static int u_audio_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + const int *srates; + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev = uac->audio_dev; + struct uac_params *params = &audio_dev->params; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + + if (prm == &uac->c_prm) + srates = params->c_srates; + else + srates = params->p_srates; + uinfo->value.integer.min = get_min_srate(srates); + uinfo->value.integer.max = get_max_srate(srates); + return 0; +} + +static int u_audio_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&prm->lock, flags); + if (prm->active) + ucontrol->value.integer.value[0] = prm->srate; + else + /* not active: reporting zero rate */ + ucontrol->value.integer.value[0] = 0; + spin_unlock_irqrestore(&prm->lock, flags); + return 0; +} static struct snd_kcontrol_new u_audio_controls[] = { [UAC_FBACK_CTRL] { @@ -973,6 +1143,13 @@ static struct snd_kcontrol_new u_audio_controls[] = { .get = u_audio_volume_get, .put = u_audio_volume_put, }, + [UAC_RATE_CTRL] { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "", /* will be filled later */ + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = u_audio_rate_info, + .get = u_audio_rate_get, + }, }; int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, @@ -1005,6 +1182,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, spin_lock_init(&prm->lock); uac->c_prm.uac = uac; prm->max_psize = g_audio->out_ep_maxpsize; + prm->srate = params->c_srates[0]; prm->reqs = kcalloc(params->req_number, sizeof(struct usb_request *), @@ -1029,6 +1207,7 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, spin_lock_init(&prm->lock); uac->p_prm.uac = uac; prm->max_psize = g_audio->in_ep_maxpsize; + prm->srate = params->p_srates[0]; prm->reqs = kcalloc(params->req_number, sizeof(struct usb_request *), @@ -1186,6 +1365,25 @@ int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, prm->volume_min = fu->volume_min; prm->volume_res = fu->volume_res; } + + /* Add rate control */ + snprintf(ctrl_name, sizeof(ctrl_name), + "%s Rate", direction); + u_audio_controls[UAC_RATE_CTRL].name = ctrl_name; + + kctl = snd_ctl_new1(&u_audio_controls[UAC_RATE_CTRL], prm); + if (!kctl) { + err = -ENOMEM; + goto snd_fail; + } + + kctl->id.device = pcm->device; + kctl->id.subdevice = 0; + + err = snd_ctl_add(card, kctl); + if (err < 0) + goto snd_fail; + prm->snd_kctl_rate = kctl; } strscpy(card->driver, card_name, sizeof(card->driver)); diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h index 8dfdae1721cd..9512b8fccfaa 100644 --- a/drivers/usb/gadget/function/u_audio.h +++ b/drivers/usb/gadget/function/u_audio.h @@ -10,6 +10,7 @@ #define __U_AUDIO_H #include <linux/usb/composite.h> +#include "uac_common.h" /* * Same maximum frequency deviation on the slower side as in @@ -40,16 +41,18 @@ struct uac_fu_params { struct uac_params { /* playback */ int p_chmask; /* channel mask */ - int p_srate; /* rate in Hz */ + int p_srates[UAC_MAX_RATES]; /* available rates in Hz (0 terminated list) */ int p_ssize; /* sample size */ struct uac_fu_params p_fu; /* Feature Unit parameters */ /* capture */ int c_chmask; /* channel mask */ - int c_srate; /* rate in Hz */ + int c_srates[UAC_MAX_RATES]; /* available rates in Hz (0 terminated list) */ int c_ssize; /* sample size */ struct uac_fu_params c_fu; /* Feature Unit parameters */ + /* rates are dynamic, in uac_rtd_params */ + int req_number; /* number of preallocated requests */ int fb_max; /* upper frequency drift feedback limit per-mil */ }; @@ -117,9 +120,16 @@ void u_audio_stop_capture(struct g_audio *g_audio); int u_audio_start_playback(struct g_audio *g_audio); void u_audio_stop_playback(struct g_audio *g_audio); +int u_audio_get_capture_srate(struct g_audio *audio_dev, u32 *val); +int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate); +int u_audio_get_playback_srate(struct g_audio *audio_dev, u32 *val); +int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate); + int u_audio_get_volume(struct g_audio *g_audio, int playback, s16 *val); int u_audio_set_volume(struct g_audio *g_audio, int playback, s16 val); int u_audio_get_mute(struct g_audio *g_audio, int playback, int *val); int u_audio_set_mute(struct g_audio *g_audio, int playback, int val); +void u_audio_suspend(struct g_audio *g_audio); + #endif /* __U_AUDIO_H */ diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h index 589fae861141..f7a616760e31 100644 --- a/drivers/usb/gadget/function/u_uac1.h +++ b/drivers/usb/gadget/function/u_uac1.h @@ -9,6 +9,7 @@ #define __U_UAC1_H #include <linux/usb/composite.h> +#include "uac_common.h" #define UAC1_OUT_EP_MAX_PACKET_SIZE 200 #define UAC1_DEF_CCHMASK 0x3 @@ -30,10 +31,10 @@ struct f_uac1_opts { struct usb_function_instance func_inst; int c_chmask; - int c_srate; + int c_srates[UAC_MAX_RATES]; int c_ssize; int p_chmask; - int p_srate; + int p_srates[UAC_MAX_RATES]; int p_ssize; bool p_mute_present; @@ -51,6 +52,8 @@ struct f_uac1_opts { int req_number; unsigned bound:1; + char function_name[32]; + struct mutex lock; int refcnt; }; diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h index e0c8e3513bfd..0510c9bad58d 100644 --- a/drivers/usb/gadget/function/u_uac2.h +++ b/drivers/usb/gadget/function/u_uac2.h @@ -14,13 +14,16 @@ #define U_UAC2_H #include <linux/usb/composite.h> +#include "uac_common.h" #define UAC2_DEF_PCHMASK 0x3 #define UAC2_DEF_PSRATE 48000 #define UAC2_DEF_PSSIZE 2 +#define UAC2_DEF_PHSBINT 0 #define UAC2_DEF_CCHMASK 0x3 #define UAC2_DEF_CSRATE 64000 #define UAC2_DEF_CSSIZE 2 +#define UAC2_DEF_CHSBINT 0 #define UAC2_DEF_CSYNC USB_ENDPOINT_SYNC_ASYNC #define UAC2_DEF_MUTE_PRESENT 1 @@ -35,12 +38,14 @@ struct f_uac2_opts { struct usb_function_instance func_inst; int p_chmask; - int p_srate; + int p_srates[UAC_MAX_RATES]; int p_ssize; + u8 p_hs_bint; int c_chmask; - int c_srate; + int c_srates[UAC_MAX_RATES]; int c_ssize; int c_sync; + u8 c_hs_bint; bool p_mute_present; bool p_volume_present; @@ -58,6 +63,8 @@ struct f_uac2_opts { int fb_max; bool bound; + char function_name[32]; + struct mutex lock; int refcnt; }; diff --git a/drivers/usb/gadget/function/uac_common.h b/drivers/usb/gadget/function/uac_common.h new file mode 100644 index 000000000000..3ecf89d6e814 --- /dev/null +++ b/drivers/usb/gadget/function/uac_common.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + */ + +#ifndef UAC_COMMON_H +#define UAC_COMMON_H + +#define UAC_MAX_RATES 10 /* maximum number of rates configurable by f_uac1/2 */ +#endif diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c index a748ed0842e8..76ea6decf7b6 100644 --- a/drivers/usb/gadget/legacy/audio.c +++ b/drivers/usb/gadget/legacy/audio.c @@ -22,91 +22,108 @@ USB_GADGET_COMPOSITE_OPTIONS(); /* Playback(USB-IN) Default Stereo - Fl/Fr */ static int p_chmask = UAC2_DEF_PCHMASK; -module_param(p_chmask, uint, S_IRUGO); +module_param(p_chmask, uint, 0444); MODULE_PARM_DESC(p_chmask, "Playback Channel Mask"); /* Playback Default 48 KHz */ -static int p_srate = UAC2_DEF_PSRATE; -module_param(p_srate, uint, S_IRUGO); -MODULE_PARM_DESC(p_srate, "Playback Sampling Rate"); +static int p_srates[UAC_MAX_RATES] = {UAC2_DEF_PSRATE}; +static int p_srates_cnt = 1; +module_param_array_named(p_srate, p_srates, uint, &p_srates_cnt, 0444); +MODULE_PARM_DESC(p_srate, "Playback Sampling Rates (array)"); /* Playback Default 16bits/sample */ static int p_ssize = UAC2_DEF_PSSIZE; -module_param(p_ssize, uint, S_IRUGO); +module_param(p_ssize, uint, 0444); MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)"); +/* Playback bInterval for HS/SS (1-4: fixed, 0: auto) */ +static u8 p_hs_bint = UAC2_DEF_PHSBINT; +module_param(p_hs_bint, byte, 0444); +MODULE_PARM_DESC(p_hs_bint, + "Playback bInterval for HS/SS (1-4: fixed, 0: auto)"); + /* Capture(USB-OUT) Default Stereo - Fl/Fr */ static int c_chmask = UAC2_DEF_CCHMASK; -module_param(c_chmask, uint, S_IRUGO); +module_param(c_chmask, uint, 0444); MODULE_PARM_DESC(c_chmask, "Capture Channel Mask"); /* Capture Default 64 KHz */ -static int c_srate = UAC2_DEF_CSRATE; -module_param(c_srate, uint, S_IRUGO); -MODULE_PARM_DESC(c_srate, "Capture Sampling Rate"); +static int c_srates[UAC_MAX_RATES] = {UAC2_DEF_CSRATE}; +static int c_srates_cnt = 1; +module_param_array_named(c_srate, c_srates, uint, &c_srates_cnt, 0444); +MODULE_PARM_DESC(c_srate, "Capture Sampling Rates (array)"); /* Capture Default 16bits/sample */ static int c_ssize = UAC2_DEF_CSSIZE; -module_param(c_ssize, uint, S_IRUGO); +module_param(c_ssize, uint, 0444); MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); + +/* capture bInterval for HS/SS (1-4: fixed, 0: auto) */ +static u8 c_hs_bint = UAC2_DEF_CHSBINT; +module_param(c_hs_bint, byte, 0444); +MODULE_PARM_DESC(c_hs_bint, + "Capture bInterval for HS/SS (1-4: fixed, 0: auto)"); + #else #ifndef CONFIG_GADGET_UAC1_LEGACY #include "u_uac1.h" /* Playback(USB-IN) Default Stereo - Fl/Fr */ static int p_chmask = UAC1_DEF_PCHMASK; -module_param(p_chmask, uint, S_IRUGO); +module_param(p_chmask, uint, 0444); MODULE_PARM_DESC(p_chmask, "Playback Channel Mask"); /* Playback Default 48 KHz */ -static int p_srate = UAC1_DEF_PSRATE; -module_param(p_srate, uint, S_IRUGO); -MODULE_PARM_DESC(p_srate, "Playback Sampling Rate"); +static int p_srates[UAC_MAX_RATES] = {UAC1_DEF_PSRATE}; +static int p_srates_cnt = 1; +module_param_array_named(p_srate, p_srates, uint, &p_srates_cnt, 0444); +MODULE_PARM_DESC(p_srate, "Playback Sampling Rates (array)"); /* Playback Default 16bits/sample */ static int p_ssize = UAC1_DEF_PSSIZE; -module_param(p_ssize, uint, S_IRUGO); +module_param(p_ssize, uint, 0444); MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)"); /* Capture(USB-OUT) Default Stereo - Fl/Fr */ static int c_chmask = UAC1_DEF_CCHMASK; -module_param(c_chmask, uint, S_IRUGO); +module_param(c_chmask, uint, 0444); MODULE_PARM_DESC(c_chmask, "Capture Channel Mask"); /* Capture Default 48 KHz */ -static int c_srate = UAC1_DEF_CSRATE; -module_param(c_srate, uint, S_IRUGO); -MODULE_PARM_DESC(c_srate, "Capture Sampling Rate"); +static int c_srates[UAC_MAX_RATES] = {UAC1_DEF_CSRATE}; +static int c_srates_cnt = 1; +module_param_array_named(c_srate, c_srates, uint, &c_srates_cnt, 0444); +MODULE_PARM_DESC(c_srate, "Capture Sampling Rates (array)"); /* Capture Default 16bits/sample */ static int c_ssize = UAC1_DEF_CSSIZE; -module_param(c_ssize, uint, S_IRUGO); +module_param(c_ssize, uint, 0444); MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); #else /* CONFIG_GADGET_UAC1_LEGACY */ #include "u_uac1_legacy.h" static char *fn_play = FILE_PCM_PLAYBACK; -module_param(fn_play, charp, S_IRUGO); +module_param(fn_play, charp, 0444); MODULE_PARM_DESC(fn_play, "Playback PCM device file name"); static char *fn_cap = FILE_PCM_CAPTURE; -module_param(fn_cap, charp, S_IRUGO); +module_param(fn_cap, charp, 0444); MODULE_PARM_DESC(fn_cap, "Capture PCM device file name"); static char *fn_cntl = FILE_CONTROL; -module_param(fn_cntl, charp, S_IRUGO); +module_param(fn_cntl, charp, 0444); MODULE_PARM_DESC(fn_cntl, "Control device file name"); static int req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE; -module_param(req_buf_size, int, S_IRUGO); +module_param(req_buf_size, int, 0444); MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size"); static int req_count = UAC1_REQ_COUNT; -module_param(req_count, int, S_IRUGO); +module_param(req_count, int, 0444); MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count"); static int audio_buf_size = UAC1_AUDIO_BUF_SIZE; -module_param(audio_buf_size, int, S_IRUGO); +module_param(audio_buf_size, int, 0444); MODULE_PARM_DESC(audio_buf_size, "Audio buffer size"); #endif /* CONFIG_GADGET_UAC1_LEGACY */ #endif @@ -237,9 +254,11 @@ static int audio_bind(struct usb_composite_dev *cdev) { #ifndef CONFIG_GADGET_UAC1 struct f_uac2_opts *uac2_opts; + int i; #else #ifndef CONFIG_GADGET_UAC1_LEGACY struct f_uac1_opts *uac1_opts; + int i; #else struct f_uac1_legacy_opts *uac1_opts; #endif @@ -263,20 +282,34 @@ static int audio_bind(struct usb_composite_dev *cdev) #ifndef CONFIG_GADGET_UAC1 uac2_opts = container_of(fi_uac2, struct f_uac2_opts, func_inst); uac2_opts->p_chmask = p_chmask; - uac2_opts->p_srate = p_srate; + + for (i = 0; i < p_srates_cnt; ++i) + uac2_opts->p_srates[i] = p_srates[i]; + uac2_opts->p_ssize = p_ssize; + uac2_opts->p_hs_bint = p_hs_bint; uac2_opts->c_chmask = c_chmask; - uac2_opts->c_srate = c_srate; + + for (i = 0; i < c_srates_cnt; ++i) + uac2_opts->c_srates[i] = c_srates[i]; + uac2_opts->c_ssize = c_ssize; + uac2_opts->c_hs_bint = c_hs_bint; uac2_opts->req_number = UAC2_DEF_REQ_NUM; #else #ifndef CONFIG_GADGET_UAC1_LEGACY uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst); uac1_opts->p_chmask = p_chmask; - uac1_opts->p_srate = p_srate; + + for (i = 0; i < p_srates_cnt; ++i) + uac1_opts->p_srates[i] = p_srates[i]; + uac1_opts->p_ssize = p_ssize; uac1_opts->c_chmask = c_chmask; - uac1_opts->c_srate = c_srate; + + for (i = 0; i < c_srates_cnt; ++i) + uac1_opts->c_srates[i] = c_srates[i]; + uac1_opts->c_ssize = c_ssize; uac1_opts->req_number = UAC1_DEF_REQ_NUM; #else /* CONFIG_GADGET_UAC1_LEGACY */ diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index 601829a6b4ba..648be3fd476a 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -2730,7 +2730,7 @@ static const struct soc_device_attribute renesas_usb3_quirks_match[] = { .soc_id = "r8a7795", .revision = "ES1.*", .data = &renesas_usb3_priv_r8a7795_es1, }, - { /* sentinel */ }, + { /* sentinel */ } }; static const unsigned int renesas_usb3_cable[] = { diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c index 43f1b0d461c1..be76f891b9c5 100644 --- a/drivers/usb/gadget/udc/tegra-xudc.c +++ b/drivers/usb/gadget/udc/tegra-xudc.c @@ -32,9 +32,6 @@ #include <linux/workqueue.h> /* XUSB_DEV registers */ -#define SPARAM 0x000 -#define SPARAM_ERSTMAX_MASK GENMASK(20, 16) -#define SPARAM_ERSTMAX(x) (((x) << 16) & SPARAM_ERSTMAX_MASK) #define DB 0x004 #define DB_TARGET_MASK GENMASK(15, 8) #define DB_TARGET(x) (((x) << 8) & DB_TARGET_MASK) @@ -275,8 +272,10 @@ BUILD_EP_CONTEXT_RW(deq_hi, deq_hi, 0, 0xffffffff) BUILD_EP_CONTEXT_RW(avg_trb_len, tx_info, 0, 0xffff) BUILD_EP_CONTEXT_RW(max_esit_payload, tx_info, 16, 0xffff) BUILD_EP_CONTEXT_RW(edtla, rsvd[0], 0, 0xffffff) -BUILD_EP_CONTEXT_RW(seq_num, rsvd[0], 24, 0xff) +BUILD_EP_CONTEXT_RW(rsvd, rsvd[0], 24, 0x1) BUILD_EP_CONTEXT_RW(partial_td, rsvd[0], 25, 0x1) +BUILD_EP_CONTEXT_RW(splitxstate, rsvd[0], 26, 0x1) +BUILD_EP_CONTEXT_RW(seq_num, rsvd[0], 27, 0x1f) BUILD_EP_CONTEXT_RW(cerrcnt, rsvd[1], 18, 0x3) BUILD_EP_CONTEXT_RW(data_offset, rsvd[2], 0, 0x1ffff) BUILD_EP_CONTEXT_RW(numtrbs, rsvd[2], 22, 0x1f) @@ -1557,6 +1556,9 @@ static int __tegra_xudc_ep_set_halt(struct tegra_xudc_ep *ep, bool halt) ep_reload(xudc, ep->index); ep_ctx_write_state(ep->context, EP_STATE_RUNNING); + ep_ctx_write_rsvd(ep->context, 0); + ep_ctx_write_partial_td(ep->context, 0); + ep_ctx_write_splitxstate(ep->context, 0); ep_ctx_write_seq_num(ep->context, 0); ep_reload(xudc, ep->index); @@ -2812,7 +2814,10 @@ static void tegra_xudc_reset(struct tegra_xudc *xudc) xudc->setup_seq_num = 0; xudc->queued_setup_packet = false; - ep_ctx_write_seq_num(ep0->context, xudc->setup_seq_num); + ep_ctx_write_rsvd(ep0->context, 0); + ep_ctx_write_partial_td(ep0->context, 0); + ep_ctx_write_splitxstate(ep0->context, 0); + ep_ctx_write_seq_num(ep0->context, 0); deq_ptr = trb_virt_to_phys(ep0, &ep0->transfer_ring[ep0->deq_ptr]); @@ -3295,11 +3300,6 @@ static void tegra_xudc_init_event_ring(struct tegra_xudc *xudc) unsigned int i; u32 val; - val = xudc_readl(xudc, SPARAM); - val &= ~(SPARAM_ERSTMAX_MASK); - val |= SPARAM_ERSTMAX(XUDC_NR_EVENT_RINGS); - xudc_writel(xudc, val, SPARAM); - for (i = 0; i < ARRAY_SIZE(xudc->event_ring); i++) { memset(xudc->event_ring[i], 0, XUDC_EVENT_RING_SIZE * sizeof(*xudc->event_ring[i])); diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 0b7f1edd9eec..c063fb042926 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -931,7 +931,7 @@ static struct debug_buffer *alloc_buffer(struct usb_bus *bus, static int fill_buffer(struct debug_buffer *buf) { - int ret = 0; + int ret; if (!buf->output_buf) buf->output_buf = vmalloc(buf->alloc_size); @@ -956,7 +956,7 @@ static ssize_t debug_output(struct file *file, char __user *user_buf, size_t len, loff_t *offset) { struct debug_buffer *buf = file->private_data; - int ret = 0; + int ret; mutex_lock(&buf->mutex); if (buf->count == 0) { diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index e87cf3a00fa4..638f03b89739 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -21,6 +21,9 @@ static const char hcd_name[] = "ehci-pci"; /* defined here to avoid adding to pci_ids.h for single instance use */ #define PCI_DEVICE_ID_INTEL_CE4100_USB 0x2e70 +#define PCI_VENDOR_ID_ASPEED 0x1a03 +#define PCI_DEVICE_ID_ASPEED_EHCI 0x2603 + /*-------------------------------------------------------------------------*/ #define PCI_DEVICE_ID_INTEL_QUARK_X1000_SOC 0x0939 static inline bool is_intel_quark_x1000(struct pci_dev *pdev) @@ -222,6 +225,12 @@ static int ehci_pci_setup(struct usb_hcd *hcd) ehci->has_synopsys_hc_bug = 1; } break; + case PCI_VENDOR_ID_ASPEED: + if (pdev->device == PCI_DEVICE_ID_ASPEED_EHCI) { + ehci_info(ehci, "applying Aspeed HC workaround\n"); + ehci->is_aspeed = 1; + } + break; } /* optional debug port, normally in the first BAR */ diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index c3dc906274d9..1115431a255d 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -43,7 +43,6 @@ #define hcd_to_ehci_priv(h) ((struct ehci_platform_priv *)hcd_to_ehci(h)->priv) #define BCM_USB_FIFO_THRESHOLD 0x00800040 -#define bcm_iproc_insnreg01 hostpc[0] struct ehci_platform_priv { struct clk *clks[EHCI_MAX_CLKS]; @@ -81,7 +80,7 @@ static int ehci_platform_reset(struct usb_hcd *hcd) if (of_device_is_compatible(pdev->dev.of_node, "brcm,xgs-iproc-ehci")) ehci_writel(ehci, BCM_USB_FIFO_THRESHOLD, - &ehci->regs->bcm_iproc_insnreg01); + &ehci->regs->brcm_insnreg[1]); return 0; } diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 2cbf4f85bff3..a2a5c2996350 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -33,12 +33,13 @@ /* fill a qtd, returning how much of the buffer we were able to queue up */ -static int +static unsigned int qtd_fill(struct ehci_hcd *ehci, struct ehci_qtd *qtd, dma_addr_t buf, size_t len, int token, int maxpacket) { - int i, count; + unsigned int count; u64 addr = buf; + int i; /* one buffer entry per 4K ... first might be short or unaligned */ qtd->hw_buf[0] = cpu_to_hc32(ehci, (u32)addr); @@ -652,7 +653,7 @@ qh_urb_transaction ( * and may serve as a control status ack */ for (;;) { - int this_qtd_len; + unsigned int this_qtd_len; this_qtd_len = qtd_fill(ehci, qtd, buf, this_sg_len, token, maxpacket); diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 0f85aa9b2fb1..bd542b6fc46b 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -1165,10 +1165,8 @@ static struct ehci_iso_sched * iso_sched_alloc(unsigned packets, gfp_t mem_flags) { struct ehci_iso_sched *iso_sched; - int size = sizeof(*iso_sched); - size += packets * sizeof(struct ehci_iso_packet); - iso_sched = kzalloc(size, mem_flags); + iso_sched = kzalloc(struct_size(iso_sched, packet, packets), mem_flags); if (likely(iso_sched != NULL)) INIT_LIST_HEAD(&iso_sched->td_list); diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index 7af17c8e069b..c3fd375b4778 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -4014,10 +4014,8 @@ static struct fotg210_iso_sched *iso_sched_alloc(unsigned packets, gfp_t mem_flags) { struct fotg210_iso_sched *iso_sched; - int size = sizeof(*iso_sched); - size += packets * sizeof(struct fotg210_iso_packet); - iso_sched = kzalloc(size, mem_flags); + iso_sched = kzalloc(struct_size(iso_sched, packet, packets), mem_flags); if (likely(iso_sched != NULL)) INIT_LIST_HEAD(&iso_sched->td_list); diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c index 4f267dc93882..76bc8d56325d 100644 --- a/drivers/usb/host/ohci-dbg.c +++ b/drivers/usb/host/ohci-dbg.c @@ -680,7 +680,7 @@ static struct debug_buffer *alloc_buffer(struct ohci_hcd *ohci, static int fill_buffer(struct debug_buffer *buf) { - int ret = 0; + int ret; if (!buf->page) buf->page = (char *)get_zeroed_page(GFP_KERNEL); @@ -705,7 +705,7 @@ static ssize_t debug_output(struct file *file, char __user *user_buf, size_t len, loff_t *offset) { struct debug_buffer *buf = file->private_data; - int ret = 0; + int ret; mutex_lock(&buf->mutex); if (buf->count == 0) { diff --git a/drivers/usb/host/xhci-dbgcap.c b/drivers/usb/host/xhci-dbgcap.c index ccb0156fcebe..e61155fa6379 100644 --- a/drivers/usb/host/xhci-dbgcap.c +++ b/drivers/usb/host/xhci-dbgcap.c @@ -914,59 +914,6 @@ static void xhci_dbc_handle_events(struct work_struct *work) mod_delayed_work(system_wq, &dbc->event_work, 1); } -static void xhci_do_dbc_exit(struct xhci_hcd *xhci) -{ - unsigned long flags; - - spin_lock_irqsave(&xhci->lock, flags); - kfree(xhci->dbc); - xhci->dbc = NULL; - spin_unlock_irqrestore(&xhci->lock, flags); -} - -static int xhci_do_dbc_init(struct xhci_hcd *xhci) -{ - u32 reg; - struct xhci_dbc *dbc; - unsigned long flags; - void __iomem *base; - int dbc_cap_offs; - - base = &xhci->cap_regs->hc_capbase; - dbc_cap_offs = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_DEBUG); - if (!dbc_cap_offs) - return -ENODEV; - - dbc = kzalloc(sizeof(*dbc), GFP_KERNEL); - if (!dbc) - return -ENOMEM; - - dbc->regs = base + dbc_cap_offs; - - /* We will avoid using DbC in xhci driver if it's in use. */ - reg = readl(&dbc->regs->control); - if (reg & DBC_CTRL_DBC_ENABLE) { - kfree(dbc); - return -EBUSY; - } - - spin_lock_irqsave(&xhci->lock, flags); - if (xhci->dbc) { - spin_unlock_irqrestore(&xhci->lock, flags); - kfree(dbc); - return -EBUSY; - } - xhci->dbc = dbc; - spin_unlock_irqrestore(&xhci->lock, flags); - - dbc->xhci = xhci; - dbc->dev = xhci_to_hcd(xhci)->self.sysdev; - INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events); - spin_lock_init(&dbc->lock); - - return 0; -} - static ssize_t dbc_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -1026,44 +973,86 @@ static ssize_t dbc_store(struct device *dev, static DEVICE_ATTR_RW(dbc); -int xhci_dbc_init(struct xhci_hcd *xhci) +struct xhci_dbc * +xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *driver) { + struct xhci_dbc *dbc; int ret; - struct device *dev = xhci_to_hcd(xhci)->self.controller; - ret = xhci_do_dbc_init(xhci); - if (ret) - goto init_err3; + dbc = kzalloc(sizeof(*dbc), GFP_KERNEL); + if (!dbc) + return NULL; - ret = xhci_dbc_tty_probe(xhci); - if (ret) - goto init_err2; + dbc->regs = base; + dbc->dev = dev; + dbc->driver = driver; + + if (readl(&dbc->regs->control) & DBC_CTRL_DBC_ENABLE) + return NULL; + + INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events); + spin_lock_init(&dbc->lock); ret = device_create_file(dev, &dev_attr_dbc); if (ret) - goto init_err1; + goto err; - return 0; + return dbc; +err: + kfree(dbc); + return NULL; +} + +/* undo what xhci_alloc_dbc() did */ +void xhci_dbc_remove(struct xhci_dbc *dbc) +{ + if (!dbc) + return; + /* stop hw, stop wq and call dbc->ops->stop() */ + xhci_dbc_stop(dbc); + + /* remove sysfs files */ + device_remove_file(dbc->dev, &dev_attr_dbc); + + kfree(dbc); +} + + +int xhci_create_dbc_dev(struct xhci_hcd *xhci) +{ + struct device *dev; + void __iomem *base; + int ret; + int dbc_cap_offs; + + /* create all parameters needed resembling a dbc device */ + dev = xhci_to_hcd(xhci)->self.controller; + base = &xhci->cap_regs->hc_capbase; + + dbc_cap_offs = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_DEBUG); + if (!dbc_cap_offs) + return -ENODEV; + + /* already allocated and in use */ + if (xhci->dbc) + return -EBUSY; + + ret = xhci_dbc_tty_probe(dev, base + dbc_cap_offs, xhci); -init_err1: - xhci_dbc_tty_remove(xhci->dbc); -init_err2: - xhci_do_dbc_exit(xhci); -init_err3: return ret; } -void xhci_dbc_exit(struct xhci_hcd *xhci) +void xhci_remove_dbc_dev(struct xhci_hcd *xhci) { - struct device *dev = xhci_to_hcd(xhci)->self.controller; + unsigned long flags; if (!xhci->dbc) return; - device_remove_file(dev, &dev_attr_dbc); xhci_dbc_tty_remove(xhci->dbc); - xhci_dbc_stop(xhci->dbc); - xhci_do_dbc_exit(xhci); + spin_lock_irqsave(&xhci->lock, flags); + xhci->dbc = NULL; + spin_unlock_irqrestore(&xhci->lock, flags); } #ifdef CONFIG_PM @@ -1098,3 +1087,13 @@ int xhci_dbc_resume(struct xhci_hcd *xhci) return ret; } #endif /* CONFIG_PM */ + +int xhci_dbc_init(void) +{ + return dbc_tty_init(); +} + +void xhci_dbc_exit(void) +{ + dbc_tty_exit(); +} diff --git a/drivers/usb/host/xhci-dbgcap.h b/drivers/usb/host/xhci-dbgcap.h index c70b78d504eb..ca04192fdab1 100644 --- a/drivers/usb/host/xhci-dbgcap.h +++ b/drivers/usb/host/xhci-dbgcap.h @@ -100,6 +100,7 @@ struct dbc_ep { struct dbc_port { struct tty_port port; spinlock_t port_lock; /* port access */ + int minor; struct list_head read_pool; struct list_head read_queue; @@ -194,10 +195,17 @@ static inline struct dbc_ep *get_out_ep(struct xhci_dbc *dbc) } #ifdef CONFIG_USB_XHCI_DBGCAP -int xhci_dbc_init(struct xhci_hcd *xhci); -void xhci_dbc_exit(struct xhci_hcd *xhci); -int xhci_dbc_tty_probe(struct xhci_hcd *xhci); +int xhci_create_dbc_dev(struct xhci_hcd *xhci); +void xhci_remove_dbc_dev(struct xhci_hcd *xhci); +int xhci_dbc_init(void); +void xhci_dbc_exit(void); +int dbc_tty_init(void); +void dbc_tty_exit(void); +int xhci_dbc_tty_probe(struct device *dev, void __iomem *res, struct xhci_hcd *xhci); void xhci_dbc_tty_remove(struct xhci_dbc *dbc); +struct xhci_dbc *xhci_alloc_dbc(struct device *dev, void __iomem *res, + const struct dbc_driver *driver); +void xhci_dbc_remove(struct xhci_dbc *dbc); struct dbc_request *dbc_alloc_request(struct xhci_dbc *dbc, unsigned int direction, gfp_t flags); @@ -208,15 +216,21 @@ int xhci_dbc_suspend(struct xhci_hcd *xhci); int xhci_dbc_resume(struct xhci_hcd *xhci); #endif /* CONFIG_PM */ #else -static inline int xhci_dbc_init(struct xhci_hcd *xhci) +static inline int xhci_create_dbc_dev(struct xhci_hcd *xhci) { return 0; } -static inline void xhci_dbc_exit(struct xhci_hcd *xhci) +static inline void xhci_remove_dbc_dev(struct xhci_hcd *xhci) +{ +} +static inline int xhci_dbc_init(void) +{ + return 0; +} +static inline void xhci_dbc_exit(void) { } - static inline int xhci_dbc_suspend(struct xhci_hcd *xhci) { return 0; diff --git a/drivers/usb/host/xhci-dbgtty.c b/drivers/usb/host/xhci-dbgtty.c index eb46e642e87a..d3acc0829ee5 100644 --- a/drivers/usb/host/xhci-dbgtty.c +++ b/drivers/usb/host/xhci-dbgtty.c @@ -10,14 +10,14 @@ #include <linux/slab.h> #include <linux/tty.h> #include <linux/tty_flip.h> +#include <linux/idr.h> #include "xhci.h" #include "xhci-dbgcap.h" -static int dbc_tty_init(void); -static void dbc_tty_exit(void); - static struct tty_driver *dbc_tty_driver; +static struct idr dbc_tty_minors; +static DEFINE_MUTEX(dbc_tty_minors_lock); static inline struct dbc_port *dbc_to_port(struct xhci_dbc *dbc) { @@ -180,7 +180,14 @@ xhci_dbc_free_requests(struct list_head *head) static int dbc_tty_install(struct tty_driver *driver, struct tty_struct *tty) { - struct dbc_port *port = driver->driver_state; + struct dbc_port *port; + + mutex_lock(&dbc_tty_minors_lock); + port = idr_find(&dbc_tty_minors, tty->index); + mutex_unlock(&dbc_tty_minors_lock); + + if (!port) + return -ENXIO; tty->driver_data = port; @@ -409,6 +416,15 @@ static int xhci_dbc_tty_register_device(struct xhci_dbc *dbc) xhci_dbc_tty_init_port(dbc, port); + mutex_lock(&dbc_tty_minors_lock); + port->minor = idr_alloc(&dbc_tty_minors, port, 0, 64, GFP_KERNEL); + mutex_unlock(&dbc_tty_minors_lock); + + if (port->minor < 0) { + ret = port->minor; + goto err_idr; + } + ret = kfifo_alloc(&port->write_fifo, DBC_WRITE_BUF_SIZE, GFP_KERNEL); if (ret) goto err_exit_port; @@ -424,7 +440,7 @@ static int xhci_dbc_tty_register_device(struct xhci_dbc *dbc) goto err_free_requests; tty_dev = tty_port_register_device(&port->port, - dbc_tty_driver, 0, NULL); + dbc_tty_driver, port->minor, NULL); if (IS_ERR(tty_dev)) { ret = PTR_ERR(tty_dev); goto err_free_requests; @@ -440,6 +456,8 @@ err_free_requests: err_free_fifo: kfifo_free(&port->write_fifo); err_exit_port: + idr_remove(&dbc_tty_minors, port->minor); +err_idr: xhci_dbc_tty_exit_port(port); dev_err(dbc->dev, "can't register tty port, err %d\n", ret); @@ -453,10 +471,14 @@ static void xhci_dbc_tty_unregister_device(struct xhci_dbc *dbc) if (!port->registered) return; - tty_unregister_device(dbc_tty_driver, 0); + tty_unregister_device(dbc_tty_driver, port->minor); xhci_dbc_tty_exit_port(port); port->registered = false; + mutex_lock(&dbc_tty_minors_lock); + idr_remove(&dbc_tty_minors, port->minor); + mutex_unlock(&dbc_tty_minors_lock); + kfifo_free(&port->write_fifo); xhci_dbc_free_requests(&port->read_pool); xhci_dbc_free_requests(&port->read_queue); @@ -468,33 +490,35 @@ static const struct dbc_driver dbc_driver = { .disconnect = xhci_dbc_tty_unregister_device, }; -int xhci_dbc_tty_probe(struct xhci_hcd *xhci) +int xhci_dbc_tty_probe(struct device *dev, void __iomem *base, struct xhci_hcd *xhci) { - struct xhci_dbc *dbc = xhci->dbc; + struct xhci_dbc *dbc; struct dbc_port *port; int status; - /* dbc_tty_init will be called by module init() in the future */ - status = dbc_tty_init(); - if (status) - return status; + if (!dbc_tty_driver) + return -ENODEV; port = kzalloc(sizeof(*port), GFP_KERNEL); - if (!port) { + if (!port) + return -ENOMEM; + + dbc = xhci_alloc_dbc(dev, base, &dbc_driver); + + if (!dbc) { status = -ENOMEM; - goto out; + goto out2; } - dbc->driver = &dbc_driver; dbc->priv = port; - - dbc_tty_driver->driver_state = port; + /* get rid of xhci once this is a real driver binding to a device */ + xhci->dbc = dbc; return 0; -out: - /* dbc_tty_exit will be called by module_exit() in the future */ - dbc_tty_exit(); +out2: + kfree(port); + return status; } @@ -506,22 +530,22 @@ void xhci_dbc_tty_remove(struct xhci_dbc *dbc) { struct dbc_port *port = dbc_to_port(dbc); - dbc->driver = NULL; - dbc->priv = NULL; + xhci_dbc_remove(dbc); kfree(port); - - /* dbc_tty_exit will be called by module_exit() in the future */ - dbc_tty_exit(); } -static int dbc_tty_init(void) +int dbc_tty_init(void) { int ret; - dbc_tty_driver = tty_alloc_driver(1, TTY_DRIVER_REAL_RAW | + idr_init(&dbc_tty_minors); + + dbc_tty_driver = tty_alloc_driver(64, TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); - if (IS_ERR(dbc_tty_driver)) + if (IS_ERR(dbc_tty_driver)) { + idr_destroy(&dbc_tty_minors); return PTR_ERR(dbc_tty_driver); + } dbc_tty_driver->driver_name = "dbc_serial"; dbc_tty_driver->name = "ttyDBC"; @@ -540,15 +564,19 @@ static int dbc_tty_init(void) if (ret) { pr_err("Can't register dbc tty driver\n"); tty_driver_kref_put(dbc_tty_driver); + idr_destroy(&dbc_tty_minors); } + return ret; } -static void dbc_tty_exit(void) +void dbc_tty_exit(void) { if (dbc_tty_driver) { tty_unregister_driver(dbc_tty_driver); tty_driver_kref_put(dbc_tty_driver); dbc_tty_driver = NULL; } + + idr_destroy(&dbc_tty_minors); } diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index df3522dab31b..1e7dc130c39a 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -762,7 +762,7 @@ static int xhci_exit_test_mode(struct xhci_hcd *xhci) } pm_runtime_allow(xhci_to_hcd(xhci)->self.controller); xhci->test_mode = 0; - return xhci_reset(xhci); + return xhci_reset(xhci, XHCI_RESET_SHORT_USEC); } void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, @@ -1088,6 +1088,9 @@ static void xhci_get_usb2_port_status(struct xhci_port *port, u32 *status, if (link_state == XDEV_U2) *status |= USB_PORT_STAT_L1; if (link_state == XDEV_U0) { + if (bus_state->resume_done[portnum]) + usb_hcd_end_port_resume(&port->rhub->hcd->self, + portnum); bus_state->resume_done[portnum] = 0; clear_bit(portnum, &bus_state->resuming_ports); if (bus_state->suspended_ports & (1 << portnum)) { diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 0e312066c5c6..bbb27ee2c6a3 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -57,7 +57,7 @@ static struct xhci_segment *xhci_segment_alloc(struct xhci_hcd *xhci, /* If the cycle state is 0, set the cycle bit to 1 for all the TRBs */ if (cycle_state == 0) { for (i = 0; i < TRBS_PER_SEGMENT; i++) - seg->trbs[i].link.control |= cpu_to_le32(TRB_CYCLE); + seg->trbs[i].link.control = cpu_to_le32(TRB_CYCLE); } seg->dma = dma; seg->next = NULL; @@ -433,8 +433,7 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, (TRBS_PER_SEGMENT - 1); /* Allocate number of segments we needed, or double the ring size */ - num_segs = ring->num_segs > num_segs_needed ? - ring->num_segs : num_segs_needed; + num_segs = max(ring->num_segs, num_segs_needed); ret = xhci_alloc_segments_for_ring(xhci, &first, &last, num_segs, ring->cycle_state, ring->type, @@ -1846,9 +1845,6 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci->event_ring = NULL; xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed event ring"); - if (xhci->lpm_command) - xhci_free_command(xhci, xhci->lpm_command); - xhci->lpm_command = NULL; if (xhci->cmd_ring) xhci_ring_free(xhci, xhci->cmd_ring); xhci->cmd_ring = NULL; @@ -2395,11 +2391,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) page_size = readl(&xhci->op_regs->page_size); xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Supported page size register = 0x%x", page_size); - for (i = 0; i < 16; i++) { - if ((0x1 & page_size) != 0) - break; - page_size = page_size >> 1; - } + i = ffs(page_size); if (i < 16) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Supported page size of %iK", (1 << (i+12)) / 1024); @@ -2425,7 +2417,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) writel(val, &xhci->op_regs->config_reg); /* - * xHCI section 5.4.6 - doorbell array must be + * xHCI section 5.4.6 - Device Context array must be * "physically contiguous and 64-byte (cache line) aligned". */ xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma, @@ -2488,10 +2480,6 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) "// Setting command ring address to 0x%016llx", val_64); xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring); - xhci->lpm_command = xhci_alloc_command_with_ctx(xhci, true, flags); - if (!xhci->lpm_command) - goto fail; - /* Reserve one command ring TRB for disabling LPM. * Since the USB core grabs the shared usb_bus bandwidth mutex before * disabling LPM, we only need to reserve one TRB for all devices. @@ -2583,7 +2571,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) fail: xhci_halt(xhci); - xhci_reset(xhci); + xhci_reset(xhci, XHCI_RESET_SHORT_USEC); xhci_mem_cleanup(xhci); return -ENOMEM; } diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index edbfa82c6565..f3139ce7b0a9 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -248,7 +248,6 @@ create_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev, struct mu3h_sch_bw_info *bw_info; struct mu3h_sch_tt *tt = NULL; u32 len_bw_budget_table; - size_t mem_size; bw_info = get_bw_info(mtk, udev, ep); if (!bw_info) @@ -262,9 +261,9 @@ create_sch_ep(struct xhci_hcd_mtk *mtk, struct usb_device *udev, else len_bw_budget_table = 1; - mem_size = sizeof(struct mu3h_sch_ep_info) + - len_bw_budget_table * sizeof(u32); - sch_ep = kzalloc(mem_size, GFP_KERNEL); + sch_ep = kzalloc(struct_size(sch_ep, bw_budget_table, + len_bw_budget_table), + GFP_KERNEL); if (!sch_ep) return ERR_PTR(-ENOMEM); diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c index 91738af0ab14..b1045f534a4b 100644 --- a/drivers/usb/host/xhci-mtk.c +++ b/drivers/usb/host/xhci-mtk.c @@ -95,6 +95,19 @@ #define WC0_SSUSB0_CDEN BIT(6) #define WC0_IS_SPM_EN BIT(1) +/* mt8195 */ +#define PERI_WK_CTRL0_8195 0x04 +#define WC0_IS_P_95 BIT(30) /* polarity */ +#define WC0_IS_C_95(x) ((u32)(((x) & 0x7) << 27)) +#define WC0_IS_EN_P3_95 BIT(26) +#define WC0_IS_EN_P2_95 BIT(25) +#define WC0_IS_EN_P1_95 BIT(24) + +#define PERI_WK_CTRL1_8195 0x20 +#define WC1_IS_C_95(x) ((u32)(((x) & 0xf) << 28)) +#define WC1_IS_P_95 BIT(12) +#define WC1_IS_EN_P0_95 BIT(6) + /* mt2712 etc */ #define PERI_SSUSB_SPM_CTRL 0x0 #define SSC_IP_SLEEP_EN BIT(4) @@ -105,6 +118,10 @@ enum ssusb_uwk_vers { SSUSB_UWK_V2, SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */ SSUSB_UWK_V1_2, /* specific revision 1.2 */ + SSUSB_UWK_V1_3, /* mt8195 IP0 */ + SSUSB_UWK_V1_4, /* mt8195 IP1 */ + SSUSB_UWK_V1_5, /* mt8195 IP2 */ + SSUSB_UWK_V1_6, /* mt8195 IP3 */ }; /* @@ -308,6 +325,26 @@ static void usb_wakeup_ip_sleep_set(struct xhci_hcd_mtk *mtk, bool enable) msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN; val = enable ? msk : 0; break; + case SSUSB_UWK_V1_3: + reg = mtk->uwk_reg_base + PERI_WK_CTRL1_8195; + msk = WC1_IS_EN_P0_95 | WC1_IS_C_95(0xf) | WC1_IS_P_95; + val = enable ? (WC1_IS_EN_P0_95 | WC1_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_4: + reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P1_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P1_95 | WC0_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_5: + reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P2_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P2_95 | WC0_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_6: + reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; + break; case SSUSB_UWK_V2: reg = mtk->uwk_reg_base + PERI_SSUSB_SPM_CTRL; msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; @@ -364,29 +401,14 @@ static int xhci_mtk_clks_get(struct xhci_hcd_mtk *mtk) return devm_clk_bulk_get_optional(mtk->dev, BULK_CLKS_NUM, clks); } -static int xhci_mtk_ldos_enable(struct xhci_hcd_mtk *mtk) +static int xhci_mtk_vregs_get(struct xhci_hcd_mtk *mtk) { - int ret; + struct regulator_bulk_data *supplies = mtk->supplies; - ret = regulator_enable(mtk->vbus); - if (ret) { - dev_err(mtk->dev, "failed to enable vbus\n"); - return ret; - } + supplies[0].supply = "vbus"; + supplies[1].supply = "vusb33"; - ret = regulator_enable(mtk->vusb33); - if (ret) { - dev_err(mtk->dev, "failed to enable vusb33\n"); - regulator_disable(mtk->vbus); - return ret; - } - return 0; -} - -static void xhci_mtk_ldos_disable(struct xhci_hcd_mtk *mtk) -{ - regulator_disable(mtk->vbus); - regulator_disable(mtk->vusb33); + return devm_regulator_bulk_get(mtk->dev, BULK_VREGS_NUM, supplies); } static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci) @@ -476,17 +498,10 @@ static int xhci_mtk_probe(struct platform_device *pdev) return -ENOMEM; mtk->dev = dev; - mtk->vbus = devm_regulator_get(dev, "vbus"); - if (IS_ERR(mtk->vbus)) { - dev_err(dev, "fail to get vbus\n"); - return PTR_ERR(mtk->vbus); - } - mtk->vusb33 = devm_regulator_get(dev, "vusb33"); - if (IS_ERR(mtk->vusb33)) { - dev_err(dev, "fail to get vusb33\n"); - return PTR_ERR(mtk->vusb33); - } + ret = xhci_mtk_vregs_get(mtk); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); ret = xhci_mtk_clks_get(mtk); if (ret) @@ -527,7 +542,7 @@ static int xhci_mtk_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_get_sync(dev); - ret = xhci_mtk_ldos_enable(mtk); + ret = regulator_bulk_enable(BULK_VREGS_NUM, mtk->supplies); if (ret) goto disable_pm; @@ -636,7 +651,7 @@ disable_clk: clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); disable_ldos: - xhci_mtk_ldos_disable(mtk); + regulator_bulk_disable(BULK_VREGS_NUM, mtk->supplies); disable_pm: pm_runtime_put_noidle(dev); @@ -664,7 +679,7 @@ static int xhci_mtk_remove(struct platform_device *pdev) usb_put_hcd(hcd); xhci_mtk_sch_exit(mtk); clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks); - xhci_mtk_ldos_disable(mtk); + regulator_bulk_disable(BULK_VREGS_NUM, mtk->supplies); pm_runtime_disable(dev); pm_runtime_put_noidle(dev); diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index 4b1ea89f959a..ffd4b493b4ba 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -11,10 +11,12 @@ #include <linux/clk.h> #include <linux/hashtable.h> +#include <linux/regulator/consumer.h> #include "xhci.h" #define BULK_CLKS_NUM 5 +#define BULK_VREGS_NUM 2 /* support at most 64 ep, use 32 size hash table */ #define SCH_EP_HASH_BITS 5 @@ -150,9 +152,8 @@ struct xhci_hcd_mtk { int num_u3_ports; int u2p_dis_msk; int u3p_dis_msk; - struct regulator *vusb33; - struct regulator *vbus; struct clk_bulk_data clks[BULK_CLKS_NUM]; + struct regulator_bulk_data supplies[BULK_VREGS_NUM]; unsigned int has_ippc:1; unsigned int lpm_support:1; unsigned int u2_lpm_disable:1; diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index dc570ce4e831..8094da34825e 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -226,20 +226,13 @@ static int xhci_plat_probe(struct platform_device *pdev) if (!sysdev) sysdev = &pdev->dev; - /* Try to set 64-bit DMA first */ if (WARN_ON(!sysdev->dma_mask)) /* Platform did not initialize dma_mask */ - ret = dma_coerce_mask_and_coherent(sysdev, - DMA_BIT_MASK(64)); + ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); else ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); - - /* If seting 64-bit DMA mask fails, fall back to 32-bit DMA mask */ - if (ret) { - ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32)); - if (ret) - return ret; - } + if (ret) + return ret; pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); diff --git a/drivers/usb/host/xhci-rcar.c b/drivers/usb/host/xhci-rcar.c index 9888ba7d85b6..aef0258a7160 100644 --- a/drivers/usb/host/xhci-rcar.c +++ b/drivers/usb/host/xhci-rcar.c @@ -82,7 +82,7 @@ static const struct soc_device_attribute rcar_quirks_match[] = { .soc_id = "r8a7795", .revision = "ES1.*", .data = (void *)RCAR_XHCI_FIRMWARE_V2, }, - { /* sentinel */ }, + { /* sentinel */ } }; static void xhci_rcar_start_gen2(struct usb_hcd *hcd) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 2d378543bc3a..642610c78f58 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -65,7 +65,7 @@ static bool td_on_ring(struct xhci_td *td, struct xhci_ring *ring) * handshake done). There are two failure modes: "usec" have passed (major * hardware flakeout), or the register reads as all-ones (hardware removed). */ -int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, int usec) +int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us) { u32 result; int ret; @@ -73,7 +73,7 @@ int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, int usec) ret = readl_poll_timeout_atomic(ptr, result, (result & mask) == done || result == U32_MAX, - 1, usec); + 1, timeout_us); if (result == U32_MAX) /* card removed */ return -ENODEV; @@ -110,6 +110,7 @@ void xhci_quiesce(struct xhci_hcd *xhci) int xhci_halt(struct xhci_hcd *xhci) { int ret; + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Halt the HC"); xhci_quiesce(xhci); @@ -119,8 +120,10 @@ int xhci_halt(struct xhci_hcd *xhci) xhci_warn(xhci, "Host halt failed, %d\n", ret); return ret; } + xhci->xhc_state |= XHCI_STATE_HALTED; xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; + return ret; } @@ -162,7 +165,7 @@ int xhci_start(struct xhci_hcd *xhci) * Transactions will be terminated immediately, and operational registers * will be set to their defaults. */ -int xhci_reset(struct xhci_hcd *xhci) +int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us) { u32 command; u32 state; @@ -195,8 +198,7 @@ int xhci_reset(struct xhci_hcd *xhci) if (xhci->quirks & XHCI_INTEL_HOST) udelay(1000); - ret = xhci_handshake(&xhci->op_regs->command, - CMD_RESET, 0, 10 * 1000 * 1000); + ret = xhci_handshake(&xhci->op_regs->command, CMD_RESET, 0, timeout_us); if (ret) return ret; @@ -209,8 +211,7 @@ int xhci_reset(struct xhci_hcd *xhci) * xHCI cannot write to any doorbells or operational registers other * than status until the "Controller Not Ready" flag is cleared. */ - ret = xhci_handshake(&xhci->op_regs->status, - STS_CNR, 0, 10 * 1000 * 1000); + ret = xhci_handshake(&xhci->op_regs->status, STS_CNR, 0, timeout_us); xhci->usb2_rhub.bus_state.port_c_suspend = 0; xhci->usb2_rhub.bus_state.suspended_ports = 0; @@ -324,7 +325,7 @@ static int xhci_setup_msi(struct xhci_hcd *xhci) */ static int xhci_setup_msix(struct xhci_hcd *xhci) { - int i, ret = 0; + int i, ret; struct usb_hcd *hcd = xhci_to_hcd(xhci); struct pci_dev *pdev = to_pci_dev(hcd->self.controller); @@ -578,7 +579,7 @@ static int xhci_all_ports_seen_u0(struct xhci_hcd *xhci) static int xhci_init(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); - int retval = 0; + int retval; xhci_dbg_trace(xhci, trace_xhci_dbg_init, "xhci_init"); spin_lock_init(&xhci->lock); @@ -695,7 +696,7 @@ int xhci_run(struct usb_hcd *hcd) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished xhci_run for USB2 roothub"); - xhci_dbc_init(xhci); + xhci_create_dbc_dev(xhci); xhci_debugfs_init(xhci); @@ -725,13 +726,13 @@ static void xhci_stop(struct usb_hcd *hcd) return; } - xhci_dbc_exit(xhci); + xhci_remove_dbc_dev(xhci); spin_lock_irq(&xhci->lock); xhci->xhc_state |= XHCI_STATE_HALTED; xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; xhci_halt(xhci); - xhci_reset(xhci); + xhci_reset(xhci, XHCI_RESET_SHORT_USEC); spin_unlock_irq(&xhci->lock); xhci_cleanup_msix(xhci); @@ -784,7 +785,7 @@ void xhci_shutdown(struct usb_hcd *hcd) xhci_halt(xhci); /* Workaround for spurious wakeups at shutdown with HSW */ if (xhci->quirks & XHCI_SPURIOUS_WAKEUP) - xhci_reset(xhci); + xhci_reset(xhci, XHCI_RESET_SHORT_USEC); spin_unlock_irq(&xhci->lock); xhci_cleanup_msix(xhci); @@ -1170,7 +1171,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) xhci_dbg(xhci, "Stop HCD\n"); xhci_halt(xhci); xhci_zero_64b_regs(xhci); - retval = xhci_reset(xhci); + retval = xhci_reset(xhci, XHCI_RESET_LONG_USEC); spin_unlock_irq(&xhci->lock); if (retval) return retval; @@ -3160,8 +3161,6 @@ rescan: ep_index = xhci_get_endpoint_index(&host_ep->desc); ep = &vdev->eps[ep_index]; - if (!ep) - goto done; /* wait for hub_tt_work to finish clearing hub TT */ if (ep->ep_state & EP_CLEARING_TT) { @@ -3219,8 +3218,6 @@ static void xhci_endpoint_reset(struct usb_hcd *hcd, return; ep_index = xhci_get_endpoint_index(&host_ep->desc); ep = &vdev->eps[ep_index]; - if (!ep) - return; /* Bail out if toggle is already being cleared by a endpoint reset */ spin_lock_irqsave(&xhci->lock, flags); @@ -3978,7 +3975,7 @@ int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id) struct xhci_command *command; unsigned long flags; u32 state; - int ret = 0; + int ret; command = xhci_alloc_command(xhci, true, GFP_KERNEL); if (!command) @@ -4014,7 +4011,7 @@ int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id) xhci_free_command(xhci, command); - return ret; + return 0; } /* @@ -4354,6 +4351,10 @@ static int __maybe_unused xhci_change_max_exit_latency(struct xhci_hcd *xhci, unsigned long flags; int ret; + command = xhci_alloc_command_with_ctx(xhci, true, GFP_KERNEL); + if (!command) + return -ENOMEM; + spin_lock_irqsave(&xhci->lock, flags); virt_dev = xhci->devs[udev->slot_id]; @@ -4370,10 +4371,10 @@ static int __maybe_unused xhci_change_max_exit_latency(struct xhci_hcd *xhci, } /* Attempt to issue an Evaluate Context command to change the MEL. */ - command = xhci->lpm_command; ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); if (!ctrl_ctx) { spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, command); xhci_warn(xhci, "%s: Could not get input context, bad type.\n", __func__); return -ENOMEM; @@ -4400,6 +4401,9 @@ static int __maybe_unused xhci_change_max_exit_latency(struct xhci_hcd *xhci, virt_dev->current_mel = max_exit_latency; spin_unlock_irqrestore(&xhci->lock, flags); } + + xhci_free_command(xhci, command); + return ret; } @@ -4520,18 +4524,8 @@ static int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, exit_latency = xhci_besl_encoding[hird]; spin_unlock_irqrestore(&xhci->lock, flags); - /* USB 3.0 code dedicate one xhci->lpm_command->in_ctx - * input context for link powermanagement evaluate - * context commands. It is protected by hcd->bandwidth - * mutex and is shared by all devices. We need to set - * the max ext latency in USB 2 BESL LPM as well, so - * use the same mutex and xhci_change_max_exit_latency() - */ - mutex_lock(hcd->bandwidth_mutex); ret = xhci_change_max_exit_latency(xhci, udev, exit_latency); - mutex_unlock(hcd->bandwidth_mutex); - if (ret < 0) return ret; spin_lock_irqsave(&xhci->lock, flags); @@ -4559,9 +4553,7 @@ static int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, readl(pm_addr); if (udev->usb2_hw_lpm_besl_capable) { spin_unlock_irqrestore(&xhci->lock, flags); - mutex_lock(hcd->bandwidth_mutex); xhci_change_max_exit_latency(xhci, udev, 0); - mutex_unlock(hcd->bandwidth_mutex); readl_poll_timeout(ports[port_num]->addr, pm_val, (pm_val & PORT_PLS_MASK) == XDEV_U0, 100, 10000); @@ -5290,8 +5282,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) xhci->hcs_params1 = readl(&xhci->cap_regs->hcs_params1); xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); - xhci->hcc_params = readl(&xhci->cap_regs->hc_capbase); - xhci->hci_version = HC_VERSION(xhci->hcc_params); + xhci->hci_version = HC_VERSION(readl(&xhci->cap_regs->hc_capbase)); xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); if (xhci->hci_version > 0x100) xhci->hcc_params2 = readl(&xhci->cap_regs->hcc_params2); @@ -5316,7 +5307,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) xhci_dbg(xhci, "Resetting HCD\n"); /* Reset the internal HC memory state and registers. */ - retval = xhci_reset(xhci); + retval = xhci_reset(xhci, XHCI_RESET_LONG_USEC); if (retval) return retval; xhci_dbg(xhci, "Reset complete\n"); @@ -5505,6 +5496,7 @@ static int __init xhci_hcd_init(void) return -ENODEV; xhci_debugfs_create_root(); + xhci_dbc_init(); return 0; } @@ -5516,6 +5508,7 @@ static int __init xhci_hcd_init(void) static void __exit xhci_hcd_fini(void) { xhci_debugfs_remove_root(); + xhci_dbc_exit(); } module_init(xhci_hcd_init); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 5a75fe563123..473a33ce299e 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -229,6 +229,9 @@ struct xhci_op_regs { #define CMD_ETE (1 << 14) /* bits 15:31 are reserved (and should be preserved on writes). */ +#define XHCI_RESET_LONG_USEC (10 * 1000 * 1000) +#define XHCI_RESET_SHORT_USEC (250 * 1000) + /* IMAN - Interrupt Management Register */ #define IMAN_IE (1 << 1) #define IMAN_IP (1 << 0) @@ -1812,8 +1815,6 @@ struct xhci_hcd { /* slot enabling and address device helpers */ /* these are not thread safe so use mutex */ struct mutex mutex; - /* For USB 3.0 LPM enable/disable. */ - struct xhci_command *lpm_command; /* Internal mirror of the HW's dcbaa */ struct xhci_virt_device *devs[MAX_HC_SLOTS]; /* For keeping track of bandwidth domains per roothub. */ @@ -2083,11 +2084,11 @@ void xhci_free_container_ctx(struct xhci_hcd *xhci, /* xHCI host controller glue */ typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); -int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, int usec); +int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us); void xhci_quiesce(struct xhci_hcd *xhci); int xhci_halt(struct xhci_hcd *xhci); int xhci_start(struct xhci_hcd *xhci); -int xhci_reset(struct xhci_hcd *xhci); +int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us); int xhci_run(struct usb_hcd *hcd); int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks); void xhci_shutdown(struct usb_hcd *hcd); @@ -2467,6 +2468,8 @@ static inline const char *xhci_decode_ctrl_ctx(char *str, unsigned int bit; int ret = 0; + str[0] = '\0'; + if (drop) { ret = sprintf(str, "Drop:"); for_each_set_bit(bit, &drop, 32) @@ -2624,8 +2627,11 @@ static inline const char *xhci_decode_usbsts(char *str, u32 usbsts) { int ret = 0; + ret = sprintf(str, " 0x%08x", usbsts); + if (usbsts == ~(u32)0) - return " 0xffffffff"; + return str; + if (usbsts & STS_HALT) ret += sprintf(str + ret, " HCHalted"); if (usbsts & STS_FATAL) diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 8f1144359012..4c5ddbd75b7e 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -137,6 +137,17 @@ config USB_APPLEDISPLAY Say Y here if you want to control the backlight of Apple Cinema Displays over USB. This driver provides a sysfs interface. +config USB_QCOM_EUD + tristate "QCOM Embedded USB Debugger(EUD) Driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_ROLE_SWITCH + help + This module enables support for Qualcomm Technologies, Inc. + Embedded USB Debugger (EUD). The EUD is a control peripheral + which reports VBUS attach/detach events and has USB-based + debug and trace capabilities. On selecting m, the module name + that is built is qcom_eud.ko + config APPLE_MFI_FASTCHARGE tristate "Fast charge control for iOS devices" select POWER_SUPPLY diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 5f4e598573ab..35bdb4b6c3b6 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o obj-$(CONFIG_USB_LCD) += usblcd.o obj-$(CONFIG_USB_LD) += ldusb.o obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o +obj-$(CONFIG_USB_QCOM_EUD) += qcom_eud.o obj-$(CONFIG_USB_TEST) += usbtest.o obj-$(CONFIG_USB_EHSET_TEST_FIXTURE) += ehset.o obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c new file mode 100644 index 000000000000..f929bffdc5d1 --- /dev/null +++ b/drivers/usb/misc/qcom_eud.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + */ + +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/usb/role.h> + +#define EUD_REG_INT1_EN_MASK 0x0024 +#define EUD_REG_INT_STATUS_1 0x0044 +#define EUD_REG_CTL_OUT_1 0x0074 +#define EUD_REG_VBUS_INT_CLR 0x0080 +#define EUD_REG_CSR_EUD_EN 0x1014 +#define EUD_REG_SW_ATTACH_DET 0x1018 +#define EUD_REG_EUD_EN2 0x0000 + +#define EUD_ENABLE BIT(0) +#define EUD_INT_PET_EUD BIT(0) +#define EUD_INT_VBUS BIT(2) +#define EUD_INT_SAFE_MODE BIT(4) +#define EUD_INT_ALL (EUD_INT_VBUS | EUD_INT_SAFE_MODE) + +struct eud_chip { + struct device *dev; + struct usb_role_switch *role_sw; + void __iomem *base; + void __iomem *mode_mgr; + unsigned int int_status; + int irq; + bool enabled; + bool usb_attached; +}; + +static int enable_eud(struct eud_chip *priv) +{ + writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN); + writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE, + priv->base + EUD_REG_INT1_EN_MASK); + writel(1, priv->mode_mgr + EUD_REG_EUD_EN2); + + return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE); +} + +static void disable_eud(struct eud_chip *priv) +{ + writel(0, priv->base + EUD_REG_CSR_EUD_EN); + writel(0, priv->mode_mgr + EUD_REG_EUD_EN2); +} + +static ssize_t enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", chip->enabled); +} + +static ssize_t enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + bool enable; + int ret; + + if (kstrtobool(buf, &enable)) + return -EINVAL; + + if (enable) { + ret = enable_eud(chip); + if (!ret) + chip->enabled = enable; + else + disable_eud(chip); + } else { + disable_eud(chip); + } + + return count; +} + +static DEVICE_ATTR_RW(enable); + +static struct attribute *eud_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; +ATTRIBUTE_GROUPS(eud); + +static void usb_attach_detach(struct eud_chip *chip) +{ + u32 reg; + + /* read ctl_out_1[4] to find USB attach or detach event */ + reg = readl(chip->base + EUD_REG_CTL_OUT_1); + chip->usb_attached = reg & EUD_INT_SAFE_MODE; +} + +static void pet_eud(struct eud_chip *chip) +{ + u32 reg; + int ret; + + /* When the EUD_INT_PET_EUD in SW_ATTACH_DET is set, the cable has been + * disconnected and we need to detach the pet to check if EUD is in safe + * mode before attaching again. + */ + reg = readl(chip->base + EUD_REG_SW_ATTACH_DET); + if (reg & EUD_INT_PET_EUD) { + /* Detach & Attach pet for EUD */ + writel(0, chip->base + EUD_REG_SW_ATTACH_DET); + /* Delay to make sure detach pet is done before attach pet */ + ret = readl_poll_timeout(chip->base + EUD_REG_SW_ATTACH_DET, + reg, (reg == 0), 1, 100); + if (ret) { + dev_err(chip->dev, "Detach pet failed\n"); + return; + } + } + /* Attach pet for EUD */ + writel(EUD_INT_PET_EUD, chip->base + EUD_REG_SW_ATTACH_DET); +} + +static irqreturn_t handle_eud_irq(int irq, void *data) +{ + struct eud_chip *chip = data; + u32 reg; + + reg = readl(chip->base + EUD_REG_INT_STATUS_1); + switch (reg & EUD_INT_ALL) { + case EUD_INT_VBUS: + usb_attach_detach(chip); + return IRQ_WAKE_THREAD; + case EUD_INT_SAFE_MODE: + pet_eud(chip); + return IRQ_HANDLED; + default: + return IRQ_NONE; + } +} + +static irqreturn_t handle_eud_irq_thread(int irq, void *data) +{ + struct eud_chip *chip = data; + int ret; + + if (chip->usb_attached) + ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE); + else + ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST); + if (ret) + dev_err(chip->dev, "failed to set role switch\n"); + + /* set and clear vbus_int_clr[0] to clear interrupt */ + writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR); + writel(0, chip->base + EUD_REG_VBUS_INT_CLR); + + return IRQ_HANDLED; +} + +static void eud_role_switch_release(void *data) +{ + struct eud_chip *chip = data; + + usb_role_switch_put(chip->role_sw); +} + +static int eud_probe(struct platform_device *pdev) +{ + struct eud_chip *chip; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &pdev->dev; + + ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to add role switch release action\n"); + + chip->role_sw = usb_role_switch_get(&pdev->dev); + if (IS_ERR(chip->role_sw)) + return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw), + "failed to get role switch\n"); + + chip->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(chip->base)) + return PTR_ERR(chip->base); + + chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(chip->mode_mgr)) + return PTR_ERR(chip->mode_mgr); + + chip->irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq, + handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip); + if (ret) + return dev_err_probe(chip->dev, ret, "failed to allocate irq\n"); + + enable_irq_wake(chip->irq); + + platform_set_drvdata(pdev, chip); + + return 0; +} + +static int eud_remove(struct platform_device *pdev) +{ + struct eud_chip *chip = platform_get_drvdata(pdev); + + if (chip->enabled) + disable_eud(chip); + + device_init_wakeup(&pdev->dev, false); + disable_irq_wake(chip->irq); + + return 0; +} + +static const struct of_device_id eud_dt_match[] = { + { .compatible = "qcom,sc7280-eud" }, + { } +}; +MODULE_DEVICE_TABLE(of, eud_dt_match); + +static struct platform_driver eud_driver = { + .probe = eud_probe, + .remove = eud_remove, + .driver = { + .name = "qcom_eud", + .dev_groups = eud_groups, + .of_match_table = eud_dt_match, + }, +}; +module_platform_driver(eud_driver); + +MODULE_DESCRIPTION("QTI EUD driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index ab480f38523a..8f921213b17d 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -52,6 +52,18 @@ source "drivers/usb/typec/ucsi/Kconfig" source "drivers/usb/typec/tipd/Kconfig" +config TYPEC_RT1719 + tristate "Richtek RT1719 Sink Only Type-C controller driver" + depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH + depends on I2C + select REGMAP_I2C + help + Say Y or M here if your system has Richtek RT1719 sink only + Type-C port controller driver. + + If you choose to build this driver as a dynamically linked module, the + module will be called rt1719.ko + config TYPEC_HD3SS3220 tristate "TI HD3SS3220 Type-C DRP Port controller driver" depends on I2C @@ -88,6 +100,16 @@ config TYPEC_QCOM_PMIC It will also enable the VBUS output to connected devices when a DFP connection is made. +config TYPEC_WUSB3801 + tristate "Willsemi WUSB3801 Type-C port controller driver" + depends on I2C + select REGMAP_I2C + help + Say Y or M here if your system has a WUSB3801 Type-C port controller. + + If you choose to build this driver as a dynamically linked module, the + module will be called wusb3801.ko. + source "drivers/usb/typec/mux/Kconfig" source "drivers/usb/typec/altmodes/Kconfig" diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 57870a2bd787..43626acc0aaf 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -9,4 +9,6 @@ obj-$(CONFIG_TYPEC_TPS6598X) += tipd/ obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o +obj-$(CONFIG_TYPEC_RT1719) += rt1719.o +obj-$(CONFIG_TYPEC_WUSB3801) += wusb3801.o obj-$(CONFIG_TYPEC) += mux/ diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 45a6f0c807cb..ee0e520707dd 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -1894,6 +1894,49 @@ void *typec_get_drvdata(struct typec_port *port) } EXPORT_SYMBOL_GPL(typec_get_drvdata); +int typec_get_fw_cap(struct typec_capability *cap, + struct fwnode_handle *fwnode) +{ + const char *cap_str; + int ret; + + cap->fwnode = fwnode; + + ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); + if (ret < 0) + return ret; + + ret = typec_find_port_power_role(cap_str); + if (ret < 0) + return ret; + cap->type = ret; + + /* USB data support is optional */ + ret = fwnode_property_read_string(fwnode, "data-role", &cap_str); + if (ret == 0) { + ret = typec_find_port_data_role(cap_str); + if (ret < 0) + return ret; + cap->data = ret; + } + + /* Get the preferred power role for a DRP */ + if (cap->type == TYPEC_PORT_DRP) { + cap->prefer_role = TYPEC_NO_PREFERRED_ROLE; + + ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str); + if (ret == 0) { + ret = typec_find_power_role(cap_str); + if (ret < 0) + return ret; + cap->prefer_role = ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(typec_get_fw_cap); + /** * typec_port_register_altmode - Register USB Type-C Port Alternate Mode * @port: USB Type-C Port that supports the alternate mode diff --git a/drivers/usb/typec/rt1719.c b/drivers/usb/typec/rt1719.c new file mode 100644 index 000000000000..f1b698edd7eb --- /dev/null +++ b/drivers/usb/typec/rt1719.c @@ -0,0 +1,961 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bitfield.h> +#include <linux/completion.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/usb/pd.h> +#include <linux/usb/role.h> +#include <linux/usb/typec.h> + +#define RT1719_REG_TXCTRL1 0x03 +#define RT1719_REG_TXCTRL2 0x04 +#define RT1719_REG_POLICYINFO 0x0E +#define RT1719_REG_SRCPDO1 0x11 +#define RT1719_REG_MASKS 0x2D +#define RT1719_REG_EVENTS 0x33 +#define RT1719_REG_STATS 0x37 +#define RT1719_REG_PSELINFO 0x3C +#define RT1719_REG_USBSETINFO 0x3E +#define RT1719_REG_VENID 0x82 + +#define RT1719_UNIQUE_PID 0x1719 +#define RT1719_REQDRSWAP_MASK BIT(7) +#define RT1719_EVALMODE_MASK BIT(4) +#define RT1719_REQSRCPDO_MASK GENMASK(2, 0) +#define RT1719_TXSPDOREQ_MASK BIT(7) +#define RT1719_INT_DRSW_ACCEPT BIT(23) +#define RT1719_INT_RX_SRCCAP BIT(21) +#define RT1719_INT_VBUS_DCT BIT(6) +#define RT1719_INT_VBUS_PRESENT BIT(5) +#define RT1719_INT_PE_SNK_RDY BIT(2) +#define RT1719_CC1_STAT GENMASK(9, 8) +#define RT1719_CC2_STAT GENMASK(11, 10) +#define RT1719_POLARITY_MASK BIT(23) +#define RT1719_DATAROLE_MASK BIT(22) +#define RT1719_PDSPECREV_MASK GENMASK(21, 20) +#define RT1719_SPDOSEL_MASK GENMASK(18, 16) +#define RT1719_SPDONUM_MASK GENMASK(15, 13) +#define RT1719_ATTACH_VBUS BIT(12) +#define RT1719_ATTACH_DBG BIT(10) +#define RT1719_ATTACH_SNK BIT(9) +#define RT1719_ATTACHDEV_MASK (RT1719_ATTACH_VBUS | RT1719_ATTACH_DBG | \ + RT1719_ATTACH_SNK) +#define RT1719_PE_EXP_CONTRACT BIT(2) +#define RT1719_PSEL_SUPPORT BIT(15) +#define RT1719_TBLSEL_MASK BIT(6) +#define RT1719_LATPSEL_MASK GENMASK(5, 0) +#define RT1719_USBINFO_MASK GENMASK(1, 0) +#define RT1719_USB_DFPUFP 3 +#define RT1719_MAX_SRCPDO 7 + +enum { + SNK_PWR_OPEN = 0, + SNK_PWR_DEF, + SNK_PWR_1P5A, + SNK_PWR_3A +}; + +enum { + USBPD_SPECREV_1_0 = 0, + USBPD_SPECREV_2_0, + USBPD_SPECREV_3_0 +}; + +enum rt1719_snkcap { + RT1719_SNKCAP_5V = 0, + RT1719_SNKCAP_9V, + RT1719_SNKCAP_12V, + RT1719_SNKCAP_15V, + RT1719_SNKCAP_20V, + RT1719_MAX_SNKCAP +}; + +struct rt1719_psel_cap { + u8 lomask; + u8 himask; + u32 milliwatt; + u32 milliamp; +}; + +struct rt1719_data { + struct device *dev; + struct regmap *regmap; + struct typec_port *port; + struct usb_role_switch *role_sw; + struct power_supply *psy; + struct typec_partner *partner; + struct power_supply_desc psy_desc; + struct usb_pd_identity partner_ident; + struct typec_partner_desc partner_desc; + struct completion req_completion; + enum power_supply_usb_type usb_type; + bool attached; + bool pd_capable; + bool drswap_support; + u32 voltage; + u32 req_voltage; + u32 max_current; + u32 op_current; + u32 spdos[RT1719_MAX_SRCPDO]; + u16 snkcaps[RT1719_MAX_SNKCAP]; + int spdo_num; + int spdo_sel; + u32 conn_info; + u16 conn_stat; +}; + +static const enum power_supply_usb_type rt1719_psy_usb_types[] = { + POWER_SUPPLY_USB_TYPE_C, + POWER_SUPPLY_USB_TYPE_PD, + POWER_SUPPLY_USB_TYPE_PD_PPS +}; + +static const enum power_supply_property rt1719_psy_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW +}; + +static int rt1719_read16(struct rt1719_data *data, unsigned int reg, u16 *val) +{ + __le16 regval; + int ret; + + ret = regmap_raw_read(data->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = le16_to_cpu(regval); + return 0; +} + +static int rt1719_read32(struct rt1719_data *data, unsigned int reg, u32 *val) +{ + __le32 regval; + int ret; + + ret = regmap_raw_read(data->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = le32_to_cpu(regval); + return 0; +} + +static int rt1719_write32(struct rt1719_data *data, unsigned int reg, u32 val) +{ + __le32 regval = cpu_to_le32(val); + + return regmap_raw_write(data->regmap, reg, ®val, sizeof(regval)); +} + +static enum typec_pwr_opmode rt1719_get_pwr_opmode(u32 conn, u16 stat) +{ + u16 cc1, cc2, cc_stat; + + cc1 = FIELD_GET(RT1719_CC1_STAT, stat); + cc2 = FIELD_GET(RT1719_CC2_STAT, stat); + + if (conn & RT1719_ATTACH_SNK) { + if (conn & RT1719_POLARITY_MASK) + cc_stat = cc2; + else + cc_stat = cc1; + + switch (cc_stat) { + case SNK_PWR_3A: + return TYPEC_PWR_MODE_3_0A; + case SNK_PWR_1P5A: + return TYPEC_PWR_MODE_1_5A; + } + } else if (conn & RT1719_ATTACH_DBG) { + if ((cc1 == SNK_PWR_1P5A && cc2 == SNK_PWR_DEF) || + (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_1P5A)) + return TYPEC_PWR_MODE_1_5A; + else if ((cc1 == SNK_PWR_3A && cc2 == SNK_PWR_DEF) || + (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_3A)) + return TYPEC_PWR_MODE_3_0A; + } + + return TYPEC_PWR_MODE_USB; +} + +static enum typec_data_role rt1719_get_data_role(u32 conn) +{ + if (conn & RT1719_DATAROLE_MASK) + return TYPEC_HOST; + return TYPEC_DEVICE; +} + +static void rt1719_set_data_role(struct rt1719_data *data, + enum typec_data_role data_role, + bool attached) +{ + enum usb_role usb_role = USB_ROLE_NONE; + + if (attached) { + if (data_role == TYPEC_HOST) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_DEVICE; + } + + usb_role_switch_set_role(data->role_sw, usb_role); + typec_set_data_role(data->port, data_role); +} + +static void rt1719_update_data_role(struct rt1719_data *data) +{ + if (!data->attached) + return; + + rt1719_set_data_role(data, rt1719_get_data_role(data->conn_info), true); +} + +static void rt1719_register_partner(struct rt1719_data *data) +{ + u16 spec_rev = 0; + + if (data->pd_capable) { + u32 rev; + + rev = FIELD_GET(RT1719_PDSPECREV_MASK, data->conn_info); + switch (rev) { + case USBPD_SPECREV_3_0: + spec_rev = 0x0300; + break; + case USBPD_SPECREV_2_0: + spec_rev = 0x0200; + break; + default: + spec_rev = 0x0100; + break; + } + } + + /* Just to prevent multiple times attach */ + if (data->partner) + typec_unregister_partner(data->partner); + + memset(&data->partner_ident, 0, sizeof(data->partner_ident)); + data->partner_desc.usb_pd = data->pd_capable; + data->partner_desc.pd_revision = spec_rev; + + if (data->conn_info & RT1719_ATTACH_DBG) + data->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG; + else + data->partner_desc.accessory = TYPEC_ACCESSORY_NONE; + + data->partner = typec_register_partner(data->port, &data->partner_desc); +} + +static void rt1719_attach(struct rt1719_data *data) +{ + enum typec_pwr_opmode pwr_opmode; + enum typec_data_role data_role; + u32 volt = 5000, curr = 500; + + if (!(data->conn_info & RT1719_ATTACHDEV_MASK)) + return; + + pwr_opmode = rt1719_get_pwr_opmode(data->conn_info, data->conn_stat); + data_role = rt1719_get_data_role(data->conn_info); + + typec_set_pwr_opmode(data->port, pwr_opmode); + rt1719_set_data_role(data, data_role, true); + + if (data->conn_info & RT1719_ATTACH_SNK) + rt1719_register_partner(data); + + if (pwr_opmode == TYPEC_PWR_MODE_3_0A) + curr = 3000; + else if (pwr_opmode == TYPEC_PWR_MODE_1_5A) + curr = 1500; + + data->voltage = volt * 1000; + data->max_current = data->op_current = curr * 1000; + data->attached = true; + + power_supply_changed(data->psy); +} + +static void rt1719_detach(struct rt1719_data *data) +{ + if (!data->attached || (data->conn_info & RT1719_ATTACHDEV_MASK)) + return; + + typec_unregister_partner(data->partner); + data->partner = NULL; + + typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_USB); + rt1719_set_data_role(data, TYPEC_DEVICE, false); + + memset32(data->spdos, 0, RT1719_MAX_SRCPDO); + data->spdo_num = 0; + data->voltage = data->max_current = data->op_current = 0; + data->attached = data->pd_capable = false; + + data->usb_type = POWER_SUPPLY_USB_TYPE_C; + + power_supply_changed(data->psy); +} + +static void rt1719_update_operating_status(struct rt1719_data *data) +{ + enum power_supply_usb_type usb_type = POWER_SUPPLY_USB_TYPE_PD; + u32 voltage, max_current, op_current; + int i, snk_sel; + + for (i = 0; i < data->spdo_num; i++) { + u32 pdo = data->spdos[i]; + enum pd_pdo_type type = pdo_type(pdo); + + if (type == PDO_TYPE_APDO) { + usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS; + break; + } + } + + data->spdo_sel = FIELD_GET(RT1719_SPDOSEL_MASK, data->conn_info); + if (data->spdo_sel <= 0) + return; + + data->usb_type = usb_type; + + voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]); + max_current = pdo_max_current(data->spdos[data->spdo_sel - 1]); + + switch (voltage) { + case 5000: + snk_sel = RT1719_SNKCAP_5V; + break; + case 9000: + snk_sel = RT1719_SNKCAP_9V; + break; + case 12000: + snk_sel = RT1719_SNKCAP_12V; + break; + case 15000: + snk_sel = RT1719_SNKCAP_15V; + break; + case 20000: + snk_sel = RT1719_SNKCAP_20V; + break; + default: + return; + } + + op_current = min(max_current, pdo_max_current(data->snkcaps[snk_sel])); + + /* covert mV/mA to uV/uA */ + data->voltage = voltage * 1000; + data->max_current = max_current * 1000; + data->op_current = op_current * 1000; + + power_supply_changed(data->psy); +} + +static void rt1719_update_pwr_opmode(struct rt1719_data *data) +{ + if (!data->attached) + return; + + if (!data->pd_capable) { + data->pd_capable = true; + + typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_PD); + rt1719_register_partner(data); + } + + rt1719_update_operating_status(data); +} + +static void rt1719_update_source_pdos(struct rt1719_data *data) +{ + int spdo_num = FIELD_GET(RT1719_SPDONUM_MASK, data->conn_info); + __le32 src_pdos[RT1719_MAX_SRCPDO] = { }; + int i, ret; + + if (!data->attached) + return; + + ret = regmap_raw_read(data->regmap, RT1719_REG_SRCPDO1, src_pdos, + sizeof(__le32) * spdo_num); + if (ret) + return; + + data->spdo_num = spdo_num; + for (i = 0; i < spdo_num; i++) + data->spdos[i] = le32_to_cpu(src_pdos[i]); +} + +static int rt1719_dr_set(struct typec_port *port, enum typec_data_role role) +{ + struct rt1719_data *data = typec_get_drvdata(port); + enum typec_data_role cur_role; + int ret; + + if (!data->attached || !data->pd_capable || !data->drswap_support) + return -EOPNOTSUPP; + + if (data->spdo_num > 0 && !(data->spdos[0] & PDO_FIXED_DATA_SWAP)) + return -EINVAL; + + cur_role = rt1719_get_data_role(data->conn_info); + if (cur_role == role) + return 0; + + ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1, + RT1719_REQDRSWAP_MASK, RT1719_REQDRSWAP_MASK); + if (ret) + return ret; + + reinit_completion(&data->req_completion); + ret = wait_for_completion_timeout(&data->req_completion, + msecs_to_jiffies(400)); + if (ret == 0) + return -ETIMEDOUT; + + cur_role = rt1719_get_data_role(data->conn_info); + if (cur_role != role) + return -EAGAIN; + + rt1719_set_data_role(data, role, true); + return 0; +} + +static const struct typec_operations rt1719_port_ops = { + .dr_set = rt1719_dr_set, +}; + +static int rt1719_usbpd_request_voltage(struct rt1719_data *data) +{ + u32 src_voltage; + int snk_sel, src_sel = -1; + int i, ret; + + if (!data->attached || !data->pd_capable || data->spdo_sel <= 0) + return -EINVAL; + + src_voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]); + if (src_voltage == data->req_voltage) + return 0; + + switch (data->req_voltage) { + case 5000: + snk_sel = RT1719_SNKCAP_5V; + break; + case 9000: + snk_sel = RT1719_SNKCAP_9V; + break; + case 12000: + snk_sel = RT1719_SNKCAP_12V; + break; + case 15000: + snk_sel = RT1719_SNKCAP_15V; + break; + case 20000: + snk_sel = RT1719_SNKCAP_20V; + break; + default: + return -EINVAL; + } + + if (!(data->snkcaps[snk_sel] & RT1719_PSEL_SUPPORT)) + return -EINVAL; + + for (i = 0; i < data->spdo_num; i++) { + enum pd_pdo_type type = pdo_type(data->spdos[i]); + + if (type != PDO_TYPE_FIXED) + continue; + + src_voltage = pdo_fixed_voltage(data->spdos[i]); + if (src_voltage == data->req_voltage) { + src_sel = i; + break; + } + } + + if (src_sel == -1) + return -EOPNOTSUPP; + + ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1, + RT1719_EVALMODE_MASK | RT1719_REQSRCPDO_MASK, + RT1719_EVALMODE_MASK | (src_sel + 1)); + ret |= regmap_update_bits(data->regmap, RT1719_REG_TXCTRL2, + RT1719_TXSPDOREQ_MASK, RT1719_TXSPDOREQ_MASK); + if (ret) + return ret; + + reinit_completion(&data->req_completion); + ret = wait_for_completion_timeout(&data->req_completion, + msecs_to_jiffies(400)); + if (!ret) + return -ETIMEDOUT; + + return 0; +} + +static int rt1719_psy_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct rt1719_data *data = power_supply_get_drvdata(psy); + + if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) { + data->req_voltage = val->intval / 1000; + return rt1719_usbpd_request_voltage(data); + } + + return -EINVAL; +} + +static int rt1719_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt1719_data *data = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = data->attached ? 1 : 0; + break; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = data->usb_type; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = data->voltage; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = data->max_current; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = data->op_current; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int rt1719_psy_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) + return 1; + return 0; +} + +static int devm_rt1719_psy_register(struct rt1719_data *data) +{ + struct power_supply_config psy_cfg = { }; + char *psy_name; + + psy_cfg.fwnode = dev_fwnode(data->dev); + psy_cfg.drv_data = data; + + psy_name = devm_kasprintf(data->dev, GFP_KERNEL, "rt1719-source-psy-%s", + dev_name(data->dev)); + if (!psy_name) + return -ENOMEM; + + data->psy_desc.name = psy_name; + data->psy_desc.type = POWER_SUPPLY_TYPE_USB; + data->psy_desc.usb_types = rt1719_psy_usb_types; + data->psy_desc.num_usb_types = ARRAY_SIZE(rt1719_psy_usb_types); + data->psy_desc.properties = rt1719_psy_properties; + data->psy_desc.num_properties = ARRAY_SIZE(rt1719_psy_properties); + data->psy_desc.get_property = rt1719_psy_get_property; + data->psy_desc.set_property = rt1719_psy_set_property; + data->psy_desc.property_is_writeable = rt1719_psy_property_is_writeable; + + data->usb_type = POWER_SUPPLY_USB_TYPE_C; + + data->psy = devm_power_supply_register(data->dev, &data->psy_desc, + &psy_cfg); + + return PTR_ERR_OR_ZERO(data->psy); +} + +static irqreturn_t rt1719_irq_handler(int irq, void *priv) +{ + struct rt1719_data *data = priv; + u32 events, conn_info; + u16 conn_stat; + int ret; + + ret = rt1719_read32(data, RT1719_REG_EVENTS, &events); + ret |= rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info); + ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat); + if (ret) + return IRQ_NONE; + + data->conn_info = conn_info; + data->conn_stat = conn_stat; + + events &= (RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP | + RT1719_INT_VBUS_PRESENT | RT1719_INT_VBUS_DCT | + RT1719_INT_PE_SNK_RDY); + + if (events & RT1719_INT_DRSW_ACCEPT) + rt1719_update_data_role(data); + + if (events & RT1719_INT_VBUS_PRESENT) + rt1719_attach(data); + + if (events & RT1719_INT_VBUS_DCT) + rt1719_detach(data); + + if (events & RT1719_INT_RX_SRCCAP) + rt1719_update_source_pdos(data); + + if (events & RT1719_INT_PE_SNK_RDY) { + complete(&data->req_completion); + rt1719_update_pwr_opmode(data); + } + + /* Write 1 to clear already handled events */ + rt1719_write32(data, RT1719_REG_EVENTS, events); + + return IRQ_HANDLED; +} + +static int rt1719_irq_init(struct rt1719_data *data) +{ + struct i2c_client *i2c = to_i2c_client(data->dev); + u32 irq_enable; + int ret; + + irq_enable = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP | + RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT | + RT1719_INT_PE_SNK_RDY; + + ret = rt1719_write32(data, RT1719_REG_MASKS, irq_enable); + if (ret) { + dev_err(&i2c->dev, "Failed to config irq enable\n"); + return ret; + } + + return devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL, + rt1719_irq_handler, IRQF_ONESHOT, + dev_name(&i2c->dev), data); +} + +static int rt1719_init_attach_state(struct rt1719_data *data) +{ + u32 conn_info, irq_clear; + u16 conn_stat; + int ret; + + irq_clear = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP | + RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT | + RT1719_INT_PE_SNK_RDY; + + ret = rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info); + ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat); + ret |= rt1719_write32(data, RT1719_REG_EVENTS, irq_clear); + if (ret) + return ret; + + data->conn_info = conn_info; + data->conn_stat = conn_stat; + + if (conn_info & RT1719_ATTACHDEV_MASK) + rt1719_attach(data); + + if (conn_info & RT1719_PE_EXP_CONTRACT) { + rt1719_update_source_pdos(data); + rt1719_update_pwr_opmode(data); + } + + return 0; +} + +#define RT1719_PSEL_CAPINFO(_lomask, _milliwatt, _himask, _milliamp) { \ + .lomask = _lomask, \ + .milliwatt = _milliwatt, \ + .himask = _himask, \ + .milliamp = _milliamp, \ +} + +static const struct rt1719_psel_cap rt1719_psel_caps[] = { + RT1719_PSEL_CAPINFO(0x18, 75000, 0x10, 5000), + RT1719_PSEL_CAPINFO(0x18, 60000, 0x10, 4500), + RT1719_PSEL_CAPINFO(0x18, 45000, 0x10, 4000), + RT1719_PSEL_CAPINFO(0x18, 30000, 0x10, 3500), + RT1719_PSEL_CAPINFO(0x18, 25000, 0x10, 3000), + RT1719_PSEL_CAPINFO(0x18, 20000, 0x10, 2500), + RT1719_PSEL_CAPINFO(0x18, 15000, 0x10, 2000), + RT1719_PSEL_CAPINFO(0x18, 10000, 0x10, 1000), + RT1719_PSEL_CAPINFO(0x1C, 60000, 0x1F, 5000), + RT1719_PSEL_CAPINFO(0x1C, 45000, 0x1F, 4500), + RT1719_PSEL_CAPINFO(0x1C, 30000, 0x1F, 4000), + RT1719_PSEL_CAPINFO(0x1C, 24000, 0x1F, 3500), + RT1719_PSEL_CAPINFO(0x1C, 15000, 0x1F, 3000), + RT1719_PSEL_CAPINFO(0x1C, 10000, 0x1F, 2500), + RT1719_PSEL_CAPINFO(0x0C, 60000, 0x1F, 2000), + RT1719_PSEL_CAPINFO(0x0C, 45000, 0x1F, 1000), + RT1719_PSEL_CAPINFO(0x0C, 36000, 0x08, 5000), + RT1719_PSEL_CAPINFO(0x0C, 30000, 0x08, 4500), + RT1719_PSEL_CAPINFO(0x0C, 24000, 0x08, 4000), + RT1719_PSEL_CAPINFO(0x0C, 15000, 0x08, 3500), + RT1719_PSEL_CAPINFO(0x0C, 10000, 0x08, 3000), + RT1719_PSEL_CAPINFO(0x1E, 45000, 0x08, 2500), + RT1719_PSEL_CAPINFO(0x1E, 36000, 0x08, 2000), + RT1719_PSEL_CAPINFO(0x1E, 27000, 0x08, 1500), + RT1719_PSEL_CAPINFO(0x1E, 20000, 0x08, 1000), + RT1719_PSEL_CAPINFO(0x1E, 15000, 0x0F, 5000), + RT1719_PSEL_CAPINFO(0x1E, 9000, 0x0F, 4500), + RT1719_PSEL_CAPINFO(0x0E, 45000, 0x0F, 4000), + RT1719_PSEL_CAPINFO(0x0E, 36000, 0x0F, 3500), + RT1719_PSEL_CAPINFO(0x0E, 27000, 0x0F, 3000), + RT1719_PSEL_CAPINFO(0x0E, 20000, 0x0F, 2500), + RT1719_PSEL_CAPINFO(0x0E, 15000, 0x0F, 2000), + RT1719_PSEL_CAPINFO(0x0E, 9000, 0x0F, 1500), + RT1719_PSEL_CAPINFO(0x06, 45000, 0x0F, 1000), + RT1719_PSEL_CAPINFO(0x06, 36000, 0x0F, 500), + RT1719_PSEL_CAPINFO(0x06, 27000, 0x04, 5000), + RT1719_PSEL_CAPINFO(0x06, 24000, 0x04, 4500), + RT1719_PSEL_CAPINFO(0x06, 18000, 0x04, 4000), + RT1719_PSEL_CAPINFO(0x06, 12000, 0x04, 3500), + RT1719_PSEL_CAPINFO(0x06, 9000, 0x04, 3000), + RT1719_PSEL_CAPINFO(0x1F, 25000, 0x04, 2500), + RT1719_PSEL_CAPINFO(0x1F, 20000, 0x04, 2000), + RT1719_PSEL_CAPINFO(0x1F, 15000, 0x04, 1500), + RT1719_PSEL_CAPINFO(0x1F, 10000, 0x04, 1000), + RT1719_PSEL_CAPINFO(0x1F, 7500, 0x07, 5000), + RT1719_PSEL_CAPINFO(0x0F, 25000, 0x07, 4500), + RT1719_PSEL_CAPINFO(0x0F, 20000, 0x07, 4000), + RT1719_PSEL_CAPINFO(0x0F, 15000, 0x07, 3500), + RT1719_PSEL_CAPINFO(0x0F, 10000, 0x07, 3000), + RT1719_PSEL_CAPINFO(0x0F, 7500, 0x07, 2500), + RT1719_PSEL_CAPINFO(0x07, 25000, 0x07, 2000), + RT1719_PSEL_CAPINFO(0x07, 20000, 0x07, 1500), + RT1719_PSEL_CAPINFO(0x07, 15000, 0x07, 1000), + RT1719_PSEL_CAPINFO(0x07, 10000, 0x07, 500), + RT1719_PSEL_CAPINFO(0x07, 7500, 0x03, 5000), + RT1719_PSEL_CAPINFO(0x03, 25000, 0x03, 4500), + RT1719_PSEL_CAPINFO(0x03, 20000, 0x03, 4000), + RT1719_PSEL_CAPINFO(0x03, 15000, 0x03, 3500), + RT1719_PSEL_CAPINFO(0x03, 10000, 0x03, 3000), + RT1719_PSEL_CAPINFO(0x03, 7500, 0x03, 2500), + RT1719_PSEL_CAPINFO(0x01, 15000, 0x03, 2000), + RT1719_PSEL_CAPINFO(0x01, 10000, 0x03, 1500), + RT1719_PSEL_CAPINFO(0x01, 7500, 0x03, 1000), + RT1719_PSEL_CAPINFO(0x01, 2500, 0x03, 500) +}; + +static u16 rt1719_gen_snkcap_by_current(const struct rt1719_psel_cap *psel_cap, + enum rt1719_snkcap capsel) +{ + u16 cap = RT1719_PSEL_SUPPORT; + + if (!(psel_cap->himask & BIT(capsel))) + return 0; + + cap |= psel_cap->milliamp / 10; + return cap; +} + +static u16 rt1719_gen_snkcap_by_watt(const struct rt1719_psel_cap *psel_cap, + enum rt1719_snkcap capsel) +{ + u32 volt_div[RT1719_MAX_SNKCAP] = { 5, 9, 12, 15, 20 }; + u16 cap = RT1719_PSEL_SUPPORT; + + if (!(psel_cap->lomask & BIT(capsel))) + return 0; + + cap |= min(psel_cap->milliwatt / volt_div[capsel], (u32)5000) / 10; + return cap; +} + +static u16 rt1719_gen_snkcap(unsigned int pselinfo, enum rt1719_snkcap capsel) +{ + int psel = FIELD_GET(RT1719_LATPSEL_MASK, pselinfo); + const struct rt1719_psel_cap *psel_cap; + bool by_current = false; + + if (pselinfo & RT1719_TBLSEL_MASK) + by_current = true; + + psel_cap = rt1719_psel_caps + psel; + if (by_current) + return rt1719_gen_snkcap_by_current(psel_cap, capsel); + + return rt1719_gen_snkcap_by_watt(psel_cap, capsel); +} + +static int rt1719_get_caps(struct rt1719_data *data) +{ + unsigned int pselinfo, usbinfo; + int i, ret; + + ret = regmap_read(data->regmap, RT1719_REG_PSELINFO, &pselinfo); + ret |= regmap_read(data->regmap, RT1719_REG_USBSETINFO, &usbinfo); + if (ret) + return ret; + + for (i = 0; i < RT1719_MAX_SNKCAP; i++) + data->snkcaps[i] = rt1719_gen_snkcap(pselinfo, i); + + usbinfo = FIELD_GET(RT1719_USBINFO_MASK, usbinfo); + if (usbinfo == RT1719_USB_DFPUFP) + data->drswap_support = true; + + return 0; +} + +static int rt1719_check_exist(struct rt1719_data *data) +{ + u16 pid; + int ret; + + ret = rt1719_read16(data, RT1719_REG_VENID, &pid); + if (ret) + return ret; + + if (pid != RT1719_UNIQUE_PID) { + dev_err(data->dev, "Incorrect PID 0x%04x\n", pid); + return -ENODEV; + } + + return 0; +} + +static const struct regmap_config rt1719_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, +}; + +static int rt1719_probe(struct i2c_client *i2c) +{ + struct rt1719_data *data; + struct fwnode_handle *fwnode; + struct typec_capability typec_cap = { }; + int ret; + + data = devm_kzalloc(&i2c->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &i2c->dev; + init_completion(&data->req_completion); + + data->regmap = devm_regmap_init_i2c(i2c, &rt1719_regmap_config); + if (IS_ERR(data->regmap)) { + ret = PTR_ERR(data->regmap); + dev_err(&i2c->dev, "Failed to init regmap (%d)\n", ret); + return ret; + } + + ret = rt1719_check_exist(data); + if (ret) + return ret; + + ret = rt1719_get_caps(data); + if (ret) + return ret; + + fwnode = device_get_named_child_node(&i2c->dev, "connector"); + if (!fwnode) + return -ENODEV; + + data->role_sw = fwnode_usb_role_switch_get(fwnode); + if (IS_ERR(data->role_sw)) { + ret = PTR_ERR(data->role_sw); + dev_err(&i2c->dev, "Failed to get usb role switch (%d)\n", ret); + goto err_fwnode_put; + } + + ret = devm_rt1719_psy_register(data); + if (ret) { + dev_err(&i2c->dev, "Failed to register psy (%d)\n", ret); + goto err_role_put; + } + + typec_cap.revision = USB_TYPEC_REV_1_2; + typec_cap.pd_revision = 0x300; /* USB-PD spec release 3.0 */ + typec_cap.type = TYPEC_PORT_SNK; + typec_cap.data = TYPEC_PORT_DRD; + typec_cap.ops = &rt1719_port_ops; + typec_cap.fwnode = fwnode; + typec_cap.driver_data = data; + typec_cap.accessory[0] = TYPEC_ACCESSORY_DEBUG; + + data->partner_desc.identity = &data->partner_ident; + + data->port = typec_register_port(&i2c->dev, &typec_cap); + if (IS_ERR(data->port)) { + ret = PTR_ERR(data->port); + dev_err(&i2c->dev, "Failed to register typec port (%d)\n", ret); + goto err_role_put; + } + + ret = rt1719_init_attach_state(data); + if (ret) { + dev_err(&i2c->dev, "Failed to init attach state (%d)\n", ret); + goto err_role_put; + } + + ret = rt1719_irq_init(data); + if (ret) { + dev_err(&i2c->dev, "Failed to init irq\n"); + goto err_role_put; + } + + fwnode_handle_put(fwnode); + + i2c_set_clientdata(i2c, data); + + return 0; + +err_role_put: + usb_role_switch_put(data->role_sw); +err_fwnode_put: + fwnode_handle_put(fwnode); + + return ret; +} + +static int rt1719_remove(struct i2c_client *i2c) +{ + struct rt1719_data *data = i2c_get_clientdata(i2c); + + typec_unregister_port(data->port); + usb_role_switch_put(data->role_sw); + + return 0; +} + +static const struct of_device_id __maybe_unused rt1719_device_table[] = { + { .compatible = "richtek,rt1719", }, + { } +}; +MODULE_DEVICE_TABLE(of, rt1719_device_table); + +static struct i2c_driver rt1719_driver = { + .driver = { + .name = "rt1719", + .of_match_table = rt1719_device_table, + }, + .probe_new = rt1719_probe, + .remove = rt1719_remove, +}; +module_i2c_driver(rt1719_driver); + +MODULE_AUTHOR("ChiYuan Huang <[email protected]>"); +MODULE_DESCRIPTION("Richtek RT1719 Sink Only USBPD Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 5fce795b69c7..3bc2f4ebd1fe 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -5928,7 +5928,6 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode) { const char *opmode_str; - const char *cap_str; int ret; u32 mw, frs_current; @@ -5944,23 +5943,10 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, */ fw_devlink_purge_absent_suppliers(fwnode); - /* USB data support is optional */ - ret = fwnode_property_read_string(fwnode, "data-role", &cap_str); - if (ret == 0) { - ret = typec_find_port_data_role(cap_str); - if (ret < 0) - return ret; - port->typec_caps.data = ret; - } - - ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); + ret = typec_get_fw_cap(&port->typec_caps, fwnode); if (ret < 0) return ret; - ret = typec_find_port_power_role(cap_str); - if (ret < 0) - return ret; - port->typec_caps.type = ret; port->port_type = port->typec_caps.type; port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable"); @@ -5997,14 +5983,6 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, if (port->port_type == TYPEC_PORT_SRC) return 0; - /* Get the preferred power role for DRP */ - ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str); - if (ret < 0) - return ret; - - port->typec_caps.prefer_role = typec_find_power_role(cap_str); - if (port->typec_caps.prefer_role < 0) - return -EINVAL; sink: port->self_powered = fwnode_property_read_bool(fwnode, "self-powered"); diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 7ffcda94d323..16b4560216ba 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -256,6 +256,10 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) typec_set_pwr_opmode(tps->port, mode); typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(status)); typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(status)); + if (TPS_STATUS_TO_UPSIDE_DOWN(status)) + typec_set_orientation(tps->port, TYPEC_ORIENTATION_REVERSE); + else + typec_set_orientation(tps->port, TYPEC_ORIENTATION_NORMAL); tps6598x_set_data_role(tps, TPS_STATUS_TO_TYPEC_DATAROLE(status), true); tps->partner = typec_register_partner(tps->port, &desc); @@ -278,6 +282,7 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status) typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB); typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(status)); typec_set_vconn_role(tps->port, TPS_STATUS_TO_TYPEC_VCONN(status)); + typec_set_orientation(tps->port, TYPEC_ORIENTATION_NONE); tps6598x_set_data_role(tps, TPS_STATUS_TO_TYPEC_DATAROLE(status), false); power_supply_changed(tps->psy); diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 3dae84c524fb..527857549d69 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -17,6 +17,7 @@ /* TPS_REG_STATUS bits */ #define TPS_STATUS_PLUG_PRESENT BIT(0) #define TPS_STATUS_PLUG_UPSIDE_DOWN BIT(4) +#define TPS_STATUS_TO_UPSIDE_DOWN(s) (!!((s) & TPS_STATUS_PLUG_UPSIDE_DOWN)) #define TPS_STATUS_PORTROLE BIT(5) #define TPS_STATUS_TO_TYPEC_PORTROLE(s) (!!((s) & TPS_STATUS_PORTROLE)) #define TPS_STATUS_DATAROLE BIT(6) diff --git a/drivers/usb/typec/wusb3801.c b/drivers/usb/typec/wusb3801.c new file mode 100644 index 000000000000..e63509f8b01e --- /dev/null +++ b/drivers/usb/typec/wusb3801.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Willsemi WUSB3801 Type-C port controller driver + * + * Copyright (C) 2022 Samuel Holland <[email protected]> + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/typec.h> + +#define WUSB3801_REG_DEVICE_ID 0x01 +#define WUSB3801_REG_CTRL0 0x02 +#define WUSB3801_REG_INT 0x03 +#define WUSB3801_REG_STAT 0x04 +#define WUSB3801_REG_CTRL1 0x05 +#define WUSB3801_REG_TEST00 0x06 +#define WUSB3801_REG_TEST01 0x07 +#define WUSB3801_REG_TEST02 0x08 +#define WUSB3801_REG_TEST03 0x09 +#define WUSB3801_REG_TEST04 0x0a +#define WUSB3801_REG_TEST05 0x0b +#define WUSB3801_REG_TEST06 0x0c +#define WUSB3801_REG_TEST07 0x0d +#define WUSB3801_REG_TEST08 0x0e +#define WUSB3801_REG_TEST09 0x0f +#define WUSB3801_REG_TEST0A 0x10 +#define WUSB3801_REG_TEST0B 0x11 +#define WUSB3801_REG_TEST0C 0x12 +#define WUSB3801_REG_TEST0D 0x13 +#define WUSB3801_REG_TEST0E 0x14 +#define WUSB3801_REG_TEST0F 0x15 +#define WUSB3801_REG_TEST10 0x16 +#define WUSB3801_REG_TEST11 0x17 +#define WUSB3801_REG_TEST12 0x18 + +#define WUSB3801_DEVICE_ID_VERSION_ID GENMASK(7, 3) +#define WUSB3801_DEVICE_ID_VENDOR_ID GENMASK(2, 0) + +#define WUSB3801_CTRL0_DIS_ACC_SUPPORT BIT(7) +#define WUSB3801_CTRL0_TRY GENMASK(6, 5) +#define WUSB3801_CTRL0_TRY_NONE (0x0 << 5) +#define WUSB3801_CTRL0_TRY_SNK (0x1 << 5) +#define WUSB3801_CTRL0_TRY_SRC (0x2 << 5) +#define WUSB3801_CTRL0_CURRENT GENMASK(4, 3) /* SRC */ +#define WUSB3801_CTRL0_CURRENT_DEFAULT (0x0 << 3) +#define WUSB3801_CTRL0_CURRENT_1_5A (0x1 << 3) +#define WUSB3801_CTRL0_CURRENT_3_0A (0x2 << 3) +#define WUSB3801_CTRL0_ROLE GENMASK(2, 1) +#define WUSB3801_CTRL0_ROLE_SNK (0x0 << 1) +#define WUSB3801_CTRL0_ROLE_SRC (0x1 << 1) +#define WUSB3801_CTRL0_ROLE_DRP (0x2 << 1) +#define WUSB3801_CTRL0_INT_MASK BIT(0) + +#define WUSB3801_INT_ATTACHED BIT(0) +#define WUSB3801_INT_DETACHED BIT(1) + +#define WUSB3801_STAT_VBUS_DETECTED BIT(7) +#define WUSB3801_STAT_CURRENT GENMASK(6, 5) /* SNK */ +#define WUSB3801_STAT_CURRENT_STANDBY (0x0 << 5) +#define WUSB3801_STAT_CURRENT_DEFAULT (0x1 << 5) +#define WUSB3801_STAT_CURRENT_1_5A (0x2 << 5) +#define WUSB3801_STAT_CURRENT_3_0A (0x3 << 5) +#define WUSB3801_STAT_PARTNER GENMASK(4, 2) +#define WUSB3801_STAT_PARTNER_STANDBY (0x0 << 2) +#define WUSB3801_STAT_PARTNER_SNK (0x1 << 2) +#define WUSB3801_STAT_PARTNER_SRC (0x2 << 2) +#define WUSB3801_STAT_PARTNER_AUDIO (0x3 << 2) +#define WUSB3801_STAT_PARTNER_DEBUG (0x4 << 2) +#define WUSB3801_STAT_ORIENTATION GENMASK(1, 0) +#define WUSB3801_STAT_ORIENTATION_NONE (0x0 << 0) +#define WUSB3801_STAT_ORIENTATION_CC1 (0x1 << 0) +#define WUSB3801_STAT_ORIENTATION_CC2 (0x2 << 0) +#define WUSB3801_STAT_ORIENTATION_BOTH (0x3 << 0) + +#define WUSB3801_CTRL1_SM_RESET BIT(0) + +#define WUSB3801_TEST01_VENDOR_SUB_ID (BIT(8) | BIT(6)) + +#define WUSB3801_TEST02_FORCE_ERR_RCY BIT(8) + +#define WUSB3801_TEST0A_WAIT_VBUS BIT(5) + +struct wusb3801 { + struct typec_capability cap; + struct device *dev; + struct typec_partner *partner; + struct typec_port *port; + struct regmap *regmap; + struct regulator *vbus_supply; + unsigned int partner_type; + enum typec_port_type port_type; + enum typec_pwr_opmode pwr_opmode; + bool vbus_on; +}; + +static enum typec_role wusb3801_get_default_role(struct wusb3801 *wusb3801) +{ + switch (wusb3801->port_type) { + case TYPEC_PORT_SRC: + return TYPEC_SOURCE; + case TYPEC_PORT_SNK: + return TYPEC_SINK; + case TYPEC_PORT_DRP: + default: + if (wusb3801->cap.prefer_role == TYPEC_SOURCE) + return TYPEC_SOURCE; + return TYPEC_SINK; + } +} + +static int wusb3801_map_port_type(enum typec_port_type type) +{ + switch (type) { + case TYPEC_PORT_SRC: + return WUSB3801_CTRL0_ROLE_SRC; + case TYPEC_PORT_SNK: + return WUSB3801_CTRL0_ROLE_SNK; + case TYPEC_PORT_DRP: + default: + return WUSB3801_CTRL0_ROLE_DRP; + } +} + +static int wusb3801_map_pwr_opmode(enum typec_pwr_opmode mode) +{ + switch (mode) { + case TYPEC_PWR_MODE_USB: + default: + return WUSB3801_CTRL0_CURRENT_DEFAULT; + case TYPEC_PWR_MODE_1_5A: + return WUSB3801_CTRL0_CURRENT_1_5A; + case TYPEC_PWR_MODE_3_0A: + return WUSB3801_CTRL0_CURRENT_3_0A; + } +} + +static unsigned int wusb3801_map_try_role(int role) +{ + switch (role) { + case TYPEC_NO_PREFERRED_ROLE: + default: + return WUSB3801_CTRL0_TRY_NONE; + case TYPEC_SINK: + return WUSB3801_CTRL0_TRY_SNK; + case TYPEC_SOURCE: + return WUSB3801_CTRL0_TRY_SRC; + } +} + +static enum typec_orientation wusb3801_unmap_orientation(unsigned int status) +{ + switch (status & WUSB3801_STAT_ORIENTATION) { + case WUSB3801_STAT_ORIENTATION_NONE: + case WUSB3801_STAT_ORIENTATION_BOTH: + default: + return TYPEC_ORIENTATION_NONE; + case WUSB3801_STAT_ORIENTATION_CC1: + return TYPEC_ORIENTATION_NORMAL; + case WUSB3801_STAT_ORIENTATION_CC2: + return TYPEC_ORIENTATION_REVERSE; + } +} + +static enum typec_pwr_opmode wusb3801_unmap_pwr_opmode(unsigned int status) +{ + switch (status & WUSB3801_STAT_CURRENT) { + case WUSB3801_STAT_CURRENT_STANDBY: + case WUSB3801_STAT_CURRENT_DEFAULT: + default: + return TYPEC_PWR_MODE_USB; + case WUSB3801_STAT_CURRENT_1_5A: + return TYPEC_PWR_MODE_1_5A; + case WUSB3801_STAT_CURRENT_3_0A: + return TYPEC_PWR_MODE_3_0A; + } +} + +static int wusb3801_try_role(struct typec_port *port, int role) +{ + struct wusb3801 *wusb3801 = typec_get_drvdata(port); + + return regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0, + WUSB3801_CTRL0_TRY, + wusb3801_map_try_role(role)); +} + +static int wusb3801_port_type_set(struct typec_port *port, + enum typec_port_type type) +{ + struct wusb3801 *wusb3801 = typec_get_drvdata(port); + int ret; + + ret = regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0, + WUSB3801_CTRL0_ROLE, + wusb3801_map_port_type(type)); + if (ret) + return ret; + + wusb3801->port_type = type; + + return 0; +} + +static const struct typec_operations wusb3801_typec_ops = { + .try_role = wusb3801_try_role, + .port_type_set = wusb3801_port_type_set, +}; + +static int wusb3801_hw_init(struct wusb3801 *wusb3801) +{ + return regmap_write(wusb3801->regmap, WUSB3801_REG_CTRL0, + wusb3801_map_try_role(wusb3801->cap.prefer_role) | + wusb3801_map_pwr_opmode(wusb3801->pwr_opmode) | + wusb3801_map_port_type(wusb3801->port_type)); +} + +static void wusb3801_hw_update(struct wusb3801 *wusb3801) +{ + struct typec_port *port = wusb3801->port; + struct device *dev = wusb3801->dev; + unsigned int partner_type, status; + int ret; + + ret = regmap_read(wusb3801->regmap, WUSB3801_REG_STAT, &status); + if (ret) { + dev_warn(dev, "Failed to read port status: %d\n", ret); + status = 0; + } + dev_dbg(dev, "status = 0x%02x\n", status); + + partner_type = status & WUSB3801_STAT_PARTNER; + + if (partner_type == WUSB3801_STAT_PARTNER_SNK) { + if (!wusb3801->vbus_on) { + ret = regulator_enable(wusb3801->vbus_supply); + if (ret) + dev_warn(dev, "Failed to enable VBUS: %d\n", ret); + wusb3801->vbus_on = true; + } + } else { + if (wusb3801->vbus_on) { + regulator_disable(wusb3801->vbus_supply); + wusb3801->vbus_on = false; + } + } + + if (partner_type != wusb3801->partner_type) { + struct typec_partner_desc desc = {}; + enum typec_data_role data_role; + enum typec_role pwr_role = wusb3801_get_default_role(wusb3801); + + switch (partner_type) { + case WUSB3801_STAT_PARTNER_STANDBY: + break; + case WUSB3801_STAT_PARTNER_SNK: + pwr_role = TYPEC_SOURCE; + break; + case WUSB3801_STAT_PARTNER_SRC: + pwr_role = TYPEC_SINK; + break; + case WUSB3801_STAT_PARTNER_AUDIO: + desc.accessory = TYPEC_ACCESSORY_AUDIO; + break; + case WUSB3801_STAT_PARTNER_DEBUG: + desc.accessory = TYPEC_ACCESSORY_DEBUG; + break; + } + + if (wusb3801->partner) { + typec_unregister_partner(wusb3801->partner); + wusb3801->partner = NULL; + } + + if (partner_type != WUSB3801_STAT_PARTNER_STANDBY) { + wusb3801->partner = typec_register_partner(port, &desc); + if (IS_ERR(wusb3801->partner)) + dev_err(dev, "Failed to register partner: %ld\n", + PTR_ERR(wusb3801->partner)); + } + + data_role = pwr_role == TYPEC_SOURCE ? TYPEC_HOST : TYPEC_DEVICE; + typec_set_data_role(port, data_role); + typec_set_pwr_role(port, pwr_role); + typec_set_vconn_role(port, pwr_role); + } + + typec_set_pwr_opmode(wusb3801->port, + partner_type == WUSB3801_STAT_PARTNER_SRC + ? wusb3801_unmap_pwr_opmode(status) + : wusb3801->pwr_opmode); + typec_set_orientation(wusb3801->port, + wusb3801_unmap_orientation(status)); + + wusb3801->partner_type = partner_type; +} + +static irqreturn_t wusb3801_irq(int irq, void *data) +{ + struct wusb3801 *wusb3801 = data; + unsigned int dummy; + + /* + * The interrupt register must be read in order to clear the IRQ, + * but all of the useful information is in the status register. + */ + regmap_read(wusb3801->regmap, WUSB3801_REG_INT, &dummy); + + wusb3801_hw_update(wusb3801); + + return IRQ_HANDLED; +} + +static const struct regmap_config config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = WUSB3801_REG_TEST12, +}; + +static int wusb3801_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *connector; + struct wusb3801 *wusb3801; + const char *cap_str; + int ret; + + wusb3801 = devm_kzalloc(dev, sizeof(*wusb3801), GFP_KERNEL); + if (!wusb3801) + return -ENOMEM; + + i2c_set_clientdata(client, wusb3801); + + wusb3801->dev = dev; + + wusb3801->regmap = devm_regmap_init_i2c(client, &config); + if (IS_ERR(wusb3801->regmap)) + return PTR_ERR(wusb3801->regmap); + + wusb3801->vbus_supply = devm_regulator_get(dev, "vbus"); + if (IS_ERR(wusb3801->vbus_supply)) + return PTR_ERR(wusb3801->vbus_supply); + + connector = device_get_named_child_node(dev, "connector"); + if (!connector) + return -ENODEV; + + ret = typec_get_fw_cap(&wusb3801->cap, connector); + if (ret) + goto err_put_connector; + wusb3801->port_type = wusb3801->cap.type; + + ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str); + if (ret) + goto err_put_connector; + + ret = typec_find_pwr_opmode(cap_str); + if (ret < 0 || ret == TYPEC_PWR_MODE_PD) + goto err_put_connector; + wusb3801->pwr_opmode = ret; + + /* Initialize the hardware with the devicetree settings. */ + ret = wusb3801_hw_init(wusb3801); + if (ret) + return ret; + + wusb3801->cap.revision = USB_TYPEC_REV_1_2; + wusb3801->cap.accessory[0] = TYPEC_ACCESSORY_AUDIO; + wusb3801->cap.accessory[1] = TYPEC_ACCESSORY_DEBUG; + wusb3801->cap.orientation_aware = true; + wusb3801->cap.driver_data = wusb3801; + wusb3801->cap.ops = &wusb3801_typec_ops; + + wusb3801->port = typec_register_port(dev, &wusb3801->cap); + if (IS_ERR(wusb3801->port)) { + ret = PTR_ERR(wusb3801->port); + goto err_put_connector; + } + + /* Initialize the port attributes from the hardware state. */ + wusb3801_hw_update(wusb3801); + + ret = request_threaded_irq(client->irq, NULL, wusb3801_irq, + IRQF_ONESHOT, dev_name(dev), wusb3801); + if (ret) + goto err_unregister_port; + + fwnode_handle_put(connector); + + return 0; + +err_unregister_port: + typec_unregister_port(wusb3801->port); +err_put_connector: + fwnode_handle_put(connector); + + return ret; +} + +static int wusb3801_remove(struct i2c_client *client) +{ + struct wusb3801 *wusb3801 = i2c_get_clientdata(client); + + free_irq(client->irq, wusb3801); + + if (wusb3801->partner) + typec_unregister_partner(wusb3801->partner); + typec_unregister_port(wusb3801->port); + + if (wusb3801->vbus_on) + regulator_disable(wusb3801->vbus_supply); + + return 0; +} + +static const struct of_device_id wusb3801_of_match[] = { + { .compatible = "willsemi,wusb3801" }, + {} +}; +MODULE_DEVICE_TABLE(of, wusb3801_of_match); + +static struct i2c_driver wusb3801_driver = { + .probe_new = wusb3801_probe, + .remove = wusb3801_remove, + .driver = { + .name = "wusb3801", + .of_match_table = wusb3801_of_match, + }, +}; + +module_i2c_driver(wusb3801_driver); + +MODULE_AUTHOR("Samuel Holland <[email protected]>"); +MODULE_DESCRIPTION("Willsemi WUSB3801 Type-C port controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/vudc_main.c b/drivers/usb/usbip/vudc_main.c index 678faa82598c..d43252b77efd 100644 --- a/drivers/usb/usbip/vudc_main.c +++ b/drivers/usb/usbip/vudc_main.c @@ -26,7 +26,7 @@ static struct platform_driver vudc_driver = { }, }; -static struct list_head vudc_devices = LIST_HEAD_INIT(vudc_devices); +static LIST_HEAD(vudc_devices); static int __init init(void) { diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index 7ba45a97eeae..fdf737d48b3b 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -295,6 +295,9 @@ int typec_set_mode(struct typec_port *port, int mode); void *typec_get_drvdata(struct typec_port *port); +int typec_get_fw_cap(struct typec_capability *cap, + struct fwnode_handle *fwnode); + int typec_find_pwr_opmode(const char *name); int typec_find_orientation(const char *name); int typec_find_port_power_role(const char *name); |